Backend: - Agregar campo ai_prompt a tabla questions - Endpoint analyze-image recibe custom_prompt - Validación de imagen apropiada (sugiere cambiar foto si no corresponde) - Script de migración migrate_ai_prompt.py Frontend: - Campo de texto para configurar prompt de IA en editor de preguntas - Envía custom_prompt al endpoint de análisis - UI con fondo morado para sección de IA La IA ahora analiza fotos según el contexto específico de cada pregunta y sugiere cambiar la imagen si no corresponde al componente solicitado.
186 lines
7.4 KiB
Python
186 lines
7.4 KiB
Python
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Text, JSON, Float
|
|
from sqlalchemy.orm import relationship
|
|
from sqlalchemy.sql import func
|
|
from app.core.database import Base
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
username = Column(String(50), unique=True, index=True, nullable=False)
|
|
email = Column(String(100), unique=True, index=True)
|
|
password_hash = Column(String(255), nullable=False)
|
|
role = Column(String(20), nullable=False) # admin, mechanic, asesor
|
|
full_name = Column(String(100))
|
|
is_active = Column(Boolean, default=True)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
# Relationships
|
|
checklists_created = relationship("Checklist", back_populates="creator")
|
|
inspections = relationship("Inspection", back_populates="mechanic")
|
|
api_tokens = relationship("APIToken", back_populates="user", cascade="all, delete-orphan")
|
|
|
|
|
|
class APIToken(Base):
|
|
__tablename__ = "api_tokens"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
token = Column(String(100), unique=True, index=True, nullable=False)
|
|
description = Column(String(200))
|
|
is_active = Column(Boolean, default=True)
|
|
last_used_at = Column(DateTime(timezone=True))
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
# Relationship
|
|
user = relationship("User", back_populates="api_tokens")
|
|
|
|
|
|
class Checklist(Base):
|
|
__tablename__ = "checklists"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
name = Column(String(200), nullable=False)
|
|
description = Column(Text)
|
|
ai_mode = Column(String(20), default="off") # off, assisted, copilot
|
|
scoring_enabled = Column(Boolean, default=True)
|
|
max_score = Column(Integer, default=0)
|
|
logo_url = Column(String(500))
|
|
is_active = Column(Boolean, default=True)
|
|
created_by = Column(Integer, ForeignKey("users.id"))
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
|
|
# Relationships
|
|
creator = relationship("User", back_populates="checklists_created")
|
|
questions = relationship("Question", back_populates="checklist", cascade="all, delete-orphan")
|
|
inspections = relationship("Inspection", back_populates="checklist")
|
|
|
|
|
|
class Question(Base):
|
|
__tablename__ = "questions"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
checklist_id = Column(Integer, ForeignKey("checklists.id"), nullable=False)
|
|
section = Column(String(100)) # Sistema eléctrico, Frenos, etc
|
|
text = Column(Text, nullable=False)
|
|
type = Column(String(30), nullable=False) # pass_fail, good_bad, text, etc
|
|
points = Column(Integer, default=1)
|
|
options = Column(JSON) # Para multiple choice
|
|
order = Column(Integer, default=0)
|
|
allow_photos = Column(Boolean, default=True)
|
|
max_photos = Column(Integer, default=3)
|
|
requires_comment_on_fail = Column(Boolean, default=False)
|
|
|
|
# Conditional logic
|
|
parent_question_id = Column(Integer, ForeignKey("questions.id"), nullable=True)
|
|
show_if_answer = Column(String(50), nullable=True) # Valor que dispara esta pregunta
|
|
|
|
# AI Analysis
|
|
ai_prompt = Column(Text, nullable=True) # Prompt personalizado para análisis de IA de esta pregunta
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
# Relationships
|
|
checklist = relationship("Checklist", back_populates="questions")
|
|
answers = relationship("Answer", back_populates="question")
|
|
subquestions = relationship("Question", backref="parent", remote_side=[id])
|
|
|
|
|
|
|
|
class Inspection(Base):
|
|
__tablename__ = "inspections"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
checklist_id = Column(Integer, ForeignKey("checklists.id"), nullable=False)
|
|
mechanic_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
|
|
# Datos de la OR
|
|
or_number = Column(String(50))
|
|
work_order_number = Column(String(50))
|
|
|
|
# Datos del vehículo
|
|
vehicle_plate = Column(String(20), nullable=False, index=True)
|
|
vehicle_brand = Column(String(50))
|
|
vehicle_model = Column(String(100))
|
|
vehicle_km = Column(Integer)
|
|
client_name = Column(String(200))
|
|
|
|
# Scoring
|
|
score = Column(Integer, default=0)
|
|
max_score = Column(Integer, default=0)
|
|
percentage = Column(Float, default=0.0)
|
|
flagged_items_count = Column(Integer, default=0)
|
|
|
|
# Estado
|
|
status = Column(String(20), default="draft") # draft, completed, inactive
|
|
is_active = Column(Boolean, default=True)
|
|
|
|
# Firma
|
|
signature_data = Column(Text) # Base64 de la firma
|
|
signed_at = Column(DateTime(timezone=True))
|
|
|
|
# Timestamps
|
|
started_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
completed_at = Column(DateTime(timezone=True))
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
|
|
# Relationships
|
|
checklist = relationship("Checklist", back_populates="inspections")
|
|
mechanic = relationship("User", back_populates="inspections")
|
|
answers = relationship("Answer", back_populates="inspection", cascade="all, delete-orphan")
|
|
|
|
|
|
class Answer(Base):
|
|
__tablename__ = "answers"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
inspection_id = Column(Integer, ForeignKey("inspections.id"), nullable=False)
|
|
question_id = Column(Integer, ForeignKey("questions.id"), nullable=False)
|
|
|
|
answer_value = Column(Text) # La respuesta del mecánico
|
|
status = Column(String(20), default="ok") # ok, warning, critical, info
|
|
points_earned = Column(Integer, default=0)
|
|
comment = Column(Text) # Comentarios adicionales
|
|
|
|
ai_analysis = Column(JSON) # Análisis de IA si aplica
|
|
is_flagged = Column(Boolean, default=False) # Si requiere atención
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
|
|
# Relationships
|
|
inspection = relationship("Inspection", back_populates="answers")
|
|
question = relationship("Question", back_populates="answers")
|
|
media_files = relationship("MediaFile", back_populates="answer", cascade="all, delete-orphan")
|
|
|
|
|
|
class MediaFile(Base):
|
|
__tablename__ = "media_files"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
answer_id = Column(Integer, ForeignKey("answers.id"), nullable=False)
|
|
|
|
file_path = Column(String(500), nullable=False)
|
|
file_type = Column(String(20), default="image") # image, video
|
|
caption = Column(Text)
|
|
order = Column(Integer, default=0)
|
|
|
|
uploaded_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
# Relationships
|
|
answer = relationship("Answer", back_populates="media_files")
|
|
|
|
|
|
class AIConfiguration(Base):
|
|
__tablename__ = "ai_configurations"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
provider = Column(String(50), nullable=False) # openai, gemini
|
|
api_key = Column(Text, nullable=False)
|
|
model_name = Column(String(100), nullable=False)
|
|
is_active = Column(Boolean, default=True)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|