PhoenixAgent / tests /test_reshape_executor.py
VarunRS5457
Reshape flow: redirect to classify page with AI pre-filled classifications
16d2e95
Raw
History Blame Contribute Delete
5.39 kB
"""Unit tests for the reshape executor's core extraction logic.
These use a synthetic ITT-style grid (no external files), so they run in CI and
pin the transform: canonical Sample_ID construction, raw timepoint extraction,
and %Basal computation against the basal (T0) value.
"""
from app.core.reshape_executor import (
_canonical_variable,
_parse_row_label_id,
_process_columns_sheet,
_process_rows_sheet,
)
from app.models.mapping import GroupAssignment, ReshapePlan, SheetReshapePlan
# An ITT-like sheet: header (sample IDs) in row 3, TIME labels in column 1,
# values in rows 4-6, and a side block from column 7 that must be ignored.
GRID = [
["", "", "", "", "", "", "", "", ""], # r0
["", "", "", "", "", "", "", "", ""], # r1
["", "", "", "", "", "", "", "% data", "858"], # r2 (side block header)
["", "TIME", "858", "861", "", "", "", "", ""], # r3 sample IDs
["", "0", "200", "100", "", "", "", "15", "x"], # r4 basal
["", "15", "300", "150", "", "", "", "30", "y"], # r5
["", "30", "100", "250", "", "", "", "45", "z"], # r6
]
def _plan() -> ReshapePlan:
return ReshapePlan(
sheets=[SheetReshapePlan(
logical_name="ITT.xlsx::KO M HFD",
samples_axis="columns",
sample_id_row=3,
variable_label_index=1,
value_first=4,
value_last=6,
group=GroupAssignment(genotype="KO", sex="Male", diet="HFD"),
variable_kind="timepoint",
)],
)
def test_canonical_ids_and_metadata():
values, metadata = _process_columns_sheet(GRID, _plan().sheets[0], _plan())
assert set(values.keys()) == {"KO-M-HFD-858", "KO-M-HFD-861"}
assert metadata["KO-M-HFD-858"] == {"Genotype": "KO", "Sex": "Male", "Diet": "HFD"}
def test_raw_timepoint_values():
values, _ = _process_columns_sheet(GRID, _plan().sheets[0], _plan())
s = values["KO-M-HFD-858"]
assert s["T0"] == 200.0
assert s["T15"] == 300.0
assert s["T30"] == 100.0
def test_percent_basal_against_t0():
values, _ = _process_columns_sheet(GRID, _plan().sheets[0], _plan())
s = values["KO-M-HFD-858"]
# 300/200*100 = 150 ; 100/200*100 = 50 ; no %Basal T0 row
assert s["%Basal T15"] == 150.0
assert s["%Basal T30"] == 50.0
assert "%Basal T0" not in s
def test_side_block_columns_ignored():
values, _ = _process_columns_sheet(GRID, _plan().sheets[0], _plan())
# Only the two real animals: the "% data" side block (col 7+) is not a sample.
assert len(values) == 2
# ---------------------------------------------------------------------------
# Samples-in-rows (insulin-style): row labels are samples, columns are measures.
# Header uses raw aliases; a blank + junk row follows the real data.
# ---------------------------------------------------------------------------
ROWS_GRID = [
["", "pg/ml", "80X", "ug / ml", "DNA", "ng Insulin/ug DNA"], # r0 header (aliases)
["Wt F Chow 1", "614", "49139", "0.049", "0.045", "1091"], # r1 sample
["KO M Chow 2", "3652", "292190", "0.292", "0.081", "1942"], # r2 sample
["", "", "", "", "", ""], # r3 blank -> data stops
["Mean", "2133", "", "", "", ""], # r4 junk summary
]
def _rows_plan() -> ReshapePlan:
return ReshapePlan(sheets=[SheetReshapePlan(
logical_name="Insulin.xls::WT F Chow HFD Total Ins",
samples_axis="rows",
sample_id_col=0,
variable_label_index=0,
group=GroupAssignment(),
variable_kind="generic",
)])
def test_rows_label_parser():
assert _parse_row_label_id("Wt F Chow 1", _rows_plan(), GroupAssignment()) == (
"WT-F-C-1", "WT", "Female", "Chow",
)
assert _parse_row_label_id("KO M HFD 12", _rows_plan(), GroupAssignment()) == (
"KO-M-HFD-12", "KO", "Male", "HFD",
)
# A junk cell doesn't parse (used to detect where the data block ends).
assert _parse_row_label_id("Mean", _rows_plan(), GroupAssignment()) is None
def test_variable_alias_mapping():
assert _canonical_variable("80X") == "80-Fold"
assert _canonical_variable("80Fold") == "80-Fold"
assert _canonical_variable("ug / ml") == "ug/ml"
assert _canonical_variable("ug Insulin") == "ug/ml"
assert _canonical_variable("DNA") == "DNA (ug)"
assert _canonical_variable("ng Insulin/ug DNA") == "ng Ins/ug DNA"
assert _canonical_variable("") is None
def test_rows_axis_ids_aliases_and_metadata():
values, metadata = _process_rows_sheet(ROWS_GRID, _rows_plan().sheets[0], _rows_plan())
assert set(values.keys()) == {"WT-F-C-1", "KO-M-C-2"}
# Aliased headers land under canonical variable names.
assert values["WT-F-C-1"]["pg/ml"] == 614.0
assert values["WT-F-C-1"]["80-Fold"] == 49139.0
assert values["WT-F-C-1"]["ug/ml"] == 0.049
assert values["WT-F-C-1"]["DNA (ug)"] == 0.045
assert values["WT-F-C-1"]["ng Ins/ug DNA"] == 1091.0
# Metadata derived from the row label, not a sheet name.
assert metadata["WT-F-C-1"] == {"Genotype": "WT", "Sex": "Female", "Diet": "Chow"}
def test_rows_axis_junk_below_blank_ignored():
values, _ = _process_rows_sheet(ROWS_GRID, _rows_plan().sheets[0], _rows_plan())
# The "Mean" summary row after the blank line must not become a sample.
assert len(values) == 2