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:
@@ -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:")
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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')
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user