openapi: 3.0.3
info:
  title: AirCarto API
  description: |
    # API AirCarto - Qualité de l'Air

    API pour récupérer les données des capteurs de qualité de l'air AirCarto et gérer les comptes utilisateurs.

    ## Types de capteurs

    | Type | Description | Accès données |
    |------|-------------|---------------|
    | **NebuleAir** | Capteur d'air extérieur | Public (open data) |
    | **ModuleAir** | Capteur d'air intérieur | Protégé par token capteur |
    | **MobileAir** | Capteur mobile | En développement |
    | **ModuleTraffic** | Capteur de comptage trafic routier (voitures, vélos, piétons, …) | Protégé par token capteur |

    ## Support

    - Site web : [aircarto.fr](https://aircarto.fr)
    - Email : contact@aircarto.fr
    - Carte des capteurs : [openairmap.aircarto.fr](https://openairmap.aircarto.fr)

  version: "3.0"
  contact:
    name: AirCarto Support
    email: contact@aircarto.fr
    url: https://aircarto.fr
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  x-logo:
    url: https://aircarto.fr/images/logo_aircarto.png
    altText: AirCarto Logo

servers:
  - url: https://api.aircarto.fr
    description: Production API

tags:
  - name: capteurs
    description: |
      Récupérer les mesures et métadonnées des capteurs.

      - **NebuleAir** : données ouvertes (pas de token requis)
      - **ModuleAir** : token capteur requis
  - name: context
    description: |
      Gestion des contextes de mesure.

      Permet d'importer et de lire des messages de contextualisation des données de mesure
      (incendie, trafic, activité industrielle, etc.) liés à un capteur ou à une position géographique.
      Ces informations aident à interpréter correctement les données de qualité de l'air.
  - name: configuration
    description: Configuration des capteurs (localisation, paramètres)

paths:
  # ============================================
  # CAPTEURS ENDPOINTS
  # ============================================

  /capteurs/metadata:
    get:
      tags:
        - capteurs
      summary: Métadonnées et dernières mesures
      description: |
        Récupère les métadonnées et les dernières mesures des capteurs.

        - **NebuleAir** : données publiques
        - **ModuleAir** : token capteur requis

        Les capteurs envoient leurs données toutes les 2 minutes.
      operationId: metadata
      parameters:
        - name: capteurType
          in: query
          description: Type de capteur
          required: true
          schema:
            type: string
            enum: [ModuleAir, NebuleAir, MobileAir, ModuleTraffic]
            default: NebuleAir
        - name: capteurID
          in: query
          description: Identifiant du capteur (optionnel, pour un capteur spécifique)
          schema:
            type: string
            example: nebuleair-025
        - name: token
          in: query
          description: Token du capteur (requis pour ModuleAir)
          schema:
            type: string
        - name: campagne
          in: query
          description: Nom de la campagne de mesure
          schema:
            type: string
        - name: format
          in: query
          description: Format de sortie
          schema:
            type: string
            enum: [JSON, GeoJSON, CSV]
            default: JSON
      responses:
        "200":
          description: Métadonnées récupérées
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/SensorMetadata"
        "400":
          description: Paramètres invalides
        "404":
          description: Capteur non trouvé

  /capteurs/dataNebuleAir:
    get:
      tags:
        - capteurs
      summary: Données historiques NebuleAir
      description: |
        Récupère les données historiques d'un capteur NebuleAir.

        **Données ouvertes** - Aucun token requis.

        Les NebuleAir envoient leurs données toutes les 2 minutes.
      operationId: dataNebuleAir
      parameters:
        - $ref: "#/components/parameters/capteurID"
        - $ref: "#/components/parameters/start"
        - $ref: "#/components/parameters/end"
        - $ref: "#/components/parameters/freq"
        - name: Five_channels
          in: query
          description: Inclure les 5 canaux PM détaillés
          schema:
            type: boolean
            default: false
        - name: gas
          in: query
          description: Inclure les polluants gazeux (CO, NO2, O3, SO2, H2S, NH3, CO2, CH4, VOC)
          schema:
            type: boolean
            default: false
        - name: battery
          in: query
          description: Inclure les données batterie/solaire
          schema:
            type: boolean
            default: false
        - name: errors
          in: query
          description: Inclure les données d'erreur (error_flags, npm_status, device_status)
          schema:
            type: boolean
            default: false
        - $ref: "#/components/parameters/format"
      responses:
        "200":
          description: Données récupérées
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/NebuleAirReading"
              example:
                - time: "2024-12-15T10:30:00Z"
                  sensorId: nebuleair-025
                  PM1: 5.23
                  PM25: 8.45
                  PM10: 12.67
                  TEMP: 22.5
                  HUM: 45.2
                  PRESS: 1013.25
                  NOISE: 42.3
                  COV: 125
            text/csv:
              schema:
                type: string
        "400":
          description: Paramètres invalides
        "404":
          description: Capteur non trouvé

  /capteurs/dataModuleAir:
    get:
      tags:
        - capteurs
      summary: Données historiques ModuleAir
      description: |
        Récupère les données historiques d'un capteur ModuleAir.

        **Token capteur requis** - Le token est inscrit au dos de l'appareil.
      operationId: dataModuleAir
      parameters:
        - $ref: "#/components/parameters/capteurID"
        - name: token
          in: query
          description: Token du capteur (inscrit au dos de l'appareil)
          required: true
          schema:
            type: string
        - $ref: "#/components/parameters/start"
        - $ref: "#/components/parameters/end"
        - $ref: "#/components/parameters/freq"
        - $ref: "#/components/parameters/format"
      responses:
        "200":
          description: Données récupérées
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ModuleAirReading"
              example:
                - time: "2024-12-15T10:30:00Z"
                  sensorId: E99
                  CO2: 420
                  COV: 125
                  PM1: 5.23
                  PM25: 8.45
                  PM10: 12.67
                  TEMP: 22.5
                  HUM: 45.2
        "401":
          description: Token invalide
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    error:
                      type: string
                    message:
                      type: string
              example:
                - error: invalid_request
                  message: Wrong token provided

  /capteurs/dataMobileAir:
    get:
      tags:
        - capteurs
      summary: Données historiques MobileAir
      description: |
        Récupère les données historiques d'un capteur MobileAir.

        Les MobileAir envoient leurs données toutes les 10 secondes.
      operationId: dataMobileAir
      parameters:
        - $ref: "#/components/parameters/capteurID"
        - $ref: "#/components/parameters/start"
        - $ref: "#/components/parameters/end"
        - name: GPSnull
          in: query
          description: |
            - `true` : inclut les données sans GPS (lat/long = 0,0)
            - `false` : uniquement les données géolocalisées
          schema:
            type: boolean
            default: true
        - $ref: "#/components/parameters/format"
      responses:
        "200":
          description: Données récupérées
        "400":
          description: Paramètres invalides
        "404":
          description: Capteur non trouvé

  /capteurs/dataModuleTraffic:
    get:
      tags:
        - capteurs
      summary: Données historiques ModuleTraffic
      description: |
        Récupère les données de comptage du trafic d'un capteur ModuleTraffic
        (voitures, vélos, piétons, camions, motos + vitesse moyenne).

        Sans `freq`, la donnée brute est retournée.
        Avec `freq`, les comptages sont **sommés** sur la fenêtre tandis que
        `avg_speed`, `signal`, `latitude`, `longitude` sont **moyennés**.
      operationId: dataModuleTraffic
      parameters:
        - $ref: "#/components/parameters/capteurID"
        - name: token
          in: query
          description: Token du capteur
          required: true
          schema:
            type: string
        - $ref: "#/components/parameters/start"
        - $ref: "#/components/parameters/end"
        - $ref: "#/components/parameters/freq"
        - $ref: "#/components/parameters/format"
      responses:
        "200":
          description: Données récupérées
        "400":
          description: Paramètres invalides
        "404":
          description: Capteur non trouvé

  /capteurs/moduleTraffic:
    post:
      tags:
        - capteurs
      summary: Endpoint d'ingestion ModuleTraffic
      description: |
        Endpoint utilisé par les capteurs ModuleTraffic pour pousser leurs comptages.

        Le `device_id` est l'identifiant unique du capteur encodé en hexadécimal
        (conversion hex → ASCII pour retrouver le numéro de série, ex.
        `4430353234313938` → `D0524198`).

        Tous les autres champs sont optionnels ; une valeur de `-1` indique une
        donnée non disponible. Une coordonnée `(0, 0)` est ignorée.
      operationId: ingestModuleTraffic
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - device_id
              properties:
                device_id:
                  type: string
                  description: Identifiant du capteur en hexadécimal (hex → ASCII)
                  example: "4430353234313938"
                signal_quality:
                  type: number
                  description: Qualité du signal réseau (dB)
                  example: -78
                version:
                  type: integer
                  description: Version du protocole
                  example: 1
                timestamp:
                  type: string
                  format: date-time
                  description: Timestamp ISO8601 de la mesure (sinon now côté serveur)
                  example: "2026-04-27T12:34:56Z"
                cars:
                  type: integer
                bikes:
                  type: integer
                pedestrians:
                  type: integer
                trucks:
                  type: integer
                motorbikes:
                  type: integer
                avg_speed:
                  type: number
                  description: Vitesse moyenne sur la période (km/h)
                latitude:
                  type: number
                  format: float
                longitude:
                  type: number
                  format: float
                version_major:
                  type: integer
                version_minor:
                  type: integer
                version_patch:
                  type: integer
            example:
              device_id: "4430353234313938"
              signal_quality: -78
              version: 1
              timestamp: "2026-04-27T12:34:56Z"
              cars: 12
              bikes: 3
              pedestrians: 5
              trucks: 1
              motorbikes: 2
              avg_speed: 45.2
              latitude: 43.2964
              longitude: 5.36978
      responses:
        "200":
          description: Données ingérées (ou ignorées avec un message lisible)

  /capteurs/dataNebuleAirAll:
    get:
      tags:
        - capteurs
      summary: Données de tous les NebuleAir
      description: |
        Récupère les données de tous les capteurs NebuleAir (ou un seul si `capteurID` est fourni).

        Les données sont regroupées par capteur dans la réponse.

        **Limite** : 1000 résultats max par requête.
      operationId: dataNebuleAirAll
      parameters:
        - name: capteurID
          in: query
          description: Identifiant du capteur (optionnel, si absent retourne tous)
          schema:
            type: string
        - $ref: "#/components/parameters/start"
        - $ref: "#/components/parameters/end"
        - $ref: "#/components/parameters/freq"
        - name: Five_channels
          in: query
          schema:
            type: boolean
            default: false
        - name: gas
          in: query
          schema:
            type: boolean
            default: false
        - $ref: "#/components/parameters/format"
      responses:
        "200":
          description: Données récupérées (groupées par capteur)
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: array
                  items:
                    $ref: "#/components/schemas/NebuleAirReading"

  /capteurs/count:
    get:
      tags:
        - capteurs
      summary: Nombre de capteurs par type
      description: Retourne le nombre total de capteurs pour un type donné.
      operationId: count
      parameters:
        - name: capteurType
          in: query
          description: Type de capteur à compter
          schema:
            type: string
            enum: [ModuleAir, NebuleAir, MobileAir, ModuleTraffic]
            default: ModuleAir
      responses:
        "200":
          description: Comptage réussi
          content:
            application/json:
              schema:
                type: object
                properties:
                  capteurType:
                    type: string
                    example: ModuleAir
                  count:
                    type: integer
                    example: 42

  # ============================================
  # CONFIGURATION ENDPOINTS
  # ============================================

  /capteurs/changeLoc:
    post:
      tags:
        - configuration
      summary: Modifier la localisation
      description: |
        Définit ou modifie les coordonnées GPS d'un capteur fixe.

        **Token capteur requis.**
      operationId: changeLoc
      parameters:
        - $ref: "#/components/parameters/capteurID"
        - name: token
          in: query
          required: true
          schema:
            type: string
        - name: lat
          in: query
          description: Latitude
          required: true
          schema:
            type: number
            format: float
            minimum: -90
            maximum: 90
            example: 43.2965
        - name: long
          in: query
          description: Longitude
          required: true
          schema:
            type: number
            format: float
            minimum: -180
            maximum: 180
            example: 5.3698
      responses:
        "200":
          description: Localisation mise à jour
        "401":
          description: Token invalide
        "404":
          description: Capteur non trouvé

  /capteurs/changeRoom:
    post:
      tags:
        - configuration
      summary: Définir le type de pièce
      description: |
        Définit le type de pièce où se situe le capteur (cuisine, chambre, etc.).

        **Token capteur requis.**
      operationId: changeRoom
      parameters:
        - $ref: "#/components/parameters/capteurID"
        - name: token
          in: query
          required: true
          schema:
            type: string
        - name: room
          in: query
          description: Type de pièce
          required: true
          schema:
            type: string
            example: "Chambre 1"
      responses:
        "200":
          description: Pièce mise à jour
        "401":
          description: Token invalide

  /capteurs/changeHeight:
    post:
      tags:
        - configuration
      summary: Définir l'étage
      description: |
        Définit l'étage où se situe le capteur.

        **Token capteur requis.**
      operationId: changeHeight
      parameters:
        - $ref: "#/components/parameters/capteurID"
        - name: token
          in: query
          required: true
          schema:
            type: string
        - name: height
          in: query
          description: Numéro d'étage
          required: true
          schema:
            type: string
            example: "2"
      responses:
        "200":
          description: Étage mis à jour
        "401":
          description: Token invalide

  /capteurs/link:
    post:
      tags:
        - configuration
      summary: Lier ModuleAir et NebuleAir
      description: |
        Lie un capteur ModuleAir (intérieur) à un NebuleAir (extérieur)
        pour comparer les mesures PM intérieur/extérieur.

        **Token ModuleAir requis.**
      operationId: link
      parameters:
        - name: capteurID_ModuleAir
          in: query
          required: true
          schema:
            type: string
        - name: token
          in: query
          description: Token du ModuleAir
          required: true
          schema:
            type: string
        - name: capteurID_NebuleAir
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Capteurs liés
        "401":
          description: Token invalide

  /capteurs/displayMap:
    post:
      tags:
        - configuration
      summary: Afficher/masquer sur OpenAirMap
      description: |
        Active ou désactive l'affichage d'un NebuleAir sur la carte OpenAirMap.

        **Token capteur requis.**
      operationId: displayMap
      parameters:
        - $ref: "#/components/parameters/capteurID"
        - name: token
          in: query
          required: true
          schema:
            type: string
        - name: displayMap
          in: query
          required: true
          schema:
            type: string
            enum: [OUI, NON]
      responses:
        "200":
          description: Paramètre mis à jour

  # ============================================
  # CONTEXT ENDPOINTS
  # ============================================

  /context/get_context:
    get:
      tags:
        - context
      summary: Récupérer les contextes
      description: |
        Récupère les événements contextuels (incendie, trafic, etc.).

        **Deux modes disponibles :**

        1. **Par capteur** : `?capteur_id=nebuleair-166`
        2. **Points géolocalisés** : `?geoloc=true`

        Toutes les dates sont retournées en **UTC** (format ISO 8601).
      operationId: getContext
      parameters:
        - name: capteur_id
          in: query
          description: Identifiant du capteur (mode capteur)
          schema:
            type: string
            example: nebuleair-166
        - name: geoloc
          in: query
          description: Récupérer les points géolocalisés (mode carte)
          schema:
            type: boolean
            example: true
        - name: start
          in: query
          description: Date de début (ISO 8601)
          schema:
            type: string
            format: date-time
            example: "2024-01-01T00:00:00Z"
        - name: end
          in: query
          description: Date de fin (ISO 8601)
          schema:
            type: string
            format: date-time
            example: "2024-12-31T23:59:59Z"
      responses:
        "200":
          description: Contextes récupérés
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Context"
              examples:
                capteurMode:
                  summary: Mode capteur
                  value:
                    - capteur_id: nebuleair-166
                      datetime_start: "2024-09-01T00:00:00Z"
                      datetime_stop: "2024-09-01T23:59:59Z"
                      context_type: indoor
                      user: username
                      comments: capteur déplacé
                geolocMode:
                  summary: Mode géolocalisation
                  value:
                    - datetime_start: "2024-09-01T00:00:00Z"
                      datetime_stop: "2024-09-01T23:59:59Z"
                      context_type: traffic
                      user: username
                      comments: embouteillage observé
                      lat: 48.8566
                      long: 2.3522
        "400":
          description: Paramètre manquant (capteur_id ou geoloc=true requis)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /context/post_context:
    post:
      tags:
        - context
      summary: Créer un contexte
      description: |
        Ajoute un message de contextualisation des données de mesure.

        **Deux modes :**

        1. **Lié à un capteur** : fournir `capteur_id`
        2. **Point géolocalisé** : fournir `geoloc: true` + `lat` + `long`

        **Gestion des fuseaux horaires :**
        - Si `timezone` est fourni, les dates sont converties de ce fuseau vers UTC
        - Si `timezone` est absent, les dates sont considérées comme étant en UTC
      operationId: postContext
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContextInput"
            examples:
              capteurMode:
                summary: Lié à un capteur
                value:
                  capteur_id: nebuleair-166
                  datetime_start: "2024-09-01T14:00:00"
                  datetime_end: "2024-09-01T15:59:59"
                  timezone: Europe/Paris
                  context_type: industrial
                  comments: fumée observée depuis l'usine
                  user: jean_dupont
              geolocMode:
                summary: Point géolocalisé
                value:
                  geoloc: true
                  lat: 48.8566
                  long: 2.3522
                  datetime_start: "2024-09-01T14:00:00"
                  datetime_end: "2024-09-01T15:59:59"
                  timezone: Europe/Paris
                  context_type: fire
                  comments: incendie de forêt
                  user: jean_dupont
      responses:
        "200":
          description: Contexte créé
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Event recorded successfully
        "400":
          description: Données invalides
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
              examples:
                missingField:
                  value:
                    success: false
                    error: Invalid or missing datetime_start
                invalidContextType:
                  value:
                    success: false
                    error: "Invalid context_type. Allowed: fire, industrial, traffic, neighbourhood"

# ============================================
# COMPONENTS
# ============================================

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Token JWT obtenu via `/auth/login.php`.

        Expire après 24 heures.
    sensorToken:
      type: apiKey
      in: query
      name: token
      description: Token du capteur (inscrit au dos de l'appareil ModuleAir)

  parameters:
    capteurID:
      name: capteurID
      in: query
      description: Identifiant du capteur
      required: true
      schema:
        type: string
      example: nebuleair-025
    start:
      name: start
      in: query
      description: |
        Début de la période. Formats acceptés :
        - Relatif : `-1h`, `-24h`, `-7d`, `-30d`
        - ISO 8601 : `2024-01-15T10:30:00Z`
      required: true
      schema:
        type: string
      example: "-24h"
    end:
      name: end
      in: query
      description: |
        Fin de la période. Formats acceptés :
        - `now` ou `now()`
        - ISO 8601 : `2024-01-16T10:30:00Z`
      schema:
        type: string
        default: "now()"
      example: "now()"
    freq:
      name: freq
      in: query
      description: |
        Pas de temps pour l'agrégation (moyenne).

        Exemples : `15m`, `1h`, `1d`
      schema:
        type: string
      example: "1h"
    format:
      name: format
      in: query
      description: Format de sortie
      schema:
        type: string
        enum: [JSON, GeoJSON, CSV]
        default: JSON

  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
          description: ID unique de l'utilisateur
          example: 1
        username:
          type: string
          description: Nom d'utilisateur
          example: john_doe
        email:
          type: string
          format: email
          example: john@example.com
        full_name:
          type: string
          example: John Doe
        role:
          type: string
          enum: [client, admin]
          example: client

    AuthResponse:
      type: object
      properties:
        message:
          type: string
          example: Login successful
        token:
          type: string
          description: Token JWT (valide 24h)
          example: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
        user:
          $ref: "#/components/schemas/User"

    ConfiguredSensor:
      type: object
      description: Capteur configuré par l'utilisateur
      properties:
        id:
          type: string
          description: Identifiant du capteur
          example: nebuleair-025
        name:
          type: string
          description: Nom personnalisé donné par l'utilisateur
          example: Salon
        type:
          type: string
          enum: [NebuleAir, ModuleAir, MobileAir]
          example: NebuleAir
        token:
          type: string
          description: Token du capteur (pour ModuleAir uniquement)
          example: abc123xyz

    SensorMetadata:
      type: object
      properties:
        id:
          type: integer
        nom:
          type: string
          example: nebuleair-025
        capteur_type:
          type: string
          example: NebuleAir
        lat:
          type: number
          format: float
          example: 43.2965
        long:
          type: number
          format: float
          example: 5.3698
        last_seen:
          type: string
          format: date-time
        pm25_last:
          type: number
          example: 8.5
        temp_last:
          type: number
          example: 22.5

    NebuleAirReading:
      type: object
      properties:
        time:
          type: string
          format: date-time
          example: "2024-12-15T10:30:00Z"
        sensorId:
          type: string
          example: nebuleair-025
        PM1:
          type: number
          nullable: true
          example: 5.23
        PM25:
          type: number
          nullable: true
          example: 8.45
        PM10:
          type: number
          nullable: true
          example: 12.67
        TEMP:
          type: number
          nullable: true
          description: Température (°C)
          example: 22.5
        HUM:
          type: number
          nullable: true
          description: Humidité (%)
          example: 45.2
        PRESS:
          type: number
          nullable: true
          description: Pression atmosphérique (hPa)
          example: 1013.25
        NOISE:
          type: number
          nullable: true
          description: Niveau sonore (dB)
          example: 42.3
        COV:
          type: number
          nullable: true
          description: Composés Organiques Volatils
          example: 125
        error_flags:
          type: integer
          nullable: true
          description: Flags d'erreur (nécessite errors=true)
        npm_status:
          type: integer
          nullable: true
          description: Statut du capteur NPM (nécessite errors=true)
        device_status:
          type: integer
          nullable: true
          description: Statut du device (nécessite errors=true)

    ModuleAirReading:
      type: object
      properties:
        time:
          type: string
          format: date-time
        sensorId:
          type: string
        CO2:
          type: number
          nullable: true
          description: CO2 (ppm)
          example: 420
        COV:
          type: number
          nullable: true
        PM1:
          type: number
          nullable: true
        PM25:
          type: number
          nullable: true
        PM10:
          type: number
          nullable: true
        TEMP:
          type: number
          nullable: true
        HUM:
          type: number
          nullable: true

    Context:
      type: object
      properties:
        capteur_id:
          type: string
          description: ID du capteur (mode capteur uniquement)
          example: nebuleair-166
        datetime_start:
          type: string
          format: date-time
          example: "2024-09-01T00:00:00Z"
        datetime_stop:
          type: string
          format: date-time
          example: "2024-09-01T23:59:59Z"
        context_type:
          type: string
          enum: [fire, industrial, traffic, neighbourhood, indoor]
          example: traffic
        user:
          type: string
          example: username
        comments:
          type: string
          example: embouteillage observé
        lat:
          type: number
          description: Latitude (mode geoloc uniquement)
          example: 48.8566
        long:
          type: number
          description: Longitude (mode geoloc uniquement)
          example: 2.3522

    ContextInput:
      type: object
      required:
        - datetime_start
        - datetime_end
      properties:
        capteur_id:
          type: string
          description: ID du capteur (requis si geoloc=false)
        geoloc:
          type: boolean
          description: Mode point géolocalisé
          default: false
        lat:
          type: number
          description: Latitude (requis si geoloc=true)
          minimum: -90
          maximum: 90
        long:
          type: number
          description: Longitude (requis si geoloc=true)
          minimum: -180
          maximum: 180
        datetime_start:
          type: string
          format: date-time
          description: Début de l'événement
        datetime_end:
          type: string
          format: date-time
          description: Fin de l'événement
        timezone:
          type: string
          description: |
            Fuseau horaire des dates (format IANA).
            Si absent, les dates sont considérées en UTC.
          example: Europe/Paris
        context_type:
          type: string
          enum: [fire, industrial, traffic, neighbourhood]
          description: Type d'événement
        comments:
          type: string
          maxLength: 1000
          description: Description de l'événement
        user:
          type: string
          maxLength: 100
          description: Nom de l'utilisateur
          default: anonymous

    Error:
      type: object
      properties:
        error:
          type: string
          description: Message d'erreur
          example: Invalid request

externalDocs:
  description: Documentation complète AirCarto
  url: https://aircarto.fr/documentation
