Calculs¶
La fonctionnalité Calculs (Admin > Métamodèle > onglet Calculs) vous permet de définir des formules qui calculent automatiquement des valeurs de champs lorsque les fiches sont sauvegardées. C'est un outil puissant pour dériver des métriques, des scores et des agrégations à partir de vos données d'architecture.
Comment ça marche¶
- Un administrateur définit une formule ciblant un type de fiche et un champ spécifiques
- Lorsqu'une fiche de ce type est créée ou mise à jour, la formule s'exécute automatiquement
- Le résultat est écrit dans le champ cible
- Le champ cible est marqué en lecture seule sur la page de détail de la fiche (les utilisateurs voient un badge « calculé »)
Création d'un calcul¶
Cliquez sur + Nouveau calcul et configurez :
| Champ | Description |
|---|---|
| Nom | Nom descriptif du calcul |
| Type cible | Le type de fiche auquel ce calcul s'applique |
| Champ cible | Le champ où le résultat est stocké |
| Formule | L'expression à évaluer (voir la syntaxe ci-dessous) |
| Ordre d'exécution | Ordre d'exécution lorsque plusieurs calculs existent pour le même type (le plus petit s'exécute en premier) |
| Actif | Activer ou désactiver le calcul |
Syntaxe des formules¶
Les formules utilisent un langage d'expression sécurisé et isolé. Vous pouvez référencer les champs de la fiche courante, les fiches liées et enfants, la fiche parente et les dates du cycle de vie.
Utilisez la clé du champ, pas son libellé
Les champs sont référencés par leur clé, généralement en camelCase (costTotalAnnual),
et non par le libellé affiché sur la fiche (Coût annuel total). Un nom qui n'existe pas
est résolu en None, et toute opération arithmétique sur None échoue avec une
erreur d'évaluation générique.
Vous trouverez la clé dans Admin > Métamodèle > (type de fiche) en ouvrant le champ
et en lisant sa Clé. Plus simple : dans l'éditeur de formules, les puces situées sous
la zone de saisie listent data.<clé> pour chaque champ du type sélectionné, et la saisie
de data. ouvre l'autocomplétion.
Variables de contexte¶
| Variable | Description | Exemple |
|---|---|---|
data.<cléDuChamp> |
N'importe quel champ personnalisé de la fiche courante, par sa clé | data.costTotalAnnual |
data.name, data.description, data.status, data.subtype, data.approval_status, data.reference |
Propriétés intégrées de la fiche | data.subtype |
data.lifecycle.<phase> |
Dates du cycle de vie, où la phase vaut plan, phaseIn, active, phaseOut ou endOfLife |
data.lifecycle.endOfLife |
relations.<cléDuTypeDeRelation> |
Tableau des fiches liées par ce type de relation, dans les deux sens | relations.relAppToITC |
relation_count.<cléDuTypeDeRelation> |
Nombre de fiches liées par ce type de relation | relation_count.relAppToITC |
children |
Tableau des fiches enfants directes (types hiérarchiques) | SUM(PLUCK(children, "attributes.costTotalAnnual")) |
children_count |
Nombre d'enfants directs | children_count |
parent |
La fiche parente (objet avec id, name, type, subtype, attributes), ou None pour une fiche racine |
IF(parent, parent.attributes.businessCriticality, data.businessCriticality) |
hierarchy_level |
Profondeur de la fiche actuelle dans sa hiérarchie parent-enfant (1 = racine, non plafonnée). 1 pour les types de fiches non hiérarchiques |
hierarchy_level * 10 |
La clé du type de relation est celle affichée dans Admin > Métamodèle > Relations, par
exemple relAppToITC ou relInitiativeToApp. Le sens n'a pas d'importance : une fiche
retrouve un type de relation sous la même clé qu'elle se trouve à l'extrémité source ou
cible. Les fiches archivées sont exclues de relations, relation_count et children.
Lire les champs d'une fiche liée¶
Chaque élément de relations.<cléDuTypeDeRelation> et de children est un objet
enveloppe, et non directement les champs de la fiche liée :
{
"id": "8f1c…",
"name": "NexaCore ERP",
"type": "Application",
"attributes": { "costTotalAnnual": 45000, "businessCriticality": "missionCritical" },
"rel_attributes": { "costTotalAnnual": 12000 }
}
attributescontient les valeurs des champs propres à la fiche liée.rel_attributescontient les valeurs stockées sur le lien lui-même, si le type de relation définit un schéma d'attributs. Par exemple,relAppToITCporte son proprecostTotalAnnual, ce qui permet d'enregistrer ce qu'une application dépense pour un composant IT donné.
C'est important pour PLUCK et FILTER, qui prennent un chemin de clé et ont donc besoin
du préfixe attributes. pour atteindre un champ :
# Somme du coût annuel des composants IT utilisés par cette application
SUM(PLUCK(relations.relAppToITC, "attributes.costTotalAnnual"))
# Somme du coût enregistré sur chaque lien application-composant
SUM(PLUCK(relations.relAppToITC, "rel_attributes.costTotalAnnual"))
Extraire une clé nue comme "costTotalAnnual" la cherche sur l'objet enveloppe, ne trouve
rien et renvoie une liste de None, que SUM restitue sous la forme 0. Une formule sur
les relations qui renvoie obstinément 0 est presque toujours un préfixe attributes.
manquant.
Gérer les valeurs vides¶
Un champ sans valeur est résolu en None, et None dans une expression arithmétique
déclenche une erreur. Encadrez avec COALESCE chaque champ susceptible d'être vide :
COALESCE(data.licenseCost, 0) + COALESCE(data.supportCost, 0) + COALESCE(data.infraCost, 0)
SUM, AVG, MIN et MAX ignorent déjà les entrées non numériques : elles n'ont pas
besoin de protection.
Données PPM sur les fiches Initiative¶
La racine ppm expose au moteur de formules les lignes de budget et de coût du module PPM,
ventilées entre capex et opex et réparties par exercice — un détail que les attributs
data.costBudget / data.costActual consolidés sur la fiche ne peuvent pas donner.
| Variable | Description |
|---|---|
ppm.capexBudget, ppm.opexBudget, ppm.totalBudget |
Budget prévu, d'après les lignes de budget PPM |
ppm.capexPlanned, ppm.opexPlanned, ppm.totalPlanned |
Montants prévus sur les lignes de coût PPM |
ppm.capexActual, ppm.opexActual, ppm.totalActual |
Réalisés sur les lignes de coût PPM |
ppm.byYear |
Les mêmes neuf mesures par exercice, sous forme de liste {year, capexBudget, …} |
ppm.currentFiscalYear |
L'exercice dans lequel tombe la date du jour |
ppm.unscheduledPlanned, ppm.unscheduledActual |
Lignes de coût sans date : comptées dans les totaux, rattachées à aucun exercice |
byYear est une liste et non un objet indexé par année, afin que les fonctions FILTER et
PLUCK habituelles fonctionnent dessus :
# Budget capex total, tous exercices confondus
ppm.capexBudget
# Budget capex du seul exercice en cours
SUM(PLUCK(FILTER(ppm.byYear, "year", ppm.currentFiscalYear), "capexBudget"))
# Budget capex de chaque initiative liée à cette fiche
SUM(PLUCK(relations.relInitiativeToApp, "ppm.capexBudget"))
Quelques règles à connaître :
- Un exercice porte le nom de l'année civile où il se termine. Avec un début d'exercice en octobre, le 15 oct. 2025 relève de l'exercice 2026 et le 30 sept. 2025 de l'exercice 2025. Avec le début en janvier par défaut, l'exercice correspond simplement à l'année civile.
- Les lignes de budget et de coût tirent leur exercice de sources différentes. Une ligne de budget porte l'exercice que vous avez saisi ; celui d'une ligne de coût est déduit de sa date. Si votre organisation nomme les exercices d'après leur année de début, les deux divergeront.
total*est la somme de toutes les lignes, pascapex + opex. Une ligne dont la catégorie n'est ni l'une ni l'autre (issue d'un import, par exemple) compte quand même dans le total.- Une fiche qui n'est pas une Initiative lit toutes les mesures
ppmà0avec unbyYearvide : une formule sur le mauvais type de fiche renvoie zéro au lieu d'échouer.
Modifier une ligne de budget ou de coût PPM relance les calculs de l'initiative, si bien que tout ce qui en dérive est mis à jour immédiatement. Les fiches qui lisent les données PPM d'une autre fiche via une relation ne sont pas rafraîchies.
Fonctions intégrées¶
| Fonction | Description | Exemple |
|---|---|---|
IF(condition, valeur_vraie, valeur_fausse) |
Logique conditionnelle. Seule la branche retenue est évaluée | IF(data.businessCriticality == "missionCritical", 100, 25) |
SUM(tableau) |
Somme des valeurs numériques | SUM(PLUCK(relations.relAppToITC, "attributes.costTotalAnnual")) |
AVG(tableau) |
Moyenne des valeurs numériques | AVG(PLUCK(children, "attributes.numberOfUsers")) |
MIN(tableau) |
Valeur minimale | MIN(PLUCK(relations.relAppToITC, "attributes.costTotalAnnual")) |
MAX(tableau) |
Valeur maximale | MAX(PLUCK(relations.relAppToITC, "attributes.costTotalAnnual")) |
COUNT(tableau) |
Nombre d'éléments | COUNT(relations.relAppToInterface) |
ROUND(valeur, decimales) |
Arrondir un nombre | ROUND(data.costTotalAnnual / 12, 2) |
ABS(valeur) |
Valeur absolue | ABS(data.budgetVariance) |
LN(valeur) |
Logarithme naturel. Renvoie None pour zéro, les valeurs négatives et les entrées non numériques |
LN(data.numberOfUsers) |
COALESCE(a, b, ...) |
Première valeur non nulle | COALESCE(data.customScore, 0) |
LOWER(texte) |
Texte en minuscules | LOWER(data.productName) |
UPPER(texte) |
Texte en majuscules | UPPER(data.subtype) |
CONCAT(a, b, ...) |
Concaténer des chaînes | CONCAT(data.name, " (", data.subtype, ")") |
CONTAINS(texte, recherche) |
Vérifier si le texte contient une sous-chaîne | CONTAINS(data.description, "legacy") |
PLUCK(tableau, chemin) |
Extraire un chemin de clé de chaque élément | PLUCK(relations.relAppToITC, "attributes.costTotalAnnual") |
FILTER(tableau, chemin, valeur) |
Conserver les éléments dont le chemin de clé vaut une valeur donnée | FILTER(relations.relOrgToApp, "attributes.hostingType", "onPremise") |
MAP_SCORE(valeur, correspondance) |
Associer des valeurs catégorielles à des scores | MAP_SCORE(data.businessCriticality, {"missionCritical": 3, "businessCritical": 2}) |
Les fonctions Python sûres len, str, int, float, bool, abs, round, min,
max et sum sont également disponibles, tout comme les opérateurs et comparaisons usuels.
Exemples de formules¶
Somme de plusieurs champs de coût de la même fiche :
COALESCE(data.licenseCost, 0) + COALESCE(data.supportCost, 0) + COALESCE(data.infraCost, 0)
Coût annuel total des composants IT utilisés par une application :
SUM(PLUCK(relations.relAppToITC, "attributes.costTotalAnnual"))
Score de risque basé sur la criticité :
IF(data.businessCriticality == "missionCritical", 100, IF(data.businessCriticality == "businessCritical", 75, 25))
Nombre d'interfaces liées :
relation_count.relAppToInterface
Nombre d'applications on-premise dans une organisation :
COUNT(FILTER(relations.relOrgToApp, "attributes.hostingType", "onPremise"))
Remonter un coût depuis les fiches enfants :
SUM(PLUCK(children, "attributes.costTotalAnnual"))
Placement TIME Model (Tolerate / Invest / Migrate / Eliminate), le même exemple que vous verrez dans le panneau Formula Reference dans Admin → Métamodèle → Calculations lors de la création d'un nouveau calcul. Type cible = Application, champ cible = timeModel. Suppose que vous avez ajouté deux champs single_select nommés businessFit et technicalFit avec les options excellent, adequate, insufficient, unreasonable :
# ── TIME Model (Tolerate / Invest / Migrate / Eliminate) ──
# Assumes single_select fields: businessFit and technicalFit
# with options: excellent, adequate, insufficient, unreasonable.
#
# Scoring: Map each dimension to 1-4 numeric scale.
# Business Fit = Y-axis (how well does it serve the business?)
# Technical Fit = X-axis (how healthy is the technology?)
#
# Quadrant logic (threshold at score 2.5):
# Invest = high business + high technical
# Migrate = high business + low technical
# Tolerate = low business + high technical
# Eliminate = low business + low technical
#
bf = MAP_SCORE(data.businessFit, {"excellent": 4, "adequate": 3, "insufficient": 2, "unreasonable": 1})
tf = MAP_SCORE(data.technicalFit, {"excellent": 4, "adequate": 3, "insufficient": 2, "unreasonable": 1})
IF(bf is None or tf is None, None, IF(bf >= 2.5, IF(tf >= 2.5, "invest", "migrate"), IF(tf >= 2.5, "tolerate", "eliminate")))
Comme le montre cet exemple, une formule peut s'étendre sur plusieurs lignes. Une ligne de
la forme nom = expression stocke une valeur intermédiaire réutilisable par les lignes
suivantes, et c'est la valeur de la dernière ligne qui est écrite dans le champ cible.
C'est aussi l'exemple opérationnel référencé par le Guide du débutant EA.
Les commentaires sont pris en charge avec # :
# Calculer le score de risque pondéré
IF(data.businessCriticality == "missionCritical", data.riskScore * 2, data.riskScore)
Valider et tester¶
L'éditeur de formules propose deux vérifications distinctes, au comportement différent :
- Valider exécute la formule sur une fiche synthétique. Chaque champ numérique reçoit la
valeur fictive
1, et la fiche n'a ni relations, ni enfants, ni données propres de parent. Cela confirme que la syntaxe est correcte et que les noms utilisés existent, mais une formule qui agrègerelationsouchildrenaffichera toujours0ou un résultat vide à cet endroit. C'est normal et ne signale pas une formule défectueuse. - Tester, disponible sur un calcul enregistré, s'exécute sur une fiche réelle de votre choix. C'est l'option à utiliser dès qu'il est question de relations, d'enfants ou de la fiche parente. Rien n'est écrit sur la fiche, le résultat vous est seulement affiché.
Lire les résultats d'une exécution manuelle¶
Exécuter un calcul depuis la liste l'évalue pour toutes les fiches du type cible et rend compte de ce qui s'est passé, pas seulement du nombre de fiches traitées. Voir le détail dans le bandeau de résultat ouvre la ventilation :
- Un bloc par calcul, avec le nombre de fiches calculées sans erreur et le nombre en échec. Tous les calculs actifs du type s'exécutent ensemble : c'est donc ce qui indique lequel est en cause.
- Une ligne par erreur distincte, avec le nombre de fiches concernées. Une formule fausse l'est de la même façon partout ; vingt et un échecs correspondent donc généralement à une seule correction, pas à vingt et une.
- Les fiches elles-mêmes, listées sous chaque erreur et cliquables, pour ouvrir l'une d'elles et examiner les données en cause. Dix au maximum sont listées par erreur ; au-delà, le reste est indiqué sous forme de nombre.
Copier le rapport place l'ensemble de la ventilation dans le presse-papiers, en texte brut.
La pastille de statut dans la liste des calculs reflète la même exécution : rouge dès qu'une fiche a échoué, verte seulement si toutes ont été calculées.
Quand les calculs s'exécutent¶
Les calculs d'une fiche sont réévalués lorsque :
- la fiche est créée ou sauvegardée ;
- une relation touchant la fiche est créée, modifiée ou supprimée (les deux extrémités de la relation sont recalculées) ;
- la fiche change de parent, ce qui recalcule tout son sous-arbre ;
- vous exécutez le calcul manuellement depuis la liste, ce qui l'évalue pour toutes les fiches du type cible et sauvegarde les résultats.
Ils ne sont pas réévalués lorsqu'une autre fiche lue par la formule est modifiée. Si vous changez un coût sur un composant IT, une application qui l'agrège ne bougera pas tant que cette application n'est pas sauvegardée, qu'une de ses relations ne change pas, ou que le calcul n'est pas exécuté pour le type. Pour les agrégats portant sur des données maintenues par d'autres, exécutez le calcul périodiquement ou après un import en masse.
Remarque
Il en va de même pour les valeurs dérivées de parent et de hierarchy_level : elles
sont actualisées lors d'un changement de parent et lors d'une exécution manuelle, pas à
chaque modification de la fiche parente. Protégez toujours une référence à parent avec
IF(parent, …) afin que les fiches racines, où parent vaut None, ne génèrent pas
d'erreur.
Ordre d'exécution¶
Lorsque plusieurs calculs ciblent le même type de fiche, ils s'exécutent dans l'ordre spécifié par leur valeur d'ordre d'exécution. C'est important lorsqu'un calcul dépend du résultat d'un autre : définissez la dépendance pour qu'elle s'exécute en premier (numéro inférieur).
Turbo EA refuse un ensemble de calculs qui formerait un cycle, par exemple un champ A calculé à partir d'un champ B alors que B est calculé à partir de A.