405 lines
18 KiB
Python
405 lines
18 KiB
Python
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
|