6de2455917
- 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
264 lines
8.0 KiB
Python
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()
|