---
name: ifpb-kobo-form
description: "IFPB KoboToolbox Form Designer - Conception et maintenance du formulaire mobile de recensement foncier IFPB (XLSForm, offline-first, GPS, photo)"
version: "1.0.0"
tags: [ifpb, kobotoolbox, xlsform, recensement, mobile, collecte-donnees]
---

# IFPB KoboToolbox Form Skill

## Objectif
Concevoir, déployer et maintenir le formulaire de collecte terrain IFPB sur KoboToolbox pour les agents recenseurs municipaux.

## 🎯 Philosophie de Design (Règles d'Or)

> Ces règles ont été validées par le porteur de projet (Rhinja). Elles sont NON NÉGOCIABLES.

### Règle 1 — L'agent recense, il n'évalue PAS
**Les agents ne doivent JAMAIS voir :**
- ❌ La catégorie A/B/C/D d'une parcelle (côté serveur uniquement)
- ❌ Le tarif par m² appliqué
- ❌ Le taux d'imposition (%)
- ❌ La grille tarifaire détaillée

**Les agents VOIENT (et montrent au propriétaire) :**
- ✅ Le montant total estimé (IFTB + CAC) en Ariary — en **lecture seule**
- ✅ Les données brutes saisies (usage, matériaux, surface) — pour transparence

**Pourquoi montrer le montant ?** → Transparence = confiance. Le propriétaire sait ce qu'il va payer, pas de surprise à la réception de l'avis d'imposition. L'agent est **messager**, pas négociateur. Si contestation → script de réponse fourni + recours auprès de la Mairie.

