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") logger.exception("find_recent_deals_for_address failed")
raise HTTPException(status_code=502, detail=str(e)) 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 search_coords = None
resolved_address = None
try: try:
ar = govmap_client.autocomplete_address(req.address) ar = govmap_client.autocomplete_address(req.address)
if ar.results and ar.results[0].coordinates: if ar.results and ar.results[0].coordinates:
c = ar.results[0].coordinates c = ar.results[0].coordinates
search_coords = {"longitude": c.longitude, "latitude": c.latitude} search_coords = {"longitude": c.longitude, "latitude": c.latitude}
resolved_address = ar.results[0].text
except Exception: except Exception:
pass pass
@@ -198,6 +200,7 @@ def create_app() -> FastAPI:
return { return {
"search": { "search": {
"address": req.address, "address": req.address,
"resolved_address": resolved_address,
"coordinates": search_coords, "coordinates": search_coords,
"years_back": req.years_back, "years_back": req.years_back,
"radius_meters": req.radius_meters, "radius_meters": req.radius_meters,
+4 -1
View File
@@ -204,7 +204,10 @@ export default function App() {
<ErrorBox error={String(deals.error)} /> <ErrorBox error={String(deals.error)} />
)} )}
{deals.data && !deals.isFetching && ( {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 && ( {!deals.isFetching && !deals.error && !deals.data && (
<div className="text-center py-12 text-muted-foreground"> <div className="text-center py-12 text-muted-foreground">
+1
View File
@@ -50,6 +50,7 @@ export interface Deal {
export interface DealsSearchResponse { export interface DealsSearchResponse {
search: { search: {
address: string; address: string;
resolved_address: string | null;
coordinates: Coordinates | null; coordinates: Coordinates | null;
years_back: number; years_back: number;
radius_meters: number; radius_meters: number;
+13 -11
View File
@@ -3,18 +3,20 @@ import { formatCurrencyILS, formatDate, formatNumber } from "../lib/utils";
interface Props { interface Props {
deals: Deal[]; deals: Deal[];
resolvedAddress?: string;
} }
function inferStreetName(deals: Deal[]): string | null { // Extract street name from a Govmap address string like "הילדסהיימר 14, ירושלים".
const freq: Record<string, number> = {}; // Takes all leading tokens that contain no digits.
for (const d of deals) { function streetFromAddress(address: string | undefined): string | null {
if (d.deal_source === "street" && d.streetNameHeb) { if (!address) return null;
freq[d.streetNameHeb] = (freq[d.streetNameHeb] ?? 0) + 1; 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); return parts.length ? parts.join(" ") : null;
if (!entries.length) return null;
return entries.reduce((a, b) => (b[1] > a[1] ? b : a))[0];
} }
function proximityLabel(deal: Deal, inferredStreet: string | null): string { 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"; return "bg-slate-100 text-slate-700";
} }
export function DealsTable({ deals }: Props) { export function DealsTable({ deals, resolvedAddress }: Props) {
if (!deals.length) { if (!deals.length) {
return ( return (
<div className="text-center py-12 text-muted-foreground"> <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 ( return (
<div className="overflow-x-auto rounded-md border"> <div className="overflow-x-auto rounded-md border">