feat: Validación inteligente de contexto en análisis de imágenes IA

Backend (v1.0.84):
- Agregado campo 'context_match' en respuesta JSON de análisis IA
- IA evalúa si imagen corresponde al contexto de la pregunta
- Tres niveles de validación: prompt personalizado, pregunta específica, análisis general
- Detección automática de imágenes fuera de contexto con recomendaciones específicas

Frontend (v1.0.84):
- Sistema de alertas cuando IA detecta imágenes que no corresponden
- Popup de advertencia muestra qué imágenes no coinciden y por qué
- Opción para eliminar automáticamente fotos incorrectas y cargar nuevas
- Validación con frases clave: "no corresponde", "no coincide", "no relacionad"
- Previene que mecánicos carguen imágenes irrelevantes a las preguntas

Evita errores en inspecciones al garantizar que cada foto corresponda a su pregunta específica
This commit is contained in:
2025-11-30 22:35:31 -03:00
parent 7b39648be5
commit 2db2833f27
3 changed files with 76 additions and 9 deletions

View File

@@ -204,7 +204,7 @@ def send_completed_inspection_to_n8n(inspection, db):
# No lanzamos excepción para no interrumpir el flujo normal # No lanzamos excepción para no interrumpir el flujo normal
BACKEND_VERSION = "1.0.83" BACKEND_VERSION = "1.0.84"
app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION) app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
# S3/MinIO configuration # S3/MinIO configuration
@@ -2582,7 +2582,8 @@ Responde SOLO en formato JSON válido (sin markdown, sin ```json):
"status": "ok", "status": "ok",
"observations": "Describe lo que observas en la imagen en relación a la tarea solicitada", "observations": "Describe lo que observas en la imagen en relación a la tarea solicitada",
"recommendation": "Acción sugerida basada en lo observado", "recommendation": "Acción sugerida basada en lo observado",
"confidence": 0.85 "confidence": 0.85,
"context_match": true
}} }}
VALORES DE STATUS: VALORES DE STATUS:
@@ -2590,7 +2591,13 @@ VALORES DE STATUS:
- "minor": Presenta observaciones menores o advertencias - "minor": Presenta observaciones menores o advertencias
- "critical": Presenta problemas graves o no cumple con lo esperado - "critical": Presenta problemas graves o no cumple con lo esperado
IMPORTANTE: Si la tarea requiere verificar funcionamiento (algo encendido, prendido, activo) pero la imagen muestra el componente apagado o en reposo, usa status "critical" e indica en "recommendation" que se necesita una foto con el componente funcionando o un video.""" VALOR DE CONTEXT_MATCH:
- true: La imagen SÍ corresponde al contexto de la pregunta/tarea
- false: La imagen NO corresponde (ej: pregunta sobre luces pero muestra motor)
IMPORTANTE:
- Si la imagen NO corresponde al contexto de la pregunta, establece context_match=false y en observations indica qué se esperaba ver vs qué se muestra
- Si la tarea requiere verificar funcionamiento (algo encendido, prendido, activo) pero la imagen muestra el componente apagado o en reposo, usa status "critical" y context_match=false, indica en "recommendation" que se necesita una foto con el componente funcionando o un video."""
user_message = f"Pregunta de inspección: {question_obj.text}\n\nAnaliza esta imagen según la tarea especificada." user_message = f"Pregunta de inspección: {question_obj.text}\n\nAnaliza esta imagen según la tarea especificada."
else: else:
@@ -2611,15 +2618,16 @@ Sé directo y enfócate solo en lo que la pregunta solicita.
Considera el kilometraje y características del vehículo para contextualizar tu análisis. Considera el kilometraje y características del vehículo para contextualizar tu análisis.
VALIDACIÓN DE IMAGEN: VALIDACIÓN DE IMAGEN:
- Si la imagen NO corresponde al contexto de la pregunta, indica en "recommendation" que deben cambiar la foto - Si la imagen NO corresponde al contexto de la pregunta, establece context_match=false y explica en observations qué se esperaba vs qué se muestra
- Si la imagen es borrosa o no permite análisis, indica en "recommendation" que tomen otra foto más clara - Si la imagen es borrosa o no permite análisis, establece context_match=false e indica en recommendation que tomen otra foto más clara
Responde SOLO en formato JSON válido (sin markdown, sin ```json): Responde SOLO en formato JSON válido (sin markdown, sin ```json):
{{ {{
"status": "ok", "status": "ok",
"observations": "Respuesta técnica específica a: {question_text}", "observations": "Respuesta técnica específica a: {question_text}",
"recommendation": "Acción técnica recomendada o mensaje si la foto no es apropiada", "recommendation": "Acción técnica recomendada o mensaje si la foto no es apropiada",
"confidence": 0.85 "confidence": 0.85,
"context_match": true
}} }}
NOTA IMPORTANTE sobre el campo "status": NOTA IMPORTANTE sobre el campo "status":
@@ -2627,6 +2635,10 @@ NOTA IMPORTANTE sobre el campo "status":
- Usa "minor" si hay problemas leves que requieren atención pero no son críticos - Usa "minor" si hay problemas leves que requieren atención pero no son críticos
- Usa "critical" si hay problemas graves que requieren reparación inmediata - Usa "critical" si hay problemas graves que requieren reparación inmediata
VALOR DE CONTEXT_MATCH:
- true: La imagen SÍ corresponde y es apropiada para responder la pregunta
- false: La imagen NO corresponde al contexto de la pregunta (ej: pregunta sobre luces pero imagen muestra motor)
RECUERDA: RECUERDA:
- Responde SOLO lo que la pregunta pide - Responde SOLO lo que la pregunta pide
- No des información genérica del vehículo - No des información genérica del vehículo
@@ -2647,16 +2659,20 @@ Analiza la imagen y proporciona:
2. Nivel de criticidad (ok/minor/critical) 2. Nivel de criticidad (ok/minor/critical)
3. Observaciones técnicas breves 3. Observaciones técnicas breves
4. Recomendación de acción 4. Recomendación de acción
5. Si la imagen corresponde al contexto automotriz
Responde SOLO en formato JSON válido (sin markdown, sin ```json): Responde SOLO en formato JSON válido (sin markdown, sin ```json):
{{ {{
"status": "ok", "status": "ok",
"observations": "descripción técnica del componente", "observations": "descripción técnica del componente",
"recommendation": "acción sugerida", "recommendation": "acción sugerida",
"confidence": 0.85 "confidence": 0.85,
"context_match": true
}} }}
NOTA: "status" debe ser "ok" (bueno), "minor" (problemas leves) o "critical" (problemas graves).""" NOTA:
- "status" debe ser "ok" (bueno), "minor" (problemas leves) o "critical" (problemas graves)
- "context_match" debe ser true si la imagen muestra un componente vehicular relevante, false si no corresponde."""
user_message = "Analiza este componente del vehículo para la inspección general." user_message = "Analiza este componente del vehículo para la inspección general."
print(f"\n🤖 PROMPT ENVIADO AL AI:") print(f"\n🤖 PROMPT ENVIADO AL AI:")

View File

@@ -1,7 +1,7 @@
{ {
"name": "checklist-frontend", "name": "checklist-frontend",
"private": true, "private": true,
"version": "1.0.83", "version": "1.0.84",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -4444,6 +4444,57 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
if (analyses.length > 0) { if (analyses.length > 0) {
console.log(`✅ Análisis IA guardado (${analyses.length} análisis)`) console.log(`✅ Análisis IA guardado (${analyses.length} análisis)`)
console.log(`📝 Las observaciones quedan para que el mecánico las escriba manualmente`) console.log(`📝 Las observaciones quedan para que el mecánico las escriba manualmente`)
// Verificar si alguna imagen no corresponde al contexto
const invalidImages = []
analyses.forEach((analysis, idx) => {
if (analysis.analysis) {
// Verificar si la IA indica que la imagen no corresponde
const obs = analysis.analysis.observations?.toLowerCase() || ''
const isInvalid =
obs.includes('no corresponde') ||
obs.includes('no coincide') ||
obs.includes('imagen incorrecta') ||
obs.includes('no es relevante') ||
obs.includes('no relacionad') ||
analysis.analysis.context_match === false
if (isInvalid) {
invalidImages.push({
index: idx + 1,
fileName: analysis.fileName,
reason: analysis.analysis.observations
})
}
}
})
// Mostrar advertencia si hay imágenes que no corresponden
if (invalidImages.length > 0) {
let warningMsg = '⚠️ ATENCIÓN: Se detectaron imágenes que podrían NO corresponder al contexto:\n\n'
invalidImages.forEach(img => {
warningMsg += `📸 Imagen ${img.index}: ${img.reason}\n\n`
})
warningMsg += '¿Deseas reemplazar estas imágenes?\n\n'
warningMsg += 'Presiona OK para eliminar las imágenes incorrectas y cargar nuevas.\n'
warningMsg += 'Presiona Cancelar si estás seguro de que las imágenes son correctas.'
if (confirm(warningMsg)) {
// Eliminar las imágenes que no corresponden
const validPhotos = files.filter((_, idx) => !invalidImages.some(inv => inv.index === idx + 1))
setAnswers(prev => ({
...prev,
[questionId]: {
...(prev[questionId] || { value: '', observations: '', photos: [] }),
photos: validPhotos,
aiAnalysis: undefined,
documentsLoaded: false // Resetear para que vuelva a cargar
}
}))
alert('📸 Por favor carga nuevas imágenes que correspondan a: ' + question.text)
return // No mostrar el popup de éxito
}
}
} else { } else {
console.log(' No se generaron análisis IA, pero documentos procesados') console.log(' No se generaron análisis IA, pero documentos procesados')
} }