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.
Files changed (245) hide show
  1. package/README.md +11 -0
  2. package/lib/core-worker.js +268 -0
  3. package/lib/index.js +160 -70
  4. package/lib/modules/autonomy.js +125 -0
  5. package/lib/modules/cache-stats.js +48 -0
  6. package/lib/modules/core-turn.js +24 -6
  7. package/lib/modules/expression-inject.js +0 -10
  8. package/lib/modules/expression-refresh.js +0 -9
  9. package/lib/modules/memory-capture.js +2 -1
  10. package/lib/modules/memory-inject.js +50 -5
  11. package/lib/modules/memory-tools.js +7 -4
  12. package/lib/modules/python-env.js +46 -0
  13. package/lib/modules/safety-gate.js +0 -3
  14. package/lib/modules/series.js +54 -0
  15. package/lib/modules/switch.js +16 -0
  16. package/lib/py-bridge.js +81 -15
  17. package/package.json +1 -1
  18. package/python/biocore_memory/core/config.py +138 -127
  19. package/python/biocore_memory/engine.py +2653 -2476
  20. package/python/biocore_memory/recall/embedding.py +129 -0
  21. package/python/biocore_memory/recall/feedback.py +202 -0
  22. package/python/biocore_model/preference_drift.py +255 -0
  23. package/python/biocore_model/tests/__init__.py +1 -0
  24. package/python/biocore_model/tests/core_mode_ui_check.mjs +63 -0
  25. package/python/biocore_model/tests/core_status_check.mjs +29 -0
  26. package/python/biocore_model/tests/dom_safe_check.mjs +22 -0
  27. package/python/biocore_model/tests/leak_samples_run.py +18 -0
  28. package/python/biocore_model/tests/orb_ring_check.mjs +20 -0
  29. package/python/biocore_model/tests/panel_html_check.mjs +63 -0
  30. package/python/biocore_model/tests/persona_flow_check.mjs +17 -0
  31. package/python/biocore_model/tests/render_guard_check.mjs +19 -0
  32. package/python/biocore_model/tests/switch_groups_check.mjs +94 -0
  33. package/python/biocore_model/tests/tabs_state_check.mjs +24 -0
  34. package/python/biocore_model/tests/test_affective_labor.py +102 -0
  35. package/python/biocore_model/tests/test_appraisal.py +137 -0
  36. package/python/biocore_model/tests/test_appraisal_action.py +55 -0
  37. package/python/biocore_model/tests/test_arch_convergence.py +35 -0
  38. package/python/biocore_model/tests/test_attachment_behavior.py +95 -0
  39. package/python/biocore_model/tests/test_attachment_regulation.py +63 -0
  40. package/python/biocore_model/tests/test_attachment_state.py +29 -0
  41. package/python/biocore_model/tests/test_attribution.py +56 -0
  42. package/python/biocore_model/tests/test_attributor.py +108 -0
  43. package/python/biocore_model/tests/test_autonomic.py +69 -0
  44. package/python/biocore_model/tests/test_autonomy_accumulator.py +84 -0
  45. package/python/biocore_model/tests/test_autonomy_affect.py +62 -0
  46. package/python/biocore_model/tests/test_autonomy_blame.py +44 -0
  47. package/python/biocore_model/tests/test_autonomy_caps.py +29 -0
  48. package/python/biocore_model/tests/test_autonomy_check.py +40 -0
  49. package/python/biocore_model/tests/test_autonomy_clingy.py +41 -0
  50. package/python/biocore_model/tests/test_autonomy_cooldown_selfcontact.py +138 -0
  51. package/python/biocore_model/tests/test_autonomy_frustration.py +42 -0
  52. package/python/biocore_model/tests/test_autonomy_hourly_limit.py +46 -0
  53. package/python/biocore_model/tests/test_autonomy_loop.py +64 -0
  54. package/python/biocore_model/tests/test_autonomy_mode.py +37 -0
  55. package/python/biocore_model/tests/test_autonomy_model.py +60 -0
  56. package/python/biocore_model/tests/test_autonomy_model_v2.py +68 -0
  57. package/python/biocore_model/tests/test_autonomy_module_text.py +104 -0
  58. package/python/biocore_model/tests/test_autonomy_params.py +163 -0
  59. package/python/biocore_model/tests/test_autonomy_reason.py +34 -0
  60. package/python/biocore_model/tests/test_autonomy_self.py +153 -0
  61. package/python/biocore_model/tests/test_autonomy_send_gate.py +38 -0
  62. package/python/biocore_model/tests/test_autonomy_silence.py +37 -0
  63. package/python/biocore_model/tests/test_autonomy_status.py +35 -0
  64. package/python/biocore_model/tests/test_autonomy_test_trigger.py +24 -0
  65. package/python/biocore_model/tests/test_autonomy_tick.py +148 -0
  66. package/python/biocore_model/tests/test_autonomy_v4.py +64 -0
  67. package/python/biocore_model/tests/test_autonomy_v5.py +43 -0
  68. package/python/biocore_model/tests/test_autonomy_v7.py +48 -0
  69. package/python/biocore_model/tests/test_autonomy_v_theta.py +85 -0
  70. package/python/biocore_model/tests/test_batch1_wiring.py +231 -0
  71. package/python/biocore_model/tests/test_batch2_wiring.py +231 -0
  72. package/python/biocore_model/tests/test_batch3_wiring.py +223 -0
  73. package/python/biocore_model/tests/test_bio_tick.py +202 -0
  74. package/python/biocore_model/tests/test_boundary.py +183 -0
  75. package/python/biocore_model/tests/test_boundary_gate.py +295 -0
  76. package/python/biocore_model/tests/test_brain_split.py +99 -0
  77. package/python/biocore_model/tests/test_co_recall.py +350 -0
  78. package/python/biocore_model/tests/test_commitment_extractor.py +197 -0
  79. package/python/biocore_model/tests/test_compassion_fatigue.py +84 -0
  80. package/python/biocore_model/tests/test_connection_bids.py +69 -0
  81. package/python/biocore_model/tests/test_core_bridge.py +88 -0
  82. package/python/biocore_model/tests/test_core_bridge_episode.py +14 -0
  83. package/python/biocore_model/tests/test_core_bridge_forget.py +17 -0
  84. package/python/biocore_model/tests/test_core_bridge_identity.py +15 -0
  85. package/python/biocore_model/tests/test_core_bridge_media.py +17 -0
  86. package/python/biocore_model/tests/test_core_bridge_relation.py +17 -0
  87. package/python/biocore_model/tests/test_core_bridge_summary.py +17 -0
  88. package/python/biocore_model/tests/test_core_client.py +156 -0
  89. package/python/biocore_model/tests/test_core_commands.py +146 -0
  90. package/python/biocore_model/tests/test_core_mode.py +161 -0
  91. package/python/biocore_model/tests/test_core_state.py +240 -0
  92. package/python/biocore_model/tests/test_criticality.py +61 -0
  93. package/python/biocore_model/tests/test_cross_session_copy.py +67 -0
  94. package/python/biocore_model/tests/test_debug_values.py +169 -0
  95. package/python/biocore_model/tests/test_defense_mechanisms.py +177 -0
  96. package/python/biocore_model/tests/test_delta_budget.py +157 -0
  97. package/python/biocore_model/tests/test_diag_trace.py +201 -0
  98. package/python/biocore_model/tests/test_directives_compat.py +698 -0
  99. package/python/biocore_model/tests/test_dissonance.py +97 -0
  100. package/python/biocore_model/tests/test_drift_sign.py +87 -0
  101. package/python/biocore_model/tests/test_embedding_fallback.py +277 -0
  102. package/python/biocore_model/tests/test_embedding_local.py +279 -0
  103. package/python/biocore_model/tests/test_embedding_mode.py +175 -0
  104. package/python/biocore_model/tests/test_embedding_modes.py +342 -0
  105. package/python/biocore_model/tests/test_emotion_geometry.py +114 -0
  106. package/python/biocore_model/tests/test_emotion_probe.py +214 -0
  107. package/python/biocore_model/tests/test_emotion_probe_concurrency.py +128 -0
  108. package/python/biocore_model/tests/test_entity_graph.py +198 -0
  109. package/python/biocore_model/tests/test_entity_resolve.py +446 -0
  110. package/python/biocore_model/tests/test_envy.py +173 -0
  111. package/python/biocore_model/tests/test_exit_or_silence.py +209 -0
  112. package/python/biocore_model/tests/test_expr_guard.py +1 -0
  113. package/python/biocore_model/tests/test_expr_intent.py +66 -0
  114. package/python/biocore_model/tests/test_expr_state.py +66 -0
  115. package/python/biocore_model/tests/test_expr_template.py +71 -0
  116. package/python/biocore_model/tests/test_expr_tone.py +41 -0
  117. package/python/biocore_model/tests/test_extreme_scenarios.py +147 -0
  118. package/python/biocore_model/tests/test_feedback_flow.py +185 -0
  119. package/python/biocore_model/tests/test_feedback_guard.py +65 -0
  120. package/python/biocore_model/tests/test_feedback_llm.py +59 -0
  121. package/python/biocore_model/tests/test_feedback_llm_inferred.py +140 -0
  122. package/python/biocore_model/tests/test_feedback_memory.py +63 -0
  123. package/python/biocore_model/tests/test_feedback_user.py +61 -0
  124. package/python/biocore_model/tests/test_frustration_curve.py +66 -0
  125. package/python/biocore_model/tests/test_global_bio_state.py +116 -0
  126. package/python/biocore_model/tests/test_governance.py +181 -0
  127. package/python/biocore_model/tests/test_granularity.py +133 -0
  128. package/python/biocore_model/tests/test_graph_extract.py +224 -0
  129. package/python/biocore_model/tests/test_graph_recall.py +226 -0
  130. package/python/biocore_model/tests/test_graph_sqlite.py +298 -0
  131. package/python/biocore_model/tests/test_growth.py +301 -0
  132. package/python/biocore_model/tests/test_intent_render.py +60 -0
  133. package/python/biocore_model/tests/test_intero_regulation.py +224 -0
  134. package/python/biocore_model/tests/test_intero_style.py +146 -0
  135. package/python/biocore_model/tests/test_item3_numeric.py +105 -0
  136. package/python/biocore_model/tests/test_item6_numeric.py +116 -0
  137. package/python/biocore_model/tests/test_layered_memory.py +174 -0
  138. package/python/biocore_model/tests/test_leak_guard.py +75 -0
  139. package/python/biocore_model/tests/test_learning_probe.py +93 -0
  140. package/python/biocore_model/tests/test_life_loop.py +222 -0
  141. package/python/biocore_model/tests/test_life_loop_10s.py +131 -0
  142. package/python/biocore_model/tests/test_local_embedding_health.py +258 -0
  143. package/python/biocore_model/tests/test_memory_affect_encoding.py +79 -0
  144. package/python/biocore_model/tests/test_memory_backend.py +332 -0
  145. package/python/biocore_model/tests/test_memory_dedup.py +316 -0
  146. package/python/biocore_model/tests/test_memory_dual.py +219 -0
  147. package/python/biocore_model/tests/test_memory_dual_wiring.py +219 -0
  148. package/python/biocore_model/tests/test_memory_feedback.py +241 -0
  149. package/python/biocore_model/tests/test_memory_fixes.py +84 -0
  150. package/python/biocore_model/tests/test_memory_forgetting.py +58 -0
  151. package/python/biocore_model/tests/test_memory_inject.py +211 -0
  152. package/python/biocore_model/tests/test_memory_mood_recall.py +118 -0
  153. package/python/biocore_model/tests/test_memory_store.py +372 -0
  154. package/python/biocore_model/tests/test_metacog.py +103 -0
  155. package/python/biocore_model/tests/test_migrate.py +245 -0
  156. package/python/biocore_model/tests/test_migrate_store.py +194 -0
  157. package/python/biocore_model/tests/test_mind_wander.py +65 -0
  158. package/python/biocore_model/tests/test_mood.py +35 -0
  159. package/python/biocore_model/tests/test_narrative.py +114 -0
  160. package/python/biocore_model/tests/test_needs_esteem.py +51 -0
  161. package/python/biocore_model/tests/test_needs_events.py +205 -0
  162. package/python/biocore_model/tests/test_needs_sleep.py +54 -0
  163. package/python/biocore_model/tests/test_noise_bank.py +54 -0
  164. package/python/biocore_model/tests/test_numeric_schema.py +98 -0
  165. package/python/biocore_model/tests/test_output_protocol.py +65 -0
  166. package/python/biocore_model/tests/test_page_debug.py +147 -0
  167. package/python/biocore_model/tests/test_page_status_autonomy.py +215 -0
  168. package/python/biocore_model/tests/test_pipeline.py +345 -0
  169. package/python/biocore_model/tests/test_pipeline_api.py +192 -0
  170. package/python/biocore_model/tests/test_pipeline_mode.py +222 -0
  171. package/python/biocore_model/tests/test_plugin_config.py +85 -0
  172. package/python/biocore_model/tests/test_policy_learning.py +116 -0
  173. package/python/biocore_model/tests/test_possible_selves.py +88 -0
  174. package/python/biocore_model/tests/test_predictor.py +227 -0
  175. package/python/biocore_model/tests/test_preference_drift.py +142 -0
  176. package/python/biocore_model/tests/test_preset.py +142 -0
  177. package/python/biocore_model/tests/test_proactive_context.py +70 -0
  178. package/python/biocore_model/tests/test_proactive_history.py +90 -0
  179. package/python/biocore_model/tests/test_proactive_memory.py +141 -0
  180. package/python/biocore_model/tests/test_proactive_send_record.py +88 -0
  181. package/python/biocore_model/tests/test_probe_contract.py +71 -0
  182. package/python/biocore_model/tests/test_prospective.py +154 -0
  183. package/python/biocore_model/tests/test_provider_fallback.py +89 -0
  184. package/python/biocore_model/tests/test_reconstruction.py +111 -0
  185. package/python/biocore_model/tests/test_regulation_strategies.py +228 -0
  186. package/python/biocore_model/tests/test_relation_stage.py +114 -0
  187. package/python/biocore_model/tests/test_relationship_stage.py +59 -0
  188. package/python/biocore_model/tests/test_release_140.py +55 -0
  189. package/python/biocore_model/tests/test_repair_after_boundary.py +249 -0
  190. package/python/biocore_model/tests/test_reply_limit.py +86 -0
  191. package/python/biocore_model/tests/test_reply_limit_persist.py +48 -0
  192. package/python/biocore_model/tests/test_reply_split.py +52 -0
  193. package/python/biocore_model/tests/test_reply_split_astrbot.py +66 -0
  194. package/python/biocore_model/tests/test_reply_split_inject.py +53 -0
  195. package/python/biocore_model/tests/test_rerank.py +229 -0
  196. package/python/biocore_model/tests/test_rollback.py +175 -0
  197. package/python/biocore_model/tests/test_runtime.py +175 -0
  198. package/python/biocore_model/tests/test_runtime_active.py +205 -0
  199. package/python/biocore_model/tests/test_runtime_pipeline.py +1583 -0
  200. package/python/biocore_model/tests/test_runtime_proactive.py +231 -0
  201. package/python/biocore_model/tests/test_scheduler.py +79 -0
  202. package/python/biocore_model/tests/test_secrecy.py +105 -0
  203. package/python/biocore_model/tests/test_self_esteem.py +45 -0
  204. package/python/biocore_model/tests/test_self_intent.py +347 -0
  205. package/python/biocore_model/tests/test_self_interest.py +183 -0
  206. package/python/biocore_model/tests/test_self_layer_wiring.py +198 -0
  207. package/python/biocore_model/tests/test_self_verification.py +113 -0
  208. package/python/biocore_model/tests/test_situation.py +142 -0
  209. package/python/biocore_model/tests/test_sleep_circadian.py +41 -0
  210. package/python/biocore_model/tests/test_social_pain.py +49 -0
  211. package/python/biocore_model/tests/test_social_repair.py +67 -0
  212. package/python/biocore_model/tests/test_social_theory.py +78 -0
  213. package/python/biocore_model/tests/test_source_monitoring.py +204 -0
  214. package/python/biocore_model/tests/test_speak_like_group.py +36 -0
  215. package/python/biocore_model/tests/test_stages.py +226 -0
  216. package/python/biocore_model/tests/test_state_tag.py +86 -0
  217. package/python/biocore_model/tests/test_template_expr.py +65 -0
  218. package/python/biocore_model/tests/test_text_inference.py +70 -0
  219. package/python/biocore_model/tests/test_theta_params.py +115 -0
  220. package/python/biocore_model/tests/test_topic_control.py +200 -0
  221. package/python/biocore_model/tests/test_turn_memory.py +105 -0
  222. package/python/biocore_model/tests/test_turn_memory_panel.py +174 -0
  223. package/python/biocore_model/tests/test_ui_settings.py +84 -0
  224. package/python/biocore_model/tests/test_ui_settings_authority.py +179 -0
  225. package/python/biocore_model/tests/test_user_appraisal_async.py +155 -0
  226. package/python/biocore_model/tests/test_user_classify.py +122 -0
  227. package/python/biocore_model/tests/test_value_order.py +117 -0
  228. package/python/biocore_model/tests/test_voice_bank.py +105 -0
  229. package/python/biocore_model/tests/test_waiting_sense.py +216 -0
  230. package/python/biocore_model/tests/test_x_directives.py +101 -0
  231. package/python/biocore_model/tests/ui_compat_check.mjs +22 -0
  232. package/python/biocore_model/tests/view_mode_check.mjs +17 -0
  233. package/python/biocore_runtime/autonomy.py +208 -0
  234. package/python/biocore_runtime/core_state.py +302 -0
  235. package/python/biocore_runtime/governance.py +495 -0
  236. package/python/biocore_runtime/growth.py +528 -0
  237. package/python/biocore_runtime/pipeline.py +850 -23
  238. package/python/biocore_runtime/relationship_state.py +214 -0
  239. package/python/biocore_runtime/self_layer.py +103 -0
  240. package/python/biocore_runtime/service.py +162 -0
  241. package/python/biocore_runtime/standby.py +472 -0
  242. package/ui/panel-layout.js +83 -39
  243. package/ui/panel.css +885 -743
  244. package/ui/panel.html +356 -296
  245. package/ui/panel.js +739 -865
