Files
boc/projects/landvex/20-automation/producera_artikel.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

246 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""
Landvex Artikelproduktion med RIKTIGT LLM-anrop
Använder Anthropic Claude API
"""
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
import anthropic
# API-konfiguration
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_prompt(sokvag):
"""Ladda systemprompt från fil."""
if not Path(sokvag).exists():
return "Du är en expert på infrastruktur och teknisk dokumentation."
with open(sokvag, "r", encoding="utf-8") as f:
return f.read()
def generera_artikel(amne, doman, system_prompt, webb_data=None):
"""
Generera en komplett artikel med Anthropic Claude.
Args:
amne: Objektets namn (t.ex. "Avstängningsventil")
doman: Domänkod (t.ex. "VAT")
system_prompt: Systemprompt från fil
webb_data: Resultat från webbsökning (valfritt)
Returns:
Dict med komplett artikeldata
"""
if not ANTHROPIC_API_KEY:
raise ValueError("ANTHROPIC_API_KEY måste sättas")
client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
# Bygg användarprompt med webbdata om tillgängligt
webb_kontext = ""
if webb_data:
webb_kontext = "\n\n## Webbresearch:\n"
for i, result in enumerate(webb_data[:3], 1):
webb_kontext += f"\n### Källa {i}: {result.get('title', 'Okänd')}\n"
webb_kontext += f"URL: {result.get('url', 'N/A')}\n"
content = result.get('content', result.get('snippet', ''))
webb_kontext += f"Innehåll: {content[:800]}...\n"
user_prompt = f"""Generera en komplett artikel om följande infrastrukturobjekt:
Objekt: {amne}
Domän: {doman}
{webb_kontext}
Returnera SVARET SOM JSON i följande format:
{{
"slug": "url-vanlig-slug-pa-engelska",
"namn": {{
"sv": "Svenskt namn",
"en": "Engelskt namn"
}},
"beskrivning": "Detaljerad beskrivning av objektet, dess funktion och användningsområden (minst 300 ord på svenska)",
"teknik": {{
"material": ["Material som används"],
"dimensioner": "Typiska dimensioner",
"standarder": ["Tillämpliga standarder och normer"],
"livslangd": "Förväntad livslängd"
}},
"anvandningsomraden": ["Lista över användningsområden"],
"relaterade_objekt": ["Lista över relaterade infrastrukturobjekt"],
"bilder": [
{{
"beskrivning": "Beskrivning av vad bilden ska visa",
"alt_text": "Alt-text för tillgänglighet"
}}
],
"kallor": [
{{
"titel": "Källans titel",
"url": "https://example.com",
"typ": "webbsida|standard|dokument"
}}
]
}}
VIKTIGT:
1. Returnera ENDAST JSON, ingen annan text före eller efter.
2. Beskrivningen ska vara på SVENSKA och minst 300 ord.
3. Inkludera tekniska detaljer som är specifika för objektet.
4. Använd domän-expertis: för VAT (vatten) inkludera tryckklasser, för TRP (trafik) inkludera belastningsklasser, etc."""
log(f"Anropar Claude API för: {amne}")
# Anropa LLM
response = client.messages.create(
model=DEFAULT_MODEL,
max_tokens=4000,
system=system_prompt,
messages=[
{"role": "user", "content": user_prompt}
]
)
# Parsa JSON-svar
try:
# Hitta text content (skip thinking blocks)
content = None
for block in response.content:
if hasattr(block, 'text'):
content = block.text
break
if not content:
raise ValueError("Ingen text hittades i svaret")
# Extrahera JSON om det är omgivet av markdown
if "```json" in content:
content = content.split("```json")[1].split("```")[0].strip()
elif "```" in content:
content = content.split("```")[1].split("```")[0].strip()
result = json.loads(content)
log(f"✓ Artikel genererad: {amne}")
return result
except (json.JSONDecodeError, ValueError) as e:
log(f"✗ Kunde inte parsa LLM-svar: {e}")
if content:
log(f"Rått svar: {content[:500]}...")
raise
def producera_kandidatpost(post, agent_id, system_prompt, webb_data=None):
"""
Produktion av komplett kandidatpost med LLM.
"""
ko_id = post["ko_id"]
amne = post["mal"]
doman = post["doman"]
log(f"Produktion startar: {ko_id} - {amne}")
# Generera artikel med LLM
artikel = generera_artikel(amne, doman, system_prompt, webb_data)
# Bygg kandidatpost
kandidat_id = f"K-{datetime.now(timezone.utc).strftime('%Y%m%d')}-{agent_id}-{ko_id}"
kandidat = {
"kandidat_id": kandidat_id,
"atgard": "ny",
"serie": post.get("serie", {"typ": "klass", "tier": 2}),
"paket_ref": [],
"patch": {},
"post": {
"posttyp": "objektklass" if post["serie"]["typ"] == "klass" else "produktmodell",
"slug": artikel.get("slug", amne.lower().replace(" ", "-")),
"namn": artikel.get("namn", {"sv": amne, "en": amne}),
"doman": doman,
"identitet": {
"status": "aktiv",
"beskrivning": artikel.get("beskrivning", f"{amne} — infrastrukturkomponent")
},
"teknik": artikel.get("teknik", {}),
"geografi": {
"marknader": ["US", "EU", "UK", "CA", "AU"]
},
"standarder": [],
"ai": {
"ocr": {
"falt": []
}
},
"problem": [],
"relationer": [
{"typ": "relaterad_till", "mal": rel}
for rel in artikel.get("relaterade_objekt", [])
],
"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", [])
}
},
"verifieringsniva_forslag": "obekraftad",
"motivering": f"LLM-genererad post för {amne} med tekniska detaljer och källhänvisningar"
}
# Lägg till tier eller parent
if post["serie"]["typ"] == "klass":
kandidat["post"]["tier"] = post["serie"]["tier"]
else:
kandidat["post"]["parent"] = post.get("parent", "")
# Lägg till standarder från artikel
if "standarder" in artikel.get("teknik", {}):
for std in artikel["teknik"]["standarder"]:
kandidat["post"]["standarder"].append({
"beteckning": std,
"organ": "Standardiseringsorgan",
"roll": "referens"
})
# Lägg till ledtrådar som standarder om tillgängliga
if "ledtrad_standard" in post:
kandidat["post"]["standarder"].append({
"beteckning": post["ledtrad_standard"],
"organ": "Standardiseringsorgan",
"roll": "referens"
})
log(f"✓ Kandidatpost klar: {kandidat_id}")
return kandidat
if __name__ == "__main__":
# Test
if len(sys.argv) < 3:
print("Användning: python3 producera_artikel.py '<amne>' '<doman>'")
sys.exit(1)
amne = sys.argv[1]
doman = sys.argv[2]
system_prompt = ladda_prompt("lvx-artikelautomation-prompt.md")
try:
result = generera_artikel(amne, doman, system_prompt)
print(json.dumps(result, ensure_ascii=False, indent=2))
except Exception as e:
log(f"Fel: {e}")
sys.exit(1)