dsh-biocore 0.2.0 → 0.3.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.
- package/README.md +11 -0
- package/lib/core-worker.js +268 -0
- package/lib/index.js +160 -70
- package/lib/modules/autonomy.js +125 -0
- package/lib/modules/cache-stats.js +48 -0
- package/lib/modules/core-turn.js +24 -6
- package/lib/modules/expression-inject.js +0 -10
- package/lib/modules/expression-refresh.js +0 -9
- package/lib/modules/memory-capture.js +2 -1
- package/lib/modules/memory-inject.js +50 -5
- package/lib/modules/memory-tools.js +7 -4
- package/lib/modules/python-env.js +46 -0
- package/lib/modules/safety-gate.js +0 -3
- package/lib/modules/series.js +54 -0
- package/lib/modules/switch.js +16 -0
- package/lib/py-bridge.js +81 -15
- package/package.json +1 -1
- package/python/biocore_memory/core/config.py +138 -127
- package/python/biocore_memory/engine.py +2653 -2476
- package/python/biocore_memory/recall/embedding.py +129 -0
- package/python/biocore_memory/recall/feedback.py +202 -0
- package/python/biocore_model/preference_drift.py +255 -0
- package/python/biocore_model/tests/__init__.py +1 -0
- package/python/biocore_model/tests/core_mode_ui_check.mjs +63 -0
- package/python/biocore_model/tests/core_status_check.mjs +29 -0
- package/python/biocore_model/tests/dom_safe_check.mjs +22 -0
- package/python/biocore_model/tests/leak_samples_run.py +18 -0
- package/python/biocore_model/tests/orb_ring_check.mjs +20 -0
- package/python/biocore_model/tests/panel_html_check.mjs +63 -0
- package/python/biocore_model/tests/persona_flow_check.mjs +17 -0
- package/python/biocore_model/tests/render_guard_check.mjs +19 -0
- package/python/biocore_model/tests/switch_groups_check.mjs +94 -0
- package/python/biocore_model/tests/tabs_state_check.mjs +24 -0
- package/python/biocore_model/tests/test_affective_labor.py +102 -0
- package/python/biocore_model/tests/test_appraisal.py +137 -0
- package/python/biocore_model/tests/test_appraisal_action.py +55 -0
- package/python/biocore_model/tests/test_arch_convergence.py +35 -0
- package/python/biocore_model/tests/test_attachment_behavior.py +95 -0
- package/python/biocore_model/tests/test_attachment_regulation.py +63 -0
- package/python/biocore_model/tests/test_attachment_state.py +29 -0
- package/python/biocore_model/tests/test_attribution.py +56 -0
- package/python/biocore_model/tests/test_attributor.py +108 -0
- package/python/biocore_model/tests/test_autonomic.py +69 -0
- package/python/biocore_model/tests/test_autonomy_accumulator.py +84 -0
- package/python/biocore_model/tests/test_autonomy_affect.py +62 -0
- package/python/biocore_model/tests/test_autonomy_blame.py +44 -0
- package/python/biocore_model/tests/test_autonomy_caps.py +29 -0
- package/python/biocore_model/tests/test_autonomy_check.py +40 -0
- package/python/biocore_model/tests/test_autonomy_clingy.py +41 -0
- package/python/biocore_model/tests/test_autonomy_cooldown_selfcontact.py +138 -0
- package/python/biocore_model/tests/test_autonomy_frustration.py +42 -0
- package/python/biocore_model/tests/test_autonomy_hourly_limit.py +46 -0
- package/python/biocore_model/tests/test_autonomy_loop.py +64 -0
- package/python/biocore_model/tests/test_autonomy_mode.py +37 -0
- package/python/biocore_model/tests/test_autonomy_model.py +60 -0
- package/python/biocore_model/tests/test_autonomy_model_v2.py +68 -0
- package/python/biocore_model/tests/test_autonomy_module_text.py +104 -0
- package/python/biocore_model/tests/test_autonomy_params.py +163 -0
- package/python/biocore_model/tests/test_autonomy_reason.py +34 -0
- package/python/biocore_model/tests/test_autonomy_self.py +153 -0
- package/python/biocore_model/tests/test_autonomy_send_gate.py +38 -0
- package/python/biocore_model/tests/test_autonomy_silence.py +37 -0
- package/python/biocore_model/tests/test_autonomy_status.py +35 -0
- package/python/biocore_model/tests/test_autonomy_test_trigger.py +24 -0
- package/python/biocore_model/tests/test_autonomy_tick.py +148 -0
- package/python/biocore_model/tests/test_autonomy_v4.py +64 -0
- package/python/biocore_model/tests/test_autonomy_v5.py +43 -0
- package/python/biocore_model/tests/test_autonomy_v7.py +48 -0
- package/python/biocore_model/tests/test_autonomy_v_theta.py +85 -0
- package/python/biocore_model/tests/test_batch1_wiring.py +231 -0
- package/python/biocore_model/tests/test_batch2_wiring.py +231 -0
- package/python/biocore_model/tests/test_batch3_wiring.py +223 -0
- package/python/biocore_model/tests/test_bio_tick.py +202 -0
- package/python/biocore_model/tests/test_boundary.py +183 -0
- package/python/biocore_model/tests/test_boundary_gate.py +295 -0
- package/python/biocore_model/tests/test_brain_split.py +99 -0
- package/python/biocore_model/tests/test_co_recall.py +350 -0
- package/python/biocore_model/tests/test_commitment_extractor.py +197 -0
- package/python/biocore_model/tests/test_compassion_fatigue.py +84 -0
- package/python/biocore_model/tests/test_connection_bids.py +69 -0
- package/python/biocore_model/tests/test_core_bridge.py +88 -0
- package/python/biocore_model/tests/test_core_bridge_episode.py +14 -0
- package/python/biocore_model/tests/test_core_bridge_forget.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_identity.py +15 -0
- package/python/biocore_model/tests/test_core_bridge_media.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_relation.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_summary.py +17 -0
- package/python/biocore_model/tests/test_core_client.py +156 -0
- package/python/biocore_model/tests/test_core_commands.py +146 -0
- package/python/biocore_model/tests/test_core_mode.py +161 -0
- package/python/biocore_model/tests/test_core_state.py +240 -0
- package/python/biocore_model/tests/test_criticality.py +61 -0
- package/python/biocore_model/tests/test_cross_session_copy.py +67 -0
- package/python/biocore_model/tests/test_debug_values.py +169 -0
- package/python/biocore_model/tests/test_defense_mechanisms.py +177 -0
- package/python/biocore_model/tests/test_delta_budget.py +157 -0
- package/python/biocore_model/tests/test_diag_trace.py +201 -0
- package/python/biocore_model/tests/test_directives_compat.py +698 -0
- package/python/biocore_model/tests/test_dissonance.py +97 -0
- package/python/biocore_model/tests/test_drift_sign.py +87 -0
- package/python/biocore_model/tests/test_embedding_fallback.py +277 -0
- package/python/biocore_model/tests/test_embedding_local.py +279 -0
- package/python/biocore_model/tests/test_embedding_mode.py +175 -0
- package/python/biocore_model/tests/test_embedding_modes.py +342 -0
- package/python/biocore_model/tests/test_emotion_geometry.py +114 -0
- package/python/biocore_model/tests/test_emotion_probe.py +214 -0
- package/python/biocore_model/tests/test_emotion_probe_concurrency.py +128 -0
- package/python/biocore_model/tests/test_entity_graph.py +198 -0
- package/python/biocore_model/tests/test_entity_resolve.py +446 -0
- package/python/biocore_model/tests/test_envy.py +173 -0
- package/python/biocore_model/tests/test_exit_or_silence.py +209 -0
- package/python/biocore_model/tests/test_expr_guard.py +1 -0
- package/python/biocore_model/tests/test_expr_intent.py +66 -0
- package/python/biocore_model/tests/test_expr_state.py +66 -0
- package/python/biocore_model/tests/test_expr_template.py +71 -0
- package/python/biocore_model/tests/test_expr_tone.py +41 -0
- package/python/biocore_model/tests/test_extreme_scenarios.py +147 -0
- package/python/biocore_model/tests/test_feedback_flow.py +185 -0
- package/python/biocore_model/tests/test_feedback_guard.py +65 -0
- package/python/biocore_model/tests/test_feedback_llm.py +59 -0
- package/python/biocore_model/tests/test_feedback_llm_inferred.py +140 -0
- package/python/biocore_model/tests/test_feedback_memory.py +63 -0
- package/python/biocore_model/tests/test_feedback_user.py +61 -0
- package/python/biocore_model/tests/test_frustration_curve.py +66 -0
- package/python/biocore_model/tests/test_global_bio_state.py +116 -0
- package/python/biocore_model/tests/test_governance.py +181 -0
- package/python/biocore_model/tests/test_granularity.py +133 -0
- package/python/biocore_model/tests/test_graph_extract.py +224 -0
- package/python/biocore_model/tests/test_graph_recall.py +226 -0
- package/python/biocore_model/tests/test_graph_sqlite.py +298 -0
- package/python/biocore_model/tests/test_growth.py +301 -0
- package/python/biocore_model/tests/test_intent_render.py +60 -0
- package/python/biocore_model/tests/test_intero_regulation.py +224 -0
- package/python/biocore_model/tests/test_intero_style.py +146 -0
- package/python/biocore_model/tests/test_item3_numeric.py +105 -0
- package/python/biocore_model/tests/test_item6_numeric.py +116 -0
- package/python/biocore_model/tests/test_layered_memory.py +174 -0
- package/python/biocore_model/tests/test_leak_guard.py +75 -0
- package/python/biocore_model/tests/test_learning_probe.py +93 -0
- package/python/biocore_model/tests/test_life_loop.py +222 -0
- package/python/biocore_model/tests/test_life_loop_10s.py +131 -0
- package/python/biocore_model/tests/test_local_embedding_health.py +258 -0
- package/python/biocore_model/tests/test_memory_affect_encoding.py +79 -0
- package/python/biocore_model/tests/test_memory_backend.py +332 -0
- package/python/biocore_model/tests/test_memory_dedup.py +316 -0
- package/python/biocore_model/tests/test_memory_dual.py +219 -0
- package/python/biocore_model/tests/test_memory_dual_wiring.py +219 -0
- package/python/biocore_model/tests/test_memory_feedback.py +241 -0
- package/python/biocore_model/tests/test_memory_fixes.py +84 -0
- package/python/biocore_model/tests/test_memory_forgetting.py +58 -0
- package/python/biocore_model/tests/test_memory_inject.py +211 -0
- package/python/biocore_model/tests/test_memory_mood_recall.py +118 -0
- package/python/biocore_model/tests/test_memory_store.py +372 -0
- package/python/biocore_model/tests/test_metacog.py +103 -0
- package/python/biocore_model/tests/test_migrate.py +245 -0
- package/python/biocore_model/tests/test_migrate_store.py +194 -0
- package/python/biocore_model/tests/test_mind_wander.py +65 -0
- package/python/biocore_model/tests/test_mood.py +35 -0
- package/python/biocore_model/tests/test_narrative.py +114 -0
- package/python/biocore_model/tests/test_needs_esteem.py +51 -0
- package/python/biocore_model/tests/test_needs_events.py +205 -0
- package/python/biocore_model/tests/test_needs_sleep.py +54 -0
- package/python/biocore_model/tests/test_noise_bank.py +54 -0
- package/python/biocore_model/tests/test_numeric_schema.py +98 -0
- package/python/biocore_model/tests/test_output_protocol.py +65 -0
- package/python/biocore_model/tests/test_page_debug.py +147 -0
- package/python/biocore_model/tests/test_page_status_autonomy.py +215 -0
- package/python/biocore_model/tests/test_pipeline.py +345 -0
- package/python/biocore_model/tests/test_pipeline_api.py +192 -0
- package/python/biocore_model/tests/test_pipeline_mode.py +222 -0
- package/python/biocore_model/tests/test_plugin_config.py +85 -0
- package/python/biocore_model/tests/test_policy_learning.py +116 -0
- package/python/biocore_model/tests/test_possible_selves.py +88 -0
- package/python/biocore_model/tests/test_predictor.py +227 -0
- package/python/biocore_model/tests/test_preference_drift.py +142 -0
- package/python/biocore_model/tests/test_preset.py +142 -0
- package/python/biocore_model/tests/test_proactive_context.py +70 -0
- package/python/biocore_model/tests/test_proactive_history.py +90 -0
- package/python/biocore_model/tests/test_proactive_memory.py +141 -0
- package/python/biocore_model/tests/test_proactive_send_record.py +88 -0
- package/python/biocore_model/tests/test_probe_contract.py +71 -0
- package/python/biocore_model/tests/test_prospective.py +154 -0
- package/python/biocore_model/tests/test_provider_fallback.py +89 -0
- package/python/biocore_model/tests/test_reconstruction.py +111 -0
- package/python/biocore_model/tests/test_regulation_strategies.py +228 -0
- package/python/biocore_model/tests/test_relation_stage.py +114 -0
- package/python/biocore_model/tests/test_relationship_stage.py +59 -0
- package/python/biocore_model/tests/test_release_140.py +55 -0
- package/python/biocore_model/tests/test_repair_after_boundary.py +249 -0
- package/python/biocore_model/tests/test_reply_limit.py +86 -0
- package/python/biocore_model/tests/test_reply_limit_persist.py +48 -0
- package/python/biocore_model/tests/test_reply_split.py +52 -0
- package/python/biocore_model/tests/test_reply_split_astrbot.py +66 -0
- package/python/biocore_model/tests/test_reply_split_inject.py +53 -0
- package/python/biocore_model/tests/test_rerank.py +229 -0
- package/python/biocore_model/tests/test_rollback.py +175 -0
- package/python/biocore_model/tests/test_runtime.py +175 -0
- package/python/biocore_model/tests/test_runtime_active.py +205 -0
- package/python/biocore_model/tests/test_runtime_pipeline.py +1583 -0
- package/python/biocore_model/tests/test_runtime_proactive.py +231 -0
- package/python/biocore_model/tests/test_scheduler.py +79 -0
- package/python/biocore_model/tests/test_secrecy.py +105 -0
- package/python/biocore_model/tests/test_self_esteem.py +45 -0
- package/python/biocore_model/tests/test_self_intent.py +347 -0
- package/python/biocore_model/tests/test_self_interest.py +183 -0
- package/python/biocore_model/tests/test_self_layer_wiring.py +198 -0
- package/python/biocore_model/tests/test_self_verification.py +113 -0
- package/python/biocore_model/tests/test_situation.py +142 -0
- package/python/biocore_model/tests/test_sleep_circadian.py +41 -0
- package/python/biocore_model/tests/test_social_pain.py +49 -0
- package/python/biocore_model/tests/test_social_repair.py +67 -0
- package/python/biocore_model/tests/test_social_theory.py +78 -0
- package/python/biocore_model/tests/test_source_monitoring.py +204 -0
- package/python/biocore_model/tests/test_speak_like_group.py +36 -0
- package/python/biocore_model/tests/test_stages.py +226 -0
- package/python/biocore_model/tests/test_state_tag.py +86 -0
- package/python/biocore_model/tests/test_template_expr.py +65 -0
- package/python/biocore_model/tests/test_text_inference.py +70 -0
- package/python/biocore_model/tests/test_theta_params.py +115 -0
- package/python/biocore_model/tests/test_topic_control.py +200 -0
- package/python/biocore_model/tests/test_turn_memory.py +105 -0
- package/python/biocore_model/tests/test_turn_memory_panel.py +174 -0
- package/python/biocore_model/tests/test_ui_settings.py +84 -0
- package/python/biocore_model/tests/test_ui_settings_authority.py +179 -0
- package/python/biocore_model/tests/test_user_appraisal_async.py +155 -0
- package/python/biocore_model/tests/test_user_classify.py +122 -0
- package/python/biocore_model/tests/test_value_order.py +117 -0
- package/python/biocore_model/tests/test_voice_bank.py +105 -0
- package/python/biocore_model/tests/test_waiting_sense.py +216 -0
- package/python/biocore_model/tests/test_x_directives.py +101 -0
- package/python/biocore_model/tests/ui_compat_check.mjs +22 -0
- package/python/biocore_model/tests/view_mode_check.mjs +17 -0
- package/python/biocore_runtime/autonomy.py +208 -0
- package/python/biocore_runtime/core_state.py +302 -0
- package/python/biocore_runtime/governance.py +495 -0
- package/python/biocore_runtime/growth.py +528 -0
- package/python/biocore_runtime/pipeline.py +850 -23
- package/python/biocore_runtime/relationship_state.py +214 -0
- package/python/biocore_runtime/self_layer.py +103 -0
- package/python/biocore_runtime/service.py +162 -0
- package/python/biocore_runtime/standby.py +472 -0
- package/ui/panel-layout.js +83 -39
- package/ui/panel.css +885 -743
- package/ui/panel.html +356 -296
- package/ui/panel.js +739 -865
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""task-205 follow-up: proactive_tick policy lives in the python pipeline.
|
|
3
|
+
|
|
4
|
+
Covers disabled switches, gap/hourly-cap/cooldown, trust threshold, pending
|
|
5
|
+
commitments, text composition and cooldown booking. No Node required.
|
|
6
|
+
"""
|
|
7
|
+
import json
|
|
8
|
+
import tempfile
|
|
9
|
+
import time
|
|
10
|
+
import unittest
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from biocore_runtime import pipeline as pl
|
|
14
|
+
|
|
15
|
+
NOW_MS = 1_800_000_000_000
|
|
16
|
+
HOUR_MS = 3600 * 1000
|
|
17
|
+
LAST_HUMAN_MS = NOW_MS - 4 * HOUR_MS
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def switches(**overrides):
|
|
21
|
+
base = {
|
|
22
|
+
"proactiveEnabled": True,
|
|
23
|
+
"proactiveSend": True,
|
|
24
|
+
"proactiveMinGapMinutes": 180,
|
|
25
|
+
"proactivePerHour": 2,
|
|
26
|
+
"proactiveMinTrust": 0.6,
|
|
27
|
+
"proactiveMaxChars": 60,
|
|
28
|
+
"proactiveRequireOpenLoop": True,
|
|
29
|
+
"proactiveIntervalMinutes": 30,
|
|
30
|
+
}
|
|
31
|
+
base.update(overrides)
|
|
32
|
+
return base
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def make_store(tmp, session_id="s1", trust=0.8, texts=None, now_ms=NOW_MS):
|
|
36
|
+
root = Path(tmp) / "memory"
|
|
37
|
+
sessions = root / "sessions" / pl.sanitize_session_id(session_id)
|
|
38
|
+
sessions.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
rows = []
|
|
40
|
+
texts = texts if texts is not None else ("明天要去体检,记得问我结果",)
|
|
41
|
+
for index, text in enumerate(texts):
|
|
42
|
+
rows.append({"id": "e%d" % index, "text": text, "role": "user",
|
|
43
|
+
"ts": now_ms / 1000.0 - index * 60, "deleted": False})
|
|
44
|
+
(sessions / "memory.json").write_text(
|
|
45
|
+
json.dumps({"entries": rows}), encoding="utf-8")
|
|
46
|
+
states = root.parent / "sessions"
|
|
47
|
+
states.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
(states / (pl.sanitize_session_id(session_id) + ".json")).write_text(
|
|
49
|
+
json.dumps({"relationship": {"trust": trust, "closeness": trust}}),
|
|
50
|
+
encoding="utf-8")
|
|
51
|
+
return root
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ShapeTest(unittest.TestCase):
|
|
55
|
+
def test_output_contract(self):
|
|
56
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
57
|
+
store = make_store(tmp)
|
|
58
|
+
out = pl.proactive_tick("s1", LAST_HUMAN_MS, switches(), str(store), now=NOW_MS)
|
|
59
|
+
for key in ("ok", "should_send", "text", "reason", "gap_minutes"):
|
|
60
|
+
self.assertIn(key, out)
|
|
61
|
+
self.assertTrue(out["ok"])
|
|
62
|
+
self.assertTrue(out["should_send"])
|
|
63
|
+
self.assertEqual(out["reason"], "ok")
|
|
64
|
+
self.assertAlmostEqual(out["gap_minutes"], 240.0, places=1)
|
|
65
|
+
self.assertIn("体检", out["text"])
|
|
66
|
+
|
|
67
|
+
def test_dispatch_route(self):
|
|
68
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
69
|
+
store = make_store(tmp)
|
|
70
|
+
out = pl.dispatch("proactive_tick", {
|
|
71
|
+
"session_id": "s1", "last_human_ts": LAST_HUMAN_MS,
|
|
72
|
+
"switches": switches(), "store": str(store), "now": NOW_MS})
|
|
73
|
+
self.assertTrue(out["should_send"])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SwitchTest(unittest.TestCase):
|
|
77
|
+
def test_disabled(self):
|
|
78
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
79
|
+
out = pl.proactive_tick("s1", LAST_HUMAN_MS,
|
|
80
|
+
switches(proactiveEnabled=False),
|
|
81
|
+
str(make_store(tmp)), now=NOW_MS)
|
|
82
|
+
self.assertFalse(out["should_send"])
|
|
83
|
+
self.assertEqual(out["reason"], "disabled")
|
|
84
|
+
|
|
85
|
+
def test_send_off(self):
|
|
86
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
87
|
+
out = pl.proactive_tick("s1", LAST_HUMAN_MS,
|
|
88
|
+
switches(proactiveSend=False),
|
|
89
|
+
str(make_store(tmp)), now=NOW_MS)
|
|
90
|
+
self.assertFalse(out["should_send"])
|
|
91
|
+
self.assertEqual(out["reason"], "send_off")
|
|
92
|
+
|
|
93
|
+
def test_no_session(self):
|
|
94
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
95
|
+
out = pl.proactive_tick("", LAST_HUMAN_MS, switches(),
|
|
96
|
+
str(make_store(tmp)), now=NOW_MS)
|
|
97
|
+
self.assertFalse(out["should_send"])
|
|
98
|
+
self.assertEqual(out["reason"], "no_session")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class TimingTest(unittest.TestCase):
|
|
102
|
+
def test_too_soon(self):
|
|
103
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
104
|
+
out = pl.proactive_tick("s1", NOW_MS - 10 * 60 * 1000, switches(),
|
|
105
|
+
str(make_store(tmp)), now=NOW_MS)
|
|
106
|
+
self.assertEqual(out["reason"], "too_soon")
|
|
107
|
+
self.assertAlmostEqual(out["gap_minutes"], 10.0, places=1)
|
|
108
|
+
|
|
109
|
+
def test_hourly_cap(self):
|
|
110
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
111
|
+
store = make_store(tmp)
|
|
112
|
+
(store / "proactive.json").write_text(json.dumps({
|
|
113
|
+
"lastSentAt": NOW_MS - 200 * 60 * 1000,
|
|
114
|
+
"sent": [NOW_MS - 30 * 60 * 1000, NOW_MS - 40 * 60 * 1000],
|
|
115
|
+
}), encoding="utf-8")
|
|
116
|
+
out = pl.proactive_tick("s1", LAST_HUMAN_MS, switches(), str(store),
|
|
117
|
+
now=NOW_MS)
|
|
118
|
+
self.assertEqual(out["reason"], "hourly_cap")
|
|
119
|
+
|
|
120
|
+
def test_cooldown(self):
|
|
121
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
122
|
+
store = make_store(tmp)
|
|
123
|
+
(store / "proactive.json").write_text(json.dumps({
|
|
124
|
+
"lastSentAt": NOW_MS - 30 * 60 * 1000, "sent": [],
|
|
125
|
+
}), encoding="utf-8")
|
|
126
|
+
out = pl.proactive_tick("s1", LAST_HUMAN_MS, switches(), str(store),
|
|
127
|
+
now=NOW_MS)
|
|
128
|
+
self.assertEqual(out["reason"], "cooldown")
|
|
129
|
+
|
|
130
|
+
def test_seconds_timestamp_accepted(self):
|
|
131
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
132
|
+
out = pl.proactive_tick("s1", LAST_HUMAN_MS / 1000.0, switches(),
|
|
133
|
+
str(make_store(tmp)), now=NOW_MS)
|
|
134
|
+
self.assertTrue(out["should_send"])
|
|
135
|
+
self.assertAlmostEqual(out["gap_minutes"], 240.0, places=1)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class RelationshipTest(unittest.TestCase):
|
|
139
|
+
def test_not_close(self):
|
|
140
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
141
|
+
out = pl.proactive_tick("s1", LAST_HUMAN_MS, switches(),
|
|
142
|
+
str(make_store(tmp, trust=0.2)),
|
|
143
|
+
now=NOW_MS)
|
|
144
|
+
self.assertEqual(out["reason"], "not_close")
|
|
145
|
+
self.assertAlmostEqual(out["trust"], 0.2, places=3)
|
|
146
|
+
|
|
147
|
+
def test_explicit_state_file(self):
|
|
148
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
149
|
+
store = make_store(tmp, trust=0.1)
|
|
150
|
+
alt = Path(tmp) / "alt.json"
|
|
151
|
+
alt.write_text(json.dumps({"relationship": {"trust": 0.9}}),
|
|
152
|
+
encoding="utf-8")
|
|
153
|
+
out = pl.proactive_tick("s1", LAST_HUMAN_MS, switches(), str(store),
|
|
154
|
+
now=NOW_MS, state_file=str(alt))
|
|
155
|
+
self.assertTrue(out["should_send"])
|
|
156
|
+
|
|
157
|
+
def test_missing_state_uses_default_trust(self):
|
|
158
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
159
|
+
store = Path(tmp) / "memory"
|
|
160
|
+
(store / "sessions" / "s1").mkdir(parents=True, exist_ok=True)
|
|
161
|
+
(store / "sessions" / "s1" / "memory.json").write_text(
|
|
162
|
+
json.dumps({"entries": [{"text": "明天要体检",
|
|
163
|
+
"ts": time.time()}]}), encoding="utf-8")
|
|
164
|
+
out = pl.proactive_tick("s1", LAST_HUMAN_MS, switches(), str(store),
|
|
165
|
+
now=NOW_MS)
|
|
166
|
+
self.assertEqual(out["reason"], "not_close")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class CommitmentTest(unittest.TestCase):
|
|
170
|
+
def test_no_commitment(self):
|
|
171
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
172
|
+
out = pl.proactive_tick(
|
|
173
|
+
"s1", LAST_HUMAN_MS, switches(),
|
|
174
|
+
str(make_store(tmp, texts=("今天天气不错", "晚饭吃了面"))),
|
|
175
|
+
now=NOW_MS)
|
|
176
|
+
self.assertEqual(out["reason"], "no_commitment")
|
|
177
|
+
|
|
178
|
+
def test_optional_commitment_falls_back(self):
|
|
179
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
180
|
+
out = pl.proactive_tick(
|
|
181
|
+
"s1", LAST_HUMAN_MS,
|
|
182
|
+
switches(proactiveRequireOpenLoop=False),
|
|
183
|
+
str(make_store(tmp, texts=("今天天气不错",))), now=NOW_MS)
|
|
184
|
+
self.assertTrue(out["should_send"])
|
|
185
|
+
self.assertIn("在忙吗", out["text"])
|
|
186
|
+
|
|
187
|
+
def test_due_hint_preferred(self):
|
|
188
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
189
|
+
texts = ("记得给朋友回消息", "明天要去体检")
|
|
190
|
+
out = pl.proactive_tick("s1", LAST_HUMAN_MS, switches(),
|
|
191
|
+
str(make_store(tmp, texts=texts)),
|
|
192
|
+
now=NOW_MS)
|
|
193
|
+
self.assertTrue(out["should_send"])
|
|
194
|
+
self.assertIn("明天要去体检", out["text"])
|
|
195
|
+
|
|
196
|
+
def test_max_chars_cap(self):
|
|
197
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
198
|
+
long_text = "明天要做的事情" * 10
|
|
199
|
+
out = pl.proactive_tick("s1", LAST_HUMAN_MS,
|
|
200
|
+
switches(proactiveMaxChars=24),
|
|
201
|
+
str(make_store(tmp, texts=(long_text,))),
|
|
202
|
+
now=NOW_MS)
|
|
203
|
+
self.assertTrue(out["should_send"])
|
|
204
|
+
self.assertLessEqual(len(out["text"]), 24)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class BookingTest(unittest.TestCase):
|
|
208
|
+
def test_booking_written_and_second_tick_cooldowns(self):
|
|
209
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
210
|
+
store = make_store(tmp)
|
|
211
|
+
first = pl.proactive_tick("s1", LAST_HUMAN_MS, switches(),
|
|
212
|
+
str(store), now=NOW_MS)
|
|
213
|
+
self.assertTrue(first["should_send"])
|
|
214
|
+
state = json.loads((store / "proactive.json").read_text(encoding="utf-8"))
|
|
215
|
+
self.assertEqual(state["lastSentAt"], float(NOW_MS))
|
|
216
|
+
self.assertTrue(state["lastText"])
|
|
217
|
+
second = pl.proactive_tick("s1", LAST_HUMAN_MS, switches(),
|
|
218
|
+
str(store), now=NOW_MS + 60 * 1000)
|
|
219
|
+
self.assertFalse(second["should_send"])
|
|
220
|
+
self.assertEqual(second["reason"], "cooldown")
|
|
221
|
+
|
|
222
|
+
def test_no_booking_when_skipped(self):
|
|
223
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
224
|
+
store = make_store(tmp)
|
|
225
|
+
pl.proactive_tick("s1", NOW_MS - 60 * 1000, switches(), str(store),
|
|
226
|
+
now=NOW_MS)
|
|
227
|
+
self.assertFalse((store / "proactive.json").exists())
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
if __name__ == "__main__":
|
|
231
|
+
unittest.main()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""HUMAN-1 scheduler tests: dimensions, lazy decay, ranking."""
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from biocore_model import scheduler as sc
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def items():
|
|
9
|
+
return [
|
|
10
|
+
{"id": "a", "due_ts": 1000.0, "entity": "u1", "channel": "chat", "weight": 1.0, "last_ts": 0.0},
|
|
11
|
+
{"id": "b", "due_ts": 1000.0, "entity": "u2", "channel": "chat", "weight": 1.0, "last_ts": 0.0},
|
|
12
|
+
{"id": "c", "due_ts": 100.0, "entity": "u1", "channel": "note", "weight": 1.0, "last_ts": 0.0},
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TimeTest(unittest.TestCase):
|
|
17
|
+
def test_time_score_peaks_at_due(self):
|
|
18
|
+
row = {"due_ts": 1000.0}
|
|
19
|
+
self.assertAlmostEqual(sc.time_score(row, 1000.0), 1.0)
|
|
20
|
+
self.assertLess(sc.time_score(row, 1000.0 + 3600.0), 1.0)
|
|
21
|
+
|
|
22
|
+
def test_time_score_symmetric_distance(self):
|
|
23
|
+
row = {"due_ts": 1000.0}
|
|
24
|
+
self.assertAlmostEqual(sc.time_score(row, 1000.0 - 7200.0),
|
|
25
|
+
sc.time_score(row, 1000.0 + 7200.0))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DimensionTest(unittest.TestCase):
|
|
29
|
+
def test_entity_match_beats_miss(self):
|
|
30
|
+
self.assertGreater(sc.entity_score({"entity": "u1"}, "u1"),
|
|
31
|
+
sc.entity_score({"entity": "u1"}, "u2"))
|
|
32
|
+
|
|
33
|
+
def test_channel_match_beats_miss(self):
|
|
34
|
+
self.assertGreater(sc.channel_score({"channel": "chat"}, "chat"),
|
|
35
|
+
sc.channel_score({"channel": "chat"}, "note"))
|
|
36
|
+
|
|
37
|
+
def test_no_filter_is_neutral(self):
|
|
38
|
+
self.assertEqual(sc.entity_score({"entity": "u1"}), 1.0)
|
|
39
|
+
self.assertEqual(sc.channel_score({"channel": "chat"}), 1.0)
|
|
40
|
+
|
|
41
|
+
def test_custom_weights_change_order(self):
|
|
42
|
+
now = 1000.0
|
|
43
|
+
default = sc.score(items()[1], now, entity="u1", channel="chat")
|
|
44
|
+
entity_heavy = sc.score(items()[1], now, entity="u1", channel="chat",
|
|
45
|
+
weights={"time": 0.1, "entity": 0.8, "channel": 0.1})
|
|
46
|
+
self.assertNotAlmostEqual(default, entity_heavy)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class DecayRankTest(unittest.TestCase):
|
|
50
|
+
def test_lazy_decay_halves_in_one_half_life(self):
|
|
51
|
+
row = sc.lazy_decay({"weight": 1.0, "last_ts": 0.0}, 12 * 3600.0)
|
|
52
|
+
self.assertAlmostEqual(row["weight"], 0.5, places=4)
|
|
53
|
+
|
|
54
|
+
def test_lazy_decay_does_not_mutate(self):
|
|
55
|
+
source = {"weight": 1.0, "last_ts": 0.0, "id": "x"}
|
|
56
|
+
sc.lazy_decay(source, 999999.0)
|
|
57
|
+
self.assertEqual(source, {"weight": 1.0, "last_ts": 0.0, "id": "x"})
|
|
58
|
+
|
|
59
|
+
def test_rank_is_descending_and_deterministic(self):
|
|
60
|
+
rows = sc.rank(items(), 1000.0, entity="u1", channel="chat")
|
|
61
|
+
scores = [row["score"] for row in rows]
|
|
62
|
+
self.assertEqual(scores, sorted(scores, reverse=True))
|
|
63
|
+
self.assertEqual(rows, sc.rank(items(), 1000.0, entity="u1", channel="chat"))
|
|
64
|
+
|
|
65
|
+
def test_pick_and_limit(self):
|
|
66
|
+
self.assertEqual(sc.pick(items(), 1000.0, entity="u1", channel="chat")["item"]["id"], "a")
|
|
67
|
+
self.assertEqual(len(sc.rank(items(), 1000.0, limit=2)), 2)
|
|
68
|
+
self.assertEqual(sc.rank([], 1000.0), [])
|
|
69
|
+
|
|
70
|
+
def test_due_window_filters(self):
|
|
71
|
+
rows = sc.due(items(), 1000.0, window_seconds=600.0)
|
|
72
|
+
ids = [row["id"] for row in rows]
|
|
73
|
+
self.assertIn("a", ids)
|
|
74
|
+
self.assertIn("b", ids)
|
|
75
|
+
self.assertNotIn("c", ids)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
unittest.main()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from biocore_model import secrecy as sc
|
|
5
|
+
|
|
6
|
+
NOW = 1_700_000_000
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SecrecyDecisionTest(unittest.TestCase):
|
|
10
|
+
def test_low_sensitivity_deep_relation_safe_setting_tells(self):
|
|
11
|
+
out = sc.decide(0.10, 0.85, 0.90)
|
|
12
|
+
self.assertEqual(out["action"], "tell")
|
|
13
|
+
|
|
14
|
+
def test_high_sensitivity_shallow_relation_unsafe_setting_withholds(self):
|
|
15
|
+
out = sc.decide(0.90, 0.15, 0.15)
|
|
16
|
+
self.assertEqual(out["action"], "withhold")
|
|
17
|
+
|
|
18
|
+
def test_high_sensitivity_close_relation_reframes(self):
|
|
19
|
+
out = sc.decide(0.80, 0.85, 0.50)
|
|
20
|
+
self.assertEqual(out["action"], "reframe")
|
|
21
|
+
|
|
22
|
+
def test_action_always_known(self):
|
|
23
|
+
for sentence in (None, -5, 5, "x"):
|
|
24
|
+
out = sc.decide(sentence, sentence, sentence)
|
|
25
|
+
self.assertIn(out["action"], sc.ACTIONS)
|
|
26
|
+
|
|
27
|
+
def test_decision_is_deterministic(self):
|
|
28
|
+
first = sc.decide(0.8, 0.85, 0.5)
|
|
29
|
+
second = sc.decide(0.8, 0.85, 0.5)
|
|
30
|
+
self.assertEqual(first, second)
|
|
31
|
+
|
|
32
|
+
def test_safer_setting_raises_tell_score(self):
|
|
33
|
+
unsafe = sc.tell_score(0.5, 0.5, 0.2)
|
|
34
|
+
safe = sc.tell_score(0.5, 0.5, 0.9)
|
|
35
|
+
self.assertGreater(safe, unsafe)
|
|
36
|
+
|
|
37
|
+
def test_confidence_is_bounded(self):
|
|
38
|
+
out = sc.decide(0.9, 0.1, 0.1)
|
|
39
|
+
self.assertGreaterEqual(out["confidence"], 0.0)
|
|
40
|
+
self.assertLessEqual(out["confidence"], 1.0)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SecrecyCostTest(unittest.TestCase):
|
|
44
|
+
def test_telling_has_no_hidden_cost(self):
|
|
45
|
+
self.assertEqual(sc.hidden_cost("tell", 0.9, 0.9), 0.0)
|
|
46
|
+
self.assertEqual(sc.decide(0.1, 0.9, 0.9)["pressure_add"], 0.0)
|
|
47
|
+
|
|
48
|
+
def test_withholding_has_a_cost(self):
|
|
49
|
+
self.assertGreater(sc.hidden_cost("withhold", 0.8, 0.5), 0.0)
|
|
50
|
+
|
|
51
|
+
def test_reframe_costs_less_than_withholding(self):
|
|
52
|
+
withheld = sc.hidden_cost("withhold", 0.8, 0.5)
|
|
53
|
+
softened = sc.hidden_cost("reframe", 0.8, 0.5)
|
|
54
|
+
self.assertGreater(withheld, softened)
|
|
55
|
+
self.assertGreater(softened, 0.0)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SecrecyPressureTest(unittest.TestCase):
|
|
59
|
+
def test_pressure_accumulates_across_hidden_turns(self):
|
|
60
|
+
first = sc.update(sc.initial(NOW - 3600), 0.9, 0.15, 0.15, now=NOW)
|
|
61
|
+
second = sc.update(first, 0.9, 0.15, 0.15, now=NOW + 3600)
|
|
62
|
+
self.assertGreater(first["pressure"], 0.0)
|
|
63
|
+
self.assertGreater(second["pressure"], first["pressure"])
|
|
64
|
+
self.assertEqual(second["withholds"], 2)
|
|
65
|
+
|
|
66
|
+
def test_same_turn_is_idempotent(self):
|
|
67
|
+
once = sc.update(sc.initial(NOW - 10), 0.9, 0.15, 0.15, now=NOW)
|
|
68
|
+
twice = sc.update(once, 0.9, 0.15, 0.15, now=NOW)
|
|
69
|
+
self.assertEqual(twice["pressure"], once["pressure"])
|
|
70
|
+
self.assertEqual(twice["withholds"], once["withholds"])
|
|
71
|
+
|
|
72
|
+
def test_telling_does_not_raise_withhold_count(self):
|
|
73
|
+
state = sc.update(sc.initial(NOW - 10), 0.1, 0.9, 0.9, now=NOW)
|
|
74
|
+
self.assertEqual(state["withholds"], 0)
|
|
75
|
+
self.assertEqual(state["pressure"], 0.0)
|
|
76
|
+
|
|
77
|
+
def test_pressure_is_capped_at_one(self):
|
|
78
|
+
state = dict(sc.initial(NOW - 10), pressure=0.99)
|
|
79
|
+
loaded = sc.update(state, 0.9, 0.15, 0.15, now=NOW)
|
|
80
|
+
self.assertLessEqual(loaded["pressure"], 1.0)
|
|
81
|
+
|
|
82
|
+
def test_pressure_decays_over_time(self):
|
|
83
|
+
state = dict(sc.initial(NOW), pressure=0.50)
|
|
84
|
+
rested = sc.update(state, 0.1, 0.9, 0.9, now=NOW + 10 * 3600)
|
|
85
|
+
self.assertAlmostEqual(rested["pressure"], 0.30)
|
|
86
|
+
|
|
87
|
+
def test_contained_flag(self):
|
|
88
|
+
self.assertTrue(sc.contained(dict(sc.initial(NOW), pressure=0.30)))
|
|
89
|
+
self.assertFalse(sc.contained(dict(sc.initial(NOW), pressure=0.70)))
|
|
90
|
+
|
|
91
|
+
def test_view_fields(self):
|
|
92
|
+
state = sc.update(sc.initial(NOW - 10), 0.9, 0.15, 0.15, now=NOW)
|
|
93
|
+
panel = sc.view(state)
|
|
94
|
+
for key in ("pressure", "contained", "withholds", "last_action",
|
|
95
|
+
"last_pressure_add"):
|
|
96
|
+
self.assertIn(key, panel)
|
|
97
|
+
|
|
98
|
+
def test_bad_inputs_do_not_crash(self):
|
|
99
|
+
state = sc.update(None, None, None, None)
|
|
100
|
+
self.assertIn(state["last_action"], sc.ACTIONS)
|
|
101
|
+
self.assertEqual(sc.hidden_cost(None, None, None), 0.0)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
unittest.main()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import unittest
|
|
3
|
+
from biocore_model import self_esteem as se
|
|
4
|
+
|
|
5
|
+
NOW = 1_700_000_000
|
|
6
|
+
|
|
7
|
+
class SelfEsteemTest(unittest.TestCase):
|
|
8
|
+
def test_initial_baseline(self):
|
|
9
|
+
state = se.initial(NOW)
|
|
10
|
+
self.assertAlmostEqual(state['value'], se.BASELINE)
|
|
11
|
+
def test_praised_raises(self):
|
|
12
|
+
self.assertAlmostEqual(se.update(se.initial(NOW), 'praised', NOW)['value'], 0.61)
|
|
13
|
+
def test_replied_raises(self):
|
|
14
|
+
self.assertAlmostEqual(se.update(se.initial(NOW), 'replied', NOW)['value'], 0.61)
|
|
15
|
+
def test_ignored_lowers(self):
|
|
16
|
+
self.assertAlmostEqual(se.update(se.initial(NOW), 'ignored', NOW)['value'], 0.5)
|
|
17
|
+
def test_insulted_lowers(self):
|
|
18
|
+
self.assertAlmostEqual(se.update(se.initial(NOW), 'insulted', NOW)['value'], 0.5)
|
|
19
|
+
def test_decay_toward_baseline(self):
|
|
20
|
+
state = {'value': 0.2, 'baseline': se.BASELINE, 'last_decay_ts': NOW - 1}
|
|
21
|
+
self.assertGreater(se.decay(state, NOW)['value'], 0.2)
|
|
22
|
+
def test_decay_same_now_idempotent(self):
|
|
23
|
+
state = {'value': 0.3, 'baseline': se.BASELINE, 'last_decay_ts': NOW}
|
|
24
|
+
self.assertEqual(se.decay(state, NOW)['value'], 0.3)
|
|
25
|
+
def test_update_same_now_does_not_double(self):
|
|
26
|
+
state = se.update(se.initial(NOW), 'praised', NOW)
|
|
27
|
+
again = se.update(se.initial(NOW), 'praised', NOW)
|
|
28
|
+
self.assertAlmostEqual(again['value'], state['value'])
|
|
29
|
+
def test_clamp_upper(self):
|
|
30
|
+
state = {'value': 0.99, 'baseline': se.BASELINE}
|
|
31
|
+
self.assertLessEqual(se.update(state, 'praised', NOW)['value'], 1.0)
|
|
32
|
+
def test_clamp_lower(self):
|
|
33
|
+
state = {'value': 0.01, 'baseline': se.BASELINE}
|
|
34
|
+
self.assertGreaterEqual(se.update(state, 'insulted', NOW)['value'], 0.0)
|
|
35
|
+
def test_unknown_event_no_change(self):
|
|
36
|
+
self.assertAlmostEqual(se.update(se.initial(NOW), 'bogus', NOW)['value'], se.BASELINE)
|
|
37
|
+
def test_confidence_low_esteem(self):
|
|
38
|
+
self.assertLess(se.confidence_delta(0.2), 0) # lower esteem -> lower confidence
|
|
39
|
+
def test_view_fields(self):
|
|
40
|
+
view = se.view(se.update(se.initial(NOW), 'praised', NOW))
|
|
41
|
+
for key in ('value', 'baseline', 'last_event', 'confidence_delta'):
|
|
42
|
+
self.assertIn(key, view)
|
|
43
|
+
|
|
44
|
+
if __name__ == '__main__':
|
|
45
|
+
unittest.main()
|