#!/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 'Gamefound' else: return 'Kickstarter' def cover_img(p): if p["image"]: # Use small thumbnail - Gamefound images might be too large, use inline CSS resize return f'{p[' 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'
{img_html}
{name_html}
' 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 = '{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 += '\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 += '
#SpielPlattformFunding%BackerTage
\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 += '\n' for p in es_sorted: extra = "row-highlight" if p["pct"] >= 100 else "" html += table_row(p, extra_class=extra) + '\n' html += '
SpielPlattformFunding%BackerTage
\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 += '\n' for p in sorted(geheimtipps, key=lambda x: x["pct"], reverse=True)[:8]: html += table_row(p, extra_class="row-highlight") + '\n' html += '
SpielPlattformFunding%BackerTage
\n\n' # === ALL CAMPAIGNS === html += f'

Alle {len(data)} Kampagnen im Überblick

\n' html += '\n' html += '\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 += '
#SpielPlattformFunding%BackerTage
\n\n' # === SOURCES === html += f"""Quellen
  1. Gamefound — Aktive Crowdfunding-Projekte (Stand: {today_de})
  2. 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")