Skip to content

Commit 6fdb2fd

Browse files
Merge pull request #2 from vimscientist69/feat/week2-completion
Feat/week2 completion
2 parents 9437c44 + 140fc8d commit 6fdb2fd

23 files changed

Lines changed: 1638 additions & 396 deletions

.cursor/rules/PROJECT_NOTE.md

Lines changed: 37 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,3 @@
1-
TODOS:
2-
3-
- [x] First, create a few datasets for testing purposes
4-
1. 1000 listings set from p24, and 1000 listings set from privateproperty
5-
2. a dataset with both p24 and 1000 listings from privateproperty joined
6-
- [ ] phase 3 in week-2-execution plan
7-
8-
Unfinished prompts for phase 3 (scoring_evaluation.py):
9-
10-
1. side note, if I am correct it should not just evaluate the top n, but also the mid n and bottom n.
11-
12-
2. get progress report on phase 3
13-
141
# 🏠 Real Estate Deal Intelligence Platform (Full System)
152

163
## 🎯 Goal
@@ -446,109 +433,36 @@ Deferred unless core goals are already complete:
446433
447434
### **Goal**
448435
449-
Ship an **ROI-first, explainable advanced scoring system** that improves ranking quality over the Week 1 baseline by:
450-
- using **micro-comparables** (location/type/bed/bath segment medians, not a single dataset median),
451-
- adding **rental yield + transaction-cost adjustments** (net-ish ROI proxy),
452-
- producing a **reasoning/explanations payload** for every score (so results are inspectable),
453-
- adding an **analytics engine** that can quantify scoring quality and data health,
454-
- integrating **LLM enrichment** in a controlled, measurable way (only if it improves outcomes).
455-
456-
### **Deliverables (Week 2)**
457-
458-
#### **2.1 Advanced scoring system (v2)**
459-
460-
- **Micro-comps pricing signals**
461-
- Compute segmented medians / distributions for:
462-
- `province/city/suburb` (use the deepest level with enough samples)
463-
- `property_type`
464-
- `bedrooms`, `bathrooms` (bucketed)
465-
- Add fallbacks when segment sample size is too small (e.g., suburb → city → province → global).
466-
- Replace baseline “single median” price deviation with:
467-
- **price_vs_comp_median** (price deviation within the best-available segment)
468-
- **price_per_sqm_vs_comp_median** (if floor_size available)
469-
470-
- **ROI proxy signals**
471-
- **Transaction-cost adjustment**
472-
- Upfront costs modeled as configurable % or fixed schedule (kept in config).
473-
- Optional LLM-assisted extraction path:
474-
- infer additional upfront-cost signals from listing fields + description text
475-
- emit `upfront_cost_estimate`, `cost_drivers`, and `confidence`
476-
- use only when confidence is above threshold, otherwise fallback to deterministic config assumptions
477-
- **Net yield proxy**
478-
- Use available fields (`rates_and_taxes`, `levies`) + configurable assumptions:
479-
- vacancy allowance %, maintenance %, management %, insurance (optional)
480-
- Rent estimation approach for Week 2:
481-
- **Phase 1 (required):** heuristic rent estimate (config-driven by `property_type`, `bedrooms`, `city/province` buckets)
482-
- **Phase 2 (optional):** upgrade rent estimate via LLM/external data only if Phase 1 is weak
483-
- Add a yield-derived score component such as:
484-
- **net_yield_signal** and **payback_signal** (optional, time-boxed)
485-
486-
- **Liquidity & risk adjustments**
487-
- Keep time-on-market but improve it:
488-
- use `date_posted` where available
489-
- add a **stale inventory non-linear curve** (e.g., diminishing returns after N days)
490-
- Penalize low-confidence or missing-critical-fields in a consistent way:
491-
- separate **data_confidence** (completeness) from **investment_risk** (flags like auction/private seller if used)
492-
493-
- **Scoring versioning**
494-
- Output `model_version="advanced_v2"` (keep baseline runnable side-by-side).
495-
- Ensure scoring is **idempotent** per job (overwrite results like Week 1).
496-
497-
#### **2.2 Reasoning engine (explainability)**
498-
499-
- Persist a structured explanation per listing score:
500-
- top contributing signals with raw values and normalized scores
501-
- “why this was ranked high/low”
502-
- confidence and missing-field notes
503-
- Output target:
504-
- a single `deal_reason` string (short)
505-
- plus a structured `explanation` JSON blob (machine-readable) for later UI.
506-
507-
#### **2.3 Analytics engine (quality + insight)**
508-
509-
- Implement job-level analytics for:
510-
- score distribution (histogram bins, min/max/median, percentiles)
511-
- top-N listing summaries (score + key drivers)
512-
- missingness report for key fields that affect scoring
513-
- comps coverage report: what % of listings got suburb-level comps vs city/province/global
514-
- Add “ranking quality checks” (offline):
515-
- sanity checks for pathological outcomes (e.g., missing price scored too high)
516-
- stability checks when changing weights (top-N overlap)
517-
518-
#### **2.4 LLM enrichment prototype (Week 2)**
519-
520-
- **Purpose:** extract high-value structured variables from `description` to improve scoring.
521-
- **Candidate variables (minimal set):**
522-
- condition/renovation level (e.g., “newly renovated”, “needs TLC”)
523-
- security/amenities not reliably structured (pool, inverter/solar, etc.)
524-
- rental hints (furnished, “investment”, “tenant in place”) as weak signals
525-
- upfront-cost hints (legal/levy/special conditions) for ROI proxy refinement
526-
- **Integration approach (controlled):**
527-
- store derived fields in a separate enrichment payload (do not overwrite canonical listing fields)
528-
- feed enrichment into scoring only behind an **experiment flag**
529-
- **Week 2 validation gate (must pass to enable by default):**
530-
- improves top-N deal quality on offline evaluation metrics (see 2.5)
531-
- does not significantly increase invalid/low-confidence scores
532-
533-
#### **2.5 Evaluation + gates (scope control)**
534-
535-
- Add a lightweight offline evaluation process:
536-
- compare baseline_v1 vs advanced_v2 on:
537-
- top-N stability and reason diversity
538-
- fewer “unknown / missing data” in top ranks
539-
- comps coverage improvements
540-
- yield proxy sanity (high yield not correlated with missing price)
541-
- **Decision gates:**
542-
- only ship LLM-influenced scoring as default if it improves metrics and is stable
543-
- otherwise keep LLM enrichment stored but not used in ranking
544-
545-
### **Suggested implementation order**
546-
547-
- Build micro-comps computation + comp-based pricing signals
548-
- Add ROI proxy (transaction costs + net yield)
549-
- Add reasoning payload format
550-
- Add analytics summaries + evaluation scripts
551-
- Add LLM enrichment prototype + validation gate
436+
Ship an ROI-first, explainable scoring system (`advanced_v2`) with deterministic evaluation gates that decide promote/revert/experimental outcomes.
437+
438+
### **Week 2 Source-of-Truth Docs (Updated)**
439+
440+
- Canonical scope: `docs/week-2-execution-plan.md`
441+
- Stability details: `docs/scoring-evaluation-middle-bottom-gating-spec.md`
442+
- Evaluation policy: `docs/evaluation-review-protocol.md`
443+
- Interface contract: `docs/week2-interface-contract.md`
444+
- Implementation playbook: `docs/week2-implementation-playbook.md`
445+
446+
### **Week 2 High-Level Deliverables**
447+
448+
- Advanced scoring (`advanced_v2`) with micro-comps + ROI proxy signals.
449+
- Structured reasoning payload (`deal_reason` + machine-readable `explanation`).
450+
- Evaluation gates with deterministic release decisions:
451+
- `promote` / `revert` / `experimental`.
452+
- Segment-based stability checks:
453+
- `top_band` (critical), `middle_band`/`bottom_band` (warning),
454+
- full-dataset displacement context,
455+
- relative displacement thresholds (`*_pct`) for dataset-size-aware gating.
456+
457+
### **Week 2 Completion Status (Latest)**
458+
459+
- Phase 5 rerun after enum + evaluation identity fixes completed successfully.
460+
- Final validation decision: `promote`.
461+
- Frozen Week 2 scoring profile values:
462+
- `advanced_v2.weights.price_vs_comp = 0.29`
463+
- `advanced_v2.weights.roi_proxy = 0.21`
464+
- Decision artifact:
465+
- `backend/output/evaluations/phase5_week2_validation_decision_2026-04-27_post_enum_eval_fix.md`
552466
553467
---
554468
@@ -602,6 +516,10 @@ Turn PropSignal into a **configurable investor decision tool** where users can:
602516
- pagination and top-N optimized retrieval
603517
- asynchronous processing for heavy jobs (ingestion/scoring/validation)
604518
- freshness metadata (`last_ingested_at`, `last_scored_at`, `model/profile version`)
519+
- Required performance baseline handoff update (more info in `week2-phase4-performance-baseline-implementation.md`):
520+
- after ranking/list/detail APIs are available, update `backend/app/services/performance_baseline.py`
521+
to measure API latency and move API SLOs from `deferred` to evaluated (`met`/`missed`)
522+
- update `backend/tests/test_performance_baseline.py` to enforce this behavior
605523
606524
#### **3.3 CLI revamp to mirror backend/dashboard capability**
607525
@@ -682,6 +600,9 @@ Harden the system for real-world use by running structured validation on real da
682600
- Optimize bottlenecks (indexes, pagination paths, batch operations).
683601
- Complete deployment checklist (env config, observability, rollback path, smoke tests).
684602
- Use `docs/mvp-performance-plan.md` as the implementation checklist and SLO reference.
603+
- Ensure performance baseline artifacts include dataset-size context and throughput metrics:
604+
- `records_total`, `records_valid`
605+
- stage throughput (rows/sec) for scoring and validation
685606
686607
#### **4.5 Documentation pack (operator + analyst guidance)**
687608
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""add analyzed ingestion job status
2+
3+
Revision ID: 20260424_0005
4+
Revises: 20260415_0004
5+
Create Date: 2026-04-24 00:05:00
6+
"""
7+
8+
from collections.abc import Sequence
9+
10+
import sqlalchemy as sa
11+
from alembic import op
12+
13+
# revision identifiers, used by Alembic.
14+
revision: str = "20260424_0005"
15+
down_revision: str | None = "20260415_0004"
16+
branch_labels: str | Sequence[str] | None = None
17+
depends_on: str | Sequence[str] | None = None
18+
19+
20+
def upgrade() -> None:
21+
op.alter_column(
22+
"ingestion_jobs",
23+
"status",
24+
existing_type=sa.Enum(
25+
"created",
26+
"processing",
27+
"completed",
28+
"completed_with_errors",
29+
"failed",
30+
name="ingestion_job_status",
31+
native_enum=False,
32+
),
33+
type_=sa.Enum(
34+
"created",
35+
"processing",
36+
"completed",
37+
"completed_with_errors",
38+
"analyzed",
39+
"failed",
40+
name="ingestion_job_status",
41+
native_enum=False,
42+
),
43+
existing_nullable=False,
44+
)
45+
46+
47+
def downgrade() -> None:
48+
op.execute("UPDATE ingestion_jobs SET status = 'completed' WHERE status = 'analyzed'")
49+
op.alter_column(
50+
"ingestion_jobs",
51+
"status",
52+
existing_type=sa.Enum(
53+
"created",
54+
"processing",
55+
"completed",
56+
"completed_with_errors",
57+
"analyzed",
58+
"failed",
59+
name="ingestion_job_status",
60+
native_enum=False,
61+
),
62+
type_=sa.Enum(
63+
"created",
64+
"processing",
65+
"completed",
66+
"completed_with_errors",
67+
"failed",
68+
name="ingestion_job_status",
69+
native_enum=False,
70+
),
71+
existing_nullable=False,
72+
)

backend/app/cli.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from pathlib import Path
2+
from typing import Annotated
23

34
import typer
45

@@ -7,6 +8,7 @@
78
from app.services.dataset_validation import run_dataset_validation
89
from app.services.exporting import export_job_results
910
from app.services.ingestion import ingest_propflux_file
11+
from app.services.performance_baseline import run_performance_baseline
1012
from app.services.scoring import run_scoring_job
1113
from app.services.scoring_evaluation import run_scoring_evaluation
1214

@@ -82,5 +84,29 @@ def evaluate_scoring(
8284
typer.echo(f"Report written to: {report['report_path']}")
8385

8486

87+
@app.command("benchmark-baseline")
88+
def benchmark_baseline(
89+
dataset: Annotated[list[str], typer.Option("--dataset")],
90+
top_n: Annotated[int, typer.Option("--top-n")] = 20,
91+
output_dir: Annotated[str | None, typer.Option("--output-dir")] = None,
92+
) -> None:
93+
with SessionLocal() as db:
94+
metrics = run_performance_baseline(
95+
db,
96+
dataset_paths=dataset,
97+
top_n=top_n,
98+
output_dir=output_dir,
99+
)
100+
typer.echo(
101+
"Performance baseline completed for "
102+
f"{len(dataset)} dataset(s). "
103+
f"met={len(metrics['slo_assessment']['met'])}, "
104+
f"missed={len(metrics['slo_assessment']['missed'])}, "
105+
f"deferred={len(metrics['slo_assessment']['deferred'])}"
106+
)
107+
typer.echo(f"Metrics written to: {metrics['metrics_path']}")
108+
typer.echo(f"Summary written to: {metrics['summary_path']}")
109+
110+
85111
if __name__ == "__main__":
86112
app()

backend/app/models/ingestion_job.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class IngestionJob(Base):
1717
"processing",
1818
"completed",
1919
"completed_with_errors",
20+
"analyzed",
2021
"failed",
2122
name="ingestion_job_status",
2223
native_enum=False,

backend/app/schemas/propflux_listing.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ class RecordValidationError(BaseModel):
1616

1717

1818
class PropfluxListing(BaseModel):
19-
model_config = ConfigDict(extra="forbid")
19+
# Be permissive with future source schema additions. We still enforce all
20+
# required fields/types below, but unknown keys are accepted.
21+
model_config = ConfigDict(extra="allow")
2022

2123
# Required fields
2224
title: str

0 commit comments

Comments
 (0)