test(concept): turn greedy backward-compat into a real golden pin (#1965 Stage 3a)
The old test_no_program_reproduces_greedy_output_unchanged only compared building_program=None (default) vs explicit None — both through the new _Placer code — so it proved the two None branches agree but did NOT pin the greedy geometry; it would still pass if the _Placer extraction had drifted the output. test_placement.py only checks invariants, never concrete counts/TEAP, so there was no anti-regression guard that the greedy path is byte-identical after the refactor. Replace it with two tests: - test_greedy_output_matches_golden_pin: hard-coded literals per strategy on the fixed _BIG_PARCEL — (features, built_area_sqm, total_floor_area_sqm, apartments_count) — frozen from the current (== pre-refactor) output, so any future deterministic drift in greedy placement FAILS. - test_explicit_none_program_equals_default_greedy: keeps the None-branch equivalence check (default vs explicit None go one greedy path).
This commit is contained in:
parent
94cf1f6217
commit
e8b05d821e
1 changed files with 48 additions and 21 deletions
|
|
@ -51,31 +51,58 @@ def _payload(coords: list[list[float]], **overrides: object) -> ConceptInput:
|
|||
return ConceptInput(**base) # type: ignore[arg-type]
|
||||
|
||||
|
||||
# ── (a) backward-compat: building_program=None reproduces greedy output ─────────────
|
||||
# ── (a) backward-compat: greedy output is byte-identical after the _Placer refactor ──
|
||||
|
||||
# GOLDEN PIN — детерминированный жадный выход на фиксированном участке _BIG_PARCEL
|
||||
# (comfort / 9 эт. / mid_rise). Захвачен ПОСЛЕ извлечения _Placer и сверен ревьювером
|
||||
# как идентичный до-рефактора. Любой будущий ДРЕЙФ жадной геометрии (число секций /
|
||||
# пятно / GFA / квартиры) уронит этот тест — это и есть anti-regression guard, которого
|
||||
# не давали инвариантные тесты в test_placement.py. Формат на стратегию:
|
||||
# (features, built_area_sqm, total_floor_area_sqm, apartments_count).
|
||||
_GREEDY_GOLDEN: dict[str, tuple[int, float, float, int]] = {
|
||||
"max_area": (83, 35856.0, 358560.0, 4830),
|
||||
"max_insolation": (70, 15120.0, 120960.0, 1629),
|
||||
"balanced": (88, 27720.0, 249480.0, 3361),
|
||||
}
|
||||
|
||||
|
||||
def test_no_program_reproduces_greedy_output_unchanged() -> None:
|
||||
# Тот же участок: с building_program=None раскладка ДОЛЖНА совпасть с жадной 1b
|
||||
# (три стратегии, те же ТЭП/число секций) — program-ветка не трогает greedy-путь.
|
||||
def test_greedy_output_matches_golden_pin() -> None:
|
||||
# Реальный golden-pin: жадный путь (building_program не задан) должен выдать РОВНО
|
||||
# замороженные литералы. Так фиксируем, что _Placer-рефактор не сдвинул раскладку.
|
||||
payload = _payload(_BIG_PARCEL)
|
||||
parcel = geometry.parse_parcel(payload)
|
||||
variants = {v.strategy: v for v in placement.place_all_strategies(parcel, payload)}
|
||||
assert set(variants) == set(_GREEDY_GOLDEN)
|
||||
for name, (features, built, gfa, apts) in _GREEDY_GOLDEN.items():
|
||||
v = variants[name]
|
||||
assert len(v.buildings_geojson["features"]) == features, name
|
||||
assert round(v.teap.built_area_sqm, 1) == built, name
|
||||
assert round(v.teap.total_floor_area_sqm, 1) == gfa, name
|
||||
assert v.teap.apartments_count == apts, name
|
||||
# Greedy-режим не выставляет partial-fit сигнал (остаётся None).
|
||||
assert v.placed_count is None
|
||||
assert v.requested_count is None
|
||||
|
||||
|
||||
def test_explicit_none_program_equals_default_greedy() -> None:
|
||||
# building_program=None (явно) и дефолт (поле опущено) идут одним greedy-путём →
|
||||
# должны совпасть покомпонентно. Дополняет golden-pin: ветка None не разветвляется.
|
||||
payload_default = _payload(_BIG_PARCEL)
|
||||
payload_none = _payload(_BIG_PARCEL, building_program=None)
|
||||
|
||||
parcel = geometry.parse_parcel(payload_default)
|
||||
greedy = placement.place_all_strategies(parcel, payload_default)
|
||||
none_branch = placement.place_all_strategies(geometry.parse_parcel(payload_none), payload_none)
|
||||
|
||||
assert {v.strategy for v in greedy} == {"max_area", "max_insolation", "balanced"}
|
||||
assert {v.strategy for v in none_branch} == {"max_area", "max_insolation", "balanced"}
|
||||
by_strategy_g = {v.strategy: v for v in greedy}
|
||||
by_strategy_n = {v.strategy: v for v in none_branch}
|
||||
for name, gv in by_strategy_g.items():
|
||||
nv = by_strategy_n[name]
|
||||
assert gv.teap == nv.teap
|
||||
assert gv.financial == nv.financial
|
||||
assert len(gv.buildings_geojson["features"]) == len(nv.buildings_geojson["features"])
|
||||
# Greedy-режим не выставляет partial-fit сигнал (остаётся None).
|
||||
assert gv.placed_count is None
|
||||
assert gv.requested_count is None
|
||||
variants_default = placement.place_all_strategies(
|
||||
geometry.parse_parcel(payload_default), payload_default
|
||||
)
|
||||
variants_none = placement.place_all_strategies(
|
||||
geometry.parse_parcel(payload_none), payload_none
|
||||
)
|
||||
by_default = {v.strategy: v for v in variants_default}
|
||||
by_none = {v.strategy: v for v in variants_none}
|
||||
assert set(by_default) == set(by_none)
|
||||
for name, dv in by_default.items():
|
||||
nv = by_none[name]
|
||||
assert dv.teap == nv.teap
|
||||
assert dv.financial == nv.financial
|
||||
assert len(dv.buildings_geojson["features"]) == len(nv.buildings_geojson["features"])
|
||||
|
||||
|
||||
# ── (b) 2-item program places EXACTLY the requested sections; TEAP reflects them ─────
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue