# Dernier diagnostic — parcours, charts, APIs et mapping des données

## 1. Objet et périmètre

Ce document décrit le fonctionnement réel du dashboard d’acquisition tel qu’il est implémenté dans le code. Il couvre :

- le parcours global et les trois vues **Quotidien**, **Hebdomadaire** et **Mensuel** ;
- chaque carte KPI, graphique et tableau ;
- les endpoints appelés, leurs payloads et leurs périodes ;
- les champs sources, calculs, agrégations, déduplications et fallbacks ;
- les données statiques du plan et les limites connues.

Fichiers centraux :

| Responsabilité | Fichier |
|---|---|
| Orchestration serveur, périodes et appels parallèles | `src/app/page.tsx` |
| Client HTTP authentifié | `src/lib/api-client.ts` |
| Wrappers des endpoints et cache | `src/lib/combined-ads-api.ts` |
| Navigation entre les vues | `src/components/dashboard/AcquisitionDashboard.tsx` |
| Composition des vues | `src/components/dashboard/DashboardViews.tsx` |
| Implémentation des graphiques | `src/components/dashboard/Charts.tsx` |
| Cartes globales | `src/lib/daily-control-cards.ts` |
| Cartes quotidiennes | `src/lib/daily-detection-cards.ts` |
| Cartes hebdomadaires | `src/lib/weekly-arbitration-cards.ts` |
| Campagnes et CPA | `src/lib/campaign-performance.ts` |
| Cartes et charts mensuels | `src/lib/monthly-projection-cards.ts` |
| Lecture du CSV CRM | `src/lib/crm-funnel.ts` |
| Contrôles de cohérence | `src/lib/dashboard-consistency.ts` |

## 2. Parcours global de la donnée

```text
URL /?since=YYYY-MM-DD&until=YYYY-MM-DD
  → validation et limitation à J-1
  → construction des périodes principale, précédente, hebdomadaire et mensuelle
  → 6 chargements parallèles
      1. funnel AppsFlyer principal
      2. funnel AppsFlyer hebdomadaire
      3. campagnes actives
      4. performance par campagne
      5. snapshot CSV CRM local
      6. historique média juillet–décembre
  → validation non bloquante des réponses
  → calcul des cartes et séries
  → affichage du bandeau global
  → vue Quotidien / Hebdomadaire / Mensuel
```

La page est rendue dynamiquement (`force-dynamic`). Les appels utilisent `Promise.allSettled` : une source en erreur n’empêche pas les autres de s’afficher. Un bandeau « Données partielles » liste les sources indisponibles. Les wrappers utilisent actuellement `unstable_cache` avec une clé logique comprenant la période.

### 2.1 Filtre de dates

- Par défaut : les 7 jours terminant à J-1.
- `until` ne peut pas dépasser J-1.
- Si `since > until`, `since` est ramené à `until`.
- Les dates sont envoyées au format `YYYY-MM-DD`.
- Le formulaire recharge la page avec les paramètres GET `since` et `until`.
- Le backend est attendu en période inclusive et fuseau `Africa/Casablanca`.

### 2.2 Payload commun

```json
{
  "client_id": 10,
  "since": "YYYY-MM-DD",
  "until": "YYYY-MM-DD",
  "previous_since": "YYYY-MM-DD",
  "previous_until": "YYYY-MM-DD"
}
```

La période précédente a la même longueur que la période sélectionnée et se termine la veille de `since`. Elle est envoyée au backend, mais n’est pas directement exploitée par les composants actuels.

## 3. APIs et sources utilisées

### 3.1 Configuration HTTP

Toutes les APIs distantes utilisent :

- base URL : variable `API_BASE_URL` ;
- header `Authorization: Bearer <API_BEARER_TOKEN>` ;
- header `x-token: <API_X_TOKEN>` ;
- méthode `POST`, JSON, `Content-Type: application/json` ;
- erreur levée pour toute réponse HTTP non `2xx`.

### 3.2 `/api/meta-ads-data/getAppsFlyerDailyFunnel`

Cet endpoint est appelé trois fois :

