fix(ui): use Govmap resolved address text for street inference, not frequency heuristic

Previous approach: pick most common streetNameHeb among street-source deals.
Problem: a busier nearby street wins even when the searched parcel is on a
quieter street (e.g. רב צעיר beats הילדסהיימר despite being a different street).

Fix: backend now returns resolved_address from Govmap autocomplete (e.g.
"הילדסהיימר 14, ירושלים"). Frontend parses the leading non-numeric tokens
as the street name and uses that as ground truth for proximity labeling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-25 15:04:32 +00:00
parent 918e7dbe02
commit 9f305d15b6
4 changed files with 22 additions and 13 deletions
+4 -1
View File
@@ -165,13 +165,15 @@ def create_app() -> FastAPI:
logger.exception("find_recent_deals_for_address failed")
raise HTTPException(status_code=502, detail=str(e))
# Search-center coords for the map (best-effort).
# Search-center coords + resolved address text for the UI (best-effort).
search_coords = None
resolved_address = None
try:
ar = govmap_client.autocomplete_address(req.address)
if ar.results and ar.results[0].coordinates:
c = ar.results[0].coordinates
search_coords = {"longitude": c.longitude, "latitude": c.latitude}
resolved_address = ar.results[0].text
except Exception:
pass
@@ -198,6 +200,7 @@ def create_app() -> FastAPI:
return {
"search": {
"address": req.address,
"resolved_address": resolved_address,
"coordinates": search_coords,
"years_back": req.years_back,
"radius_meters": req.radius_meters,
+4 -1
View File
@@ -204,7 +204,10 @@ export default function App() {
<ErrorBox error={String(deals.error)} />
)}
{deals.data && !deals.isFetching && (
<DealsTable deals={deals.data.deals} />
<DealsTable
deals={deals.data.deals}
resolvedAddress={deals.data.search.resolved_address ?? undefined}
/>
)}
{!deals.isFetching && !deals.error && !deals.data && (
<div className="text-center py-12 text-muted-foreground">
+1
View File
@@ -50,6 +50,7 @@ export interface Deal {
export interface DealsSearchResponse {
search: {
address: string;
resolved_address: string | null;
coordinates: Coordinates | null;
years_back: number;
radius_meters: number;
+13 -11
View File
@@ -3,18 +3,20 @@ import { formatCurrencyILS, formatDate, formatNumber } from "../lib/utils";
interface Props {
deals: Deal[];
resolvedAddress?: string;
}
function inferStreetName(deals: Deal[]): string | null {
const freq: Record<string, number> = {};
for (const d of deals) {
if (d.deal_source === "street" && d.streetNameHeb) {
freq[d.streetNameHeb] = (freq[d.streetNameHeb] ?? 0) + 1;
// Extract street name from a Govmap address string like "הילדסהיימר 14, ירושלים".
// Takes all leading tokens that contain no digits.
function streetFromAddress(address: string | undefined): string | null {
if (!address) return null;
const tokens = address.split(/[\s,]+/);
const parts: string[] = [];
for (const t of tokens) {
if (/\d/.test(t)) break;
if (t) parts.push(t);
}
}
const entries = Object.entries(freq);
if (!entries.length) return null;
return entries.reduce((a, b) => (b[1] > a[1] ? b : a))[0];
return parts.length ? parts.join(" ") : null;
}
function proximityLabel(deal: Deal, inferredStreet: string | null): string {
@@ -34,7 +36,7 @@ function proximityClass(deal: Deal, inferredStreet: string | null): string {
return "bg-slate-100 text-slate-700";
}
export function DealsTable({ deals }: Props) {
export function DealsTable({ deals, resolvedAddress }: Props) {
if (!deals.length) {
return (
<div className="text-center py-12 text-muted-foreground">
@@ -43,7 +45,7 @@ export function DealsTable({ deals }: Props) {
);
}
const inferredStreet = inferStreetName(deals);
const inferredStreet = streetFromAddress(resolvedAddress);
return (
<div className="overflow-x-auto rounded-md border">