crexs commited on
Commit
1ddf51b
·
verified ·
1 Parent(s): 4e302f0

sync: update tests/test_pedi.py

Browse files
Files changed (1) hide show
  1. tests/test_pedi.py +203 -0
tests/test_pedi.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sqlite3
3
+ import tempfile
4
+ import unittest
5
+ from datetime import datetime, timezone
6
+ from pathlib import Path
7
+ from unittest.mock import MagicMock
8
+
9
+ from infj_bot.metrics.pedi import (
10
+ PediIndex,
11
+ StateSnapshot,
12
+ ResetEvent,
13
+ NEED_DIMENSIONS,
14
+ MAX_TOLERATED_JUMP,
15
+ CRITICAL_FLUIDITY,
16
+ )
17
+ from infj_bot.core.cognitive_orchestrator import CognitiveOrchestrator
18
+ from infj_bot.core.homeostasis import get_homeostasis
19
+ from infj_bot.core.commands import BotState
20
+
21
+ class TestPediIndex(unittest.TestCase):
22
+ def setUp(self):
23
+ # Create a temp database for PediIndex testing
24
+ self.db_fd, self.db_path = tempfile.mkstemp(suffix=".db")
25
+ os.close(self.db_fd)
26
+ self.pedi = PediIndex(db_path=self.db_path)
27
+
28
+ def tearDown(self):
29
+ if os.path.exists(self.db_path):
30
+ os.unlink(self.db_path)
31
+
32
+ def test_record_and_get_last_snapshot(self):
33
+ needs = {dim: 0.5 for dim in NEED_DIMENSIONS}
34
+ snap = StateSnapshot(
35
+ turn_id=1,
36
+ timestamp=datetime.now(timezone.utc),
37
+ needs=needs,
38
+ context_tokens_used=100,
39
+ )
40
+ self.pedi.record_snapshot(snap)
41
+
42
+ last_snap = self.pedi.get_last_snapshot()
43
+ self.assertIsNotNone(last_snap)
44
+ self.assertEqual(last_snap.turn_id, 1)
45
+ self.assertEqual(last_snap.context_tokens_used, 100)
46
+ for dim in NEED_DIMENSIONS:
47
+ self.assertAlmostEqual(last_snap.needs[dim], 0.5)
48
+
49
+ def test_zero_jump(self):
50
+ needs = {dim: 0.5 for dim in NEED_DIMENSIONS}
51
+ snap1 = StateSnapshot(
52
+ turn_id=1,
53
+ timestamp=datetime.now(timezone.utc),
54
+ needs=needs,
55
+ context_tokens_used=100,
56
+ )
57
+ snap2 = StateSnapshot(
58
+ turn_id=2,
59
+ timestamp=datetime.now(timezone.utc),
60
+ needs=needs,
61
+ context_tokens_used=120,
62
+ )
63
+
64
+ event = ResetEvent(turn_id=2, timestamp=datetime.now(timezone.utc), reason="test")
65
+ report = self.pedi.evaluate_reset(snap1, snap2, event)
66
+
67
+ self.assertEqual(report.state_jump, 0.0)
68
+ self.assertEqual(report.fluidity_score, 1.0)
69
+ self.assertEqual(report.cumulative_fluidity, 1.0)
70
+ self.assertTrue(report.recovered)
71
+ self.assertFalse(report.crisis_flag)
72
+
73
+ def test_max_jump(self):
74
+ # Create states with maximum possible differences to trigger SFS = 0
75
+ needs_min = {dim: 0.0 for dim in NEED_DIMENSIONS}
76
+ needs_max = {dim: 1.0 for dim in NEED_DIMENSIONS}
77
+
78
+ snap1 = StateSnapshot(
79
+ turn_id=1,
80
+ timestamp=datetime.now(timezone.utc),
81
+ needs=needs_min,
82
+ context_tokens_used=100,
83
+ )
84
+ snap2 = StateSnapshot(
85
+ turn_id=2,
86
+ timestamp=datetime.now(timezone.utc),
87
+ needs=needs_max,
88
+ context_tokens_used=120,
89
+ )
90
+
91
+ event = ResetEvent(turn_id=2, timestamp=datetime.now(timezone.utc), reason="test")
92
+ report = self.pedi.evaluate_reset(snap1, snap2, event)
93
+
94
+ # Max Euclidean jump distance should be sqrt(len(NEED_DIMENSIONS) * 1.0^2)
95
+ # SFS should collapse to 0.0 because the jump exceeds MAX_TOLERATED_JUMP
96
+ self.assertEqual(report.fluidity_score, 0.0)
97
+
98
+ def test_ema_smoothing(self):
99
+ # Test cumulative fluidity updates: CF_t = 0.9 * CF_{t-1} + 0.1 * SFS_t
100
+ needs_1 = {dim: 0.5 for dim in NEED_DIMENSIONS}
101
+ snap1 = StateSnapshot(
102
+ turn_id=1,
103
+ timestamp=datetime.now(timezone.utc),
104
+ needs=needs_1,
105
+ context_tokens_used=100,
106
+ )
107
+
108
+ # Modify needs to cause a specific jump
109
+ needs_2 = {dim: 0.6 for dim in NEED_DIMENSIONS}
110
+ snap2 = StateSnapshot(
111
+ turn_id=2,
112
+ timestamp=datetime.now(timezone.utc),
113
+ needs=needs_2,
114
+ context_tokens_used=120,
115
+ )
116
+
117
+ # Baseline start: EMA = 1.0
118
+ self.assertEqual(self.pedi._cumulative_fluidity, 1.0)
119
+
120
+ event = ResetEvent(turn_id=2, timestamp=datetime.now(timezone.utc), reason="test")
121
+ report = self.pedi.evaluate_reset(snap1, snap2, event)
122
+
123
+ # Expected EMA calculation
124
+ expected_ema = 0.9 * 1.0 + 0.1 * report.fluidity_score
125
+ self.assertAlmostEqual(report.cumulative_fluidity, expected_ema, places=4)
126
+
127
+ def test_crisis_trigger(self):
128
+ # Force consecutive low SFS to drop cumulative fluidity below CRITICAL_FLUIDITY
129
+ needs_min = {dim: 0.0 for dim in NEED_DIMENSIONS}
130
+ needs_max = {dim: 1.0 for dim in NEED_DIMENSIONS}
131
+
132
+ snap_prev = StateSnapshot(
133
+ turn_id=1,
134
+ timestamp=datetime.now(timezone.utc),
135
+ needs=needs_min,
136
+ context_tokens_used=100,
137
+ )
138
+
139
+ # Trigger multiple resets with max jump
140
+ for turn in range(2, 20):
141
+ snap_curr = StateSnapshot(
142
+ turn_id=turn,
143
+ timestamp=datetime.now(timezone.utc),
144
+ needs=needs_max if turn % 2 == 0 else needs_min,
145
+ context_tokens_used=100,
146
+ )
147
+ event = ResetEvent(turn_id=turn, timestamp=datetime.now(timezone.utc), reason="test")
148
+ report = self.pedi.evaluate_reset(snap_prev, snap_curr, event)
149
+ snap_prev = snap_curr
150
+
151
+ if report.cumulative_fluidity < CRITICAL_FLUIDITY:
152
+ self.assertTrue(report.crisis_flag)
153
+ break
154
+ else:
155
+ self.fail("Failed to trigger critical fluidity crisis")
156
+
157
+ def test_cognitive_orchestrator_integration(self):
158
+ # Verify that snapshot is recorded on every assemble_prompt() call
159
+ orchestrator = CognitiveOrchestrator()
160
+ state = BotState(mode="companion")
161
+
162
+ mock_memory = MagicMock()
163
+ mock_memory.retrieve_context_ranked.return_value = ""
164
+ mock_memory.retrieve_context.return_value = ""
165
+
166
+ # Patch PediIndex db_path to use our test db path
167
+ with patch("infj_bot.metrics.pedi.get_pedi") as mock_get_pedi:
168
+ mock_get_pedi.return_value = self.pedi
169
+
170
+ # Run prompt assembly
171
+ orchestrator.assemble_prompt("Hello", state, mock_memory)
172
+
173
+ # Verify snapshot was written
174
+ last_snap = self.pedi.get_last_snapshot()
175
+ self.assertIsNotNone(last_snap)
176
+ self.assertEqual(last_snap.turn_id, 0)
177
+
178
+ # Run another turn to confirm increment
179
+ state.turns = 1
180
+ orchestrator.assemble_prompt("How are you", state, mock_memory)
181
+ last_snap_2 = self.pedi.get_last_snapshot()
182
+ self.assertEqual(last_snap_2.turn_id, 1)
183
+
184
+ # Helper patch class
185
+ class patch:
186
+ def __init__(self, target):
187
+ self.target = target
188
+ self.mock = MagicMock()
189
+
190
+ def __enter__(self):
191
+ import importlib
192
+ parts = self.target.split('.')
193
+ module_path = '.'.join(parts[:-1])
194
+ member = parts[-1]
195
+ self.module = importlib.import_module(module_path)
196
+ self.original = getattr(self.module, member)
197
+ setattr(self.module, member, self.mock)
198
+ return self.mock
199
+
200
+ def __exit__(self, exc_type, exc_val, exc_tb):
201
+ parts = self.target.split('.')
202
+ member = parts[-1]
203
+ setattr(self.module, member, self.original)