Skip to content

Commit b637a74

Browse files
committed
test: add regression test for undigitised unit affecting later units' trained values
1 parent ed17d93 commit b637a74

1 file changed

Lines changed: 62 additions & 8 deletions

File tree

tests/qgis/test_stratigraphic_value_consistency.py

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,32 @@
66
name to stamp on each extracted isosurface, using its own idea of which
77
value belongs to which unit.
88
9-
These two must agree on direction (does value increase from oldest-to-
10-
youngest, or youngest-to-oldest?), or every extracted surface gets labelled
11-
with the wrong unit while keeping correct geometry -- see the "stratigraphic
12-
column was reversed" fixes in model_manager.py (2025-07-21) and the widget
13-
(2025-08-21, reverted 2025-09-08). This has flipped back and forth as this
14-
plugin and LoopStructural evolved independently; this test pins the
15-
invariant so a future change on either side fails loudly here instead of
16-
silently inverting a user's model.
9+
10+
Both walk `reversed(group.units)`, accumulating cumulative thickness the
11+
same way, so a unit's own training value must equal `u.min()` -- the
12+
cumulative thickness *before* that unit's own thickness is added. This is
13+
also each unit's true base: `add_unit(..., where='top')` (the default)
14+
appends to the end of the column, so building a column correctly means
15+
adding the truly oldest unit first and progressively younger ones after --
16+
each unit's own base is the boundary shared with the next-older neighbour
17+
processed just before it, i.e. `min()`. See LoopStructural's own
18+
`test_get_isovalues_multi_unit_group` (`tests/unit/modelling/
19+
test_stratigraphic_column.py`), whose comment states this explicitly: "the
20+
base of the oldest unit in a group is 0".
21+
22+
If training and `get_isovalues()` disagree on this, every extracted surface
23+
gets labelled with the wrong unit while keeping correct geometry -- see the
24+
"stratigraphic column was reversed" fixes in model_manager.py (2025-07-21)
25+
and the widget (2025-08-21, reverted 2025-09-08). This has flipped back and
26+
forth as this plugin and LoopStructural evolved independently; this test
27+
pins the invariant so a future change on either side fails loudly here
28+
instead of silently inverting a user's model.
29+
30+
Note this is a separate concern from whether a stratigraphic column's units
31+
were themselves *added* in the correct oldest-to-youngest order -- if they
32+
weren't, `min()`/`max()` stop corresponding to true geological base/top no
33+
matter what training does, and the fix is to reorder the column's units,
34+
not to change which value training uses.
1735
"""
1836

1937
import pandas as pd
@@ -96,6 +114,42 @@ def test_two_groups_split_by_unconformity(self, manager):
96114
for unit_name in ('basin_floor', 'basin_fill', 'cover_lower', 'cover_upper'):
97115
assert training_values[unit_name] == pytest.approx(expected_values[unit_name])
98116

117+
118+
def test_undigitised_unit_does_not_shift_later_units_in_group(self, manager):
119+
"""Regression test for a real bug: a unit with no digitised contact
120+
or orientation data (e.g. a "Top" unit nobody has mapped points for)
121+
must still contribute its own thickness to `val` for every unit
122+
that follows it in the group -- `update_foliation_features` used to
123+
`continue` past an undigitised unit before accumulating its
124+
thickness, which shifted every later unit's trained value relative
125+
to what `get_isovalues()` expects.
126+
"""
127+
column = StratigraphicColumn()
128+
column.clear(basement=False)
129+
column.add_unit(name='basin_floor', thickness=50.0, where='top')
130+
column.add_unit(name='basin_fill', thickness=150.0, where='top')
131+
column.add_unit(name='Top', thickness=999.0, where='top')
132+
133+
manager.stratigraphic_column = column
134+
for name in ('basin_floor', 'basin_fill'):
135+
manager.stratigraphy[name]['contact'] = _contact(name)
136+
# 'Top' deliberately has no entry in manager.stratigraphy at all.
137+
138+
manager.update_foliation_features()
139+
140+
training_values = self._training_values_by_unit(manager._captured_calls)
141+
expected_values = {
142+
name: entry['value'] for name, entry in column.get_isovalues().items()
143+
}
144+
145+
for unit_name in ('basin_floor', 'basin_fill'):
146+
assert training_values[unit_name] == pytest.approx(expected_values[unit_name]), (
147+
f"'{unit_name}' was trained with val={training_values[unit_name]} but "
148+
f"get_isovalues() expects value={expected_values[unit_name]} -- an "
149+
f"undigitised unit earlier in the group must still shift later units' "
150+
f"trained values by its own thickness."
151+
)
152+
99153
@staticmethod
100154
def _training_values_by_unit(captured_calls):
101155
combined = pd.concat(captured_calls, ignore_index=True)

0 commit comments

Comments
 (0)