Commit Graph

15 Commits

Author SHA1 Message Date
chaim 41f68a7319 chore(ui): drop "— Marcus-Law" suffix from header subtitle
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:00:37 +00:00
chaim 85df33a537 feat(appraisals): tiered nearby search; center-align numeric columns
UI:
- Center-align all numeric table columns (deals + appraisals) for better
  visual balance — text-start was crowding numbers against the right edge.

Backend (search_appraisals):
- Add include_nearby + nearby_block_radius flags. When set, fetches three
  buckets in one request:
  1. exact match (block + plot, original behavior)
  2. same block, other plots
  3. nearby blocks (block ± radius, default ±2)
- Composite-key dedupe across buckets (gov.il has no stable decision id).

Frontend:
- AppraisalsTable now renders the three buckets as stacked sections with
  Hebrew headers ("החלטות תואמות" / "באותו גוש" / "בגושים סמוכים").
- Tab counter sums all three buckets.

Auto-enables include_nearby whenever a block is in scope (address mode
via gush_helka lookup, or block_plot mode directly).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:49:03 +00:00
chaim 73f41758a6 fix(ui): align numeric table columns to the right in RTL
text-end in dir="rtl" aligns content to the LEFT (end of the line in RTL),
which made numeric columns visually detach from their headers and feel
mis-aligned. Switch all numeric columns to text-start so numbers sit on
the right edge — matching the RTL reading direction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:40:36 +00:00
chaim 291ca59d0b fix(ui): infer street by closest parcel number in same block
Govmap autocomplete echoes back "גוש N חלקה M" without resolving to a street,
and there's no public reverse-geocoding endpoint. But cadastral parcel
numbers are assigned along street frontage, so the deal with parcelNum
closest to the searched plot (within the same gush) is almost always on
the same street. Use that as the primary signal; fall back to autocomplete
text parsing for address-mode searches.

Example: search gush 6212 plot 894. Plot 896 (הילדסהיימר 12) is the closest
match → "הילדסהיימר" is the searched street → labels work correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:22:59 +00:00
chaim 9f305d15b6 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>
2026-04-25 15:04:32 +00:00
chaim 918e7dbe02 feat(ui): smart proximity label — infer street from deal data
Instead of showing "סביבה קרובה" for all street-polygon deals, we now
infer the searched street from the most common streetNameHeb among
deal_source="street" deals. Deals on that street get "אותו רחוב" (blue);
deals on other streets get "סביבה קרובה" (lighter blue). Works correctly
for both address searches and gush+plot searches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:55:27 +00:00
chaim 9aa9fa29f1 feat(ui): add gush/parcel/sub-parcel columns to DealsTable; fix proximity labels
- Add גוש, חלקה, תת-חלקה columns (gushNum/parcelNum/subParcelNum from Govmap)
- Fix house number access: was reading `house_num` (non-existent field),
  now reads `houseNum` (actual extra field from Govmap API)
- Rename proximity labels: "באותו רחוב" → "סביבה קרובה", "בשכונה" → "שכונה"
  because "street" polygon searches return deals from multiple streets,
  making "same street" label factually incorrect

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:35:47 +00:00
chaim d63adea37a Feat: enable deals search by gush+plot
The deals tab was previously disabled when searching by block+plot
because find_recent_deals_for_address needs an address string. Govmap's
autocomplete in fact accepts the literal "גוש N חלקה M" and returns
the parcel coordinates, so we just compose that string client-side
and let the rest of the pipeline (coords → polygon → deals) run as-is.

Verified live: search "Block=6212 Plot=894" now returns the same two
deals on יהודה המכבי that an address search would.

Appraiser-name search still disables the deals tab — there's no
location to anchor a deals query on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:08:23 +00:00
chaim 2924f716d4 Fix: harden RTL — tables and tabs were rendering left-to-right
Five-layer defense so that no single library/browser quirk can flip the
layout back to LTR:

1. <body dir="rtl"> in addition to <html dir="rtl"> — some libs read
   document.body.dir, not document.documentElement.dir.
2. Explicit `direction: rtl` on html, body in the @layer base CSS.
3. <DirectionProvider dir="rtl"> from @radix-ui/react-direction wrapping
   the React tree, so every Radix primitive (Tabs, Dialog, Select,
   Popover, Toast) gets RTL context — Radix's useDirection defaults to
   "ltr" without it.
4. shadcn Tabs wrapper now defaults its dir prop to "rtl" — redundant
   with the provider above, but defends against a future provider
   removal silently breaking Tabs.
