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 with type-specific fields: - "seismograph" - Seismic monitoring devices (default) - "modem" - Field modems and network equipment - "slm" - Sound level meters (NL-43/NL-53) """ __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" | "slm" 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) deployment_type = Column(String, nullable=True) # "seismograph" | "slm" - what type of device this modem is deployed with deployed_with_unit_id = Column(String, nullable=True) # ID of seismograph/SLM this modem is deployed with # 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. Project naming convention: - project_number: TMI internal ID format xxxx-YY (e.g., "2567-23") - client_name: Client/contractor name (e.g., "PJ Dick") - name: Project/site name (e.g., "RKM Hall", "CMU Campus") Display format: "2567-23 - PJ Dick - RKM Hall" Users can search by any of these fields. """ __tablename__ = "projects" id = Column(String, primary_key=True, index=True) # UUID project_number = Column(String, nullable=True, index=True) # TMI ID: xxxx-YY format (e.g., "2567-23") name = Column(String, nullable=False, unique=True) # Project/site name (e.g., "RKM Hall") 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, index=True) # Client name (e.g., "PJ Dick") 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) # "slm" | "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) # "slm" | "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) class ReportTemplate(Base): """ Report templates: saved configurations for generating Excel reports. Allows users to save time filter presets, titles, etc. for reuse. """ __tablename__ = "report_templates" id = Column(String, primary_key=True, index=True) # UUID name = Column(String, nullable=False) # "Nighttime Report", "Full Day Report" project_id = Column(String, nullable=True) # Optional: project-specific template # Template settings report_title = Column(String, default="Background Noise Study") start_time = Column(String, nullable=True) # "19:00" format end_time = Column(String, nullable=True) # "07:00" format start_date = Column(String, nullable=True) # "2025-01-15" format (optional) end_date = Column(String, nullable=True) # "2025-01-20" format (optional) created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) # ============================================================================ # Sound Monitoring Scheduler # ============================================================================ class RecurringSchedule(Base): """ Recurring schedule definitions for automated sound monitoring. Supports two schedule types: - "weekly_calendar": Select specific days with start/end times (e.g., Mon/Wed/Fri 7pm-7am) - "simple_interval": For 24/7 monitoring with daily stop/download/restart cycles """ __tablename__ = "recurring_schedules" 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 (optional, can use assignment) name = Column(String, nullable=False) # "Weeknight Monitoring", "24/7 Continuous" schedule_type = Column(String, nullable=False) # "weekly_calendar" | "simple_interval" device_type = Column(String, nullable=False) # "slm" | "seismograph" # Weekly Calendar fields (schedule_type = "weekly_calendar") # JSON format: { # "monday": {"enabled": true, "start": "19:00", "end": "07:00"}, # "tuesday": {"enabled": false}, # ... # } weekly_pattern = Column(Text, nullable=True) # Simple Interval fields (schedule_type = "simple_interval") interval_type = Column(String, nullable=True) # "daily" | "hourly" cycle_time = Column(String, nullable=True) # "00:00" - time to run stop/download/restart include_download = Column(Boolean, default=True) # Download data before restart # Automation options (applies to both schedule types) auto_increment_index = Column(Boolean, default=True) # Auto-increment store/index number before start # When True: prevents "overwrite data?" prompts by using a new index each time # Shared configuration enabled = Column(Boolean, default=True) timezone = Column(String, default="America/New_York") # Tracking last_generated_at = Column(DateTime, nullable=True) # When actions were last generated next_occurrence = Column(DateTime, nullable=True) # Computed next action time created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) class Alert(Base): """ In-app alerts for device status changes and system events. Designed for future expansion to email/webhook notifications. Currently supports: - device_offline: Device became unreachable - device_online: Device came back online - schedule_failed: Scheduled action failed to execute - schedule_completed: Scheduled action completed successfully """ __tablename__ = "alerts" id = Column(String, primary_key=True, index=True) # UUID # Alert classification alert_type = Column(String, nullable=False) # "device_offline" | "device_online" | "schedule_failed" | "schedule_completed" severity = Column(String, default="warning") # "info" | "warning" | "critical" # Related entities (nullable - may not all apply) project_id = Column(String, nullable=True, index=True) location_id = Column(String, nullable=True, index=True) unit_id = Column(String, nullable=True, index=True) schedule_id = Column(String, nullable=True) # RecurringSchedule or ScheduledAction id # Alert content title = Column(String, nullable=False) # "NRL-001 Device Offline" message = Column(Text, nullable=True) # Detailed description alert_metadata = Column(Text, nullable=True) # JSON: additional context data # Status tracking status = Column(String, default="active") # "active" | "acknowledged" | "resolved" | "dismissed" acknowledged_at = Column(DateTime, nullable=True) resolved_at = Column(DateTime, nullable=True) created_at = Column(DateTime, default=datetime.utcnow) expires_at = Column(DateTime, nullable=True) # Auto-dismiss after this time