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

150 lines
5.9 KiB
Python

"""Test e2e del LAND-simplified pipeline con escenario Vero Beach $7K.
Verifica:
1. detect_property_type_anomalies dispara should_bypass=True
2. analyze_deal route a _analyze_land_simplified
3. NO se ejecutan DealAnalyzer/LenderMatcher/ValueEstimator/OfferStrategist
4. NO se computa Cap Rate / DSCR (computed_scenarios queda {})
5. Briefing arranca con "🚨 ALERTA CRITICA: PROPIEDAD TIPO TERRENO DETECTADA"
6. Pipeline mode = land_simplified
"""
from __future__ import annotations
import io, sys, time
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from orchestrator import DealInputs, BuyerProfile, analyze_deal # noqa: E402
def main() -> int:
print("=" * 70)
print("LAND bugfix test — Vero Beach $7K (user mistakenly declared SFR)")
print("=" * 70)
deal = DealInputs(
address="5800 32nd Ave, Vero Beach, FL 32966",
price=7_000,
rent=0,
property_tax=200,
insurance=0,
hoa=0,
sqft=0,
beds=0,
baths=0,
year_built=0,
arv=0,
property_type="sfr", # USER MISTAKENLY DECLARED SFR
listing_description="0.23 acre buildable lot, ready for construction. Cleared and graded.",
)
profile = BuyerProfile(
profile_class="C",
fico=720,
capital_available=50_000,
)
print(f"DEAL: {deal.address}")
print(f" price=${deal.price:,} | property_type DECLARED='{deal.property_type}'")
print(f" year_built={deal.year_built}, sqft={deal.sqft}, beds/baths={deal.beds}/{deal.baths}")
print(f" rent={deal.rent}, arv={deal.arv}")
print(f" description: {deal.listing_description}")
print()
def log(msg):
print(f" [{time.strftime('%H:%M:%S')}] {msg}")
t0 = time.perf_counter()
result = analyze_deal(deal, profile, photo_bytes=None, status_cb=log)
elapsed = time.perf_counter() - t0
print()
print("=" * 70)
print(f"PIPELINE COMPLETED in {elapsed:.0f}s")
print("=" * 70)
# ─── Validaciones ──────────────────────────────────────────────────────
ptw = result.property_type_warning or {}
mode = result.pipeline_mode
print()
print("VALIDATIONS:")
print(f" pipeline_mode: {mode}")
assert mode == "land_simplified", f"FAIL: expected land_simplified, got {mode}"
print(" ✅ pipeline_mode == 'land_simplified'")
print(f" warnings detectados: {len(ptw.get('warnings', []))}")
assert len(ptw.get("warnings", [])) >= 4, f"FAIL: expected >=4 warnings"
print(" ✅ >= 4 warnings dispararon detection")
print(f" should_bypass_sfr_pipeline: {ptw.get('should_bypass_sfr_pipeline')}")
assert ptw.get("should_bypass_sfr_pipeline") is True
print(" ✅ should_bypass=True")
# SFR agents should be SKIPPED
da_out = (result.deal_analysis or {}).get("output", "")
le_out = (result.lender or {}).get("output", "")
ve_out = (result.value_estimate or {}).get("output", "")
os_out = (result.offer_strategy or {}).get("output", "")
print(f" DealAnalyzer output (should be SKIPPED): {da_out[:80]!r}")
print(f" LenderMatcher output (should be SKIPPED): {le_out[:80]!r}")
print(f" ValueEstimator output (should be SKIPPED): {ve_out[:80]!r}")
print(f" OfferStrategist output (should be SKIPPED): {os_out[:80]!r}")
assert "SKIPPED" in da_out and "land" in da_out.lower()
assert "SKIPPED" in le_out
assert "SKIPPED" in ve_out
assert "SKIPPED" in os_out
print(" ✅ Los 4 agentes SFR-specific NO se ejecutaron (SKIPPED)")
# computed_scenarios should be empty (no DSCR/Cap Rate)
cs = result.computed_scenarios or {}
print(f" computed_scenarios (should be empty for LAND): {len(cs)} keys")
assert cs == {}, f"FAIL: computed_scenarios should be empty for LAND, got: {cs}"
print(" ✅ NO se computó finance_calculator (no hay Cap Rate / DSCR inflado)")
# Briefing should start with land warning
briefing_out = (result.executive_briefing or {}).get("output", "")
print(f"\n Briefing first 300 chars:")
print(f" {briefing_out[:300]}")
briefing_lower = briefing_out.lower()
has_alert = any(kw in briefing_lower for kw in [
"alerta", "terreno", "land", "propiedad tipo terreno",
])
assert has_alert, "FAIL: briefing missing LAND alert"
print()
print(" ✅ Briefing destaca alerta LAND")
# Coordinator output should NOT have DSCR/Cap Rate inflados
coord_out = (result.final or {}).get("output", "")
coord_lower = coord_out.lower()
has_dscr = "dscr" in coord_lower
has_caprate = "cap rate" in coord_lower
has_coc = "coc" in coord_lower or "cash on cash" in coord_lower
print(f" Coordinator mentions DSCR: {has_dscr}")
print(f" Coordinator mentions Cap Rate: {has_caprate}")
print(f" Coordinator mentions CoC: {has_coc}")
# Es OK que el LLM las mencione para EXPLICAR que no aplican, pero NO debe presentarlas
# como metricas calculadas. Validamos que mencione "no aplica" o "land" si las menciona.
if has_dscr or has_caprate or has_coc:
explains_inapplicable = any(kw in coord_lower for kw in [
"no aplica", "no apply", "land", "terreno", "no se calcul",
])
if explains_inapplicable:
print(" ✅ Coordinator menciona metricas SFR pero CLARIFICA que no aplican")
else:
print(" ⚠️ Coordinator menciona DSCR/Cap Rate sin clarificar — review")
print()
print("=" * 70)
print("✅ ALL VALIDATIONS PASSED — LAND bug FIXED")
print("=" * 70)
# Save the result for inspection
print(f"\nFull result saved by orchestrator. Check analyses/*_LAND.json")
return 0
if __name__ == "__main__":
sys.exit(main())