| Appel | Période | Usage |
|---|---|---|
| Principal | filtre `since → until` | bandeau global et vue quotidienne |
| Hebdomadaire | `until - 6 jours → until` | KPI et chart OS de la vue hebdomadaire |
| Historique mensuel | `2026-07-01 → min(J-1, 2026-12-31)` | dépenses mensuelles et CPA CDC |

Mapping de `data[]` :

| Champ API | Signification | Consommateurs |
|---|---|---|
| `date` | jour métier | sélection J-1/J-2, fenêtres, axes et regroupement mensuel |
| `google_spend` | dépense Google en MAD | chart dépense, donut plateforme |
| `meta_spend` | dépense Meta en MAD | chart dépense, donut plateforme |
| `total_spend` | dépense totale en MAD | pacing, dépenses 7 j/semaine/mois, CPA |
| `appsflyer_installs_total` | installs totaux | cartes installs, taux install → démarrage |
| `appsflyer_installs_android` | installs Android | cartes J-1, chart OS quotidien et hebdomadaire |
| `appsflyer_installs_ios` | installs iOS | cartes J-1, chart OS quotidien et hebdomadaire |
| `onboarding_start_active_users` | utilisateurs uniques ayant démarré | cartes J-1 et taux du funnel |
| `onboarding_success_active_users` | utilisateurs uniques ayant réussi | cartes, CPA, charts de réussite |
| `onboarding_success_active_users_android` | réussites Android | parts OS hebdomadaires |
| `onboarding_success_active_users_ios` | réussites iOS | parts OS hebdomadaires |
| `*_unattributed_os` | volumes sans OS attribué | mappés et validés, pas affichés dans les charts |
| `source_status` | `complete`, `partial` ou `unavailable` par source | marque une fenêtre comme partielle |

Métadonnées utilisées pour les contrôles : `period.since`, `period.until`, `inclusive`, `timezone`, `expected_days`, `returned_days`, `missing_dates`, `duplicate_dates`, ainsi que `debug.source_warnings` et les indicateurs `can_detect_unattributed_*`.

### 3.3 `/api/meta-ads-data/getActiveCampaigns`

- Appelé sur la période principale.
- Champs utilisés : `counts.total_active_campaigns`, `counts.google_active_campaigns`, `counts.meta_active_campaigns`.
- Fallback si `counts` est indisponible : `campaign_summary.active_campaigns` du funnel principal.
- Le détail « campagnes actives à zéro dépense » vient uniquement de `campaign_summary.active_campaigns_zero_spend`.
- Limite : la temporalité métier exacte de « active » doit encore être confirmée côté backend.

### 3.4 `/api/meta-ads-data/getOnboardingSuccessMetaGoogle`

- Appelé sur la fenêtre hebdomadaire inclusive `until - 6 jours → until`.
- La liste plate `campaigns[]` est prioritaire ; fallback vers `meta_ads.campaigns[]` et `google_ads.campaigns[]`.

Mapping campagne :

| Champ API | Usage |
|---|---|
| `campaign_id`, `campaign_name`, `platform` | clé, libellé et provenance |
| `spend_mad` | dépense utilisée dans tous les calculs campagne |
| `onboarding_success` | conversions natives Meta/Google par défaut |
| `cost_per_onboarding_success` | contrôlé, puis recalculé côté frontend |
| `appsflyer_onboarding_success_active_users` | source de réussite alternative si attribution complète |
| `appsflyer_attribution_match_status` | décide si AppsFlyer peut remplacer la source native |
| `spend_original`, `original_currency`, `currency`, `metric`, `conversion_action_name` | mappés dans le modèle, non affichés dans l’UI actuelle |

Règle de sélection : AppsFlyer est utilisé uniquement si **toutes** les campagnes avec dépense sont `matched` et possèdent une valeur numérique AppsFlyer. Sinon, toute la vue utilise les conversions natives afin de ne pas mélanger deux définitions. Le CPA affiché est toujours recalculé :

```text
si onboarding_success > 0 : CPA = spend_mad / onboarding_success
sinon : CPA = null, affiché N/A et budget classé « non rapproché »
```

### 3.5 Snapshot CRM local

Source : `src/data/mensuel-projete/funnel snapshot.csv`.

Champs consommés :

