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