feat: AR-House initial commit
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
"""Test focalizado de Wave 2 (ValueEstimator + OfferStrategist) con deal real.
|
||||
|
||||
DEAL: 446 VERMONT Avenue, Green Cove Springs, FL 32043 — $219K MLS retail
|
||||
- Clay County (NO court_records scraper — soft-fail NOT_IMPLEMENTED esperado)
|
||||
- 2005 build, 3/2, 1461 sqft, tax $3348, insurance $876, rent $1789, no HOA
|
||||
- ARV $240K (estimacion razonable para zona Class B/C 2005 build)
|
||||
|
||||
Criterios validados (8 puntos del spec del usuario):
|
||||
1. ValueEstimator: comparison listing vs comps
|
||||
2. ValueEstimator: rango low/mid/high
|
||||
3. OfferStrategist: Strike/Stretch/Walk-Away
|
||||
4. OfferStrategist: angulo de ataque (psychological points)
|
||||
5. OfferStrategist: contra-ofertas anticipadas
|
||||
6. OfferStrategist: si auction → MAB, si MLS → Strike (este deal es MLS)
|
||||
7. Briefing: seccion "Valor Real vs Listing"
|
||||
8. Briefing: seccion "Oferta Recomendada"
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import sys
|
||||
import 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 status_cb(msg: str) -> None:
|
||||
print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print("=" * 70)
|
||||
print("Wave 2 Validation — 446 Vermont Ave, Green Cove Springs FL $219K MLS")
|
||||
print("=" * 70)
|
||||
|
||||
deal = DealInputs(
|
||||
address="446 VERMONT Avenue, Green Cove Springs, FL 32043",
|
||||
price=219_000,
|
||||
rent=1_789,
|
||||
property_tax=3_348,
|
||||
insurance=876,
|
||||
hoa=0,
|
||||
sqft=1_461,
|
||||
beds=3,
|
||||
baths=2.0,
|
||||
year_built=2005,
|
||||
arv=240_000, # estimacion razonable; sin rehab significativo para 2005 build
|
||||
rehab_override=8_000, # cosmetics solo, no major rehab
|
||||
deal_type="mls", # NORMAL MLS retail, no auction
|
||||
)
|
||||
profile = BuyerProfile(
|
||||
profile_class="C",
|
||||
fico=720,
|
||||
capital_available=65_000,
|
||||
nationality="Argentina",
|
||||
)
|
||||
|
||||
print(f"DEAL: {deal.address}")
|
||||
print(f" price=${deal.price:,} rent=${deal.rent:,}/mo arv=${deal.arv:,}")
|
||||
print(f" beds={deal.beds}/baths={deal.baths} sqft={deal.sqft} year={deal.year_built}")
|
||||
print(f" tax=${deal.property_tax:,}/y insurance=${deal.insurance:,}/y hoa=${deal.hoa}/mo")
|
||||
print(f" deal_type={deal.deal_type}, rehab_override=${deal.rehab_override:,}")
|
||||
print()
|
||||
|
||||
t0 = time.perf_counter()
|
||||
result = analyze_deal(deal, profile, photo_bytes=None, status_cb=status_cb)
|
||||
elapsed = time.perf_counter() - t0
|
||||
|
||||
print()
|
||||
print("=" * 70)
|
||||
print(f"COMPLETADO en {elapsed:.0f}s ({elapsed/60:.1f} min)")
|
||||
print("=" * 70)
|
||||
|
||||
# Get outputs
|
||||
ve_out = (result.value_estimate or {}).get("output", "") or ""
|
||||
os_out = (result.offer_strategy or {}).get("output", "") or ""
|
||||
briefing_out = (result.executive_briefing or {}).get("output", "") or ""
|
||||
|
||||
# ===========================================================
|
||||
# 8 CRITERIOS DE VALIDACION (spec del usuario)
|
||||
# ===========================================================
|
||||
print()
|
||||
print("─" * 70)
|
||||
print("VALIDACION 8 CRITERIOS")
|
||||
print("─" * 70)
|
||||
|
||||
# 1. ValueEstimator: comparison listing vs comps
|
||||
c1 = any(kw in ve_out.lower() for kw in [
|
||||
"listing", "precio listado", "$219", "comp", "comparable"
|
||||
])
|
||||
print(f" 1. ValueEstimator: comparison listing vs comps: {'✅' if c1 else '❌'}")
|
||||
|
||||
# 2. ValueEstimator: rango low/mid/high
|
||||
has_low = "low" in ve_out.lower() or "bajo:" in ve_out.lower()
|
||||
has_mid = "mid" in ve_out.lower() or "medio:" in ve_out.lower()
|
||||
has_high = "high" in ve_out.lower() or "alto:" in ve_out.lower()
|
||||
c2 = has_low and has_mid and has_high
|
||||
print(f" 2. ValueEstimator: rango low/mid/high: {'✅' if c2 else '⚠️ (parcial)'}")
|
||||
|
||||
# 3. OfferStrategist: Strike/Stretch/Walk-Away
|
||||
has_strike = "strike" in os_out.lower()
|
||||
has_stretch = "stretch" in os_out.lower()
|
||||
has_walkaway = "walk-away" in os_out.lower() or "walk away" in os_out.lower()
|
||||
c3 = has_strike and has_stretch and has_walkaway
|
||||
print(f" 3. OfferStrategist: Strike/Stretch/Walk-Away: {'✅' if c3 else '⚠️ (parcial)'}")
|
||||
|
||||
# 4. OfferStrategist: angulo de ataque
|
||||
c4 = any(kw in os_out.lower() for kw in [
|
||||
"angulo de ataque", "ángulo de ataque", "angulo", "psicologic", "psicológ",
|
||||
"presentacion", "argumento",
|
||||
])
|
||||
print(f" 4. OfferStrategist: angulo de ataque (psicologico): {'✅' if c4 else '❌'}")
|
||||
|
||||
# 5. OfferStrategist: contra-ofertas anticipadas
|
||||
c5 = any(kw in os_out.lower() for kw in [
|
||||
"contra-oferta", "contra oferta", "counter-offer", "counter offer",
|
||||
"contraoferta", "anticipad",
|
||||
])
|
||||
print(f" 5. OfferStrategist: contra-ofertas anticipadas: {'✅' if c5 else '❌'}")
|
||||
|
||||
# 6. MLS deal → debe usar Strike NO MAB (este test es MLS)
|
||||
has_mab_only = "mab" in os_out.lower() and not has_strike
|
||||
c6 = has_strike and not has_mab_only
|
||||
print(f" 6. MLS deal usa Strike (no MAB exclusivo): {'✅' if c6 else '❌'}")
|
||||
|
||||
# 7. Briefing: seccion "Valor Real" / "Valor vs Listing"
|
||||
c7 = any(kw in briefing_out.lower() for kw in [
|
||||
"valor real", "valor estimado", "estimación de valor", "estimacion de valor",
|
||||
"valor vs listing", "valor del inmueble",
|
||||
])
|
||||
print(f" 7. Briefing incluye seccion Valor Real: {'✅' if c7 else '❌'}")
|
||||
|
||||
# 8. Briefing: seccion "Oferta Recomendada"
|
||||
c8 = any(kw in briefing_out.lower() for kw in [
|
||||
"oferta recomendada", "recomendación de oferta", "recomendacion de oferta",
|
||||
"estrategia de oferta", "oferta sugerida", "strike", "walk-away",
|
||||
])
|
||||
print(f" 8. Briefing incluye seccion Oferta Recomendada: {'✅' if c8 else '❌'}")
|
||||
|
||||
print()
|
||||
score = sum([c1, c2, c3, c4, c5, c6, c7, c8])
|
||||
print(f"SCORE: {score}/8")
|
||||
|
||||
# ===========================================================
|
||||
# Excerpts para revisión humana
|
||||
# ===========================================================
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("EXCERPTS — ValueEstimator (primeras 800 chars)")
|
||||
print("=" * 70)
|
||||
print(ve_out[:800])
|
||||
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("EXCERPTS — OfferStrategist (primeras 1000 chars)")
|
||||
print("=" * 70)
|
||||
print(os_out[:1000])
|
||||
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("EXCERPTS — ContextualGlossaryAgent (primeras 1200 chars)")
|
||||
print("=" * 70)
|
||||
print(briefing_out[:1200])
|
||||
|
||||
return 0 if score >= 7 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user