Aller au contenu

Brief Freelance — Extraction & Consolidation de Données pour l'Outil d'Analyse

Version : 1.0 | Date : 10 avril 2026 | Contact : équipe Produit Bricks


1. Contexte entreprise

Bricks.co

Bricks est une plateforme de financement participatif immobilier régulée par l'AMF. Les investisseurs particuliers financent des projets immobiliers portés par des professionnels (marchands de biens, promoteurs, exploitants, bailleurs).

Le problème qu'on résout

Aujourd'hui, l'analyse des projets repose principalement sur un seul décideur → goulot d'étranglement à ~50 projets/mois. Notre objectif est de passer à 500+ projets/an avec une qualité constante, en automatisant l'extraction, l'enrichissement et l'analyse des dossiers.

L'outil d'analyse

On construit un système d'analyse automatisée en 3 couches :

1. DONNÉES       Documents → Base structurée et requêtable
2. INTELLIGENCE   Agents spécialisés → Analyse par dimension (financier, juridique, réputation…)
3. DÉCISION       Règles métier → Score + Recommandation (go / no-go)

La mission de ce brief concerne la couche 1 : l'extraction et la consolidation des données.


2. Périmètre de la mission

Ce qu'on attend

Développer le module d'extraction de données qui, à partir de documents OCR-isés et indexés dans un RAG, extrait et consolide toutes les données structurées nécessaires au fonctionnement des agents d'analyse en aval.

Ce module est le socle de données de tout le système. Tous les agents spécialisés (financier, juridique, réputation, marché) s'appuient sur son output.

Architecture cible : Scatter-Gather

┌──────────────────────────────────────────────────────────────────────┐
│                          ORCHESTRATEUR                                │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │                    SCATTER (parallèle)                          │  │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐          │  │
│  │  │ Extract  │ │ Extract  │ │ Extract  │ │ Extract  │  ...×15  │  │
│  │  │ Prix Acq │ │ Surface  │ │ Travaux  │ │ Porteur  │          │  │
│  │  └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘          │  │
│  └───────┼────────────┼────────────┼────────────┼─────────────────┘  │
│          ▼            ▼            ▼            ▼                    │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │                    GATHER (consolidation)                       │  │
│  │  - Fusion des résultats                                         │  │
│  │  - Calcul des données dérivées (code, pas LLM)                 │  │
│  │  - Détection de conflits                                        │  │
│  │  - Validation de complétude                                     │  │
│  └────────────────────────────────────────────────────────────────┘  │
│                              │                                       │
│                              ▼                                       │
│                    ConsolidatedData (JSON)                            │
└──────────────────────────────────────────────────────────────────────┘

Principe clé : le LLM EXTRAIT, le CODE CALCULE. Les données dérivées (ratios, prix/m², taux) sont calculées en Python/JS après consolidation, jamais par le LLM.


3. Les données à extraire

3.1. Groupes d'extraction (~15 groupes parallèles)

Les données sont regroupées par corrélation (souvent dans le même document/section). Chaque groupe = 1 requête LLM ciblée.

# Groupe Données Source principale
1 financial_acquisition Prix d'acquisition, prix/m² marché Compromis, BP
2 financial_works Coût travaux BP, Devis travaux
3 financial_resale Prix de revente prévu BP
4 financial_funding Apport personnel, montant emprunt BP, Offre de prêt
5 financial_guarantee Valeur garantie, type de garantie Compromis, Garantie
6 project_timeline Durée du projet (mois) BP
7 property_basics Surface habitable, adresse Compromis, Cadastre
8 property_rental Loyer mensuel HT, charges annuelles BP, Bail
9 property_units Lots pré-vendus, total lots BP, Réservations
10 carrier_profile Années d'expérience, opérations réussies CV, Bricks DB
11 carrier_risk Litige actif (oui/non), incidents bancaires Pappers, Bricks DB
12 carrier_bricks Performance historique sur Bricks Bricks DB
13 company_identity Ancienneté de la société Statuts, Kbis
14 company_financials Résultat net N/N-1/N-2, dette totale, capitaux propres Bilans comptables
15 project_type Typologie du projet Bricks DB

3.2. Output Schema (objet JSON consolidé)

Chaque donnée extraite suit ce format :

{
  "value": "<valeur extraite ou null>",
  "source": {
    "document": "compromis.pdf",
    "page": 3,
    "excerpt": "Le prix de vente est fixé à la somme de QUATRE CENT CINQUANTE MILLE EUROS (450 000€)"
  }
}

