fix(tools): trace-replay compare une action state sur son état seul (brief 4 T7)
Le plan du rule-based tracé porte la puissance ESTIMÉE d'une action state ; un optimiseur ne la donne pas (§6 ne la définit pas). Le rejeu d'une trace rule-based contre l'optimiseur de référence voyait donc une divergence sur chaque PAC. Seuls l'état (state) et la puissance d'une consigne (setpoint) sont comparés. Test unitaire ajouté (7 / 0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6fcce1a470
commit
f42dc709e3
@ -228,5 +228,35 @@ class TestTraceReplay(unittest.TestCase):
|
||||
self.assertNotIn('requêtes rejouées : 0', out)
|
||||
|
||||
|
||||
class TestComparaison(unittest.TestCase):
|
||||
"""La comparaison seule (fonctions de l'outil, sans serveur)."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
from importlib.machinery import SourceFileLoader
|
||||
cls.outil = SourceFileLoader('trace_replay', OUTIL).load_module()
|
||||
|
||||
def test_action_state_comparee_sur_son_etat_seul(self):
|
||||
# Brief 4 T7 : le plan du rule-based porte la puissance ESTIMÉE d'une action `state`,
|
||||
# l'optimiseur de référence ne la donne pas (le §6 ne la définit pas) — ce n'est pas un écart.
|
||||
o = self.outil
|
||||
maintenant = o.instant('2026-06-08T13:00:00Z')
|
||||
creneau = {'from': '2026-06-08T13:00:00Z', 'to': '2026-06-08T13:01:00Z'}
|
||||
regles = {'result': {'slots': [dict(creneau, actions=[
|
||||
{'loadId': 'pac', 'kind': 'state', 'state': 3, 'powerW': 1500},
|
||||
{'loadId': 'ecs', 'kind': 'setpoint', 'powerW': 1000}])]}}
|
||||
optimiseur = {'result': {'slots': [dict(creneau, actions=[
|
||||
{'loadId': 'pac', 'kind': 'state', 'state': 3},
|
||||
{'loadId': 'ecs', 'kind': 'setpoint', 'powerW': 1000.0}])]}}
|
||||
a = o.actions_courantes(regles, maintenant)
|
||||
self.assertEqual(o.differences(a, o.actions_courantes(optimiseur, maintenant), 0.5), [])
|
||||
optimiseur['result']['slots'][0]['actions'][0]['state'] = 4
|
||||
self.assertEqual(o.differences(a, o.actions_courantes(optimiseur, maintenant), 0.5), ['pac : état 3 → 4'])
|
||||
optimiseur['result']['slots'][0]['actions'][0]['state'] = 3
|
||||
optimiseur['result']['slots'][0]['actions'][1]['powerW'] = 1200
|
||||
self.assertEqual(o.differences(a, o.actions_courantes(optimiseur, maintenant), 0.5),
|
||||
['ecs : 1000 W → 1200 W'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
|
||||
@ -13,7 +13,10 @@ de la trace (jamais d'écrasement), puis un résumé : requêtes, taux d'abstent
|
||||
médian et max, cycles dont les actions diffèrent du plan enregistré.
|
||||
|
||||
« Actions du plan » : celles du créneau qui couvre l'instant de la requête (`timestamp`) —
|
||||
charge, kind, puissance (à --tolerance-w près), état SG-Ready. Une abstention (`abstain`, aucun
|
||||
charge, kind, puissance d'une consigne (`setpoint`, à --tolerance-w près), état d'une action
|
||||
`state` (SG-Ready). La puissance d'une action `state` n'est pas comparée : le protocole ne la
|
||||
définit pas (§6), et le plan du rule-based la porte (puissance estimée) quand un optimiseur ne la
|
||||
donne pas. Une abstention (`abstain`, aucun
|
||||
créneau, aucun créneau courant) n'a pas d'actions. Une ligne dont la réponse enregistrée est une
|
||||
erreur (`no-response`, `timeout`…) n'est pas comparable : comptée à part.
|
||||
|
||||
@ -156,8 +159,9 @@ def actions_courantes(reponse, maintenant):
|
||||
continue
|
||||
actions = []
|
||||
for a in creneau.get('actions') or []:
|
||||
actions.append((str(a.get('loadId')), str(a.get('kind', 'setpoint')),
|
||||
a.get('powerW'), a.get('state')))
|
||||
kind = str(a.get('kind', 'setpoint'))
|
||||
actions.append((str(a.get('loadId')), kind,
|
||||
None if kind == 'state' else a.get('powerW'), a.get('state')))
|
||||
return sorted(actions, key=lambda x: (x[0], x[1]))
|
||||
return []
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user