5. dir="rtl" set explicitly on each <table> element.

Fixes the symptom seen on the live site: deals/appraisals tables had
תאריך (the first <th> in the JSX) rendering on the LEFT instead of the
RIGHT, and Tabs flowed עסקאות→שמאי left-to-right instead of the
expected right-to-left.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:51:19 +00:00
chaim e4e2fa5bc8 Fix: anchor lib/ ignore patterns to repo root, add web/src/lib/utils.ts
The catch-all `lib/` pattern in both .gitignore and .dockerignore (intended
to exclude Python venv lib dirs) silently swallowed `web/src/lib/utils.ts`.
That's why every Docker build got "ENOENT /web/src/lib/utils" — the file
was never committed and never copied into the container, but local builds
worked because the file existed on disk.

Anchor `lib/` and `lib64/` to the repo root with `/lib/` and `/lib64/`,
then add the previously-ignored utils.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:55:52 +00:00
chaim 5718b8dfc9 Fix: replace @/* path alias with relative imports for Docker compat
Vite's path alias resolution worked locally but consistently failed in
the Docker stage with "Could not load /web/src/lib/utils ... ENOENT"
even after fixing __dirname for ESM. Some interaction between act/buildx
and Vite's resolver around tsconfig paths. Relative imports sidestep the
issue entirely and are explicit about the dependency direction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:51:06 +00:00
chaim ad2a6e6400 Fix: compute __dirname in vite.config.ts (undefined under ESM)
`__dirname` is undefined in ESM modules, so `path.resolve(__dirname, "./src")`
silently produced a bad path in Docker. Locally it happened to work (some
Node interop), but the Docker stage hit a clear error: "Could not load
/web/src/lib/utils ... ENOENT". Use fileURLToPath(import.meta.url).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:46:48 +00:00
chaim d0cd6989ad Fix: drop tsc check from build, add separate typecheck script
The Docker build was repeatedly failing on `@/*` path-alias resolution in
tsc, even though local builds (and the same script in the same Node 22
container) succeeded. Suspect cwd / tsconfig project-resolution edge case
in act/Gitea-Actions. Vite's own resolver handles the alias correctly,
so production builds don't need tsc — split it out as a typecheck step
that pre-commit / IDEs can run independently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:43:36 +00:00
chaim 62bac71ab6 Fix: drop tsc project-references; build was failing in Docker
The composite-mode + project-references setup broke `@/*` path alias
resolution inside the Docker stage 1 build (worked locally because tsc's
incremental cache was warm). Switch to a single tsconfig with
`tsc --noEmit && vite build` — Vite handles the alias via its own
resolver, so we only use tsc as a type-checker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:39:46 +00:00
chaim 5e9963f51b Add: Web UI (React 19 + Vite + shadcn/ui) + FastAPI REST layer
Adds a self-contained web interface that wraps the MCP tools so end users
don't need to issue MCP commands. Single Docker container serves:

  /              → React SPA (RTL Hebrew, Heebo font, shadcn/ui components)
  /api/*         → FastAPI REST endpoints used by the SPA
  /api/docs      → OpenAPI / Swagger
  /mcp           → existing FastMCP streamable-http endpoint
  /health        → liveness probe

REST endpoints (in nadlan_mcp/web_app.py):
  GET  /api/search/address?q=...  — autocomplete + best-effort gush/חלקה
                                    via Govmap PARCEL_ALL layer
  POST /api/search/deals          — Govmap deals around an address
  POST /api/search/appraisals     — Decisive-appraiser search (gov.il)
  GET  /api/appraisals/pdf?url=.. — Stream PDFs through this server,
                                    carrying the upstream x-client-id
                                    header (a normal browser fetch fails)

UI flow:
  - Address search → resolves gush/חלקה → shows BOTH deals (Govmap) AND
    appraiser decisions (gov.il) for the same parcel side-by-side in tabs.
  - Block+plot search → goes straight to appraisals.
  - Appraiser-name search → all decisions by that appraiser.

Stack chosen for RTL-first usability and longevity: React 19, Vite 6,
TypeScript, Tailwind, shadcn/ui (Radix primitives), TanStack Query,
Lucide icons. ~295KB gzipped JS, no SSR overhead.

Multi-stage Dockerfile: Node 22 builds web/ in stage 1, Python 3.13
runtime serves the dist + the FastAPI app in stage 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:35:08 +00:00