improve: Ollama prompt structured into 4 sections with full recall/complaint detail, num_predict 250→700
This commit is contained in:
@@ -183,6 +183,8 @@ async def fetch(
|
||||
"bid": bid or "—",
|
||||
"vpic": vpic,
|
||||
"risk": risk,
|
||||
"recalls": recalls,
|
||||
"complaints": complaints,
|
||||
"critical_recall_count": extended["critical_recall_count"],
|
||||
"engine_complaints": extended["engine_complaints"],
|
||||
"transmission_complaints": extended["transmission_complaints"],
|
||||
|
||||
+86
-29
@@ -4,42 +4,99 @@ OLLAMA_URL = "http://localhost:11434/api/generate"
|
||||
PRIMARY_MODEL = "DealAnalyzer"
|
||||
FALLBACK_MODEL = "qwen2.5:14b"
|
||||
|
||||
_OLLAMA_OPTIONS = {"temperature": 0.3, "num_predict": 250}
|
||||
_OLLAMA_OPTIONS = {"temperature": 0.3, "num_predict": 700}
|
||||
|
||||
|
||||
def _build_prompt(data: dict) -> str:
|
||||
# Sanitize string values to prevent prompt injection via control sequences
|
||||
def s(val) -> str:
|
||||
def s(val, maxlen=200) -> str:
|
||||
if val is None:
|
||||
return "N/A"
|
||||
cleaned = str(val).replace("\n", " ").replace("\r", " ").strip()
|
||||
return cleaned[:200] # cap length per field
|
||||
return str(val).replace("\n", " ").replace("\r", " ").strip()[:maxlen]
|
||||
|
||||
vpic = data.get("vpic", {})
|
||||
risk = data.get("risk", {})
|
||||
alerts = data.get("alerts", [])
|
||||
vpic = data.get("vpic", {})
|
||||
risk = data.get("risk", {})
|
||||
alerts = data.get("alerts", [])
|
||||
recalls = data.get("recalls", [])
|
||||
complaints = data.get("complaints", [])
|
||||
|
||||
return (
|
||||
"Eres un experto en evaluación de vehículos usados en subastas americanas (Copart, IAAI).\n"
|
||||
"Analiza estos datos y genera un resumen ejecutivo en español de máximo 150 palabras.\n"
|
||||
"Sé directo y específico. Termina con una recomendación clara: COMPRAR, INVESTIGAR MÁS, o EVITAR.\n\n"
|
||||
"DATOS DEL VEHÍCULO:\n"
|
||||
f"- VIN: {s(data.get('vin'))}\n"
|
||||
f"- Vehículo: {s(vpic.get('ModelYear'))} {s(vpic.get('Make'))} {s(vpic.get('Model'))} {s(vpic.get('Trim'))}\n"
|
||||
f"- Odómetro: {s(data.get('odometer'))} millas\n"
|
||||
f"- Daño primario: {s(data.get('primary_damage'))}\n"
|
||||
f"- Daño secundario: {s(data.get('secondary_damage'))}\n"
|
||||
f"- Tipo de título: {s(data.get('title'))}\n"
|
||||
f"- Bid actual: ${s(data.get('bid'))}\n"
|
||||
f"- Recalls activos NHTSA: {s(risk.get('recall_count'))}\n"
|
||||
f"- Recalls críticos: {s(data.get('critical_recall_count'))}\n"
|
||||
f"- Total quejas propietarios: {s(risk.get('complaint_count'))}\n"
|
||||
f"- Quejas de motor: {s(data.get('engine_complaints'))}\n"
|
||||
f"- Quejas de transmisión: {s(data.get('transmission_complaints'))}\n"
|
||||
f"- Score de riesgo calculado: {s(risk.get('score'))}/100\n"
|
||||
f"- Alertas detectadas: {s('; '.join(alerts) if alerts else 'Ninguna')}\n\n"
|
||||
"RESUMEN EJECUTIVO:\n"
|
||||
)
|
||||
# Top recall names (component + campaign)
|
||||
recall_lines = []
|
||||
for r in recalls[:8]:
|
||||
comp = s(r.get("Component") or r.get("component") or "", 80)
|
||||
campaign = s(r.get("NHTSACampaignNumber") or r.get("campaignNumber") or "", 20)
|
||||
summary = s(r.get("Summary") or r.get("summary") or "", 120)
|
||||
if comp or summary:
|
||||
recall_lines.append(f" • [{campaign}] {comp}: {summary}")
|
||||
recalls_text = "\n".join(recall_lines) if recall_lines else " Ninguno"
|
||||
|
||||
# Complaint component breakdown (top 6)
|
||||
from collections import Counter
|
||||
comp_counter: Counter = Counter()
|
||||
for c in complaints:
|
||||
raw = (c.get("components") or c.get("Component") or c.get("component") or "")
|
||||
for part in raw.split(","):
|
||||
part = part.strip()
|
||||
if part:
|
||||
comp_counter[part[:50]] += 1
|
||||
complaint_breakdown = "\n".join(
|
||||
f" • {comp}: {cnt} quejas"
|
||||
for comp, cnt in comp_counter.most_common(6)
|
||||
) if comp_counter else " Sin datos de componentes"
|
||||
|
||||
critical_count = data.get("critical_recall_count", 0)
|
||||
engine_q = data.get("engine_complaints", 0)
|
||||
trans_q = data.get("transmission_complaints", 0)
|
||||
alerts_text = "; ".join(alerts) if alerts else "Ninguna"
|
||||
|
||||
vehicle = f"{s(vpic.get('ModelYear'))} {s(vpic.get('Make'))} {s(vpic.get('Model'))} {s(vpic.get('Trim'))}".strip()
|
||||
|
||||
return f"""Eres un experto en evaluación de vehículos usados en subastas americanas (Copart, IAAI).
|
||||
Tienes amplio conocimiento sobre problemas comunes por marca/modelo, qué tan costosas son las reparaciones,
|
||||
y cómo interpretar los datos de NHTSA para tomar decisiones de compra.
|
||||
|
||||
Analiza los siguientes datos y genera un análisis estructurado en español.
|
||||
Sé directo, técnico y específico. Explica el SIGNIFICADO real de cada dato, no solo lo repitas.
|
||||
|
||||
═══════════════════════════════════════════
|
||||
DATOS DEL VEHÍCULO
|
||||
═══════════════════════════════════════════
|
||||
Vehículo : {vehicle}
|
||||
VIN : {s(data.get('vin'))}
|
||||
Odómetro : {s(data.get('odometer'))} millas
|
||||
Daño prim.: {s(data.get('primary_damage'))}
|
||||
Daño sec. : {s(data.get('secondary_damage'))}
|
||||
Título : {s(data.get('title'))}
|
||||
Bid actual: ${s(data.get('bid'))}
|
||||
Score riesgo: {risk.get('score', 'N/A')}/100
|
||||
|
||||
RECALLS NHTSA ({risk.get('recall_count', 0)} total, {critical_count} críticos de motor/transmisión/frenos):
|
||||
{recalls_text}
|
||||
|
||||
QUEJAS DE PROPIETARIOS ({risk.get('complaint_count', 0)} total):
|
||||
{complaint_breakdown}
|
||||
→ Quejas de motor: {engine_q}
|
||||
→ Quejas de transmisión: {trans_q}
|
||||
|
||||
ALERTAS DETECTADAS: {alerts_text}
|
||||
═══════════════════════════════════════════
|
||||
|
||||
Genera el análisis con EXACTAMENTE estas 4 secciones:
|
||||
|
||||
**CONDICIÓN DEL VEHÍCULO**
|
||||
Explica qué significa el daño primario/secundario en términos prácticos: qué componentes pueden estar afectados, qué tan costosa puede ser la reparación, si el tipo de título implica restricciones de registro o seguro.
|
||||
|
||||
**ANÁLISIS DE RECALLS Y QUEJAS**
|
||||
Explica los recalls más importantes: qué falla concreta describen, si afectan seguridad crítica (motor, frenos, dirección, airbag) o son administrativos (etiquetas, software menor). Interpreta el patrón de quejas: si hay muchas quejas de motor significa un problema sistémico del modelo, no casos aislados. Menciona si este volumen de quejas es alto o normal para este modelo/año.
|
||||
|
||||
**FACTORES FINANCIEROS**
|
||||
Evalúa si el bid tiene sentido dado el daño, el odómetro, el título y los problemas conocidos. Estima el rango de reparación si aplica. Menciona si el modelo tiene piezas costosas o escasas.
|
||||
|
||||
**RECOMENDACIÓN FINAL**
|
||||
Termina con exactamente una de estas tres palabras en mayúsculas: COMPRAR, INVESTIGAR MÁS, o EVITAR.
|
||||
Justifica en 2 líneas por qué.
|
||||
|
||||
ANÁLISIS:
|
||||
"""
|
||||
|
||||
|
||||
async def analyze_vehicle(data: dict) -> str | None:
|
||||
|
||||
Reference in New Issue
Block a user