# Onboarding Funnel — Chart 1

## 1. Graphique concerné

Le premier graphique du parcours **Parcours Onboarding Funnel** est le grand graphique à barres verticales affiché en haut de l'onglet :

- titre : `Saham Bank : Onboarding funnel 23 events` ;
- source : événements GA4 ;
- mesure utilisée : `active_users` ;
- comparaison : période courante contre période précédente ;
- filtre local : Tous, iOS ou Android.

Ce graphique n'utilise pas Chart.js. Chaque barre est un `<div>` Django dont la hauteur est pilotée par une variable CSS.

## 2. Fichiers impliqués

| Rôle | Fichier |
|---|---|
| Déclaration de l'onglet | `dashboard/templates/dashboard/components/tabs.html` |
| HTML du graphique | `dashboard/templates/dashboard/components/onboarding_funnel_view.html` |
| Chargement API et mapping | `dashboard/views.py` |
| Client des API GA4 | `dashboard/services/ga4_service.py` |
| Mise en forme des barres | `dashboard/static/dashboard/css/dashboard.css` |
| Chargement asynchrone et filtre plateforme | `dashboard/static/dashboard/js/dashboard.js` |

## 3. Flux complet des données

```text
Navigateur
  GET /dashboard-data/onboarding/?filtres...
        |
        v
dashboard/views.py — section == "onboarding"
        |
        +--> POST /api/google-analytics/getAllTrackedEvents
        |        données des événements courant/précédent
        |
        +--> POST /api/google-analytics/getAllTrackedEventsdaily
        |        courbes et heatmap, pas nécessaire aux barres du chart 1
        |
        +--> POST /api/google-analytics/acquisitionFunnelGA4
                 contexte d'acquisition et ancien mini-funnel
        |
        v
_build_onboarding_funnel_context()
        |
        v
_build_detailed_onboarding_funnel()
        |
        v
_onboarding_event_rows()
        |
        v
onboarding_funnel.detailed.chart_events
        |
        v
onboarding_funnel_view.html + dashboard.css
```

Pour les barres du premier chart, la source principale est `getAllTrackedEvents`. L'API quotidienne sert aux graphiques placés plus bas dans la page.

## 4. Appel frontend vers Django

Le parcours est chargé comme un fragment HTML par la route interne :

```http
GET /dashboard-data/onboarding/?client_id=10&start_date=...&end_date=...
```

Dans `dashboard/views.py`, le bloc `if section == "onboarding"` lance trois appels en parallèle :

```python
outcome = run_parallel_tasks({
    "acquisition": lambda: _load_acquisition_api_data(filters),
    "events": lambda: _load_ga4_all_data(...),
    "daily": lambda: _load_ga4_all_tracked_events_daily(...),
}, max_workers=3)
```

Le serveur rend ensuite `dashboard/components/onboarding_funnel_view.html`. Le navigateur remplace le skeleton ou l'ancien fragment par ce HTML.

## 5. API métier utilisée par le chart 1

La fonction `_load_ga4_all_data()` construit ce payload :

```json
{
  "client_id": 10,
  "start_date": "YYYY-MM-DD",
  "end_date": "YYYY-MM-DD",
  "previous_start_date": "YYYY-MM-DD",
  "previous_end_date": "YYYY-MM-DD"
}
```

Lorsque la période précédente est fournie, elle appelle :

```http
POST /api/google-analytics/getAllTrackedEvents
```

Le chemin est déclaré par `GA4_ALL_TRACKED_EVENTS_URL` dans `dashboard/services/ga4_service.py`, puis appelé par `get_ga4_all_tracked_events()` via le client HTTP commun `post()`.

Si aucune date précédente n'est fournie, `_load_ga4_all_data()` utilise à la place :

```http
POST /api/google-analytics/acquisitionFunnelGA4all
```

Le format utile attendu dans la réponse est :

```json
{
  "period": {
    "current": {
      "start_date": "YYYY-MM-DD",
      "end_date": "YYYY-MM-DD"
    }
  },
  "events": [
    {
      "event_name": "onboarding_start",
      "active_users": {
        "current_value": 1000,
        "previous_value": 900,
        "difference": 100,
        "variation_percent": 11.11
      }
    }
  ],
  "events_by_stream": {
    "ios": { "events": [] },
    "android": { "events": [] },
    "saham.com": { "events": [] }
  }
}
```

