Explorer
/proc/2012/task/2121/root/tmp/patch_cockpit.py
← Zurück ↓ Download
from pathlib import Path
p = Path('/opt/struktur/agent-solutions-cockpit/app.py')
s = p.read_text()
old = '''  {% endfor %}\n</div>\n<footer>Agent Solutions · VPS IONOS · 31.70.68.228</footer>'''
new = '''  {% endfor %}\n  <a class="card" href="/routing" target="_blank" rel="noopener noreferrer" style="text-decoration:none;color:inherit;">\n    <div class="card-header"><div class="card-icon">🧭</div><span class="badge planned">read-only</span></div>\n    <div class="card-title">Routing</div>\n    <div class="card-desc">Hermes Routing Control Plane: Entscheidungen, Modelle, Feedback und Hygiene.</div>\n    <div class="card-actions"><span class="btn btn-open">Dashboard öffnen ↗</span></div>\n  </a>\n</div>\n<footer>Agent Solutions · VPS IONOS · 31.70.68.228</footer>'''
if old not in s:
    raise SystemExit('main page insertion anchor not found')
s = s.replace(old, new, 1)
anchor = "@app.route('/favicon.ico')\ndef favicon():\n"
insert = r'''ROUTING_DB = '/home/hermes/.hermes/routing-control/routing.db'

ROUTING_HTML = ''' + "'''" + r'''<!doctype html>
<html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Agent Solutions · Routing</title>
<style>
body{margin:0;background:#0f1117;color:#e2e8f0;font:14px system-ui,sans-serif}header{padding:1.5rem 3rem;border-bottom:1px solid #1e2330;display:flex;align-items:center;justify-content:space-between}h1,h2{color:#fff;margin:0 0 .7rem}h1{font-size:1.5rem}.sub,.muted{color:#94a3b8}.back{color:#93c5fd;text-decoration:none;border:1px solid #1e3a5f;padding:.4rem .8rem;border-radius:7px}.wrap{padding:1.8rem 3rem;max-width:1500px;margin:auto}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:1rem;margin-bottom:1.5rem}.card,section{background:#161b27;border:1px solid #26324a;border-radius:10px;padding:1rem}.metric{font-size:1.8rem;font-weight:700;color:#fff}.label{color:#94a3b8;font-size:.82rem}.two{display:grid;grid-template-columns:repeat(auto-fit,minmax(360px,1fr));gap:1rem;margin-bottom:1.5rem}table{width:100%;border-collapse:collapse;font-size:.82rem}th,td{text-align:left;padding:.55rem .45rem;border-bottom:1px solid #26324a;vertical-align:top}th{color:#93c5fd;font-weight:600;white-space:nowrap}td{color:#dbe4f0}.scroll{overflow:auto;max-height:650px}.pill{display:inline-block;padding:.15rem .45rem;border-radius:5px;background:#26324a;color:#cbd5e1}.sev-critical{color:#fca5a5}.sev-error{color:#fb923c}.sev-warning{color:#fbbf24}.sev-info{color:#93c5fd}.empty{color:#94a3b8;padding:.5rem 0}
</style></head><body><header><div><h1>🧭 Hermes Routing Control Plane</h1><div class="sub">Read-only Auswertung · Datenquelle: routing.db</div></div><a class="back" href="/" target="_blank" rel="noopener noreferrer">← Agent Solutions</a></header>
<main class="wrap"><div class="grid">{% for label,value in metrics %}<div class="card"><div class="label">{{label}}</div><div class="metric">{{value}}</div></div>{% endfor %}</div>
<div class="two"><section><h2>Modellverteilung</h2><table><tr><th>Modellgruppe</th><th>Anzahl</th></tr>{% for row in model_distribution %}<tr><td>{{row.label}}</td><td>{{row.count}}</td></tr>{% endfor %}</table></section>
<section><h2>Router vs Legacy</h2><table><tr><th>Quelle</th><th>Anzahl</th></tr>{% for row in source_distribution %}<tr><td>{{row.label}}</td><td>{{row.count}}</td></tr>{% endfor %}</table></section></div>
<section><h2>Letzte 20 Routingentscheidungen</h2><div class="scroll"><table><tr><th>Zeit</th><th>Board</th><th>Task</th><th>Assignee</th><th>Modell</th><th>Reasoning</th><th>Risk</th><th>Confidence</th><th>Begründung</th></tr>{% for row in decisions %}<tr><td>{{row.time}}</td><td>{{row.board}}</td><td>{{row.task}}</td><td>{{row.assignee}}</td><td>{{row.model}}</td><td>{{row.reasoning}}</td><td>{{row.risk}}</td><td>{{row.confidence}}</td><td>{{row.reasons}}</td></tr>{% endfor %}</table></div></section>
<div class="two"><section><h2>Feedback-Auswertung</h2><h3>Nach Modell</h3><table><tr><th>Modell</th><th>Erfolge</th><th>Gesamt</th><th>Quote</th></tr>{% for row in feedback_model %}<tr><td>{{row.label}}</td><td>{{row.success}}</td><td>{{row.total}}</td><td>{{row.rate}}</td></tr>{% endfor %}</table><h3>Nach Familie</h3><table><tr><th>Familie</th><th>Erfolge</th><th>Gesamt</th><th>Quote</th></tr>{% for row in feedback_family %}<tr><td>{{row.label}}</td><td>{{row.success}}</td><td>{{row.total}}</td><td>{{row.rate}}</td></tr>{% endfor %}</table></section>
<section><h2>Hygiene-Befunde nach Severity</h2><table><tr><th>Severity</th><th>Anzahl</th></tr>{% for row in hygiene_summary %}<tr><td class="sev-{{row.label|lower}}">{{row.label}}</td><td>{{row.count}}</td></tr>{% endfor %}</table><h3>Befunde</h3><table><tr><th>Severity</th><th>Kategorie</th><th>Item</th><th>Detail</th></tr>{% for row in hygiene %}<tr><td class="sev-{{row.severity|lower}}">{{row.severity}}</td><td>{{row.category}}</td><td>{{row.item}}</td><td>{{row.detail}}</td></tr>{% endfor %}</table></section></div></main></body></html>''' + "'''" + '''


def _routing_rows():
    import sqlite3
    from datetime import datetime, timezone
    db = sqlite3.connect('file:' + ROUTING_DB + '?mode=ro', uri=True)
    db.row_factory = sqlite3.Row
    try:
        decisions = db.execute('SELECT board, task_id, assignee, model, reasoning, risk_score, risk_class, confidence, reasons_json, source, created_at FROM decisions ORDER BY created_at DESC LIMIT 20').fetchall()
        all_decisions = db.execute('SELECT model, source FROM decisions').fetchall()
        feedback = db.execute('SELECT model, family, success FROM feedback').fetchall()
        hygiene = db.execute('SELECT severity, category, item, detail FROM hygiene ORDER BY observed_at DESC').fetchall()
    finally:
        db.close()
    def count_by(rows, key, labels=None):
        counts = {}
        for row in rows:
            value = row[key] or 'sonstige'
            counts[value] = counts.get(value, 0) + 1
        if labels is None:
            labels = sorted(counts)
        return [{'label': label, 'count': counts.get(label, 0)} for label in labels]
    def model_group(model):
        value = (model or '').lower()
        for label in ('Luna', 'Sol', 'Astra'):
            if label.lower() in value:
                return label
        return 'sonstige'
    model_counts = {label: 0 for label in ('Luna', 'Sol', 'Astra', 'sonstige')}
    source_counts = {'Router': 0, 'Legacy': 0, 'sonstige': 0}
    for row in all_decisions:
        model_counts[model_group(row['model'])] += 1
        source = (row['source'] or '').lower()
        source_counts['Router' if source == 'router' else 'Legacy' if source == 'legacy' else 'sonstige'] += 1
    def feedback_table(key):
        groups = {}
        for row in feedback:
            label = row[key] or 'unbekannt'
            item = groups.setdefault(label, [0, 0])
            item[0] += int(row['success'] or 0); item[1] += 1
        return [{'label': label, 'success': values[0], 'total': values[1], 'rate': f"{(100 * values[0] / values[1]):.1f}%"} for label, values in sorted(groups.items())]
    def timestamp(value):
        try: return datetime.fromtimestamp(int(value), timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
        except (TypeError, ValueError, OverflowError): return str(value or '')
    formatted = []
    for row in decisions:
        try: reasons = ', '.join(json.loads(row['reasons_json'] or '[]'))
        except (TypeError, ValueError): reasons = str(row['reasons_json'] or '')
        confidence = '' if row['confidence'] is None else f"{float(row['confidence']):.2f}"
        formatted.append({'time': timestamp(row['created_at']), 'board': row['board'], 'task': row['task_id'], 'assignee': row['assignee'] or '—', 'model': row['model'] or '—', 'reasoning': row['reasoning'] or '—', 'risk': f"{row['risk_class']} ({row['risk_score']})", 'confidence': confidence, 'reasons': reasons or '—'})
    hygiene_summary = count_by(hygiene, 'severity', ['critical', 'error', 'warning', 'info'])
    return {'metrics': [('Gesamtentscheidungen', len(all_decisions)), ('Router', source_counts['Router']), ('Legacy', source_counts['Legacy']), ('Feedback-Einträge', len(feedback)), ('Hygiene-Befunde', len(hygiene))], 'model_distribution': [{'label': label, 'count': model_counts[label]} for label in ('Luna', 'Sol', 'Astra', 'sonstige')], 'source_distribution': [{'label': label, 'count': source_counts[label]} for label in ('Router', 'Legacy', 'sonstige')], 'decisions': formatted, 'feedback_model': feedback_table('model'), 'feedback_family': feedback_table('family'), 'hygiene_summary': hygiene_summary, 'hygiene': [dict(row) for row in hygiene]}


@app.route('/routing')
def routing():
    try:
        return render_template_string(ROUTING_HTML, **_routing_rows())
    except (OSError, sqlite3.Error):
        return Response('Routing-Daten nicht verfügbar', status=503, mimetype='text/plain')

'''
if anchor not in s:
    raise SystemExit('route anchor not found')
s = s.replace(anchor, insert + anchor, 1)
p.write_text(s)