| Colonne CSV | Usage |
|---|---|
| `jour` | unique date utilisée pour filtrer, trier et regrouper par mois |
| `device_id` | identifiant de déduplication d’un dossier |
| `journey_status` | finalisation et position dans le funnel |
| `isCDC` | identification d’un compte CDC |
| `current_step` | normalisé et conservé, non affiché directement |

`jour_selection` et les autres colonnes existent dans le CSV mais ne participent pas aux calculs actuels.

Déduplication : pour chaque `device_id`, la ligne au `jour` le plus récent gagne. À date égale, l’état le plus avancé gagne selon l’ordre `FINALISE > SIGNE_PROVISIONING > ESCALATED_CRC > ABANDON_SIGNATURE > ABANDON_PARCOURS > EN_COURS > ABANDON_AMONT`.

## 4. Bandeau global — toujours visible

| Carte | Source | Mapping et formule | Seuil / état |
|---|---|---|---|
| Pacing du jour | funnel principal, ligne `date === until` | `total_spend / (budget mensuel / nombre de jours du mois) × 100` | warning si `< 60 %` ou `> 115 %` |
| Coût / onboarding | funnel principal, 7 jours | `Σ total_spend / Σ onboarding_success_active_users` | ok ≤ 1 500 ; warning > 1 500 ; danger > 3 000 |
| Onboardings 7 j | funnel principal, 7 jours | `Σ onboarding_success_active_users` | fenêtre partielle signalée |
| Install → démarrage | funnel principal, 7 jours | `Σ onboarding_start_active_users / Σ appsflyer_installs_total × 100` | danger sous 10 % |
| Campagnes actives | endpoint campagnes actives | total, avec détail Google/Meta | fallback sur `campaign_summary` |

## 5. Parcours Quotidien — détecter

Objectif métier : détecter une anomalie dans les dernières 24 heures, sans arbitrer le budget.

### 5.1 Cartes quotidiennes

| Carte | Champs | Calcul |
|---|---|---|
| Dépense J-1 | `date`, `total_spend` | ligne `date === until`; variation contre `until - 1 jour` |
| Installs J-1 | `appsflyer_installs_total/android/ios` | total affiché, détail Android et iOS |
| Démarrages J-1 | `onboarding_start_active_users` | valeur de la ligne J-1 |
| Réussites J-1 | `onboarding_success_active_users` | valeur de la ligne J-1, signalée comme incomplète |
| Dépense 7 j | `total_spend` | somme de `until - 6 jours` à `until` |
| Coût / réussite | dépense et réussites 7 j | `Σ total_spend / Σ success`; seuils 1 500 et 3 000 MAD |

Si la ligne J-1 est absente, les cartes J-1 affichent `N/A`. Une date manquante ou un `source_status` non complet marque la fenêtre 7 jours comme partielle.

### 5.2 Graphiques quotidiens

| Chart | Type | Séries / champs | Transformation |
|---|---|---|---|
| Dépense quotidienne | barres empilées + ligne | `google_spend`, `meta_spend`, budget statique | cible journalière = budget du mois / jours du mois, recalculée pour chaque date |
| Répartition du jour | donut | `google_spend`, `meta_spend` | utilise la dernière date réellement reçue, puis calcule chaque part du total |
| Onboardings réussis par jour | barres + ligne | `onboarding_success_active_users` | valeur quotidienne et moyenne mobile simple sur au plus 7 points |
| Coût par onboarding réussi | ligne | `total_spend`, `onboarding_success_active_users` | pour chaque point : somme des 7 derniers points / somme des réussites ; lignes cibles 1 500 et 3 000 |
| Installs par système | barres empilées | `appsflyer_installs_android`, `appsflyer_installs_ios` | volumes quotidiens, sans non-attribués |
| Taux de passage du funnel | 2 lignes | installs, démarrages, réussites | `starts / installs_total × 100` et `success / starts × 100` par jour |

Attention : les deux charts glissants travaillent sur les **points reçus**, pas sur une grille de dates complétée. Les trous sont contrôlés globalement mais ne sont pas injectés comme valeurs manquantes dans chaque fenêtre. Les sept derniers points sont visuellement présentés comme incomplets à cause du délai de remontée.

## 6. Parcours Hebdomadaire — arbitrer

Objectif métier : déplacer le budget entre campagnes sur une fenêtre stable de 7 jours.

