From 0835e3785bfaa004640284da25433e7dee4bbb64 Mon Sep 17 00:00:00 2001 From: Kai Chappell Date: Sun, 17 Aug 2025 11:34:53 +0000 Subject: [PATCH] Add data persistence models --- src/py_dvt_ate/data/__init__.py | 14 ++++++ src/py_dvt_ate/data/models.py | 83 +++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/py_dvt_ate/data/__init__.py create mode 100644 src/py_dvt_ate/data/models.py diff --git a/src/py_dvt_ate/data/__init__.py b/src/py_dvt_ate/data/__init__.py new file mode 100644 index 0000000..ddab624 --- /dev/null +++ b/src/py_dvt_ate/data/__init__.py @@ -0,0 +1,14 @@ +"""Data persistence layer. + +Provides storage for test runs, results, and measurements using +SQLite for metadata and Parquet for time-series data. +""" + +from py_dvt_ate.data.models import Measurement, TestResult, TestRun, TestStatus + +__all__ = [ + "Measurement", + "TestResult", + "TestRun", + "TestStatus", +] diff --git a/src/py_dvt_ate/data/models.py b/src/py_dvt_ate/data/models.py new file mode 100644 index 0000000..907bf3d --- /dev/null +++ b/src/py_dvt_ate/data/models.py @@ -0,0 +1,83 @@ +"""Data models for test persistence. + +This module defines dataclasses representing test runs, results, and measurements. +These models map to SQLite tables (for metadata) and Parquet files (for time-series). +""" + +from dataclasses import dataclass, field +from datetime import datetime +from enum import Enum + + +class TestStatus(Enum): + """Test run status.""" + + PENDING = "pending" + RUNNING = "running" + PASSED = "passed" + FAILED = "failed" + ERROR = "error" + SKIPPED = "skipped" + + +@dataclass +class TestRun: + """Test run metadata. + + Maps to the test_runs SQLite table. + """ + + id: str # UUID + test_name: str + started_at: datetime + status: TestStatus + config_json: str # JSON string of test configuration + description: str | None = None + completed_at: datetime | None = None + operator: str | None = None + notes: str | None = None + created_at: datetime = field(default_factory=datetime.now) + + +@dataclass(frozen=True) +class TestResult: + """Immutable test result with limits. + + Maps to the test_results SQLite table. + Represents a single scalar measurement with pass/fail limits. + """ + + id: str # UUID + test_run_id: str # Foreign key to test_runs.id + parameter: str + value: float + unit: str + measured_at: datetime + lower_limit: float | None = None + upper_limit: float | None = None + + @property + def passed(self) -> bool | None: + """Evaluate pass/fail. None if no limits defined.""" + if self.lower_limit is None and self.upper_limit is None: + return None + lower_ok = self.lower_limit is None or self.value >= self.lower_limit + upper_ok = self.upper_limit is None or self.value <= self.upper_limit + return lower_ok and upper_ok + + +@dataclass(frozen=True) +class Measurement: + """Immutable measurement record for time-series data. + + Maps to Parquet files for efficient storage and analysis. + Includes measurement conditions (temperature, voltage, current) at time of measurement. + """ + + timestamp: float # Seconds since epoch (high precision) + parameter: str + value: float + unit: str + temperature: float = 0.0 # Chamber temperature at measurement + input_voltage: float = 0.0 # DUT input voltage at measurement + load_current: float = 0.0 # DUT load current at measurement