feat: AR-House initial commit
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
"""Test bug fix: ValueEstimator should NOT apply blind age deductions when
|
||||
listing condition is 'Updated/Remodeled' or description mentions new items.
|
||||
|
||||
Test fixture: 2352 SCENIC VIEW Court Jacksonville FL (Zillow 44455413_zpid).
|
||||
Year built: 1997, Condition: "Updated/Remodeled", Features: "BRAND NEW ROOF",
|
||||
"NEW AC", "Fresh paint", description: "Fully updated throughout and move in ready".
|
||||
|
||||
EXPECTED before fix: total_deductions ≈ $28,000 (AC + roof + plumbing? No, plumbing
|
||||
no aplica porque 1997 > 1995. Solo AC + roof = $16K).
|
||||
EXPECTED after fix: total_deductions = $0 (condition + keywords ambos disparan suprimir).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
sys.path.insert(0, "D:/Proyectos Software/AR-House")
|
||||
|
||||
|
||||
def test_scenic_view_data():
|
||||
"""Real user fixture — 2352 SCENIC VIEW Court."""
|
||||
from data_fetchers.property_value import calculate_age_deductions
|
||||
|
||||
# Real Zillow data as user reported
|
||||
fixture = {
|
||||
"year_built": 1997,
|
||||
"listing_description": (
|
||||
"Fully updated throughout and move in ready. Fresh paint, "
|
||||
"BRAND NEW ROOF, garage epoxy floor, NEW AC, exterior paint."
|
||||
),
|
||||
"condition_status": "Updated/Remodeled",
|
||||
"features_special": [
|
||||
"Fresh paint",
|
||||
"BRAND NEW ROOF",
|
||||
"Garage epoxy",
|
||||
"NEW AC",
|
||||
"Exterior paint",
|
||||
],
|
||||
}
|
||||
|
||||
# CASE A: global skip via condition_status='Updated/Remodeled'
|
||||
result_a = calculate_age_deductions(**fixture)
|
||||
print("=" * 70)
|
||||
print("CASE A — Full fixture (condition + features + description):")
|
||||
print(f" total: ${result_a['total']:,}")
|
||||
print(f" _skipped_global: {result_a.get('_skipped_global')}")
|
||||
print(f" _skip_reason: {result_a.get('_skip_reason')}")
|
||||
assert result_a["total"] == 0, f"Expected 0 deductions, got ${result_a['total']:,}"
|
||||
assert result_a["_skipped_global"] is True, "Expected _skipped_global=True"
|
||||
print(" PASS: total=$0, _skipped_global=True")
|
||||
|
||||
# CASE B: only condition_status, no description/features → still skip
|
||||
result_b = calculate_age_deductions(
|
||||
year_built=1997,
|
||||
condition_status="Updated/Remodeled",
|
||||
)
|
||||
print()
|
||||
print("CASE B — Only condition_status='Updated/Remodeled':")
|
||||
print(f" total: ${result_b['total']:,}")
|
||||
assert result_b["total"] == 0, f"Expected 0, got ${result_b['total']:,}"
|
||||
print(" PASS: total=$0 (condition tag alone suffices)")
|
||||
|
||||
# CASE C: only description, no condition_status → still skip via 'fully updated'
|
||||
result_c = calculate_age_deductions(
|
||||
year_built=1997,
|
||||
listing_description="Fully updated throughout and move in ready.",
|
||||
)
|
||||
print()
|
||||
print("CASE C — Only description 'fully updated, move in ready':")
|
||||
print(f" total: ${result_c['total']:,}")
|
||||
print(f" _skip_reason: {result_c.get('_skip_reason')}")
|
||||
assert result_c["total"] == 0, f"Expected 0, got ${result_c['total']:,}"
|
||||
print(" PASS: global keyword detected, total=$0")
|
||||
|
||||
# CASE D: only features array (NEW AC, BRAND NEW ROOF) → per-item suppression
|
||||
# No 'Updated/Remodeled' tag, no global 'fully updated' keyword.
|
||||
# AC suppressed (year<2010), Roof suppressed (year<2005 N/A since 1997).
|
||||
result_d = calculate_age_deductions(
|
||||
year_built=1997,
|
||||
features_special=["BRAND NEW ROOF", "NEW AC"],
|
||||
)
|
||||
print()
|
||||
print("CASE D — Only features_special tags (no condition, no description):")
|
||||
print(f" total: ${result_d['total']:,}")
|
||||
print(f" ac: ${result_d['ac']:,} (year<2010, expecting suppression)")
|
||||
print(f" roof: ${result_d['roof']:,} (year=1997 > 2005, no deduction triggered anyway)")
|
||||
print(f" _suppressed_items: {result_d.get('_suppressed_items')}")
|
||||
print(f" _reasons: {result_d.get('_reasons')}")
|
||||
assert result_d["ac"] == 0, f"AC should be suppressed (NEW AC in features), got ${result_d['ac']:,}"
|
||||
assert "ac" in result_d["_suppressed_items"], "ac should be in suppressed_items"
|
||||
print(" PASS: AC deduction suppressed by features tag 'NEW AC'")
|
||||
|
||||
# CASE E: baseline — old property NO renovation evidence → all deductions apply
|
||||
result_e = calculate_age_deductions(year_built=1985)
|
||||
print()
|
||||
print("CASE E — Old property (1985), no renovation evidence (baseline):")
|
||||
print(f" total: ${result_e['total']:,}")
|
||||
print(f" ac: ${result_e['ac']:,}, roof: ${result_e['roof']:,}, plumbing: ${result_e['plumbing']:,}, panel: ${result_e['panel']:,}")
|
||||
assert result_e["ac"] > 0, "AC should apply (year<2010)"
|
||||
assert result_e["roof"] > 0, "Roof should apply (year<2005)"
|
||||
assert result_e["plumbing"] > 0, "Plumbing polybutylene should apply (1985 in 1978-1995)"
|
||||
assert result_e["panel"] > 0, "Panel should apply (year<1990)"
|
||||
print(f" PASS: all 4 deductions apply correctly = ${result_e['total']:,}")
|
||||
|
||||
# CASE F: same old property BUT description says repiped + new panel
|
||||
result_f = calculate_age_deductions(
|
||||
year_built=1985,
|
||||
listing_description="Re-piped 2022 with PEX. New 200 amp panel. Original AC and roof.",
|
||||
)
|
||||
print()
|
||||
print("CASE F — Old property (1985) with partial renovation evidence:")
|
||||
print(f" total: ${result_f['total']:,}")
|
||||
print(f" ac: ${result_f['ac']:,} (should apply — Original AC)")
|
||||
print(f" roof: ${result_f['roof']:,} (should apply — Original roof)")
|
||||
print(f" plumbing: ${result_f['plumbing']:,} (should be 0 — repiped)")
|
||||
print(f" panel: ${result_f['panel']:,} (should be 0 — new panel)")
|
||||
print(f" _suppressed_items: {result_f.get('_suppressed_items')}")
|
||||
assert result_f["plumbing"] == 0, "Plumbing should be suppressed by 'Re-piped'"
|
||||
assert result_f["panel"] == 0, "Panel should be suppressed by 'New 200 amp panel'"
|
||||
assert result_f["ac"] > 0, "AC should still apply (Original AC mentioned)"
|
||||
assert result_f["roof"] > 0, "Roof should still apply"
|
||||
print(" PASS: partial suppression works correctly")
|
||||
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("ALL TESTS PASSED. Bug fix verified.")
|
||||
print("=" * 70)
|
||||
|
||||
|
||||
def test_zillow_detail_parser():
|
||||
"""Test the markdown parser extracts condition/features/status correctly."""
|
||||
from scrapers.zillow import _parse_property_detail_md
|
||||
|
||||
# Simulated Zillow markdown for 2352 SCENIC VIEW
|
||||
fake_md = """
|
||||
# 2352 Scenic View Ct, Jacksonville, FL 32218
|
||||
|
||||
**$265,000**
|
||||
|
||||
Status: Active under contract
|
||||
|
||||
## What's special
|
||||
- Fresh paint
|
||||
- BRAND NEW ROOF
|
||||
- Garage epoxy
|
||||
- NEW AC
|
||||
- Exterior paint
|
||||
|
||||
## Description
|
||||
|
||||
Fully updated throughout and move in ready. This 1997 home features fresh paint
|
||||
inside and out, brand new roof, new AC, garage epoxy floor.
|
||||
|
||||
## Facts & Features
|
||||
|
||||
Year built: 1997
|
||||
Condition: Updated/Remodeled
|
||||
Type: Single Family
|
||||
|
||||
## Home value
|
||||
|
||||
Zestimate: $261,800
|
||||
Tax assessed value: $222,653
|
||||
"""
|
||||
parsed = _parse_property_detail_md(fake_md)
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("ZILLOW DETAIL PARSER TEST:")
|
||||
print("=" * 70)
|
||||
for k, v in parsed.items():
|
||||
if isinstance(v, list):
|
||||
print(f" {k}: {v[:5]}")
|
||||
elif isinstance(v, str) and len(v) > 80:
|
||||
print(f" {k}: {v[:100]}...")
|
||||
else:
|
||||
print(f" {k}: {v}")
|
||||
|
||||
assert parsed["condition_status"] in ("Updated/Remodeled", "Updated/Remodeled".lower().capitalize()), \
|
||||
f"Expected condition_status='Updated/Remodeled', got {parsed['condition_status']!r}"
|
||||
assert parsed["year_built"] == 1997, f"Expected year_built=1997, got {parsed['year_built']}"
|
||||
assert "active under contract" in (parsed["home_status"] or "").lower(), \
|
||||
f"Expected active under contract, got {parsed['home_status']!r}"
|
||||
assert parsed["active_under_contract"] is True, "Expected active_under_contract=True"
|
||||
assert len(parsed["features_special"]) >= 4, \
|
||||
f"Expected ≥4 features, got {len(parsed['features_special'])}: {parsed['features_special']}"
|
||||
assert parsed["zestimate"] == 261800, f"Expected zestimate=261800, got {parsed['zestimate']}"
|
||||
assert parsed["tax_assessed_value"] == 222653, \
|
||||
f"Expected tax_assessed=222653, got {parsed['tax_assessed_value']}"
|
||||
assert len(parsed["renovation_keywords_found"]) > 0, "Expected renovation keywords detected"
|
||||
print()
|
||||
print("PASS: parser extracts all expected fields correctly.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_scenic_view_data()
|
||||
test_zillow_detail_parser()
|
||||
Reference in New Issue
Block a user