#!/usr/bin/env python3 """ Landvex Kontrollintelligens Agent — händelser som kunde förebyggts Genererar artiklar om incidenter, olyckor, och systemfel där kontrollintelligens hade kunnat förhindra eller mitigera. """ import argparse import json import os import sys import time from datetime import datetime, timezone from pathlib import Path import anthropic # Konfiguration DEFAULT_KO = Path(__file__).parent / "intag" / "kontrollintelligens_ko.json" DEFAULT_OUTPUT_DIR = Path(__file__).parent / "intag" ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY") DEFAULT_MODEL = "claude-sonnet-5" def log(msg): ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") print(f"[{ts}] {msg}", flush=True) def ladda_ko(ko_path): with open(ko_path, "r", encoding="utf-8") as f: return json.load(f) def spara_ko(ko_path, data): with open(ko_path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) def hitta_nasta(ko, agent_id): """Hitta översta olåsta posten.""" oppen = [p for p in ko["ko"] if p.get("status") == "oppen"] if not oppen: return None oppen.sort(key=lambda x: x["prio"]) return oppen[0] def las_las(post, agent_id): """Lås en post för arbete.""" post["status"] = "in_arbete" post["agent_id"] = agent_id post["las_tid"] = datetime.now(timezone.utc).isoformat() return post def markera_klar(post): """Markera post som klar.""" post["status"] = "klar" post["klar_tid"] = datetime.now(timezone.utc).isoformat() if "las_tid" in post: del post["las_tid"] def skapa_kandidatfil(kandidater, agent_id, output_dir): """Skriv kandidatposter till fil.""" datum = datetime.now(timezone.utc).strftime("%Y-%m-%d") filnamn = f"kontrollintelligens-{datum}-{agent_id}.json" sokvag = output_dir / filnamn payload = { "beskrivning": f"Landvex Kontrollintelligens — händelser {datum}", "extraktor": "landvex-kontrollintelligens", "genererad": datum, "kandidater": kandidater } with open(sokvag, "w", encoding="utf-8") as f: json.dump(payload, f, ensure_ascii=False, indent=2) return sokvag def generera_artikel(handelse, doman, kontext): """ Generera en artikel om en händelse som kunde förebyggts. """ client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY) user_prompt = f"""Analyze this infrastructure incident and explain how control intelligence could have prevented or mitigated it. Write in ENGLISH. Incident: {handelse} Domain: {doman} Context: {kontext} Return as JSON: {{ "slug": "url-friendly-slug", "titel": {{ "sv": "Swedish title", "en": "English title" }}, "incident_beskrivning": "What happened (300+ words in ENGLISH)", "konsekvenser": ["List of consequences"], "forebyggande": ["How control intelligence could have prevented this"], "tidig_varning": ["Early warning signs that were missed"], "rekommendationer": ["Actionable recommendations for infrastructure owners"], "relaterade_system": ["Related infrastructure systems"], "kallor": [ {{ "titel": "Source title", "url": "https://example.com", "typ": "webbsida|rapport|dokument" }} ] }} IMPORTANT: 1. Return ONLY JSON 2. Be specific about control intelligence technologies (IoT sensors, AI analysis, predictive maintenance, etc.) 3. Include real-world applicability 4. Focus on prevention, not just reaction""" log(f"Analyzing incident: {handelse[:50]}...") response = client.messages.create( model=DEFAULT_MODEL, max_tokens=4000, system="You are an expert in infrastructure control intelligence and predictive maintenance. You analyze incidents and explain how smart monitoring could prevent them.", messages=[{"role": "user", "content": user_prompt}] ) # Parsa JSON content = None for block in response.content: if hasattr(block, 'text'): content = block.text break if not content: raise ValueError("No text in response") if "```json" in content: content = content.split("```json")[1].split("```")[0].strip() elif "```" in content: content = content.split("```")[1].split("```")[0].strip() import re content = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f]', '', content) return json.loads(content) def producera_kandidatpost(post, agent_id): """Producera komplett kandidatpost.""" ko_id = post["ko_id"] handelse = post["handelse"] doman = post["doman"] kontext = post.get("kontext", "") log(f"Production starting: {ko_id} - {handelse[:50]}") artikel = generera_artikel(handelse, doman, kontext) kandidat_id = f"KI-{datetime.now(timezone.utc).strftime('%Y%m%d')}-{agent_id}-{ko_id}" kandidat = { "kandidat_id": kandidat_id, "atgard": "ny", "post": { "posttyp": "incident_analysis", "slug": artikel.get("slug", handelse.lower().replace(" ", "-")[:50]), "titel": artikel.get("titel", {"sv": handelse, "en": handelse}), "doman": doman, "incident": { "beskrivning": artikel.get("incident_beskrivning", ""), "konsekvenser": artikel.get("konsekvenser", []), "forebyggande": artikel.get("forebyggande", []), "tidig_varning": artikel.get("tidig_varning", []), "rekommendationer": artikel.get("rekommendationer", []) }, "relaterade_system": artikel.get("relaterade_system", []), "proveniens": { "skapad": datetime.now(timezone.utc).strftime("%Y-%m-%d"), "uppdaterad": datetime.now(timezone.utc).strftime("%Y-%m-%d"), "verifieringsniva": "obekraftad", "konfidens": 0.7, "kallor": artikel.get("kallor", []) } }, "motivering": f"Control intelligence analysis of {handelse}" } log(f"✓ Analysis ready: {kandidat_id}") return kandidat def kor_agent(agent_id, cap, ko_path, output_dir): """Huvudloop för agenten.""" log(f"Control Intelligence Agent {agent_id} starting (cap={cap})") if not ANTHROPIC_API_KEY: log("ERROR: ANTHROPIC_API_KEY missing") return 0 ko = ladda_ko(ko_path) kandidater = [] producerade = 0 while producerade < cap: post = hitta_nasta(ko, agent_id) if not post: log("No more open posts in queue.") break las_las(post, agent_id) spara_ko(ko_path, ko) log(f"Locked {post['ko_id']}: {post['handelse'][:50]}...") try: kandidat = producera_kandidatpost(post, agent_id) kandidater.append(kandidat) producerade += 1 markera_klar(post) log(f"Done {post['ko_id']}") except Exception as e: log(f"ERROR {post['ko_id']}: {e}") import traceback traceback.print_exc() post["status"] = "oppen" if "las_tid" in post: del post["las_tid"] spara_ko(ko_path, ko) if kandidater: fil = skapa_kandidatfil(kandidater, agent_id, output_dir) log(f"Saved {len(kandidater)} analyses to {fil}") log(f"Agent {agent_id} finished. Produced: {producerade}/{cap}") return producerade def main(): parser = argparse.ArgumentParser(description="Landvex Control Intelligence Agent") parser.add_argument("--agent-id", required=True, help="Unique agent ID") parser.add_argument("--cap", type=int, default=5, help="Max posts per run") parser.add_argument("--ko", type=Path, default=DEFAULT_KO, help="Queue path") parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR, help="Output dir") args = parser.parse_args() kor_agent( agent_id=args.agent_id, cap=args.cap, ko_path=args.ko, output_dir=args.output_dir ) if __name__ == "__main__": main()