### Règle 2 — Calcul double (Kobo caché + Serveur source de vérité)
- Les champs `calculate` dans le XLSForm produisent une **estimation instantanée** affichée en R3
- Cette estimation est **read-only** (l'agent ne peut pas la modifier)
- Le **Fiscal Engine (backend Python)** recalcule tout depuis zéro = source de vérité unique
- En cas d'écart entre estimation Kobo et calcul serveur → le serveur gagne toujours

### Règle 3 — Photo = preuve technique anti-fraude
- **1 photo par étage/niveau** (`photo_facade_etage` dans repeat r_etage) — prouve l'usage ET les matériaux déclarés
- **1 photo générale par bâtiment** (`photo_batiment`) — vue d'ensemble
- Le Superviseur Qualité compare photo ↔ déclaration lors des audits aléatoires 5%

## Architecture du Formulaire (4 sections)

> **Version courante** : `v2026.06.02_Final.xlsx` (déployée dans `deliverables/kobo/`)
> **Feuilles** : survey (38 lignes) / choices (28 choix) / settings (4 paramètres)

### SECTION 1 — LOCALISATION PARCELLE

| Champ | Type | Contrainte | Note |
|-------|------|------------|------|
| `fokontany` | select_one (13 options) | Obligatoire | Liste des quartiers de Moramanga |
| `gps_parcelle` | geopoint | **Précision < 10m (constraint `.[3] <= 10`)** | Capture auto lat/long/alt/précision. Rejet si >10m. |

**Fokontany disponibles (13) :**
1. Ambarilava / 2. Ambodiakondro / 3. Ambodimadera / 4. Ambohitranjavidy
5. Antanamandroso Est / 6. Antanamandroso Ouest / 7. Camp des Mariés
8. Moramanga Ambony / 9. Moramanga Ville / 10. Tanambao
11. Tsarafasina / 12. Tsarahonenana / 13. Tsaralalana

### SECTION 2 — IDENTITÉ CONTRIBUTABLE

| Champ | Type | Contrainte | Validation |
|-------|------|------------|------------|
| `statut_occupant` | select_one | Obligatoire | Propriétaire / Locataire / Occupant à titre gratuit |
| `nom_proprietaire` | text | Majuscules | Individu ou Raison Sociale |
| `cin_proprietaire` | text | **12 chiffres, regex `^[0-9]{12}$`** | Relevance : seulement si propriétaire |
| `contact_mobile` | text | **10 chiffres, regex `^[0-9]{10}$`** | Format MG : 034xxxxxxx |
| `nbre_batiments` | integer | **≥ 1 (constraint `. > 0`)** | Gestion cas mixtes |

### SECTION 3 — BÂTIMENT (begin_repeat r_batiment)

**Champs par bâtiment :**

| Champ | Type | Règle |
|-------|------|-------|
| `zone_visuelle` | select_one | rn / centre / peripherie / enclave |
| `occupation_effective` | select_one | oui / non → déclenche question exonération |
| `eligible_exon_temp` | select_one | oui / non → relevance : seulement si occupation=oui |
| `nb_etages` | integer | **≥ 0 (constraint `. >= 0`)** — 0 = RDC seulement |

#### SOUS-SECTION ÉTAGE (begin_repeat r_etage — imbriqué dans r_batiment)

| Champ | Type | Règle |
|-------|-------------|
| `usage_visuel` | select_one | industriel / commercial / habitation |
| `materiau_visuel` | select_one | dur / semi_dur / traditionnel |
| `surface_etage` | integer | **> 0 m² (constraint `. > 0`)** |
| `photo_facade_etage` | **image** | **Obligatoire — preuve technique matériaux+usage** ⚠️ |

#### Calculs cachés dans repeat r_etage (calculate = read-only pour agent) :
- `tarif_m2` → grille selon usage × zone (5000/2500/1000/200 Ar)
- `taux_applicable` → selon usage + matériaux (10%/7%/5%/2%)
- `valeur_locative_etage` = surface × tarif_m2
- `impot_principal_etage_brut` = valeur_locative × taux

#### Calculs cachés après end_repeat r_etage (par bâtiment) :
- `principal_batiment_somme` = sum(impot_principal_etage_brut)
- `principal_batiment` = if(exoneration_oui, 0, somme) ← exonération CGI <5 ans
- `cac_batiment` = principal × 0.10
- `total_batiment` = principal + cac
- `photo_batiment` = **image obligatoire — vue générale bâtiment**

### SECTION 4 — RÉCAPITULATIF PARCELLE

| Champ | Type | Contenu | Visible par agent ? |
|-------|------|---------|---------------------|
| `total_parcelle` | calculate | sum(total_batiment) | ❌ Caché (intermédiaire) |
| `r1` | note | Propriétaire recensé | ✅ Oui (logistique) |
| `r2` | note | Téléphone + nb bâtiments | ✅ Oui (logistique) |
| `r3` | note | **💰 Montant estimé IFTB+CAC en Ariary** + mention calcul auto + recours Mairie | ✅ **OUI — transparence propriétaire** |

## Skip Logic & Relevance Rules

```yaml
# CIN : seulement visible si statut = propriétaire
relevant_cin:
  - if: ${statut_occupant} = 'proprietaire'
    then: afficher champ cin_proprietaire

# Exonération <5 ans : seulement si occupation effective confirmée
relevant_exoneration:
  - if: ${occupation_effective} = 'oui'
    then: afficher question eligible_exon_temp
    # Si occupé → vérifier si construction neuve (<5 ans) éligible exonération CGI
    # Si NON occupé → pas de question (bâtiment vide = pas exonéré de toute façon)

# Calcul tarif m² (caché — calculate) :
#   industriel → 5000 Ar/m²
#   commercial + (zone=rn OR zone=centre) → 2500 Ar/m²
#   habitation + materiau=dur → 1000 Ar/m²
#   tout autre cas (vulnérable) → 200 Ar/m²

# Calcul taux (caché — calculate) :
#   industriel → 10%
#   commercial → 7%
#   habitation + dur → 5%
#   vulnérable → 2%

# Exonération appliquée au niveau bâtiment (pas étage) :
#   if eligible_exon_temp = 'oui' → principal_batiment = 0
```

## ⚠️ Pièges & Anti-Patterns (Leçons Apprises)

### ❌ Ne PAS cacher le montant total au propriétaire
**Erreur commise** : J'ai initialement recommandé de supprimer la note R3 (montant) pour "sécurité".
**Correction (Rhinja)** : Le montant doit être VISIBLE en R3 car :
- L'agent ne peut PAS modifier un champ `calculate` ou `note` (read-only)
- La transparence réduit les contestations et contentieux futurs
- Le propriétaire sait à quoi s'attendre → pas de surprise avec l'avis d'imposition
- **Règle** : montrer le montant, cacher la catégorisation A/B/C/D et les tarifs

### ❌ Ne PAS montrer les catégories A/B/C/D aux agents
Les catégories sont un outil **interne** du Fiscal Engine. Si un agent connaît la catégorie d'une parcelle :
- Il peut être tenté de "négocier" avec le propriétaire ("vous êtes Cat D, c'est chanceux")
- Il peut orienter ses déclarations pour changer une catégorie
- **Solution** : les calculateurs dans le XLSForm ne exposent jamais la catégorie. Seul le montant final apparaît.

### ❌ Ne pas oublier la photo par étage (pas seulement par bâtiment)
Version originale du XLSForm : 1 photo par bâtiment uniquement.
**Problème** : impossible de vérifier que l'agent a bien déclaré usage+matériaux pour CHAQUE niveau d'un immeuble mixte.
**Correction v2026.06.02** : ajouter `image photo_facade_etage` DANS le repeat `r_etage`.

### ❌ Ne pas oublier la contrainte GPS précision
Le champ geopoint capture 4 valeurs : `[lat, lon, alt, precision]`. La précision est à l'index **3**.
Contrainte XLSForm : `.[3] <= 10` (rejette si >10 mètres).

## Spécifications Techniques

### Offline-First
- Formulaire fonctionnel sans connexion internet
- Synchronisation différée quand connectivité disponible
- Queue locale des soumissions en attente

### GPS Requirements
- Précision minimale : **10 mètres**
- **Constraint XLSForm** : `.[3] <= 10` avec message "Précision GPS insuffisante (>10m). Réessayer."
- Capture automatique (pas de saisie manuelle des coordonnées)
- Format : latitude, longitude, altitude, précision

### Photo Requirements
- **1 photo par étage/niveau** (`photo_facade_etage`) — preuve technique matériaux + usage ⚠️
- **1 photo générale par bâtiment** (`photo_batiment`) — vue d'ensemble
- Taille max : **10MB** (setting `max_bytes: 10000000`)
- Format accepté : JPG, PNG
- Preuve technique obligatoire pour validation fiche + audit anti-fraude

### Constraints (syntaxe XLSForm complète)
```yaml
constraints:
  cin:
    regex: "^[0-9]{12}$"
    message: "La CIN malgache doit comporter exactement 12 chiffres"
  
  telephone:
    regex: "^[0-9]{10}$"
    message: "Le numéro doit comporter 10 chiffres (ex: 034xxxxxxx)"
  
  gps_parcelle:
    constraint: ".[3] <= 10"
    message: "Précision GPS insuffisante (>10m). Réessayer."
  
  superficie:
    constraint: ". > 0"
    message: "La surface doit être supérieure à 0 m²"
  
  nb_batiments:
    constraint: ". > 0"
    message: "Il doit y avoir au moins 1 bâtiment"
  
  nb_etages:
    constraint: ". >= 0"
    message: "Ne peut pas être négatif (0 = RDC seulement)"
```

## Feuille Settings (obligatoire)

| Setting | Valeur | Note |
|---------|--------|------|
| `form_title` | IFPB — Recensement Foncier Moramanga | Titre affiché dans KoboCollect |
| `form_id` | ifpb_recensement_foncier_2026 | UID unique du formulaire |
| `version` | YYYY.MM.DD | Versioning sémantique (incrémenter à chaque déploiement) |
| `max_bytes` | 10000000 | 10MB max par photo |

⚠️ Toujours incrémenter `version` lors d'une mise à jour. Les soumissions de différentes versions ont des schémas différents.

## Déploiement KoboToolbox

### Environnement
- **Kobo Toolbox Server** : Cloud (kobotoolbox.org) pour Phase 1/Pilot → Self-hosted Docker pour Phase 2/Production
- **Collect App** : KoBoCollect Android 5.0+ (Tecno, Itel, Samsung entrée de gamme ~80-150€)
- **API REST v2** : Pour extraction automatisée des données (polling recommandé, pas webhook)

### Workflow de mise à jour
1. Modifier fichier XLSForm localement
2. Tester dans interface preview Kobo
3. Déployer version nouvelle (incrémenter version dans settings !)
4. Notifier agents de mise à jour Collect App
5. Monitorer erreurs de validation sur 48h premières
6. **Toujours ajouter des champs, jamais renommer/supprimer** (compatibilité soumissions existantes)

### Architecture d'intégration (voir `references/kobo-architecture.md`)
- Pattern recommandé : **Polling API v2** (toutes les 5min) → PostgreSQL → Fiscal Engine
- ETL Python : extraction avec pagination (max 1000/page), transformation, upsert
- Phase 1 Pilot : KoboCloud gratuit (≤10k submissions/mois)
- Phase 2 Prod : Self-hosted Docker (8GB RAM, 100GB SSD)

## Intégration avec Fiscal Engine

Les données soumises via Kobo sont automatiquement transmises au **Fiscal Engine** (`ifpb-fiscal-engine`) qui :
1. Valide la complétude des champs obligatoires
2. Applique l'algorithme de catégorisation (**côté serveur uniquement** — invisible agent)
3. Recalcule le montant IFTB + CAC depuis zéro (source de vérité)
4. Génère le rôle fiscal
5. Déclenche la notification SMS au contribuable

## Plateforme IFPB (SvelteKit + Cloudflare) — Post-Kobo

> **Cible de migration** : KoboToolbox → Plateforme propriétaire full-cloud
> **Stack** : SvelteKit 5 + HonoJS + Cloudflare Pages/D1 + PWA offline-first
> **Domaine** : `ifpb.nexio.work/*` (sous-routes par sujet)

### Architecture cible

```
┌──────────────────────────────────────────────┐
│  ifpb.nexio.work/*  (Cloudflare CDN global)   │
├─────────────┬────────────────────────────────┤
│  /docs      │ Charte, RACI, Profils, Glossaire │
│  /fiscal    │ Moteur fiscal IFTB, grille A-D    │
│  /kobo      │ Recensement (Phase 1 MVP)         │
│  /collection│ Circuit Zéro-Coulage, recouvrement│
│  /elop      │ Chaîne ELOP dépenses budgétaires  │
│  /ppp       │ Cadre PPP/BOT Loi 2015-039        │
│  /qualite   │ Anti-fraude, KPIs agents, audit   │
│  /tech      │ Architecture, API specs, infra    │
├─────────────┴────────────────────────────────┤
│  API HonoJS (/api/*)                          │
│  D1 SQLite (contribuables, paiements, relances)│
│  Service Worker (PWA offline-first)            │
└──────────────────────────────────────────────┘
```

### Transition Kobo → IA (4 phases)

| Phase | Stack | Durée | Dépendance |
|-------|-------|-------|------------|
| **1. MVP** | KoboToolbox tel quel + règles basiques | Immédiat | Aucune |
| **2. Hybrid** | Kobo collect → Webhook → Vision IA validation | 2-3 mois | Dataset photos |
| **3. IA-Assisted** | App mobile propriétaire (Flutter/SvelteKit) + IA inline | 6 mois | Phase 2 validée |
| **4. Full Autonomous** | Recensement conversationnel par agent IA | 12+ mois | Modèles fine-tunés |

### Déploiement Cloudflare

Voir skill `cloudflare-deploy` pour la procédure complète :
- Projet Pages : `ifpb-platform` sur compte Cloudflare
- Token API requis (scope Account + Pages:Edit)
- Manifest auto-généré depuis build dir `.svelte-kit/cloudflare`
- Custom domain : `ifpb.nexio.work` (DNS CNAME → pages.dev)

> **Voir aussi** : `references/kobo-documentation.md` (API v2, XLSForm syntaxe), `references/kobo-alternatives.md` (comparatif ODK/Kobo/SurveyCTO), `references/kobo-architecture.md` (stack Docker, ETL, sécurité), skill `cloudflare-deploy` (déploiement Cloudflare complet)

## Formation Agents (Support)

Points clés à couvrir lors de la formation terrain :
- Comment activer le GPS et vérifier la précision (< 10m)
- Comment photographier correctement chaque niveau (1 photo/étage)
- Comment gérer un immeuble mixte (RDC commerce + étages logement)
- Que faire face à un contribuable réticent (script légal fourni)
- Interdiction formelle de manipuler de l'argent
- **Le montant affiché est automatique et non négociable** — en cas de désaccord → recours Mairie
