dsh-biocore 0.3.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/lib/index.js +30 -1
  2. package/lib/modules/standby.js +61 -0
  3. package/lib/modules/state.js +22 -3
  4. package/package.json +1 -1
  5. package/python/biocore_memory/core/config.py +7 -3
  6. package/python/biocore_memory/engine.py +91 -6
  7. package/python/biocore_memory/recall/embedding.py +0 -51
  8. package/python/biocore_memory/recall/feedback.py +34 -7
  9. package/python/biocore_memory/recall/fusion.py +5 -1
  10. package/python/biocore_memory/scoring.py +203 -0
  11. package/python/biocore_memory/space.py +355 -0
  12. package/python/biocore_memory/store/dedupe.py +1 -0
  13. package/python/biocore_memory/store/tiering.py +14 -3
  14. package/python/biocore_model/affective_labor.py +14 -1
  15. package/python/biocore_model/appraisal.py +172 -3
  16. package/python/biocore_model/bio_tick.py +43 -13
  17. package/python/biocore_model/emotion_geometry.py +9 -0
  18. package/python/biocore_model/envy.py +21 -0
  19. package/python/biocore_model/feedback_memory.py +53 -0
  20. package/python/biocore_model/needs_events.py +32 -11
  21. package/python/biocore_model/regulation_strategies.py +5 -0
  22. package/python/biocore_model/repair_after_boundary.py +1 -0
  23. package/python/biocore_model/self_esteem.py +14 -2
  24. package/python/biocore_model/self_intent.py +6 -4
  25. package/python/biocore_model/tests/test_autonomy_self.py +153 -153
  26. package/python/biocore_model/tests/test_cache_series.py +137 -0
  27. package/python/biocore_model/tests/test_core_state.py +240 -240
  28. package/python/biocore_model/tests/test_extensions.py +167 -167
  29. package/python/biocore_model/tests/test_field_arbiter.py +444 -0
  30. package/python/biocore_model/tests/test_governance.py +181 -181
  31. package/python/biocore_model/tests/test_lifecycle.py +153 -0
  32. package/python/biocore_model/tests/test_memory_inject.py +211 -211
  33. package/python/biocore_model/tests/test_retrieval_plus.py +143 -0
  34. package/python/biocore_model/tests/test_signals.py +106 -106
  35. package/python/biocore_model/tests/test_standby.py +199 -0
  36. package/python/biocore_runtime/__init__.py +3 -0
  37. package/python/biocore_runtime/arbiter.py +307 -0
  38. package/python/biocore_runtime/batch_b.py +724 -0
  39. package/python/biocore_runtime/batch_c.py +186 -0
  40. package/python/biocore_runtime/batch_d.py +659 -0
  41. package/python/biocore_runtime/batch_e.py +328 -0
  42. package/python/biocore_runtime/batch_registry.py +446 -0
  43. package/python/biocore_runtime/cache_stats.py +161 -0
  44. package/python/biocore_runtime/core_state.py +106 -3
  45. package/python/biocore_runtime/dimensions.py +419 -0
  46. package/python/biocore_runtime/expression.py +261 -0
  47. package/python/biocore_runtime/field.py +440 -0
  48. package/python/biocore_runtime/lifecycle.py +233 -0
  49. package/python/biocore_runtime/perception.py +1045 -0
  50. package/python/biocore_runtime/pipeline.py +4458 -2974
  51. package/python/biocore_runtime/priority.py +210 -0
  52. package/python/biocore_runtime/retrieval_plus.py +352 -0
  53. package/python/biocore_runtime/standby.py +581 -472
  54. package/python/biocore_runtime/timescales.py +263 -0
  55. package/python/data/signals.json +3266 -206
  56. package/ui/panel-layout.js +32 -29
  57. package/ui/panel.css +563 -974
  58. package/ui/panel.html +427 -401
  59. package/ui/panel.js +543 -450
