# Paradisomatch v2 — Feature Queue

**North Star:** an autonomous, weekly, background-refreshed property search that stays
current across every platform Jonathan curates favorites on, surfaces the **best
price/quality matches** (Pareto frontier, not just a liveability score), and pushes the
insight to him — with the only human touch being an occasional ~2-min re-auth when a
hostile platform's login expires.

**Scope (locked 2026-06-08):** Jonathan's accounts only · platforms = the 6 in-system +
SeLoger + Bien'ici + Rightmove + Green-Acres multi-region · geography = anywhere in
Europe + UK, quality over country.

---

## The honest edge of "zero effort"

Pure hands-off is achievable for **friendly** platforms (saved profile / API token) and
NOT for **hostile** ones (DataDome / Cloudflare actively fight scheduled access). The
design therefore never claims full autonomy — it claims **autonomous-by-default,
alert-on-auth-expiry, never-silent-failure.**

| Camp | Platforms | Refresh reality |
|------|-----------|-----------------|
| Friendly | Green-Acres, Leggett, Immonot, Properstar (API) | Truly hands-off, weekly forever |
| Hostile | Idealista, SeLoger, Bien'ici, Rightmove | ~monthly 2-min re-auth, system tells you exactly when |

---

## The value chain (weakest link caps the answer)

```
1. COMPLETE capture → 2. COMPARABLE data → 3. VALUE metric → 4. FRONTIER → 5. SURFACE
   (all favorites,      (price+land+m²+      (€/m², quality-   (non-dominated  (digest push +
    all platforms)       photos per prop)     per-€100k)        = "best")        Sunday review)
```

Links 1–2 are **preconditions** — value can't be ranked over missing data (this is the
null-bias that already bit us). Links 3–4 are the insight being asked for.

---

## Five robustness principles (baked into every item)

1. **Per-platform isolation** — one platform blocked never fails the run; partial completeness beats total failure.
2. **Auth is the only human touchpoint** — liveness probe per platform; expiry → specific actionable alert; never silent.
3. **Incremental everything** — only new favorites enriched/scored; weekly delta is tiny, cheap, idempotent.
4. **Completeness gate** — frontier ranks only properties meeting the comparable-data floor; incomplete shown separately as "needs enrichment," never silently dropped or falsely ranked.
5. **Push, don't pull** — insight feeds the existing `/sunday-session` step that already runs `pipeline.py`; no new review ritual to maintain.

---

## Staged delivery — the schedule is EARNED, not built day-one

Hard rule (from MEMORY): *test scheduled actions end-to-end NOW, not at fire time; one
live run before any LaunchAgent.* And: *don't pour effort into automation until the value
metric has proven it's worth automating.*

| Stage | Gate to enter | Gate to exit |
|-------|---------------|--------------|
| 0 | — | frontier separates the 20 vetted (or we learn it needs regional comps) |
| 1 | Stage 0 done | full pipeline runs as ONE manual command, output verified |
| 2 | Stage 1 done | each new platform proven manually before it joins |
| 3 | Stage 1 clean ≥2× manually | LaunchAgent + digest verified by one live fire |

---

## Feature queue

Effort: S ≤1h · M ≤½day · L ≥1 session. Link = which chain-link it serves.

### Stage 0 — Immediate insight (validates the metric before we build the machine)

| ID | Feature | Link | Effort | Acceptance check | Status |
|----|---------|------|--------|------------------|--------|
| V2-0a | `frontier.py` — value ratios (€/m² building, €/m² land, CP-per-€100k) over the 20 vetted | 3 | S | each vetted prop prints 3 ratios | ✅ 2026-06-08 |
| V2-0b | Pareto non-dominated set on (price↓, CP↑) | 4 | S | frontier set printed; dominated props flagged | ✅ 2026-06-08 |
| V2-0c | "Best value" toggle in `shortlist.html` (frontier highlighted, ratios shown, listing URL always present) | 5 | S | toggle renders; verified served at :8765 | ☐ |
| V2-0d | Decision: does the frontier discriminate, or do we need regional €/m² comps? | — | S | written verdict in this file | ✅ 2026-06-08 |

**V2-0d verdict (2026-06-08):** frontier discriminates — 5/20 non-dominated, clean
CP/€100k spread (1.79–2.03), €/m²-land spread 13.7–55.9 (meaningful). Internal frontier
is usable now; regional comps (V2-bk1) NOT yet needed. **BUT** all 5 diamonds are FR
(immonot + frenchestateagents) — Idealista/Properstar absent because they're unvetted
(no photos/land). **The frontier is only as good as coverage** → this is direct evidence
that Stages 1–2 (backfill + platform expansion) are the binding constraint, not the metric.

### Stage 1 — Backfill + one manual end-to-end run

