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 |
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¶
- Sourçage obligatoire — Chaque donnée extraite doit pointer vers son document source, sa page et un extrait textuel.
- Null explicite — Si une donnée n'est pas trouvée, retourner
nullavecsource.status = "not_found"et la raison. - Pas d'interprétation — L'agent extrait, il ne juge pas. Pas de calculs, pas de scores, pas d'alertes.
- 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 :
- Le module extrait > 85% des champs sur un dossier type
- La précision d'extraction est > 95% sur le golden dataset
- Le temps d'exécution total est < 30 secondes par dossier
- Les conflits sont correctement détectés et résolus
- Les agents en aval peuvent consommer le JSON sans adaptation
- 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