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 "—",
|
"bid": bid or "—",
|
||||||
"vpic": vpic,
|
"vpic": vpic,
|
||||||
"risk": risk,
|
"risk": risk,
|
||||||
|
"recalls": recalls,
|
||||||
|
"complaints": complaints,
|
||||||
"critical_recall_count": extended["critical_recall_count"],
|
"critical_recall_count": extended["critical_recall_count"],
|
||||||
"engine_complaints": extended["engine_complaints"],
|
"engine_complaints": extended["engine_complaints"],
|
||||||
"transmission_complaints": extended["transmission_complaints"],
|
"transmission_complaints": extended["transmission_complaints"],
|
||||||
|
|||||||
+86
-29
@@ -4,42 +4,99 @@ OLLAMA_URL = "http://localhost:11434/api/generate"
|
|||||||
PRIMARY_MODEL = "DealAnalyzer"
|
PRIMARY_MODEL = "DealAnalyzer"
|
||||||
FALLBACK_MODEL = "qwen2.5:14b"
|
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:
|
def _build_prompt(data: dict) -> str:
|
||||||
# Sanitize string values to prevent prompt injection via control sequences
|
def s(val, maxlen=200) -> str:
|
||||||
def s(val) -> str:
|
|
||||||
if val is None:
|
if val is None:
|
||||||
return "N/A"
|
return "N/A"
|
||||||
cleaned = str(val).replace("\n", " ").replace("\r", " ").strip()
|
return str(val).replace("\n", " ").replace("\r", " ").strip()[:maxlen]
|
||||||
return cleaned[:200] # cap length per field
|
|
||||||
|
|
||||||
vpic = data.get("vpic", {})
|
vpic = data.get("vpic", {})
|
||||||
risk = data.get("risk", {})
|
risk = data.get("risk", {})
|
||||||
alerts = data.get("alerts", [])
|
alerts = data.get("alerts", [])
|
||||||
|
recalls = data.get("recalls", [])
|
||||||
|
complaints = data.get("complaints", [])
|
||||||
|
|
||||||
return (
|
# Top recall names (component + campaign)
|
||||||
"Eres un experto en evaluación de vehículos usados en subastas americanas (Copart, IAAI).\n"
|
recall_lines = []
|
||||||
"Analiza estos datos y genera un resumen ejecutivo en español de máximo 150 palabras.\n"
|
for r in recalls[:8]:
|
||||||
"Sé directo y específico. Termina con una recomendación clara: COMPRAR, INVESTIGAR MÁS, o EVITAR.\n\n"
|
comp = s(r.get("Component") or r.get("component") or "", 80)
|
||||||
"DATOS DEL VEHÍCULO:\n"
|
campaign = s(r.get("NHTSACampaignNumber") or r.get("campaignNumber") or "", 20)
|
||||||
f"- VIN: {s(data.get('vin'))}\n"
|
summary = s(r.get("Summary") or r.get("summary") or "", 120)
|
||||||
f"- Vehículo: {s(vpic.get('ModelYear'))} {s(vpic.get('Make'))} {s(vpic.get('Model'))} {s(vpic.get('Trim'))}\n"
|
if comp or summary:
|
||||||
f"- Odómetro: {s(data.get('odometer'))} millas\n"
|
recall_lines.append(f" • [{campaign}] {comp}: {summary}")
|
||||||
f"- Daño primario: {s(data.get('primary_damage'))}\n"
|
recalls_text = "\n".join(recall_lines) if recall_lines else " Ninguno"
|
||||||
f"- Daño secundario: {s(data.get('secondary_damage'))}\n"
|
|
||||||
f"- Tipo de título: {s(data.get('title'))}\n"
|
# Complaint component breakdown (top 6)
|
||||||
f"- Bid actual: ${s(data.get('bid'))}\n"
|
from collections import Counter
|
||||||
f"- Recalls activos NHTSA: {s(risk.get('recall_count'))}\n"
|
comp_counter: Counter = Counter()
|
||||||
f"- Recalls críticos: {s(data.get('critical_recall_count'))}\n"
|
for c in complaints:
|
||||||
f"- Total quejas propietarios: {s(risk.get('complaint_count'))}\n"
|
raw = (c.get("components") or c.get("Component") or c.get("component") or "")
|
||||||
f"- Quejas de motor: {s(data.get('engine_complaints'))}\n"
|
for part in raw.split(","):
|
||||||
f"- Quejas de transmisión: {s(data.get('transmission_complaints'))}\n"
|
part = part.strip()
|
||||||
f"- Score de riesgo calculado: {s(risk.get('score'))}/100\n"
|
if part:
|
||||||
f"- Alertas detectadas: {s('; '.join(alerts) if alerts else 'Ninguna')}\n\n"
|
comp_counter[part[:50]] += 1
|
||||||
"RESUMEN EJECUTIVO:\n"
|
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:
|
async def analyze_vehicle(data: dict) -> str | None:
|
||||||
|
|||||||
Reference in New Issue
Block a user