feat(pos): add inventory-aware chat context (#31) and VIN decoder (#17)

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:
2026-04-04 08:14:45 +00:00
parent 5d5a2777eb
commit f9589f4a4e
6 changed files with 406 additions and 5 deletions

View File

@@ -89,10 +89,80 @@ El search_query SIEMPRE debe ser en ingles (el catalogo TecDoc esta en ingles).
"""
def chat(user_message, conversation_history=None):
"""Send a message to the AI and get a response with search suggestions."""
def get_inventory_context(tenant_conn, branch_id=None):
"""Build a summary string of the tenant's inventory for AI context.
Returns a string like:
Este negocio tiene 1234 productos en inventario.
Categorias: BOSCH (45), MONROE (32), ACDelco (28), ...
Productos con stock bajo (<=3): 15
"""
cur = tenant_conn.cursor()
try:
# Total items
where = "i.is_active = true"
params = []
if branch_id:
where += " AND i.branch_id = %s"
params.append(branch_id)
cur.execute(f"SELECT COUNT(*) FROM inventory i WHERE {where}", params)
total = cur.fetchone()[0] or 0
if total == 0:
return "CONTEXTO DEL INVENTARIO:\nEste negocio aun no tiene productos en inventario."
# Top brands with counts
cur.execute(f"""
SELECT i.brand, COUNT(*) as cnt
FROM inventory i
WHERE {where} AND i.brand IS NOT NULL AND i.brand != ''
GROUP BY i.brand
ORDER BY cnt DESC
LIMIT 15
""", params)
brands = cur.fetchall()
brand_list = ", ".join(f"{row[0]} ({row[1]})" for row in brands if row[0])
# Products with low stock (<=3)
cur.execute(f"""
SELECT COUNT(*) FROM inventory i
WHERE {where}
AND COALESCE((SELECT SUM(quantity) FROM inventory_operations WHERE inventory_id = i.id), 0) <= 3
""", params)
low_stock = cur.fetchone()[0] or 0
lines = [
"CONTEXTO DEL INVENTARIO:",
f"Este negocio tiene {total} productos en inventario.",
]
if brand_list:
lines.append(f"Marcas disponibles: {brand_list}")
lines.append(f"Productos con stock bajo (<=3 unidades): {low_stock}")
lines.append("IMPORTANTE: Cuando busques partes, SIEMPRE prioriza lo que el negocio tiene en inventario local.")
return "\n".join(lines)
except Exception:
return ""
finally:
cur.close()
def chat(user_message, conversation_history=None, inventory_context=None):
"""Send a message to the AI and get a response with search suggestions.
Args:
user_message: The user's chat message.
conversation_history: Previous messages in the conversation.
inventory_context: Optional inventory summary string to inject into the system prompt.
"""
_validate_model(MODEL) # Block paid models
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
system_content = SYSTEM_PROMPT
if inventory_context:
system_content = SYSTEM_PROMPT + "\n\n" + inventory_context
messages = [{"role": "system", "content": system_content}]
if conversation_history:
messages.extend(conversation_history)
messages.append({"role": "user", "content": user_message})