merge: add padelnomics Market Score methodology page
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Market Score methodology page** — standalone page at `/{lang}/market-score`
|
||||||
|
explaining the padelnomics Market Score (Zillow Zestimate-style). Reveals four
|
||||||
|
input categories (demographics, economic strength, demand evidence, data
|
||||||
|
completeness) and score band interpretations without exposing weights or
|
||||||
|
formulas. Full JSON-LD (WebPage + FAQPage + BreadcrumbList), OG tags, and
|
||||||
|
bilingual content (EN professional, DE Du-form). Added to sitemap and footer.
|
||||||
|
First "padelnomics Market Score" mention in each article template now links
|
||||||
|
to the methodology page (hub-and-spoke internal linking).
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- **Double language prefix in article URLs** — articles were served at
|
- **Double language prefix in article URLs** — articles were served at
|
||||||
`/en/en/markets/italy` (double prefix) because `generate_articles()` stored
|
`/en/en/markets/italy` (double prefix) because `generate_articles()` stored
|
||||||
|
|||||||
@@ -118,6 +118,7 @@
|
|||||||
- [x] Cookie consent banner (functional/A/B categories, 1-year cookie)
|
- [x] Cookie consent banner (functional/A/B categories, 1-year cookie)
|
||||||
- [x] Virtual office address on imprint
|
- [x] Virtual office address on imprint
|
||||||
- [x] SEO/GEO admin hub — GSC + Bing + Umami sync, search/funnel/scorecard views, daily background sync
|
- [x] SEO/GEO admin hub — GSC + Bing + Umami sync, search/funnel/scorecard views, daily background sync
|
||||||
|
- [x] Market Score methodology page (`/{lang}/market-score`) — Zillow-style explanation of the padelnomics Market Score; EN + DE; JSON-LD (WebPage + FAQPage + BreadcrumbList); hub-and-spoke internal linking from all article templates
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
- [x] Playwright visual/E2E test suite — 77 tests across 3 files (visual, e2e flows, quote wizard); single session-scoped server + browser; mocked emails + waitlist mode; ~59s runtime
|
- [x] Playwright visual/E2E test suite — 77 tests across 3 files (visual, e2e flows, quote wizard); single session-scoped server + browser; mocked emails + waitlist mode; ~59s runtime
|
||||||
|
|||||||
@@ -292,6 +292,10 @@ def create_app() -> Quart:
|
|||||||
async def legacy_suppliers():
|
async def legacy_suppliers():
|
||||||
return redirect("/en/suppliers", 301)
|
return redirect("/en/suppliers", 301)
|
||||||
|
|
||||||
|
@app.route("/market-score")
|
||||||
|
async def legacy_market_score():
|
||||||
|
return redirect("/en/market-score", 301)
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Blueprint registration
|
# Blueprint registration
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ priority_column: population
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ city_name }} erreicht einen **<span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;color:#0F172A;letter-spacing:-0.02em">padelnomics</span> Market Score von {{ market_score | round(1) }}/100** — damit liegt die Stadt{% if market_score >= 70 %} unter den stärksten Padel-Märkten in {{ country_name_en }}{% elif market_score >= 45 %} im soliden Mittelfeld der Padel-Märkte in {{ country_name_en }}{% else %} in einem frühen Padel-Markt mit Wachstumspotenzial{% endif %}. Aktuell gibt es **{{ padel_venue_count }} Padelanlagen** für {% if population >= 1000000 %}{{ (population / 1000000) | round(1) }}M{% else %}{{ (population / 1000) | round(0) | int }}K{% endif %} Einwohner — das entspricht {{ venues_per_100k | round(1) }} Anlagen pro 100.000 Einwohner.
|
{{ city_name }} erreicht einen **<a href="/{{ language }}/market-score" style="text-decoration:none"><span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;color:#0F172A;letter-spacing:-0.02em">padelnomics</span> Market Score</a> von {{ market_score | round(1) }}/100** — damit liegt die Stadt{% if market_score >= 70 %} unter den stärksten Padel-Märkten in {{ country_name_en }}{% elif market_score >= 45 %} im soliden Mittelfeld der Padel-Märkte in {{ country_name_en }}{% else %} in einem frühen Padel-Markt mit Wachstumspotenzial{% endif %}. Aktuell gibt es **{{ padel_venue_count }} Padelanlagen** für {% if population >= 1000000 %}{{ (population / 1000000) | round(1) }}M{% else %}{{ (population / 1000) | round(0) | int }}K{% endif %} Einwohner — das entspricht {{ venues_per_100k | round(1) }} Anlagen pro 100.000 Einwohner.
|
||||||
|
|
||||||
Die entscheidende Frage für Investoren: Was bringt ein Padel-Investment bei den aktuellen Preisen, Auslastungsraten und Baukosten tatsächlich? Das Finanzmodell unten rechnet mit echten Marktdaten aus {{ city_name }}.
|
Die entscheidende Frage für Investoren: Was bringt ein Padel-Investment bei den aktuellen Preisen, Auslastungsraten und Baukosten tatsächlich? Das Finanzmodell unten rechnet mit echten Marktdaten aus {{ city_name }}.
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ Der <span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;co
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ city_name }} has a **<span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;color:#0F172A;letter-spacing:-0.02em">padelnomics</span> Market Score of {{ market_score | round(1) }}/100** — placing it{% if market_score >= 70 %} among the strongest padel markets in {{ country_name_en }}{% elif market_score >= 45 %} in the mid-tier of {{ country_name_en }}'s padel markets{% else %} in an early-stage padel market with room for growth{% endif %}. The city currently has **{{ padel_venue_count }} padel venues** serving a population of {% if population >= 1000000 %}{{ (population / 1000000) | round(1) }}M{% else %}{{ (population / 1000) | round(0) | int }}K{% endif %} residents — a density of {{ venues_per_100k | round(1) }} venues per 100,000 people.
|
{{ city_name }} has a **<a href="/{{ language }}/market-score" style="text-decoration:none"><span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;color:#0F172A;letter-spacing:-0.02em">padelnomics</span> Market Score</a> of {{ market_score | round(1) }}/100** — placing it{% if market_score >= 70 %} among the strongest padel markets in {{ country_name_en }}{% elif market_score >= 45 %} in the mid-tier of {{ country_name_en }}'s padel markets{% else %} in an early-stage padel market with room for growth{% endif %}. The city currently has **{{ padel_venue_count }} padel venues** serving a population of {% if population >= 1000000 %}{{ (population / 1000000) | round(1) }}M{% else %}{{ (population / 1000) | round(0) | int }}K{% endif %} residents — a density of {{ venues_per_100k | round(1) }} venues per 100,000 people.
|
||||||
|
|
||||||
The question investors actually need answered is: given current pricing, occupancy, and build costs, what does the return look like? The financial model below uses real {{ city_name }} market data to give you that answer.
|
The question investors actually need answered is: given current pricing, occupancy, and build costs, what does the return look like? The financial model below uses real {{ city_name }} market data to give you that answer.
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ Die Preisspanne von {{ hourly_rate_p25 | round(0) | int }} bis {{ hourly_rate_p7
|
|||||||
|
|
||||||
## Wie steht {{ city_name }} im Vergleich da?
|
## Wie steht {{ city_name }} im Vergleich da?
|
||||||
|
|
||||||
{{ city_name }} hat {{ padel_venue_count }} Padelanlagen für {% if population >= 1000000 %}{{ (population / 1000000) | round(1) }}M{% else %}{{ (population / 1000) | round(0) | int }}K{% endif %} Einwohner ({{ venues_per_100k | round(1) }} Anlagen pro 100K Einwohner). {% if market_score >= 65 %}Mit einem Market Score von {{ market_score | round(1) }}/100 gehört {{ city_name }} zu den stärksten Padel-Märkten in {{ country_name_en }} — höhere Auslastung und Preise sind typisch für dichte, etablierte Märkte. {% elif market_score >= 40 %}Ein Market Score von {{ market_score | round(1) }}/100 steht für einen Markt im Aufbau: genug Angebot für marktgerechte Preise, aber Raum für neue Anlagen. {% else %}Ein Market Score von {{ market_score | round(1) }}/100 deutet auf einen Markt in der Frühphase hin, in dem sich Preise und Auslastung mit dem Wachstum des Sports noch deutlich entwickeln können. {% endif %}
|
{{ city_name }} hat {{ padel_venue_count }} Padelanlagen für {% if population >= 1000000 %}{{ (population / 1000000) | round(1) }}M{% else %}{{ (population / 1000) | round(0) | int }}K{% endif %} Einwohner ({{ venues_per_100k | round(1) }} Anlagen pro 100K Einwohner). {% if market_score >= 65 %}Mit einem <a href="/{{ language }}/market-score" style="text-decoration:none"><span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;color:#0F172A;letter-spacing:-0.02em">padelnomics</span> Market Score</a> von {{ market_score | round(1) }}/100 gehört {{ city_name }} zu den stärksten Padel-Märkten in {{ country_name_en }} — höhere Auslastung und Preise sind typisch für dichte, etablierte Märkte. {% elif market_score >= 40 %}Ein Market Score von {{ market_score | round(1) }}/100 steht für einen Markt im Aufbau: genug Angebot für marktgerechte Preise, aber Raum für neue Anlagen. {% else %}Ein Market Score von {{ market_score | round(1) }}/100 deutet auf einen Markt in der Frühphase hin, in dem sich Preise und Auslastung mit dem Wachstum des Sports noch deutlich entwickeln können. {% endif %}
|
||||||
|
|
||||||
Die Anlagendichte von {{ venues_per_100k | round(1) }} pro 100K Einwohner beeinflusst die Preisgestaltung direkt: {% if venues_per_100k >= 3.0 %}Höhere Dichte bedeutet mehr Wettbewerb, was die Preise eher stabilisiert oder senkt.{% elif venues_per_100k >= 1.0 %}Moderate Dichte ermöglicht marktgerechte Preise bei gleichzeitigem Wachstumsspielraum.{% else %}Niedrige Dichte gibt Betreibern mehr Preissetzungsmacht — vorausgesetzt, die Nachfrage ist da.{% endif %}
|
Die Anlagendichte von {{ venues_per_100k | round(1) }} pro 100K Einwohner beeinflusst die Preisgestaltung direkt: {% if venues_per_100k >= 3.0 %}Höhere Dichte bedeutet mehr Wettbewerb, was die Preise eher stabilisiert oder senkt.{% elif venues_per_100k >= 1.0 %}Moderate Dichte ermöglicht marktgerechte Preise bei gleichzeitigem Wachstumsspielraum.{% else %}Niedrige Dichte gibt Betreibern mehr Preissetzungsmacht — vorausgesetzt, die Nachfrage ist da.{% endif %}
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ The P25–P75 price range of {{ hourly_rate_p25 | round(0) | int }} to {{ hourly
|
|||||||
|
|
||||||
## How Does {{ city_name }} Compare?
|
## How Does {{ city_name }} Compare?
|
||||||
|
|
||||||
{{ city_name }} has {{ padel_venue_count }} padel venues for a population of {% if population >= 1000000 %}{{ (population / 1000000) | round(1) }}M{% else %}{{ (population / 1000) | round(0) | int }}K{% endif %} ({{ venues_per_100k | round(1) }} venues per 100K residents). {% if market_score >= 65 %}With a market score of {{ market_score | round(1) }}/100, {{ city_name }} is one of the stronger padel markets in {{ country_name_en }} — higher occupancy and pricing typically follow dense, competitive markets. {% elif market_score >= 40 %}A market score of {{ market_score | round(1) }}/100 reflects a mid-tier market: enough supply to have competitive pricing, but room for new venues to grow. {% else %}A market score of {{ market_score | round(1) }}/100 indicates an early-stage market where pricing and occupancy benchmarks may shift as the sport grows. {% endif %}
|
{{ city_name }} has {{ padel_venue_count }} padel venues for a population of {% if population >= 1000000 %}{{ (population / 1000000) | round(1) }}M{% else %}{{ (population / 1000) | round(0) | int }}K{% endif %} ({{ venues_per_100k | round(1) }} venues per 100K residents). {% if market_score >= 65 %}With a <a href="/{{ language }}/market-score" style="text-decoration:none"><span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;color:#0F172A;letter-spacing:-0.02em">padelnomics</span> Market Score</a> of {{ market_score | round(1) }}/100, {{ city_name }} is one of the stronger padel markets in {{ country_name_en }} — higher occupancy and pricing typically follow dense, competitive markets. {% elif market_score >= 40 %}A market score of {{ market_score | round(1) }}/100 reflects a mid-tier market: enough supply to have competitive pricing, but room for new venues to grow. {% else %}A market score of {{ market_score | round(1) }}/100 indicates an early-stage market where pricing and occupancy benchmarks may shift as the sport grows. {% endif %}
|
||||||
|
|
||||||
Venue density of {{ venues_per_100k | round(1) }} per 100K residents directly influences pricing: {% if venues_per_100k >= 3.0 %}higher density means more competition, which tends to stabilize or compress prices.{% elif venues_per_100k >= 1.0 %}moderate density supports market-rate pricing with room for growth.{% else %}low density gives operators more pricing power — provided demand exists.{% endif %}
|
Venue density of {{ venues_per_100k | round(1) }} per 100K residents directly influences pricing: {% if venues_per_100k >= 3.0 %}higher density means more competition, which tends to stabilize or compress prices.{% elif venues_per_100k >= 1.0 %}moderate density supports market-rate pricing with room for growth.{% else %}low density gives operators more pricing power — provided demand exists.{% endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ priority_column: total_venues
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
In {{ country_name_en }} erfassen wir aktuell **{{ total_venues }} Padelanlagen** in **{{ city_count }} Städten**. Der durchschnittliche <span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;color:#0F172A;letter-spacing:-0.02em">padelnomics</span> Market Score liegt bei **{{ avg_market_score }}/100**{% if avg_market_score >= 65 %} — ein starker Markt mit breiter Infrastruktur und belastbaren Preisdaten{% elif avg_market_score >= 40 %} — ein wachsender Markt mit guter Abdeckung{% else %} — ein aufstrebender Markt, in dem Früheinsteiger noch Premiumstandorte sichern können{% endif %}.
|
In {{ country_name_en }} erfassen wir aktuell **{{ total_venues }} Padelanlagen** in **{{ city_count }} Städten**. Der durchschnittliche <a href="/{{ language }}/market-score" style="text-decoration:none"><span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;color:#0F172A;letter-spacing:-0.02em">padelnomics</span> Market Score</a> liegt bei **{{ avg_market_score }}/100**{% if avg_market_score >= 65 %} — ein starker Markt mit breiter Infrastruktur und belastbaren Preisdaten{% elif avg_market_score >= 40 %} — ein wachsender Markt mit guter Abdeckung{% else %} — ein aufstrebender Markt, in dem Früheinsteiger noch Premiumstandorte sichern können{% endif %}.
|
||||||
|
|
||||||
## Marktlandschaft
|
## Marktlandschaft
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ Städte mit höherem <span style="font-family:'Bricolage Grotesque',sans-serif;f
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ country_name_en }} has **{{ total_venues }} padel venues** tracked across **{{ city_count }} cities**. The average <span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;color:#0F172A;letter-spacing:-0.02em">padelnomics</span> Market Score across tracked cities is **{{ avg_market_score }}/100**{% if avg_market_score >= 65 %} — a strong market with widespread venue penetration and solid pricing data{% elif avg_market_score >= 40 %} — a growing market with healthy city coverage{% else %} — an emerging market where early entrants can still capture prime locations{% endif %}.
|
{{ country_name_en }} has **{{ total_venues }} padel venues** tracked across **{{ city_count }} cities**. The average <a href="/{{ language }}/market-score" style="text-decoration:none"><span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;color:#0F172A;letter-spacing:-0.02em">padelnomics</span> Market Score</a> across tracked cities is **{{ avg_market_score }}/100**{% if avg_market_score >= 65 %} — a strong market with widespread venue penetration and solid pricing data{% elif avg_market_score >= 40 %} — a growing market with healthy city coverage{% else %} — an emerging market where early entrants can still capture prime locations{% endif %}.
|
||||||
|
|
||||||
## Market Landscape
|
## Market Landscape
|
||||||
|
|
||||||
|
|||||||
@@ -1641,5 +1641,49 @@
|
|||||||
"email_business_plan_preheader": "Professioneller Padel-Finanzplan \u2014 jetzt herunterladen",
|
"email_business_plan_preheader": "Professioneller Padel-Finanzplan \u2014 jetzt herunterladen",
|
||||||
|
|
||||||
"email_footer_tagline": "Die Planungsplattform f\u00fcr Padel-Unternehmer",
|
"email_footer_tagline": "Die Planungsplattform f\u00fcr Padel-Unternehmer",
|
||||||
"email_footer_copyright": "\u00a9 {year} {app_name}. Du erh\u00e4ltst diese E-Mail, weil du ein Konto hast oder eine Anfrage gestellt hast."
|
"email_footer_copyright": "\u00a9 {year} {app_name}. Du erh\u00e4ltst diese E-Mail, weil du ein Konto hast oder eine Anfrage gestellt hast.",
|
||||||
|
|
||||||
|
"footer_market_score": "Market Score",
|
||||||
|
"mscore_page_title": "Der padelnomics Market Score \u2014 So messen wir Marktpotenzial",
|
||||||
|
"mscore_meta_desc": "Der padelnomics Market Score bewertet St\u00e4dte von 0 bis 100 nach ihrem Potenzial f\u00fcr Padel-Investitionen. Erfahre, wie Demografie, Wirtschaftskraft, Nachfragesignale und Datenabdeckung einflie\u00dfen.",
|
||||||
|
"mscore_og_desc": "Ein datengest\u00fctzter Komposit-Score (0\u2013100), der die Attraktivit\u00e4t einer Stadt f\u00fcr Padelanlagen-Investitionen misst. Was steckt dahinter \u2014 und was bedeutet er f\u00fcr Deine Planung?",
|
||||||
|
"mscore_h1": "Der padelnomics Market Score",
|
||||||
|
"mscore_subtitle": "Ein datengest\u00fctztes Ma\u00df f\u00fcr die Attraktivit\u00e4t einer Stadt als Padel-Investitionsstandort.",
|
||||||
|
"mscore_what_h2": "Was der Score misst",
|
||||||
|
"mscore_what_intro": "Der Market Score ist ein Komposit-Index von 0 bis 100, der das Potenzial einer Stadt als Standort f\u00fcr Padelanlagen bewertet. Vier Datenkategorien flie\u00dfen in eine einzige Kennzahl ein \u2014 damit Du schnell einsch\u00e4tzen kannst, welche M\u00e4rkte sich n\u00e4her anzuschauen lohnt.",
|
||||||
|
"mscore_cat_demo_h3": "Demografie",
|
||||||
|
"mscore_cat_demo_p": "Bev\u00f6lkerungsgr\u00f6\u00dfe als Indikator f\u00fcr den adressierbaren Markt. Gr\u00f6\u00dfere St\u00e4dte tragen in der Regel mehr Anlagen und h\u00f6here Auslastung.",
|
||||||
|
"mscore_cat_econ_h3": "Wirtschaftskraft",
|
||||||
|
"mscore_cat_econ_p": "Regionale Kaufkraft und Einkommensindikatoren. In M\u00e4rkten mit h\u00f6herem verf\u00fcgbarem Einkommen ist die Nachfrage nach Freizeitsportarten wie Padel tendenziell st\u00e4rker.",
|
||||||
|
"mscore_cat_demand_h3": "Nachfrageindikatoren",
|
||||||
|
"mscore_cat_demand_p": "Signale aus dem laufenden Betrieb bestehender Anlagen \u2014 Auslastungsraten, Buchungsdaten, Anzahl aktiver Standorte. Wo sich reale Nachfrage bereits messen l\u00e4sst, ist das der st\u00e4rkste Indikator.",
|
||||||
|
"mscore_cat_data_h3": "Datenqualit\u00e4t",
|
||||||
|
"mscore_cat_data_p": "Wie umfassend die Datenlage f\u00fcr eine Stadt ist. Ein Score auf Basis unvollst\u00e4ndiger Daten ist weniger belastbar \u2014 wir machen das transparent, damit Du wei\u00dft, wo eigene Recherche sinnvoll ist.",
|
||||||
|
"mscore_read_h2": "Wie Du den Score liest",
|
||||||
|
"mscore_band_high_label": "70\u2013100: Starker Markt",
|
||||||
|
"mscore_band_high_p": "Gro\u00dfe Bev\u00f6lkerung, hohe Wirtschaftskraft und nachgewiesene Nachfrage durch bestehende Anlagen. Diese St\u00e4dte haben validierte Padel-M\u00e4rkte mit belastbaren Benchmarks f\u00fcr die Finanzplanung.",
|
||||||
|
"mscore_band_mid_label": "45\u201369: Solides Mittelfeld",
|
||||||
|
"mscore_band_mid_p": "Gute Grundlagen mit Wachstumspotenzial. Genug Daten f\u00fcr fundierte Planung, aber weniger Wettbewerb als in den Top-St\u00e4dten. H\u00e4ufig der Sweet Spot f\u00fcr Neueinsteiger.",
|
||||||
|
"mscore_band_low_label": "Unter 45: Fr\u00fcher Markt",
|
||||||
|
"mscore_band_low_p": "Weniger validierte Daten oder kleinere Bev\u00f6lkerung. Das hei\u00dft nicht, dass die Stadt unattraktiv ist \u2014 es kann weniger Wettbewerb und bessere Konditionen f\u00fcr Fr\u00fcheinsteiger bedeuten. Rechne mit mehr eigener Recherche vor Ort.",
|
||||||
|
"mscore_read_note": "Ein niedriger Score bedeutet nicht automatisch eine schlechte Investition. Er kann auf begrenzte Datenlage oder einen noch jungen Markt hinweisen \u2014 weniger Wettbewerb und g\u00fcnstigere Einstiegsbedingungen sind m\u00f6glich.",
|
||||||
|
"mscore_sources_h2": "Datenquellen",
|
||||||
|
"mscore_sources_p": "Der Market Score basiert auf Daten europ\u00e4ischer Statistik\u00e4mter (Bev\u00f6lkerung und Wirtschaftsindikatoren), Buchungsplattformen f\u00fcr Padelanlagen (Standortanzahl, Preise, Auslastung) und geografischen Datenbanken (Standortdaten). Die Daten werden monatlich aktualisiert.",
|
||||||
|
"mscore_limits_h2": "Einschr\u00e4nkungen",
|
||||||
|
"mscore_limits_p1": "Der Score bildet die verf\u00fcgbare Datenlage ab, nicht die absolute Marktwahrheit. St\u00e4dte, in denen weniger Anlagen auf Buchungsplattformen erfasst sind, k\u00f6nnen bei den Nachfrageindikatoren niedrigere Werte zeigen \u2014 selbst wenn die lokale Nachfrage hoch ist.",
|
||||||
|
"mscore_limits_p2": "Der Score ber\u00fccksichtigt keine lokalen Faktoren wie Immobilienkosten, Genehmigungszeitr\u00e4ume, Wettbewerbsdynamik oder regulatorische Rahmenbedingungen. Diese Aspekte sind entscheidend und erfordern Recherche vor Ort.",
|
||||||
|
"mscore_limits_p3": "Nutze den Market Score als Ausgangspunkt f\u00fcr die Priorisierung, nicht als finale Investitionsentscheidung. Im Finanzplaner kannst Du Dein konkretes Szenario durchrechnen.",
|
||||||
|
"mscore_cta_markets": "Stadtbewertungen ansehen",
|
||||||
|
"mscore_cta_planner": "Dein Investment modellieren",
|
||||||
|
"mscore_faq_h2": "H\u00e4ufig gestellte Fragen",
|
||||||
|
"mscore_faq_q1": "Was ist der padelnomics Market Score?",
|
||||||
|
"mscore_faq_a1": "Ein Komposit-Index von 0 bis 100, der die Attraktivit\u00e4t einer Stadt f\u00fcr Padelanlagen-Investitionen misst. Er kombiniert Demografie, Wirtschaftskraft, Nachfrageindikatoren und Datenqualit\u00e4t in einer vergleichbaren Kennzahl.",
|
||||||
|
"mscore_faq_q2": "Wie oft wird der Score aktualisiert?",
|
||||||
|
"mscore_faq_a2": "Monatlich. Neue Daten aus Statistik\u00e4mtern, Buchungsplattformen und Standortdatenbanken werden regelm\u00e4\u00dfig extrahiert und verarbeitet. Der Score spiegelt immer die aktuellsten verf\u00fcgbaren Daten wider.",
|
||||||
|
"mscore_faq_q3": "Warum hat meine Stadt einen niedrigen Score?",
|
||||||
|
"mscore_faq_a3": "Meist wegen begrenzter Datenabdeckung oder geringerer Bev\u00f6lkerung. Ein niedriger Score bedeutet nicht, dass die Stadt unattraktiv ist \u2014 sondern dass uns weniger Daten zur Quantifizierung der Chance vorliegen. Eigene Recherche kann die L\u00fccken schlie\u00dfen.",
|
||||||
|
"mscore_faq_q4": "Kann ich Scores l\u00e4nder\u00fcbergreifend vergleichen?",
|
||||||
|
"mscore_faq_a4": "Ja. Die Methodik ist f\u00fcr alle M\u00e4rkte einheitlich, sodass ein Score von 72 in Deutschland direkt vergleichbar ist mit einem 72 in Spanien oder Gro\u00dfbritannien.",
|
||||||
|
"mscore_faq_q5": "Garantiert ein hoher Score eine gute Investition?",
|
||||||
|
"mscore_faq_a5": "Nein. Der Score misst die Marktattraktivit\u00e4t auf Makroebene. Deine konkrete Investition h\u00e4ngt von Anlagentyp, Baukosten, Mietkonditionen und Dutzenden weiterer Faktoren ab. Im Finanzplaner kannst Du Dein Szenario mit echten Zahlen durchrechnen."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1641,5 +1641,49 @@
|
|||||||
"email_business_plan_preheader": "Professional padel facility financial plan \u2014 download now",
|
"email_business_plan_preheader": "Professional padel facility financial plan \u2014 download now",
|
||||||
|
|
||||||
"email_footer_tagline": "The padel business planning platform",
|
"email_footer_tagline": "The padel business planning platform",
|
||||||
"email_footer_copyright": "\u00a9 {year} {app_name}. You received this email because you have an account or submitted a request."
|
"email_footer_copyright": "\u00a9 {year} {app_name}. You received this email because you have an account or submitted a request.",
|
||||||
|
|
||||||
|
"footer_market_score": "Market Score",
|
||||||
|
"mscore_page_title": "The padelnomics Market Score \u2014 How We Measure Market Potential",
|
||||||
|
"mscore_meta_desc": "The padelnomics Market Score rates cities from 0 to 100 on their potential for padel investment. Learn how demographics, economic strength, demand signals, and data coverage feed into the score.",
|
||||||
|
"mscore_og_desc": "A data-driven composite score (0\u2013100) that measures how attractive a city is for padel court investment. See what goes into it and what it means for your planning.",
|
||||||
|
"mscore_h1": "The padelnomics Market Score",
|
||||||
|
"mscore_subtitle": "A data-driven measure of how attractive a city is for padel investment.",
|
||||||
|
"mscore_what_h2": "What It Measures",
|
||||||
|
"mscore_what_intro": "The Market Score is a composite index from 0 to 100 that evaluates a city\u2019s potential as a location for padel court investment. It combines four categories of data into a single number designed to help you prioritize markets worth investigating further.",
|
||||||
|
"mscore_cat_demo_h3": "Demographics",
|
||||||
|
"mscore_cat_demo_p": "Population size as a proxy for the addressable market. Larger cities generally support more venues and higher utilization.",
|
||||||
|
"mscore_cat_econ_h3": "Economic Strength",
|
||||||
|
"mscore_cat_econ_p": "Regional purchasing power and income indicators. Markets where people have higher disposable income tend to sustain stronger demand for leisure sports like padel.",
|
||||||
|
"mscore_cat_demand_h3": "Demand Evidence",
|
||||||
|
"mscore_cat_demand_p": "Signals from existing venue activity \u2014 occupancy rates, booking data, and the number of operating venues. Where real demand is already measurable, it\u2019s the strongest indicator.",
|
||||||
|
"mscore_cat_data_h3": "Data Completeness",
|
||||||
|
"mscore_cat_data_p": "How much data we have for that city. A score influenced by incomplete data is less reliable \u2014 we surface this explicitly so you know when to dig deeper on your own.",
|
||||||
|
"mscore_read_h2": "How To Read the Score",
|
||||||
|
"mscore_band_high_label": "70\u2013100: Strong market",
|
||||||
|
"mscore_band_high_p": "Large population, economic power, and proven demand from existing venues. These cities have validated padel markets with reliable benchmarks for financial planning.",
|
||||||
|
"mscore_band_mid_label": "45\u201369: Solid mid-tier",
|
||||||
|
"mscore_band_mid_p": "Good fundamentals with room for growth. Enough data to plan with confidence, but less competition than top-tier cities. Often the sweet spot for new entrants.",
|
||||||
|
"mscore_band_low_label": "Below 45: Early-stage market",
|
||||||
|
"mscore_band_low_p": "Less validated data or smaller populations. This does not mean a city is a bad investment \u2014 it may mean less competition and first-mover advantage. Expect to do more local research.",
|
||||||
|
"mscore_read_note": "A lower score does not mean a city is a bad investment. It may indicate less available data or a market still developing \u2014 which can mean less competition and better terms for early entrants.",
|
||||||
|
"mscore_sources_h2": "Data Sources",
|
||||||
|
"mscore_sources_p": "The Market Score draws on data from European statistical offices (population and economic indicators), court booking platforms (venue counts, pricing, occupancy), and geographic databases (venue locations). Data is refreshed monthly as new extractions run.",
|
||||||
|
"mscore_limits_h2": "Limitations",
|
||||||
|
"mscore_limits_p1": "The score reflects available data, not absolute market truth. Cities where fewer venues are tracked on booking platforms may score lower on demand evidence \u2014 even if local demand is strong.",
|
||||||
|
"mscore_limits_p2": "The score does not account for local factors like real estate costs, permitting timelines, competitive dynamics, or regulatory environment. These matter enormously and require on-the-ground research.",
|
||||||
|
"mscore_limits_p3": "Use the Market Score as a starting point for prioritization, not a final investment decision. The financial planner is where you model your specific scenario.",
|
||||||
|
"mscore_cta_markets": "Browse city scores",
|
||||||
|
"mscore_cta_planner": "Model your investment",
|
||||||
|
"mscore_faq_h2": "Frequently Asked Questions",
|
||||||
|
"mscore_faq_q1": "What is the padelnomics Market Score?",
|
||||||
|
"mscore_faq_a1": "A composite index from 0 to 100 that measures how attractive a city is for padel court investment. It combines demographics, economic strength, demand evidence, and data completeness into a single comparable number.",
|
||||||
|
"mscore_faq_q2": "How often is the score updated?",
|
||||||
|
"mscore_faq_a2": "Monthly. New data from statistical offices, booking platforms, and venue databases is extracted and processed on a regular cycle. Scores reflect the most recent available data.",
|
||||||
|
"mscore_faq_q3": "Why is my city\u2019s score low?",
|
||||||
|
"mscore_faq_a3": "Usually because of limited data coverage or smaller population. A low score doesn\u2019t mean the city is unattractive \u2014 it means we have less data to quantify the opportunity. Local research can fill the gaps.",
|
||||||
|
"mscore_faq_q4": "Can I compare scores across countries?",
|
||||||
|
"mscore_faq_a4": "Yes. The methodology is consistent across all markets we track, so a score of 72 in Germany is directly comparable to a 72 in Spain or the UK.",
|
||||||
|
"mscore_faq_q5": "Does a high score guarantee a good investment?",
|
||||||
|
"mscore_faq_a5": "No. The score measures market attractiveness at a macro level. Your specific investment depends on venue type, build costs, lease terms, and dozens of other factors. Use the financial planner to model your scenario with real numbers."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ async def about():
|
|||||||
return await render_template("about.html")
|
return await render_template("about.html")
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/market-score")
|
||||||
|
async def market_score():
|
||||||
|
return await render_template("market_score.html")
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/imprint")
|
@bp.route("/imprint")
|
||||||
async def imprint():
|
async def imprint():
|
||||||
lang = g.get("lang", "en")
|
lang = g.get("lang", "en")
|
||||||
|
|||||||
175
web/src/padelnomics/public/templates/market_score.html
Normal file
175
web/src/padelnomics/public/templates/market_score.html
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ t.mscore_page_title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<meta name="description" content="{{ t.mscore_meta_desc }}">
|
||||||
|
<meta property="og:title" content="{{ t.mscore_page_title }}">
|
||||||
|
<meta property="og:description" content="{{ t.mscore_og_desc }}">
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@graph": [
|
||||||
|
{
|
||||||
|
"@type": "WebPage",
|
||||||
|
"name": "{{ t.mscore_page_title }}",
|
||||||
|
"description": "{{ t.mscore_meta_desc }}",
|
||||||
|
"url": "{{ config.BASE_URL }}/{{ lang }}/market-score",
|
||||||
|
"inLanguage": "{{ lang }}",
|
||||||
|
"isPartOf": {
|
||||||
|
"@type": "WebSite",
|
||||||
|
"name": "Padelnomics",
|
||||||
|
"url": "{{ config.BASE_URL }}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "BreadcrumbList",
|
||||||
|
"itemListElement": [
|
||||||
|
{"@type": "ListItem", "position": 1, "name": "Home", "item": "{{ config.BASE_URL }}/{{ lang }}"},
|
||||||
|
{"@type": "ListItem", "position": 2, "name": "Market Score", "item": "{{ config.BASE_URL }}/{{ lang }}/market-score"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "FAQPage",
|
||||||
|
"mainEntity": [
|
||||||
|
{
|
||||||
|
"@type": "Question",
|
||||||
|
"name": "{{ t.mscore_faq_q1 }}",
|
||||||
|
"acceptedAnswer": {"@type": "Answer", "text": "{{ t.mscore_faq_a1 }}"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Question",
|
||||||
|
"name": "{{ t.mscore_faq_q2 }}",
|
||||||
|
"acceptedAnswer": {"@type": "Answer", "text": "{{ t.mscore_faq_a2 }}"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Question",
|
||||||
|
"name": "{{ t.mscore_faq_q3 }}",
|
||||||
|
"acceptedAnswer": {"@type": "Answer", "text": "{{ t.mscore_faq_a3 }}"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Question",
|
||||||
|
"name": "{{ t.mscore_faq_q4 }}",
|
||||||
|
"acceptedAnswer": {"@type": "Answer", "text": "{{ t.mscore_faq_a4 }}"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Question",
|
||||||
|
"name": "{{ t.mscore_faq_q5 }}",
|
||||||
|
"acceptedAnswer": {"@type": "Answer", "text": "{{ t.mscore_faq_a5 }}"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<main class="container-page py-12">
|
||||||
|
<div class="max-w-3xl mx-auto">
|
||||||
|
|
||||||
|
<!-- Hero -->
|
||||||
|
<header class="text-center mb-12">
|
||||||
|
<h1 class="text-3xl mb-2">
|
||||||
|
<span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;color:#0F172A;letter-spacing:-0.02em">padelnomics</span>
|
||||||
|
Market Score
|
||||||
|
</h1>
|
||||||
|
<p class="text-lg text-slate">{{ t.mscore_subtitle }}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- What It Measures -->
|
||||||
|
<section class="mb-10">
|
||||||
|
<h2 class="text-xl mb-4">{{ t.mscore_what_h2 }}</h2>
|
||||||
|
<p class="text-slate-dark leading-relaxed mb-6">{{ t.mscore_what_intro }}</p>
|
||||||
|
|
||||||
|
<div class="grid-2">
|
||||||
|
<div class="card">
|
||||||
|
<div style="font-size:1.5rem;margin-bottom:0.5rem">👥</div>
|
||||||
|
<h3 class="font-semibold text-navy mb-1">{{ t.mscore_cat_demo_h3 }}</h3>
|
||||||
|
<p class="text-sm text-slate-dark">{{ t.mscore_cat_demo_p }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div style="font-size:1.5rem;margin-bottom:0.5rem">💶</div>
|
||||||
|
<h3 class="font-semibold text-navy mb-1">{{ t.mscore_cat_econ_h3 }}</h3>
|
||||||
|
<p class="text-sm text-slate-dark">{{ t.mscore_cat_econ_p }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div style="font-size:1.5rem;margin-bottom:0.5rem">📈</div>
|
||||||
|
<h3 class="font-semibold text-navy mb-1">{{ t.mscore_cat_demand_h3 }}</h3>
|
||||||
|
<p class="text-sm text-slate-dark">{{ t.mscore_cat_demand_p }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div style="font-size:1.5rem;margin-bottom:0.5rem">🔍</div>
|
||||||
|
<h3 class="font-semibold text-navy mb-1">{{ t.mscore_cat_data_h3 }}</h3>
|
||||||
|
<p class="text-sm text-slate-dark">{{ t.mscore_cat_data_p }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- How To Read the Score -->
|
||||||
|
<section class="card mb-8">
|
||||||
|
<h2 class="text-xl mb-4">{{ t.mscore_read_h2 }}</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.25rem">
|
||||||
|
<span style="display:inline-block;width:12px;height:12px;border-radius:50%;background:#16A34A;flex-shrink:0"></span>
|
||||||
|
<span class="font-semibold text-navy">{{ t.mscore_band_high_label }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-slate-dark" style="margin-left:1.75rem">{{ t.mscore_band_high_p }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.25rem">
|
||||||
|
<span style="display:inline-block;width:12px;height:12px;border-radius:50%;background:#D97706;flex-shrink:0"></span>
|
||||||
|
<span class="font-semibold text-navy">{{ t.mscore_band_mid_label }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-slate-dark" style="margin-left:1.75rem">{{ t.mscore_band_mid_p }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.25rem">
|
||||||
|
<span style="display:inline-block;width:12px;height:12px;border-radius:50%;background:#64748B;flex-shrink:0"></span>
|
||||||
|
<span class="font-semibold text-navy">{{ t.mscore_band_low_label }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-slate-dark" style="margin-left:1.75rem">{{ t.mscore_band_low_p }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-slate mt-4" style="border-left:3px solid #E2E8F0;padding-left:0.75rem">{{ t.mscore_read_note }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Data Sources -->
|
||||||
|
<section class="card mb-8">
|
||||||
|
<h2 class="text-xl mb-4">{{ t.mscore_sources_h2 }}</h2>
|
||||||
|
<p class="text-slate-dark leading-relaxed">{{ t.mscore_sources_p }}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Limitations -->
|
||||||
|
<section class="card mb-8">
|
||||||
|
<h2 class="text-xl mb-4">{{ t.mscore_limits_h2 }}</h2>
|
||||||
|
<div class="space-y-3 text-slate-dark leading-relaxed">
|
||||||
|
<p>{{ t.mscore_limits_p1 }}</p>
|
||||||
|
<p>{{ t.mscore_limits_p2 }}</p>
|
||||||
|
<p>{{ t.mscore_limits_p3 }}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- CTA -->
|
||||||
|
<div class="text-center my-12">
|
||||||
|
<a href="{{ url_for('content.markets') }}" class="btn" style="margin-right:0.75rem">{{ t.mscore_cta_markets }}</a>
|
||||||
|
<a href="{{ url_for('planner.index') }}" class="btn-secondary" style="display:inline-block;padding:0.625rem 1.25rem;border-radius:6px;font-weight:600;font-size:0.875rem;text-decoration:none">{{ t.mscore_cta_planner }}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- FAQ -->
|
||||||
|
<section>
|
||||||
|
<h2 class="text-xl mb-4">{{ t.mscore_faq_h2 }}</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
{% for i in range(1, 6) %}
|
||||||
|
<details style="border:1px solid #E2E8F0;border-radius:8px;padding:0.75rem 1rem">
|
||||||
|
<summary class="font-semibold text-navy" style="cursor:pointer">{{ t['mscore_faq_q' ~ i] }}</summary>
|
||||||
|
<p class="text-sm text-slate-dark mt-2">{{ t['mscore_faq_a' ~ i] }}</p>
|
||||||
|
</details>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
||||||
@@ -23,6 +23,7 @@ STATIC_PATHS = [
|
|||||||
"/imprint",
|
"/imprint",
|
||||||
"/suppliers",
|
"/suppliers",
|
||||||
"/markets",
|
"/markets",
|
||||||
|
"/market-score",
|
||||||
"/planner/",
|
"/planner/",
|
||||||
"/directory/",
|
"/directory/",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -171,6 +171,7 @@
|
|||||||
<li><a href="{{ url_for('planner.index') }}">{{ t.nav_planner }}</a></li>
|
<li><a href="{{ url_for('planner.index') }}">{{ t.nav_planner }}</a></li>
|
||||||
<li><a href="{{ url_for('directory.index') }}">{{ t.nav_directory }}</a></li>
|
<li><a href="{{ url_for('directory.index') }}">{{ t.nav_directory }}</a></li>
|
||||||
<li><a href="{{ url_for('content.markets') }}">{{ t.nav_markets }}</a></li>
|
<li><a href="{{ url_for('content.markets') }}">{{ t.nav_markets }}</a></li>
|
||||||
|
<li><a href="{{ url_for('public.market_score') }}">{{ t.footer_market_score }}</a></li>
|
||||||
<li><a href="{{ url_for('public.suppliers') }}">{{ t.nav_suppliers }}</a></li>
|
<li><a href="{{ url_for('public.suppliers') }}">{{ t.nav_suppliers }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ _IDENTICAL_VALUE_ALLOWLIST = {
|
|||||||
# Business plan — Indoor/Outdoor same in DE, financial abbreviations
|
# Business plan — Indoor/Outdoor same in DE, financial abbreviations
|
||||||
"bp_indoor", "bp_outdoor",
|
"bp_indoor", "bp_outdoor",
|
||||||
"bp_lbl_ebitda", "bp_lbl_irr", "bp_lbl_moic", "bp_lbl_opex",
|
"bp_lbl_ebitda", "bp_lbl_irr", "bp_lbl_moic", "bp_lbl_opex",
|
||||||
|
# Market Score — branded term kept in English in DE
|
||||||
|
"footer_market_score",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
59
web/tests/test_market_score.py
Normal file
59
web/tests/test_market_score.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
"""Tests for the Market Score methodology page."""
|
||||||
|
|
||||||
|
|
||||||
|
async def test_en_returns_200(client):
|
||||||
|
resp = await client.get("/en/market-score")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
text = await resp.get_data(as_text=True)
|
||||||
|
assert "Market Score" in text
|
||||||
|
assert "padelnomics" in text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_de_returns_200(client):
|
||||||
|
resp = await client.get("/de/market-score")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
text = await resp.get_data(as_text=True)
|
||||||
|
assert "Market Score" in text
|
||||||
|
assert "padelnomics" in text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_legacy_redirect(client):
|
||||||
|
resp = await client.get("/market-score")
|
||||||
|
assert resp.status_code == 301
|
||||||
|
assert resp.headers["Location"].endswith("/en/market-score")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_contains_jsonld(client):
|
||||||
|
resp = await client.get("/en/market-score")
|
||||||
|
text = await resp.get_data(as_text=True)
|
||||||
|
assert '"@type": "WebPage"' in text
|
||||||
|
assert '"@type": "FAQPage"' in text
|
||||||
|
assert '"@type": "BreadcrumbList"' in text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_contains_faq_section(client):
|
||||||
|
resp = await client.get("/en/market-score")
|
||||||
|
text = await resp.get_data(as_text=True)
|
||||||
|
assert "Frequently Asked Questions" in text
|
||||||
|
assert "<details" in text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_de_contains_faq_section(client):
|
||||||
|
resp = await client.get("/de/market-score")
|
||||||
|
text = await resp.get_data(as_text=True)
|
||||||
|
assert "Häufig gestellte Fragen" in text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_contains_og_tags(client):
|
||||||
|
resp = await client.get("/en/market-score")
|
||||||
|
text = await resp.get_data(as_text=True)
|
||||||
|
assert 'og:title' in text
|
||||||
|
assert 'og:description' in text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_footer_has_market_score_link(client):
|
||||||
|
resp = await client.get("/en/market-score")
|
||||||
|
text = await resp.get_data(as_text=True)
|
||||||
|
assert "/en/market-score" in text
|
||||||
|
# Footer should link to market score page
|
||||||
|
assert "Market Score" in text
|
||||||
Reference in New Issue
Block a user