L'objet complet :

{
  "projectId": "string",
  "extractionDate": "date",
  "version": "1.0",

  "financial": {
    "acquisitionPrice": { "value": "number | null", "currency": "EUR", "source": {} },
    "marketPricePerSqm": { "value": "number | null", "source": {} },
    "worksCost": { "value": "number | null", "currency": "EUR", "source": {} },
    "plannedResalePrice": { "value": "number | null", "currency": "EUR", "source": {} },
    "personalContribution": { "value": "number | null", "currency": "EUR", "source": {} },
    "loanAmount": { "value": "number | null", "currency": "EUR", "source": {} },
    "guaranteeValue": { "value": "number | null", "currency": "EUR", "source": {} },
    "guaranteeType": { "value": "string | null", "source": {} }
  },

  "project": {
    "durationMonths": { "value": "number | null", "source": {} },
    "projectType": { "value": "marchand_de_bien | locatif | exploitation | promotion", "source": "bricks_db" }
  },

  "property": {
    "livingArea": { "value": "number | null", "unit": "m2", "source": {} },
    "address": { "value": "string | null", "source": {} },
    "monthlyRentExcludingTax": { "value": "number | null", "currency": "EUR", "source": {} },
    "annualCharges": { "value": "number | null", "currency": "EUR", "source": {} },
    "presoldUnits": { "value": "number | null", "source": {} },
    "totalUnits": { "value": "number | null", "source": {} }
  },

  "carrier": {
    "experienceYears": { "value": "number | null", "source": {} },
    "successfulOperations": { "value": "number | null", "source": {} },
    "hasActiveLitigation": { "value": "boolean | null", "details": "string | null", "source": {} },
    "bankingIncidents": { "value": "boolean | null", "count": "number | null", "source": {} },
    "bricksPerformance": { "value": "object | null", "source": "bricks_db" }
  },

  "company": {
    "yearsOfExistence": { "value": "number | null", "source": {} },
    "netResultYear1": { "value": "number | null", "currency": "EUR", "source": {} },
    "netResultYear2": { "value": "number | null", "currency": "EUR", "source": {} },
    "netResultYear3": { "value": "number | null", "currency": "EUR", "source": {} },
    "totalDebt": { "value": "number | null", "currency": "EUR", "source": {} },
    "equity": { "value": "number | null", "currency": "EUR", "source": {} }
  },

  "metadata": {
    "sources": [{ "document": "string", "type": "string", "extractionDate": "date" }],
    "completeness": {
      "totalFields": "number",
      "foundFields": "number",
      "missingFields": ["string"],
      "rate": "number (0-1)"
    },
    "conflicts": [{
      "field": "string",
      "values": [{ "value": "any", "source": {} }],
      "resolution": "string"
    }]
  },

  "confidence": "number (0-1)"
}

3.3. Données dérivées (calculées en code post-extraction)

Ces données ne sont jamais extraites par le LLM. Elles sont calculées par du code Python/JS à partir des données brutes :

Donnée dérivée Formule
acquisitionPricePerSqm acquisitionPrice / livingArea
worksRatio worksCost / acquisitionPrice
debtRatio totalDebt / equity
preMarketingRate presoldUnits / totalUnits
grossMargin (plannedResalePrice - acquisitionPrice - worksCost) / acquisitionPrice

4. Inputs disponibles

4.1. Base RAG (documents indexés)

L'OCR et l'indexation sont gérées en amont (hors périmètre de cette mission). Le freelance reçoit des documents déjà OCR-isés et indexés dans une base vectorielle interrogeable.

Types de documents présents dans le RAG :

Type Exemples Format
Documents légaux KBIS, Statuts, RCS PDF natif
Documents financiers Bilans, Liasses fiscales, Relevés bancaires PDF, Excel
Documents immobiliers Compromis, Actes, Diagnostics, Plans PDF scan
Projections Business Plan, Tableaux prévisionnels Excel, PDF
Supports Présentations, Plaquettes PDF

4.2. Données Bricks (DB interne)

Certaines données viennent directement de notre base de données interne : - Fiche projet : type de projet (marchand de bien, locatif, promotion, exploitation) - Historique porteur : projets passés sur Bricks, performance, incidents

4.3. Interface d'interrogation

Outil Description
query_rag(question, filters) Interroge le RAG avec une question en langage naturel, retourne les chunks pertinents avec métadonnées (document, page, score)
get_bricks_project_data(project_id) Récupère les données projet depuis la DB Bricks
get_bricks_carrier_data(carrier_id) Récupère les données porteur depuis la DB Bricks

