diff --git a/server/README.md b/server/README.md index 27e30c3d6..7ef26c855 100644 --- a/server/README.md +++ b/server/README.md @@ -100,7 +100,7 @@ Notation, all in feet: the battery stands at s from `s0` to `s1` (width W = 2.58 | `route_path` | `walls`, openings on the route | wall band from the meter to the battery's near edge, seen higher than the cable's run (`route.height_ft`, 1.0) | | `route_length` | `walls`, `meter` | nothing to observe | -Errors: a measured value passes only when it clears the rule by more than its error. Objects take the default error for their `source` (tape 0.05, tap 0.3, vlm 1.5, plus 0.16 per foot along the walls for tap and vlm) unless they carry `plus_minus_ft`, and walls likewise (tap 0.3, mesh 0.5, plane 0.75, each plus the same drift). Send a wall's `source` when its line comes from the mesh or detected planes rather than taps, so the error bars match how it was measured; `facing` and `overheads` entries default to the mesh error, 0.5. Every `out_ft` is taken as exact, so report the distance you are sure of (for a walked path, the distance from the wall less your position error). +Errors: a measured value passes only when it clears the rule by more than its error. Objects take the default error for their `source` (tape 0.05, tap 0.3, vlm 1.5, plus 0.16 per foot along the walls for tap and vlm) unless they carry `plus_minus_ft`, and walls likewise (tap 0.3, mesh 0.5, plane 0.75, each plus the same drift). Send a wall's `source` when its line comes from the mesh or detected planes rather than taps, so the error bars match how it was measured; `facing` and `overheads` entries default to the mesh error, 0.5. The meter's working space passes only when the battery is clear of it by more than the meter's error plus e, but fails as soon as the battery overlaps it by more than e alone: the spot is placed and shown against the meter's anchor, so the meter's error can hold back a pass but never excuse an overlap (issue #44). Every `out_ft` is taken as exact, so report the distance you are sure of (for a walked path, the distance from the wall less your position error). Ends and corners: diff --git a/server/solver.py b/server/solver.py index fd22f1459..04ec202e4 100644 --- a/server/solver.py +++ b/server/solver.py @@ -591,8 +591,12 @@ def check_meter_space(self, piece: Piece, fp: Polygon, s0: float, s1: float) -> if d <= EPS: d = min(0.0, max(self.ws_span[0] - s1, s0 - self.ws_span[1])) e = self.scene.meter_plus_minus + piece.plus_minus - c.measured, c.plus_minus = d, e c.outcome = at_least(d, e, 0.0) + # The spot is placed and shown against the meter's anchor, so the meter's error can hold + # back a pass but never excuse an overlap the wall's error alone doesn't cover. + if c.outcome == UNSURE and at_least(d, piece.plus_minus, 0.0) == FAIL: + c.outcome, e = FAIL, piece.plus_minus + c.measured, c.plus_minus = d, e box = f"{ft(ws.width_ft.value)} wide by {ft(ws.depth_ft.value)} deep" if c.outcome == FAIL: c.reason = f"The battery would stand in the {box} working space in front of the meter." diff --git a/server/tests/test_field_run_2.py b/server/tests/test_field_run_2.py index de439f051..dae1f0061 100644 --- a/server/tests/test_field_run_2.py +++ b/server/tests/test_field_run_2.py @@ -2,12 +2,13 @@ import copy -from helpers import observed_band, parsed, rect, shared_fixture +import pytest +from helpers import W, at_start, observed_band, parsed, rect, shared_fixture from hypothesis import given, settings from hypothesis import strategies as st from test_s4_round import PUBLIC, answer -from solver import SOLVE_BUDGET_S, UNSURE, Solver, estimate_fails +from solver import FAIL, PASS, SOLVE_BUDGET_S, UNSURE, Solver, estimate_fails # --- #44: a spot whose best estimate overlaps the meter's working space ---------------------- @@ -57,6 +58,29 @@ def test_an_estimate_past_a_rule_never_outranks_one_that_clears( assert not estimate_fails(chosen) +def test_an_overlap_past_the_walls_error_fails_the_working_space() -> None: + # Before: -1.25 against the meter's 0.792 plus the wall's 0.713 was UNSURE. The meter's + # error no longer excuses an overlap, so the numbers give FAIL: -1.25 + 0.713 < 0. + c = at_start(meter_known_loosely(), -W, "meter_working_space", PUBLIC) + assert c.measured == pytest.approx(-1.25) + assert c.outcome == FAIL + assert c.plus_minus == pytest.approx(0.713) + + +@pytest.mark.parametrize( + ("s1", "outcome"), + [ + (-1.25 - 0.792 - 0.713 - 0.1, PASS), # clear by more than both errors + (-1.25 - 0.1, UNSURE), # clear, but by less than both + (-1.25 + 0.713 - 0.1, UNSURE), # an overlap within the wall's error + ], +) +def test_the_meters_error_still_holds_back_a_working_space_pass(s1: float, outcome: str) -> None: + c = at_start(meter_known_loosely(), s1 - W, "meter_working_space", PUBLIC) + assert c.outcome == outcome + assert c.plus_minus == pytest.approx(0.792 + 0.713) + + # --- #45: the summary counts only unseen checks -----------------------------------------------