Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions datamimic_ce/tasks/task_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@


class TaskUtil:
@staticmethod
def _wgt_csv_has_header(file_path, separator: str) -> bool:
"""Whether a '.wgt.csv' file's first row is a header: its weight column isn't numeric
(same sniff FileUtil.read_weight_csv itself uses to skip an optional header row)."""
raw_data = FileUtil._read_raw_csv(file_path, separator, "utf-8")
return bool(raw_data) and len(raw_data[0]) > 1 and not FileUtil._parses_as_float(raw_data[0][1])

@staticmethod
def get_task_by_statement(
ctx: SetupContext,
Expand Down Expand Up @@ -344,19 +351,23 @@ def gen_task_load_data_from_source_or_script(
else:
# Evaluate script in source
source_data = context.evaluate_python_expression(stmt.script)
elif source_str.endswith(".wgt.csv"):
# A ".wgt.csv" file is headerless (value|weight, no column names) - the plain CSV
# reader below would treat its first data row as a header, producing nonsense column
# names and one fewer row than the file has. Unlike ".wgt.ent.csv" (a normal headered
# CSV that merely has an extra "weight" column - reading it plainly is coherent, just
# unweighted), there is no coherent plain-CSV reading of this format at all. <key
# source="...wgt.csv"> already applies its weights correctly; <generate>-level
# weighted-entity sourcing is real work, not yet done - fail loudly instead of
# silently generating garbage.
elif source_str.endswith(".wgt.csv") and not TaskUtil._wgt_csv_has_header(
root_context.descriptor_dir / source_str, separator
):
# A HEADERLESS ".wgt.csv" file (value|weight, no column names) has no coherent plain-CSV
# reading at all - the reader below would treat its first data row as a header,
# producing nonsense column names and one fewer row than the file has. A HEADERED
# ".wgt.csv" falls through to the plain ".csv" branch below instead: like ".wgt.ent.csv"
# (a normal headered CSV that merely has an extra "weight" column), reading it plainly is
# coherent, just unweighted. <key source="...wgt.csv"> already applies weights correctly
# either way (FileUtil.read_weight_csv auto-detects the header); <generate>-level
# weighted-entity sourcing is real work, not yet done - fail loudly instead of silently
# generating garbage, but only where there IS no coherent fallback.
raise ValueError(
f"<generate> '{stmt.full_name}': source '{source_str}' is a headerless weighted "
f"value|weight file - not supported at <generate>-level (only <key source=...> "
f"applies '.wgt.csv' weights today)"
f"applies '.wgt.csv' weights today; add a header row to read it as a plain, "
f"unweighted CSV instead)"
)
# Load data from CSV
elif source_str.endswith(".csv"):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
value|weight
true|80
false|20
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<setup>
<generate name="rows" source="data/active_with_header.wgt.csv" count="5" target="JSON"/>
</setup>
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@
# See LICENSE file for the full text of the license.
# For questions and support, contact: info@rapiddweller.com

"""<generate source="x.wgt.csv"> (the headerless value|weight format) falls through to the plain
CSV reader, which has no coherent way to read it - it would treat the first data row as a header,
producing nonsense column names and one fewer row than the file has. Full weighted-entity support
at <generate>-level needs pagination-core work (see PR #194 discussion); until that lands, fail
loudly instead of silently producing garbage.

".wgt.ent.csv" is different: it's a normal headered CSV that happens to carry an extra "weight"
column, so reading it plainly (ignoring the weight, no resampling) is coherent - just unweighted.
An existing test (test_source_script/test_iterate_source_scripted.xml) already relies on exactly
that, so this must keep working."""
"""<generate source="x.wgt.csv"> (the value|weight format) only raises when the file is
HEADERLESS - the plain CSV reader has no coherent way to read that at all, it would treat the
first data row as a header, producing nonsense column names and one fewer row than the file has.
A HEADERED ".wgt.csv" (FileUtil.read_weight_csv now accepts either, see feat/wgt-csv-header-
optional) falls through to the plain CSV reader instead: reading it plainly (ignoring the weight,
no resampling) is coherent, just unweighted - the same tolerance ".wgt.ent.csv" already gets.

Full weighted-entity support at <generate>-level needs pagination-core work (see PR #194
discussion); until that lands, fail loudly instead of silently producing garbage, but only where
there is no coherent fallback.

".wgt.ent.csv" is a normal headered CSV that happens to carry an extra "weight" column, so reading
it plainly (ignoring the weight, no resampling) is coherent - just unweighted. An existing test
(test_source_script/test_iterate_source_scripted.xml) already relies on exactly that, so this must
keep working."""

from pathlib import Path

Expand All @@ -24,12 +29,23 @@
_dir = Path(__file__).resolve().parent


def test_generate_source_wgt_csv_raises():
def test_generate_source_wgt_csv_headerless_raises():
engine = DataMimicTest(_dir, "generate_wgt_plain.xml", capture_test_result=True)
with pytest.raises(ValueError, match=r"\.wgt\.csv"):
engine.test_with_timer()


def test_generate_source_wgt_csv_with_header_reads_as_plain_csv():
"""A headered '.wgt.csv' is NOT weighted (no resampling, no bias toward the higher-weight
row) - just an ordinary headered CSV read, weight column included as literal data. Must NOT
raise, unlike the headerless case."""
engine = DataMimicTest(_dir, "generate_wgt_with_header.xml", capture_test_result=True)
engine.test_with_timer()
rows = engine.capture_result()["rows"]
assert {row["value"] for row in rows} <= {"true", "false"}
assert all("weight" in row for row in rows)


def test_generate_source_wgt_ent_csv_reads_as_plain_csv():
"""Not weighted (no resampling, no bias toward the higher-weight row) - just an ordinary
headered CSV read, weight column included as literal data. Must NOT raise."""
Expand Down