### 6.1 Cartes hebdomadaires

| Carte | Source | Formule |
|---|---|---|
| Dépense semaine | funnel hebdomadaire | `Σ total_spend` |
| Onboardings réussis | funnel hebdomadaire | `Σ onboarding_success_active_users` |
| Coût / onboarding | funnel hebdomadaire | dépense / réussites ; seuils 1 500 et 3 000 MAD |
| Budget sous la cible | campagnes | dépense des campagnes avec CPA ≤ 1 500 / dépense totale classée |
| Part iOS du volume | funnel hebdomadaire | installs iOS / (Android + iOS) |
| Part iOS des réussites | funnel hebdomadaire | réussites iOS / (Android + iOS) |

### 6.2 Graphiques et tableau hebdomadaires

| Élément | Type | Mapping détaillé |
|---|---|---|
| Coût par onboarding, par campagne | barres horizontales | campagnes avec CPA non nul, triées du CPA le plus faible au plus élevé ; vert ≤ 1 500, orange < 5 000, rouge sinon |
| Où va le budget | donut | part de dépense sous cible, au-dessus de la cible et non rapprochée ; une campagne avec zéro réussite est non rapprochée |
| Android contre iOS | barres horizontales empilées | deux catégories : part des installs et part des réussites ; chaque catégorie totalise 100 % des volumes attribués Android+iOS |
| Détail par campagne | tableau | nom, `spend_mad`, réussites sélectionnées, CPA recalculé et statut de cible ; tri par CPA, `N/A` en dernier |

Limite majeure : tant que l’attribution AppsFlyer par campagne n’est pas complète, les performances campagne reposent sur les conversions natives Meta/Google, alors que les KPI agrégés reposent sur AppsFlyer. Ces deux volumes ne doivent pas être comparés comme s’ils étaient identiques.

## 7. Parcours Mensuel — projeter

Objectif métier : comparer la trajectoire réelle au plan de juillet à décembre 2026.

### 7.1 Cartes mensuelles

Les cartes CRM sont filtrées exactement sur le filtre principal `since → until`, puis dédupliquées par `device_id`.

| Carte | Sources | Formule |
|---|---|---|
| Comptes CDC | CSV CRM | dossiers uniques avec `isCDC === TRUE` |
| Comptes finalisés | CSV CRM | dossiers uniques avec `journey_status === FINALISE` |
| Ratio CDC réel | CSV CRM | finalisés CDC / tous les finalisés × 100 |
| CPA par compte CDC | funnel historique + CSV | dépense média filtrée / comptes CDC filtrés |
| Budget consommé | funnel historique + plan | dépense filtrée / budget planifié du mois de `until` × 100 |
| Dossiers uniques | CSV filtré | nombre de `device_id` uniques dans la période filtrée |

Le mois cible est `until.slice(0, 7)`. Cibles CPA : 1 450–1 500 MAD. Hypothèse de ratio CDC : 50 %.

### 7.2 Graphiques mensuels

| Chart | Type | Données réelles | Données statiques / transformation |
|---|---|---|---|
| Comptes CDC contre objectif | barres | nombre mensuel de dossiers uniques `isCDC=TRUE` | objectifs CDC du plan |
| Ratio de comptes CDC | barres + ligne | `finalisés CDC / finalisés × 100` par mois | ligne constante à 50 % |
| Budget consommé contre plan | barres | somme mensuelle de `total_spend` | budgets planifiés mensuels |
| Entonnoir des dossiers | barres horizontales | dernier état global de chaque `device_id` dans tout le snapshot | `ESCALATED_CRC` est regroupé dans « En cours » |

Le funnel CRM est volontairement un snapshot global, non limité au mois. Les trois autres charts couvrent juillet à décembre 2026.

### 7.3 Valeurs statiques du plan

| Mois 2026 | Objectif comptes CDC | Budget planifié MAD |
|---|---:|---:|
| Juillet | 1 300 | 2 100 000 |
| Août | 1 300 | 1 900 000 |
| Septembre | 3 200 | 4 500 000 |
| Octobre | 3 200 | 4 500 000 |
| Novembre | 3 200 | 4 500 000 |
| Décembre | 3 000 | 4 100 000 |

