29 ap = argparse.ArgumentParser(description=
"Validate Elodin CSV export directory")
34 default=Path(
"./export_csv"),
35 help=
"Directory containing flattened CSVs from elodin-db export",
40 help=
"Fail if no calibrated PT pressure series found (typical hotfire/GSE run)",
42 args = ap.parse_args()
43 d: Path = args.export_dir
45 print(f
"❌ Not a directory: {d}", file=sys.stderr)
47 csvs = sorted(d.glob(
"*.csv"))
49 print(f
"❌ No *.csv in {d.resolve()}", file=sys.stderr)
52 by_prefix: dict[str, list[Path]] = defaultdict(list)
55 prefix = stem.split(
".", 1)[0]
if "." in stem
else stem
56 by_prefix[prefix].append(f)
58 print(f
"📂 Export: {d.resolve()}")
59 print(f
" Total CSV files: {len(csvs)}")
60 print(
" By entity prefix:")
61 for pref
in sorted(by_prefix.keys()):
62 print(f
" {pref}: {len(by_prefix[pref])} file(s)")
68 if "PT" in f.name
and "Cal" in f.name
and "pressure_psi" in f.name
73 if f.name.startswith(
"PT")
and "Cal" not in f.name
and "pressure" in f.name
75 tc_cal = [f
for f
in csvs
if "TC" in f.name
and "Cal" in f.name]
76 rtd_cal = [f
for f
in csvs
if "RTD" in f.name
and "Cal" in f.name]
77 lc_cal = [f
for f
in csvs
if "LC" in f.name
and "Cal" in f.name]
79 f
for f
in csvs
if "ACT_CMD" in f.name
or "actuator_state_commanded" in f.name
84 if f.name.startswith(
"ACT")
and "CH" in f.name
and "actuator_state" in f.name
86 ctrl = [f
for f
in csvs
if f.name.startswith(
"CONTROLLER")]
87 brd = [f
for f
in csvs
if f.name.startswith(
"BOARD")]
89 def rows_sample(paths: list[Path]) -> int:
97 print(
"\n Content hints (file counts / ~data rows in first 20 files each):")
99 f
" PT calibrated (PSI): {len(pt_cal)} files, ~{rows_sample(pt_cal)} rows"
102 f
" PT raw: {len(pt_raw)} files, ~{rows_sample(pt_raw)} rows"
104 print(f
" TC/RTD cal: {len(tc_cal) + len(rtd_cal)} files")
105 print(f
" LC cal: {len(lc_cal)} files")
106 print(f
" Actuators (CMD): {len(act_cmd)} files")
107 print(f
" Actuators (HW sense): {len(act_hw)} files")
108 print(f
" CONTROLLER: {len(ctrl)} files")
109 print(f
" BOARD (HB etc.): {len(brd)} files")
111 warnings: list[str] = []
114 "No PT*_Cal.*pressure_psi.csv — calibrated PT may be missing or export pattern changed."
116 if not pt_raw
and not pt_cal:
118 "No PT raw/cal files — check daq_bridge publish + calibration_service."
122 "No CONTROLLER*.csv — controller may be off or not writing to DB."
124 if not act_cmd
and not act_hw:
125 warnings.append(
"No ACT* actuator_state*.csv — actuator tables may be missing.")
128 print(
"\n ⚠️ Warnings:")
132 print(
"\n ✅ Core sensor/actuator/controller families present.")
134 def total_data_rows(paths: list[Path]) -> int:
145 "\n❌ --strict: require at least one PT calibrated pressure CSV.",
149 total_pt_rows = total_data_rows(pt_cal)
150 if total_pt_rows < 2:
152 "\n❌ --strict: PT cal series have insufficient rows.",