From 0bcd706fe0e229ad4b0835247bd8dd1e0c6b5a74 Mon Sep 17 00:00:00 2001 From: Lukas Wuttke Date: Tue, 7 Jul 2026 12:40:35 +0200 Subject: [PATCH] test(preflight): pin #152's schema-type-aware label diversity in the parity harness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #152 shipped the labelSchemaType/isStringSQLType refinement without parity coverage — the exact case it fixes (labels '1' vs '1.0' under a VARCHAR label, which the blanket collapse falsely rejected) was pinned by nothing. Two new golden cases close that: - tabular-varchar-numeric-labels: VARCHAR label → dtype=str in-cluster, no collapse, 2 classes → accept on BOTH sides - tabular-float-numeric-labels: FLOAT label → numeric read collapses 1/1.0 → reject on BOTH sides The harness now honors an explicit per-case schema (mirroring the --schema flow) on both the generator and the Go side, so dtype- sensitive verdicts stay comparable. Goldens regenerated from the real Python validators (25 cases green). Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/push/parity_golden_test.go | 28 +++++++++++-------- internal/push/testdata/parity/cases.json | 26 +++++++++++++++++ .../tabular-float-numeric-labels/data.csv | 3 ++ .../tabular-varchar-numeric-labels/data.csv | 3 ++ internal/push/testdata/parity/goldens.json | 10 +++++++ scripts/gen-validator-goldens.py | 13 ++++++--- 6 files changed, 67 insertions(+), 16 deletions(-) create mode 100644 internal/push/testdata/parity/cases/tabular-float-numeric-labels/data.csv create mode 100644 internal/push/testdata/parity/cases/tabular-varchar-numeric-labels/data.csv diff --git a/internal/push/parity_golden_test.go b/internal/push/parity_golden_test.go index fda5a722..2006b39d 100644 --- a/internal/push/parity_golden_test.go +++ b/internal/push/parity_golden_test.go @@ -23,15 +23,16 @@ import ( // silent. type parityCase struct { - Name string `json:"name"` - Category string `json:"category"` - CSV string `json:"csv"` - LabelColumn string `json:"label_column"` - Extension string `json:"extension"` - TargetSize []int `json:"target_size"` - CLIVerdict string `json:"cli_verdict"` - IngestorVerdict string `json:"ingestor_verdict"` - Note string `json:"note"` + Name string `json:"name"` + Category string `json:"category"` + CSV string `json:"csv"` + LabelColumn string `json:"label_column"` + Extension string `json:"extension"` + TargetSize []int `json:"target_size"` + Schema map[string]string `json:"schema"` + CLIVerdict string `json:"cli_verdict"` + IngestorVerdict string `json:"ingestor_verdict"` + Note string `json:"note"` } func TestValidatorParity(t *testing.T) { @@ -85,9 +86,12 @@ func runGoPreflight(t *testing.T, c parityCase) string { TargetSize: c.TargetSize, } if IsTabular(c.Category) { - // Mirror runDataIngest: the schema is inferred from the CSV before - // the preflight runs (the schema-columns preview needs it). - if sch, _, _, err := InferSchema(layout.LabelsCSV); err == nil { + // Mirror runDataIngest: an explicit schema (the --schema flow) wins, + // else it is inferred from the CSV — the golden generator derives the + // schema the same way, so dtype-sensitive verdicts stay comparable. + if len(c.Schema) > 0 { + spec.Schema = c.Schema + } else if sch, _, _, err := InferSchema(layout.LabelsCSV); err == nil { spec.Schema = sch } } diff --git a/internal/push/testdata/parity/cases.json b/internal/push/testdata/parity/cases.json index fdc4e5d7..27ff423a 100644 --- a/internal/push/testdata/parity/cases.json +++ b/internal/push/testdata/parity/cases.json @@ -281,6 +281,32 @@ "cli_verdict": "accept", "ingestor_verdict": "accept", "note": "pins the text-family dispatch" + }, + { + "name": "tabular-varchar-numeric-labels", + "category": "tabular_classification", + "csv": "data.csv", + "label_column": "label", + "schema": { + "age": "INT", + "label": "VARCHAR(255)" + }, + "cli_verdict": "accept", + "ingestor_verdict": "accept", + "note": "labels '1' vs '1.0' under a VARCHAR label: the ingestor pins dtype=str (no numeric collapse) so they are 2 classes \u2014 pins #152's schema-type-aware collapse (the earlier blanket collapse falsely rejected this)" + }, + { + "name": "tabular-float-numeric-labels", + "category": "tabular_classification", + "csv": "data.csv", + "label_column": "label", + "schema": { + "age": "INT", + "label": "FLOAT" + }, + "cli_verdict": "reject", + "ingestor_verdict": "reject", + "note": "same values under a FLOAT label: numeric read collapses 1/1.0 into one class \u2014 both sides reject (the counterpart pin)" } ] } diff --git a/internal/push/testdata/parity/cases/tabular-float-numeric-labels/data.csv b/internal/push/testdata/parity/cases/tabular-float-numeric-labels/data.csv new file mode 100644 index 00000000..36bf6816 --- /dev/null +++ b/internal/push/testdata/parity/cases/tabular-float-numeric-labels/data.csv @@ -0,0 +1,3 @@ +age,label +30,1 +40,1.0 diff --git a/internal/push/testdata/parity/cases/tabular-varchar-numeric-labels/data.csv b/internal/push/testdata/parity/cases/tabular-varchar-numeric-labels/data.csv new file mode 100644 index 00000000..36bf6816 --- /dev/null +++ b/internal/push/testdata/parity/cases/tabular-varchar-numeric-labels/data.csv @@ -0,0 +1,3 @@ +age,label +30,1 +40,1.0 diff --git a/internal/push/testdata/parity/goldens.json b/internal/push/testdata/parity/goldens.json index 033a73b8..ac7553dd 100644 --- a/internal/push/testdata/parity/goldens.json +++ b/internal/push/testdata/parity/goldens.json @@ -90,6 +90,12 @@ ], "verdict": "reject" }, + "tabular-float-numeric-labels": { + "errors": [ + "LabelDiversityValidator: Classification category requires at least 2 distinct label values in column 'label' (after whitespace stripping); this dataset has 1 distinct value(s): [np.float64(1.0)]. Raw value counts: {1.0: 2}. If this is intentional (e.g. you have a continuous target), pick a regression-family category like tabular_regression or time_series_forecasting instead." + ], + "verdict": "reject" + }, "tabular-header-only": { "errors": [ "IngestableRecordsValidator: No data rows found in CSV 'data.csv': the file has a header but no data rows (0 ingestable records). Add at least one data row and re-ingest.", @@ -119,6 +125,10 @@ "errors": [], "verdict": "accept" }, + "tabular-varchar-numeric-labels": { + "errors": [], + "verdict": "accept" + }, "text-clf-ok": { "errors": [], "verdict": "accept" diff --git a/scripts/gen-validator-goldens.py b/scripts/gen-validator-goldens.py index 5634e513..f87aebb5 100644 --- a/scripts/gen-validator-goldens.py +++ b/scripts/gen-validator-goldens.py @@ -72,10 +72,15 @@ def run_case(case): if case.get("target_size"): options["target_size"] = case["target_size"] if case["category"].startswith(("tabular", "time_")): - try: - schema = infer_schema(csv_path) - except Exception: - schema = {} + # An explicit per-case schema (mirroring --schema) wins; else + # infer — BOTH sides of the harness use the same source so + # dtype-sensitive cases (label diversity) stay comparable. + schema = case.get("schema") + if not schema: + try: + schema = infer_schema(csv_path) + except Exception: + schema = {} options["schema"] = schema options["full_schema"] = schema