| ID | Feature | Link | Effort | Acceptance check | Status |
|----|---------|------|--------|------------------|--------|
| V2-1a | `idealista_detail_enrich.py` — visit 7 shortlisted detail pages, full photos + land + building | 1,2 | M | 7 stubs gain photo_urls + land_size_m2 | ☐ |
| V2-1b | `import_favorites.py --download-photos` — cache 3 photos/prop locally → unblocks auth-gated vision scoring | 2 | M | Properstar shortlist props get local file:// photos + character_score | ☐ |
| V2-1c | `dedup.py` — cross-platform fingerprint (lat/lon + price + size); same house = 1 entry | 2 | M | known duplicate pair collapses to 1 | ☐ |
| V2-1d | Completeness gate in scorer — props below the data floor go to "needs enrichment," not the frontier | 2,4 | S | incomplete prop never appears on frontier | ☐ |
| V2-1e | `refresh.py` orchestrator — harvest→enrich→score→dedup→frontier→publish→digest as ONE command, per-platform isolation, self-test per scraper | all | L | one live run produces verified shortlist + digest | ☐ |
| V2-1f | Token/cookie liveness probe + age check per platform; expiry → `notify-self.sh` with exact fix command | 2 | S | expired token triggers alert, not silent skip | ☐ |

### Stage 2 — Platform coverage expansion (each proven manually first)

| ID | Feature | Link | Effort | Acceptance check | Status |
|----|---------|------|--------|------------------|--------|
| V2-2a | SeLoger harvester (FR, hostile — DataDome) | 1 | M | favorites list captured with photos+land | ☐ |
| V2-2b | Bien'ici harvester (FR, hostile) | 1 | M | same | ☐ |
| V2-2c | Rightmove Overseas harvester (EU+UK) | 1 | M | same | ☐ |
| V2-2d | Green-Acres multi-region (.com / .it / .es beyond FR) | 1 | S | non-FR favorites captured | ☐ |
| V2-2e | Wire `recheck_availability.py` into refresh — drop sold/stale listings weekly | 1 | S | sold listing marked Removed within 1 cycle | ☐ |

### Stage 3 — Autonomous weekly refresh (LAST — earned after Stage 1 clean ≥2×)

| ID | Feature | Link | Effort | Acceptance check | Status |
|----|---------|------|--------|------------------|--------|
| V2-3a | LaunchAgent on Mac mini, Sunday night, `StartCalendarInterval` (catches up on wake) | all | S | live fire produces fresh shortlist + log | ☐ |
| V2-3b | Weekly digest → `notify-self.sh`: "N new, M on frontier, top-3: [link]" + "last successful run: X" | 5 | S | digest received after a real run | ☐ |
| V2-3c | `/sunday-session` §2 reads the auto-refreshed frontier instead of running pipeline.py manually | 5 | S | Sunday step shows current frontier with zero manual run | ☐ |

### Practical-facilitation layer (from 2026-06-08 thread — "can the property actually host the operation?")

The scorer captures the *presence* of venture-shape features but not their *capacity/fitness*.
Three tiers by what's derivable: text (now) → numbers/floor-plan (v2) → site-only (checklist).

