Files
kam-mobile/tools/decision_report.py
T

84 lines
6.4 KiB
Python
Raw Normal View History

2026-09-11 18:18:40 +05:00
"""Collect Task 1 decision-probe evidence. Does not choose architecture."""
from __future__ import annotations
import json
from pathlib import Path
from common import ROOT, write_json
QUESTION_MAP={
'T1-RP01-Q01':['oq_host_view','oq_host_callback'],
'T1-RP01-Q02':['decision.schedule.callback_progress','decision.schedule.worker_progress','decision.schedule.lifecycle'],
'T1-RP01-Q03':['decision.persistence.rename_replace','decision.persistence.append_reopen'],
'T1-RP02-Q02':['decision.text.cwstring_unit','decision.text.cp1251_decode','decision.text.cp1251_encode'],
'T1-Q01':['decision.fixture.castle_stream','decision.fixture.materialized_native_path','fixture_staging_driver'],
}
def load(path:Path):
return json.loads(path.read_text(encoding='utf-8'))
def main()->int:
latest_path=ROOT/'reports/LATEST.json'
if not latest_path.is_file():
raise SystemExit('reports/LATEST.json not found; run build first')
latest=load(latest_path);run_id=latest['run_id'];run_dir=ROOT/latest['directory'];build=load(run_dir/'result.json')
devices=[]
for result_path in sorted((ROOT/'reports/device').glob('*/device-result.json')):
try:rec=load(result_path)
except Exception:continue
if rec.get('build_id')!=run_id and rec.get('package',{}).get('build_id')!=run_id:continue
runtime_path=result_path.with_name('runtime.json')
runtime=load(runtime_path) if runtime_path.is_file() else None
devices.append({'directory':str(result_path.parent.relative_to(ROOT)),'result':rec,'runtime':runtime})
out={'schema':'kam-task1-decision-evidence/2','build_id':run_id,'gate':build.get('gate'),
'environment':{'toolchain':build.get('toolchain')},'questions':QUESTION_MAP,'profiles':{},'device_runs':[]}
out['infrastructure_files_changed']=['tools/runner.py','tools/artifacts.py','tools/device.py','tools/decision_report.py',
'config/profiles.json','code/GameViewMain.pas','code/ProbeReport.pas','CODEX_RUN_TASK1_DECISION_TESTS.md','tests/test_probe.py','tests/test_pipeline.py']
for row in build.get('profiles',[]):
if row.get('id') in ('platform','oq_host_view','oq_host_callback'):
out['profiles'][row['id']]={'compile_link':row.get('compile_link'),'package':row.get('package'),'apk':row.get('apk'),'errors':row.get('errors',[])}
for d in devices:
rec=d['result'];runtime=d['runtime'];entry={'directory':d['directory'],'profile':rec.get('profile',rec.get('package',{}).get('profile')),
'status':rec.get('status'),'errors':rec.get('errors',[]),'preflight':rec.get('preflight'),'infrastructure_blocker':rec.get('infrastructure_blocker'),'fixture_staging_driver':rec.get('fixture_staging_driver')}
if runtime:
entry['observations']=[x for x in runtime.get('tests',[]) if str(x.get('id','')).startswith('decision.')]
entry['session']=runtime.get('session')
out['device_runs'].append(entry)
out['history']=[d for d in out['device_runs'] if d.get('status')!='AUTOMATIC_SMOKE_PASS']
valid_runs=[d for d in out['device_runs'] if d.get('status')=='AUTOMATIC_SMOKE_PASS']
observations={}
for d in valid_runs:
for item in d.get('observations',[]):observations[item.get('id')]=item
if d.get('fixture_staging_driver') is not None:observations['fixture_staging_driver']=d['fixture_staging_driver']
blocker=next((d.get('infrastructure_blocker',{}).get('reason') for d in reversed(out['device_runs']) if d.get('infrastructure_blocker')),None)
out['decision_test_results']=[{'question_id':q,'test':test,'status':observations.get(test,{}).get('status','NOT_RUN'),
'evidence':observations.get(test),'blocker':None if test in observations else blocker} for q,tests in QUESTION_MAP.items() for test in tests]
json_path=ROOT/'reports/TASK1-DECISION-EVIDENCE.json';write_json(json_path,out)
lines=['# Task 1 — decision-probe evidence','',f"Build ID: `{run_id}`",'',
'Этот отчёт фиксирует наблюдения и не закрывает архитектурные вопросы Task 1.','',
'## Environment','',f"- Toolchain receipt: `reports/runs/{run_id}/toolchain.json`",'- Docker command receipts are retained under `reports/runs/*/docker-command.json`.','',
'## Infrastructure files changed','']
lines += [f'- `{path}`' for path in out['infrastructure_files_changed']]
lines += ['','## Build/package','', '| Profile | Compile/link | APK | Native library inside APK |','|---|---|---|---|']
for p in ('platform','oq_host_view','oq_host_callback'):
r=out['profiles'].get(p,{});apk=r.get('apk') or {};lines.append(f"| {p} | {r.get('compile_link',{}).get('status','NOT_RUN')} | {r.get('package',{}).get('status','NOT_RUN')} | `{apk.get('main_library_path','')}` |")
lines+=['','## Device observations','']
if not valid_runs:lines.append('Valid device runs for this build: NOT_RUN.')
for d in valid_runs:
lines.append(f"### {d.get('profile')} — {d.get('status')}")
lines.append('')
if d.get('fixture_staging_driver') is not None:lines.append('fixture_staging_driver: `'+json.dumps(d['fixture_staging_driver'],ensure_ascii=False)+'`')
if d.get('preflight') is not None:lines.append('preflight: `'+json.dumps(d['preflight'],ensure_ascii=False)+'`')
if d.get('infrastructure_blocker') is not None:lines.append('infrastructure_blocker: `'+json.dumps(d['infrastructure_blocker'],ensure_ascii=False)+'`')
for obs in d.get('observations',[]):lines.append(f"- `{obs.get('id')}` — `{obs.get('status')}` — {obs.get('detail','')}")
if d.get('errors'):
for e in d['errors']:lines.append('- error: '+str(e))
lines.append('')
if out['history']:
lines+=['## Historical blocked/failed attempts','']
for d in out['history']:
lines.append(f"- `{d.get('directory')}` / {d.get('profile')} — {d.get('status')}: {d.get('infrastructure_blocker') or d.get('errors')}")
lines+=['## Decision-test results','', '| Question / test | Status | Blocker |','|---|---|---|']
for item in out['decision_test_results']:lines.append(f"| {item['question_id']} / {item['test']} | {item['status']} | {item['blocker'] or ''} |")
md_path=ROOT/'reports/TASK1-DECISION-EVIDENCE.md';md_path.write_text('\n'.join(lines)+'\n',encoding='utf-8')
print(md_path);print(json_path);return 0
if __name__=='__main__':raise SystemExit(main())