from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, Text, ARRAY, ForeignKey from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.sql import func from sqlalchemy.orm import relationship import uuid from database import Base class User(Base): """User model for authentication""" __tablename__ = "users" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) username = Column(String(100), unique=True, nullable=False) email = Column(String(255), unique=True, nullable=False) hashed_password = Column(String(255), nullable=False) full_name = Column(String(255)) role = Column(String(20), nullable=False, default="operator") is_active = Column(Boolean, default=True) created_at = Column(DateTime, server_default=func.now()) last_login = Column(DateTime) class Case(Base): """Unified case model - combines search case and result""" __tablename__ = "cases" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) created_at = Column(DateTime, server_default=func.now()) status = Column(String(20), default="active") # Ребёнок (Шаг 1) child_name = Column(String(255)) age_years = Column(Integer, nullable=False) gender = Column(String(1)) height_build = Column(Text) clothes_upper = Column(Text) clothes_lower = Column(Text) shoes = Column(Text) clothes_description = Column(Text) special_marks = Column(Text) phone_status = Column(String(20)) # Здоровье (Шаг 2) has_diagnosis = Column(Boolean, default=False) diagnosis_type = Column(ARRAY(String)) fitness_level = Column(String(20)) has_transport = Column(String(20), default="none") cant_swim = Column(Boolean, default=False) # Психотип (Шаг 2б) psychotype = Column(String(50)) psychotype_answers = Column(JSONB) # Обстоятельства (Шаг 3) loss_reason = Column(String(100)) loss_time = Column(DateTime) elapsed_hours = Column(Float) last_seen_direction = Column(String(10)) last_seen_reliability = Column(String(20)) last_seen_description = Column(Text) behavior_description = Column(Text) familiar_places = Column(Text) lost_before = Column(String(20)) # Среда (Шаг 4) season = Column(String(20)) temperature_c = Column(Float) precipitation = Column(String(20)) visibility = Column(String(20)) wind = Column(String(20)) terrain = Column(ARRAY(String)) # GPS (Шаг 4) tnp_lat = Column(Float) tnp_lon = Column(Float) tnp_address = Column(Text) # Ресурсы (Шаг 5) teams_count = Column(Integer) team_size = Column(Integer) has_dog = Column(Boolean, default=False) extra_resources = Column(ARRAY(String)) # Исход (заполняется после завершения) found_alive = Column(Boolean) found_distance_km = Column(Float) found_direction = Column(String(10)) found_location_type = Column(String(50)) found_lat = Column(Float) found_lon = Column(Float) search_duration_hours = Column(Float) who_found = Column(String(50)) # Мета confidence_avg = Column(Float) raw_text = Column(Text) analysis_log = Column(JSONB) class AnalysisLog(Base): """Analysis log for audit and debugging""" __tablename__ = "analysis_log" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) case_id = Column(UUID(as_uuid=True), ForeignKey("cases.id", ondelete="CASCADE"), nullable=False) user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL")) analysis_type = Column(String(50), nullable=False) input_data = Column(JSONB) output_data = Column(JSONB) execution_time = Column(Float) status = Column(String(20), default="success") error_message = Column(Text) created_at = Column(DateTime, server_default=func.now()) class RawDocument(Base): """Raw document storage for parsed reports""" __tablename__ = "raw_documents" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) filename = Column(String(255), nullable=False) raw_text = Column(Text) extracted_json = Column(JSONB) created_at = Column(DateTime, server_default=func.now())