| ID | Feature | Tier | Effort | Status |
|----|---------|------|--------|--------|
| V2-1g | **Track 1 — facilitation keyword layer** (3-phase, level land, south aspect, water source, independent guest unit, convertible attic, mains drainage) → `facilitation` delta [0,+0.20] | text | S | ✅ 2026-06-08 — fires on 54/921 store props; shortlist 0 until Stage-1 enrichment feeds descriptions (median shortlist blob = 44 chars) |
| V2-1h | **Spatial-capacity check** (deterministic): built m² + convertible-outbuilding m² vs functional program (~302m² for a 4-room table-d'hôtes op); flag props that can't host as-is. Upgrades `capacity_sweet_spot` from bedroom-COUNT to area-validated. | numbers | M | ☐ — **HIGH LEVERAGE**: 4 of 5 current "diamonds" are 26–55% of needed area (5th has no building size); feasibility rests on outbuilding/attic conversion we don't size (e.g. the 79m² one advertises a 153m² convertible attic). This is exactly why the check must count convertible area, not just main-building m². |
| V2-2f | **Floor-plan vision** (Tier B): when a listing includes a floor plan, GPT-vision reads room program, separability, ensuite-ability, large-volume ID, attic convertibility → `reconfiguration_potential` signal | floor-plan | L | ☐ |
| V2-1i | **Shortlist thumbnails**: (a) wired `thumbnail`/`photo_urls` into the shortlist export (were stripped → page showed NO photos for anyone), (b) `thumbnail_backfill.py` fetches og:image for missing ones | numbers | S | ✅ 2026-06-08 — export now carries photos; backfilled 5 immonot; 35/48 shortlist entries have a thumbnail. **13 Idealista still missing** — DataDome blocks curl+og:image AND the patchright harvester across 3 IPs (2 VPN + residential, 2026-06-08) → "uso improprio" every time. Conclusion: it's the AUTOMATION FINGERPRINT, not the IP — no IP switch beats it. Only real-session capture works: the browser extension (V2-bk2) or a one-shot DevTools console snippet run in Jonathan's real logged-in browser (snippet drafted, deferred). This is the definitive case FOR the extension as the hostile-platform mechanism. Folds into V2-bk2. |
| V2-bk6 | **Track 2 — viewing checklist** (one-pager, grouped by the 5 functions, data-visible vs verify-on-site): septic/water capacity, 3-phase, hot-water/laundry scale, soil aspect/slope, year-round access, ERP permit headroom, knock-through feasibility | site-only | S | ☐ — deferred per user 2026-06-08 (Track 1 now, Track 2 v2) |

Note: "listed-or-not" already handled — `listed_heritage: exclude` gate fires via `is_listed_heritage` flag + keyword fallback (`monument historique`/`classé`/`DRAC`) in `cyber_prairie_score.py:1165`.

### LLM on Claude subscription — no paid API (2026-06-08, user directive)

Goal: run all LLM analysis on the Claude Code CLI (Max subscription), zero metered OpenAI.

| ID | Feature | Status |
|----|---------|--------|
| V2-1j | Character scorer → `claude -p` vision (downloads lead photo, reads it on the subscription). Replaced gpt-4o-mini in `character_score_vision.py` | ✅ 2026-06-08 — proven (scored a prop at 4/5); `--rescore` flag added |
| V2-1k | Criteria analyzer → `claude -p` JSON (validated via `CPCriteria` pydantic). Replaced gpt-4o-mini in `analyze_store.py`; `config.json` openai_model removed; pipeline step relabeled | ✅ 2026-06-08 — proven (valid 1-5 rubric written, `gpt_model=claude-cli`) |
| V2-1l | **Re-score the ~193 cached OpenAI character_scores with Claude** (`character_score_vision.py --rescore`) so no OpenAI-derived data remains. Slow (per-prop `claude -p`), flat-rate. | ☐ — offered, deferred. Vetted-15 still carry OpenAI-era character_scores until done. |
| V2-1m | **Prune legacy OpenAI scripts** (analyze_from_urls/_properties/_optimized, analyze_with_structured_output, batch_gpt_analysis, extract_property_kpis) — unreferenced dead code, but still import openai | ☐ — needs explicit consent (no-silent-removal rule); inert unless manually invoked |

Note: free public-data APIs (Géorisques risk, OSM/Overpass amenities+geocoding, open-meteo climate, ISRIC soil, geo.api.gouv) are KEPT — unmetered, and they feed the deterministic gates. Platform scrapers also stay. "No external API calls" = no paid LLM, not no HTTP.

### Backlog / v2.1 (deferred, not blocking)

| ID | Feature | Why deferred |
|----|---------|--------------|
| V2-bk1 | Regional €/m² comps (median per département/comune) → "underpriced for area" signal | Heavy; internal frontier is meaningful first-cut. Revisit if V2-0d says it's needed. |
| V2-bk2 | Browser extension (passive forward-capture) | Demoted — only captures what you browse, not zero-effort. Optional convenience layer over scheduled scrapers. |
| V2-bk3 | Commercial-viability layer (B&B / workshop / guest ROI scoring) for business-plan framing | Paradiso-session Track 2; separate from value frontier. |
| V2-bk4 | Properstar unfavorite (9 pending IDs) — capture correct DELETE endpoint | Housekeeping, not coverage. IDs in [[project-paradisomatch-w24]]. |

---

## Risks → mitigations

| Risk | Mitigation |
|------|-----------|
| Hostile platform blocks scheduled run | Persistent authed profile + human-like delays; on block → alert + skip, never fail whole run |
| Auth expiry stops capture silently | Liveness probe at job start (V2-1f); exact 2-min fix in alert; never silent |
| Scraper breaks on layout change | Self-test per platform — historical-N to 0 triggers "may be broken" alert, not silent zero |
| Mac mini asleep at fire time | StartCalendarInterval catch-up; digest shows last-successful-run timestamp |
| Frontier over incomplete data misleads | Completeness gate (V2-1d) — never rank a prop below the data floor |
| Cost creep from re-scoring | Strictly incremental; scored props cached; only new photos hit GPT-4o-mini |
| Building marginal infra | Stage gates — prove value (Stage 0) and manual run (Stage 1) before scheduling (Stage 3) |
