from sqlalchemy import Column, String, DateTime, Boolean, Text, Date, Integer from datetime import datetime from backend.database import Base class Emitter(Base): __tablename__ = "emitters" id = Column(String, primary_key=True, index=True) unit_type = Column(String, nullable=False) last_seen = Column(DateTime, default=datetime.utcnow) last_file = Column(String, nullable=False) status = Column(String, nullable=False) notes = Column(String, nullable=True) class RosterUnit(Base): """ Roster table: represents our *intended assignment* of a unit. This is editable from the GUI. Supports multiple device types (seismograph, modem, sound_level_meter) with type-specific fields. """ __tablename__ = "roster" # Core fields (all device types) id = Column(String, primary_key=True, index=True) unit_type = Column(String, default="series3") # Backward compatibility device_type = Column(String, default="seismograph") # "seismograph" | "modem" | "sound_level_meter" deployed = Column(Boolean, default=True) retired = Column(Boolean, default=False) note = Column(String, nullable=True) project_id = Column(String, nullable=True) location = Column(String, nullable=True) # Legacy field - use address/coordinates instead address = Column(String, nullable=True) # Human-readable address coordinates = Column(String, nullable=True) # Lat,Lon format: "34.0522,-118.2437" last_updated = Column(DateTime, default=datetime.utcnow) # Seismograph-specific fields (nullable for modems and SLMs) last_calibrated = Column(Date, nullable=True) next_calibration_due = Column(Date, nullable=True) # Modem assignment (shared by seismographs and SLMs) deployed_with_modem_id = Column(String, nullable=True) # FK to another RosterUnit (device_type=modem) # Modem-specific fields (nullable for seismographs and SLMs) ip_address = Column(String, nullable=True) phone_number = Column(String, nullable=True) hardware_model = Column(String, nullable=True) # Sound Level Meter-specific fields (nullable for seismographs and modems) slm_host = Column(String, nullable=True) # Device IP or hostname slm_tcp_port = Column(Integer, nullable=True) # TCP control port (default 2255) slm_ftp_port = Column(Integer, nullable=True) # FTP data retrieval port (default 21) slm_model = Column(String, nullable=True) # NL-43, NL-53, etc. slm_serial_number = Column(String, nullable=True) # Device serial number slm_frequency_weighting = Column(String, nullable=True) # A, C, Z slm_time_weighting = Column(String, nullable=True) # F (Fast), S (Slow), I (Impulse) slm_measurement_range = Column(String, nullable=True) # e.g., "30-130 dB" slm_last_check = Column(DateTime, nullable=True) # Last communication check class IgnoredUnit(Base): """ Ignored units: units that report but should be filtered out from unknown emitters. Used to suppress noise from old projects. """ __tablename__ = "ignored_units" id = Column(String, primary_key=True, index=True) reason = Column(String, nullable=True) ignored_at = Column(DateTime, default=datetime.utcnow) class UnitHistory(Base): """ Unit history: complete timeline of changes to each unit. Tracks note changes, status changes, deployment/benched events, and more. """ __tablename__ = "unit_history" id = Column(Integer, primary_key=True, autoincrement=True) unit_id = Column(String, nullable=False, index=True) # FK to RosterUnit.id change_type = Column(String, nullable=False) # note_change, deployed_change, retired_change, etc. field_name = Column(String, nullable=True) # Which field changed old_value = Column(Text, nullable=True) # Previous value new_value = Column(Text, nullable=True) # New value changed_at = Column(DateTime, default=datetime.utcnow, nullable=False, index=True) source = Column(String, default="manual") # manual, csv_import, telemetry, offline_sync notes = Column(Text, nullable=True) # Optional reason/context for the change class UserPreferences(Base): """ User preferences: persistent storage for application settings. Single-row table (id=1) to store global user preferences. """ __tablename__ = "user_preferences" id = Column(Integer, primary_key=True, default=1) timezone = Column(String, default="America/New_York") theme = Column(String, default="auto") # auto, light, dark auto_refresh_interval = Column(Integer, default=10) # seconds date_format = Column(String, default="MM/DD/YYYY") table_rows_per_page = Column(Integer, default=25) calibration_interval_days = Column(Integer, default=365) calibration_warning_days = Column(Integer, default=30) status_ok_threshold_hours = Column(Integer, default=12) status_pending_threshold_hours = Column(Integer, default=24) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) # ============================================================================ # Project Management System # ============================================================================ class ProjectType(Base): """ Project type templates: defines available project types and their capabilities. Pre-populated with: sound_monitoring, vibration_monitoring, combined. """ __tablename__ = "project_types" id = Column(String, primary_key=True) # sound_monitoring, vibration_monitoring, combined name = Column(String, nullable=False, unique=True) # "Sound Monitoring", "Vibration Monitoring" description = Column(Text, nullable=True) icon = Column(String, nullable=True) # Icon identifier for UI supports_sound = Column(Boolean, default=False) # Enables SLM features supports_vibration = Column(Boolean, default=False) # Enables seismograph features created_at = Column(DateTime, default=datetime.utcnow) class Project(Base): """ Projects: top-level organization for monitoring work. Type-aware to enable/disable features based on project_type_id. """ __tablename__ = "projects" id = Column(String, primary_key=True, index=True) # UUID name = Column(String, nullable=False, unique=True) description = Column(Text, nullable=True) project_type_id = Column(String, nullable=False) # FK to ProjectType.id status = Column(String, default="active") # active, completed, archived # Project metadata client_name = Column(String, nullable=True) site_address = Column(String, nullable=True) site_coordinates = Column(String, nullable=True) # "lat,lon" start_date = Column(Date, nullable=True) end_date = Column(Date, nullable=True) created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) class MonitoringLocation(Base): """ Monitoring locations: generic location for monitoring activities. Can be NRL (Noise Recording Location) for sound projects, or monitoring point for vibration projects. """ __tablename__ = "monitoring_locations" id = Column(String, primary_key=True, index=True) # UUID project_id = Column(String, nullable=False, index=True) # FK to Project.id location_type = Column(String, nullable=False) # "sound" | "vibration" name = Column(String, nullable=False) # NRL-001, VP-North, etc. description = Column(Text, nullable=True) coordinates = Column(String, nullable=True) # "lat,lon" address = Column(String, nullable=True) # Type-specific metadata stored as JSON # For sound: {"ambient_conditions": "urban", "expected_sources": ["traffic"]} # For vibration: {"ground_type": "bedrock", "depth": "10m"} location_metadata = Column(Text, nullable=True) created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) class UnitAssignment(Base): """ Unit assignments: links devices (SLMs or seismographs) to monitoring locations. Supports temporary assignments with assigned_until. """ __tablename__ = "unit_assignments" id = Column(String, primary_key=True, index=True) # UUID unit_id = Column(String, nullable=False, index=True) # FK to RosterUnit.id location_id = Column(String, nullable=False, index=True) # FK to MonitoringLocation.id assigned_at = Column(DateTime, default=datetime.utcnow) assigned_until = Column(DateTime, nullable=True) # Null = indefinite status = Column(String, default="active") # active, completed, cancelled notes = Column(Text, nullable=True) # Denormalized for efficient queries device_type = Column(String, nullable=False) # sound_level_meter | seismograph project_id = Column(String, nullable=False, index=True) # FK to Project.id created_at = Column(DateTime, default=datetime.utcnow) class ScheduledAction(Base): """ Scheduled actions: automation for recording start/stop/download. Terra-View executes these by calling SLMM or SFM endpoints. """ __tablename__ = "scheduled_actions" id = Column(String, primary_key=True, index=True) # UUID project_id = Column(String, nullable=False, index=True) # FK to Project.id location_id = Column(String, nullable=False, index=True) # FK to MonitoringLocation.id unit_id = Column(String, nullable=True, index=True) # FK to RosterUnit.id (nullable if location-based) action_type = Column(String, nullable=False) # start, stop, download, calibrate device_type = Column(String, nullable=False) # sound_level_meter | seismograph scheduled_time = Column(DateTime, nullable=False, index=True) executed_at = Column(DateTime, nullable=True) execution_status = Column(String, default="pending") # pending, completed, failed, cancelled # Response from device module (SLMM or SFM) module_response = Column(Text, nullable=True) # JSON error_message = Column(Text, nullable=True) notes = Column(Text, nullable=True) created_at = Column(DateTime, default=datetime.utcnow) class RecordingSession(Base): """ Recording sessions: tracks actual monitoring sessions. Created when recording starts, updated when it stops. """ __tablename__ = "recording_sessions" id = Column(String, primary_key=True, index=True) # UUID project_id = Column(String, nullable=False, index=True) # FK to Project.id location_id = Column(String, nullable=False, index=True) # FK to MonitoringLocation.id unit_id = Column(String, nullable=False, index=True) # FK to RosterUnit.id session_type = Column(String, nullable=False) # sound | vibration started_at = Column(DateTime, nullable=False) stopped_at = Column(DateTime, nullable=True) duration_seconds = Column(Integer, nullable=True) status = Column(String, default="recording") # recording, completed, failed # Snapshot of device configuration at recording time session_metadata = Column(Text, nullable=True) # JSON created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) class DataFile(Base): """ Data files: references to recorded data files. Terra-View tracks file metadata; actual files stored in data/Projects/ directory. """ __tablename__ = "data_files" id = Column(String, primary_key=True, index=True) # UUID session_id = Column(String, nullable=False, index=True) # FK to RecordingSession.id file_path = Column(String, nullable=False) # Relative to data/Projects/ file_type = Column(String, nullable=False) # wav, csv, mseed, json file_size_bytes = Column(Integer, nullable=True) downloaded_at = Column(DateTime, nullable=True) checksum = Column(String, nullable=True) # SHA256 or MD5 # Additional file metadata file_metadata = Column(Text, nullable=True) # JSON created_at = Column(DateTime, default=datetime.utcnow)