194 lines
8.2 KiB
Python
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()
|