refactor(arch): redesign the code structure

This commit is contained in:
Horldsence
2026-09-10 22:38:27 +08:00
parent 21e8a8bf52
commit 83d2e24448
42 changed files with 2514 additions and 251 deletions
+71
View File
@@ -0,0 +1,71 @@
"""Build reproducible examples and the interactive visualization from Python results."""
from dataclasses import asdict
import json
import math
from pathlib import Path
from mcm26b import Measurement, solve
ROOT = Path(__file__).resolve().parents[1]
ASSETS = Path(__file__).resolve().parent / "assets"
def measured(stations, target, errors):
return [Measurement(x,y,(math.degrees(math.atan2(target[1]-y,target[0]-x))+e)%360)
for (x,y),e in zip(stations,errors)]
def scenarios():
target=(80,90)
normal=measured([(-600,-250),(650,-100),(100,700)],target,[.65,-.55,.35])
h=20*math.sqrt(3)
triangle=[Measurement(-1000,0,1),Measurement(540,-500*math.sqrt(3),121),
Measurement(520,h+500*math.sqrt(3),241)]
return [
dict(id='normal',name='三点交会',source='synthetic',target=target,measurements=normal),
dict(id='triangle',name='直径圆反例',source='synthetic',target=(20,h/3),measurements=triangle),
dict(id='poor',name='近共线检测点',source='synthetic',target=(900,50),
measurements=measured([(0,0),(100,0),(200,0)],(900,50),[0,0,0])),
dict(id='empty',name='不相容观测',source='synthetic',target=None,
measurements=[Measurement(-100,0,180),Measurement(100,0,0)]),
dict(id='segment',name='零误差退化线段',source='synthetic',target=(40,0),
measurements=[Measurement(0,0,0,0),Measurement(100,0,180,0)]),
]
def main():
cases=scenarios()
practice=ROOT/'output/q1/practice'
for path in sorted(practice.glob('*_observations.json')):
doc=json.loads(path.read_text(encoding='utf-8'))
cases.append(dict(id='practice-'+doc['case_code'],name='官方演练 '+doc['case_code'],
source='official_practice',target=None,
measurements=[Measurement(**m) for m in doc['measurements']]))
examples=ROOT/'examples/q1'
results=ROOT/'output/q1'
examples.mkdir(parents=True,exist_ok=True)
results.mkdir(parents=True,exist_ok=True)
dataset=[]
for case in cases:
ms=case['measurements']
frames=[solve(ms[:n]) for n in range(1,len(ms)+1)]
data={**case,'measurements':[asdict(m) for m in ms],'frames':frames}
dataset.append(data)
if case['source']=='synthetic':
(examples/(case['id']+'.json')).write_text(json.dumps({k:v for k,v in data.items() if k!='frames'},ensure_ascii=False,indent=2),encoding='utf-8')
(results/(case['id']+'_result.json')).write_text(json.dumps(frames[-1],ensure_ascii=False,indent=2,allow_nan=False),encoding='utf-8')
(results/'demo_data.json').write_text(json.dumps(dataset,ensure_ascii=False,indent=2,allow_nan=False),encoding='utf-8')
template=(ASSETS / 'q1.template.html').read_text(encoding='utf-8')
fragment=template.replace('/*__Q1_DATA__*/',json.dumps(dataset,ensure_ascii=False,allow_nan=False))
(ROOT/'output'/'q1'/'question1_full.html').write_text(fragment,encoding='utf-8')
# A standalone companion uses the same fragment and computation results.
theme=(ASSETS / 'standalone.css').read_text(encoding='utf-8')
page='<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>B题问题一交会定位</title><style>'+theme+'</style><body>'+fragment+'</body></html>'
(results/'question1.html').write_text(page,encoding='utf-8')
print(json.dumps({'cases':len(dataset),'fragment_bytes':len(fragment.encode('utf-8')),
'standalone':str(results/'question1.html')},ensure_ascii=False))
if __name__=='__main__':
main()