Spaces:
Sleeping
Sleeping
File size: 5,394 Bytes
16d2e95 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | """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
|