## 6. Mapping API vers les 23 barres

L'ordre, le libellé, la couleur et le séparateur sont définis dans `ONBOARDING_FUNNEL_EVENT_CONFIG` :

| No | `event_name` GA4 | Groupe visuel |
|---:|---|---|
| 1 | `first_open` | bleu |
| 2 | `onboarding_start` | bleu |
| 3 | `otp_requested_phone` | bleu |
| 4 | `otp_verified_phone` | bleu |
| 5 | `otp_requested_email` | bleu |
| 6 | `otp_verified_email` | bleu |
| 7 | `document_scan` | vert |
| 8 | `personal_data_confirmation` | vert |
| 9 | `face_verification_begin` | vert |
| 10 | `address_confirmation` | orange |
| 11 | `job_selection` | orange |
| 12 | `monthly_revenue_selection` | orange |
| 13 | `fund_origin_selection` | orange |
| 14 | `international_operations` | violet |
| 15 | `fatca_status_declared` | violet |
| 16 | `card_begin` | cyan, séparateur avant la barre |
| 17 | `see_cards` | cyan |
| 18 | `confirm_card` | cyan |
| 19 | `card_reception` | cyan |
| 20 | `card_agency_confirmation` | cyan |
| 21 | `card_signed` | cyan |
| 22 | `card_confirmation` | cyan |
| 23 | `contract_signed` | rouge |

La fonction `_onboarding_event_rows()` cherche chaque `event_name` dans `data["events"]`, puis lit :

```python
current = metrics["current_value"]
previous = metrics["previous_value"]
period_variation = metrics["variation_percent"]
```

Un événement absent est conservé dans le chart avec une valeur égale à `0`. Ainsi, l'ordre métier reste toujours stable.

### Mapping d'une barre

Chaque élément de `chart_events` contient notamment :

| Champ du contexte | Origine ou calcul | Utilisation HTML |
|---|---|---|
| `key` | `event_name` configuré | identification métier |
| `order` | position de 1 à 23 | numéro sous la barre |
| `current` | `active_users.current_value` | valeur numérique courante |
| `previous` | `active_users.previous_value` | comparaison précédente |
| `bar_value` | valeur courante compactée (`K`, `M`) | texte au-dessus de la barre |
| `height_percent` | hauteur normalisée | variable CSS `--h` |
| `previous_height_percent` | ancienne hauteur normalisée | variable CSS `--prev-h` |
| `distribution_percent` | part par rapport au plus grand courant | tooltip |
| `step_variation_label` | variation par rapport à l'étape précédente | perte affichée sous la barre |
| `period_variation_label` | variation courant/précédent fournie par l'API | tooltip |
| `color_class` | configuration métier | couleur de la barre |
| `divider_before` | vrai uniquement pour `card_begin` | séparation du card flow |

## 7. Calculs

### Hauteur de la période courante

La plus grande valeur, courante ou précédente, sert d'échelle commune. La barre maximale occupe 78 % de la zone disponible :

```python
height_percent = current / max(current_et_previous) * 78
```

### Marqueur de la période précédente

```python
previous_height_percent = previous / max(current_et_previous) * 78
```

Le résultat est rendu comme une ligne horizontale pointillée sur la barre.

### Distribution

```python
distribution_percent = current / max_des_valeurs_courantes * 100
```

### Variation entre deux étapes successives

```python
step_variation = (current - previous_step_value) / previous_step_value * 100
```

Seules les pertes (`step_variation < 0`) sont affichées sous les barres. Attention : cette variation compare deux événements successifs du chart ; elle ne compare pas les deux périodes.

### Variation de période

`period_variation_label` provient de `active_users.variation_percent`. Elle compare la période courante à la période précédente et apparaît dans le tooltip.

## 8. Structure HTML/Django du graphique

La zone principale est créée par :

