feat: add read-only training dataset loader
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import sqlite3
|
||||
|
||||
import pytest
|
||||
|
||||
from lmpm.training.dataset import (
|
||||
TrainingDataError,
|
||||
load_sqlite_training_dataset,
|
||||
)
|
||||
|
||||
MATERIAL_SCHEMA = """
|
||||
CREATE TABLE material_records (
|
||||
id INTEGER PRIMARY KEY,
|
||||
entry_batch_id INTEGER NOT NULL,
|
||||
experiment_id VARCHAR(100) NOT NULL UNIQUE,
|
||||
test_date DATE NOT NULL,
|
||||
material_category VARCHAR(100) NOT NULL,
|
||||
material_name VARCHAR(200) NOT NULL,
|
||||
apparent_density FLOAT,
|
||||
uv_absorption_355nm FLOAT,
|
||||
material_thickness FLOAT NOT NULL,
|
||||
moisture_content FLOAT,
|
||||
thermal_conductivity FLOAT,
|
||||
initial_decomposition_temp FLOAT,
|
||||
melting_vaporization_temp FLOAT,
|
||||
specific_heat_capacity FLOAT,
|
||||
carbon_residue_rate FLOAT,
|
||||
surface_roughness FLOAT,
|
||||
hardness FLOAT,
|
||||
actual_output_power FLOAT NOT NULL,
|
||||
display_current FLOAT NOT NULL,
|
||||
scanning_speed FLOAT NOT NULL,
|
||||
pulse_frequency FLOAT NOT NULL,
|
||||
pulse_width FLOAT NOT NULL,
|
||||
defocus_amount FLOAT NOT NULL,
|
||||
scan_line_spacing FLOAT NOT NULL,
|
||||
filling_method VARCHAR(100) NOT NULL,
|
||||
processing_size VARCHAR(100) NOT NULL,
|
||||
is_cut_through BOOLEAN NOT NULL,
|
||||
carbonized_edge_width FLOAT NOT NULL,
|
||||
etching_depth FLOAT NOT NULL,
|
||||
is_fire_smolder BOOLEAN NOT NULL,
|
||||
pattern_clarity_score FLOAT NOT NULL,
|
||||
presentation_balance_score FLOAT NOT NULL,
|
||||
finished_image_filename VARCHAR(255) NOT NULL,
|
||||
remarks TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL
|
||||
);
|
||||
"""
|
||||
|
||||
|
||||
def make_record(number: int) -> tuple:
|
||||
return (
|
||||
number,
|
||||
1,
|
||||
f"LAS-2026-{number:04d}",
|
||||
"2026-09-11",
|
||||
"polymer",
|
||||
f"Acrylic {number}",
|
||||
1.18 if number == 1 else None,
|
||||
0.92,
|
||||
3.0,
|
||||
None,
|
||||
0.19,
|
||||
None,
|
||||
None,
|
||||
1.5,
|
||||
None,
|
||||
1.2,
|
||||
None,
|
||||
8.5 + number,
|
||||
2.0,
|
||||
80.0,
|
||||
20.0,
|
||||
5.0,
|
||||
0.0,
|
||||
0.1,
|
||||
"双向填充",
|
||||
"20x20",
|
||||
number == 1,
|
||||
0.2,
|
||||
30.0,
|
||||
False,
|
||||
9.0,
|
||||
8.0,
|
||||
f"image-{number}.jpg",
|
||||
"stable",
|
||||
"2026-09-11T08:00:00+00:00",
|
||||
)
|
||||
|
||||
|
||||
def create_database(tmp_path, *, with_table: bool = True) -> object:
|
||||
database_path = tmp_path / f"platform-{int(with_table)}.db"
|
||||
connection = sqlite3.connect(database_path)
|
||||
if with_table:
|
||||
connection.execute(MATERIAL_SCHEMA)
|
||||
connection.executemany(
|
||||
"""
|
||||
INSERT INTO material_records VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
""",
|
||||
[make_record(1), make_record(2)],
|
||||
)
|
||||
connection.commit()
|
||||
connection.close()
|
||||
return database_path
|
||||
|
||||
|
||||
def test_sqlite_dataset_maps_features_and_targets(tmp_path):
|
||||
dataset = load_sqlite_training_dataset(create_database(tmp_path))
|
||||
|
||||
assert dataset.experiment_ids == ["LAS-2026-0001", "LAS-2026-0002"]
|
||||
assert dataset.feature_names[:5] == [
|
||||
"apparent_density",
|
||||
"uv_absorption_355nm",
|
||||
"material_thickness",
|
||||
"moisture_content",
|
||||
"thermal_conductivity",
|
||||
]
|
||||
assert dataset.feature_names[-1] == "processing_size"
|
||||
assert dataset.target_names == [
|
||||
"is_cut_through",
|
||||
"carbonized_edge_width",
|
||||
"etching_depth",
|
||||
"is_fire_smolder",
|
||||
"pattern_clarity_score",
|
||||
"presentation_balance_score",
|
||||
]
|
||||
assert dataset.records[0].metadata == {
|
||||
"experiment_id": "LAS-2026-0001",
|
||||
"test_date": "2026-09-11",
|
||||
"material_category": "polymer",
|
||||
"material_name": "Acrylic 1",
|
||||
}
|
||||
assert dataset.records[0].features["actual_output_power"] == pytest.approx(9.5)
|
||||
assert dataset.records[0].targets["is_cut_through"] is True
|
||||
assert dataset.records[1].targets["is_cut_through"] is False
|
||||
|
||||
|
||||
def test_missing_database_raises_expected_error(tmp_path):
|
||||
missing_path = tmp_path / "missing.db"
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
load_sqlite_training_dataset(missing_path)
|
||||
|
||||
|
||||
def test_database_without_material_table_raises_expected_error(tmp_path):
|
||||
database_path = create_database(tmp_path, with_table=False)
|
||||
|
||||
with pytest.raises(TrainingDataError, match="material_records"):
|
||||
load_sqlite_training_dataset(database_path)
|
||||
Reference in New Issue
Block a user