#!/usr/bin/env python3
"""P2Z-I: Genau 3 produktive Writes mit voller Kostenmessung."""
import sqlite3, subprocess, time, json, urllib.request, datetime, sys
def credits():
key = ""
with open("/etc/graphiti/graphiti.env") as f:
for line in f:
if line.startswith("OPENROUTER_API_KEY="):
key = line.split("=", 1)[1].strip()
break
req = urllib.request.Request("https://openrouter.ai/api/v1/credits",
headers={"Authorization": f"Bearer {key}"})
return json.loads(urllib.request.urlopen(req, timeout=30).read())["data"]
def llm_calls_since(ts_iso):
"""Count LLM calls + tokens from processing events since ts."""
try:
with open('/opt/struktur/graphiti/request-data/processing-events.jsonl') as f:
calls = 0; inp = 0; outp = 0
for line in f:
try: e = json.loads(line)
except: continue
if e.get('timestamp','') <= ts_iso: continue
if e['phase'].startswith('llm_call_') and e['outcome']=='completed':
calls += 1
d = e.get('details', {})
inp += d.get('input_tokens', 0) or 0
outp += d.get('output_tokens', 0) or 0
return calls, inp, outp
except FileNotFoundError:
return 0, 0, 0
k = sqlite3.connect('/opt/struktur/youtube-research/knowledge.db')
now = datetime.datetime.now(datetime.timezone.utc)
# Recover stale processing rows (pre-STOP era leases long expired)
rows = k.execute("select id, lease_expires_at from graphiti_import_queue "
"where reconciliation_record_id like 'aa043:%' and graphiti_status='processing'").fetchall()
stale = [rid for rid, lease in rows if not lease or datetime.datetime.fromisoformat(lease) < now]
for rid in stale:
k.execute("""update graphiti_import_queue set graphiti_status='lease_expired',
last_error_class='lease_expired', updated_at=CURRENT_TIMESTAMP where id=?""", (rid,))
k.commit()
print(f"Recovered {len(stale)} stale rows -> lease_expired", flush=True)
usage = credits()['total_usage']
print(f"Usage start: {usage}", flush=True)
results = []
ok_count = 0
for qid, in k.execute("select id from graphiti_import_queue where reconciliation_record_id like 'aa043:%' "
"and graphiti_status='lease_expired' order by id limit 5").fetchall():
if ok_count >= 3: break
ts_before = datetime.datetime.now(datetime.timezone.utc).isoformat()
t0 = time.time()
r = subprocess.run(['python3', '/opt/struktur/obsidian-graphiti-reconciler-staging/aa043_single_writer.py',
'--queue-id', str(qid)],
capture_output=True, text=True, timeout=900, cwd='/opt/obsidian-vault')
dur = time.time() - t0
st, ep = k.execute("select graphiti_status, graphiti_episode_id from graphiti_import_queue where id=?", (qid,)).fetchone()
u_new = credits()['total_usage']
cost = u_new - usage
calls, inp, outp = llm_calls_since(ts_before)
usage = u_new
rec = {'qid': qid, 'start': ts_before[11:19], 'dur': round(dur,1), 'status': st,
'episode': bool(ep), 'calls': calls, 'in': inp, 'out': outp,
'cost': round(cost, 6), 'rc': r.returncode}
results.append(rec)
print(json.dumps(rec), flush=True)
if st == 'done':
ok_count += 1
else:
print(f"NOT DONE - stderr tail: {r.stderr.strip()[-200:]}", flush=True)
u_end = credits()['total_usage']
print(f"USAGE_END={u_end}", flush=True)
print(f"RESULTS={json.dumps(results)}", flush=True)