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
BACKEND_VERSION = "1.0.83"
BACKEND_VERSION = "1.0.84"
app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
# S3/MinIO configuration
@@ -2582,7 +2582,8 @@ Responde SOLO en formato JSON válido (sin markdown, sin ```json):
"status": "ok",
"observations": "Describe lo que observas en la imagen en relación a la tarea solicitada",
"recommendation": "Acción sugerida basada en lo observado",
"confidence": 0.85
"confidence": 0.85,
"context_match": true
}}
VALORES DE STATUS:
@@ -2590,7 +2591,13 @@ VALORES DE STATUS:
- "minor": Presenta observaciones menores o advertencias
- "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."
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.
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 es borrosa o no permite análisis, indica en "recommendation" que tomen otra foto más clara
- 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, 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):
{{
"status": "ok",
"observations": "Respuesta técnica específica a: {question_text}",
"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":
@@ -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 "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:
- Responde SOLO lo que la pregunta pide
- 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)
3. Observaciones técnicas breves
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):
{{
"status": "ok",
"observations": "descripción técnica del componente",
"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."
print(f"\n🤖 PROMPT ENVIADO AL AI:")

View File

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

View File

@@ -4444,6 +4444,57 @@ function InspectionModal({ checklist, existingInspection, user, onClose, onCompl
if (analyses.length > 0) {
console.log(`✅ Análisis IA guardado (${analyses.length} análisis)`)
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 {
console.log(' No se generaron análisis IA, pero documentos procesados')
}