Chat now fetches tenant inventory summary (brands, counts, low-stock) and injects it into the AI system prompt so responses prioritize local stock. VIN decoder uses free NHTSA vPIC API to decode 17-char VINs and auto-fills the vehicle selector dropdowns when a catalog match is found. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ from flask import Blueprint, request, jsonify, g
|
||||
from middleware import require_auth
|
||||
from tenant_db import get_master_conn, get_tenant_conn
|
||||
from services import catalog_service
|
||||
from services.vin_decoder import decode_vin
|
||||
|
||||
catalog_bp = Blueprint('catalog', __name__, url_prefix='/pos/api/catalog')
|
||||
|
||||
@@ -184,3 +185,126 @@ def search():
|
||||
data = catalog_service.smart_search(master, q, tenant, branch_id, limit)
|
||||
return jsonify({'data': data})
|
||||
return _with_conns(_do)
|
||||
|
||||
|
||||
# ─── VIN Decoder ───
|
||||
|
||||
@catalog_bp.route('/vin/<vin>', methods=['GET'])
|
||||
@require_auth('catalog.view')
|
||||
def decode_vin_route(vin):
|
||||
"""Decode a VIN and try to match to a brand/model/year in our catalog DB."""
|
||||
vin = (vin or "").strip().upper()
|
||||
if len(vin) != 17:
|
||||
return jsonify({'error': 'VIN debe tener exactamente 17 caracteres.'}), 400
|
||||
|
||||
try:
|
||||
info = decode_vin(vin)
|
||||
except Exception as e:
|
||||
return jsonify({'error': f'Error al decodificar VIN: {str(e)}'}), 502
|
||||
|
||||
if info.get('error'):
|
||||
return jsonify(info), 200 # Return info even with partial errors
|
||||
|
||||
# Try to match the decoded vehicle to our catalog DB
|
||||
db_match = None
|
||||
master = None
|
||||
try:
|
||||
master = get_master_conn()
|
||||
db_match = _match_vin_to_catalog(master, info)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
if master:
|
||||
try:
|
||||
master.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
result = {**info}
|
||||
if db_match:
|
||||
result['catalog_match'] = db_match
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
def _match_vin_to_catalog(master_conn, vin_info):
|
||||
"""Try to find brand_id, model_id, year_id, mye_id from decoded VIN info."""
|
||||
make = (vin_info.get('make') or '').upper().strip()
|
||||
model = (vin_info.get('model') or '').strip()
|
||||
year = vin_info.get('year')
|
||||
|
||||
if not make:
|
||||
return None
|
||||
|
||||
cur = master_conn.cursor()
|
||||
result = {}
|
||||
try:
|
||||
# Find brand (try exact, then LIKE)
|
||||
cur.execute(
|
||||
"SELECT id_brand, name_brand FROM brands WHERE UPPER(name_brand) = %s",
|
||||
(make,)
|
||||
)
|
||||
brand_row = cur.fetchone()
|
||||
if not brand_row:
|
||||
cur.execute(
|
||||
"SELECT id_brand, name_brand FROM brands WHERE UPPER(name_brand) LIKE %s ORDER BY name_brand LIMIT 1",
|
||||
(f"%{make}%",)
|
||||
)
|
||||
brand_row = cur.fetchone()
|
||||
|
||||
if not brand_row:
|
||||
return None
|
||||
|
||||
result['brand_id'] = brand_row[0]
|
||||
result['brand_name'] = brand_row[1]
|
||||
|
||||
# Find model
|
||||
if model:
|
||||
cur.execute(
|
||||
"""SELECT m.id_model, m.name_model
|
||||
FROM models m
|
||||
WHERE m.brand_id = %s AND UPPER(m.name_model) LIKE %s
|
||||
ORDER BY m.name_model LIMIT 5""",
|
||||
(brand_row[0], f"%{model.upper()}%")
|
||||
)
|
||||
model_row = cur.fetchone()
|
||||
if model_row:
|
||||
result['model_id'] = model_row[0]
|
||||
result['model_name'] = model_row[1]
|
||||
|
||||
# Find year
|
||||
if year:
|
||||
cur.execute(
|
||||
"SELECT id_year, year_car FROM years WHERE year_car = %s",
|
||||
(int(year),)
|
||||
)
|
||||
year_row = cur.fetchone()
|
||||
if year_row:
|
||||
result['year_id'] = year_row[0]
|
||||
result['year_car'] = year_row[1]
|
||||
|
||||
# Find MYE options
|
||||
cur.execute(
|
||||
"""SELECT mye.id_mye, e.name_engine, mye.trim_level
|
||||
FROM model_year_engine mye
|
||||
JOIN engines e ON e.id_engine = mye.engine_id
|
||||
WHERE mye.model_id = %s AND mye.year_id = %s
|
||||
ORDER BY e.name_engine
|
||||
LIMIT 10""",
|
||||
(model_row[0], year_row[0])
|
||||
)
|
||||
mye_rows = cur.fetchall()
|
||||
if mye_rows:
|
||||
result['engines'] = [
|
||||
{'id_mye': r[0], 'name_engine': r[1], 'trim_level': r[2]}
|
||||
for r in mye_rows
|
||||
]
|
||||
# Auto-select if only one engine
|
||||
if len(mye_rows) == 1:
|
||||
result['id_mye'] = mye_rows[0][0]
|
||||
|
||||
return result
|
||||
except Exception:
|
||||
return None
|
||||
finally:
|
||||
cur.close()
|
||||
|
||||
@@ -23,8 +23,23 @@ def chat():
|
||||
|
||||
history = body.get("history") or []
|
||||
|
||||
# Call AI
|
||||
ai_response = ai_chat.chat(user_message, history)
|
||||
# Fetch inventory context so the AI knows what this tenant has in stock
|
||||
inventory_context = None
|
||||
tenant_for_context = None
|
||||
try:
|
||||
tenant_for_context = get_tenant_conn(g.tenant_id)
|
||||
inventory_context = ai_chat.get_inventory_context(tenant_for_context, g.branch_id)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
if tenant_for_context:
|
||||
try:
|
||||
tenant_for_context.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Call AI with inventory context
|
||||
ai_response = ai_chat.chat(user_message, history, inventory_context=inventory_context)
|
||||
|
||||
search_results = []
|
||||
vehicle_match = None
|
||||
|
||||
Reference in New Issue
Block a user