Files
boc/projects/landvex/20-automation/kontrollintelligens_agent.py
T
Bernt 6de2455917 v1.2.0: Add Global Markets footer, translated to 9 languages
- Added GLOBAL_MARKETS_TITLE to all translation files
- Updated footer with 12 markets (4 active + 8 upcoming)
- Translated market section to: zh-cn, zh-tw, ja, ko, th, vi, id, ms, hi
- Built and deployed to production
- CloudFront invalidation: I3RTMXVFDJXWLG3SYX208OP1CC
2026-07-08 19:56:03 +00:00

264 lines
8.0 KiB
Python

#!/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()