5. Règles métier de l'extraction

5.1. Principes fondamentaux

  1. Sourçage obligatoire — Chaque donnée extraite doit pointer vers son document source, sa page et un extrait textuel.
  2. Null explicite — Si une donnée n'est pas trouvée, retourner null avec source.status = "not_found" et la raison.
  3. Pas d'interprétation — L'agent extrait, il ne juge pas. Pas de calculs, pas de scores, pas d'alertes.
  4. Normalisation — Les formats doivent être normalisés (ex: "450K€" → 450000, "4 500 €/m²" → 4500).

5.2. Gestion des conflits

Quand une même donnée apparaît dans plusieurs documents avec des valeurs différentes :

Priorité des sources (du plus fiable au moins fiable) : 1. Acte de vente (juridiquement engageant) 2. Compromis de vente (engagement) 3. Kbis / Statuts (officiel) 4. Bilans certifiés (comptable) 5. Business plan (déclaratif) 6. Échanges / Conversations (informatif)

Comportement attendu : Retenir la valeur de la source la plus fiable ET logger toutes les valeurs dans metadata.conflicts.

5.3. Cas edge

Situation Comportement attendu
Donnée dans plusieurs docs Appliquer la priorité des sources
Valeurs contradictoires Logger dans conflicts + appliquer priorité
Donnée manquante null + source.status = "not_found"
Format ambigu Normaliser ("450K€"450000)
Donnée partielle Extraire ce qui est disponible + flag
Document illisible source.status = "extraction_failed"

6. Exemples concrets

Exemple 1 — Extraction réussie

Compromis indique prix = 450 000€, surface = 120m²

{
  "financial": {
    "acquisitionPrice": {
      "value": 450000,
      "currency": "EUR",
      "source": { "document": "compromis.pdf", "page": 3, "excerpt": "…fixé à 450 000€…" }
    }
  },
  "property": {
    "livingArea": {
      "value": 120,
      "unit": "m2",
      "source": { "document": "compromis.pdf", "page": 2, "excerpt": "…surface habitable de 120 m²…" }
    }
  }
}

Puis le code calcule : acquisitionPricePerSqm = 450000 / 120 = 3750

Exemple 2 — Conflit de données

Compromis dit 450 000€, BP dit 480 000€

{
  "financial": {
    "acquisitionPrice": {
      "value": 450000,
      "currency": "EUR",
      "source": { "document": "compromis.pdf", "page": 3 }
    }
  },
  "metadata": {
    "conflicts": [{
      "field": "financial.acquisitionPrice",
      "values": [
        { "value": 450000, "source": { "document": "compromis.pdf" } },
        { "value": 480000, "source": { "document": "business_plan.pdf" } }
      ],
      "resolution": "compromis prioritaire (document juridique)"
    }]
  }
}

Exemple 3 — Donnée manquante

Pas de bilan comptable fourni :

{
  "company": {
    "netResultYear1": {
      "value": null,
      "source": { "status": "not_found", "reason": "Bilan N non fourni dans le dossier" }
    }
  },
  "metadata": {
    "completeness": {
      "missingFields": ["company.netResultYear1", "company.netResultYear2", "company.netResultYear3"]
    }
  }
}

7. Spécifications techniques

7.1. Prompts d'extraction

Chaque groupe d'extraction a son propre prompt ciblé. Exemple pour financial_acquisition :

Tu es un extracteur de données spécialisé.

## Ta mission UNIQUE
Extraire les données suivantes à partir des documents fournis :
- Prix d'acquisition (en euros)
- Prix du marché au m² (si mentionné)

## Règles
- Retourne UNIQUEMENT les données demandées en JSON
- Source chaque donnée : document, page, extrait exact
- Si la donnée n'est pas trouvée, retourne null avec "not_found" comme source
- Si plusieurs valeurs contradictoires, retourne toutes avec leurs sources
- Ne fais AUCUNE interprétation ou calcul non demandé

Le freelance devra écrire les 15 prompts d'extraction (un par groupe), les tester et les itérer.

7.2. Stack technique

Composant Choix / Contrainte
Langage Python (préféré) ou TypeScript
LLM extraction Claude (Anthropic) — via API
Base vectorielle (RAG) À définir (Weaviate, Pinecone, ou Qdrant)
Orchestration Au choix du freelance (asyncio, CrewAI, LangGraph, ou custom)
Output JSON conforme au schema ci-dessus
Tests Golden dataset fourni (vrais dossiers anonymisés)

