+
    Z3-j$                        ^ RI Ht ^ RIt^ RIHtHt ^ RIHtHt ^RI	H
t
 Rt ! R R]4      tRR	 R
 llt]R8X  d
   ]! 4        R# R# )    )annotationsN)BaseHTTPRequestHandlerThreadingHTTPServer)parse_qsurlparse)search_propertiesu  <!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Montpellier Immobilier Finder</title>
  <style>
    :root { color-scheme: dark; }
    body { font-family: system-ui, sans-serif; margin: 0; background: #0b1220; color: #e8edf7; }
    header { padding: 20px 24px; background: #111a2f; border-bottom: 1px solid #24304d; }
    main { max-width: 1200px; margin: 0 auto; padding: 24px; }
    .card { background: #121c31; border: 1px solid #25314d; border-radius: 14px; padding: 16px; box-shadow: 0 8px 30px rgba(0,0,0,.25); }
    .grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
    label { display: block; font-size: 13px; color: #9db0d0; margin-bottom: 6px; }
    input, select, button { width: 100%; box-sizing: border-box; border-radius: 10px; border: 1px solid #31405f; background: #0b1324; color: #e8edf7; padding: 12px; }
    button { cursor: pointer; background: linear-gradient(180deg, #2f86ff, #245fd0); border: none; font-weight: 700; }
    button:hover { filter: brightness(1.04); }
    .results { margin-top: 18px; display: grid; gap: 12px; }
    .result { border: 1px solid #283552; border-radius: 14px; padding: 14px; background: #0f1728; }
    .meta { display: flex; flex-wrap: wrap; gap: 10px; color: #9eb1d3; font-size: 13px; margin-bottom: 8px; }
    .pill { background: #1a2640; border: 1px solid #2a3960; padding: 4px 8px; border-radius: 999px; }
    .title { margin: 0 0 8px 0; font-size: 18px; }
    .snippet { margin: 0; color: #c7d2e6; line-height: 1.45; }
    a { color: #8cc2ff; text-decoration: none; }
    a:hover { text-decoration: underline; }
    .muted { color: #93a4c3; font-size: 13px; }
    .topbar { display:flex; justify-content: space-between; gap: 16px; align-items: center; margin-bottom: 20px; }
    .status { font-size: 13px; color: #9db0d0; }
    @media (max-width: 960px) { .grid { grid-template-columns: 1fr 1fr; } }
    @media (max-width: 640px) { .grid { grid-template-columns: 1fr; } }
  </style>
</head>
<body>
  <header>
    <strong>Montpellier Immobilier Finder</strong>
    <div class="muted">Recherche locale, max 20 km autour de Montpellier, budget max 400k</div>
  </header>
  <main>
    <div class="card">
      <div class="topbar">
        <div>
          <h1 style="margin:0 0 4px 0;">Chercher des maisons</h1>
          <div class="muted">Le moteur interroge Bing RSS puis trie les résultats par proximité et budget.</div>
        </div>
        <div class="status" id="status">Prêt.</div>
      </div>
      <form id="search-form">
        <div class="grid">
          <div>
            <label>Centre</label>
            <input name="center" value="Montpellier" />
          </div>
          <div>
            <label>Rayon km</label>
            <input name="radius_km" type="number" value="20" min="1" max="100" step="1" />
          </div>
          <div>
            <label>Budget max</label>
            <input name="max_price" type="number" value="400000" min="1" step="1000" />
          </div>
          <div>
            <label>Type</label>
            <select name="property_type">
              <option value="maison" selected>Maison</option>
              <option value="appartement">Appartement</option>
              <option value="terrain">Terrain</option>
            </select>
          </div>
        </div>
        <div style="margin-top: 12px; display:flex; gap: 12px; align-items:center; flex-wrap: wrap;">
          <button type="submit">Lancer la recherche</button>
          <span class="muted">Astuce: laisse le formulaire tel quel pour Montpellier + 20 km + 400k.</span>
        </div>
      </form>
    </div>

    <div id="results" class="results"></div>
  </main>

  <script>
    const form = document.getElementById('search-form');
    const results = document.getElementById('results');
    const status = document.getElementById('status');

    function esc(s) {
      return String(s || '').replace(/[&<>'"]/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
    }

    form.addEventListener('submit', async (e) => {
      e.preventDefault();
      status.textContent = 'Recherche en cours...';
      results.innerHTML = '';
      const params = new URLSearchParams(new FormData(form));
      const resp = await fetch('/api/search?' + params.toString());
      const data = await resp.json();
      status.textContent = `${data.count} résultats, ${data.elapsed_ms} ms`;
      if (data.error) {
        results.innerHTML = `<div class="result"><h3 class="title">Erreur</h3><p class="snippet">${esc(data.error)}</p></div>`;
        return;
      }
      if (!data.results.length) {
        results.innerHTML = `<div class="result"><h3 class="title">Aucun résultat</h3><p class="snippet">Essaie en élargissant le rayon ou le type de bien.</p></div>`;
        return;
      }
      results.innerHTML = data.results.map((r, idx) => `
        <div class="result">
          <div class="meta">
            <span class="pill">#${idx+1}</span>
            <span class="pill">Score ${esc(r.score)}</span>
            <span class="pill">Source ${esc(r.source)}</span>
            ${r.price ? `<span class="pill">${esc(r.price)} €</span>` : ''}
            ${r.distance_km !== null && r.distance_km !== undefined ? `<span class="pill">${esc(r.distance_km)} km</span>` : ''}
            ${r.town ? `<span class="pill">${esc(r.town)}</span>` : ''}
          </div>
          <h3 class="title"><a href="${esc(r.url)}" target="_blank" rel="noopener noreferrer">${esc(r.title)}</a></h3>
          <p class="snippet">${esc(r.snippet || 'Pas de snippet')}</p>
        </div>
      `).join('');
    });
  </script>
</body>
</html>c                  8    ] tR t^tR R ltRR R lltR tRtR# )	Handlerc                    V ^8  d   QhRRRR/# )   formatstrreturnNone )r   s   "=/home/wildlama/real-estate-finder/real_estate_agent/server.py__annotate__Handler.__annotate__   s      #      c                	    R # )Nr   )selfr   argss   &&*r   log_messageHandler.log_message   s    r   c               (    V ^8  d   QhRRRRRRRR/# )r   bodyr   content_typecodeintr   r   r   )r   s   "r   r   r      s*     " "# "S "UX "cg "r   c                	   VP                  R 4      pV P                  V4       V P                  RV4       V P                  R\        \	        V4      4      4       V P                  4        V P                  P                  V4       R# )zutf-8zContent-TypezContent-LengthN)encodesend_responsesend_headerr   lenend_headerswfilewrite)r   r   r   r   encodeds   &&&& r   _sendHandler._send   sf    ++g&4 6)3s7|+<=

!r   c                 	   \        V P                  4      pVP                  R&9   d   V P                  \        4       R# VP                  R8X  d,   V P                  \        P
                  ! RR/4      RR7       R# VP                  R8X  Ed   \        R4      P                  4       p\        VP                  4      pVP                  R	R
.4      ^ ,          p\        VP                  RR.4      ^ ,          4      p\        VP                  RR.4      ^ ,          4      pVP                  RR.4      ^ ,          p\        VP                  RR.4      ^ ,          4      p \        VVVVVR7      p	R	TRTRTRTR\        V	4      R\        \        R4      P                  4       V,
          R,          4      RV	 U
u. uFe  p
RV
P                   RV
P"                  RV
P$                  RV
P&                  RV
P(                  RV
P*                  RV
P,                  RV
P.                  /NKg  	  up
/pV P                  \        P
                  ! VR!R"7      RR7       R# V P                  R#R$R%7       R# u up
i   \0         dc   pR	TRTRTRTR^ R\        \        R4      P                  4       T,
          R,          4      R\3        T4      P4                   R T 2R. /p Rp?LRp?ii ; i)'/Nz/healthokTzapplication/json; charset=utf-8)r   z/api/searchtimecenterMontpellier	radius_km20	max_price400000property_typemaisonlimit)r/   r1   r3   r5   r7   count
elapsed_msi  resultstitleurlsnippetsourcepricetowndistance_kmscoreerrorz: F)ensure_asciiz	Not foundi  )r   >   r,   /index.html)r   pathr)   PAGEjsondumps
__import__r.   r   querygetfloatr   r   r$   roundr;   r<   r=   r>   r?   r@   rA   rB   	Exceptiontype__name__)r   parsedstartparamsr/   r1   r3   r5   r7   r:   rpayloadexcs   &            r   do_GETHandler.do_GET   s   $))$;;..JJt;;)#JJtzz4,/>_J`;;-'v&++-Efll+FZZ=/:1=Ffjjtf=a@AIFJJ{XJ?BCI"JJ
CAFM

7TF3A67E'+!''"/ f#]S\ %F);)@)@)BU)Jd(R"S ")  ")A $QWW!155%qyy$ahh#QWW"AFF)1==#QWW	 ") @ JJtzz'>MnJo

;S
)7   
f#]Q %F);)@)@)BU)Jd(R"SS	 2 232cU;r	
s-   AI$ 0A+II$ I$ $K/AKKr   N)ztext/html; charset=utf-8   )rQ   
__module____qualname____firstlineno__r   r)   rX   __static_attributes__r   r   r   r
   r
      s    ":*r   r
   c               $    V ^8  d   QhRRRRRR/# )r   hostr   portr   r   r   r   )r   s   "r   r   r      s!         r   c                    \        W3\        4      p\        R V  RV 24        VP                  4        VP                  4        R#   \         d     Li ; i  TP                  4        i ; i)zServing on http://:N)r   r
   printserve_foreverKeyboardInterruptserver_close)r`   ra   servers   && r   serveri      sh     $w7F	tfAdV
,- 	   	s#   A AA AA A)__main__)z	127.0.0.1i="  )
__future__r   rH   http.serverr   r   urllib.parser   r   scraperr   rG   r
   ri   rQ   r   r   r   <module>ro      sF    "  C + &yxF*$ F*R z	G r   