AISA v2.0 / Technical documentation / build_case_content_docs.py
build_case_content_docs.py
Python · 108 lines · 8,068 bytes · wiki path 10 Architecture/ui/build_case_content_docs.py · download the raw file · cited from Case protocol
Same folder: agent-catalog.json · agent_catalog.py · agent_map_svg.py · build_agent_map.py · build_case_journeys.py · build_case_traces.py · build_operations_examples.py · case-content.json · case-journeys.json · case-runtime-traces.json · case-ui-specs.json · case_content.py · case_ui_model.py · frequent-operations.json · memory-capabilities.json · validate_agent_map.py · validate_case_content.py
"""Keep the catalogue's sample content and current paths with its shared data source."""
from pathlib import Path
from html import escape
import re
import json
from _paths import ARCH,SITE,DOCS_DIRNAME
HERE=Path(__file__).resolve().parent
def current_path(j):
lookup={s['uid']:s for s in j['paths']['first']}
show=lambda ids:' → '.join(lookup[x]['stage']+' '+lookup[x]['label'] for x in ids)
parts=[('['+' ∥ '.join(show(l) for l in g['lanes'])+']') if g['kind']=='parallel' else show(g['steps']) for g in j['groups']['first']]
owner={'commercial':'Assessment & Offers inside the same Support case','support':'Core classification and communication in the same Support case','data':'Data & Manipulation in the same case','configuration':'accepted Configuration subcase','source':'accepted Development subcase'}[j['ui']['working_module']]
extra=' Required S7 restoration precedes resolution.' if j['ui']['restoration_required'] else ''
if j['ui']['working_module']=='data':extra+=' Investigation is conditional; a ready skill or checked answer uses its current preflight without an unnecessary S2 visit.'
if j['ui']['outcome_type']=='referral':extra+=' The outcome is scoped request/referral communication; no implementation is claimed.'
return 'Support intake/classification → route → '+owner+': '+' → '.join(parts)+' → Support closure.'+extra
def details(case,j):
p=j['ui']['content'];list_html=lambda items:'<ol>'+''.join('<li>'+escape(x)+'</li>' for x in items)+'</ol>'
sections=[('Customer request','<p>'+escape(j['ui']['request'])+'</p>'),
('Specific failure illustrated','<p>'+escape(p['failure'])+'</p>'),
('Initial solution take','<p>'+escape(p.get('take',p['failure']))+'</p><p>'+escape(p.get('take_decision','The related procedure is a candidate until its current preconditions are checked.'))+'</p>'),
('Evidence to establish — investigation or the known skill’s preflight',list_html(p['findings'])),
('Return to solution take','<p>'+escape(p['solution'])+'</p><p>Reject this shortcut: '+escape(p['falsifier'])+'</p>'),
('Confirmation and application',list_html(p['steps'])),
('Verification',list_html(p['checks'])),
('Recovery','<p>'+escape(p['recovery'])+'</p>'),
('What is retained','<p><b>Memory:</b> '+escape(p['memory'])+'</p><p><b>Skill:</b> '+escape(p['skill'])+'</p><p><b>Tool candidate:</b> '+escape(p['tool'])+'</p><p><b>Next request:</b> '+escape(p['similar'])+'</p>')]
if p.get('archive_result'):sections.insert(6,('What the archive actually records','<p>'+escape(p['archive_result'])+'</p>'))
content='<details class="case-content"><summary>Concrete sample content: request, evidence, action and learning [P]</summary><p>'+escape(p['basis'])+'</p>'
content+=''.join('<p><strong>'+title+'</strong></p>'+body for title,body in sections)
content+='<p><b>Source basis:</b> '+escape('; '.join(s['label']+' — '+s['path'] for s in p['sources']))+'</p></details>'
return '<!-- CASE-CONTENT:'+case['id']+' -->\n'+content+'\n<!-- END CASE-CONTENT:'+case['id']+' -->'
def update_catalogues(cases,traces):
for source in sorted({c['source'] for c in cases}):
path=ARCH/source;text=path.read_text(encoding='utf-8')
for c in [c for c in cases if c['source']==source]:
j=traces[c['id']]['journey'];match=re.search(r'\*\*'+re.escape(c['id'])+r' — [^\n]+',text)
if not match:raise ValueError('Missing catalogue entry '+c['id'])
paragraph=re.sub(r'\*Current illustrated path \[P\]:\* .*?(?=\*Journey:\*)','*Current illustrated path [P]:* '+current_path(j)+' ',match.group(0))
text=text[:match.start()]+paragraph+text[match.end():]
block=details(c,j);pattern=r'<!-- CASE-CONTENT:'+c['id']+r' -->.*?<!-- END CASE-CONTENT:'+c['id']+r' -->'
if re.search(pattern,text,re.S):text=re.sub(pattern,lambda _:block,text,flags=re.S)
else:
at=match.start()+len(paragraph);text=text[:at]+'\n\n'+block+text[at:]
path.write_text(text,encoding='utf-8')
def update_studies(traces):
studies=json.loads((HERE/'case-content.json').read_text(encoding='utf-8'))['studies']
path=ARCH/'40 Support/Case Studies.md';text=path.read_text(encoding='utf-8')
for number,entry in studies.items():
if 'case_id' in entry:
j=traces[entry['case_id']]['journey'];p={**j['ui']['content'],'request':j['ui']['request']}
else:p=entry
row=lambda title,value:'<p><strong>'+title+'</strong></p>'+('<ol>'+''.join('<li>'+escape(x)+'</li>' for x in value)+'</ol>' if isinstance(value,list) else '<p>'+escape(value)+'</p>')
body=row('Request and first hypothesis',p['request']+' '+p['failure'])+row('Investigation / current preflight',p['findings'])+row('Return to solution take',p['solution'])+row('Confirmation and application',p['steps'])+row('Verification',p['checks'])+row('Learning',p['memory']+' '+p['skill']+' '+p['tool'])
block='<!-- STUDY-CONTENT:'+number+' -->\n<details class="case-content"><summary>Content carried between the screens [P]</summary><p>The historical evidence above belongs to this case. These are the proposed v2 screen contents and checks; they do not claim a new execution.</p>'+body+'</details>\n<!-- END STUDY-CONTENT:'+number+' -->'
pattern=r'<!-- STUDY-CONTENT:'+number+r' -->.*?<!-- END STUDY-CONTENT:'+number+r' -->'
if re.search(pattern,text,re.S):text=re.sub(pattern,lambda _:block,text,flags=re.S)
else:
section=re.search(r'^## Case '+number+r'\b.*?(?=^## Case |^## Gap table|\Z)',text,re.M|re.S)
if not section:raise ValueError('Missing case study '+number)
at=text.index('**Walk through v2',section.start());text=text[:at]+block+'\n\n'+text[at:]
path.write_text(text,encoding='utf-8')
def marketing_block(m,j,cid):
"""The goals-section example: one real case, its module detail and its screen."""
li=lambda items:''.join('<li>'+escape(x)+'</li>' for x in items)
return ('<article class="goal-example reveal" id="one-case" aria-labelledby="goal-example-title">\n'
'<div class="goal-example-context">\n'
'<p class="goal-example-label">'+escape(m['label'])+'</p>\n'
'<h3 id="goal-example-title">'+'<br>'.join(escape(x) for x in m['heading'])+'</h3>\n'
'<blockquote>'+escape(m['quote'])+'</blockquote>\n'
'<p class="goal-example-origin">'+escape(m['origin'])+'</p>\n'
'<p class="goal-example-today">'+escape(m['today'])+'</p>\n'
'</div>\n'
'<div class="goal-example-value">\n'
'<div><h4>For this customer</h4><p>'+escape(m['customer'])+'</p></div>\n'
'<div><h4>For the next request</h4><p>'+escape(m['next'])+'</p></div>\n'
'</div>\n'
'<div class="goal-example-mock">\n'
'<h4>The screen the approver works in, with this case’s own content<span class="ge-zoom-tip">hover it for full size</span></h4>\n'
'<div class="ge-row">'
'<div class="ge-zoom">'
'<iframe src="'+DOCS_DIRNAME+'/10-architecture/ui/operations_preview.html?view=decision&compact=1" '
'title="'+escape(cid)+' plan and decision screen" loading="lazy"></iframe>'
'</div>\n'
'<div class="ge-tech"><h4>What it touches in the Configuration module</h4><ul>'+li(m['touches'])+'</ul>'
'<h4>What has to be proved before it is delivered</h4><ul>'+li(j['ui']['content']['checks'])+'</ul>'
'<p class="ge-fail"><b>The variant that must fail:</b> '+escape(j['ui']['content']['falsifier'])+'</p></div>'
'</div>\n'
'</div>\n</article>')
def update_marketing_content(traces):
p=SITE/'index.html';text=p.read_text(encoding='utf-8')
m=json.loads((HERE/'case-content.json').read_text(encoding='utf-8'))['marketing']
start=text.index('<article class="goal-example');end=text.index('</article>',start)+len('</article>')
text=text[:start]+marketing_block(m,traces[m['case']]['journey'],m['case'])+text[end:]
p.write_text(text,encoding='utf-8')