BIG update: Update to 0.5.1. Added:
-Project management -Modem Managerment -Modem/unit pairing and more
This commit is contained in:
@@ -531,6 +531,37 @@ def set_note(unit_id: str, note: str = Form(""), db: Session = Depends(get_db)):
|
||||
return {"message": "Updated", "id": unit_id, "note": note}
|
||||
|
||||
|
||||
def _parse_bool(value: str) -> bool:
|
||||
"""Parse boolean from CSV string value."""
|
||||
return value.lower() in ('true', '1', 'yes') if value else False
|
||||
|
||||
|
||||
def _parse_int(value: str) -> int | None:
|
||||
"""Parse integer from CSV string value, return None if empty or invalid."""
|
||||
if not value or not value.strip():
|
||||
return None
|
||||
try:
|
||||
return int(value.strip())
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _parse_date(value: str) -> date | None:
|
||||
"""Parse date from CSV string value (YYYY-MM-DD format)."""
|
||||
if not value or not value.strip():
|
||||
return None
|
||||
try:
|
||||
return datetime.strptime(value.strip(), '%Y-%m-%d').date()
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _get_csv_value(row: dict, key: str, default=None):
|
||||
"""Get value from CSV row, return default if empty."""
|
||||
value = row.get(key, '').strip() if row.get(key) else ''
|
||||
return value if value else default
|
||||
|
||||
|
||||
@router.post("/import-csv")
|
||||
async def import_csv(
|
||||
file: UploadFile = File(...),
|
||||
@@ -541,13 +572,40 @@ async def import_csv(
|
||||
Import roster units from CSV file.
|
||||
|
||||
Expected CSV columns (unit_id is required, others are optional):
|
||||
- unit_id: Unique identifier for the unit
|
||||
- unit_type: Type of unit (default: "series3")
|
||||
- deployed: Boolean for deployment status (default: False)
|
||||
- retired: Boolean for retirement status (default: False)
|
||||
|
||||
Common fields (all device types):
|
||||
- unit_id: Unique identifier for the unit (REQUIRED)
|
||||
- device_type: "seismograph", "modem", or "slm" (default: "seismograph")
|
||||
- unit_type: Sub-type (e.g., "series3", "series4" for seismographs)
|
||||
- deployed: Boolean (true/false/yes/no/1/0)
|
||||
- retired: Boolean
|
||||
- note: Notes about the unit
|
||||
- project_id: Project identifier
|
||||
- location: Location description
|
||||
- address: Street address
|
||||
- coordinates: GPS coordinates (lat;lon or lat,lon)
|
||||
|
||||
Seismograph-specific:
|
||||
- last_calibrated: Date (YYYY-MM-DD)
|
||||
- next_calibration_due: Date (YYYY-MM-DD)
|
||||
- deployed_with_modem_id: ID of paired modem
|
||||
|
||||
Modem-specific:
|
||||
- ip_address: Device IP address
|
||||
- phone_number: SIM card phone number
|
||||
- hardware_model: Hardware model (e.g., IBR900, RV55)
|
||||
|
||||
SLM-specific:
|
||||
- slm_host: Device IP or hostname
|
||||
- slm_tcp_port: TCP control port (default 2255)
|
||||
- slm_ftp_port: FTP port (default 21)
|
||||
- slm_model: Device model (NL-43, NL-53)
|
||||
- slm_serial_number: Serial number
|
||||
- slm_frequency_weighting: A, C, or Z
|
||||
- slm_time_weighting: F (Fast), S (Slow), I (Impulse)
|
||||
- slm_measurement_range: e.g., "30-130 dB"
|
||||
|
||||
Lines starting with # are treated as comments and skipped.
|
||||
|
||||
Args:
|
||||
file: CSV file upload
|
||||
@@ -560,6 +618,12 @@ async def import_csv(
|
||||
# Read file content
|
||||
contents = await file.read()
|
||||
csv_text = contents.decode('utf-8')
|
||||
|
||||
# Filter out comment lines (starting with #)
|
||||
lines = csv_text.split('\n')
|
||||
filtered_lines = [line for line in lines if not line.strip().startswith('#')]
|
||||
csv_text = '\n'.join(filtered_lines)
|
||||
|
||||
csv_reader = csv.DictReader(io.StringIO(csv_text))
|
||||
|
||||
results = {
|
||||
@@ -580,6 +644,9 @@ async def import_csv(
|
||||
})
|
||||
continue
|
||||
|
||||
# Determine device type
|
||||
device_type = _get_csv_value(row, 'device_type', 'seismograph')
|
||||
|
||||
# Check if unit exists
|
||||
existing_unit = db.query(RosterUnit).filter(RosterUnit.id == unit_id).first()
|
||||
|
||||
@@ -588,31 +655,84 @@ async def import_csv(
|
||||
results["skipped"].append(unit_id)
|
||||
continue
|
||||
|
||||
# Update existing unit
|
||||
existing_unit.unit_type = row.get('unit_type', existing_unit.unit_type or 'series3')
|
||||
existing_unit.deployed = row.get('deployed', '').lower() in ('true', '1', 'yes') if row.get('deployed') else existing_unit.deployed
|
||||
existing_unit.retired = row.get('retired', '').lower() in ('true', '1', 'yes') if row.get('retired') else existing_unit.retired
|
||||
existing_unit.note = row.get('note', existing_unit.note or '')
|
||||
existing_unit.project_id = row.get('project_id', existing_unit.project_id)
|
||||
existing_unit.location = row.get('location', existing_unit.location)
|
||||
existing_unit.address = row.get('address', existing_unit.address)
|
||||
existing_unit.coordinates = row.get('coordinates', existing_unit.coordinates)
|
||||
# Update existing unit - common fields
|
||||
existing_unit.device_type = device_type
|
||||
existing_unit.unit_type = _get_csv_value(row, 'unit_type', existing_unit.unit_type or 'series3')
|
||||
existing_unit.deployed = _parse_bool(row.get('deployed', '')) if row.get('deployed') else existing_unit.deployed
|
||||
existing_unit.retired = _parse_bool(row.get('retired', '')) if row.get('retired') else existing_unit.retired
|
||||
existing_unit.note = _get_csv_value(row, 'note', existing_unit.note)
|
||||
existing_unit.project_id = _get_csv_value(row, 'project_id', existing_unit.project_id)
|
||||
existing_unit.location = _get_csv_value(row, 'location', existing_unit.location)
|
||||
existing_unit.address = _get_csv_value(row, 'address', existing_unit.address)
|
||||
existing_unit.coordinates = _get_csv_value(row, 'coordinates', existing_unit.coordinates)
|
||||
existing_unit.last_updated = datetime.utcnow()
|
||||
|
||||
# Seismograph-specific fields
|
||||
if row.get('last_calibrated'):
|
||||
existing_unit.last_calibrated = _parse_date(row.get('last_calibrated'))
|
||||
if row.get('next_calibration_due'):
|
||||
existing_unit.next_calibration_due = _parse_date(row.get('next_calibration_due'))
|
||||
if row.get('deployed_with_modem_id'):
|
||||
existing_unit.deployed_with_modem_id = _get_csv_value(row, 'deployed_with_modem_id')
|
||||
|
||||
# Modem-specific fields
|
||||
if row.get('ip_address'):
|
||||
existing_unit.ip_address = _get_csv_value(row, 'ip_address')
|
||||
if row.get('phone_number'):
|
||||
existing_unit.phone_number = _get_csv_value(row, 'phone_number')
|
||||
if row.get('hardware_model'):
|
||||
existing_unit.hardware_model = _get_csv_value(row, 'hardware_model')
|
||||
|
||||
# SLM-specific fields
|
||||
if row.get('slm_host'):
|
||||
existing_unit.slm_host = _get_csv_value(row, 'slm_host')
|
||||
if row.get('slm_tcp_port'):
|
||||
existing_unit.slm_tcp_port = _parse_int(row.get('slm_tcp_port'))
|
||||
if row.get('slm_ftp_port'):
|
||||
existing_unit.slm_ftp_port = _parse_int(row.get('slm_ftp_port'))
|
||||
if row.get('slm_model'):
|
||||
existing_unit.slm_model = _get_csv_value(row, 'slm_model')
|
||||
if row.get('slm_serial_number'):
|
||||
existing_unit.slm_serial_number = _get_csv_value(row, 'slm_serial_number')
|
||||
if row.get('slm_frequency_weighting'):
|
||||
existing_unit.slm_frequency_weighting = _get_csv_value(row, 'slm_frequency_weighting')
|
||||
if row.get('slm_time_weighting'):
|
||||
existing_unit.slm_time_weighting = _get_csv_value(row, 'slm_time_weighting')
|
||||
if row.get('slm_measurement_range'):
|
||||
existing_unit.slm_measurement_range = _get_csv_value(row, 'slm_measurement_range')
|
||||
|
||||
results["updated"].append(unit_id)
|
||||
else:
|
||||
# Create new unit
|
||||
# Create new unit with all fields
|
||||
new_unit = RosterUnit(
|
||||
id=unit_id,
|
||||
unit_type=row.get('unit_type', 'series3'),
|
||||
deployed=row.get('deployed', '').lower() in ('true', '1', 'yes'),
|
||||
retired=row.get('retired', '').lower() in ('true', '1', 'yes'),
|
||||
note=row.get('note', ''),
|
||||
project_id=row.get('project_id'),
|
||||
location=row.get('location'),
|
||||
address=row.get('address'),
|
||||
coordinates=row.get('coordinates'),
|
||||
last_updated=datetime.utcnow()
|
||||
device_type=device_type,
|
||||
unit_type=_get_csv_value(row, 'unit_type', 'series3'),
|
||||
deployed=_parse_bool(row.get('deployed', '')),
|
||||
retired=_parse_bool(row.get('retired', '')),
|
||||
note=_get_csv_value(row, 'note', ''),
|
||||
project_id=_get_csv_value(row, 'project_id'),
|
||||
location=_get_csv_value(row, 'location'),
|
||||
address=_get_csv_value(row, 'address'),
|
||||
coordinates=_get_csv_value(row, 'coordinates'),
|
||||
last_updated=datetime.utcnow(),
|
||||
# Seismograph fields
|
||||
last_calibrated=_parse_date(row.get('last_calibrated', '')),
|
||||
next_calibration_due=_parse_date(row.get('next_calibration_due', '')),
|
||||
deployed_with_modem_id=_get_csv_value(row, 'deployed_with_modem_id'),
|
||||
# Modem fields
|
||||
ip_address=_get_csv_value(row, 'ip_address'),
|
||||
phone_number=_get_csv_value(row, 'phone_number'),
|
||||
hardware_model=_get_csv_value(row, 'hardware_model'),
|
||||
# SLM fields
|
||||
slm_host=_get_csv_value(row, 'slm_host'),
|
||||
slm_tcp_port=_parse_int(row.get('slm_tcp_port', '')),
|
||||
slm_ftp_port=_parse_int(row.get('slm_ftp_port', '')),
|
||||
slm_model=_get_csv_value(row, 'slm_model'),
|
||||
slm_serial_number=_get_csv_value(row, 'slm_serial_number'),
|
||||
slm_frequency_weighting=_get_csv_value(row, 'slm_frequency_weighting'),
|
||||
slm_time_weighting=_get_csv_value(row, 'slm_time_weighting'),
|
||||
slm_measurement_range=_get_csv_value(row, 'slm_measurement_range'),
|
||||
)
|
||||
db.add(new_unit)
|
||||
results["added"].append(unit_id)
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
unit_id,unit_type,deployed,retired,note,project_id,location
|
||||
BE1234,series3,true,false,Primary unit at main site,PROJ-001,San Francisco CA
|
||||
BE5678,series3,true,false,Backup sensor,PROJ-001,Los Angeles CA
|
||||
BE9012,series3,false,false,In maintenance,PROJ-002,Workshop
|
||||
BE3456,series3,true,false,,PROJ-003,New York NY
|
||||
BE7890,series3,false,true,Decommissioned 2024,,Storage
|
||||
unit_id,device_type,unit_type,deployed,retired,note,project_id,location,address,coordinates,last_calibrated,next_calibration_due,deployed_with_modem_id,ip_address,phone_number,hardware_model,slm_host,slm_tcp_port,slm_ftp_port,slm_model,slm_serial_number,slm_frequency_weighting,slm_time_weighting,slm_measurement_range
|
||||
# ============================================
|
||||
# SEISMOGRAPHS (device_type=seismograph)
|
||||
# ============================================
|
||||
BE1234,seismograph,series3,true,false,Primary unit at main site,PROJ-001,San Francisco CA,123 Market St,37.7749;-122.4194,2025-06-15,2026-06-15,MDM001,,,,,,,,,,,
|
||||
BE5678,seismograph,series3,true,false,Backup sensor,PROJ-001,Los Angeles CA,456 Sunset Blvd,34.0522;-118.2437,2025-03-01,2026-03-01,MDM002,,,,,,,,,,,
|
||||
BE9012,seismograph,series4,false,false,In maintenance - needs calibration,PROJ-002,Workshop,789 Industrial Way,,,,,,,,,,,,,,
|
||||
BE3456,seismograph,series3,true,false,,PROJ-003,New York NY,101 Broadway,40.7128;-74.0060,2025-01-10,2026-01-10,,,,,,,,,,,
|
||||
BE7890,seismograph,series3,false,true,Decommissioned 2024,,Storage,Warehouse B,,,,,,,,,,,,,,,
|
||||
# ============================================
|
||||
# MODEMS (device_type=modem)
|
||||
# ============================================
|
||||
MDM001,modem,,true,false,Cradlepoint at SF site,PROJ-001,San Francisco CA,123 Market St,37.7749;-122.4194,,,,,192.168.1.100,+1-555-0101,IBR900,,,,,,,
|
||||
MDM002,modem,,true,false,Sierra Wireless at LA site,PROJ-001,Los Angeles CA,456 Sunset Blvd,34.0522;-118.2437,,,,,10.0.0.50,+1-555-0102,RV55,,,,,,,
|
||||
MDM003,modem,,false,false,Spare modem in storage,,,Storage,Warehouse A,,,,,,+1-555-0103,IBR600,,,,,,,
|
||||
MDM004,modem,,true,false,NYC backup modem,PROJ-003,New York NY,101 Broadway,40.7128;-74.0060,,,,,172.16.0.25,+1-555-0104,IBR1700,,,,,,,
|
||||
# ============================================
|
||||
# SOUND LEVEL METERS (device_type=slm)
|
||||
# ============================================
|
||||
SLM001,slm,,true,false,NL-43 at construction site A,PROJ-004,Downtown Site,500 Main St,40.7589;-73.9851,,,,,,,,192.168.10.101,2255,21,NL-43,12345678,A,F,30-130 dB
|
||||
SLM002,slm,,true,false,NL-43 at construction site B,PROJ-004,Midtown Site,600 Park Ave,40.7614;-73.9776,,,MDM004,,,,,192.168.10.102,2255,21,NL-43,12345679,A,S,30-130 dB
|
||||
SLM003,slm,,false,false,NL-53 spare unit,,,Storage,Warehouse A,,,,,,,,,,,NL-53,98765432,C,F,25-138 dB
|
||||
SLM004,slm,,true,false,NL-43 nighttime monitoring,PROJ-005,Residential Area,200 Quiet Lane,40.7484;-73.9857,,,,,,,,10.0.5.50,2255,21,NL-43,11112222,A,S,30-130 dB
|
||||
|
||||
|
Reference in New Issue
Block a user