#!/usr/bin/env python3 """Merge GF+KS data, build HTML article""" import json, datetime, re # Load data with open('/home/hermes/workspace/gf_filtered.json') as f: gf = json.load(f) with open('/home/hermes/workspace/ks_data.json') as f: ks = json.load(f) # Normalize: all to EUR (1 USD = 0.93 EUR) USD_TO_EUR = 0.93 now = datetime.datetime.now(datetime.timezone.utc) # Process Gamefound gf_entries = [] for p in gf: funding = p['fundsGathered'] goal = p['campaignGoal'] currency = p.get('currencyShortName', 'EUR') if currency == 'USD': funding = funding * USD_TO_EUR goal = goal * USD_TO_EUR elif currency == 'PLN': funding = funding * 0.23 # ~1 PLN = 0.23 EUR goal = goal * 0.23 end_str = p.get('campaignEndDate', '') days_left = -1 if end_str: end = datetime.datetime.fromisoformat(end_str.replace('Z', '+00:00')) days_left = max(0, (end - now).days) pct = round(funding / max(goal, 1) * 100, 1) # Get image URL — use smaller version img = p.get('projectImageUrl', '') gf_entries.append({ 'name': p['projectName'], 'url': p['projectHomeUrl'], 'img': img, 'funding_eur': round(funding, 2), 'goal_eur': round(goal, 2), 'pct': pct, 'backers': p['backerCount'], 'days_left': days_left, 'source': 'gamefound', 'currency_orig': currency, 'funding_display': f"{funding:,.0f} {currency}", }) # Process Kickstarter ks_entries = [] ks_skip = ['dice', 'terrain', 'miniature', 'STL', 'token', 'map pack', 'battle map', '3d print', 'accessory', 'D&D', 'DnD', 'RPG supplement', 'dice bag', 'dice tray', 'playmat', 'character sheet', 'spellbook', 'rpg', '5e', '5th edition', "player's handbook", 'handbook', 'props', 'mapcase', 'touchscreen', '15mm', '28mm', '32mm', 'resin', 'stl', 'soundtrack', 'soundscapes', 'music album'] for p in ks: name_lower = p['name'].lower() skip = False for kw in ks_skip: if kw in name_lower: skip = True break if skip: continue funding = p['pledged'] goal = p['goal'] currency = p['currency'] if currency == 'USD': funding_eur = funding * USD_TO_EUR goal_eur = goal * USD_TO_EUR else: funding_eur = funding goal_eur = goal end_raw = p.get('deadline', '') days_left = -1 if end_raw: if isinstance(end_raw, (int, float)): end = datetime.datetime.fromtimestamp(end_raw, tz=datetime.timezone.utc) else: end = datetime.datetime.fromisoformat(str(end_raw).replace('Z', '+00:00')) days_left = max(0, (end - now).days) pct = round(funding / max(goal, 1) * 100, 1) ks_entries.append({ 'name': p['name'].replace('&', '&'), 'url': p['url'], 'img': p.get('photo', ''), 'funding_eur': round(funding_eur, 2), 'goal_eur': round(goal_eur, 2), 'pct': pct, 'backers': p['backers'], 'days_left': days_left, 'source': 'kickstarter', 'currency_orig': currency, 'funding_display': f"${funding:,.0f}", }) print(f"Gamefound: {len(gf_entries)}, Kickstarter: {len(ks_entries)}") # Merge and sort by funding_eur all = gf_entries + ks_entries all.sort(key=lambda x: x['funding_eur'], reverse=True) print(f"Total: {len(all)}") # Quick stats total_eur = sum(e['funding_eur'] for e in all) total_backers = sum(e['backers'] for e in all) gf_count = sum(1 for e in all if e['source'] == 'gamefound') ks_count = sum(1 for e in all if e['source'] == 'kickstarter') print(f"Gesamtfunding: {total_eur:,.0f} EUR, {total_backers:,} Backers, {gf_count} GF + {ks_count} KS") # Save merged with open('/home/hermes/workspace/merged.json', 'w') as f: json.dump(all, f, indent=2, ensure_ascii=False) # Now build the HTML KW = 27 # Current calendar week # Top 10 top10 = all[:10] def rank_class(i): if i == 0: return 'rank-1' elif i == 1: return 'rank-2' elif i == 2: return 'rank-3' return '' def row_class(i): if i == 0: return 'row-mega' elif i < 10: return 'row-highlight' return '' def days_class(d): if d <= 1: return 'days-urgent' elif d <= 3: return 'days-warning' return 'days-ok' def source_icon(source): if source == 'gamefound': return 'Gamefound' else: return 'Kickstarter' def funding_display(e): return e['funding_display'] def build_table_row(e, rank=None, show_rank=True): rc = row_class(rank-1) if rank and rank <= 10 else '' rk = f'{rank}' if show_rank else '' img_tag = f'{e[' if e['img'] else '' name_cell = f'
{img_tag}
{e["name"]}
' days = f'{e["days_left"]}' if not show_rank: return f'{name_cell}{source_icon(e["source"])}{e["funding_display"]}{e["pct"]} %{e["backers"]}{days}' return f'{rk}{name_cell}{source_icon(e["source"])}{e["funding_display"]}{e["pct"]} %{e["backers"]}{days}' # Endspurte: days_left <= 3, sorted by days_left endspurte = sorted([e for e in all if 1 <= e['days_left'] <= 3], key=lambda x: x['days_left']) # Geheimtipps: under 500 backers, over 200% funded, not in top 20 geheimtipps = sorted([e for e in all if e['backers'] < 500 and e['pct'] > 200 and e not in all[:20]], key=lambda x: x['pct'], reverse=True)[:5] # Now build HTML MONTHS_DE = {7: 'Juli', 8: 'August'} today = datetime.date.today() date_str = f"{today.day}. {MONTHS_DE[today.month]} {today.year}" # Determine KW dates kw_start = today - datetime.timedelta(days=today.weekday()) kw_end = kw_start + datetime.timedelta(days=6) html = f''' Crowdfunding KW {KW}: {len(all)} Brettspiel-Kampagnen im Check

