Introduction
Les compteurs continus dans IBM Maximo sont au cœur de nombreux processus de maintenance préventive : kilométrage, heures de fonctionnement, cycles, consommation, nombre d’opérations, etc.
Maximo permet d’associer ces compteurs aussi bien aux équipements qu’aux emplacements. Les données principales sont notamment stockées dans :
ASSETMETER
METERREADING
LOCATIONMETER
LOCMETERREADING
Pour un compteur continu, Maximo propose plusieurs méthodes de calcul de la consommation moyenne :
- ALL
- SLIDING-DAYS
- SLIDING-READINGS
- STATIC
Dans certaines situations plus complexes, notamment lors de la modification d’un historique, de la saisie rétroactive, d’un RESET ou de la modification du dernier relevé de type DELTA, des comportements différents peuvent être observés entre ASSETMETER et LOCATIONMETER.
Cet article présente l’analyse technique réalisée, les différents problèmes identifiés et une stratégie de correction permettant de conserver au maximum le comportement standard IBM.
1. Rappel sur les compteurs continus Maximo
Un compteur continu représente une grandeur évoluant dans le temps : kilométrage, heures moteur, cycles, quantité produite, etc.
Pour un équipement, les informations courantes sont principalement portées par :
ASSETMETER
et son historique par :
METERREADING
Pour un emplacement :
LOCATIONMETER
et :
LOCMETERREADING
Maximo permet également la propagation de relevés d’un emplacement vers les équipements configurés pour accepter le rolldown depuis l’emplacement.
2. Les méthodes de calcul de moyenne
Pour les compteurs continus, IBM expose quatre méthodes principales.
ALL
La moyenne est basée sur l’ensemble de l’historique.
Schématiquement :
Somme des DELTA
────────────────────────────
Durée totale de l'historique
SLIDING-DAYS
Seuls les relevés appartenant à une fenêtre de N jours sont utilisés.
Exemple générique :
AVGCALCMETHOD = SLIDING-DAYS
SLIDINGWINDOWSIZE = N jours
Le principe est :
Somme des DELTA de la fenêtre
──────────────────────────────
Durée de la fenêtre
SLIDING-READINGS
La fenêtre n’est plus définie en jours mais en nombre de relevés.
STATIC
Aucun recalcul dynamique :
AVERAGE = valeur configurée
3. Pourquoi AVERAGE est important
Le champ :
ASSETMETER.AVERAGE
ou :
LOCATIONMETER.AVERAGE
ne sert pas uniquement à afficher une information à l’utilisateur.
Cette moyenne peut également participer à l’estimation de la consommation future et donc indirectement à la planification des maintenances basées sur compteur.
La modification d’un relevé historique doit donc être considérée comme un traitement différent de l’ajout d’un nouveau relevé en fin de chronologie.
4. Premier problème : saisie hors ordre chronologique
Prenons un historique saisi dans cet ordre :
| Ordre de saisie | Date | Reading |
|---|---|---|
| 1 | Date A | 100 |
| 2 | Date C | 200 |
| 3 | Date B | 150 |
avec :
Date A < Date B < Date C
Chronologiquement, l’historique réel devient :
Date A Date B Date C
100 → 150 → 200
▲
│
insertion rétroactive
Le relevé 150 est ajouté après le relevé 200 , mais sa date est antérieure.
Le système doit donc potentiellement recalculer :
READING
DELTA
PREVIOUSREADING
PREVIOUSREADINGDATE
LIFETODATE
AVERAGE
et pas uniquement enregistrer une nouvelle ligne.
5. Le piège ALL / SLIDING-DAYS
L’un des comportements analysés concerne une logique comparable à :
if (allReadings <= slidingWindowSize) {
calculateAverageALL();
}
Le problème conceptuel est immédiatement visible.
allReadings représente :
un nombre de relevés
alors que, pour SLIDING-DAYS, slidingWindowSize représente :
un nombre de jours
Exemple générique :
allReadings = quelques relevés
slidingWindowSize = plusieurs centaines de jours
La comparaison peut être vraie mathématiquement, mais elle compare deux grandeurs qui n’ont pas la même unité.
Pour une configuration :
AVGCALCMETHOD = SLIDING-DAYS
la fenêtre doit être déterminée temporellement.
Schéma :
date du dernier relevé
│
▼
──────────────────────────────●
N jours │
◄─────────────────────────────┤
fenêtre │
6. Deuxième problème : skipAverageRefresh
L’analyse du standard Maximo montre également l’importance d’un indicateur interne :
skipAverageRefresh
Son rôle est de permettre à Maximo d’éviter certains recalculs successifs de moyenne pendant le traitement d’une chaîne de relevés.
Schématiquement :
Traitement des relevés
│
▼
skipAverageRefresh = true
│
▼
refreshAverage()
│
├── true → ne pas recalculer
│
└── false → recalculer
Dans certains scénarios de modification historique ou de RESET , ce flag peut empêcher le recalcul final attendu.
Une première correction consiste donc à restaurer correctement cet état avant le recalcul approprié.
7. Architecture retenue
L’objectif d’architecture est volontairement conservateur :
AssetMeter → STANDARD IBM
LocationMeter → STANDARD IBM
et seules les classes de gestion des historiques sont personnalisées :
MeterReadingSet > NYRMeterReadingSet
LocMeterReadingSet > NYRLocMeterReadingSet
Architecture :
STANDARD IBM
┌────────────────────┐
│ DeployedMeter │
└─────────┬──────────┘
│
┌───────────┴───────────┐
▼ ▼
AssetMeter LocationMeter
STANDARD STANDARD
│ │
▼ ▼
MeterReadingSet LocMeterReadingSet
│ │
▼ ▼
NYRMeterReadingSet NYRLocMeterReadingSet
CUSTOM CUSTOM
Cette approche permet :
- de limiter la surface de personnalisation ;
- de conserver le maximum de code IBM ;
- de faciliter les montées de version ;
- de réduire les risques de régression ;
- d’isoler précisément le correctif.
8. Correction des insertions rétroactives
Deux méthodes sont particulièrement importantes dans les ReadingSet:
updateReadingsAfterNewReading()
et :
updateReadingsAfterReadingUpdate()
La première intervient lors de l’ajout d’un relevé nécessitant la mise à jour des relevés suivants.
La seconde intervient lorsqu’un relevé existant est modifié.
Le principe retenu est :
Traitement standard IBM
│
▼
recalcul des relevés/deltas suivants
│
▼
correction NYR du contexte de moyenne
│
▼
recalcul SLIDING-DAYS
avec des logs permettant notamment de suivre :
READING
DELTA
READINGTYPE
READINGDATE
LASTREADING
PREVIOUSREADING
AVERAGE
AVGCALCMETHOD
SLIDINGWINDOWSIZE
skipAverageRefresh
9. Troisième problème : RESET
Un autre cas particulier est :
READINGTYPE = RESET
Le RESET introduit une rupture dans la continuité du compteur.
Exemple générique :
Valeur A
│
▼
Valeur B
│
▼
Valeur C RESET
│
▼
Valeur D
│
▼
Valeur E
On ne peut pas simplement considérer :
Valeur D - Valeur B
comme une consommation réelle.
Le RESET modifie la manière dont les deltas et le LIFETODATE doivent être interprétés.
Le problème rencontré était qu’après certains RESET , les relevés étaient correctement traités mais la moyenne n’était pas nécessairement rafraîchie.
La correction apportée aux ReadingSet doit donc garantir :
RESET
│
▼
traitement IBM du RESET
│
▼
recalcul des relevés impactés
│
▼
réactivation du refresh de moyenne
│
▼
AVERAGE cohérente
sans réimplémenter toute la logique métier de DeployedMeter.
10. Quatrième problème : modification du dernier relevé DELTA
C’est l’un des cas les plus subtils rencontrés.
Considérons un historique générique :
| Date | Reading | Delta | Type |
|---|---|---|---|
| T1 | 100 | 100 | ACTUAL |
| T2 | 150 | 50 | ACTUAL |
| T3 | 200 | 50 | ACTUAL |
| T4 | 500 | 300 | ACTUAL |
| T5 | 520 | 20 | DELTA |
| T6 | 600 | 80 | ACTUAL |
| T7 | 650 | 50 | DELTA |
| T8 | 800 | 150 | DELTA |
| T9 | 700 | 0 | RESET |
| T10 | 820 | 120 | ACTUAL |
| T11 | 900 | 80 | DELTA |
Les historiques METERREADING et LOCMETERREADING peuvent être strictement identiques.
Pourtant, après modification du dernier DELTA, on peut observer un écart du type :
ASSETMETER.AVERAGE = valeur A
LOCATIONMETER.AVERAGE = valeur B
avec :
valeur A ≠ valeur B
11. Diagnostic : $locmeterReadingsToUpd est vide
Le log Maximo permet d’identifier le problème.
Le standard construit une requête comparable à :
relationName=$locmeterReadingsToUpd
readingdate >= <date du relevé modifié>
AND meterreadingid != <id du relevé modifié>
Lorsque le relevé modifié est précisément le dernier relevé, le résultat est :
This mboset has 0 records
Pourquoi ?
Parce que Maximo cherche :
les relevés postérieurs ou égaux à la date modifiée
SAUF le relevé lui-même
et qu’aucun autre relevé n’existe après lui.
Schéma :
historique
────────────────────────────────────────► temps
ACTUAL RESET ACTUAL DELTA
│ │ │ │
●───────────●───────────●──────────●
▲
│
relevé modifié
│
▼
aucun relevé après
12. Conséquence sur updateReadingsAfterReadingUpdate()
Le standard suit conceptuellement ce flux :
getReadings()
│
▼
$locmeterReadingsToUpd
│
▼
isEmpty() ?
│
├── NON → updateReadingsAfterReadingUpdate()
│
└── OUI → ne pas appeler la méthode
Dans ce scénario :
$locmeterReadingsToUpd = EMPTY
donc :
NYRLocMeterReadingSet.updateReadingsAfterReadingUpdate()
n’est jamais appelée.
C’est précisément le trou fonctionnel identifié.
13. Pourquoi isEmpty() n’a pas été surchargée
La première idée naturelle serait :
@Override
public boolean isEmpty() {
...
}
Mais MboSet.isEmpty() est une méthode final.
Le compilateur retourne alors :
Cannot override the final method from MboSet
Il faut donc trouver un autre point d’interception sans modifier LocationMeter .
14. Utilisation de setOrderBy() comme hook technique
Pendant la construction de $locmeterReadingsToUpd, appelle :
setOrderBy("location,readingdate asc");
Cette méthode est appelée avant le test :
isEmpty()
La solution consiste donc à surcharger :
@Override
public void setOrderBy(String orderBy) throws MXException
tout en conservant d’abord le comportement standard :
super.setOrderBy(orderBy);
setOrderBy() n’est naturellement pas une méthode de calcul de moyenne.
Elle sert normalement à définir :
ORDER BY ...
sur le MboSet.
Elle est ici utilisée comme hook technique ciblé, car elle reste appelée même lorsque le MboSet qui sera ensuite retourné est vide.
15. Sécurisation du hook
Il serait dangereux d’exécuter le correctif sur chaque appel à setOrderBy().
Plusieurs conditions sont donc vérifiées :
"location,readingdate asc"
puis :
deployedMeter.getIsReadingUpdate()
puis :
AVGCALCMETHOD = "SLIDING-DAYS"
puis :
super.isEmpty()
et enfin :
READINGTYPE = "DELTA"
Le correctif ne s’applique donc que dans ce scénario :
LOCATIONMETER
+
modification de relevé
+
SLIDING-DAYS
+
$locmeterReadingsToUpd vide
+
dernier relevé = DELTA
16. Le correctif minimal
Le cœur du correctif reste volontairement limité :
deployedMeter.addModifiedReadingsForAvgCalc(modifiedReading);
deployedMeter.setSkipAverageRefresh(false);
Lorsque :
$locmeterReadingsToUpd = EMPTY
le relevé modifié ne passe jamais par :
updateReadingsAfterReadingUpdate()
Il n’est donc pas enregistré dans le contexte utilisé par le calcul de moyenne.
On le fait explicitement avec :
addModifiedReadingsForAvgCalc(modifiedReading);
Puis :
setSkipAverageRefresh(false);
permet au :
refreshAverage()
standard IBM qui suit de continuer son traitement.
Le principe est donc :
fournir au standard IBM le contexte qui lui manque plutôt que remplacer entièrement son calcul.
17. Flux avant correction
Modification dernier LOCMETERREADING DELTA
│
▼
LocationMeter.getReadings()
│
▼
$locmeterReadingsToUpd
│
▼
EMPTY
│
▼
updateReadingsAfterReadingUpdate()
NON APPELÉE
│
▼
relevé modifié non enregistré
│
▼
refreshAverage()
│
▼
contexte incomplet
│
▼
LOCATIONMETER.AVERAGE
incorrecte
18. Flux après correction
Modification dernier LOCMETERREADING DELTA
│
▼
LocationMeter.getReadings()
│
▼
NYRLocMeterReadingSet.setOrderBy()
│
▼
$locmeterReadingsToUpd
EMPTY
│
▼
dernier relevé DELTA ?
│
OUI
│
▼
addModifiedReadingsForAvgCalc(modifiedReading)
│
▼
skipAverageRefresh = false
│
▼
retour LocationMeter
│
▼
refreshAverage()
STANDARD IBM
│
▼
AVERAGE recalculée
19. Exemple de logs génériques
Les logs permettent de confirmer le scénario sans exposer de données réelles :
NYRMETER#LOCMETERREADINGSET;
method=setOrderBy-emptyLastDelta;
meter=METER_X;
location=LOCATION_X;
site=SITE_X;
correctionRequired=true;
reason=EMPTY_UPDATE_SET_LAST_DELTA;
currentReadingId=<ID>;
readingDate=<DATE>;
readingType=DELTA;
reading=<CURRENT_READING>;
delta=<CURRENT_DELTA>;
lastReading=<LAST_READING>;
previousReading=<PREVIOUS_READING>;
lifeToDate=<LIFETODATE>;
averageBefore=<AVERAGE>;
skipBefore=false
puis :
modifiedReadingRegistered=true;
averageBeforeIBMRefresh=<AVERAGE>;
skipAfter=false
Ces logs sont particulièrement utiles pour distinguer :
historique incorrect
de :
historique correct mais contexte de recalcul incomplet
20. Pourquoi ASSETMETER et LOCATIONMETER peuvent diverger
Deux historiques identiques :
METERREADING
LOCMETERREADING
ne garantissent pas automatiquement :
ASSETMETER.AVERAGE = LOCATIONMETER.AVERAGE
Le résultat dépend également :
- du contexte DeployedMeter ;
- des relevés considérés comme modifiés ;
- des traitements de propagation ;
- de skipAverageRefresh ;
- de la méthode standard appelée ;
- du contenu du MboSet intermédiaire ;
- du type ACTUAL, DELTA ou RESET.
Dans le cas analysé, les historiques étaient identiques : l’origine de l’écart se situait donc dans le cycle de recalcul, pas dans les données persistées elles-mêmes.
21. Attention aux relevés ayant la même date
Un autre point à surveiller concerne plusieurs relevés très rapprochés.
Il est important de conserver les timestamps complets :
YYYY-MM-DD HH24:MI:SS
et pas seulement :
YYYY-MM-DD HH24:MI
Une différence de quelques secondes peut modifier le résultat d’une clause :
READINGDATE >= ...
Exemple :
T1 = 10:00:10
T2 = 10:00:35
Même si les deux relevés apparaissent à l’écran comme :
10:00
ils ne sont pas équivalents pour les requêtes SQL de Maximo.
22. Requête SQL générique de diagnostic
Une requête utile consiste à comparer les historiques Asset et Location.
SELECT
mr.readingdate,
mr.metername,
mr.reading AS asset_reading,
mr.delta AS asset_delta,
mr.rollover AS asset_rollover,
mr.readingtype AS asset_readingtype,
lmr.reading AS loc_reading,
lmr.delta AS loc_delta,
lmr.rollover AS loc_rollover,
lmr.readingtype AS loc_readingtype,
CASE
WHEN NVL(mr.reading, -999999) = NVL(lmr.reading, -999999)
AND NVL(mr.delta, -999999) = NVL(lmr.delta, -999999)
AND NVL(mr.rollover, -999999) = NVL(lmr.rollover, -999999)
AND NVL(mr.readingtype, '#') = NVL(lmr.readingtype, '#')
THEN 'IDENTIQUE'
ELSE 'DIFFERENT'
END AS diagnostic
FROM meterreading mr
JOIN locmeterreading lmr
ON lmr.readingdate = mr.readingdate
AND lmr.metername = mr.metername
WHERE mr.assetnum = :assetnum
AND mr.siteid = :siteid
AND lmr.location = :location
AND lmr.siteid = :siteid
ORDER BY mr.readingdate DESC;
Cette requête reste un outil de diagnostic et doit être adaptée si plusieurs relevés partagent exactement le même timestamp.
23. Checklist de diagnostic
Lorsque AVERAGE semble incorrecte, il est recommandé de contrôler dans cet ordre :
1. AVGCALCMETHOD
2. SLIDINGWINDOWSIZE
3. historique complet
4. READINGDATE avec secondes
5. READING
6. DELTA
7. READINGTYPE
8. RESET
9. LASTREADING
10. LASTREADINGDATE
11. PREVIOUSREADING
12. PREVIOUSREADINGDATE
13. LIFETODATE
14. skipAverageRefresh
15. contenu de $assetmeterReadingsToUpd
16. contenu de $locmeterReadingsToUpd
17. relevés enregistrés dans modifiedReadings
24. Tests de non-régression indispensables
Un correctif sur les compteurs ne doit jamais être validé avec un seul scénario.
| Test | Scénario | Résultat attendu |
|---|---|---|
| T01 | ACTUAL dans l’ordre | moyenne correcte |
| T02 | ACTUAL rétroactif | recalcul des suivants |
| T03 | DELTA dans l’ordre | moyenne correcte |
| T04 | DELTA historique modifié | moyenne recalculée |
| T05 | dernier DELTA modifié | moyenne recalculée |
| T06 | RESET | moyenne recalculée |
| T07 | relevé après RESET | continuité correcte |
| T08 | plusieurs RESET | moyenne correcte |
| T09 | timestamps très proches | comportement maîtrisé |
| T10 | SLIDING-DAYS | fenêtre temporelle correcte |
| T11 | SLIDING-READINGS | aucune régression |
| T12 | ALL | aucune régression |
| T13 | STATIC | aucun recalcul |
| T14 | propagation Asset / Location | propagation correcte |
| T15 | historiques identiques | moyennes cohérentes lorsque le contexte métier est équivalent |
25. Références techniques IBM
Pour approfondir le fonctionnement standard, les références IBM utiles sont notamment :
- documentation IBM sur les méthodes de calcul de moyenne des compteurs ;
- documentation sur les compteurs d’équipements ;
- documentation sur les compteurs d’emplacements ;
- documentation sur le mécanisme de propagation des relevés ;
- APAR IBM relatifs aux calculs de moyenne après modification de l’historique ;
- APAR relatifs aux comportements SLIDING-DAYS, SLIDING-READINGS et aux relevés hérités.
Dans une analyse de code Maximo 7.6.1.x, les classes particulièrement importantes sont :
psdi.app.meter.DeployedMeter
psdi.app.asset.AssetMeter
psdi.app.asset.MeterReadingSet
psdi.app.location.LocationMeter
psdi.app.location.LocMeterReadingSet
et notamment les méthodes :
refreshAverage()
calculateAverageALL()
calculateAverageSLIDINGDAYS()
calculateAverageSLIDINGREADINGS()
addModifiedReadingsForAvgCalc()
adjustAverageNumeratorForModifiedReading()
processContinuousReadings()
updateReadingsAfterNewReading()
updateReadingsAfterReadingUpdate()
Pour résumé :
Une anomalie de AVERAGE dans Maximo ne provient pas nécessairement de la formule mathématique elle-même.
Plusieurs mécanismes peuvent intervenir simultanément :
saisie rétroactive
+
recalcul des DELTA
+
RESET
+
SLIDING-DAYS
+
skipAverageRefresh
+
modifiedReadings
+
MboSet de relevés suivants
Le cas le plus subtil concerne la modification du dernier LOCMETERREADING de type DELTA.
Comme aucun relevé postérieur n’existe :
$locmeterReadingsToUpd = EMPTY
le standard n’appelle pas :
updateReadingsAfterReadingUpdate()
Le relevé modifié peut alors ne pas être enregistré dans le contexte nécessaire au calcul de la moyenne.
La solution retenue respecte un principe important en personnalisation Maximo :
Corriger le contexte nécessaire au standard plutôt que réécrire tout le standard.
L’architecture reste ainsi :
AssetMeter → IBM standard
LocationMeter → IBM standard
avec des personnalisations concentrées dans :
NYRMeterReadingSet
NYRLocMeterReadingSet
et, pour le cas spécifique du dernier DELTA, l’enregistrement explicite du relevé modifié :
deployedMeter.addModifiedReadingsForAvgCalc(modifiedReading);
tout en maintenant :
deployedMeter.setSkipAverageRefresh(false);
afin que le refreshAverage() standard IBM puisse terminer correctement le traitement.
Cette approche illustre une méthode efficace pour diagnostiquer les anomalies complexes dans Maximo : partir des données, tracer le cycle MBO, comparer les chemins Asset et Location, identifier précisément le point de divergence puis appliquer la correction la plus locale possible.