@@ -0,0 +1,198 @@
1
+ # -*- coding: utf-8 -*-
2
+ """task-149/150/151 接线测试:三个自我层模块 + 面板投射(只读源码断言)。
3
+
4
+ 纯函数行为由 test_prospective / test_voice_bank / test_metacog 覆盖;这里只确认
5
+ main.py 的接线位置、开关默认值、回退守卫、提示合流与面板投射都存在,不导入
6
+ AstrBot。
7
+ """
8
+ import json
9
+ import unittest
10
+ from pathlib import Path
11
+
12
+ from biocore_model import self_intent as si
13
+
14
+ ROOT = Path(__file__).resolve().parents[2]
15
+ MAIN = (ROOT / "main.py").read_text(encoding="utf-8")
16
+ ROUTES = (ROOT / "biocore_model" / "plugin_routes.py").read_text(encoding="utf-8")
17
+ SCHEMA = json.loads((ROOT / "_conf_schema.json").read_text(encoding="utf-8"))
18
+ DASH = ROOT / "pages" / "dashboard"
19
+ SRC_HTML = (DASH / "index.src.html").read_text(encoding="utf-8")
20
+ INDEX_HTML = (DASH / "index.html").read_text(encoding="utf-8")
21
+ APP_JS = (DASH / "app.js").read_text(encoding="utf-8")
22
+
23
+
24
+ class FlagTest(unittest.TestCase):
25
+ def test_schema_flags_default_true(self):
26
+ for key in ("prospective_enabled", "voice_bank_enabled",
27
+ "metacog_enabled"):
28
+ entry = SCHEMA.get(key)
29
+ self.assertIsInstance(entry, dict, key)
30
+ self.assertEqual(entry.get("type"), "bool", key)
31
+ self.assertIs(entry.get("default"), True, key)
32
+
33
+ def test_main_reads_flags(self):
34
+ for key in ("prospective_enabled", "voice_bank_enabled",
35
+ "metacog_enabled"):
36
+ self.assertIn('self.config.get("%s", True)' % key, MAIN)
37
+ self.assertIn("self.prospective_enabled = True", MAIN)
38
+ self.assertIn("self.voice_bank_enabled = True", MAIN)
39
+ self.assertIn("self.metacog_enabled = True", MAIN)
40
+
41
+
42
+ class ProspectiveWiringTest(unittest.TestCase):
43
+ def test_due_check_and_pending_prompts(self):
44
+ self.assertIn("from .biocore_model import prospective as _pm_mod", MAIN)
45
+ self.assertIn("_pm_mod.mark_overdue(", MAIN)
46
+ self.assertIn("_pm_mod.due_now(", MAIN)
47
+ self.assertIn("_pm_mod.build_hint(", MAIN)
48
+ self.assertIn("core.pending_prompts = _pm_hints", MAIN)
49
+ self.assertIn('if bool(getattr(self, "prospective_enabled", True)):', MAIN)
50
+
51
+ def test_prospective_hint_feeds_expression_layer(self):
52
+ self.assertIn("self._prospective_hint = _pm_hints[0]", MAIN)
53
+ self.assertIn("self._prospective_hint", MAIN)
54
+ self.assertNotIn("await event.send", MAIN.split("prospective as _pm_mod")[1][:4000])
55
+
56
+ def test_prospective_store_path(self):
57
+ self.assertIn('self._self_layer_data_file("prospective.json")', MAIN)
58
+ self.assertIn('base = DATA_DIR / "biocore"', MAIN)
59
+
60
+
61
+ class VoiceBankWiringTest(unittest.TestCase):
62
+ def test_pick_and_phrases(self):
63
+ self.assertIn("from .biocore_model import voice_bank as _vb_mod", MAIN)
64
+ self.assertIn("_vb_mod.pick(", MAIN)
65
+ self.assertIn("core.voice_phrases = _vb_phrases", MAIN)
66
+ self.assertIn('if bool(getattr(self, "voice_bank_enabled", True)):', MAIN)
67
+
68
+ def test_voice_data_file_and_usage(self):
69
+ self.assertIn('self._self_layer_data_file("voice_bank.json")', MAIN)
70
+ self.assertIn('self._self_layer_data_file(\n "voice_bank_used.json")', MAIN)
71
+ self.assertIn("_vb_mod.mark_used(", MAIN)
72
+
73
+ def test_voice_hint_bounded(self):
74
+ self.assertIn('"口头习惯:" + ";".join(_vb_phrases)', MAIN)
75
+ self.assertIn("self._voice_hint", MAIN)
76
+
77
+
78
+ class MetacogWiringTest(unittest.TestCase):
79
+ def test_evaluate_and_history(self):
80
+ self.assertIn("from .biocore_model import metacog as _mc_mod", MAIN)
81
+ self.assertIn("_mc_mod.evaluate(", MAIN)
82
+ self.assertIn("_mc_mod.directives(", MAIN)
83
+ self.assertIn("_mc_mod.remember(", MAIN)
84
+ self.assertIn('if _sl_meta_on:', MAIN)
85
+
86
+ def test_directives_drive_length(self):
87
+ self.assertIn("core.self_length_scale = max(0.5, min(1.5, _sl_scale))", MAIN)
88
+ self.assertIn("_sl_scale = min(_sl_scale, max(0.0, _mc_scale))", MAIN)
89
+ self.assertIn("self._metacog_hint = _mc_tone", MAIN)
90
+ self.assertIn('self._self_layer_data_file("metacog.json")', MAIN)
91
+
92
+ def test_expression_layers_read_metacog_flag(self):
93
+ self.assertIn('getattr(core, "metacog_enabled", False)', MAIN)
94
+
95
+
96
+ class HintCombineTest(unittest.TestCase):
97
+ def test_hints_combined_once(self):
98
+ self.assertIn("for _sl_hint in (getattr(self, \"_prospective_hint\", \"\"),", MAIN)
99
+ self.assertIn("interpretation.combine_strategy_hint(", MAIN)
100
+ self.assertIn("self._last_strategy_hint = str(_strategy_hint)[:140]", MAIN)
101
+
102
+
103
+ class PanelProjectionTest(unittest.TestCase):
104
+ def test_payload_uses_module_projection(self):
105
+ self.assertIn("_si_view.payload(", MAIN)
106
+ self.assertIn('_self_intent_status_payload', MAIN)
107
+ self.assertIn('"self_intent": (self._self_intent_status_payload()', MAIN)
108
+ self.assertIn('"self_intent": (self._self_intent_status_payload()', ROUTES)
109
+
110
+ def test_payload_projection_bounds(self):
111
+ result = si.evaluate(snapshot={"stress": 0.2}, relationship={},
112
+ needs={"belonging": 0.2})
113
+ out = si.payload(result, pending_prompts=["p" * 100],
114
+ voice_phrases=["v" * 40],
115
+ metacog={"band": "low" * 5, "confidence": 0.3})
116
+ self.assertLessEqual(len(out["pending_prompts"][0]), 60)
117
+ self.assertLessEqual(len(out["voice_phrases"][0]), 20)
118
+ self.assertTrue(out["metacog"]["band"].startswith("low"))
119
+ self.assertLessEqual(out["top_weight"], 1.0)
120
+ self.assertGreaterEqual(out["top_weight"], 0.0)
121
+
122
+ def test_dashboard_row_next_to_memory_meta(self):
123
+ self.assertIn('id="selfIntentNote"', SRC_HTML)
124
+ self.assertIn('id="selfIntentNote"', INDEX_HTML)
125
+ self.assertIn('siEl=$("selfIntentNote")', APP_JS)
126
+ self.assertIn("她的意图:", SRC_HTML)
127
+ # the row sits right after the memory-meta strategy line
128
+ self.assertLess(SRC_HTML.index("memHintNote"),
129
+ SRC_HTML.index("selfIntentNote"))
130
+
131
+
132
+ class RollbackTest(unittest.TestCase):
133
+ def test_disabled_switch_clears_projection(self):
134
+ self.assertIn('core.pending_prompts = []', MAIN)
135
+ self.assertIn('core.voice_phrases = []', MAIN)
136
+ self.assertIn('if not _sl_self_on and not _sl_meta_on:', MAIN)
137
+
138
+ def test_one_turn_computation(self):
139
+ # a single occurrence in the turn path (prospective block marker)
140
+ self.assertEqual(MAIN.count('self._self_layer_data_file("prospective.json")'), 1)
141
+ self.assertEqual(MAIN.count('_pm_mod.due_now('), 1)
142
+ self.assertEqual(MAIN.count('_vb_mod.pick('), 1)
143
+ self.assertEqual(MAIN.count('_mc_mod.evaluate('), 1)
144
+
145
+
146
+ class PanelMethodTest(unittest.TestCase):
147
+ """Exec the real projection method from main.py against a fake plugin."""
148
+
149
+ @staticmethod
150
+ def _method():
151
+ import ast
152
+ import types
153
+ tree = ast.parse(MAIN)
154
+ nodes = [node for node in ast.walk(tree)
155
+ if isinstance(node, ast.FunctionDef)
156
+ and node.name == "_self_intent_status_payload"]
157
+ assert len(nodes) == 1, "expected exactly one projection method"
158
+ module = ast.Module(body=nodes, type_ignores=[])
159
+ ns = {}
160
+ exec(compile(module, "<panel_projection>", "exec"), ns)
161
+ return ns["_self_intent_status_payload"]
162
+
163
+ def test_projection_reads_core_state(self):
164
+ import types
165
+ method = self._method()
166
+ result = si.evaluate(
167
+ snapshot={"stress": 0.2, "energy": 0.7},
168
+ relationship={"closeness": 0.9, "trust": 0.9},
169
+ needs={"belonging": 0.2, "self_esteem": 0.6,
170
+ "control": 0.6, "meaningful_existence": 0.6},
171
+ silence_seconds=12 * 3600)
172
+ core = types.SimpleNamespace(
173
+ self_intent=result,
174
+ pending_prompts=["你答应过要问一句:体检结果"],
175
+ voice_phrases=["少来。"],
176
+ metacog={"band": "low", "confidence": 0.3, "converge": False,
177
+ "allow_self_correction": False, "low_streak": 1})
178
+ plugin = types.SimpleNamespace(cores={"k": core},
179
+ self_intent_enabled=True)
180
+ out = method(plugin)
181
+ self.assertEqual(out["top"], result["top"])
182
+ self.assertEqual(out["top_weight"], result["intents"][0]["weight"])
183
+ self.assertEqual(out["pending_prompts"][0][:4], "你答应过")
184
+ self.assertEqual(out["voice_phrases"], ["少来。"])
185
+ self.assertEqual(out["metacog"]["band"], "low")
186
+
187
+ def test_projection_defaults_without_core(self):
188
+ method = self._method()
189
+ import types
190
+ plugin = types.SimpleNamespace(cores={}, self_intent_enabled=True)
191
+ out = method(plugin)
192
+ self.assertEqual(out["top"], "顺着你")
193
+ self.assertEqual(out["intents"], [])
194
+ self.assertGreaterEqual(out["self_weight"], si.SELF_WEIGHT_FLOOR)
195
+
196
+
197
+ if __name__ == "__main__":
198
+ unittest.main()
@@ -0,0 +1,113 @@
1
+ # -*- coding: utf-8 -*-
2
+ import unittest
3
+
4
+ from biocore_model import self_verification as sv
5
+
6
+ NOW = 1_700_000_000
7
+
8
+
9
+ class SelfVerificationUrgeTest(unittest.TestCase):
10
+ def test_accurate_description_has_no_urge(self):
11
+ self.assertEqual(sv.correction_urge(False, 0.9, 0.9, 0.9), 0.0)
12
+
13
+ def test_misunderstood_is_higher_than_accurate(self):
14
+ pair = sv.compare(clarity=0.7, relation=0.7, severity=0.7)
15
+ self.assertGreater(pair["misunderstood"], pair["accurate"])
16
+ self.assertGreater(pair["delta"], 0.10)
17
+
18
+ def test_higher_clarity_raises_urge(self):
19
+ low = sv.correction_urge(True, clarity=0.1, relation=0.6)
20
+ high = sv.correction_urge(True, clarity=0.9, relation=0.6)
21
+ self.assertGreater(high, low)
22
+
23
+ def test_higher_relation_raises_urge(self):
24
+ low = sv.correction_urge(True, clarity=0.6, relation=0.1)
25
+ high = sv.correction_urge(True, clarity=0.6, relation=0.9)
26
+ self.assertGreater(high, low)
27
+
28
+ def test_urge_stays_in_unit_interval(self):
29
+ low = sv.correction_urge(True, clarity=-5, relation=-5, severity=-5)
30
+ high = sv.correction_urge(True, clarity=5, relation=5, severity=5)
31
+ self.assertGreaterEqual(low, 0.0)
32
+ self.assertLessEqual(high, 1.0)
33
+
34
+ def test_clarity_factor_floor_and_ceiling(self):
35
+ self.assertAlmostEqual(sv.clarity_factor(0.0), sv.CLARITY_FLOOR)
36
+ self.assertAlmostEqual(sv.clarity_factor(1.0), 1.0)
37
+
38
+ def test_relation_factor_floor_and_ceiling(self):
39
+ self.assertAlmostEqual(sv.relation_factor(0.0), sv.RELATION_FLOOR)
40
+ self.assertAlmostEqual(sv.relation_factor(1.0), 1.0)
41
+
42
+
43
+ class SelfVerificationMisreadTest(unittest.TestCase):
44
+ def test_identical_self_image_is_zero_gap(self):
45
+ image = {"warmth": 0.8, "honesty": 0.9}
46
+ self.assertEqual(sv.misread_level(image, dict(image)), 0.0)
47
+
48
+ def test_different_self_image_opens_gap(self):
49
+ level = sv.misread_level({"warmth": 0.8, "honesty": 0.9},
50
+ {"warmth": 0.2, "honesty": 0.9})
51
+ self.assertAlmostEqual(level, 0.3)
52
+
53
+ def test_missing_traits_are_not_total_misread(self):
54
+ level = sv.misread_level({"warmth": 0.8, "honesty": 0.9},
55
+ {"warmth": 0.2})
56
+ self.assertAlmostEqual(level, 0.6)
57
+
58
+ def test_bad_inputs_do_not_crash(self):
59
+ self.assertEqual(sv.misread_level(None, None), 0.0)
60
+ self.assertEqual(sv.misread_level({"warmth": 0.5}, "nope"), 0.0)
61
+ self.assertEqual(sv.correction_urge(None, None, None, None), 0.0)
62
+
63
+
64
+ class SelfVerificationStateTest(unittest.TestCase):
65
+ def test_initial_defaults(self):
66
+ state = sv.initial(NOW)
67
+ self.assertEqual(state["urge"], 0.0)
68
+ self.assertEqual(state["misread_count"], 0)
69
+
70
+ def test_observe_counts_misreads(self):
71
+ state = sv.observe(sv.initial(NOW - 10), True,
72
+ clarity=0.7, relation=0.7, now=NOW)
73
+ self.assertEqual(state["misread_count"], 1)
74
+ self.assertEqual(state["last_event"], "misunderstood")
75
+ self.assertGreater(state["urge"], 0.0)
76
+
77
+ def test_observe_accurate_does_not_count(self):
78
+ state = sv.observe(sv.initial(NOW - 10), False,
79
+ clarity=0.7, relation=0.7, now=NOW)
80
+ self.assertEqual(state["misread_count"], 0)
81
+ self.assertEqual(state["urge"], 0.0)
82
+ self.assertEqual(state["last_event"], "seen_accurately")
83
+
84
+ def test_same_turn_is_idempotent(self):
85
+ once = sv.observe(sv.initial(NOW - 10), True, now=NOW)
86
+ twice = sv.observe(once, True, now=NOW)
87
+ self.assertEqual(twice["urge"], once["urge"])
88
+ self.assertEqual(twice["misread_count"], once["misread_count"])
89
+
90
+ def test_decay_fades_urge(self):
91
+ state = sv.observe(sv.initial(NOW - 10), True,
92
+ clarity=0.9, relation=0.9, severity=1.0, now=NOW)
93
+ faded = sv.decay(state, NOW + int(sv.DECAY_HALF_LIFE_HOURS * 3600))
94
+ self.assertLess(faded["urge"], state["urge"])
95
+
96
+ def test_intent_threshold_switches(self):
97
+ state = dict(sv.initial(NOW), urge=0.10)
98
+ self.assertFalse(sv.intent(state)["want_correct"])
99
+ self.assertEqual(sv.intent(state)["strength"], "none")
100
+ state = dict(sv.initial(NOW), urge=0.80)
101
+ self.assertTrue(sv.intent(state)["want_correct"])
102
+ self.assertEqual(sv.intent(state)["strength"], "strong")
103
+
104
+ def test_view_fields(self):
105
+ state = sv.observe(sv.initial(NOW - 10), True, now=NOW)
106
+ panel = sv.view(state)
107
+ for key in ("urge", "want_correct", "strength", "peak",
108
+ "misread_count", "last_event"):
109
+ self.assertIn(key, panel)
110
+
111
+
112
+ if __name__ == "__main__":
113
+ unittest.main()
@@ -0,0 +1,142 @@
1
+ # -*- coding: utf-8 -*-
2
+ import unittest
3
+
4
+ from biocore_model import situation
5
+
6
+ NOW = 1_700_000_000
7
+
8
+
9
+ def _history(*rows):
10
+ return [{"role": role, "content": text, "ts": ts} for role, text, ts in rows]
11
+
12
+
13
+ class SituationTest(unittest.TestCase):
14
+ def test_challenge_from_probe_number(self):
15
+ out = situation.summarize(
16
+ history=_history(("user", "这句话什么意思?", NOW - 60)),
17
+ core_state={"v": -0.5, "stress": 0.7,
18
+ "labels": {"emotion": "anger"}},
19
+ relationship={"state": {"trust": 0.5, "conflict": 0.2,
20
+ "closeness": 0.5}},
21
+ situation={"challenge": 0.8},
22
+ now=NOW)
23
+ self.assertTrue(out.startswith("局面:"))
24
+ self.assertIn("质问你", out)
25
+ self.assertIn("语气不满", out)
26
+ self.assertIn("关系:", out)
27
+ self.assertLessEqual(len(out.splitlines()), 3)
28
+
29
+ def test_praise_from_probe_number(self):
30
+ out = situation.summarize(
31
+ history=[{"role": "user", "content": "今天多亏你了"}],
32
+ relationship={"state": {"closeness": 0.72, "trust": 0.7,
33
+ "conflict": 0.05}},
34
+ situation={"praise": 0.75},
35
+ now=NOW)
36
+ self.assertIn("夸你", out)
37
+ self.assertIn("亲近", out)
38
+ self.assertIn("信任高", out)
39
+
40
+ def test_hurt_from_probe_number(self):
41
+ out = situation.summarize(
42
+ history=[{"role": "user", "content": "随便你"}],
43
+ situation={"hurt": 0.7},
44
+ now=NOW)
45
+ self.assertIn("火气", out)
46
+
47
+ def test_weak_signals_keep_neutral_line(self):
48
+ out = situation.summarize(
49
+ history=[{"role": "user", "content": "今天天气不错"}],
50
+ situation={"challenge": 0.2, "praise": 0.3, "hurt": 0.1},
51
+ now=NOW)
52
+ self.assertIn("日常闲聊", out)
53
+
54
+ def test_no_word_scanning_without_probe(self):
55
+ # the old keyword tables are gone: text alone never picks a scene
56
+ out = situation.summarize(
57
+ history=[{"role": "user", "content": "你凭什么骗我?"}] +
58
+ [{"role": "user", "content": "你真厉害,谢谢你"}],
59
+ now=NOW)
60
+ self.assertIn("日常闲聊", out)
61
+ self.assertNotIn("质问你", out)
62
+ self.assertNotIn("夸你", out)
63
+
64
+ def test_unanswered_proactive_messages(self):
65
+ out = situation.summarize(
66
+ history=_history(
67
+ ("user", "在吗", NOW - 120),
68
+ ("assistant", "在的", NOW - 100),
69
+ ("assistant", "看到回我一下", NOW - 80),
70
+ ("assistant", "我先睡了", NOW - 60)),
71
+ now=NOW)
72
+ self.assertIn("主动发了 3 条", out)
73
+ self.assertIn("还没回", out)
74
+
75
+ def test_long_silence(self):
76
+ out = situation.summarize(
77
+ history=_history(("user", "晚安", NOW - 8 * 3600)), now=NOW)
78
+ self.assertIn("8 小时", out)
79
+ self.assertIn("没说话", out)
80
+
81
+ def test_silence_not_triggered_recently(self):
82
+ out = situation.summarize(
83
+ history=_history(("user", "刚到家", NOW - 300)), now=NOW)
84
+ self.assertNotIn("没说话", out)
85
+ self.assertIn("最近话题", out)
86
+
87
+ def test_relationship_tension(self):
88
+ out = situation.summarize(
89
+ history=[{"role": "user", "content": "嗯"}],
90
+ relationship={"state": {"conflict": 0.6, "trust": 0.3,
91
+ "closeness": 0.6}},
92
+ now=NOW)
93
+ self.assertIn("紧张", out)
94
+ self.assertIn("信任偏低", out)
95
+
96
+ def test_three_line_budget_and_no_overflow(self):
97
+ out = situation.summarize(
98
+ history=_history(("user", "你为什么骗我?", NOW - 9 * 3600)),
99
+ core_state={"v": -0.6, "stress": 0.9,
100
+ "labels": {"emotion": "anger"}},
101
+ relationship={"state": {"conflict": 0.7, "trust": 0.2,
102
+ "closeness": 0.8}},
103
+ situation={"challenge": 0.9, "hurt": 0.6},
104
+ now=NOW)
105
+ lines = out.splitlines()
106
+ self.assertLessEqual(len(lines), 3)
107
+ self.assertGreaterEqual(len(lines), 2)
108
+ self.assertLessEqual(len(out), situation.MAX_CHARS)
109
+ self.assertTrue(all(line.strip() for line in lines))
110
+
111
+ def test_empty_inputs(self):
112
+ self.assertEqual(situation.summarize(), "")
113
+ self.assertEqual(situation.summarize(None, None, None, None, None, None),
114
+ "")
115
+
116
+ def test_tolerant_history_shapes(self):
117
+ out = situation.summarize(
118
+ history=["在吗", ("user", "早"), {"text": "吃了吗"}], now=NOW)
119
+ self.assertIn("局面:", out)
120
+
121
+ def test_events_clock_fallback(self):
122
+ out = situation.summarize(
123
+ history=[],
124
+ events=[{"ts": NOW - 10 * 3600, "event": "上次聊天"}],
125
+ now=NOW)
126
+ self.assertIn("10 小时", out)
127
+ self.assertIn("没说话", out)
128
+
129
+ def test_malformed_situation_values_are_safe(self):
130
+ out = situation.summarize(
131
+ history=[{"role": "user", "content": "在吗"}],
132
+ situation={"challenge": "高", "praise": None, "hurt": 2.0},
133
+ now=NOW)
134
+ self.assertIn("局面:", out)
135
+ self.assertIn("火气", out) # hurt clamped to 1.0
136
+ values = situation.situation_values({"challenge": -1, "praise": 3})
137
+ self.assertEqual(values["challenge"], 0.0)
138
+ self.assertEqual(values["praise"], 1.0)
139
+
140
+
141
+ if __name__ == "__main__":
142
+ unittest.main()
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+ import unittest
3
+ from biocore_model import sleep_circadian as sc
4
+
5
+ NOW_04 = 1_700_000_000 - ((1_700_000_000 / 3600 + 8) % 24 - 4) * 3600
6
+
7
+ class SleepTest(unittest.TestCase):
8
+ def test_circadian_trough(self):
9
+ self.assertLess(sc.circadian(4), 0.1)
10
+ def test_circadian_peak(self):
11
+ self.assertGreater(sc.circadian(10), 0.9)
12
+ def test_circadian_range(self):
13
+ for hour in range(24):
14
+ self.assertGreaterEqual(sc.circadian(hour), 0.0); self.assertLessEqual(sc.circadian(hour), 1.0)
15
+ def test_sleep_window_inside(self):
16
+ self.assertTrue(sc.in_sleep_window(1.0))
17
+ def test_sleep_window_wake(self):
18
+ self.assertFalse(sc.in_sleep_window(8.0))
19
+ def test_sleep_window_boundaries(self):
20
+ self.assertTrue(sc.in_sleep_window(0.5)); self.assertFalse(sc.in_sleep_window(7.6))
21
+ def test_sleep_state_fields(self):
22
+ state = sc.sleep_state(1_700_000_000)
23
+ for key in ('sleeping', 'circadian', 'sleep_quality', 'asleep_since', 'wake_ts'):
24
+ self.assertIn(key, state)
25
+ def test_quality_from_duration(self):
26
+ self.assertAlmostEqual(sc.quality_from_durations(3.5), 0.5)
27
+ def test_short_sleep_lowers_energy(self):
28
+ self.assertLess(sc.next_day_energy(0.5), sc.next_day_energy(1.0))
29
+ def test_energy_effects(self):
30
+ low = sc.energy_effects(0.2)
31
+ self.assertLess(low['drive_mult'], 1.0); self.assertLess(low['max_chars_mult'], 1.0); self.assertGreater(low['stress_delta'], 0)
32
+ def test_energy_idempotent_same_now(self):
33
+ energy, ts = sc.settled_energy(1.0, 0.5, 8, 1_700_000_000, last_applied_ts=1_700_000_000)
34
+ self.assertEqual(ts, 1_700_000_000)
35
+ self.assertEqual(energy, 1.0) # same now => no re-application
36
+ def test_energy_wakes_apply(self):
37
+ energy, ts = sc.settled_energy(1.0, 1.0, 8, 1_700_000_000, last_applied_ts=0)
38
+ self.assertEqual(ts, 1_700_000_000); self.assertAlmostEqual(energy, 1.0)
39
+
40
+ if __name__ == '__main__':
41
+ unittest.main()
@@ -0,0 +1,49 @@
1
+ # -*- coding: utf-8 -*-
2
+ import unittest
3
+ from biocore_model import social_pain as sp
4
+
5
+ NOW = 1_700_000_000
6
+
7
+ class SocialPainTest(unittest.TestCase):
8
+ def test_initial_zero(self):
9
+ self.assertEqual(sp.initial(NOW)['value'], 0.0)
10
+ def test_ignored_adds(self):
11
+ self.assertAlmostEqual(sp.update(sp.initial(NOW - 10), 'ignored', NOW)['value'], 0.05)
12
+ def test_rejected_adds(self):
13
+ self.assertAlmostEqual(sp.update(sp.initial(NOW - 10), 'rejected', NOW)['value'], 0.10)
14
+ def test_excluded_adds(self):
15
+ self.assertAlmostEqual(sp.update(sp.initial(NOW - 10), 'excluded', NOW)['value'], 0.15)
16
+ def test_replied_lowers(self):
17
+ state = dict(sp.initial(NOW - 10), value=0.5, last_value=0.5)
18
+ self.assertAlmostEqual(sp.update(state, 'replied', NOW)['value'], 0.4)
19
+ def test_comforted_lowers(self):
20
+ state = dict(sp.initial(NOW - 10), value=0.5, last_value=0.5)
21
+ self.assertAlmostEqual(sp.update(state, 'comforted', NOW)['value'], 0.35)
22
+ def test_decay_per_hour(self):
23
+ state = dict(sp.initial(NOW - 3600 * 2), value=0.3, last_value=0.3)
24
+ self.assertAlmostEqual(sp.decay(state, NOW)['value'], 0.26)
25
+ def test_decay_same_now_idempotent(self):
26
+ state = dict(sp.initial(NOW - 10), value=0.3, last_decay_ts=NOW)
27
+ self.assertEqual(sp.decay(state, NOW)['value'], 0.3)
28
+ def test_update_same_now_no_double(self):
29
+ first = sp.update(sp.initial(NOW - 10), 'rejected', NOW)
30
+ second = sp.update(first, 'rejected', NOW)
31
+ self.assertEqual(second['value'], first['value'])
32
+ def test_clamp_upper(self):
33
+ state = dict(sp.initial(NOW - 10), value=0.95)
34
+ self.assertLessEqual(sp.update(state, 'excluded', NOW)['value'], 1.0)
35
+ def test_clamp_lower(self):
36
+ state = dict(sp.initial(NOW - 10), value=0.02)
37
+ self.assertGreaterEqual(sp.update(state, 'comforted', NOW)['value'], 0.0)
38
+ def test_independent_from_stress(self):
39
+ state = sp.update(sp.initial(NOW - 10), 'rejected', NOW)
40
+ self.assertNotIn('stress', state); self.assertNotIn('allostatic_load', state)
41
+ def test_drive_and_gate(self):
42
+ self.assertAlmostEqual(sp.drive_delta(0.4), 0.2)
43
+ self.assertAlmostEqual(sp.gate_cost(0.4), 0.02)
44
+ def test_view_fields(self):
45
+ view = sp.view(sp.update(sp.initial(NOW - 10), 'ignored', NOW))
46
+ for key in ('value', 'last_event', 'trend'): self.assertIn(key, view)
47
+
48
+ if __name__ == '__main__':
49
+ unittest.main()
@@ -0,0 +1,67 @@
1
+ # -*- coding: utf-8 -*-
2
+ import unittest
3
+ from biocore_model import social_repair as sr
4
+
5
+ class GratitudeTest(unittest.TestCase):
6
+ def test_kindness_raises(self):
7
+ self.assertAlmostEqual(sr.gratitude_event(0.0, True), 0.15)
8
+ def test_message_references_event(self):
9
+ events = [{'owner': 'user', 'valence': 0.5, 'text': '帮我拿快递'}]
10
+ msg = sr.gratitude_message(events, 0.8)
11
+ self.assertIn('帮我拿快递', msg); self.assertIn('谢了', msg)
12
+ def test_no_event_falls_back(self):
13
+ self.assertEqual(sr.gratitude_message([{'owner': 'self', 'text': 'x'}], 0.8), '')
14
+ def test_low_gratitude_no_message(self):
15
+ self.assertEqual(sr.gratitude_message([{'owner': 'user', 'valence': 1, 'text': 'x'}], 0.2), '')
16
+
17
+ class BoundaryTest(unittest.TestCase):
18
+ def test_depth_by_stage(self):
19
+ self.assertLess(sr.boundaries('stranger')['disclosure_depth'], sr.boundaries('close')['disclosure_depth'])
20
+ def test_blocked_reply(self):
21
+ self.assertEqual(sr.boundary_reply('ex', ['ex']), '这个先不说。')
22
+ def test_normal_topic(self):
23
+ self.assertEqual(sr.boundary_reply('天气', ['ex']), '')
24
+
25
+ class EmpathyTest(unittest.TestCase):
26
+ def test_affective_shift(self):
27
+ self.assertAlmostEqual(sr.empathy(1.0, cognitive_accuracy=0.0)['valence_shift'], 0.3)
28
+ def test_four_modes(self):
29
+ self.assertEqual(sr.empathy(1.0, cognitive_accuracy=1.0)['mode'], 'comfort')
30
+ self.assertEqual(sr.empathy(0.0, cognitive_accuracy=1.0)['mode'], 'reason')
31
+ self.assertEqual(sr.empathy(1.0, cognitive_accuracy=0.0)['mode'], 'resonance')
32
+ self.assertEqual(sr.empathy(0.0, cognitive_accuracy=0.0)['mode'], 'cold')
33
+
34
+ class RepairTest(unittest.TestCase):
35
+ def test_trigger(self):
36
+ self.assertTrue(sr.repair_check('你说什么?', '这是一句比较长的上一条回复内容')['needed'])
37
+ def test_no_trigger_short(self):
38
+ self.assertFalse(sr.repair_check('嗯', '长回复长回复长回复长回复')['needed'])
39
+ def test_failure_degrades(self):
40
+ out = sr.repair_check('没懂?', '这是一句比较长的上一条回复内容', clarify_failures=2)
41
+ self.assertIn('简单点说', out['text'])
42
+
43
+ class TimeTest(unittest.TestCase):
44
+ def test_gap_bands(self):
45
+ self.assertEqual(sr.time_gap_text(0.5), '刚刚'); self.assertEqual(sr.time_gap_text(3), '几小时前')
46
+ self.assertEqual(sr.time_gap_text(48), '两天多'); self.assertEqual(sr.time_gap_text(200), '一周前')
47
+ def test_future(self):
48
+ self.assertEqual(sr.future_text(3), '还有 3 天'); self.assertEqual(sr.future_text(0), '已经过了')
49
+ def test_memory_label(self):
50
+ self.assertIn('你说过', sr.memory_time_label(3))
51
+
52
+ def test_trust_raises_depth(self):
53
+ self.assertGreater(sr.boundaries('stranger', trust=0.9)['disclosure_depth'], sr.boundaries('stranger', trust=0.1)['disclosure_depth'])
54
+
55
+ def test_empathy_fields(self):
56
+ info = sr.empathy(0.5, cognitive_accuracy=0.6)
57
+ for key in ('affective', 'cognitive', 'mode', 'valence_shift'):
58
+ self.assertIn(key, info)
59
+
60
+ def test_view_fields(self):
61
+ view = sr.view(0.4, sr.boundaries('friend'), sr.empathy(0.5), sr.repair_check('嗯'), {'gap': '刚刚'})
62
+ for key in ('gratitude', 'boundaries', 'empathy', 'repair', 'time_sense'):
63
+ self.assertIn(key, view)
64
+
65
+
66
+ if __name__ == '__main__':
67
+ unittest.main()