feat: Review-Builder — Word-Datei + Bilder hochladen, LLM-strukturiert HTML bauen

- GET /review-builder: Upload-Formular (Word .docx + Bilder Drag/Drop + Metadaten)
- POST /review-builder/build: Word parsen → LLM (DeepSeek Flash) strukturiert
  Teaser/Spielerklärung/Fazit → HTML bauen → im Workspace speichern
- Bild-Resize: 1920x1080 @ 72dpi (PIL), Bilderverzeichnis/01/{buchstabe}/{spiel}/
- review_config.json: 25+ Autoren, 5 Disclosures, 5 Rubriken, Komplexitätsstufen
- Live-Publish via Joomla-API optional (Button: Vorschau / Veröffentlichen)
- Link im Dashboard: 📝 Review-Builder
This commit is contained in:
Hermes Agent
2026-07-13 16:36:00 +02:00
parent 1a681e00b2
commit d02c9906a9
77 changed files with 14176 additions and 495 deletions
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""Merge GF + KS data, compute rankings, generate HTML article."""
import json, os, sys
from datetime import datetime, timezone
# Load data
with open("/home/hermes/workspace/gf_filtered.json") as f:
gf_data = json.load(f)
with open("/home/hermes/workspace/ks_filtered.json") as f:
ks_data = json.load(f)
print(f"GF: {len(gf_data)}, KS: {len(ks_data)}")
# Convert both to common format
merged = []
now = datetime.now(timezone.utc)
# Process Gamefound
for p in gf_data:
name = p.get("projectName", "")
funds = float(p.get("fundsGathered", 0) or 0)
goal = float(p.get("campaignGoal", 0) or 0)
backers = int(p.get("backerCount", 0) or 0)
end_str = p.get("campaignEndDate", "")
if end_str:
try:
end_dt = datetime.fromisoformat(end_str.replace('Z', '+00:00'))
except:
end_dt = None
else:
end_dt = None
days_left = (end_dt - now).days + 1 if end_dt else 999
if days_left < 0:
days_left = 0
pct = round(funds / goal * 100, 1) if goal > 0 else 0
# EUR to USD for ranking consistency (1 EUR ≈ 1.08 USD)
usd_equiv = funds * 1.08
merged.append({
"name": name,
"url": p.get("projectHomeUrl", ""),
"platform": "Gamefound",
"funds": funds,
"goal": goal,
"funding_usd": usd_equiv,
"backers": backers,
"days_left": days_left,
"pct": pct,
"end_date": end_str,
"image": p.get("projectImageUrl", ""),
"blurb": p.get("shortDescription", ""),
"creator": p.get("creatorName", ""),
"currency": p.get("currencyShortName", "EUR"),
"state": "live",
})
# Process Kickstarter
for p in ks_data:
funds = float(p.get("pledged", 0) or 0)
goal = float(p.get("goal", 0) or 0)
currency = p.get("currency", "USD")
backers = int(p.get("backers_count", 0) or 0)
# KS is in USD by default
if currency == "USD":
usd_equiv = funds
elif currency == "EUR":
usd_equiv = funds * 1.08
else:
usd_equiv = funds * 1.0 # approximate
# Days left
deadline_str = p.get("deadline")
if deadline_str:
try:
end_dt = datetime.fromisoformat(deadline_str.replace('Z', '+00:00'))
except:
end_dt = None
else:
# Try timestamp
ts = p.get("deadline_ts")
if isinstance(ts, int):
end_dt = datetime.fromtimestamp(ts, tz=timezone.utc)
else:
end_dt = None
days_left = (end_dt - now).days + 1 if end_dt else 999
if days_left < 0:
days_left = 0
pct = round(funds / goal * 100, 1) if goal > 0 else 0
merged.append({
"name": p.get("name", ""),
"url": p.get("url", ""),
"platform": "Kickstarter",
"funds": funds,
"goal": goal,
"funding_usd": usd_equiv,
"backers": backers,
"days_left": days_left,
"pct": pct,
"end_date": deadline_str,
"image": p.get("photo", ""),
"blurb": p.get("blurb", ""),
"creator": p.get("creator", ""),
"currency": currency,
"state": p.get("state", "live"),
})
# Sort by funding (USD equivalent)
merged.sort(key=lambda x: x["funding_usd"], reverse=True)
# Save merged data
with open("/home/hermes/workspace/merged_data.json", "w") as f:
json.dump(merged, f, indent=2, ensure_ascii=False)
print(f"\nTotal merged: {len(merged)}")
print(f"\nTop 10 by funding:")
for i, p in enumerate(merged[:10]):
plat = p['platform'][:2]
funds_str = f"${p['funding_usd']:,.0f}" if p['currency'] == 'USD' else f"{p['funds']:,.0f}"
print(f" {i+1}. [{plat}] {p['name']}: {funds_str} ({p['pct']}%) · {p['backers']} backers · {p['days_left']}d left")
# Save top 10, endspurts (≤3d), etc.
print(f"\nEndspurte (≤3d): {sum(1 for p in merged if p['days_left'] <= 3)}")
print(f"Geheimtipps (<$10k, >100% funded, >50 backers): {sum(1 for p in merged if p['funding_usd'] < 10000 and p['pct'] > 100 and p['backers'] > 50)}")
# Get KW number
kw = now.isocalendar()[1]
print(f"\nKW: {kw}")
print(f"\nDONE")