```django
<section class="saham-funnel-chart-wrap" aria-label="Funnel chart">
    <div class="saham-funnel-stage-labels">
        {% for stage in onboarding_funnel.detailed.stage_labels %}
            <div class="saham-funnel-stage {{ stage.class }}">
                {{ stage.label }}
            </div>
        {% endfor %}
    </div>

    <div class="saham-funnel-chart">
        {% for event in onboarding_funnel.detailed.chart_events %}
            {% if event.divider_before %}
                <div class="saham-funnel-divider"></div>
            {% endif %}

            <div
                class="saham-funnel-bar-group {{ event.color_class }}"
                style="--h: {{ event.height_percent }}%; --prev-h: {{ event.previous_height_percent }}%;"
            >
                <b>{{ event.bar_value }}</b>
                <div class="saham-funnel-previous-marker"></div>
                <div class="saham-funnel-bar"></div>
                <em>{{ event.step_variation_label }}</em>
                <span>{{ event.order }}</span>
            </div>
        {% endfor %}
    </div>
</section>
```

Rôle des éléments :

- `.saham-funnel-chart-wrap` : conteneur avec défilement horizontal ;
- `.saham-funnel-stage-labels` : bandeau des groupes métier ;
- `.saham-funnel-chart` : grille des 23 événements et du séparateur ;
- `.saham-funnel-bar-group` : cellule complète d'un événement ;
- `<b>` : valeur courante compactée ;
- `.saham-funnel-previous-marker` : niveau de la période précédente ;
- `.saham-funnel-bar` : rectangle de la période courante ;
- `<em>` : perte par rapport à l'étape précédente ;
- `<span>` : numéro de l'événement.

## 9. Construction CSS

Les libellés et les barres utilisent la même grille :

```css
.saham-funnel-stage-labels,
.saham-funnel-chart {
    display: grid;
    grid-template-columns: repeat(15, 1fr) 4px repeat(8, 1fr);
    gap: 18px;
    min-width: 980px;
}
```

Les 15 premiers événements sont suivis d'une colonne de 4 px pour le séparateur, puis des 8 derniers événements.

La hauteur est appliquée sans JavaScript :

```css
.saham-funnel-bar {
    position: absolute;
    bottom: 58px;
    height: var(--h);
}

.saham-funnel-previous-marker {
    position: absolute;
    bottom: calc(58px + var(--prev-h));
    border-top: 2px dashed currentColor;
}
```

Les classes `saham-funnel-blue`, `green`, `orange`, `purple`, `cyan` et `red` règlent `color` et `--saham-fill`.

## 10. Filtre Tous / iOS / Android

Quand aucun appareil précis n'est sélectionné, `_onboarding_event_rows()` lit directement `data["events"]`.

Pour iOS ou Android, il lit `data["events_by_stream"]`, conserve seulement le stream demandé, puis additionne pour chaque événement :

```python
current_value += active_users.current_value
previous_value += active_users.previous_value
```

Le backend recalcule ensuite toutes les hauteurs et variations. Les variantes HTML `total`, `ios` et `android` sont préparées côté Django ; le JavaScript remplace le contenu du parcours au changement du select `[data-onboarding-platform-filter]`. Le changement de plateforme ne nécessite donc pas un nouvel appel GA4.

## 11. Objet final envoyé au template

Le chemin exact dans le contexte Django est :

```text
onboarding_funnel
  -> detailed
      -> period_label
      -> stage_labels
      -> chart_events
      -> event_columns
      -> stream_event_table
      -> event_chart
      -> anomaly_heatmap
      -> kpis
```

Le premier chart consomme principalement :

```text
onboarding_funnel.detailed.stage_labels
onboarding_funnel.detailed.chart_events
onboarding_funnel.detailed.kpis
onboarding_funnel.detailed.period_label
```

## 12. Points d'attention

- La métrique visualisée est `active_users`, et non `event_count`.
- Le funnel est un enchaînement visuel d'événements agrégés ; le code ne reconstruit pas une cohorte utilisateur étape par étape.
- La perte entre étapes peut donc être positive si davantage d'utilisateurs déclenchent l'événement suivant.
- La hauteur courante et le marqueur précédent partagent la même échelle, ce qui rend leur comparaison correcte.
- Les événements absents restent affichés à zéro afin de conserver les 23 positions.
- Le séparateur avant `card_begin` explique la colonne CSS spéciale entre les événements 15 et 16.
