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>
118 lines
6.8 KiB
Python
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()
|