AgentProjectMinenraeumer / ProjectIntakeTower /verifier /generate_learning_graphic.py
smlflg's picture
Initial public upload from Projekte/AgentProjectMinenraeumer (part 9)
b67a1e9 verified
Raw History Blame Contribute Delete
9.11 kB
#!/usr/bin/env python3
import json, re, html, textwrap
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
ROOT=Path(__file__).resolve().parents[1]
TRIAGE=ROOT/'triage'
OUT=ROOT/'visual_summary'
OUT.mkdir(parents=True,exist_ok=True)
def load(p): return json.loads(Path(p).read_text(encoding='utf-8'))
def short(path):
s=path.replace('/home/smlflg/Projekte/','').replace('/home/smlflg/','~/' )
return s if len(s)<=48 else '…'+s[-47:]
def wrap(draw, text, font, width):
words=str(text).split(); lines=[]; cur=''
for w in words:
test=(cur+' '+w).strip()
if draw.textbbox((0,0),test,font=font)[2] <= width: cur=test
else:
if cur: lines.append(cur)
cur=w
if cur: lines.append(cur)
return lines
def top_names(items,n=8): return [short(x['path']) for x in items[:n]]
def main():
pri=load(TRIAGE/'PROJECT_PRIORITIZATION.json')
summary=load(TRIAGE/'PROJECT_PRIORITIZATION_SUMMARY.json')
keep=pri['categories']['KEEP']; urgent=pri['categories']['URGENT']; merge=pri['categories']['MERGE']; pause=pri['categories']['PAUSE']; archive=pri['categories']['ARCHIVE']; token=pri['categories']['TOKEN_BURN_CANDIDATE']; human=pri['categories']['HUMAN_DECISION_REQUIRED']
bypath={r['path']:r for r in pri['projects']}
def get(path): return bypath.get(path, {'path':path,'score':0,'dirty_count':0,'categories':[]})
top10=[
get('/home/smlflg/Projekte/Sidecar-Workspace/Sidecar-V7'),
get('/home/smlflg/Projekte/Zentrale'),
get('/home/smlflg/Projekte/ACT-agentcommandotower'),
get('/home/smlflg/Projekte/AIOS-Cockpit'),
get('/home/smlflg/Projekte/AgentArena'),
get('/home/smlflg/Projekte/Jobs_Selbständigkeit_Geldverdienen/AgentTestUniSupport'),
get('/home/smlflg/Projekte/Sidecar-Workspace/Sidecar-V8'),
get('/home/smlflg/.hermes'),
get('/home/smlflg/Projekte/UEBER_AGENT'),
get('/home/smlflg/Projekte/PromptGarage'),
]
gold=[get(p) for p in ['/home/smlflg/Projekte/Zentrale','/home/smlflg/Projekte/ACT-agentcommandotower','/home/smlflg/Projekte/AIOS-Cockpit','/home/smlflg/Projekte/Sidecar-Workspace/Sidecar-V8','/home/smlflg/.hermes']]
almost=[get(p) for p in ['/home/smlflg/Projekte/Jobs_Selbständigkeit_Geldverdienen/AgentTestUniSupport','/home/smlflg/Projekte/Sidecar-Workspace/Sidecar-V7','/home/smlflg/Projekte/ClaudeCodePanel','/home/smlflg/Projekte/PromptGarage']]
needs_tests=[r for r in keep if 'python' in (r.get('tech') or '').lower()][:7]
tomorrow=[get('/home/smlflg/Projekte/Zentrale'), get('/home/smlflg/Projekte/ACT-agentcommandotower'), get('/home/smlflg/.hermes')]
garbage=archive[:8]
data={
'title':'Samuel Project Map — Gold, Müll, Morgen',
'counts':summary,
'top10':top10,'gold':gold,'garbage':garbage,'almost_done':almost,'needs_tests':needs_tests,
'scope_creep':token[:8],'archive':archive[:8],'tomorrow':tomorrow,'human':human[:8],
'rules':[
'KEEP = weiterführen, aber mit Verifier',
'MERGE = wertvoll, aber nicht separat weiterbauen',
'PAUSE = nicht löschen, aber keine Tokens mehr verbrennen',
'ARCHIVE = nur nach Human Gate; zuerst Manifest sichern',
'URGENT = dirty/risikoreich: morgen zuerst stabilisieren'
]
}
(OUT/'learning_graphic_data.json').write_text(json.dumps(data,indent=2,ensure_ascii=False),encoding='utf-8')
W,H=1800,2400
bg='#08111f'; card='#101b2d'; stroke='#26364f'; text='#e5eefc'; muted='#94a3b8'
img=Image.new('RGB',(W,H),bg); d=ImageDraw.Draw(img)
def font(size,bold=False):
p='/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf' if bold else '/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf'
return ImageFont.truetype(p,size)
f_title=font(60,True); f_h=font(36,True); f=font(25); f_s=font(20); f_tiny=font(17)
d.text((70,55),'🧭 Samuel Project Map',fill='#67e8f9',font=f_title)
d.text((72,130),'Was ist Gold, was ist Müll, was braucht Tests, was morgen zuerst?',fill=muted,font=f)
# metric pills
x=70; y=190
pills=[('KEEP',summary['KEEP'],'#22c55e'),('MERGE',summary['MERGE'],'#a78bfa'),('PAUSE',summary['PAUSE'],'#94a3b8'),('ARCHIVE',summary['ARCHIVE'],'#f97316'),('URGENT',summary['URGENT'],'#ef4444'),('TOKEN BURN',summary['TOKEN_BURN_CANDIDATE'],'#facc15')]
for label,val,col in pills:
d.rounded_rectangle((x,y,x+250,y+64),radius=18,fill='#0f172a',outline=col,width=3)
d.text((x+18,y+14),f'{label}: {val}',fill=col,font=f_s)
x+=270
def card_box(x,y,w,h,title,color,items,subtitle=None):
d.rounded_rectangle((x,y,x+w,y+h),radius=24,fill=card,outline=color,width=4)
d.text((x+28,y+22),title,fill=color,font=f_h)
cy=y+78
if subtitle:
for ln in wrap(d,subtitle,f_s,w-56)[:2]:
d.text((x+28,cy),ln,fill=muted,font=f_s); cy+=27
cy+=8
for i,it in enumerate(items):
name=short(it['path']) if isinstance(it,dict) else str(it)
score=it.get('score') if isinstance(it,dict) else None
dirty=it.get('dirty_count') if isinstance(it,dict) else None
line=f'{i+1}. {name}'
if score is not None: line+=f' s={score}'
if dirty: line+=f' dirty={dirty}'
for j,ln in enumerate(wrap(d,line,f_s,w-70)[:2]):
d.text((x+30,cy),ln,fill=text if i<5 else muted,font=f_s); cy+=25
cy+=6
if cy>y+h-30: break
card_box(70,290,800,535,'🏆 Deine 10 wichtigsten Projekte','#22d3ee',top10,'Das ist dein echtes Agentic-OS: Control Tower + Sidecar + Hermes + Eval + Uni.')
card_box(930,290,800,535,'💎 Gold','#22c55e',gold,'Nicht neu anfangen. Diese Kerne stabilisieren und verbinden.')
card_box(70,870,530,420,'🗑️ Müll / Cache','#fb923c',garbage,'Vor allem repo_cache/fixtures. Erst Manifest, dann archivieren.')
card_box(635,870,530,420,'✅ Fast fertig','#84cc16',almost,'Hat klare Idee/README oder MVP-Signal. Braucht Closure statt Neubau.')
card_box(1200,870,530,420,'🧪 Braucht nur Tests','#38bdf8',needs_tests,'Nicht weiter bauen, bevor Smoke-Test/pytest grün ist.')
card_box(70,1335,530,455,'🌀 Scope-Creep','#facc15',token[:8],'Viele Pläne/TODOs/Agent-Spuren. Token-Burn-Gefahr.')
card_box(635,1335,530,455,'📦 Archivieren','#fb7185',archive[:8],'Nur Human-Gated. Nichts löschen; erst Patch/Manifest sichern.')
card_box(1200,1335,530,455,'🌅 Morgen als erstes','#c084fc',tomorrow,'1) Zentrale stabilisieren 2) ACT als Closure-Prozess 3) Hermes dirty state sichern')
# rules bottom
d.rounded_rectangle((70,1840,1660,455+1840),radius=24,fill='#0f172a',outline='#64748b',width=3)
d.text((100,1870),'🧠 Lernregel',fill='#e879f9',font=f_h)
yy=1930
for rule in data['rules']:
for ln in wrap(d,'• '+rule,f,1480):
d.text((110,yy),ln,fill=text,font=f); yy+=34
d.text((100,2220),'Morgen-Default: Keine neuen Projekte. Erst Zentrale + ACT + Hermes in einen verifizierbaren Loop bringen.',fill='#67e8f9',font=f)
d.text((100,2280),'Human Gate bleibt hart: Archive/Merge/Commit/Push nur nach expliziter Freigabe.',fill='#fca5a5',font=f_s)
img.save(OUT/'project_learning_graphic.png')
# HTML companion
def ul(items):
return '<ul>'+''.join(f'<li><code>{html.escape(short(x["path"]))}</code> <span>s={x.get("score")}</span></li>' for x in items)+'</ul>'
html_doc=f'''<!doctype html><html><head><meta charset="utf-8"><title>Samuel Project Map</title><style>body{{background:{bg};color:{text};font-family:Menlo,monospace;margin:0;padding:36px}}.grid{{display:grid;grid-template-columns:repeat(auto-fit,minmax(360px,1fr));gap:18px}}.card{{background:{card};border:1px solid {stroke};border-radius:18px;padding:20px}}h1{{color:#67e8f9}}h2{{color:#c084fc}}code{{color:#7dd3fc}}img{{max-width:100%;border-radius:20px;border:1px solid #334155}}</style></head><body><h1>Samuel Project Map — Lern-Grafik</h1><p><a href="project_learning_graphic.png">PNG öffnen</a></p><img src="project_learning_graphic.png"><div class="grid"><div class="card"><h2>10 wichtigste Projekte</h2>{ul(top10)}</div><div class="card"><h2>Gold</h2>{ul(gold)}</div><div class="card"><h2>Müll / Cache</h2>{ul(garbage)}</div><div class="card"><h2>Fast fertig</h2>{ul(almost)}</div><div class="card"><h2>Braucht Tests</h2>{ul(needs_tests)}</div><div class="card"><h2>Scope-Creep</h2>{ul(token[:8])}</div><div class="card"><h2>Archivieren</h2>{ul(archive[:8])}</div><div class="card"><h2>Morgen zuerst</h2>{ul(tomorrow)}</div></div></body></html>'''
(OUT/'project_learning_graphic.html').write_text(html_doc,encoding='utf-8')
result={'status':'succeeded','png':str(OUT/'project_learning_graphic.png'),'html':str(OUT/'project_learning_graphic.html'),'data':str(OUT/'learning_graphic_data.json'),'top10':[x['path'] for x in top10]}
(OUT/'result.json').write_text(json.dumps(result,indent=2,ensure_ascii=False),encoding='utf-8')
print(json.dumps(result,indent=2))
if __name__=='__main__': main()