Add data_logging/src/data_logging/core.py
This commit is contained in:
parent
caa1ede233
commit
d9f5da5762
1 changed files with 75 additions and 0 deletions
75
data_logging/src/data_logging/core.py
Normal file
75
data_logging/src/data_logging/core.py
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
import csv
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
# Setup basic logging for internal operations
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
|
||||||
|
|
||||||
|
|
||||||
|
class LogFileError(Exception):
|
||||||
|
"""Custom exception raised when logfile writing fails."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LogEntry:
|
||||||
|
"""Represents a single sensor data point."""
|
||||||
|
time: str
|
||||||
|
lux: float
|
||||||
|
dB: float
|
||||||
|
temperature: float
|
||||||
|
inference_score: float
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
if not isinstance(self.time, str):
|
||||||
|
raise TypeError("time must be a string")
|
||||||
|
for field_name, value in [("lux", self.lux), ("dB", self.dB), ("temperature", self.temperature), ("inference_score", self.inference_score)]:
|
||||||
|
if not isinstance(value, (float, int)):
|
||||||
|
raise TypeError(f"{field_name} must be a number")
|
||||||
|
# Convert numeric values to float explicitly
|
||||||
|
self.lux = float(self.lux)
|
||||||
|
self.dB = float(self.dB)
|
||||||
|
self.temperature = float(self.temperature)
|
||||||
|
self.inference_score = float(self.inference_score)
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
"""Convert instance data to a dictionary for CSV/JSON writing."""
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
|
def log_data(entry: LogEntry, output_path: str = 'output/sensor_data_log.csv') -> None:
|
||||||
|
"""Appends a LogEntry to the configured log file."""
|
||||||
|
if not isinstance(entry, LogEntry):
|
||||||
|
raise TypeError("entry must be an instance of LogEntry")
|
||||||
|
if not isinstance(output_path, str):
|
||||||
|
raise TypeError("output_path must be a string")
|
||||||
|
|
||||||
|
output_file = Path(output_path)
|
||||||
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
row = entry.to_dict()
|
||||||
|
file_exists = output_file.exists()
|
||||||
|
try:
|
||||||
|
with open(output_file, mode='a', newline='', encoding='utf-8') as csvfile:
|
||||||
|
writer = csv.DictWriter(csvfile, fieldnames=row.keys())
|
||||||
|
if not file_exists:
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerow(row)
|
||||||
|
logging.info(f"Logged data to {output_file}: {row}")
|
||||||
|
except (OSError, csv.Error) as e:
|
||||||
|
logging.error(f"Failed to write to log file {output_file}: {e}")
|
||||||
|
raise LogFileError(f"Error writing to log file {output_file}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
# Self-test safeguard for CI integrity
|
||||||
|
if __name__ == '__main__':
|
||||||
|
now = datetime.now().strftime('%H:%M:%S')
|
||||||
|
test_entry = LogEntry(time=now, lux=120.5, dB=45.2, temperature=23.7, inference_score=0.87)
|
||||||
|
log_data(test_entry)
|
||||||
|
logging.info("Sample log entry written successfully.")
|
||||||
Loading…
Reference in a new issue