Files
boc/generate-pages.py
T
Bernt aee0f09db8 landvex: Fixar och tester klara för alla komponenter
- Datafabrik: Dockerfile fix, agentorkestrering fungerar
- Vision: Identify-modell, FAISS, OCR alla testade
- API: Alla 7 integrationstester passerade
- Upplösare: Entitetsupplösning verifierad
2026-07-05 06:41:32 +00:00

318 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Generera alla quiXzoom landningssidor
15 marknader × 20 use cases = 300 sidor
"""
import os
import shutil
BASE_DIR = "/home/bernt/.openclaw/workspace/quixzoom-market-pages"
# Marknadskonfigurationer
MARKETS = {
"se": {"lang": "sv", "country": "Sverige", "country_en": "Sweden", "code": "SE",
"pay": "150500 kr", "tax": "Skatteverket", "curr": "kr",
"badge": "🇸🇪 Lanseras i Sverige · 1 augusti 2026", "domain": "quixzoom.se"},
"de": {"lang": "de", "country": "Deutschland", "country_en": "Germany", "code": "DE",
"pay": "1550 €", "tax": "Finanzamt", "curr": "",
"badge": "🇩🇪 Startet in Deutschland · August 2026", "domain": "quixzoom.de"},
"fr": {"lang": "fr", "country": "France", "country_en": "France", "code": "FR",
"pay": "1550 €", "tax": "DGFiP", "curr": "",
"badge": "🇫🇷 Lance en France · Août 2026", "domain": "quixzoom.fr"},
"nl": {"lang": "nl", "country": "Nederland", "country_en": "Netherlands", "code": "NL",
"pay": "1550 €", "tax": "Belastingdienst", "curr": "",
"badge": "🇳🇱 Lanceert in Nederland · Augustus 2026", "domain": "quixzoom.nl"},
"uk": {"lang": "en-gb", "country": "United Kingdom", "country_en": "United Kingdom", "code": "GB",
"pay": "£1242", "tax": "HMRC", "curr": "£",
"badge": "🇬🇧 Launching in the UK · August 2026", "domain": "quixzoom.co.uk"},
"it": {"lang": "it", "country": "Italia", "country_en": "Italy", "code": "IT",
"pay": "1550 €", "tax": "Agenzia delle Entrate", "curr": "",
"badge": "🇮🇹 Lancio in Italia · Agosto 2026", "domain": "quixzoom.it"},
"es": {"lang": "es", "country": "España", "country_en": "Spain", "code": "ES",
"pay": "1550 €", "tax": "Agencia Tributaria", "curr": "",
"badge": "🇪🇸 Lanzamiento en España · Agosto 2026", "domain": "quixzoom.es"},
"fi": {"lang": "fi", "country": "Suomi", "country_en": "Finland", "code": "FI",
"pay": "1550 €", "tax": "Verohallinto", "curr": "",
"badge": "🇫🇮 Lanserar i Finland · Elokuu 2026", "domain": "quixzoom.fi"},
"da": {"lang": "da", "country": "Danmark", "country_en": "Denmark", "code": "DK",
"pay": "100350 kr", "tax": "Skattestyrelsen", "curr": "kr",
"badge": "🇩🇰 Lancering i Danmark · August 2026", "domain": "quixzoom.dk"},
"at": {"lang": "de", "country": "Österreich", "country_en": "Austria", "code": "AT",
"pay": "1550 €", "tax": "BMF", "curr": "",
"badge": "🇦🇹 Start in Österreich · August 2026", "domain": "quixzoom.at"},
"be": {"lang": "nl", "country": "België", "country_en": "Belgium", "code": "BE",
"pay": "1550 €", "tax": "FOD Financiën", "curr": "",
"badge": "🇧🇪 Lancering in België · Augustus 2026", "domain": "quixzoom.be"},
"ch": {"lang": "de", "country": "Schweiz", "country_en": "Switzerland", "code": "CH",
"pay": "1550 CHF", "tax": "ESTV", "curr": "CHF",
"badge": "🇨🇭 Start in der Schweiz · August 2026", "domain": "quixzoom.ch"},
"no": {"lang": "no", "country": "Norge", "country_en": "Norway", "code": "NO",
"pay": "150500 kr", "tax": "Skatteetaten", "curr": "kr",
"badge": "🇳🇴 Lansering i Norge · August 2026", "domain": "quixzoom.no"},
"ie": {"lang": "en-ie", "country": "Ireland", "country_en": "Ireland", "code": "IE",
"pay": "€1550", "tax": "Revenue Commissioners", "curr": "",
"badge": "🇮🇪 Launching in Ireland · August 2026", "domain": "quixzoom.ie"},
"pl": {"lang": "pl", "country": "Polska", "country_en": "Poland", "code": "PL",
"pay": "60200 zł", "tax": "Ministerstwo Finansów", "curr": "",
"badge": "🇵🇱 Start w Polsce · Sierpień 2026", "domain": "quixzoom.pl"},
}
# Use cases med översättningar
USE_CASES = {
"bridge-inspection": {
"sv": "Broinspektion", "de": "Brückeninspektion", "fr": "Inspection de ponts",
"nl": "Bruginspectie", "en-gb": "Bridge Inspection", "it": "Ispezione ponti",
"es": "Inspección de puentes", "fi": "Sillan tarkastus", "da": "Broinspektion",
"no": "Broinspeksjon", "en-ie": "Bridge Inspection", "pl": "Inspekcja mostów"
},
"infrastructure-monitoring": {
"sv": "Infrastrukturövervakning", "de": "Infrastrukturüberwachung", "fr": "Surveillance d'infrastructure",
"nl": "Infrastructuurmonitoring", "en-gb": "Infrastructure Monitoring", "it": "Monitoraggio infrastrutture",
"es": "Monitoreo de infraestructura", "fi": "Infrastruktuurin valvonta", "da": "Infrastrukturovervågning",
"no": "Infrastrukturovervåking", "en-ie": "Infrastructure Monitoring", "pl": "Monitorowanie infrastruktury"
},
"property-documentation": {
"sv": "Fastighetsdokumentation", "de": "Immobiliendokumentation", "fr": "Documentation immobilière",
"nl": "Vastgoeddocumentatie", "en-gb": "Property Documentation", "it": "Documentazione immobiliare",
"es": "Documentación de propiedades", "fi": "Kiinteistödokumentaatio", "da": "Ejendomsdokumentation",
"no": "Eiendomsdokumentasjon", "en-ie": "Property Documentation", "pl": "Dokumentacja nieruchomości"
},
"storm-damage": {
"sv": "Stormskadebedömning", "de": "Sturmschadenbewertung", "fr": "Évaluation des dégâts de tempête",
"nl": "Stormschadebeoordeling", "en-gb": "Storm Damage Assessment", "it": "Valutazione danni da tempesta",
"es": "Evaluación de daños por tormenta", "fi": "Myrskyvaurioarvio", "da": "Stormskadevurdering",
"no": "Stormskadevurdering", "en-ie": "Storm Damage Assessment", "pl": "Ocena szkód po burzy"
},
"retail-audit": {
"sv": "Butiksaudit", "de": "Einzelhandelsaudit", "fr": "Audit de vente au détail",
"nl": "Retail audit", "en-gb": "Retail Audit", "it": "Audit retail",
"es": "Auditoría retail", "fi": "Vähittäiskaupan tarkastus", "da": "Detailhandelsaudit",
"no": "Detaljhandelsrevisjon", "en-ie": "Retail Audit", "pl": "Audyt detaliczny"
},
"ooh-audit": {
"sv": "OOH-audit", "de": "OOH-Audit", "fr": "Audit OOH",
"nl": "OOH-audit", "en-gb": "OOH Audit", "it": "Audit OOH",
"es": "Auditoría OOH", "fi": "OOH-tarkastus", "da": "OOH-audit",
"no": "OOH-revisjon", "en-ie": "OOH Audit", "pl": "Audyt OOH"
},
"construction-progress": {
"sv": "Byggframsteg", "de": "Baufortschritt", "fr": "Progrès de construction",
"nl": "Bouwvoortgang", "en-gb": "Construction Progress", "it": "Avanzamento lavori",
"es": "Progreso de construcción", "fi": "Rakennustyön edistyminen", "da": "Byggefremgang",
"no": "Byggefremskridt", "en-ie": "Construction Progress", "pl": "Postęp budowy"
},
"facade-inspection": {
"sv": "Fasadinspektion", "de": "Fassadeninspektion", "fr": "Inspection de façade",
"nl": "Gevelinspectie", "en-gb": "Facade Inspection", "it": "Ispezione facciata",
"es": "Inspección de fachadas", "fi": "Julkisivutarkastus", "da": "Facadeinspektion",
"no": "Fasadeinspeksjon", "en-ie": "Facade Inspection", "pl": "Inspekcja elewacji"
},
"roof-inspection": {
"sv": "Takinspektion", "de": "Dachinspektion", "fr": "Inspection de toit",
"nl": "Dakinspectie", "en-gb": "Roof Inspection", "it": "Ispezione tetto",
"es": "Inspección de tejados", "fi": "Kattotarkastus", "da": "Taginspektion",
"no": "Takinspeksjon", "en-ie": "Roof Inspection", "pl": "Inspekcja dachu"
},
"road-condition": {
"sv": "Vägcondition", "de": "Straßenzustand", "fr": "État des routes",
"nl": "Wegconditie", "en-gb": "Road Condition", "it": "Condizione stradale",
"es": "Condición de carreteras", "fi": "Tien kunto", "da": "Vejtilstand",
"no": "Veitilstand", "en-ie": "Road Condition", "pl": "Stan dróg"
},
"signage-audit": {
"sv": "Skyltaudit", "de": "Beschilderungsaudit", "fr": "Audit de signalisation",
"nl": "Signalisatie-audit", "en-gb": "Signage Audit", "it": "Audit segnaletica",
"es": "Auditoría de señalización", "fi": "Opastetarkastus", "da": "Skilteaudit",
"no": "Skiltingrevisjon", "en-ie": "Signage Audit", "pl": "Audyt oznakowania"
},
"parking-audit": {
"sv": "Parkeringsaudit", "de": "Parkplatz-Audit", "fr": "Audit de stationnement",
"nl": "Parkeer-audit", "en-gb": "Parking Audit", "it": "Audit parcheggi",
"es": "Auditoría de aparcamiento", "fi": "Pysäköintitarkastus", "da": "Parkeringsaudit",
"no": "Parkeringsrevisjon", "en-ie": "Parking Audit", "pl": "Audyt parkingów"
},
"asset-tracking": {
"sv": "Tillgångsspårning", "de": "Asset Tracking", "fr": "Suivi d'actifs",
"nl": "Asset tracking", "en-gb": "Asset Tracking", "it": "Tracciamento asset",
"es": "Seguimiento de activos", "fi": "Omaisuuden seuranta", "da": "Asset tracking",
"no": "Eiendomssporing", "en-ie": "Asset Tracking", "pl": "Śledzenie zasobów"
},
"environmental-survey": {
"sv": "Miljöinventering", "de": "Umwelterhebung", "fr": "Enquête environnementale",
"nl": "Milieudenquête", "en-gb": "Environmental Survey", "it": "Indagine ambientale",
"es": "Estudio medioambiental", "fi": "Ympäristöselvitys", "da": "Miljøundersøgelse",
"no": "Miljøundersøkelse", "en-ie": "Environmental Survey", "pl": "Badanie środowiskowe"
},
"insurance-claim": {
"sv": "Försäkringsärende", "de": "Versicherungsfall", "fr": "Réclamation d'assurance",
"nl": "Verzekeringsclaim", "en-gb": "Insurance Claim", "it": "Sinistro assicurativo",
"es": "Reclamación de seguro", "fi": "Vakuutusvaade", "da": "Forsikringskrav",
"no": "Forsikringskrav", "en-ie": "Insurance Claim", "pl": "Roszczenie ubezpieczeniowe"
},
"lease-audit": {
"sv": "Hyresaudit", "de": "Lease-Audit", "fr": "Audit de bail",
"nl": "Lease audit", "en-gb": "Lease Audit", "it": "Audit locazione",
"es": "Auditoría de arrendamiento", "fi": "Vuokraustarkastus", "da": "Leasingaudit",
"no": "Leasingrevisjon", "en-ie": "Lease Audit", "pl": "Audyt leasingowy"
},
"event-documentation": {
"sv": "Eventdokumentation", "de": "Eventdokumentation", "fr": "Documentation d'événement",
"nl": "Evenementdocumentatie", "en-gb": "Event Documentation", "it": "Documentazione eventi",
"es": "Documentación de eventos", "fi": "Tapahtumadokumentaatio", "da": "Begivenhedsdokumentation",
"no": "Arrangementsdokumentasjon", "en-ie": "Event Documentation", "pl": "Dokumentacja wydarzeń"
},
"pre-purchase-inspection": {
"sv": "Köpesbesiktning", "de": "Kaufinspektion", "fr": "Inspection pré-achat",
"nl": "Pre-aankoopinspectie", "en-gb": "Pre-Purchase Inspection", "it": "Ispezione pre-acquisto",
"es": "Inspección pre-compra", "fi": "Esihankintatarkastus", "da": "Forhåndskøbsinspektion",
"no": "Forhåndsinspeksjon", "en-ie": "Pre-Purchase Inspection", "pl": "Inspekcja przedzakupowa"
},
"rental-inspection": {
"sv": "Hyresinspektion", "de": "Mietinspektion", "fr": "Inspection locative",
"nl": "Huurinspectie", "en-gb": "Rental Inspection", "it": "Ispezione locazione",
"es": "Inspección de alquiler", "fi": "Vuokraustarkastus", "da": "Lejeinspektion",
"no": "Leieinspeksjon", "en-ie": "Rental Inspection", "pl": "Inspekcja wynajmu"
},
"accessibility-audit": {
"sv": "Tillgänglighetsaudit", "de": "Barrierefreiheits-Audit", "fr": "Audit d'accessibilité",
"nl": "Toegankelijkheidsaudit", "en-gb": "Accessibility Audit", "it": "Audit accessibilità",
"es": "Auditoría de accesibilidad", "fi": "Saavutettavuustarkastus", "da": "Tilgængelighedsaudit",
"no": "Universell utforming revisjon", "en-ie": "Accessibility Audit", "pl": "Audyt dostępności"
},
}
def generate_page(market, use_case):
m = MARKETS[market]
lang = m["lang"]
# Hämta use case-namn på rätt språk
uc_names = USE_CASES[use_case]
uc_name = uc_names.get(lang, uc_names.get("en-gb", use_case.replace("-", " ").title()))
# Skapa katalog
dir_path = os.path.join(BASE_DIR, market, use_case)
os.makedirs(dir_path, exist_ok=True)
# Generera HTML
html = f"""<!DOCTYPE html>
<html lang="{lang}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{uc_name} — quiXzoom | {m['country']}</title>
<meta name="description" content="{uc_name} med quiXzoom i {m['country']}. Tjäna {m['pay']} per uppdrag. Betalt varje vecka.">
<meta name="geo.region" content="{m['code']}">
<meta name="geo.country" content="{m['country_en']}">
<meta name="language" content="{m['country']}">
<link rel="canonical" href="https://{m['domain']}/{use_case}/">
<style>
:root{{--qx-black:#0a0a0a;--qx-white:#fff;--qx-blue:#06f;--qx-gray:#f5f5f7;--qx-dark-gray:#1a1a1a}}
*{{margin:0;padding:0;box-sizing:border-box}}
body{{font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','Helvetica Neue',sans-serif;color:var(--qx-black);line-height:1.5}}
.hero{{background:var(--qx-black);color:var(--qx-white);padding:80px 20px;text-align:center}}
.hero h1{{font-size:2.5rem;margin-bottom:1rem}}
.hero p{{font-size:1.25rem;color:#ccc;margin-bottom:2rem}}
.badge{{display:inline-block;background:var(--qx-blue);color:#fff;padding:8px 16px;border-radius:20px;font-size:.875rem;margin-bottom:1rem}}
.container{{max-width:1200px;margin:0 auto;padding:40px 20px}}
.section{{padding:60px 20px}}
.section-gray{{background:var(--qx-gray)}}
.grid{{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:24px}}
.card{{background:#fff;padding:24px;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,.1)}}
.card h3{{margin-bottom:.5rem}}
.cta{{display:inline-block;background:var(--qx-blue);color:#fff;padding:16px 32px;border-radius:8px;text-decoration:none;font-weight:600;margin-top:1rem}}
.footer{{background:var(--qx-dark-gray);color:#fff;padding:40px 20px;text-align:center}}
</style>
</head>
<body>
<div class="hero">
<div class="badge">{m['badge']}</div>
<h1>{uc_name}</h1>
<p>quiXzoom — Crowdsourcad datainsamling i {m['country']}. Tjäna {m['pay']} per uppdrag.</p>
<a href="https://app.quixzoom.com/join" class="cta">Bli Zoomer nu</a>
</div>
<div class="container">
<div class="section">
<h2>Hur det fungerar</h2>
<div class="grid">
<div class="card">
<h3>1. Hitta uppdrag</h3>
<p>Se tillgängliga {uc_name}-uppdrag nära dig i appen.</p>
</div>
<div class="card">
<h3>2. Dokumentera</h3>
<p>Följ instruktionerna och ta bilder med din smartphone.</p>
</div>
<div class="card">
<h3>3. Få betalt</h3>
<p>Betalt varje vecka. {m['pay']} per godkänt uppdrag.</p>
</div>
</div>
</div>
</div>
<div class="section section-gray">
<div class="container">
<h2>Varför quiXzoom?</h2>
<div class="grid">
<div class="card">
<h3>Flexibelt</h3>
<p>Jobba när du vill. Inga scheman, inga minimikrav.</p>
</div>
<div class="card">
<h3>Betalt snabbt</h3>
<p>Varje vecka direkt till ditt konto via Stripe.</p>
</div>
<div class="card">
<h3>Tillförlitligt</h3>
<p>Global plattform med verifierade uppdrag och tydlig feedback.</p>
</div>
</div>
</div>
</div>
<div class="footer">
<p>© 2026 quiXzoom AB — En del av <a href="https://aamos.systems" style="color:#fff">AAMOS</a></p>
<p>{m['domain']}</p>
</div>
</body>
</html>"""
filepath = os.path.join(dir_path, "index.html")
with open(filepath, 'w', encoding='utf-8') as f:
f.write(html)
return True
def main():
total = 0
done = 0
# Räkna totalt
for market in MARKETS:
for use_case in USE_CASES:
total += 1
print(f"Genererar {total} landningssidor...")
print()
# Generera alla sidor
for market in sorted(MARKETS.keys()):
print(f"=== Marknad: {market} ===")
for use_case in USE_CASES:
if generate_page(market, use_case):
done += 1
print()
print()
print(f"✅ Klart! {done} landningssidor genererade.")
print()
print("Nästa steg:")
print("1. Deploya till S3: aws s3 sync quixzoom-market-pages s3://quixzoom-landing-prod/markets/")
print("2. Invalida CloudFront-cache")
print("3. Verifiera att alla sidor är tillgängliga")
if __name__ == "__main__":
main()