Pilote Orléans · configuration, pas un fork · aucun lieu inventé

Livrable 06

City DNA

La City DNA est la configuration qui rend une ville spécifique sans fork de code.

Objet

Validé par schéma (Zod côté serveur, JSON Schema exportable). Stocké en jsonb versionné + colonnes indexées pour ce qu'on filtre souvent (slug, timezone, status, country_code).

city:
  id: uuid
  slug: string            # stable, URL
  name: string
  country_code: ISO-3166-1 alpha-2
  timezone: IANA          # ex. Europe/Paris
  default_locale: fr
  status: draft | soft_launch | published | suspended
  centroid: point | null  # EXTERNAL si non saisi par un humain
  bounding_box: polygon | null

  positioning:
    primary: string
    secondary: string[]

  audiences: enum[]       # residents, students, tourists, young_professionals, families, ...

  navigation:
    # règles, pas une liste figée unique
    # weekdays : ISO 1 = lundi … 7 = dimanche. Ne pas réutiliser venue_hours.weekday.
    rules:
      - id: string
        when: { weekdays: [1-7], from: "HH:MM", to: "HH:MM", months: [1-12] optional }
        items: [slug]     # ordre d'affichage contextuel

  time_windows:           # politique ville, défauts non validés par l'usage
    evening: { from: "18:00", to: "05:00" }     # to <= from ⇒ traverse minuit
    weekend:
      weekdays: [5, 6, 7]                       # ven, sam, dim
      from: "18:00"
      to: "05:00"                                # le dimanche soir se termine lundi 05:00
    starting_soon_minutes: 90
    hero_freshness_min: 50
    # Ce soir et ce week-end peuvent se chevaucher. La page choisie par l'URL gagne.
    # On ne force pas l'exclusivité.

  modules:
    nightlife: bool          # découverte des sorties. N'ouvre pas la pub alcool.
    student: bool
    culture: bool
    shopping: bool
    tourism: bool
    nautical: bool
    ski: bool
    beach: bool
    ticketing: bool
    booking: bool
    city_pulse: bool
    night_plan: bool
    editorial_rail: bool     # rail « Sélection locale ». Jamais un terme de ranking.

  legal_gates:
    alcohol_ads: false       # indépendant de nightlife. Défaut off. LEGAL REVIEW REQUIRED.

  seasonal_rules: []      # voir flags

  editorial:
    tone: string
    priority_categories: string[]
    vocabulary: { key: label }   # ex. "zone" -> "quartier"

  feature_flags: { key: bool }  # surcharge locale des flags globaux

Les zones, catégories et pages ne sont pas un blob opaque seulement : ce sont des tables (zones, categories, editorial_pages) liées par city_id, afin de les requêter. Le blob DNA porte la politique ; les tables portent les entités.

Décisions

D1 — Configuration, pas thème CSS par ville

  • Choix : une ville = données + DNA + flags. Le design system est commun. Une ville peut surcharger logo, couleur d'accent, nom (branding léger), pas la mise en page.
  • Alternatives : site clone par ville ; thème total.
  • Compromis : moins de « wow » local visuel ; expansion réelle possible.
  • Risques : Orléans codé en dur dans un composant. Lint / revue : interdit d'importer une constante ORLEANS_ dans le domaine. Exception unique : la ligne de configuration seed, marquée fixture si elle n'est pas validée sur le terrain.

D2 — Navigation contextuelle à structure stable

  • Choix : barre persistante = Recherche, Carte, Maintenant, Ce soir, Week-end. Les slots variables (Déjeuner, Rue de Bourgogne, Clubs…) viennent de la règle horaire et ne déplacent pas les cinq ancrages.
  • Alternatives : nav entièrement dynamique ; nav fixe ignorante de l'heure.
  • Compromis : la ville ne peut pas tout réordonner.
  • Risques : règle mal écrite → slot vide. Empty : on n'affiche pas le slot.

D3 — Fenêtres horaires en heure locale de la ville

  • Choix : les règles from/to sont en heure civile de city.timezone, converties en UTC à l'exécution. DST géré par la base IANA (librairie, pas à la main).
  • Risques : créneau qui traverse minuit (22:00–05:00). Règle : si to <= from, la fenêtre passe minuit.
  • Multi-ville : une ville dans un autre fuseau ne demande aucun code.

D4 — Saisons

seasonal_rules : intervalle de dates civiles + modules ou priorités temporaires (ex. activer ski seulement si le flag global et la règle saisonnière sont vrais). Sans donnée saisonnière, la règle n'existe pas. Ne pas inventer un calendrier touristique.

D5 — Vocabulaire

Les libellés visibles (« quartier », « secteur », « front de neige ») viennent de editorial.vocabulary avec repli français générique. Les slugs restent stables en français pour le pilote (ce-soir, etablissements) — décision SEO. Les autres langues : clés i18n, pas 20 langues au MVP.

Console

Le City Manager édite : navigation, zones, modules, catégories, priorités, pages, mises en avant, règles saisonnières, flags locaux, vocabulaire. Publication explicite. Audit before/after.

Flags

  • Centroïde et bbox d'Orléans : EXTERNAL DATA REQUIRED tant qu'un humain n'a pas saisi une source (open data officielle ou relevé). Ne pas hardcoder une coordonnée « de mémoire ».