@@ -1,153 +1,153 @@
1
- # -*- coding: utf-8 -*-
2
- """task-222: self layer (persona/narrative/drift) + autonomy decision/limits."""
3
- import json
4
- import tempfile
5
- import time
6
- import unittest
7
- from pathlib import Path
8
-
9
- from biocore_runtime import autonomy, pipeline, self_layer
10
-
11
-
12
- def write_json(path, data):
13
- path = Path(path)
14
- path.parent.mkdir(parents=True, exist_ok=True)
15
- path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
16
-
17
-
18
- class Case(unittest.TestCase):
19
- def setUp(self):
20
- self.tmp = tempfile.TemporaryDirectory()
21
- self.data_dir = Path(self.tmp.name) / "data"
22
- self.store = self.data_dir / "memory"
23
- self.persona = Path(self.tmp.name) / "missing.json"
24
- (self.store / "sessions").mkdir(parents=True, exist_ok=True)
25
-
26
- def tearDown(self):
27
- self.tmp.cleanup()
28
-
29
- def payload(self, session_id="s1", **extra):
30
- data = {"store": str(self.store), "session_id": session_id,
31
- "persona_profile": str(self.persona)}
32
- data.update(extra)
33
- return data
34
-
35
- def configure(self, **extra):
36
- data = {"data_dir": str(self.data_dir)}
37
- data.update(extra)
38
- pipeline.dispatch("configure", data)
39
-
40
- def actions(self, rows):
41
- lines = []
42
- for row in rows:
43
- line = {"ts": time.time(), "kind": "write", "args": {},
44
- "reason": "test", "result": {"ok": True}}
45
- line.update(row)
46
- lines.append(json.dumps(line, ensure_ascii=False))
47
- (self.data_dir / "actions.jsonl").write_text(
48
- "\n".join(lines) + "\n", encoding="utf-8")
49
-
50
-
51
- class SelfLayerTest(Case):
52
- def test_persona_locked(self):
53
- self.configure(persona_text="我是安吉拉")
54
- doc = self_layer.update(self.store, self_layer.hash_persona("我是安吉拉"))
55
- self.assertTrue(doc["persona_locked"])
56
- summary = self_layer.summary(self.store)
57
- self.assertTrue(summary["persona_locked"])
58
- self.assertEqual(summary["drift_count"], 0)
59
-
60
- def test_drift_detected(self):
61
- self.configure(persona_text="旧人设")
62
- self_layer.update(self.store, self_layer.hash_persona("旧人设"))
63
- self_layer.update(self.store, self_layer.hash_persona("新人设"))
64
- summary = self_layer.summary(self.store)
65
- self.assertEqual(summary["drift_count"], 1)
66
-
67
- def test_same_hash_is_idempotent(self):
68
- h = self_layer.hash_persona("稳定人设")
69
- self_layer.update(self.store, h)
70
- self_layer.update(self.store, h)
71
- self.assertEqual(self_layer.summary(self.store)["drift_count"], 0)
72
-
73
- def test_narrative_accumulates(self):
74
- self_layer.update(self.store, "", {"kind": "warmth", "text": "谢谢你"})
75
- self_layer.update(self.store, "", {"kind": "repair", "text": "对不起"})
76
- summary = self_layer.summary(self.store)
77
- self.assertEqual(summary["narrative_count"], 2)
78
- self.assertEqual(summary["last_narrative"], "对不起")
79
-
80
-
81
- class AutonomyTest(Case):
82
- def test_default_limits(self):
83
- lim = autonomy.limits(self.store)
84
- self.assertEqual(lim["max_actions_per_hour"], 20)
85
- self.assertEqual(lim["max_sends_per_hour"], 5)
86
- self.assertEqual(lim["max_sends_per_day"], 30)
87
- self.assertEqual(lim["quiet_hours"], [])
88
- self.assertTrue(lim["auto_decay_on_silence"])
89
-
90
- def test_send_when_social_low(self):
91
- out = autonomy.decide(self.store, now=time.time(),
92
- state={"core": {"social": 0.2, "energy": 0.7}})
93
- self.assertEqual(out["action"]["kind"], "send")
94
-
95
- def test_retrieve_when_memory_exists(self):
96
- write_json(self.store / "sessions" / "autonomy" / "memory.json",
97
- {"entries": [{"id": "a", "text": "hello", "ts": 1.0}]})
98
- out = autonomy.decide(self.store, now=time.time(),
99
- state={"core": {"social": 0.9}})
100
- self.assertEqual(out["action"]["kind"], "retrieve")
101
-
102
- def test_write_when_idle(self):
103
- out = autonomy.decide(self.store, now=time.time(),
104
- state={"core": {"social": 0.9, "energy": 0.8}})
105
- self.assertEqual(out["action"]["kind"], "write")
106
-
107
- def test_hourly_action_cap_blocks(self):
108
- now = time.time()
109
- self.actions([{"ts": now - 5}] * 20)
110
- out = autonomy.decide(self.store, now=now, state={"core": {"social": 0.2}})
111
- self.assertIsNone(out["action"])
112
- self.assertEqual(out["reason"], "limit_actions_hour")
113
-
114
- def test_send_hour_cap_blocks_send(self):
115
- now = time.time()
116
- self.actions([{"ts": now - 10, "kind": "send"}] * 5)
117
- out = autonomy.decide(self.store, now=now, state={"core": {"social": 0.2}})
118
- self.assertNotEqual((out["action"] or {}).get("kind"), "send")
119
-
120
- def test_quiet_hours_block_send(self):
121
- self.configure(autonomy={"quiet_hours": ["00:00-23:59"]})
122
- out = autonomy.decide(self.store, now=time.time(),
123
- state={"core": {"social": 0.2}})
124
- self.assertNotEqual((out["action"] or {}).get("kind"), "send")
125
-
126
- def test_decay_on_silence_throttles(self):
127
- now = time.time()
128
- self.actions([{"ts": now - 60}]) # last action 1 min ago
129
- out = autonomy.decide(self.store, now=now,
130
- state={"core": {"social": 0.2}},
131
- silence_minutes=24 * 60)
132
- self.assertIsNone(out["action"])
133
- self.assertEqual(out["reason"], "decayed_silence")
134
-
135
- def test_core_disabled_no_action(self):
136
- self.configure(switches={"core_enabled": False})
137
- out = pipeline.dispatch("autonomy_tick", self.payload())
138
- self.assertTrue(out["ok"])
139
- self.assertIsNone(out["action"])
140
- self.assertEqual(out["reason"], "disabled")
141
-
142
- def test_state_exposes_autonomy_numbers(self):
143
- self.actions([{"ts": time.time(), "kind": "send"},
144
- {"ts": time.time(), "kind": "write"}])
145
- body = pipeline.dispatch("state", self.payload())["state"]
146
- self.assertEqual(body["autonomy"]["actions_today"], 2)
147
- self.assertEqual(body["autonomy"]["sends_today"], 1)
148
- self.assertEqual(body["autonomy"]["limits"]["max_sends_per_hour"], 5)
149
- self.assertIn("identity", body)
150
-
151
-
152
- if __name__ == "__main__":
153
- unittest.main()
1
+ # -*- coding: utf-8 -*-
2
+ """task-222: self layer (persona/narrative/drift) + autonomy decision/limits."""
3
+ import json
4
+ import tempfile
5
+ import time
6
+ import unittest
7
+ from pathlib import Path
8
+
9
+ from biocore_runtime import autonomy, pipeline, self_layer
10
+
11
+
12
+ def write_json(path, data):
13
+ path = Path(path)
14
+ path.parent.mkdir(parents=True, exist_ok=True)
15
+ path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
16
+
17
+
18
+ class Case(unittest.TestCase):
19
+ def setUp(self):
20
+ self.tmp = tempfile.TemporaryDirectory()
21
+ self.data_dir = Path(self.tmp.name) / "data"
22
+ self.store = self.data_dir / "memory"
23
+ self.persona = Path(self.tmp.name) / "missing.json"
24
+ (self.store / "sessions").mkdir(parents=True, exist_ok=True)
25
+
26
+ def tearDown(self):
27
+ self.tmp.cleanup()
28
+
29
+ def payload(self, session_id="s1", **extra):
30
+ data = {"store": str(self.store), "session_id": session_id,
31
+ "persona_profile": str(self.persona)}
32
+ data.update(extra)
33
+ return data
34
+
35
+ def configure(self, **extra):
36
+ data = {"data_dir": str(self.data_dir)}
37
+ data.update(extra)
38
+ pipeline.dispatch("configure", data)
39
+
40
+ def actions(self, rows):
41
+ lines = []
42
+ for row in rows:
43
+ line = {"ts": time.time(), "kind": "write", "args": {},
44
+ "reason": "test", "result": {"ok": True}}
45
+ line.update(row)
46
+ lines.append(json.dumps(line, ensure_ascii=False))
47
+ (self.data_dir / "actions.jsonl").write_text(
48
+ "\n".join(lines) + "\n", encoding="utf-8")
49
+
50
+
51
+ class SelfLayerTest(Case):
52
+ def test_persona_locked(self):
53
+ self.configure(persona_text="我是安吉拉")
54
+ doc = self_layer.update(self.store, self_layer.hash_persona("我是安吉拉"))
55
+ self.assertTrue(doc["persona_locked"])
56
+ summary = self_layer.summary(self.store)
57
+ self.assertTrue(summary["persona_locked"])
58
+ self.assertEqual(summary["drift_count"], 0)
59
+
60
+ def test_drift_detected(self):
61
+ self.configure(persona_text="旧人设")
62
+ self_layer.update(self.store, self_layer.hash_persona("旧人设"))
63
+ self_layer.update(self.store, self_layer.hash_persona("新人设"))
64
+ summary = self_layer.summary(self.store)
65
+ self.assertEqual(summary["drift_count"], 1)
66
+
67
+ def test_same_hash_is_idempotent(self):
68
+ h = self_layer.hash_persona("稳定人设")
69
+ self_layer.update(self.store, h)
70
+ self_layer.update(self.store, h)
71
+ self.assertEqual(self_layer.summary(self.store)["drift_count"], 0)
72
+
73
+ def test_narrative_accumulates(self):
74
+ self_layer.update(self.store, "", {"kind": "warmth", "text": "谢谢你"})
75
+ self_layer.update(self.store, "", {"kind": "repair", "text": "对不起"})
76
+ summary = self_layer.summary(self.store)
77
+ self.assertEqual(summary["narrative_count"], 2)
78
+ self.assertEqual(summary["last_narrative"], "对不起")
79
+
80
+
81
+ class AutonomyTest(Case):
82
+ def test_default_limits(self):
83
+ lim = autonomy.limits(self.store)
84
+ self.assertEqual(lim["max_actions_per_hour"], 20)
85
+ self.assertEqual(lim["max_sends_per_hour"], 5)
86
+ self.assertEqual(lim["max_sends_per_day"], 30)
87
+ self.assertEqual(lim["quiet_hours"], [])
88
+ self.assertTrue(lim["auto_decay_on_silence"])
89
+
90
+ def test_send_when_social_low(self):
91
+ out = autonomy.decide(self.store, now=time.time(),
92
+ state={"core": {"social": 0.2, "energy": 0.7}})
93
+ self.assertEqual(out["action"]["kind"], "send")
94
+
95
+ def test_retrieve_when_memory_exists(self):
96
+ write_json(self.store / "sessions" / "autonomy" / "memory.json",
97
+ {"entries": [{"id": "a", "text": "hello", "ts": 1.0}]})
98
+ out = autonomy.decide(self.store, now=time.time(),
99
+ state={"core": {"social": 0.9}})
100
+ self.assertEqual(out["action"]["kind"], "retrieve")
101
+
102
+ def test_write_when_idle(self):
103
+ out = autonomy.decide(self.store, now=time.time(),
104
+ state={"core": {"social": 0.9, "energy": 0.8}})
105
+ self.assertEqual(out["action"]["kind"], "write")
106
+
107
+ def test_hourly_action_cap_blocks(self):
108
+ now = time.time()
109
+ self.actions([{"ts": now - 5}] * 20)
110
+ out = autonomy.decide(self.store, now=now, state={"core": {"social": 0.2}})
111
+ self.assertIsNone(out["action"])
112
+ self.assertEqual(out["reason"], "limit_actions_hour")
113
+
114
+ def test_send_hour_cap_blocks_send(self):
115
+ now = time.time()
116
+ self.actions([{"ts": now - 10, "kind": "send"}] * 5)
117
+ out = autonomy.decide(self.store, now=now, state={"core": {"social": 0.2}})
118
+ self.assertNotEqual((out["action"] or {}).get("kind"), "send")
119
+
120
+ def test_quiet_hours_block_send(self):
121
+ self.configure(autonomy={"quiet_hours": ["00:00-23:59"]})
122
+ out = autonomy.decide(self.store, now=time.time(),
123
+ state={"core": {"social": 0.2}})
124
+ self.assertNotEqual((out["action"] or {}).get("kind"), "send")
125
+
126
+ def test_decay_on_silence_throttles(self):
127
+ now = time.time()
128
+ self.actions([{"ts": now - 60}]) # last action 1 min ago
129
+ out = autonomy.decide(self.store, now=now,
130
+ state={"core": {"social": 0.2}},
131
+ silence_minutes=24 * 60)
132
+ self.assertIsNone(out["action"])
133
+ self.assertEqual(out["reason"], "decayed_silence")
134
+
135
+ def test_core_disabled_no_action(self):
136
+ self.configure(switches={"core_enabled": False})
137
+ out = pipeline.dispatch("autonomy_tick", self.payload())
138
+ self.assertTrue(out["ok"])
139
+ self.assertIsNone(out["action"])
140
+ self.assertEqual(out["reason"], "disabled")
141
+
142
+ def test_state_exposes_autonomy_numbers(self):
143
+ self.actions([{"ts": time.time(), "kind": "send"},
144
+ {"ts": time.time(), "kind": "write"}])
145
+ body = pipeline.dispatch("state", self.payload())["state"]
146
+ self.assertEqual(body["autonomy"]["actions_today"], 2)
147
+ self.assertEqual(body["autonomy"]["sends_today"], 1)
148
+ self.assertEqual(body["autonomy"]["limits"]["max_sends_per_hour"], 5)
149
+ self.assertIn("identity", body)
150
+
151
+
152
+ if __name__ == "__main__":
153
+ unittest.main()
@@ -0,0 +1,137 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Contract migration 1: cache stats + series are computed by the core."""
3
+ import json
4
+ import tempfile
5
+ import time
6
+ import unittest
7
+ from pathlib import Path
8
+
9
+ from biocore_runtime import cache_stats, core_state, pipeline
10
+
11
+
12
+ class CacheCase(unittest.TestCase):
13
+ def setUp(self):
14
+ self.tmp = tempfile.TemporaryDirectory()
15
+ self.data_dir = Path(self.tmp.name) / "data"
16
+ self.store = self.data_dir / "memory"
17
+ (self.store / "sessions").mkdir(parents=True)
18
+
19
+ def tearDown(self):
20
+ self.tmp.cleanup()
21
+
22
+ def event(self, **values):
23
+ row = {"prompt_tokens": 100, "output_tokens": 20,
24
+ "cache_read": 60, "cache_write": 40}
25
+ row.update(values)
26
+ return cache_stats.record_event(self.store, row,
27
+ now=values.pop("ts", time.time()))
28
+
29
+ def test_accumulates_events(self):
30
+ now = time.time()
31
+ cache_stats.record_event(self.store, {"prompt_tokens": 10,
32
+ "output_tokens": 2, "cache_read": 4, "cache_write": 1}, now=now)
33
+ cache_stats.record_event(self.store, {"prompt_tokens": 20,
34
+ "output_tokens": 3, "cache_read": 6, "cache_write": 2}, now=now)
35
+ out = cache_stats.compute(self.store, now=now)
36
+ self.assertEqual(out["turns"], 2)
37
+ self.assertEqual(out["prompt_tokens"], 30)
38
+ self.assertEqual(out["output_tokens"], 5)
39
+ self.assertEqual(out["cache_read"], 10)
40
+ self.assertEqual(out["cache_write"], 3)
41
+ self.assertEqual(out["source"], "usage_events")
42
+
43
+ def test_hit_rate_ratio(self):
44
+ now = time.time()
45
+ cache_stats.record_event(self.store, {"prompt_tokens": 100,
46
+ "output_tokens": 10, "cache_read": 75, "cache_write": 25}, now=now)
47
+ self.assertAlmostEqual(cache_stats.compute(self.store, now=now)["hit_rate"],
48
+ 0.75, places=4)
49
+
50
+ def test_hit_rate_falls_back_to_prompt_tokens(self):
51
+ now = time.time()
52
+ cache_stats.record_event(self.store, {"prompt_tokens": 100,
53
+ "output_tokens": 10, "cache_read": 50, "cache_write": 0}, now=now)
54
+ self.assertAlmostEqual(cache_stats.compute(self.store, now=now)["hit_rate"],
55
+ 0.5, places=4)
56
+
57
+ def test_hit_rate_zero_when_empty(self):
58
+ self.assertEqual(cache_stats.compute(self.store)["hit_rate"], 0.0)
59
+
60
+ def test_hit_rate_is_clamped(self):
61
+ now = time.time()
62
+ cache_stats.record_event(self.store, {"prompt_tokens": 10,
63
+ "output_tokens": 1, "cache_read": 50, "cache_write": 0}, now=now)
64
+ rate = cache_stats.compute(self.store, now=now)["hit_rate"]
65
+ self.assertGreaterEqual(rate, 0.0)
66
+ self.assertLessEqual(rate, 1.0)
67
+
68
+ def test_window_excludes_old_events(self):
69
+ now = time.time()
70
+ cache_stats.record_event(self.store, {"prompt_tokens": 10,
71
+ "cache_read": 5}, now=now - 8 * 86400)
72
+ cache_stats.record_event(self.store, {"prompt_tokens": 20,
73
+ "cache_read": 10}, now=now)
74
+ out = cache_stats.compute(self.store, now=now)
75
+ self.assertEqual(out["turns"], 1)
76
+ self.assertEqual(out["prompt_tokens"], 20)
77
+
78
+ def test_legacy_file_is_read_only_fallback(self):
79
+ now = time.time()
80
+ (self.data_dir / "cache_stats.json").write_text(json.dumps({
81
+ "turns": 4, "prompt_tokens": 400, "output_tokens": 40,
82
+ "cache_read": 200, "cache_write": 200, "hit_rate": 0.5,
83
+ "updated_at": now}), encoding="utf-8")
84
+ out = cache_stats.compute(self.store, now=now)
85
+ self.assertEqual(out["source"], "legacy_file")
86
+ self.assertEqual(out["turns"], 4)
87
+ self.assertEqual(out["hit_rate"], 0.5)
88
+
89
+ def test_usage_event_action_appends(self):
90
+ out = pipeline.dispatch("usage_event", {
91
+ "store": str(self.store), "prompt_tokens": 30,
92
+ "cache_read": 15, "cache_write": 5, "now": time.time()})
93
+ self.assertTrue(out["ok"])
94
+ self.assertEqual(out["cache"]["cache_read"], 15)
95
+
96
+ def test_stats_action_includes_core_cache(self):
97
+ cache_stats.record_event(self.store, {"prompt_tokens": 10,
98
+ "cache_read": 5, "cache_write": 5}, now=time.time())
99
+ out = pipeline.dispatch("stats", {"store": str(self.store)})
100
+ self.assertIn("cache", out)
101
+ self.assertEqual(out["cache"]["source"], "usage_events")
102
+ self.assertEqual(out["cache"]["hit_rate"], 0.5)
103
+
104
+
105
+ class SeriesCase(unittest.TestCase):
106
+ def setUp(self):
107
+ self.tmp = tempfile.TemporaryDirectory()
108
+ self.data_dir = Path(self.tmp.name) / "data"
109
+ self.store = self.data_dir / "memory"
110
+ (self.store / "sessions").mkdir(parents=True)
111
+
112
+ def tearDown(self):
113
+ self.tmp.cleanup()
114
+
115
+ def test_empty_series_arrays(self):
116
+ out = pipeline.dispatch("series", {"store": str(self.store),
117
+ "data_dir": str(self.data_dir)})
118
+ self.assertTrue(out["ok"])
119
+ for key in ("ts", "v", "tension", "warmth", "heaviness", "clarity"):
120
+ self.assertIsInstance(out["series"][key], list)
121
+ self.assertEqual(out["series"][key], [])
122
+
123
+ def test_series_points_from_core_state(self):
124
+ path = core_state.path_for(data_dir=str(self.data_dir))
125
+ now = time.time()
126
+ core_state.apply(path, absolute={"v": 0.2, "stress": 0.4}, now=now)
127
+ core_state.apply(path, absolute={"v": 0.3, "stress": 0.2}, now=now + 60)
128
+ out = pipeline.dispatch("series", {"store": str(self.store),
129
+ "data_dir": str(self.data_dir),
130
+ "limit": 24})
131
+ self.assertTrue(out["points"])
132
+ self.assertLessEqual(len(out["points"]), 24)
133
+ self.assertEqual(len(out["series"]["ts"]), len(out["points"]))
134
+
135
+
136
+ if __name__ == "__main__":
137
+ unittest.main()