7.3. Contraintes non-fonctionnelles

Contrainte Cible
Temps d'exécution total < 30 secondes (extractions parallèles)
Temps par groupe d'extraction < 5 secondes
Taux de complétude > 85% des champs renseignés
Taux de conflits < 10% des champs
Précision d'extraction > 95% (vs vérification humaine)

8. Livrables attendus

# Livrable Description
1 Module d'extraction Code complet : orchestrateur scatter-gather + 15 prompts d'extraction
2 Module de consolidation Code de fusion, résolution de conflits, validation de complétude
3 Calcul des données dérivées Fonctions Python/JS pour les ratios et métriques calculées
4 Tests Suite de tests sur le golden dataset, avec métriques de précision
5 Documentation technique Architecture, choix techniques, guide d'ajout d'un nouveau groupe d'extraction
6 Itérations prompts Historique des itérations sur les prompts avec métriques d'amélioration

9. Ce qui est hors périmètre

Pour être clair sur les frontières :

Hors périmètre Responsable
OCR / Extraction documentaire brute Équipe interne (Alban)
Indexation RAG (chunking, vectorisation) Équipe interne (Alban)
Agents d'analyse (financier, juridique, réputation…) Équipe interne (Romain)
Règles métier et scoring Équipe interne (Nicolas)
Interface utilisateur / Front-end Équipe interne
Infrastructure / Déploiement Équipe interne

Le freelance reçoit un RAG fonctionnel en input et produit un JSON structuré en output. Les agents d'analyse en aval consomment ce JSON.


10. Contexte aval : qui consomme les données ?

Pour comprendre l'importance de chaque donnée, voici les agents en aval et ce qu'ils utilisent :

Agent Données consommées Ce qu'il en fait
Agent Financier Prix acquisition, travaux, revente, loyers, financement Challenge les projections vs données marché, produit 3 scénarios
Agent Juridique Identités société/porteur, adresses, montants, garanties Cross-check inter-documents, détection risques et incohérences
Agent Réputation SIREN, noms dirigeants, adresses Investigation OSINT, KYC, e-réputation
Agent Document Manquant metadata.completeness.missingFields Identifie les pièces à demander au porteur
Scoring Toutes les données + données dérivées Calcul du score global et recommandation go/no-go

Conséquence : une erreur dans l'extraction se propage dans toutes les analyses. La précision est critique.


11. Déroulé et organisation

Jalons proposés

Jalon Contenu Durée estimée
Kick-off Prise de contexte, accès aux outils, golden dataset 0.5j
V0 — Proof of concept 3 groupes d'extraction fonctionnels (financial_acquisition, property_basics, company_financials) + consolidation basique 3-5j
V1 — Couverture complète 15 groupes d'extraction, gestion des conflits, calcul dérivés, tests 5-8j
V1.1 — Itérations Amélioration des prompts sur la base des résultats du golden dataset, edge cases 3-5j

Durée totale estimée : 12-18 jours

Modalités

  • Rythme : Point hebdomadaire (30 min) + communication async Slack/Linear
  • Accès : API LLM (Anthropic), RAG, golden dataset, repo Git privé
  • Feedback : Tests sur dossiers réels anonymisés, revue par l'équipe interne

12. Profil recherché

Compétences requises

  • LLM Engineering — Expérience solide en prompt engineering, structured extraction, function calling
  • Python (ou TypeScript) — Développement propre, testé, documenté
  • RAG — Compréhension des bases vectorielles et de la recherche sémantique
  • Orchestration async — Capacité à paralléliser les appels LLM efficacement

Compétences appréciées

  • Connaissance du domaine immobilier ou financier
  • Expérience avec des pipelines d'extraction documentaire (IDP)
  • Familiarité avec les APIs Anthropic (Claude), LangChain/LangGraph, ou CrewAI

13. Critères de succès

La mission est réussie si :

  1. Le module extrait > 85% des champs sur un dossier type
  2. La précision d'extraction est > 95% sur le golden dataset
  3. Le temps d'exécution total est < 30 secondes par dossier
  4. Les conflits sont correctement détectés et résolus
  5. Les agents en aval peuvent consommer le JSON sans adaptation
  6. Le code est maintenable et extensible (ajout d'un nouveau groupe = ajout d'un prompt + config)

Document rédigé le 10 avril 2026 — équipe Produit Bricks