MEGAINFRA-WEBAPP/scripts/site-model/test_munyaka.py
DEls 8e1cbc8b75 feat(site-map): Munyaka 2 stormwater and village network, Progress section highlight, layer switches
Munyaka model: 27 stormwater structures and 26 pipes from the issued structure table
(MH/KI/outlet by CAD block), water node points, and the information-stage village network
(services, ponds, boundary, lots, hydrants) aligned from the SDP Rev C PDF.

Map 3: selected Progress section is outlined and its row highlighted; Show-on-map gains
ground surface, contours, kerbs, markings, toes, reserve, signs, studs and HOLD switches;
the ground fades when underground services are ticked; pipes and manholes are chunkier.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 12:13:38 +02:00

118 lines
6.8 KiB
Python

"""Guard source fidelity when importing the Munyaka construction drawings."""
import unittest
import json
from pathlib import Path
from shapely.geometry import box
from shapely.ops import unary_union
from extract_munyaka import point_table, planning_bands
class MunyakaTests(unittest.TestCase):
def test_existing_sewer_connection_retains_the_issued_level_check(self):
root = Path(__file__).resolve().parents[2] / 'reports/site-models/munyaka/2026-10-28'
services = json.loads((root/'services.json').read_text())
sr1 = next(m for m in services['sewer']['manholes'] if m['id'] == 'SR1')
pipe = next(p for p in services['sewer']['pipes'] if p['to'] == 'SR1')
self.assertTrue(sr1.get('hold'))
self.assertEqual(pipe['hold'], sr1['hold'])
self.assertIn('confirm', ' '.join(h['english'] for h in services['holds']).lower())
def test_import_holds_unresolved_points_and_has_no_fabricated_ground(self):
root = Path(__file__).resolve().parents[2] / 'reports/site-models/munyaka/2026-10-28'
road = json.loads((root/'bushveld.json').read_text())
earth = json.loads((root/'earthworks.json').read_text())
report = json.loads((root/'validation.json').read_text())
self.assertEqual(len(road['pegs']), 206)
self.assertNotIn('R155', {p['name'] for p in road['pegs']})
self.assertEqual(len(report['setting_out']['pending_levels']), 30)
self.assertFalse(report['cut_fill_available'])
self.assertEqual(earth['sections'], [])
self.assertEqual(earth['volumes'], [])
self.assertNotIn('groundSurface', earth)
def test_sewer_grade_uses_viewer_percent_units_and_junction_drop_is_retained(self):
root = Path(__file__).resolve().parents[2] / 'reports/site-models/munyaka/2026-10-28'
services = json.loads((root/'services.json').read_text())
for pipe in services['sewer']['pipes']:
expected = 100*(pipe['ilFrom']-pipe['ilTo'])/pipe['length']
self.assertAlmostEqual(pipe['grade'], expected, delta=.001)
junction = next(m for m in services['sewer']['manholes'] if m['id'] == 'SR5')
self.assertEqual(junction['il'], 1479.230)
incoming = next(p for p in services['sewer']['pipes'] if p['from'] == 'SR6')
self.assertEqual(incoming['ilTo'], 1479.280)
def test_stormwater_network_is_reconciled_to_the_issued_structure_table(self):
root = Path(__file__).resolve().parents[2] / 'reports/site-models/munyaka/2026-10-28'
sw = json.loads((root/'services.json').read_text())['stormwater']
self.assertEqual(len(sw['structures']), 27)
self.assertEqual(len(sw['pipes']), 26)
names = {s['name'] for s in sw['structures']}
self.assertNotIn('SW22', names)
for pipe in sw['pipes']:
self.assertIn(pipe['from'], names)
self.assertIn(pipe['to'], names)
self.assertGreater(pipe['ilFrom'], pipe['ilTo'])
self.assertAlmostEqual(pipe['grade'], 100*(pipe['ilFrom']-pipe['ilTo'])/pipe['length'], delta=.001)
road = json.loads((root/'bushveld.json').read_text())
es, ns = [p['e'] for p in road['pegs']], [p['n'] for p in road['pegs']]
for st in sw['structures']: # local metres, on the site: guards against a sign flip off the map
self.assertTrue(min(es)-50 < st['e'] < max(es)+50 and min(ns)-50 < st['n'] < max(ns)+50, st['name'])
self.assertAlmostEqual(st['e'], -st['Y']+88650, places=2)
kinds = {k: sum(1 for st in sw['structures'] if st['kind'] == k) for k in ('MH', 'KI', 'OUT')}
self.assertEqual(kinds, {'MH': 18, 'KI': 8, 'OUT': 1})
water = json.loads((root/'services.json').read_text())['water']['points']
self.assertEqual(len(water), 18)
for w in water:
self.assertTrue(min(es)-50 < w['e'] < max(es)+50 and min(ns)-50 < w['n'] < max(ns)+50, w['name'])
outlet = next(p for p in sw['pipes'] if p['to'] == 'SW1')
self.assertEqual((outlet['dia'], outlet['ilTo']), (1050, 1474.795))
# equal-size arrivals at SW6 are paired high-to-high, giving the 1 % road-crossing fall
self.assertEqual(next(p for p in sw['pipes'] if p['from'] == 'SW19')['ilTo'], 1478.766)
def test_village_plan_is_aligned_to_the_cad_model_and_marked_plan_only(self):
root = Path(__file__).resolve().parents[2] / 'reports/site-models/munyaka/2026-10-28'
village = json.loads((root/'services.json').read_text())['village']
self.assertGreaterEqual(village['alignment']['matchedPoints'], 15)
self.assertLess(village['alignment']['medianResidualM'], .6)
by = {s: [r for r in village['routes'] if r['service'] == s] for s in ('stormwater', 'sewer', 'water')}
self.assertTrue(all(by.values()))
self.assertEqual(len(village['hydrants']), 28)
self.assertEqual(len(village['ponds']) > 0, True)
self.assertIn('plan position only', village['source'].lower())
for r in village['routes']: # inside the village footprint, not off in another coordinate system
for e, n in r['pts']:
self.assertTrue(-300 < e < 700 and -500 < n < 400)
self.assertNotIn('quantity', ' '.join(r.get('source', '') for r in village['routes']))
def test_tbc_level_is_not_a_zero_height_design_peg(self):
texts = [(0, 10, 'R1'), (20, 10, '88 804.258'), (40, 10, '2 882 022.158'), (60, 10, '1477.357'),
(0, 0, 'R2'), (20, 0, '88 805.258'), (40, 0, '2 882 023.158'), (60, 0, 'T.B.C.')]
pegs, pending = point_table(texts, (-88650, -2882050))
self.assertEqual([p['name'] for p in pegs], ['R1'])
self.assertEqual(pending, ['R2'])
self.assertAlmostEqual(pegs[0]['e'], -154.258)
self.assertAlmostEqual(pegs[0]['n'], 27.842)
self.assertEqual(pegs[0]['z'], 1477.357)
def test_planning_bands_preserve_holes_and_area_without_overlap(self):
shape = box(0, 0, 63, 10).difference(box(25, 2, 30, 8))
bands = planning_bands(shape, (1, 0), 'access', 'Access road')
self.assertEqual(len(bands), 4)
self.assertAlmostEqual(sum(g.area for _, g in bands), shape.area)
self.assertLess(unary_union([g for _, g in bands]).symmetric_difference(shape).area, 1e-8)
for i, (_, g) in enumerate(bands):
for _, other in bands[i+1:]:
self.assertLess(g.intersection(other).area, 1e-8)
self.assertEqual(bands[-1][0]['to'], 63)
self.assertIn('planning', bands[0][0]['name'].lower())
def test_planning_origin_is_spatially_stable_across_work_types(self):
shape = box(5, 0, 48, 10)
bands = planning_bands(shape, (0, -1), 'west', 'West connection')
self.assertEqual(bands[0][0]['from'], 0)
self.assertEqual(bands[0][0]['to'], 10)
self.assertEqual(len({s['key'] for s, _ in bands}), len(bands))
if __name__ == '__main__':
unittest.main()