diff --git a/padelnomics/src/padelnomics/i18n.py b/padelnomics/src/padelnomics/i18n.py
index cf10908..062d854 100644
--- a/padelnomics/src/padelnomics/i18n.py
+++ b/padelnomics/src/padelnomics/i18n.py
@@ -533,6 +533,68 @@ _TRANSLATIONS: dict[str, dict[str, str]] = {
"sl_hold_years": "Holding Period",
"sl_exit_multiple": "Exit EBITDA Multiple",
"sl_annual_rev_growth": "Annual Revenue Growth",
+ # ── Tooltip tip texts ────────────────────────────────────────────────
+ "wiz_summary_label": "Live Summary",
+ "tip_permits_compliance": "Building permits, noise studies, change-of-use, fire safety, and regulatory compliance. Adjusts automatically when you pick a country \u2014 feel free to override.",
+ "tip_dbl_courts": "Standard padel court for 4 players. Most common format with highest recreational demand.",
+ "tip_sgl_courts": "Narrow court for 2 players. Popular for coaching, training, and competitive play.",
+ "tip_sqm_dbl_hall": "Total hall space needed per double court. Includes court (200\u202fm\u00b2), safety zones, circulation, and minimum clearances. Standard: 300\u2013350\u202fm\u00b2.",
+ "tip_sqm_sgl_hall": "Total hall space needed per single court. Includes court (120\u202fm\u00b2), safety zones, and access. Standard: 200\u2013250\u202fm\u00b2.",
+ "tip_sqm_dbl_outdoor": "Outdoor land area per double court. Includes court area, drainage slopes, access paths, and buffer zones. Standard: 280\u2013320\u202fm\u00b2.",
+ "tip_sqm_sgl_outdoor": "Outdoor land area per single court. Includes court, surrounding space, and access paths. Standard: 180\u2013220\u202fm\u00b2.",
+ "tip_rate_peak": "Price per court per hour during peak times (evenings 17:00\u201322:00 and weekends). Highest demand period.",
+ "tip_rate_offpeak": "Price per court per hour during off-peak (weekday mornings/afternoons). Typically 30\u201340\u00a0% lower than peak.",
+ "tip_rate_single": "Hourly rate for single-width courts. Usually lower than doubles since fewer players share the cost.",
+ "tip_peak_pct": "Percentage of total booked hours at peak rate. Higher means more revenue but harder to fill off-peak slots.",
+ "tip_booking_fee": "Commission taken by booking platforms like Playtomic or Matchi. Typically 5\u201315\u00a0% of court revenue.",
+ "tip_util_target": "Percentage of available court-hours that are actually booked. 35\u201345\u00a0% is realistic for new venues, 50\u00a0%+ is strong.",
+ "tip_hours_per_day": "Total operating hours per day. Typical padel venues run 7:00\u201323:00 (16\u202fh). Some extend to 6:00\u201324:00.",
+ "tip_days_indoor": "Average operating days per month for indoor venue. ~29 accounts for holidays and maintenance closures.",
+ "tip_days_outdoor": "Average playable days per month outdoors. Reduced by rain, extreme heat, or cold weather.",
+ "tip_membership_rev": "Monthly membership/subscription income per court. From loyalty programs, monthly plans, or club memberships.",
+ "tip_fb_rev": "Food & Beverage revenue per court per month. Income from bar, caf\u00e9, restaurant, or vending machines at the venue.",
+ "tip_coaching_rev": "Revenue from coaching sessions, clinics, tournaments, and events allocated per court per month.",
+ "tip_retail_rev": "Revenue from pro shop sales: grip tape, overgrips, accessories, and branded merchandise per court per month.",
+ "tip_glass_type": "Standard glass: \u20ac25\u201330K per court. Panoramic glass: \u20ac30\u201345K. Panoramic offers full visibility and premium feel.",
+ "tip_court_cost_dbl": "Base price of one double padel court. The glass type multiplier is applied automatically.",
+ "tip_court_cost_sgl": "Base price of one single padel court. Generally 60\u201370\u00a0% of a double court cost.",
+ "tip_hall_cost_sqm": "Construction cost per m\u00b2 for a new hall (Warmhalle). Includes structure, insulation, and cladding. Requires 10\u201312\u202fm clear height.",
+ "tip_foundation_sqm": "Foundation cost per m\u00b2. Depends on soil conditions, load-bearing requirements, and local ground water levels.",
+ "tip_land_price_sqm": "Land purchase price per m\u00b2. Rural: \u20ac20\u201360. Suburban: \u20ac60\u2013150. Urban: \u20ac150\u2013300+. Varies hugely by location.",
+ "tip_hvac": "Heating, ventilation, and air conditioning. Essential for indoor comfort and humidity control. Cost scales with hall volume.",
+ "tip_electrical": "Complete electrical installation: court lighting (LED, 500+ lux), power distribution, panels, and outlets.",
+ "tip_sanitary": "Changing rooms, showers, toilets, and plumbing. Includes fixtures, tiling, waterproofing, and ventilation.",
+ "tip_fire_protection": "Fire detection, sprinkler suppression, emergency exits, and smoke ventilation. Often the biggest surprise cost for large halls.",
+ "tip_planning": "Architectural planning, structural engineering, building permits, zoning applications, and regulatory compliance costs.",
+ "tip_floor_prep": "Floor leveling, sealing, and preparation for court installation in an existing rented building.",
+ "tip_hvac_upgrade": "Upgrading existing HVAC in a rented building to handle sports venue airflow and humidity requirements.",
+ "tip_lighting_upgrade": "Upgrading existing lighting to meet padel requirements: minimum 500 lux, no glare, even distribution across courts.",
+ "tip_fitout": "Interior fit-out for reception, lounge, viewing areas, and common spaces when renting an existing building.",
+ "tip_outdoor_foundation": "Concrete pad per m\u00b2 for outdoor courts. Needs proper drainage, level surface, and frost-resistant construction.",
+ "tip_outdoor_site_work": "Grading, drainage installation, utilities connection, and site preparation for outdoor courts.",
+ "tip_outdoor_lighting": "Floodlight installation per court. LED recommended for energy efficiency. Must meet competition standards if applicable.",
+ "tip_outdoor_fencing": "Perimeter fencing around outdoor court area. Includes wind screens, security gates, and ball containment nets.",
+ "tip_working_capital": "Cash reserve for operating losses during ramp-up phase and seasonal dips. Critical buffer \u2014 underfunding is a common startup failure.",
+ "tip_contingency": "Percentage buffer on total CAPEX for unexpected costs. 10\u201315\u00a0% is standard for construction, 15\u201320\u00a0% for complex projects.",
+ "tip_budget_target": "Set your total budget to see how your planned CAPEX compares. Leave at 0 to hide the budget indicator.",
+ "tip_rent_sqm": "Monthly rent per square meter for indoor hall space. Varies by location, building quality, and lease terms.",
+ "tip_outdoor_rent": "Monthly land rent for outdoor court area. Much cheaper than indoor space but weather-dependent.",
+ "tip_property_tax": "Monthly property tax when owning the building/land. Grundsteuer in Germany, varies by municipality and property value.",
+ "tip_insurance": "Monthly insurance premium covering liability, property damage, business interruption, and equipment.",
+ "tip_electricity": "Monthly electricity cost. Major driver for indoor venues due to court lighting, HVAC, and equipment.",
+ "tip_heating": "Monthly heating cost for indoor venue. Significant in northern European climates during winter months.",
+ "tip_water": "Monthly water cost for showers, toilets, cleaning, and potentially outdoor court irrigation.",
+ "tip_maintenance": "Monthly court and facility maintenance: glass cleaning, surface repair, net replacement, and equipment upkeep.",
+ "tip_cleaning": "Monthly professional cleaning of courts, changing rooms, common areas, and reception.",
+ "tip_marketing": "Monthly spend on marketing, booking platform subscriptions, website, social media, and customer acquisition.",
+ "tip_staff": "Monthly staff costs including wages, social contributions, and benefits. Many venues run lean using automated booking and access systems.",
+ "tip_loan_pct": "Percentage of total CAPEX financed by debt. Banks typically offer 70\u201385\u00a0%. Higher with personal guarantees or subsidies.",
+ "tip_interest_rate": "Annual interest rate on the loan. Depends on creditworthiness, collateral, market conditions, and bank relationship.",
+ "tip_loan_term": "Loan repayment period in years. Longer terms mean lower monthly payments but more total interest paid.",
+ "tip_construction_months": "Months of construction/setup before opening. Costs accrue (loan interest, rent) but no revenue is generated.",
+ "tip_hold_years": "Investment holding period before exit/sale. Typical for PE/investors: 5\u20137 years. Owner-operators may hold indefinitely.",
+ "tip_exit_multiple": "EBITDA multiple used to value the business at exit. Reflects market demand, brand strength, and growth potential. Small business: 4\u20136\u00d7, strong brand: 6\u20138\u00d7.",
+ "tip_annual_rev_growth": "Expected annual revenue growth rate after the initial 12-month ramp-up period. Driven by price increases and utilization gains.",
"btn_save": "Save",
"btn_my_scenarios": "My Scenarios",
"btn_reset": "Reset to Defaults",
@@ -1157,6 +1219,68 @@ _TRANSLATIONS: dict[str, dict[str, str]] = {
"sl_hold_years": "Haltedauer",
"sl_exit_multiple": "Exit-EBITDA-Multiplikator",
"sl_annual_rev_growth": "J\u00e4hrliches Umsatzwachstum",
+ # ── Tooltip tip texts ────────────────────────────────────────────────
+ "wiz_summary_label": "Aktuelle Werte",
+ "tip_permits_compliance": "Baugenehmigungen, L\u00e4rmgutachten, Nutzungs\u00e4nderungen, Brandschutz und beh\u00f6rdliche Auflagen. Wird automatisch angepasst, wenn du ein Land w\u00e4hlst \u2014 kann manuell \u00fcberschrieben werden.",
+ "tip_dbl_courts": "Standard-Padelplatz f\u00fcr 4 Spieler. H\u00e4ufigstes Format mit der h\u00f6chsten Freizeitnachfrage.",
+ "tip_sgl_courts": "Schmaler Platz f\u00fcr 2 Spieler. Beliebt f\u00fcr Coaching, Training und Wettkampf.",
+ "tip_sqm_dbl_hall": "Gesamte Hallenfl\u00e4che pro Doppelplatz. Enth\u00e4lt Spielfeld (200\u202fm\u00b2), Sicherheitszonen, Laufwege und Mindestabst\u00e4nde. Standard: 300\u2013350\u202fm\u00b2.",
+ "tip_sqm_sgl_hall": "Gesamte Hallenfl\u00e4che pro Einzelplatz. Enth\u00e4lt Spielfeld (120\u202fm\u00b2), Sicherheitszonen und Zugang. Standard: 200\u2013250\u202fm\u00b2.",
+ "tip_sqm_dbl_outdoor": "Au\u00dfenfl\u00e4che pro Doppelplatz. Enth\u00e4lt Spielfeld, Entw\u00e4sserungsgef\u00e4lle, Zugangswege und Pufferzonen. Standard: 280\u2013320\u202fm\u00b2.",
+ "tip_sqm_sgl_outdoor": "Au\u00dfenfl\u00e4che pro Einzelplatz. Enth\u00e4lt Spielfeld, Umgebungsfl\u00e4che und Zugangswege. Standard: 180\u2013220\u202fm\u00b2.",
+ "tip_rate_peak": "Preis pro Platz und Stunde w\u00e4hrend Sto\u00dfzeiten (Abends 17\u201322\u202fUhr und Wochenende). H\u00f6chste Nachfragezeit.",
+ "tip_rate_offpeak": "Preis pro Platz und Stunde au\u00dferhalb der Sto\u00dfzeiten (Werktage morgens/nachmittags). Typischerweise 30\u201340\u00a0% niedriger als Peak.",
+ "tip_rate_single": "Stundensatz f\u00fcr Einzelpl\u00e4tze. Meist niedriger als Doppelpl\u00e4tze, da sich weniger Spieler die Kosten teilen.",
+ "tip_peak_pct": "Anteil der gebuchten Stunden zum Spitzentarif. H\u00f6herer Wert bedeutet mehr Umsatz, aber schwieriger zu f\u00fcllende Nebenstunden.",
+ "tip_booking_fee": "Provision von Buchungsplattformen wie Playtomic oder Matchi. Typisch: 5\u201315\u00a0% des Platzumsatzes.",
+ "tip_util_target": "Anteil der verf\u00fcgbaren Platzstunden, der tats\u00e4chlich gebucht wird. 35\u201345\u00a0% sind realistisch f\u00fcr neue Anlagen, 50\u00a0%+ ist stark.",
+ "tip_hours_per_day": "Gesamte Betriebsstunden pro Tag. Typische Padel-Anlagen \u00f6ffnen 7\u201323\u202fUhr (16\u202fh). Manche auch 6\u201324\u202fUhr.",
+ "tip_days_indoor": "Durchschnittliche Betriebstage pro Monat f\u00fcr Indoor-Anlagen. ~29 ber\u00fccksichtigt Feiertage und Wartungsschlie\u00dfungen.",
+ "tip_days_outdoor": "Durchschnittliche bespielbaren Tage pro Monat im Freien. Reduziert durch Regen, Extremhitze oder K\u00e4lte.",
+ "tip_membership_rev": "Monatliche Mitgliedschafts-/Abonnementeinnahmen pro Platz. Aus Treueprogrammen, Monatsp\u00e4ssen oder Clubmitgliedschaften.",
+ "tip_fb_rev": "F&B-Einnahmen pro Platz und Monat. Einnahmen aus Bar, Caf\u00e9, Restaurant oder Automaten in der Anlage.",
+ "tip_coaching_rev": "Einnahmen aus Coaching-Stunden, Kursen, Turnieren und Veranstaltungen, pro Platz und Monat.",
+ "tip_retail_rev": "Einnahmen aus dem Fachhandel: Griffband, Overgrips, Zubeh\u00f6r und Merchandise pro Platz und Monat.",
+ "tip_glass_type": "Standardglas: 25\u201330\u202fT\u20ac pro Platz. Panoramaglas: 30\u201345\u202fT\u20ac. Panoramaglas bietet volle Sicht und Premium-Atmosph\u00e4re.",
+ "tip_court_cost_dbl": "Grundpreis eines Doppel-Padelplatzes. Der Glastyp-Multiplikator wird automatisch angewendet.",
+ "tip_court_cost_sgl": "Grundpreis eines Einzelplatzes. In der Regel 60\u201370\u00a0% der Kosten eines Doppelplatzes.",
+ "tip_hall_cost_sqm": "Baukosten pro m\u00b2 f\u00fcr eine neue Halle (Warmhalle). Enth\u00e4lt Tragwerk, D\u00e4mmung und Verkleidung. Erfordert 10\u201312\u202fm lichte H\u00f6he.",
+ "tip_foundation_sqm": "Fundamentkosten pro m\u00b2. Abh\u00e4ngig von Bodenbedingungen, Tragf\u00e4higkeit und lokalem Grundwasserstand.",
+ "tip_land_price_sqm": "Grundst\u00fcckskaufpreis pro m\u00b2. Land: 20\u201360\u202f\u20ac. Stadtrand: 60\u2013150\u202f\u20ac. Stadtlage: 150\u2013300\u202f\u20ac+. Stark standortabh\u00e4ngig.",
+ "tip_hvac": "Heizung, L\u00fcftung und Klimatisierung. Unverzichtbar f\u00fcr Raumklima und Feuchtigkeitskontrolle. Kosten skalieren mit dem Hallenvolumen.",
+ "tip_electrical": "Komplette Elektroinstallation: Platzbeleuchtung (LED, 500+ Lux), Stromverteilung, Schaltschr\u00e4nke und Steckdosen.",
+ "tip_sanitary": "Umkleidekabinen, Duschen, Toiletten und Sanit\u00e4rinstallation. Enth\u00e4lt Armaturen, Fliesen, Abdichtung und L\u00fcftung.",
+ "tip_fire_protection": "Brandmeldeanlage, Sprinkler, Notausg\u00e4nge und Rauchabzug. Oft der gr\u00f6\u00dfte \u00dcberraschungsposten bei gro\u00dfen Hallen.",
+ "tip_planning": "Architektenplanung, Tragwerksplanung, Baugenehmigungen, Bebauungsplanantrag und beh\u00f6rdliche Auflagen.",
+ "tip_floor_prep": "Bodenausgleich, Abdichtung und Vorbereitung f\u00fcr die Platzinstallation in einem bestehenden Mietobjekt.",
+ "tip_hvac_upgrade": "Ausr\u00fcstung der vorhandenen HLK-Anlage im Mietobjekt f\u00fcr sportgerechten Luftstrom und Feuchtigkeitskontrolle.",
+ "tip_lighting_upgrade": "Ausr\u00fcstung der vorhandenen Beleuchtung auf Padel-Standard: mind. 500 Lux, blendfrei, gleichm\u00e4\u00dfige Ausleuchtung.",
+ "tip_fitout": "Innenausbau f\u00fcr Empfang, Lounge, Zuschauerbereich und Gemeinschaftsfl\u00e4chen im Mietobjekt.",
+ "tip_outdoor_foundation": "Betonplatte pro m\u00b2 f\u00fcr Outdoor-Pl\u00e4tze. Erfordert ordentliche Entw\u00e4sserung, ebenen Untergrund und frostsichere Bauweise.",
+ "tip_outdoor_site_work": "Gel\u00e4ndeausgleich, Entw\u00e4sserungsinstallation, Versorgungsanschl\u00fcsse und Erschlie\u00dfung f\u00fcr Au\u00dfenpl\u00e4tze.",
+ "tip_outdoor_lighting": "Flutlichtinstallation pro Platz. LED empfohlen f\u00fcr Energieeffizienz. Wettkampfnormen einhalten, falls relevant.",
+ "tip_outdoor_fencing": "Einz\u00e4unung der Au\u00dfenplatzanlage. Enth\u00e4lt Windschutz, Sicherheitstore und Ballr\u00fcckhaltevorrichtungen.",
+ "tip_working_capital": "Kassenreserve f\u00fcr Betriebsverluste in der Anlaufphase und bei saisonalen Schwankungen. Kritischer Puffer \u2014 zu geringes Betriebskapital ist ein h\u00e4ufiger Startup-Fehler.",
+ "tip_contingency": "Prozentualer Puffer auf den Gesamt-CAPEX f\u00fcr unvorhergesehene Kosten. 10\u201315\u00a0% sind beim Bau Standard, 15\u201320\u00a0% bei komplexen Projekten.",
+ "tip_budget_target": "Gesamtbudget festlegen, um den geplanten CAPEX zu vergleichen. 0 lassen, um den Budgetindikator auszublenden.",
+ "tip_rent_sqm": "Monatliche Miete pro m\u00b2 f\u00fcr Hallenfl\u00e4che. Abh\u00e4ngig von Lage, Geb\u00e4udequalit\u00e4t und Mietkonditionen.",
+ "tip_outdoor_rent": "Monatliche Grundst\u00fccksmiete f\u00fcr Au\u00dfenplatzfl\u00e4che. Deutlich g\u00fcnstiger als Hallenfl\u00e4che, aber wetterabh\u00e4ngig.",
+ "tip_property_tax": "Monatliche Grundsteuer bei Eigentum an Geb\u00e4ude/Grundst\u00fcck. Variiert je nach Gemeinde und Grundst\u00fcckswert.",
+ "tip_insurance": "Monatlicher Versicherungsbeitrag: Haftpflicht, Sachschaden, Betriebsunterbrechung und Ausr\u00fcstung.",
+ "tip_electricity": "Monatliche Stromkosten. Gr\u00f6\u00dfter Kostentreiber f\u00fcr Indoor-Anlagen durch Platzbeleuchtung, HLK und Ger\u00e4te.",
+ "tip_heating": "Monatliche Heizkosten f\u00fcr Indoor-Anlagen. Relevant in nordeurop\u00e4ischen Klimazonen in den Wintermonaten.",
+ "tip_water": "Monatliche Wasserkosten f\u00fcr Duschen, Toiletten, Reinigung und ggf. Outdoor-Platzbew\u00e4sserung.",
+ "tip_maintenance": "Monatliche Platz- und Anlagenwartung: Glasreinigung, Belagreparatur, Netzaustausch und Ger\u00e4tepflege.",
+ "tip_cleaning": "Monatliche professionelle Reinigung von Pl\u00e4tzen, Umkleiden, Gemeinschaftsfl\u00e4chen und Empfang.",
+ "tip_marketing": "Monatliche Ausgaben f\u00fcr Marketing, Buchungsplattform-Abonnements, Website, Social Media und Kundengewinnung.",
+ "tip_staff": "Monatliche Personalkosten: Geh\u00e4lter, Sozialabgaben und Leistungen. Viele Anlagen fahren schlank mit automatisierten Buchungs- und Zugangssystemen.",
+ "tip_loan_pct": "Anteil des Gesamt-CAPEX, der fremdfinanziert wird. Banken bieten typisch 70\u201385\u00a0%. H\u00f6her mit Bürg\u00fcschaft oder F\u00f6rdermitteln.",
+ "tip_interest_rate": "J\u00e4hrlicher Zinssatz des Darlehens. Abh\u00e4ngig von Bonit\u00e4t, Sicherheiten, Marktlage und Bankbeziehung.",
+ "tip_loan_term": "Kreditlaufzeit in Jahren. L\u00e4ngere Laufzeit bedeutet niedrigere Monatsraten, aber mehr Gesamtzinsen.",
+ "tip_construction_months": "Monate Bau/Einrichtung vor der Er\u00f6ffnung. Kosten laufen bereits auf (Zinsen, Miete), aber noch kein Umsatz.",
+ "tip_hold_years": "Investitionshaltedauer bis zum Exit/Verkauf. Typisch f\u00fcr PE/Investoren: 5\u20137 Jahre. Betreiber-Eigent\u00fcmer k\u00f6nnen unbegrenzt halten.",
+ "tip_exit_multiple": "EBITDA-Multiplikator zur Unternehmensbewertung beim Exit. Spiegelt Marktnachfrage, Markenst\u00e4rke und Wachstumspotenzial wider. Kleines Business: 4\u20136\u00d7, starke Marke: 6\u20138\u00d7.",
+ "tip_annual_rev_growth": "Erwartetes j\u00e4hrliches Umsatzwachstum nach der ersten 12-monatigen Anlaufphase. Getrieben durch Preiserh\u00f6hungen und steigende Auslastung.",
"btn_save": "Speichern",
"btn_my_scenarios": "Meine Szenarien",
"btn_reset": "Zur\u00fccksetzen",
diff --git a/padelnomics/src/padelnomics/planner/routes.py b/padelnomics/src/padelnomics/planner/routes.py
index 0e67fdd..f662430 100644
--- a/padelnomics/src/padelnomics/planner/routes.py
+++ b/padelnomics/src/padelnomics/planner/routes.py
@@ -93,57 +93,170 @@ def augment_d(d: dict, s: dict, lang: str) -> None:
d["irr_ok"] = math.isfinite(d.get("irr", 0))
- # Chart data — embedded as JSON in partials, consumed by Chart.js via JS
+ # Chart data — full Chart.js 4.x config objects, embedded as JSON in partials
+ _PALETTE = [
+ "#1D4ED8", "#16A34A", "#D97706", "#EF4444", "#8B5CF6",
+ "#EC4899", "#06B6D4", "#84CC16", "#F97316", "#475569",
+ "#0EA5E9", "#A78BFA",
+ ]
+ _cap_items = [i for i in d["capexItems"] if i["amount"] > 0]
d["capex_chart"] = {
- "labels": [i["name"] for i in d["capexItems"] if i["amount"] > 0],
- "data": [i["amount"] for i in d["capexItems"] if i["amount"] > 0],
+ "type": "doughnut",
+ "data": {
+ "labels": [i["name"] for i in _cap_items],
+ "datasets": [{
+ "data": [i["amount"] for i in _cap_items],
+ "backgroundColor": [_PALETTE[i % len(_PALETTE)] for i in range(len(_cap_items))],
+ "borderWidth": 0,
+ }],
+ },
+ "options": {
+ "responsive": True,
+ "maintainAspectRatio": False,
+ "cutout": "60%",
+ "plugins": {"legend": {"position": "right", "labels": {"boxWidth": 10, "font": {"size": 10}}}},
+ },
}
ramp_data = d["months"][:24]
d["ramp_chart"] = {
- "months": [f"M{m['m']}" for m in ramp_data],
- "revenue": [round(m["totalRev"]) for m in ramp_data],
- "opex_debt": [round(abs(m["opex"]) + abs(m["loan"])) for m in ramp_data],
- "label_revenue": t["chart_revenue"],
- "label_opex_debt": t["chart_opex_debt"],
+ "type": "line",
+ "data": {
+ "labels": [f"M{m['m']}" for m in ramp_data],
+ "datasets": [
+ {
+ "label": t["chart_revenue"],
+ "data": [round(m["totalRev"]) for m in ramp_data],
+ "borderColor": "#16A34A",
+ "backgroundColor": "rgba(22,163,74,0.08)",
+ "fill": True,
+ "tension": 0.35,
+ "pointRadius": 0,
+ "borderWidth": 2,
+ },
+ {
+ "label": t["chart_opex_debt"],
+ "data": [round(abs(m["opex"]) + abs(m["loan"])) for m in ramp_data],
+ "borderColor": "#EF4444",
+ "backgroundColor": "rgba(239,68,68,0.06)",
+ "fill": True,
+ "tension": 0.35,
+ "pointRadius": 0,
+ "borderWidth": 2,
+ },
+ ],
+ },
+ "options": {
+ "responsive": True,
+ "maintainAspectRatio": False,
+ "plugins": {"legend": {"display": True, "labels": {"boxWidth": 12, "font": {"size": 10}}}},
+ "scales": {"y": {"ticks": {"font": {"size": 10}}}, "x": {"ticks": {"font": {"size": 9}}}},
+ },
}
+ _pl_values = [
+ round(d["courtRevMonth"]),
+ -round(d["feeDeduction"]),
+ round(d["racketRev"] + d["ballMargin"] + d["membershipRev"]
+ + d["fbRev"] + d["coachingRev"] + d["retailRev"]),
+ -round(d["opex"]),
+ -round(d["monthlyPayment"]),
+ ]
d["pl_chart"] = {
- "labels": [
- t["chart_court_rev"], t["chart_fees"], t["chart_ancillary"],
- t["chart_opex"], t["chart_debt"],
- ],
- "values": [
- round(d["courtRevMonth"]),
- -round(d["feeDeduction"]),
- round(d["racketRev"] + d["ballMargin"] + d["membershipRev"]
- + d["fbRev"] + d["coachingRev"] + d["retailRev"]),
- -round(d["opex"]),
- -round(d["monthlyPayment"]),
- ],
+ "type": "bar",
+ "data": {
+ "labels": [t["chart_court_rev"], t["chart_fees"], t["chart_ancillary"], t["chart_opex"], t["chart_debt"]],
+ "datasets": [{
+ "data": _pl_values,
+ "backgroundColor": ["rgba(22,163,74,0.7)" if v >= 0 else "rgba(239,68,68,0.7)" for v in _pl_values],
+ "borderRadius": 4,
+ }],
+ },
+ "options": {
+ "indexAxis": "y",
+ "responsive": True,
+ "maintainAspectRatio": False,
+ "plugins": {"legend": {"display": False}},
+ "scales": {"x": {"ticks": {"font": {"size": 9}}}, "y": {"ticks": {"font": {"size": 10}}}},
+ },
}
+ _cf_values = [round(m["ncf"]) for m in d["months"]]
d["cf_chart"] = {
- "labels": [f"Y{m['yr']}" if m["m"] % 12 == 1 else "" for m in d["months"]],
- "values": [round(m["ncf"]) for m in d["months"]],
- "pos": [m["ncf"] >= 0 for m in d["months"]],
+ "type": "bar",
+ "data": {
+ "labels": [f"Y{m['yr']}" if m["m"] % 12 == 1 else "" for m in d["months"]],
+ "datasets": [{
+ "data": _cf_values,
+ "backgroundColor": ["rgba(22,163,74,0.7)" if v >= 0 else "rgba(239,68,68,0.7)" for v in _cf_values],
+ "borderRadius": 2,
+ }],
+ },
+ "options": {
+ "responsive": True,
+ "maintainAspectRatio": False,
+ "plugins": {"legend": {"display": False}},
+ "scales": {"y": {"ticks": {"font": {"size": 10}}}, "x": {"ticks": {"font": {"size": 9}}}},
+ },
}
d["cum_chart"] = {
- "labels": [f"M{m['m']}" if m["m"] % 6 == 1 else "" for m in d["months"]],
- "values": [round(m["cum"]) for m in d["months"]],
+ "type": "line",
+ "data": {
+ "labels": [f"M{m['m']}" if m["m"] % 6 == 1 else "" for m in d["months"]],
+ "datasets": [{
+ "data": [round(m["cum"]) for m in d["months"]],
+ "borderColor": "#1D4ED8",
+ "backgroundColor": "rgba(29,78,216,0.08)",
+ "fill": True,
+ "tension": 0.3,
+ "pointRadius": 0,
+ "borderWidth": 2,
+ }],
+ },
+ "options": {
+ "responsive": True,
+ "maintainAspectRatio": False,
+ "plugins": {"legend": {"display": False}},
+ "scales": {"y": {"ticks": {"font": {"size": 10}}}, "x": {"ticks": {"font": {"size": 9}}}},
+ },
}
+ _dscr_values = [min(x["dscr"], 10) for x in d["dscr"]]
d["dscr_chart"] = {
- "labels": [f"Y{x['year']}" for x in d["dscr"]],
- "values": [min(x["dscr"], 10) for x in d["dscr"]],
- "pos": [x["dscr"] >= 1.2 for x in d["dscr"]],
+ "type": "bar",
+ "data": {
+ "labels": [f"Y{x['year']}" for x in d["dscr"]],
+ "datasets": [{
+ "data": _dscr_values,
+ "backgroundColor": ["rgba(22,163,74,0.7)" if v >= 1.2 else "rgba(239,68,68,0.7)" for v in _dscr_values],
+ "borderRadius": 4,
+ }],
+ },
+ "options": {
+ "responsive": True,
+ "maintainAspectRatio": False,
+ "plugins": {"legend": {"display": False}},
+ "scales": {"y": {"ticks": {"font": {"size": 10}}, "min": 0}, "x": {"ticks": {"font": {"size": 10}}}},
+ },
}
d["season_chart"] = {
- "labels": [t[f"month_{k}"] for k in month_keys],
- "values": [v * 100 for v in s["season"]],
- "pos": [v > 0 for v in s["season"]],
+ "type": "bar",
+ "data": {
+ "labels": [t[f"month_{k}"] for k in month_keys],
+ "datasets": [{
+ "data": [v * 100 for v in s["season"]],
+ "backgroundColor": "rgba(29,78,216,0.6)",
+ "borderRadius": 3,
+ }],
+ },
+ "options": {
+ "responsive": True,
+ "maintainAspectRatio": False,
+ "plugins": {"legend": {"display": False}},
+ "scales": {"y": {"ticks": {"font": {"size": 10}}, "min": 0}, "x": {"ticks": {"font": {"size": 10}}}},
+ },
}
# Sensitivity tables (pre-computed for returns tab)
diff --git a/padelnomics/src/padelnomics/planner/templates/partials/calculate_response.html b/padelnomics/src/padelnomics/planner/templates/partials/calculate_response.html
index 0028cdc..0d4742e 100644
--- a/padelnomics/src/padelnomics/planner/templates/partials/calculate_response.html
+++ b/padelnomics/src/padelnomics/planner/templates/partials/calculate_response.html
@@ -5,4 +5,4 @@
- {{ slider('courtCostDbl', t.sl_court_cost_dbl, 0, 80000, 1000, s.courtCostDbl, 'Base price of one double padel court. The glass type multiplier is applied automatically.') }}
- {{ slider('courtCostSgl', t.sl_court_cost_sgl, 0, 60000, 1000, s.courtCostSgl, 'Base price of one single padel court. Generally 60–70% of a double court cost.') }}
+ {{ slider('courtCostDbl', t.sl_court_cost_dbl, 0, 80000, 1000, s.courtCostDbl, t.tip_court_cost_dbl) }}
+ {{ slider('courtCostSgl', t.sl_court_cost_sgl, 0, 60000, 1000, s.courtCostSgl, t.tip_court_cost_sgl) }}
- {{ slider('hallCostSqm', t.sl_hall_cost_sqm, 0, 2000, 10, s.hallCostSqm, 'Construction cost per m² for a new hall (Warmhalle). Includes structure, insulation, and cladding. Requires 10–12m clear height.') }}
- {{ slider('foundationSqm', t.sl_foundation_sqm, 0, 400, 5, s.foundationSqm, 'Foundation cost per m². Depends on soil conditions, load-bearing requirements, and local ground water levels.') }}
- {{ slider('landPriceSqm', t.sl_land_price_sqm, 0, 500, 5, s.landPriceSqm, 'Land purchase price per m². Rural: €20–60. Suburban: €60–150. Urban: €150–300+. Varies hugely by location.') }}
- {{ slider('hvac', t.sl_hvac, 0, 500000, 5000, s.hvac, 'Heating, ventilation, and air conditioning. Essential for indoor comfort and humidity control. Cost scales with hall volume.') }}
- {{ slider('electrical', t.sl_electrical, 0, 400000, 5000, s.electrical, 'Complete electrical installation: court lighting (LED, 500+ lux), power distribution, panels, and outlets.') }}
- {{ slider('sanitary', t.sl_sanitary, 0, 400000, 5000, s.sanitary, 'Changing rooms, showers, toilets, and plumbing. Includes fixtures, tiling, waterproofing, and ventilation.') }}
- {{ slider('fireProtection', t.sl_fire, 0, 500000, 5000, s.fireProtection, 'Fire detection, sprinkler suppression, emergency exits, and smoke ventilation. Often the biggest surprise cost for large halls.') }}
- {{ slider('planning', t.sl_planning, 0, 500000, 5000, s.planning, 'Architectural planning, structural engineering, building permits, zoning applications, and regulatory compliance costs.') }}
+ {{ slider('hallCostSqm', t.sl_hall_cost_sqm, 0, 2000, 10, s.hallCostSqm, t.tip_hall_cost_sqm) }}
+ {{ slider('foundationSqm', t.sl_foundation_sqm, 0, 400, 5, s.foundationSqm, t.tip_foundation_sqm) }}
+ {{ slider('landPriceSqm', t.sl_land_price_sqm, 0, 500, 5, s.landPriceSqm, t.tip_land_price_sqm) }}
+ {{ slider('hvac', t.sl_hvac, 0, 500000, 5000, s.hvac, t.tip_hvac) }}
+ {{ slider('electrical', t.sl_electrical, 0, 400000, 5000, s.electrical, t.tip_electrical) }}
+ {{ slider('sanitary', t.sl_sanitary, 0, 400000, 5000, s.sanitary, t.tip_sanitary) }}
+ {{ slider('fireProtection', t.sl_fire, 0, 500000, 5000, s.fireProtection, t.tip_fire_protection) }}
+ {{ slider('planning', t.sl_planning, 0, 500000, 5000, s.planning, t.tip_planning) }}
- {{ slider('floorPrep', t.sl_floor_prep, 0, 100000, 1000, s.floorPrep, 'Floor leveling, sealing, and preparation for court installation in an existing rented building.') }}
- {{ slider('hvacUpgrade', t.sl_hvac_upgrade, 0, 200000, 1000, s.hvacUpgrade, 'Upgrading existing HVAC in a rented building to handle sports venue airflow and humidity requirements.') }}
- {{ slider('lightingUpgrade', t.sl_lighting_upgrade, 0, 100000, 1000, s.lightingUpgrade, 'Upgrading existing lighting to meet padel requirements: minimum 500 lux, no glare, even distribution across courts.') }}
- {{ slider('fitout', t.sl_fitout, 0, 300000, 1000, s.fitout, 'Interior fit-out for reception, lounge, viewing areas, and common spaces when renting an existing building.') }}
+ {{ slider('floorPrep', t.sl_floor_prep, 0, 100000, 1000, s.floorPrep, t.tip_floor_prep) }}
+ {{ slider('hvacUpgrade', t.sl_hvac_upgrade, 0, 200000, 1000, s.hvacUpgrade, t.tip_hvac_upgrade) }}
+ {{ slider('lightingUpgrade', t.sl_lighting_upgrade, 0, 100000, 1000, s.lightingUpgrade, t.tip_lighting_upgrade) }}
+ {{ slider('fitout', t.sl_fitout, 0, 300000, 1000, s.fitout, t.tip_fitout) }}
- {{ slider('outdoorFoundation', t.sl_outdoor_foundation, 0, 150, 1, s.outdoorFoundation, 'Concrete pad per m² for outdoor courts. Needs proper drainage, level surface, and frost-resistant construction.') }}
- {{ slider('outdoorSiteWork', t.sl_outdoor_site_work, 0, 60000, 500, s.outdoorSiteWork, 'Grading, drainage installation, utilities connection, and site preparation for outdoor courts.') }}
- {{ slider('outdoorLighting', t.sl_outdoor_lighting, 0, 20000, 500, s.outdoorLighting, 'Floodlight installation per court. LED recommended for energy efficiency. Must meet competition standards if applicable.') }}
- {{ slider('outdoorFencing', t.sl_outdoor_fencing, 0, 40000, 500, s.outdoorFencing, 'Perimeter fencing around outdoor court area. Includes wind screens, security gates, and ball containment nets.') }}
-
{{ slider('landPriceSqm', t.sl_land_price_sqm, 0, 500, 5, s.landPriceSqm, 'Land purchase price per m². Varies by location, zoning, and accessibility.') }}
- {{ slider('workingCapital', t.sl_working_capital, 0, 200000, 1000, s.workingCapital, 'Cash reserve for operating losses during ramp-up phase and seasonal dips. Critical buffer — underfunding is a common startup failure.') }}
- {{ slider('contingencyPct', t.sl_contingency, 0, 30, 1, s.contingencyPct, 'Percentage buffer on total CAPEX for unexpected costs. 10–15% is standard for construction, 15–20% for complex projects.') }}
- {{ slider('budgetTarget', t.sl_budget_target, 0, 5000000, 10000, s.budgetTarget, 'Set your total budget to see how your planned CAPEX compares. Leave at 0 to hide the budget indicator.') }}
+ {{ slider('workingCapital', t.sl_working_capital, 0, 200000, 1000, s.workingCapital, t.tip_working_capital) }}
+ {{ slider('contingencyPct', t.sl_contingency, 0, 30, 1, s.contingencyPct, t.tip_contingency) }}
+ {{ slider('budgetTarget', t.sl_budget_target, 0, 5000000, 10000, s.budgetTarget, t.tip_budget_target) }}
@@ -315,41 +315,41 @@
{% if lang == 'de' %}
Monatliche Betriebskosten
{% else %}
Monthly Operating Costs
{% endif %}
-
{{ slider('rentSqm', t.sl_rent_sqm, 0, 25, 0.5, s.rentSqm, 'Monthly rent per square meter for indoor hall space. Varies by location, building quality, and lease terms.') }}
-
{{ slider('outdoorRent', t.sl_outdoor_rent, 0, 5000, 50, s.outdoorRent, 'Monthly land rent for outdoor court area. Much cheaper than indoor space but weather-dependent.') }}
-
{{ slider('propertyTax', t.sl_property_tax, 0, 2000, 25, s.propertyTax, 'Monthly property tax when owning the building/land. Grundsteuer in Germany, varies by municipality and property value.') }}
{{ slider('cleaning', t.sl_cleaning, 0, 2000, 25, s.cleaning, 'Monthly professional cleaning of courts, changing rooms, common areas, and reception.') }}