Modèle de données du référentiel d'entités
Soixante-trois tables concrètes et trois socles abstraits : quinze pour le référentiel et son socle, six pour les balances comptables, quatre pour les parties, douze pour le registre investisseurs, onze pour les actifs, huit pour les emprunts et six pour la trésorerie (détentions, opérations et positions), une pour les frais. Toute structure de fonds, quelle que soit sa profondeur, s'exprime avec deux tables seulement : Entity pour les nœuds et EntityRelationship pour les arêtes typées. Le reste porte l'identité stable des entités, les identifiants, la configuration et les NAV.
Diagramme entité-relation
on_delete=PROTECT
Identité et versions
Une entité a une identité (EntityIdentity, dont l'id est le uid de ses versions) et des versions (Entity) : nom, forme juridique, LEI… à chaque date. Toute référence à une entité — arêtes, codes externes, politiques d'arrondi, NAV, balances — vise l'identité, jamais une version. Renommer une entité crée une version ; rien n'est repointé et rien n'est perdu : la version valide à la date demandée fournit les attributs. Le type d'une entité est celui de son identité et ne change jamais.
Modifier un fait passe par supersede(). Avec une date d'effet (par défaut : maintenant), l'état antérieur reste vrai jusqu'à cette date et reste lisible à une date passée. Avec la date de début de la version, c'est une correction : seul le temps de connaissance change. end_validity() met fin à un fait sans rien effacer.
Statut qualité
Un fait reçu (NAV, balance) arrive en Bronze. Son statut évolue par événements (NavQualityEvent, BalanceQualityEvent, socle abstrait core.QualityEventBase), jamais par modification du fait, qui reste immuable. Un événement vise une version : le statut d'une version est celui de son dernier événement, Bronze sans événement. Une correction ou un restatement crée une version sans événement, qui repart donc en Bronze ; l'ancienne version garde sa qualité d'alors.
Transitions (core.quality.record_quality_event) : promotion d'un cran à la fois, Bronze → Silver → Gold ; rétrogradation avec motif obligatoire ; seule la version courante reçoit un événement. Chaque événement porte son auteur (decided_by, ex. Trail), sa date et, le cas échéant, la référence du contrôle. Les événements ne se modifient ni ne se suppriment.
Catégories de relation
| Catégorie | Types | Parcourue par positions et look-through | Quantité portée | Exemple parent → child |
|---|---|---|---|---|
| OWNERSHIP actif | OWNS | Oui, seule catégorie parcourue | ownership_pct, ou units_held si le détenu est une SHARE_CLASS | HOLDCO → SPV |
| STRUCTURAL passif | COMPARTMENT_OF SHARE_CLASS_OF | Non, jamais | Aucune. Le poids d'une classe vient des NAV. | UMBRELLA → SUB_FUND SUB_FUND → SHARE_CLASS |
| SERVICE prestataire | MANAGES · INVESTMENT_MANAGER_OF SUB_INVESTMENT_MANAGER_OF · INVESTMENT_ADVISOR_OF DEPOSITARY_OF · CUSTODIAN_OF · ADMINISTRATOR_OF TRANSFER_AGENT_OF · AUDITOR_OF · DOMICILIARY_AGENT_OF PAYING_AGENT_OF · DISTRIBUTOR_OF · VALUER_OF PROPERTY_MANAGER_OF · LEGAL_ADVISER_OF | Non | Aucune | MANCO → UMBRELLA LEGAL_PERSON → SUB_FUND |
| COUNTERPARTY contrepartie financière | LENDER_TO · ACCOUNT_BANK_OF · DERIVATIVE_COUNTERPARTY_OF PRIME_BROKER_OF · BROKER_OF | Non | Aucune | LEGAL_PERSON → SPV |
| GOVERNANCE | GP_OF · DIRECTOR_OF CONDUCTING_OFFICER_OF · KEY_MANAGEMENT_OF | Non | Aucune | GP → UMBRELLA NATURAL_PERSON → MANCO |
| RELATED_PARTY | FEEDS_INTO · BENEFICIAL_OWNER_OF RELATED_PARTY_SAME_GROUP · RELATED_PARTY_SIGNIF_INFLUENCE RELATED_PARTY_KEY_MANAGEMENT · RELATED_PARTY_OTHER | Non | Aucune | feeder → master |
Contraintes en base
| Table | Contrainte |
|---|---|
| EntityIdentity | immuable : ni modification ni suppression par l'application ; entity_type fixé à la création |
| Entity | isin renseigné seulement pour une SHARE_CLASS ; currency seulement pour un fonds, une HoldCo ou un SPV (devise de référence) ou une share class (devise de libellé) — l'import des NAV, les engagements et les transactions la contrôlent quand elle est renseignée ; internal_code unique parmi les versions courantes ; short_code (code court d'affichage, ex. CLC) seulement pour une SHARE_CLASS ; une seule version courante par identité ; uid = identité |
| EntityRelationship | parent ≠ child ; quantité présente si et seulement si le type est OWNS ; units_held > 0 ; ownership_pct entre 0 et 100 ; triplet (parent, type, child) unique parmi les versions courantes |
| EntityExternalCode | un code unique par source ; un seul code par couple (entité, source) |
| FundNavPolicy | une seule politique courante par fonds |
| ShareClassNav | une ligne courante par (classe, nav_date, nav_type) ; parts > 0 ; montants ≥ 0 ; taux > 0 |
| RelationshipRule | triplet (parent_type, relationship_type, child_type) unique |
| NavImport | Journal d'import, accepté ou refusé : un fichier CSV (FILE) ou les NAV d'un lot API (API, SHARE_CLASS_NAV). sha256 unique parmi les fichiers acceptés : un même fichier n'est jamais importé deux fois, un fichier refusé peut être resoumis. file_ref = fichier d'origine chez l'émetteur (ex. AlfaWay), facultatif |
| NavImportRow | Une ligne reçue, telle que reçue (raw), avec son issue (outcome) et son motif de refus ; source_raw = ligne d'origine complète chez l'émetteur (toutes ses colonnes, non interprétées) : Acta garde seul la trace de ce qu'il a reçu, sans dépendre de l'émetteur |
| NavQualityEvent | FK vers une version de NAV en PROTECT ; journal en ajout seul (garde applicatif) |
Garde-fous applicatifs
| Règle | Où |
|---|---|
Combinaison (parent_type, type, child_type) autorisée par RelationshipRule | services.py |
| Quantité selon la catégorie et le type du détenu | validate_quantities |
| Aucun cycle à l'écriture (parcours OWNERSHIP et STRUCTURAL) | services.py |
Aucun cycle à la lecture : tableau path dans chaque CTE | graph.py |
Toute modification par supersede() (API, admin), jamais par UPDATE ; suppression = fin de validité | core/models.py |
| Le type d'une entité ne change pas d'une version à l'autre | Entity.clean |
| Arrondi résolu par devise, surchargeable par fonds | rounding_for |
Balances comptables
Balances reçues déjà contrôlées par AlfaWay et Trail (équilibre débit / crédit compris) et stockées telles quelles, en Bronze à l'arrivée ; leur statut qualité évolue ensuite par BalanceQualityEvent. Une balance est rattachée à n'importe quel nœud du graphe : fonds, compartiment, share class, SPV… Les codes de compte, de contrepartie, d'instrument et de share class des lignes sont des chaînes de la source, sans clé étrangère vers le référentiel.
on_delete=PROTECT
Contraintes des balances
| Table | Contrainte |
|---|---|
| ChartOfAccounts | code unique parmi les versions courantes ; un plan inactif n'accepte pas de nouvelle balance |
| AccountClassification | (plan, code) unique parmi les versions courantes ; parent du même plan et du même état financier, jamais soi-même (CHECK), sans cycle ; classes 1 à 7 du PCN seedées |
| BalanceSheet | hors PROVISIONAL, une balance courante par (entité, date, plan, devise, nature) ; PROVISIONAL, une par (…, sequence) ; sequence = 1 hors PROVISIONAL |
| BalanceCategoryMapping | (plan, préfixe) unique, préfixe non vide ; configuration du contrôle INV-05, modifiable en admin ; proposition LU_PCN seedée (22, 51, 19), à valider |
| BalanceLine | montants ≥ 0 ; fx_rate NULL ou > 0, jamais recalculé ; code d'instrument et sa nature renseignés ensemble ; une ligne par (balance, compte, devise, contrepartie, instrument, share class) |
Écriture et affichage
| Règle | Où |
|---|---|
Écriture uniquement par record_balance (tout ou rien) et restate_balance | balances/services.py |
Immuabilité : save() d'un existant, delete() et update() refusés, sauf la clôture de supersede() | balances/models.py |
| Statut qualité : dernier événement de la version, calculé (jamais stocké sur la balance) | core/quality.py |
| Compte en devise de l'entité : montants égaux dans les deux devises | BalanceLine.clean |
| Dernière par (plan, devise, nature) : arrêté le plus récent, puis séquence la plus haute | selectors.py |
| Une PROVISIONAL est remplacée, à la même date, par l'OFFICIAL, sinon par la séquence la plus haute ; CLOSING et AUDITED ne remplacent rien | selectors.py |
Fiche : dernières non remplacées, le reste en historique ; API : ?latest_only=true | views_web.py · views.py |
Parties
Une partie — investisseur, contrepartie, partie liée — est une entité du référentiel : une personne physique (NATURAL_PERSON), une personne morale (LEGAL_PERSON) ou un fonds (fund of funds), sans table Party. Le rôle n'est jamais un type : INVESTOR est déprécié. Une personne physique est enregistrée sous un pseudonyme (code investisseur) ; son nom, sa date de naissance, son adresse et son identifiant fiscal vivent dans PartyPersonalData, isolés et effaçables. Un identifiant fiscal ou un numéro de pièce d'identité ne va jamais dans les codes externes.
Lire les données personnelles exige la permission parties.pii_read (groupe acta.pii.read) et chaque lecture est journalisée (PersonalDataAccessLog) ; l'admin les masque sinon, et les masque toujours en liste. L'effacement supprime toutes les versions une fois la durée de conservation échue — 5 ans après la fin de la relation, paramétrable — et journalise qui, quand et pourquoi, sans le contenu (PersonalDataErasure). Le pseudonyme, le profil, les codes et tout l'historique métier restent intacts. Le statut KYC du profil est celui que publie TrustLink.
on_delete=PROTECT
| Règle | Où |
|---|---|
Personne physique : nom pseudonyme (majuscules, chiffres, . _ / -), ni forme juridique, ni juridiction, ni LEI, RCS ou ISIN | Entity.save / clean |
Catégorie selon le type : INDIVIDUAL pour une personne physique et elle seule ; INVESTMENT_FUND pour un fonds | PartyProfile.clean |
| Un profil courant par partie ; statut KYC toujours daté ; résidence pour une personne physique seulement | PartyProfile |
| Données personnelles d'une personne physique seulement ; jamais de suppression directe | PartyPersonalData |
Numéro de registre (COMPANY_REGISTRY_NUMBER) pour une personne morale seulement ; un code par (entité, source, type) | EntityExternalCode |
| Effacement refusé si une relation est ouverte, si aucune fin n'est connue ou avant l'échéance | erase_personal_data |
Rôles : prestataires, contreparties, gouvernance
Qui joue quel rôle auprès d'un fonds, d'un compartiment, d'un SPV, d'une holding ou d'une ManCo est une arête typée, jamais un indicateur sur l'entité. Sens : prestataire → entité servie, comme MANAGES. Un rôle ne porte ni pourcentage ni parts et n'est jamais parcouru par la détention. Les combinaisons viennent de RelationshipRule : une personne morale pour tous les rôles, une personne physique aussi pour la gouvernance et le conseil en investissement. Chaque rôle a ses entités servies : un compartiment n'a ni siège ni conseil (il hérite domiciliataire et administrateurs de l'umbrella), le gestionnaire immobilier sert un SPV ou une HoldCo, une share class n'est jamais servie. Une ManCo n'est jamais prestataire : si elle gère elle-même le portefeuille, c'est MANAGES ; INVESTMENT_MANAGER_OF désigne une gestion déléguée.
counterparties_of(entité, as_of, roles, inherit) rend les rôles tenus auprès d'une entité. Un rôle posé sur l'umbrella vaut pour ses compartiments (hérité), sauf si le compartiment porte le même type de rôle en propre : c'est alors le sien qui s'applique (direct). roles_of(partie, as_of) rend tous les rôles d'une partie avec l'entité servie. Les deux sont lus à date (validité et connaissance) : un changement de dépositaire au 1er juillet montre l'ancien avant, le nouveau après.
Registre investisseurs
Le registre investisseurs : comptes, engagements, transactions et leurs tables de référence. Compte, engagement et transaction ont chacun une identité stable que toute référence vise, comme les entités. OperationType (souscription, rachat, appel de fonds… : le domaine EQUITY du référentiel unique, section suivante) et TransactionComponentType (composantes d'un appel, d'une distribution, d'un prêt d'actionnaire, déductions) sont de la configuration, non bitemporelle : une nouvelle composante de waterfall est une ligne, pas une migration. Un code ne se supprime pas, il se désactive (is_active). Leurs libellés sont des données, en français et en anglais ; les autres langues de l'interface reçoivent l'anglais.
| Règle | Où |
|---|---|
| Une opération appariée (switch, transfert) augmente ou diminue les parts, jamais « optionnel » ni « aucun » | OperationType (CHECK) |
| Seul un versement de l'investisseur au fonds réduit l'engagement non appelé ; seule une distribution peut être rappelable ; une déduction a le sens « déduction » | TransactionComponentType (CHECK) |
Profil de liquidité (OPEN_ENDED, SEMI_OPEN, CLOSED_ENDED) d'un compartiment ou d'un fonds sans compartiment, et clôture de l'exercice (jour et mois, les deux ou aucun), datés ; une umbrella porte un profil pour sa seule clôture, héritée par les compartiments qui n'en déclarent pas ; fréquence de NAV attendue (nav_frequency) et seuil de variation signalée (nav_change_alert_pct) de l'écran NAV, hérités de l'umbrella champ par champ ; absent = non renseigné, jamais une valeur par défaut | FundProfile |
| Classement des sources par domaine : rang 1 = la source qui fait foi (TrustLink pour le statut KYC) ; une source non classée ou inactive passe après toutes les autres | SourcePriority · core.sources |
Compte : identité stable (InvestorAccountIdentity, cible de toute FK) et versions ; tenu par un compartiment ou un fonds sans compartiment ; l'agent de transfert et le distributeur ont leur rôle vers le fonds à la date d'ouverture (héritage de l'umbrella compris) ; numéro unique par agent de transfert | InvestorAccount |
| En direct, l'investisseur final est le détenteur inscrit ou n'est pas renseigné ; un compte nominee ou omnibus est au nom d'une personne morale ; un compte clos a une date de clôture, postérieure à l'ouverture | InvestorAccount (CHECK + clean) |
Engagement : fonds fermé ou semi-ouvert (profil de liquidité renseigné, jamais OPEN_ENDED), compte ouvert, classe du fonds du compte à la date ; montant > 0 ; une référence de bulletin par compte et par classe | InvestorCommitment |
Engagement immuable : écrit par record_commitment, modifié par amend_commitment (nouvelle version, correction ou changement daté, retour en Bronze) ; compte, classe, date et closing ne se modifient pas | investors.services |
| La clôture des comptes d'une partie fixe la fin de relation pour la conservation de ses données personnelles | account_relationship_end |
| Éligibilité : un fonds ouvert accepte les opérations LIQUID et BOTH, un fonds fermé ALTERNATIVE et BOTH, un semi-ouvert toutes ; le profil de liquidité doit être renseigné | InvestorTransaction |
| Parts signées par l'effet de l'opération (signe inversé pour une contre-passation), obligatoires une fois exécutée ; NAV, date de NAV et base de prix obligatoires pour une opération à la NAV exécutée ; engagement obligatoire pour un appel, une distribution, une égalisation, un prêt d'actionnaire | InvestorTransaction |
| Paires : un switch reste sur le même compte (ou deux comptes du même détenteur) entre deux classes ; un transfert reste sur la même classe entre deux comptes ; champs de transfert sur un transfert seulement ; une contre-passation reprend compte, classe et opération, une seule fois | InvestorTransaction |
| Statuts : PENDING → DEALT → SETTLED, tout statut → CANCELLED (final) ; chaque changement est une version qui réémet ses composantes et repart en Bronze | change_status · correct_transaction |
| Composantes : une par type et par version, montant ≥ 0 (sens donné par le type), catégorie compatible avec l'opération, part rappelable ≤ montant et datée, seulement sur un type rappelable | InvestorTransactionComponent |
Lecture. transactions rend les transactions d'un compte, d'une classe ou d'un fonds sur une période de dates d'opération, telles que connues à une date de connaissance. cash_flows additionne les flux d'un compte par famille et par sens, en devise de la classe (montant × taux), sur la date de valeur : les composantes, et le principal (famille PRINCIPAL) selon le sens déclaré par le type d'opération — le brut payé par l'investisseur pour une souscription, le net qu'il reçoit pour un rachat ou un dividende en numéraire. Les déductions y sont déjà comprises : ce sont des lignes informatives. Une transaction annulée ne compte pas ; une contre-passation compte en négatif.
Engagement non appelé — convention provisoire, à valider par LPA (elle sera paramétrable par structure) : montant engagé de la version valide à la date, moins les composantes qui réduisent l'engagement non appelé des transactions non annulées de l'engagement jusqu'à la date (date d'avis), plus les montants rappelables encore rappelables à la date. L'engagement transmis par une cession n'y entre pas en tant que tel : une cession se saisit dans les engagements — celui du vendeur est modifié à la date de cession (montant réduit, TRANSFERRED si la cession est totale), l'acheteur reçoit un nouvel engagement sur la même classe avec le numéro de closing d'origine. transferred_commitment_amount reste la trace reçue sur la transaction ; sa cohérence avec les engagements est contrôlée (C4).
Positions. Une position est reçue telle quelle (parts, NAV, valeur, et pour un fonds fermé engagé, appelé, non appelé, distribué, rappelable), une par compte, classe, date, base (date d'opération ou de règlement) et source. Le registre (register) et le portefeuille d'une partie (portfolio) retiennent, quand plusieurs sources livrent la même position, celle de la source la mieux classée (SourcePriority) ; un registre ne mélange jamais deux dates pour une classe. Le registre porte une colonne calculée « partie liée » (IAS 24) envers le fonds et sa société de gestion.
| Contrôle | Règle (non bloquante : un constat, jamais un refus) |
|---|---|
| C1 | Parts de la position = parts de la position précédente + parts des transactions exécutées de la période, sur la base de la position |
| C2 | Somme des parts du registre d'une classe = parts en circulation de la NAV officielle à la date |
| C3 | Valeur = parts × NAV par part ; NAV par part = NAV officielle à sa date (non contrôlé si la devise de la classe n'est pas renseignée) |
| C4 | Engagement non appelé reçu = calcul dérivé ; cession : l'engagement transmis se retrouve en baisse chez le vendeur et en hausse chez l'acheteur |
| C5 | Fund of funds : parts du fonds investisseur au registre = parts de son arête OWNS vers la classe |
| C6 | Aucune position non nulle après la clôture du compte ; aucun mouvement sur un compte bloqué |
Trail. Chaque engagement, transaction ou position stocké publie un événement (DomainEvent) dans la même transaction. Trail lit ces événements à son rythme, dans l'ordre ; Acta ne l'appelle jamais.
Ingestion. Les données du registre arrivent par lot (Standard Data File v1, schémas JSON dans docs/sdf/v1/) : parties, données personnelles, comptes, engagements, transactions et positions. Chaque enregistrement est traité seul, dans l'ordre : un rejet motivé n'arrête pas les autres et rien de ce qui est rejeté n'est stocké. Le même enregistrement renvoyé ne change rien ; une valeur différente crée une nouvelle version par le service d'écriture du fait. Les références (parties, fonds, classes, comptes) sont des clés naturelles résolues à l'ingestion ; aucune partie n'est créée implicitement.
Démonstration. Le jeu seed_investor_demo (données fictives, préfixe DEMOREG-) couvre un fonds UCITS ouvert à deux classes, un fonds immobilier fermé (deux closings, appels avec égalisation, distribution rappelable, carried interest, cession secondaire), un fund of funds, le groupe de la société de gestion et ses parties liées, et les prestataires (changement de dépositaire en cours d'année). Les contrôles C1 à C6 n'y relèvent aucun écart.
Détentions, opérations et positions
Ce que chaque entité détient (actifs), a en trésorerie et doit (emprunts), comment cela bouge et ce que cela vaut à une date. Fonds, holdings et SPV restent des entités du graphe ; un actif est ce qui est détenu au bout de la chaîne (immeuble, infrastructure, terrain, créance, fonds cible inconnu d'Acta). Décisions : docs/positions_model_mapping.md.
Référentiel unique des types d'opération. OperationType vit dans le socle (core) et sert les quatre catégories de bilan : domain = ASSET, CASH, BORROWING ou EQUITY (les douze types du registre investisseurs). direction donne le sens du flux de trésorerie vu du détenteur : le fonds pour EQUITY (une souscription est une entrée), le détenteur de l'actif, l'emprunteur (un tirage est une entrée), le titulaire du compte. MIXED (égalisation, prêt d'actionnaire) : le sens vient des composantes. Les champs propres au registre (parts, NAV, engagement, paires, principal) restent neutres hors EQUITY, et une transaction investisseur n'accepte qu'un type EQUITY.
Fonds propres. Le domaine EQUITY est le registre investisseurs (section précédente), aligné sans renommage : l'InvestorOperation du prompt est InvestorTransaction, et ses colonnes d'appel et de distribution (called_*, distributed_*, withholding_tax, fees) sont calculées depuis les composantes par breakdown ; une composante sans colonne reste lisible dans other. Sa jambe de trésorerie est une CashOperation. Correspondance complète : docs/positions_model_mapping.md, § 7.
Actifs. Un actif (Asset) a une identité stable et des versions, comme une entité ; sa fiche immobilière ou de créance est une table bitemporelle à part, une version courante par actif. Qui le détient, et dans quelle proportion, n'est jamais sur le graphe : ce sont ses positions, datées, par détenteur direct (SPV, HoldCo ou fonds). Le type d'entité ASSET est déprécié. Un actif indivisible (immeuble, terrain, infrastructure) se détient en quantité 1 et en pourcentage ; un actif divisible (créance, parts, actions) en quantité. Opérations, valorisations (à 100 %) et positions sont des faits reçus, immuables : un changement de statut, une valorisation finalisée ou une correction est une nouvelle version au même valid_from ; une position corrigée repart en Bronze.
| Règle | Où |
|---|---|
| Hors EQUITY : ni NAV, ni engagement, ni paire, ni principal ; effet sur la quantité seulement pour ASSET (acquisition +, cession −, échange au signe reçu) et BORROWING (montant tiré) ; MIXED réservé à EQUITY | OperationType (CHECK) |
| Une transaction investisseur prend un type du domaine EQUITY | InvestorTransaction |
| Type d'actif : classe d'actif et fiche complémentaire portée (immobilier, dette, aucune) ; configuration, un type se désactive, ne se supprime pas | AssetType |
Banque teneuse de compte : rôle ACCOUNT_BANK_OF (contrepartie) d'une personne morale envers un fonds, un compartiment, un SPV ou une HoldCo | RelationshipRule |
| La HoldCo et le SPV portent une devise de référence, comme un fonds | Entity (CHECK + clean) |
| Actif : code unique parmi les versions courantes ; type actif à la création ; fiche immobilière ou de créance seulement si le type la porte ; taux fixe renseigné ou indice pour un taux variable | Asset · AssetRealEstate · AssetDebt |
| Nouvelle entité de type ASSET refusée ; aucune règle ne mène plus à une entité ASSET | Entity.clean · RelationshipRule |
| Opération : type du domaine ASSET ; détenteur SPV, HoldCo ou fonds constitué à la date ; actif existant à la date ; pourcentage (indivisible) ou quantité (divisible), jamais les deux, signé par l'effet du type (inversé pour une contre-passation), obligatoire une fois exécutée ; taux 1 dans la devise du détenteur ; parties = personnes ou fonds | AssetOperation |
| Valorisation à 100 %, externe = évaluateur nommé ; une courante par (actif, date, évaluateur, source) ; DRAFT → FINAL une seule fois | AssetValuation · finalise_valuation |
| Position : une courante par (détenteur, actif, date, nature, numéro, source) ; indivisible = quantité 1 et pourcentage ; devise de l'actif ; dans la devise du détenteur, taux 1 et montants égaux ; valorisation du même actif à la date | AssetPosition |
Emprunts. Un emprunt (Borrowing) a un emprunteur (SPV, HoldCo ou fonds) et exactement un prêteur : une entité du groupe pour un prêt intragroupe, sinon une partie (banque, investisseur, vendeur, bailleur). Un prêt intragroupe est enregistré une fois : borrowings_of le rend au passif de l'emprunteur et à l'actif du prêteur. Une banque, un bailleur ou un financeur de fonds doit porter le rôle LENDER_TO envers l'emprunteur à la signature. Tranches, opérations (montant positif, effet sur le tiré donné par le type : tirage et intérêts capitalisés +, remboursements et abandon −) et positions (tiré, non tiré, intérêts courus, covenants constatés, tels que reçus) suivent le même principe que les actifs.
| Règle | Où |
|---|---|
Exactement un prêteur (INV-03) ; jamais l'emprunteur lui-même ; prêt intragroupe ⇔ prêteur entité du groupe (SPV, HoldCo, fonds) ; banque, crédit-bail, financement de fonds : rôle LENDER_TO à la signature | Borrowing (CHECK + clean) |
| Code unique parmi les versions courantes ; montant engagé > 0 ; taux fixe renseigné ou indice pour un taux variable ; échéance après la signature ; nominal et échéance de couverture seulement avec un instrument | Borrowing (CHECK) |
| Tranche : code unique par emprunt ; opération et position sur une tranche de l'emprunt, dans la devise de la tranche, sinon de l'emprunt | BorrowingTranche · BorrowingOperation · BorrowingPosition |
| Opération : type du domaine BORROWING, montant > 0 ; contre-passation sur le même emprunt, la même tranche et le même type, une seule fois | BorrowingOperation |
| Position : une courante par (emprunt, tranche, date, nature, numéro, source) ; dans la devise de l'emprunteur, taux 1 et montants égaux | BorrowingPosition |
Trésorerie. Un compte (CashAccount) appartient à un SPV, une HoldCo ou un fonds, dans une banque qui porte le rôle ACCOUNT_BANK_OF envers lui ; sa référence est masquée, jamais un IBAN complet. Un compte DSRA ou nanti désigne l'emprunt qu'il garantit. Une opération de trésorerie est une ligne de relevé, au montant signé (+ entrée, − sortie, selon le sens de son type) ; sa jambe est au plus une opération d'actif, d'emprunt ou d'investisseur du titulaire. Une ligne chargée d'un relevé reste non rapprochée jusqu'à ce qu'une nouvelle version lui donne sa jambe.
| Règle | Où |
|---|---|
Compte : code unique ; une référence par banque ; jamais un IBAN complet (clé mod 97 valide refusée) ; restreint ⇔ restriction nommée ; emprunt lié seulement pour un DSRA ou un compte nanti, emprunt du titulaire ; banque avec le rôle ACCOUNT_BANK_OF à l'ouverture | CashAccount (CHECK + clean) |
| Opération : type du domaine CASH ; compte ouvert à la date de valeur, dans sa devise ; montant non nul, signé selon le sens du type (inversé pour une contre-passation) | CashOperation |
| Jambe : au plus une (INV-03) ; non rapprochée ⇔ aucune jambe ; l'opération d'actif est du titulaire, l'emprunt a le titulaire pour emprunteur ou prêteur, la transaction investisseur est au registre du fonds titulaire | CashOperation (CHECK + clean) |
| Position : solde signé, une courante par (compte, date, nature, numéro, source) ; dans la devise du titulaire, taux 1 et montants égaux | CashPosition |
Contrôles INV-01 à INV-06. Services Python non bloquants (holdings/controls.py), lus par Trail : ils rendent des constats (code, clés, attendu, constaté, écart, tolérance), sans rien refuser ni corriger. INV-01 roll-forward (quantité ou % d'un actif, solde d'un compte, tiré d'un emprunt, parts du registre) ; INV-02 jambe de trésorerie de toute opération réglée qui fait circuler de l'argent, et liste des lignes non rapprochées ; INV-03 un prêteur, une jambe (CHECK en base, relus) ; INV-04 Σ % d'un actif ≤ 100 ; INV-05 rapprochement avec la balance par catégorie, par la table BalanceCategoryMapping (préfixe de compte → immobilisations, trésorerie, dettes financières, prêts intragroupe à part) ; INV-06 taux reçu pour toute position en devise étrangère, concordant par devise (pas de table de cours). Pour un objet et une date, une seule position est lue : la source la mieux classée, puis la nature la plus définitive. API : /api/v1/controls/holdings/. Actifs, emprunts, trésorerie et types d'opération se lisent par l'API (/api/v1/assets/, borrowings/, cash-accounts/, operation-types/…), en lecture seule.
API et ingestion. Tout se lit par l'API, en lecture seule : /api/v1/assets/, asset-operations/, asset-valuations/, asset-positions/, borrowings/, borrowing-tranches/, borrowing-operations/, borrowing-positions/, entities/{uid}/borrowings/, cash-accounts/, cash-operations/, cash-positions/, operation-types/ — fiches à as_of / known_at, faits à known_at. Tout entre par le Standard Data File v1 (POST /api/v1/ingest/) : fiches identifiées par leur code, faits par (source, source_record_id), écriture par les services ci-dessus.
Frais
Chantier en cours (prompt frais) : les frais d'un fonds, du référentiel des types aux montants reçus avec la NAV. Acta ne calcule pas la NAV : il stocke les frais reçus de l'administrateur et les contrôle contre les conditions contractuelles. Décisions : docs/fees_model_mapping.md.
Types de frais. FeeType est une table de configuration à code, comme OperationType : ajouter un type de frais est une ligne. Chaque type a une famille (prestataire, performance, taxe, autre), une assiette (% de la NAV, montant fixe, % des engagements, % du capital investi) et, pour un prestataire, le rôle qu'il porte envers le fonds (DEPOSITARY_OF, ADMINISTRATOR_OF…), qui servira à contrôler le prestataire d'une condition de frais. Une taxe (taxe d'abonnement) n'a pas de prestataire. Treize types seedés.
Conditions contractuelles. FeeSchedule (bitemporelle) porte les conditions d'un type de frais sur une share class ou sur un fonds — umbrella, compartiment ou fonds autonome —, exactement l'un des deux (CHECK), une condition courante par portée et par type. Taux annuel en % pour une assiette en pourcentage (ou des paliers), montant fixe annuel sinon ; minimum et maximum annuels, périodicité, convention de décompte (ACT/365 par défaut). Le prestataire doit porter le rôle du type de frais envers le fonds (héritage de l'umbrella compris) à la date de validité ; un frais de prestataire en nomme un, une taxe jamais. Condition applicable à une classe : celle de la classe, sinon de son fonds, sinon de l'umbrella (resolve_fee_schedule). Les paliers (FeeScheduleTier) appartiennent à une version : contigus, de 0 à l'infini, recopiés sur chaque nouvelle version ; la condition dit comment ils s'appliquent (tier_method : chaque tranche à son taux, ou le taux du palier atteint sur toute l'assiette). Une modification crée une version (correction, ou changement à une date) ; portée et type ne changent jamais.
Frais reçus avec la NAV. FeeAccrual est un fait daté immuable, tel que reçu de l'administrateur (même mécanique que les positions) : pour une share class — ou pour un compartiment ou un fonds autonome quand le frais n'est pas ventilé par classe —, une date et un type de NAV, un type de frais et une source, la provision de la période (négative pour une reprise) et le cumul de l'exercice (vide s'il n'est pas reçu, jamais recalculé), dans la devise de la classe. valid_from = date de NAV ; une correction est une nouvelle version au même valid_from, qui repart en Bronze (statut qualité par FeeAccrualQualityEvent). Plusieurs sources coexistent (SourcePriority, domaine FEE_ACCRUAL). Les frais arrivent par le Standard Data File (type FEE_ACCRUAL) ; leur paiement effectif est une opération de trésorerie FUND_FEE_PAYMENT. Seuls les frais d'une NAV officielle seront contrôlés (FEE-01 à FEE-04).
Paiements de frais. FeePayment (fait immuable, tel que reçu du rapport de frais) enregistre ce qui a été payé : type de frais, portée (classe ou fonds), date de paiement, période couverte, montant (négatif pour un remboursement), prestataire payé — qui doit porter le rôle du type de frais envers le fonds à la date. Il est relié, dès qu'elle est connue, à l'opération de trésorerie FUND_FEE_PAYMENT qui l'a réglé, sur un compte du fonds ; une opération peut régler plusieurs frais, et une ligne ainsi rattachée n'apparaît plus parmi les opérations non rapprochées. Provisionné contre payé : contrôles du lot 4.
Contrôles. Non bloquants, ils ne lisent que les frais d'une NAV officielle, une source par date (SourcePriority). FEE-01 : la provision de la période égale le montant annuel de la condition applicable (assiette × taux, paliers, montant fixe, borné par le minimum et le maximum) × la fraction d'année depuis la provision précédente, selon la convention de décompte ; l'assiette est la NAV officielle de la classe, ou la somme de celles des classes du fonds. FEE-02 : le cumul de l'exercice égale le cumul précédent plus la provision, et repart de la provision après la clôture de l'exercice (celle du fonds, sinon de son umbrella). FEE-03 : à la date d'une balance, la somme des cumuls de l'exercice reçus (frais du fonds et de ses classes, convertis en devise du fonds au rapport des montants de la NAV) égale les charges des comptes rattachés au type de frais (FeeAccountMapping : préfixe de compte → type, le plus long l'emporte, configuration sans seed). FEE-05 : le prestataire d'une condition porte encore son rôle à la date. FEE-06 : les paiements de frais rattachés à une opération FUND_FEE_PAYMENT en couvrent le montant. Une donnée manquante donne « non disponible », jamais une valeur par défaut.
Calculs à la demande, jamais stockés. FEE-04, frais courants : somme des provisions de la période (commission de performance exclue) sur la NAV moyenne de la période — moyenne simple des dates de NAV ou pondérée par le nombre de jours où chaque NAV vaut, au choix —, et ce ratio annualisé ; pour une classe, les frais du fonds lui sont imputés à son poids à chaque date de NAV. Rapport de frais : par portée et type, provisionné et payé sur la période, provisionné, payé et restant dû à la date de fin.
Valeurs des énumérations
EntityType
- UMBRELLA
- STANDALONE_FUND
- SUB_FUND
- SHARE_CLASS
- HOLDCO
- SPV
- GP
- MANCO
- INVESTOR
- NATURAL_PERSON
- LEGAL_PERSON
- ASSET
- OTHER
RelationshipType
- COMPARTMENT_OF
- SHARE_CLASS_OF
- OWNS
- MANAGES
- GP_OF
- FEEDS_INTO
- BENEFICIAL_OWNER_OF
- INVESTMENT_MANAGER_OF
- SUB_INVESTMENT_MANAGER_OF
- INVESTMENT_ADVISOR_OF
- DEPOSITARY_OF
- CUSTODIAN_OF
- ADMINISTRATOR_OF
- TRANSFER_AGENT_OF
- AUDITOR_OF
- DOMICILIARY_AGENT_OF
- PAYING_AGENT_OF
- DISTRIBUTOR_OF
- VALUER_OF
- PROPERTY_MANAGER_OF
- LEGAL_ADVISER_OF
- LENDER_TO
- ACCOUNT_BANK_OF
- DERIVATIVE_COUNTERPARTY_OF
- PRIME_BROKER_OF
- BROKER_OF
- DIRECTOR_OF
- CONDUCTING_OFFICER_OF
- KEY_MANAGEMENT_OF
- RELATED_PARTY_SAME_GROUP
- RELATED_PARTY_SIGNIF_INFLUENCE
- RELATED_PARTY_KEY_MANAGEMENT
- RELATED_PARTY_OTHER
LegalForm
- SICAV
- SICAF
- FCP
- SCSp
- SCS
- SA
- SARL
- RAIF
- OTHER
NavType
- OFFICIAL
- ESTIMATED
- TECHNICAL
FxMethod
- MULTIPLY
- DIVIDE
RoundingMode
- HALF_UP
- HALF_EVEN
- DOWN
- UP
AccountingFramework
- LUX_GAAP
- IT_GAAP
- IFRS
- OTHER
FinancialStatement
- BALANCE_SHEET
- PROFIT_AND_LOSS
- OFF_BALANCE
BalanceNature
- PROVISIONAL
- OFFICIAL
- CLOSING
- AUDITED
InstrumentCodeType
- ISIN
- INTERNAL
- OTHER
ExternalCodeType
- SOURCE_CODE
- TA_INVESTOR_CODE
- COMPANY_REGISTRY_NUMBER
PartyCategory
- INDIVIDUAL
- BANK
- INSURANCE
- PENSION_FUND
- INVESTMENT_FUND
- ASSET_MANAGER
- SOVEREIGN
- PUBLIC_BODY
- FAMILY_OFFICE
- CORPORATE
- NON_PROFIT
- OTHER
KycStatus
- VALIDATED
- PENDING
- EXPIRED
- REJECTED
QualityStatus
- BRONZE
- SILVER
- GOLD
LiquidityProfile
- OPEN_ENDED
- SEMI_OPEN
- CLOSED_ENDED
NavFrequency
- DAILY
- WEEKLY
- MONTHLY
- QUARTERLY
- SEMI_ANNUAL
- ANNUAL
SourceDomain
- PARTY_KYC
- PARTY_PROFILE
- INVESTOR_REGISTER
- ASSET_POSITION
- BORROWING_POSITION
- CASH_POSITION
- FEE_ACCRUAL
OperationDomain
- ASSET
- CASH
- BORROWING
- EQUITY
CashFlowDirection
- INFLOW
- OUTFLOW
- NEUTRAL
- MIXED
FundCategory
- LIQUID
- ALTERNATIVE
- BOTH
UnitsEffect
- INCREASE
- DECREASE
- NONE
- OPTIONAL_INCREASE
- OPTIONAL_DECREASE
- EITHER
Pairing
- NONE
- SWITCH
- TRANSFER
ComponentFamily
- CALL
- DISTRIBUTION
- LOAN
- DEDUCTION
CashDirection
- INVESTOR_TO_FUND
- FUND_TO_INVESTOR
- DEDUCTION
AccountType
- DIRECT
- NOMINEE
- OMNIBUS
InvestorClassification
- RETAIL
- PROFESSIONAL
- ELIGIBLE_COUNTERPARTY
- WELL_INFORMED
AccountStatus
- ACTIVE
- BLOCKED
- CLOSED
CommitmentType
- CASH
- IN_KIND
CommitmentStatus
- ACTIVE
- TRANSFERRED
- RELEASED
- CLOSED
TransactionStatus
- PENDING
- DEALT
- SETTLED
- CANCELLED
PricingBasis
- FORWARD
- HISTORIC
TransferType
- SECONDARY_SALE
- SUCCESSION
- DONATION
- INTRA_GROUP
- OTHER
PositionBasis
- TRADE_DATE
- SETTLEMENT_DATE
AssetClass
- REAL_ESTATE
- INFRASTRUCTURE
- PRIVATE_EQUITY
- LAND
- DIRECT_LENDING
- MEZZANINE_DEBT
- DISTRESSED_DEBT
- TARGET_FUND
- OTHER
AssetExtension
- REAL_ESTATE
- DEBT
- NONE
RiskProfile
- CORE
- CORE_PLUS
- VALUE_ADD
- OPPORTUNISTIC
- DEVELOPMENT
AssetStatus
- ACTIVE
- DISPOSED
- IN_LIQUIDATION
PropertyType
- OFFICE
- RETAIL
- HOTEL
- LOGISTICS
- RESIDENTIAL
- MIXED
- OTHER
ValuationMethod
- DCF
- COMPARABLE
- INCOME_CAPITALISATION
- COST
- RESIDUAL
ValuationStatus
- DRAFT
- FINAL
InterestRateType
- FIXED
- FLOATING
Seniority
- SENIOR
- UNITRANCHE
- MEZZANINE
- JUNIOR
- SUBORDINATED
BorrowingCategory
- BANK_LOAN
- MEZZANINE_LOAN
- INTRAGROUP_LOAN
- INVESTOR_LOAN
- FUND_FINANCING
- VENDOR_LOAN
- FINANCE_LEASE
FacilityType
- TERM
- REVOLVING
- BRIDGE
- SUBSCRIPTION_LINE
- NAV_FACILITY
- OTHER
AmortisationProfile
- BULLET
- AMORTISING
- BALLOON
- OTHER
SecurityType
- MORTGAGE
- SHARE_PLEDGE
- ACCOUNT_PLEDGE
- RECEIVABLES_PLEDGE
- UNCALLED_COMMITMENTS
- GUARANTEE
- UNSECURED
- OTHER
RecourseType
- FULL_RECOURSE
- LIMITED_RECOURSE
- NON_RECOURSE
HedgeInstrument
- NONE
- INTEREST_RATE_SWAP
- CAP
- COLLAR
- OTHER
TrancheType
- ACQUISITION
- CAPEX
- OTHER
CashType
- CURRENT_ACCOUNT
- TERM_DEPOSIT
- MONEY_MARKET_FUND
- OTHER_EQUIVALENT
RestrictionType
- NONE
- DSRA
- ESCROW
- PLEDGED
- TENANT_DEPOSIT
MatchStatus
- UNMATCHED
- MATCHED
- PARTIAL
HoldingCategory
- FIXED_ASSETS
- CASH
- FINANCIAL_DEBT
- INTRAGROUP_LOANS
- EXCLUDED
FeeFamily
- PROVIDER
- PERFORMANCE
- TAX
- OTHER
CalculationBasis
- NAV_PCT
- FIXED_AMOUNT
- COMMITMENT_PCT
- INVESTED_CAPITAL_PCT
FeeFrequency
- DAILY
- MONTHLY
- QUARTERLY
- ANNUAL
DayCountConvention
- ACT_365
- ACT_360
- ACT_ACT
- 30_360
TierMethod
- MARGINAL
- WHOLE
NavAverage
- SIMPLE
- TIME_WEIGHTED
Intervalles bitemporels semi-ouverts [from, to) ; une borne _to à NULL est ouverte. Les FK vers une entité visent son identité (EntityIdentity), jamais une version. Source : entities/models.py, balances/models.py, parties/models.py, core/models.py, assets/models.py, borrowings/models.py, cash/models.py, fees/models.py.