from pathlib import Path
# Central persistence normalization.
p = Path('/opt/struktur/social-media-radar/aa045_enrich.py')
s = p.read_text()
s = s.replace('from datetime import datetime, timezone\n', 'from datetime import datetime, timezone\n\nfrom published_at_normalizer import normalize_published_at\n')
s = s.replace(' now = datetime.now(timezone.utc).isoformat()\n conn.execute("""', ' now = datetime.now(timezone.utc).isoformat()\n observed_at = data.get("observed_at", "") or now\n conn.execute("""', 1)
s = s.replace(' data.get("observed_at", "") or now,\n data.get("published_at", "") or "",\n', ' observed_at,\n normalize_published_at(data.get("published_at"), observed_at),\n', 1)
p.write_text(s)
# YouTube: preserve raw source wording until central normalizer, and use actual observed_at.
p = Path('/opt/struktur/social-media-radar/intake/multi_intake.py')
s = p.read_text()
s = s.replace(' expires = (datetime.now(timezone.utc) + timedelta(hours=_DECAY_H)).isoformat()\n', ' observed_at = datetime.now(timezone.utc).isoformat()\n expires = (datetime.now(timezone.utc) + timedelta(hours=_DECAY_H)).isoformat()\n', 1)
s = s.replace(' "published_at": (pub_iso if "T" in pub_iso else _parse_relative_time_german(pub_iso)),\n', ' "observed_at": observed_at,\n "published_at": (pub_iso or None),\n', 1)
p.write_text(s)
# Robust SQLite datetime semantics and explicit deep-link highlight loading.
p = Path('/opt/struktur/social-media-agent/app.py')
s = p.read_text()
s = s.replace('def get_radar_signals(limit=12, days=3, sort="relevance",\n status_filter=None, competition_only=False, pinned_only=False):', 'def get_radar_signals(limit=12, days=3, sort="relevance",\n status_filter=None, competition_only=False, pinned_only=False,\n highlight_id=None):', 1)
s = s.replace(' sql = (\n', ' if highlight_id:\n date_clause = "AND signal_id = ?"\n date_params = [highlight_id]\n st_clause = ""\n st_params = []\n comp_clause = ""\n else:\n date_clause = "AND datetime(published_at) IS NOT NULL AND datetime(published_at) >= datetime(?)"\n date_params = [cutoff]\n\n sql = (\n', 1)
s = s.replace(' " AND (published_at GLOB \'[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T%\' AND published_at >= ?)"\n', ' " " + date_clause\n', 1)
s = s.replace(' params = [cutoff] + st_params + [limit]\n', ' params = date_params + st_params + [limit]\n', 1)
s = s.replace(' tab = request.args.get("tab", "radar")\n\n signals = get_radar_signals(limit=20, days=days, sort=sort,\n status_filter=status_filter)\n', ' tab = request.args.get("tab", "radar")\n highlight = request.args.get("highlight", "").strip()\n\n signals = get_radar_signals(limit=20, days=days, sort=sort,\n status_filter=status_filter,\n highlight_id=highlight or None)\n', 1)
s = s.replace(' tab=tab,\n version=cockpit_version)', ' tab=tab,\n highlight=highlight,\n version=cockpit_version)', 1)
p.write_text(s)
# Rendered Cockpit deep-link: select the highlighted signal in the right detail panel.
p = Path('/opt/struktur/social-media-agent/templates/cockpit.html')
s = p.read_text()
s = s.replace('window._S = {};\n', 'window._S = {};\nconst HIGHLIGHT = {{(highlight or "")|tojson}};\n', 1)
marker = "function closePanel() {\n"
# Insert auto-selection immediately before panel functions so sel is defined by execution time.
needle = "// ═══ Panel ═══════════════════════════════════════════════════════════════\nlet _cur = null;\n"
replacement = "// ═══ Panel ═══════════════════════════════════════════════════════════════\nlet _cur = null;\n"
s = s.replace(needle, replacement, 1)
# Append after sel() definition, before panel builder.
needle = "}\n\n// ═══ Panel Builder ════════════════════════════════════════════════════════\n"
replacement = "}\n\nif (HIGHLIGHT && window._S[HIGHLIGHT]) {\n window.addEventListener('DOMContentLoaded', function() { sel(HIGHLIGHT); });\n}\n\n// ═══ Panel Builder ════════════════════════════════════════════════════════\n"
s = s.replace(needle, replacement, 1)
p.write_text(s)