## 8. Données locales statiques et données non utilisées

`src/data/dashboard.ts` contient :

- les couleurs communes des charts ;
- la cible CPA globale de `1 500 MAD` ;
- les helpers de formatage et de moyenne mobile utilisés ;
- un tableau `raw` transformé en `daily` et une liste `campaigns` de démonstration.

Les tableaux statiques `daily` et `campaigns` ne sont importés par aucun écran actuel : les vues utilisent les réponses API. Ils ne constituent donc pas un fallback de production. Les constantes `monthlyCdcTarget` et `plannedCdcRatio` exportées dans ce fichier ne pilotent pas les calculs mensuels ; ceux-ci utilisent les constantes de `monthly-projection-cards.ts`.

Le footer annonce également GA4 Data API et un taux de conversion de 11 MAD/EUR. Dans le code inspecté, aucun appel GA4 direct ni conversion EUR→MAD frontend n’est exécuté : le frontend consomme déjà `spend_mad` fourni par le backend.

## 9. Contrôles et règles de qualité

Les validations sont non bloquantes et émettent des `console.warn` :

- dates manquantes ou dupliquées ;
- incohérences entre période demandée et période retournée ;
- inclusivité et fuseau incorrects ;
- dépenses négatives ;
- total installs/réussites incohérent avec Android + iOS ;
- sources partielles ou indisponibles ;
- incapacité à détecter les volumes sans OS attribué ;
- campagnes dupliquées ou nombre de campagnes incohérent ;
- campagne avec dépense et zéro réussite absente ;
- CPA backend incompatible avec la règle frontend ;
- attribution AppsFlyer campagne incomplète ou ambiguë.

Règle commune importante : une donnée absente devrait être `null`, pas `0`. Plusieurs helpers frontend convertissent néanmoins une valeur absente en zéro lors des sommes. La complétude doit donc être interprétée avec `period`, `source_status` et les avertissements, jamais avec les seuls totaux.

## 10. Fallbacks et comportement en erreur

| Échec | Comportement |
|---|---|
| Funnel principal | tableau vide ; cartes et charts deviennent vides ou `N/A` |
| Funnel hebdomadaire | vue hebdomadaire agrégée vide, campagnes encore possibles |
| Campagnes actives | fallback possible via `campaign_summary` du funnel |
| Performance campagne | liste vide ; charts/tableau campagne vides |
| CSV CRM | cartes CRM `N/A` ou zéro selon la carte ; funnel vide |
| Historique média mensuel | fallback sur les données du funnel principal |

Le fallback mensuel sur le funnel principal peut rendre les mois hors période principale absents. Il maintient le rendu, mais ne remplace pas un historique mensuel complet.

## 11. Limites et points à clarifier

1. Les totaux AppsFlyer par OS ne permettent pas encore de mesurer de façon indépendante les volumes non attribués.
2. L’attribution AppsFlyer par campagne est structurellement prévue mais non complète dans les données de référence.
3. La définition temporelle des campagnes actives doit être confirmée côté backend.
4. Les moyennes glissantes suivent les lignes reçues et non une série calendaire matérialisant les trous.
5. Le donut « Répartition du jour » utilise la dernière ligne reçue, qui peut différer de `until` si J-1 manque.
6. Le footer cite GA4 et une conversion EUR/MAD qui ne sont pas exécutées directement dans ce frontend.
7. Les tableaux statiques de démonstration dans `src/data/dashboard.ts` sont inutilisés et peuvent prêter à confusion.

## 12. Résumé source → écrans

| Source | Bandeau | Quotidien | Hebdomadaire | Mensuel |
|---|:---:|:---:|:---:|:---:|
| Funnel AppsFlyer/Ads principal | Oui | Oui | Non | fallback seulement |
| Funnel AppsFlyer/Ads hebdomadaire | Non | Non | Oui | Non |
| Funnel AppsFlyer/Ads historique | Non | Non | Non | dépenses et CPA |
| Campagnes actives | Oui | Non | Non | Non |
| Performance Meta/Google par campagne | Non | Non | Oui | Non |
| CSV CRM | Non | Non | Non | Oui |
| Plan statique juillet–décembre | pacing | cible journalière | cible CPA | objectifs et budgets |

