feat: add manual SD card data upload for offline NRLs; rename RecordingSession to MonitoringSession

- Add POST /api/projects/{project_id}/nrl/{location_id}/upload-data endpoint
  accepting a ZIP or multi-file select of .rnd/.rnh files from an SD card.
  Parses .rnh metadata for session start/stop times, serial number, and store
  name. Creates a MonitoringSession (no unit assignment required) and DataFile
  records for each measurement file.

- Add Upload Data button and collapsible upload panel to the NRL detail Data
  Files tab, with inline success/error feedback and automatic file list refresh
  via HTMX after import.

- Rename RecordingSession -> MonitoringSession throughout the codebase
  (models.py, projects.py, project_locations.py, scheduler.py, roster_rename.py,
  main.py, init_projects_db.py, scripts/rename_unit.py). DB table renamed from
  recording_sessions to monitoring_sessions; old indexes dropped and recreated.

- Update all template UI copy from Recording Sessions to Monitoring Sessions
  (nrl_detail, projects/detail, session_list, schedule_oneoff, roster).

- Add backend/migrate_rename_recording_to_monitoring_sessions.py for applying
  the table rename on production databases before deploying this build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 19:54:40 +00:00
parent da4e5f66c5
commit 7516bbea70
16 changed files with 509 additions and 123 deletions

View File

@@ -245,17 +245,17 @@ class ScheduledAction(Base):
created_at = Column(DateTime, default=datetime.utcnow)
class RecordingSession(Base):
class MonitoringSession(Base):
"""
Recording sessions: tracks actual monitoring sessions.
Created when recording starts, updated when it stops.
Monitoring sessions: tracks actual monitoring sessions.
Created when monitoring starts, updated when it stops.
"""
__tablename__ = "recording_sessions"
__tablename__ = "monitoring_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
unit_id = Column(String, nullable=True, index=True) # FK to RosterUnit.id (nullable for offline uploads)
session_type = Column(String, nullable=False) # sound | vibration
started_at = Column(DateTime, nullable=False)
@@ -278,7 +278,7 @@ class DataFile(Base):
__tablename__ = "data_files"
id = Column(String, primary_key=True, index=True) # UUID
session_id = Column(String, nullable=False, index=True) # FK to RecordingSession.id
session_id = Column(String, nullable=False, index=True) # FK to MonitoringSession.id
file_path = Column(String, nullable=False) # Relative to data/Projects/
file_type = Column(String, nullable=False) # wav, csv, mseed, json