{date_str}

{len(all)} aktive Brettspiel-Kampagnen auf Kickstarter und Gamefound — alle Funding-Zahlen, Endspurte und Geheimtipps der KW {KW}.

Crowdfunding KW {KW}: {len(all)} Brettspiel-Kampagnen im Check

Die Kalenderwoche {KW} bringt {len(all)} aktive Brettspiel-Crowdfunding-Kampagnen auf Kickstarter und Gamefound. Das Gesamtfunding liegt bei rund {total_eur/1000000:.1f} Millionen Euro, verteilt auf {total_backers:,} Unterstützer. {gf_count} Kampagnen laufen auf Gamefound, {ks_count} auf Kickstarter. Wir haben alle Zahlen, Endspurte und Geheimtipps für euch zusammengestellt.


Die Großen: Top 10 nach Funding

''' # Top 10 rows for i, e in enumerate(top10): html += build_table_row(e, rank=i+1) + '\n' html += '''
#SpielPlattformFunding%BackerTage
''' # Endspurte if endspurte: html += '''

Endspurte: Diese Kampagnen enden in Kürze

''' for e in endspurte: html += build_table_row(e, show_rank=False) + '\n' html += '
SpielPlattformFunding%BackerTage
\n\n' # Geheimtipps if geheimtipps: html += '''

Geheimtipps: Kleine Kampagnen mit großem Potenzial

''' for e in geheimtipps: html += build_table_row(e, show_rank=False) + '\n' html += '
SpielPlattformFunding%BackerTage
\n\n' # Complete list html += f'''

Alle {len(all)} Kampagnen im Überblick

''' for i, e in enumerate(all): html += build_table_row(e, rank=i+1) + '\n' html += '''
#SpielPlattformFunding%BackerTage
Quellen
  1. Gamefound — Aktive Crowdfunding-Kampagnen (KW ''' + str(KW) + ''')
  2. Kickstarter — Tabletop Games Kategorie, aktive Projekte (KW ''' + str(KW) + ''')
  3. EverythingBoardGames — Crowdfunding-Tracker für Brettspiele

Recherchestand: ''' + date_str + '''

''' # Write HTML with open('/home/hermes/workspace/crowdfunding_kw27.html', 'w') as f: f.write(html) print(f"\nHTML written: {len(html)} chars") print(f"Top 10: {[e['name'] for e in top10]}") print(f"Endspurte: {len(endspurte)}") print(f"Geheimtipps: {len(geheimtipps)}")