#!/usr/bin/env python3
"""Generate the crowdfunding article HTML from merged data."""
import json, os, sys
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
kw = now.isocalendar()[1]
today_de = now.strftime("%d. %B %Y")
# German month names
months_de = {"January":"Januar","February":"Februar","March":"März","April":"April","May":"Mai","June":"Juni",
"July":"Juli","August":"August","September":"September","October":"Oktober","November":"November","December":"Dezember"}
for en, de in months_de.items():
today_de = today_de.replace(en, de)
with open("/home/hermes/workspace/merged_data.json") as f:
data = json.load(f)
# Sort by USD funding
data.sort(key=lambda x: x["funding_usd"], reverse=True)
top10 = data[:10]
endspurte = [p for p in data if p["days_left"] <= 3 and p["funding_usd"] >= 500]
# Geheimtipps: well-funded smaller campaigns
geheimtipps = [p for p in data if p["funding_usd"] >= 1000 and p["pct"] >= 100
and p["backers"] >= 30 and p["funding_usd"] < 50000
and p not in top10]
def fmt_funds(p):
if p["currency"] == "EUR":
return f'{p["funds"]:,.0f} €'.replace(',', '.')
else:
return f'{p["funds"]:,.0f} $'.replace(',', '.')
def fmt_goal(p):
if p["currency"] == "EUR":
return f'{p["goal"]:,.0f} €'.replace(',', '.')
else:
return f'{p["goal"]:,.0f} $'.replace(',', '.')
def fmt_pct(p):
return f'{p["pct"]} %' if p["pct"] == int(p["pct"]) else f'{p["pct"]:.0f} %'
def days_class(d):
if d <= 1: return "days-urgent"
elif d <= 3: return "days-warning"
return "days-ok"
def rank_class(n):
if n == 1: return "rank-1"
elif n == 2: return "rank-2"
elif n == 3: return "rank-3"
return ""
def platform_cell(p):
if p["platform"] == "Gamefound":
return '
 | '
else:
return ' | '
def cover_img(p):
if p["image"]:
# Use small thumbnail - Gamefound images might be too large, use inline CSS resize
return f'
'
return ""
def game_cell(p, rank=None):
img_html = cover_img(p)
url = p["url"]
name = p['name']
if rank and rank <= 3:
name_html = f'{name}'
elif rank and rank <= 10:
name_html = f'{name}'
else:
name_html = f'{name}'
if img_html:
return f' | '
else:
return f'{name_html} | '
def table_row(p, rank=None, extra_class=""):
r = rank or 0
rc = rank_class(r) if r <= 3 else ""
badge = f'{r}' if r else ""
cls = extra_class
dc = days_class(p["days_left"])
# Determine row class
if p["funding_usd"] >= 100000:
if "row-mega" not in cls:
cls = (cls + " row-mega").strip()
row = '\n'
if rank:
row += f' | {badge} | \n'
row += f' {game_cell(p, rank=r)}\n'
row += f' {platform_cell(p)}\n'
row += f' {fmt_funds(p)} | \n'
row += f' {fmt_pct(p)} | \n'
row += f' {p["backers"]:,} | \n'.replace(',', '.')
row += f' {p["days_left"]} | \n'
row += '
'
return row
# === BUILD HTML ===
html = f"""
Crowdfunding-Übersicht KW {kw} – {len(data)} Brettspiel-Kampagnen im Check
{today_de}
KW {kw}: {len(top10)} dicke Fische und {len(endspurte)} Endspurte — wir checken alle {len(data)} aktiven Brettspiel-Kampagnen auf Kickstarter und Gamefound.
Die KW {kw} bringt satte {len(data)} aktive Brettspiel-Kampagnen auf Kickstarter und Gamefound. Darunter echte Schwergewichte wie {top10[0]['name']} mit über {fmt_funds(top10[0])} — aber auch {sum(1 for p in data if p['days_left'] <= 1)} Kampagnen, die heute oder morgen auslaufen. Wir haben alle für euch im Blick.
"""
# === TOP 10 TABLE ===
html += 'Die Großen: Top 10 nach Funding
\n'
html += '\n'
html += '| # | Spiel | Plattform | Funding | % | Backer | Tage |
\n'
for i, p in enumerate(top10):
extra = ""
# Mark mega campaigns
if p["funding_usd"] >= 250000:
extra = "row-mega"
elif p["funding_usd"] >= 100000:
extra = "row-highlight"
html += table_row(p, rank=i+1, extra_class=extra) + '\n'
html += '
\n\n'
# === ENDSPURTE ===
if endspurte:
es_sorted = sorted(endspurte, key=lambda x: x["days_left"])
html += 'Endspurte: Diese Kampagnen laufen bald aus
\n'
html += '\n'
html += '| Spiel | Plattform | Funding | % | Backer | Tage |
\n'
for p in es_sorted:
extra = "row-highlight" if p["pct"] >= 100 else ""
html += table_row(p, extra_class=extra) + '\n'
html += '
\n\n'
# === GEHEIMTIPPS ===
if geheimtipps:
html += 'Kleine Verlage, große Kampagnen
\n'
html += 'Diese kleineren Projekte haben ihr Ziel bereits erreicht und verdienen einen zweiten Blick:
\n'
html += '\n'
html += '| Spiel | Plattform | Funding | % | Backer | Tage |
\n'
for p in sorted(geheimtipps, key=lambda x: x["pct"], reverse=True)[:8]:
html += table_row(p, extra_class="row-highlight") + '\n'
html += '
\n\n'
# === ALL CAMPAIGNS ===
html += f'Alle {len(data)} Kampagnen im Überblick
\n'
html += '\n'
html += '| # | Spiel | Plattform | Funding | % | Backer | Tage |
\n'
for i, p in enumerate(data):
extra = ""
if i < 10:
extra = "row-highlight"
html += table_row(p, rank=i+1, extra_class=extra) + '\n'
html += '
\n\n'
# === SOURCES ===
html += f"""Quellen
- Gamefound — Aktive Crowdfunding-Projekte (Stand: {today_de})
- Kickstarter — Tabletop Games Kategorie (Stand: {today_de})
Recherchestand: {today_de}
"""
# Write output
out_path = "/home/hermes/workspace/crowdfunding_kw28_article.html"
with open(out_path, "w") as f:
f.write(html)
# Stats
print(f"Article written: {out_path}")
print(f"Projects: {len(data)} (GF: {sum(1 for p in data if p['platform']=='Gamefound')}, KS: {sum(1 for p in data if p['platform']=='Kickstarter')})")
print(f"Top 10 range: {fmt_funds(top10[-1])} — {fmt_funds(top10[0])}")
print(f"Endspurte: {len(endspurte)}")
print(f"Geheimtipps: {len(geheimtipps)}")
print("DONE")