Files
AR-House/scripts/test_age_deductions_bug_fix.py
2026-07-03 12:24:58 -04:00

194 lines
8.2 KiB
Python

"""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()