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,347 @@
1
+ # -*- coding: utf-8 -*-
2
+ """task-148 自我层单测:纯函数、意图归一、SVO、满足度、时间调制、接线。
3
+
4
+ 全部断言只依赖 biocore_model.self_intent 的纯函数;接线测试只读 main.py 与
5
+ _conf_schema.json 的源码文本,不导入 AstrBot。
6
+ """
7
+ import ast
8
+ import json
9
+ import math
10
+ import unittest
11
+ from pathlib import Path
12
+
13
+ from biocore_model import self_intent as si
14
+
15
+ ROOT = Path(__file__).resolve().parents[2]
16
+ MAIN_SRC = (ROOT / "main.py").read_text(encoding="utf-8")
17
+ SCHEMA = json.loads((ROOT / "_conf_schema.json").read_text(encoding="utf-8"))
18
+
19
+ DEFAULT_NEEDS = {"belonging": 0.6, "self_esteem": 0.6,
20
+ "control": 0.6, "meaningful_existence": 0.6}
21
+ CALM = {"stress": 0.2, "energy": 0.7, "social": 0.6, "trust": 0.6,
22
+ "boundary_pressure": 0.0}
23
+
24
+
25
+ def _rel(closeness=0.5, trust=0.5, conflict=0.0):
26
+ return {"closeness": closeness, "trust": trust, "conflict": conflict}
27
+
28
+
29
+ def _score(result, kind):
30
+ for row in result.get("candidates") or []:
31
+ if row.get("kind") == kind:
32
+ return float(row.get("score", 0.0))
33
+ return 0.0
34
+
35
+
36
+ class ContractTest(unittest.TestCase):
37
+ def test_contract_keys(self):
38
+ result = si.evaluate(snapshot=CALM, relationship=_rel(),
39
+ needs=DEFAULT_NEEDS)
40
+ for key in ("intents", "motivation", "self_weight", "satisfaction",
41
+ "expected", "top", "drive", "urge", "candidates"):
42
+ self.assertIn(key, result)
43
+
44
+ def test_at_most_two_intents(self):
45
+ result = si.evaluate(snapshot=CALM, relationship=_rel(0.9, 0.9),
46
+ needs={"belonging": 0.0, "self_esteem": 0.1,
47
+ "control": 0.1, "meaningful_existence": 0.1})
48
+ self.assertGreaterEqual(len(result["intents"]), 1)
49
+ self.assertLessEqual(len(result["intents"]), si.MAX_INTENTS)
50
+
51
+ def test_weights_normalized_exactly(self):
52
+ for needs in (DEFAULT_NEEDS, {"belonging": 0.1, "self_esteem": 0.2,
53
+ "control": 0.3, "meaningful_existence": 0.4}):
54
+ result = si.evaluate(snapshot=CALM, relationship=_rel(),
55
+ needs=needs)
56
+ total = sum(item["weight"] for item in result["intents"])
57
+ self.assertAlmostEqual(total, 1.0, places=3)
58
+
59
+ def test_kinds_and_reasons(self):
60
+ result = si.evaluate(snapshot=CALM, relationship=_rel(), needs=DEFAULT_NEEDS)
61
+ for item in result["intents"]:
62
+ self.assertIn(item["kind"], si.KINDS)
63
+ self.assertTrue(item.get("reason"))
64
+ self.assertGreaterEqual(item["weight"], 0.0)
65
+ self.assertLessEqual(item["weight"], 1.0)
66
+
67
+ def test_motivation_within_twenty_chars(self):
68
+ cases = [
69
+ {"snapshot": CALM, "relationship": _rel(0.9, 0.9),
70
+ "needs": {"belonging": 0.1, "self_esteem": 0.6,
71
+ "control": 0.6, "meaningful_existence": 0.6},
72
+ "silence_seconds": 36000},
73
+ {"snapshot": {"stress": 0.9, "energy": 0.1},
74
+ "relationship": _rel(0.5, 0.3), "needs": DEFAULT_NEEDS},
75
+ {"snapshot": CALM, "relationship": _rel(0.3, 0.3, 0.9),
76
+ "needs": DEFAULT_NEEDS},
77
+ {"snapshot": CALM, "relationship": _rel(), "needs": DEFAULT_NEEDS},
78
+ ]
79
+ for case in cases:
80
+ result = si.evaluate(**case)
81
+ self.assertLessEqual(len(result["motivation"]), 20)
82
+ self.assertTrue(result["motivation"])
83
+
84
+ def test_disabled_falls_back_to_comply(self):
85
+ result = si.evaluate(snapshot=CALM, relationship=_rel(0.3, 0.3, 0.9),
86
+ needs=DEFAULT_NEEDS, flags={"enabled": False})
87
+ self.assertFalse(result["enabled"])
88
+ self.assertEqual(result["top"], "顺着你")
89
+ self.assertEqual(result["intents"], [{"kind": "顺着你", "weight": 1.0,
90
+ "reason": "自我层关闭"}])
91
+ self.assertEqual(result["candidates"], [])
92
+
93
+ def test_candidates_count_and_order(self):
94
+ result = si.evaluate(snapshot=CALM, relationship=_rel(), needs=DEFAULT_NEEDS)
95
+ rows = result["candidates"]
96
+ self.assertGreaterEqual(len(rows), 2)
97
+ self.assertLessEqual(len(rows), si.MAX_CANDIDATES)
98
+ scores = [row["score"] for row in rows]
99
+ self.assertEqual(scores, sorted(scores, reverse=True))
100
+
101
+
102
+ class SelfWeightTest(unittest.TestCase):
103
+ def test_bounded(self):
104
+ self.assertEqual(si.self_weight(1.0, -1.0, 1.0, 1.0, 0.0),
105
+ si.SELF_WEIGHT_CEIL)
106
+ self.assertEqual(si.self_weight(-1.0, 1.0, 0.0, 0.0, 1.0),
107
+ si.SELF_WEIGHT_FLOOR)
108
+ for closeness in (0.0, 0.5, 1.0):
109
+ for trust in (0.0, 0.5, 1.0):
110
+ for stress in (0.0, 0.5, 1.0):
111
+ value = si.self_weight(0.0, 0.0, closeness, trust, stress)
112
+ self.assertGreaterEqual(value, si.SELF_WEIGHT_FLOOR)
113
+ self.assertLessEqual(value, si.SELF_WEIGHT_CEIL)
114
+
115
+ def test_closeness_raises_weight(self):
116
+ low = si.self_weight(0.0, 0.0, 0.1, 0.5, 0.3)
117
+ high = si.self_weight(0.0, 0.0, 0.9, 0.5, 0.3)
118
+ self.assertGreater(high, low)
119
+
120
+ def test_stress_lowers_weight(self):
121
+ calm = si.self_weight(0.0, 0.0, 0.6, 0.6, 0.1)
122
+ tense = si.self_weight(0.0, 0.0, 0.6, 0.6, 0.9)
123
+ self.assertLess(tense, calm)
124
+
125
+ def test_trust_raises_weight(self):
126
+ low = si.self_weight(0.0, 0.0, 0.5, 0.1, 0.3)
127
+ high = si.self_weight(0.0, 0.0, 0.5, 0.9, 0.3)
128
+ self.assertGreater(high, low)
129
+
130
+ def test_probe_other_state_shifts_weight(self):
131
+ negative = si.evaluate(snapshot={"stress": 0.2},
132
+ relationship=_rel(0.6, 0.6),
133
+ needs=DEFAULT_NEEDS,
134
+ other_estimate={"v": -0.9, "stress": 0.8})
135
+ positive = si.evaluate(snapshot={"stress": 0.2},
136
+ relationship=_rel(0.6, 0.6),
137
+ needs=DEFAULT_NEEDS,
138
+ other_estimate={"v": 0.9, "stress": 0.0})
139
+ self.assertLess(negative["other_satisfaction"], 0.0)
140
+ self.assertGreater(positive["other_satisfaction"], 0.0)
141
+ self.assertGreater(negative["self_weight"], positive["self_weight"])
142
+
143
+
144
+ class SatisfactionTest(unittest.TestCase):
145
+ def test_deficit_is_negative(self):
146
+ result = si.evaluate(snapshot=CALM, relationship=_rel(),
147
+ needs={"belonging": 0.2, "self_esteem": 0.2,
148
+ "control": 0.2, "meaningful_existence": 0.2})
149
+ self.assertLess(result["satisfaction"], 0.0)
150
+ self.assertLess(result["satisfaction_by_need"]["belonging"], 0.0)
151
+
152
+ def test_surplus_is_positive(self):
153
+ result = si.evaluate(snapshot=CALM, relationship=_rel(),
154
+ needs={"belonging": 0.95, "self_esteem": 0.95,
155
+ "control": 0.95, "meaningful_existence": 0.95})
156
+ self.assertGreater(result["satisfaction"], 0.0)
157
+
158
+ def test_expected_drifts_toward_needs(self):
159
+ prev = {"belonging": 0.5, "self_esteem": 0.5,
160
+ "control": 0.5, "meaningful_existence": 0.5}
161
+ result = si.evaluate(snapshot=CALM, relationship=_rel(),
162
+ needs={"belonging": 0.9, "self_esteem": 0.9,
163
+ "control": 0.9, "meaningful_existence": 0.9},
164
+ expected=prev)
165
+ self.assertGreater(result["expected"]["belonging"], 0.5)
166
+ self.assertLess(result["expected"]["belonging"], 0.9)
167
+ # pure: the caller's dict is never mutated
168
+ self.assertEqual(prev["belonging"], 0.5)
169
+
170
+ def test_expected_is_bounded(self):
171
+ result = si.evaluate(snapshot=CALM, relationship=_rel(),
172
+ needs=DEFAULT_NEEDS,
173
+ expected={"belonging": 12.0, "self_esteem": -3.0})
174
+ for key in si.NEED_KEYS:
175
+ self.assertGreaterEqual(result["expected"][key], 0.0)
176
+ self.assertLessEqual(result["expected"][key], 1.0)
177
+
178
+
179
+ class TimeAndRelationTest(unittest.TestCase):
180
+ def test_long_silence_raises_company_candidate(self):
181
+ needy = dict(DEFAULT_NEEDS, belonging=0.2)
182
+ long_gap = si.evaluate(snapshot=CALM, relationship=_rel(0.9, 0.9),
183
+ needs=needy, silence_seconds=12 * 3600)
184
+ just_talked = si.evaluate(snapshot=CALM, relationship=_rel(0.9, 0.9),
185
+ needs=needy, silence_seconds=0)
186
+ self.assertEqual(long_gap["top"], "要你陪")
187
+ self.assertGreater(_score(long_gap, "要你陪"),
188
+ _score(just_talked, "要你陪"))
189
+
190
+ def test_high_conflict_raises_refusal(self):
191
+ result = si.evaluate(snapshot={"stress": 0.9, "energy": 0.8,
192
+ "boundary_pressure": 0.8},
193
+ relationship=_rel(0.3, 0.3, 0.9),
194
+ needs=DEFAULT_NEEDS)
195
+ self.assertEqual(result["top"], "拒绝话题")
196
+
197
+ def test_low_energy_favors_being_alone(self):
198
+ result = si.evaluate(snapshot={"stress": 0.9, "energy": 0.05},
199
+ relationship=_rel(0.5, 0.3),
200
+ needs=DEFAULT_NEEDS)
201
+ self.assertEqual(result["top"], "自己待着")
202
+
203
+ def test_default_calm_turn_complies(self):
204
+ result = si.evaluate(snapshot=CALM, relationship=_rel(),
205
+ needs=DEFAULT_NEEDS)
206
+ self.assertEqual(result["top"], "顺着你")
207
+
208
+ def test_snapshot_derived_needs_when_missing(self):
209
+ result = si.evaluate(snapshot={"social": 1.0, "stress": 0.0,
210
+ "energy": 1.0, "trust": 1.0,
211
+ "attention": 1.0, "boredom": 0.0},
212
+ relationship=_rel())
213
+ self.assertIn(result["top"], si.KINDS)
214
+ self.assertGreaterEqual(result["drive"], 0.0)
215
+ self.assertLessEqual(result["drive"], 1.0)
216
+
217
+
218
+ class RobustnessTest(unittest.TestCase):
219
+ def test_garbage_inputs_are_safe(self):
220
+ result = si.evaluate(snapshot={"stress": "abc", "energy": float("nan")},
221
+ relationship={"closeness": None, "state": "x"},
222
+ needs={"belonging": float("inf"), "control": -5},
223
+ other_estimate={"v": "bad"},
224
+ expected="nope",
225
+ silence_seconds="soon")
226
+ self.assertGreaterEqual(len(result["intents"]), 1)
227
+ self.assertLessEqual(len(result["intents"]), si.MAX_INTENTS)
228
+ self.assertAlmostEqual(sum(i["weight"] for i in result["intents"]), 1.0,
229
+ places=3)
230
+
231
+ def test_deterministic(self):
232
+ first = si.evaluate(snapshot=CALM, relationship=_rel(), needs=DEFAULT_NEEDS)
233
+ second = si.evaluate(snapshot=CALM, relationship=_rel(), needs=DEFAULT_NEEDS)
234
+ self.assertEqual(first, second)
235
+
236
+ def test_select_always_returns_normalized(self):
237
+ for context in ({}, {"self_weight": 0.0, "drive": 1.0},
238
+ {"self_weight": 1.0, "drive": 0.0}):
239
+ intents = si.select(context)
240
+ self.assertGreaterEqual(len(intents), 1)
241
+ self.assertLessEqual(len(intents), si.MAX_INTENTS)
242
+ self.assertAlmostEqual(sum(i["weight"] for i in intents), 1.0,
243
+ places=3)
244
+
245
+ def test_directives_differ_by_intent(self):
246
+ alone = si.evaluate(snapshot={"stress": 0.9, "energy": 0.05},
247
+ relationship=_rel(0.5, 0.3), needs=DEFAULT_NEEDS)
248
+ company = si.evaluate(snapshot=CALM, relationship=_rel(0.9, 0.9),
249
+ needs=dict(DEFAULT_NEEDS, belonging=0.2),
250
+ silence_seconds=12 * 3600)
251
+ has_alone = si.directives(alone)
252
+ has_company = si.directives(company)
253
+ self.assertNotEqual(has_alone["kind"], has_company["kind"])
254
+ self.assertNotEqual(has_alone["length_scale"],
255
+ has_company["length_scale"])
256
+ self.assertNotEqual(has_alone["initiative"], has_company["initiative"])
257
+
258
+ def test_directives_disabled(self):
259
+ out = si.directives({"enabled": False, "top": "要你陪"})
260
+ self.assertEqual(out["length_scale"], 1.0)
261
+ self.assertEqual(out["initiative"], 0.0)
262
+
263
+
264
+ class PurityTest(unittest.TestCase):
265
+ def test_no_host_imports(self):
266
+ tree = ast.parse((ROOT / "biocore_model" / "self_intent.py")
267
+ .read_text(encoding="utf-8"))
268
+ modules = set()
269
+ for node in ast.walk(tree):
270
+ if isinstance(node, ast.Import):
271
+ modules.update(alias.name.split(".")[0] for alias in node.names)
272
+ elif isinstance(node, ast.ImportFrom) and node.module:
273
+ modules.add(node.module.split(".")[0])
274
+ self.assertEqual(modules, {"__future__", "math"})
275
+ self.assertNotIn("import main", (ROOT / "biocore_model" / "self_intent.py")
276
+ .read_text(encoding="utf-8"))
277
+
278
+
279
+ class WiringTest(unittest.TestCase):
280
+ def test_config_flag_in_schema(self):
281
+ entry = SCHEMA.get("self_intent_enabled")
282
+ self.assertIsInstance(entry, dict)
283
+ self.assertEqual(entry.get("type"), "bool")
284
+ self.assertIs(entry.get("default"), True)
285
+
286
+ def test_main_reads_flag_and_computes_once(self):
287
+ self.assertIn('self.config.get("self_intent_enabled", True)', MAIN_SRC)
288
+ self.assertIn("self_intent_mod.evaluate(", MAIN_SRC)
289
+ self.assertIn("core.self_intent", MAIN_SRC)
290
+ self.assertIn("core.self_motivation", MAIN_SRC)
291
+ self.assertIn("core.self_initiative", MAIN_SRC)
292
+ self.assertIn("[biocore] self intent", MAIN_SRC)
293
+
294
+ def test_expression_layer_reads_directives(self):
295
+ self.assertIn("self_length_scale", MAIN_SRC)
296
+ self.assertIn("self_intent_mod.directives(", MAIN_SRC)
297
+
298
+
299
+ class PayloadTest(unittest.TestCase):
300
+ def test_payload_defaults(self):
301
+ out = si.payload(None)
302
+ self.assertTrue(out["enabled"])
303
+ self.assertEqual(out["top"], "顺着你")
304
+ self.assertEqual(out["intents"], [])
305
+ self.assertEqual(out["pending_prompts"], [])
306
+ self.assertEqual(out["voice_phrases"], [])
307
+ self.assertEqual(out["metacog"], {})
308
+
309
+ def test_payload_projects_result(self):
310
+ result = si.evaluate(snapshot=CALM, relationship=_rel(0.9, 0.9),
311
+ needs=dict(DEFAULT_NEEDS, belonging=0.2),
312
+ silence_seconds=12 * 3600)
313
+ out = si.payload(result)
314
+ self.assertEqual(out["top"], result["top"])
315
+ self.assertEqual(len(out["intents"]), len(result["intents"]))
316
+ self.assertAlmostEqual(out["top_weight"],
317
+ result["intents"][0]["weight"], places=3)
318
+ self.assertLessEqual(out["self_weight"], si.SELF_WEIGHT_CEIL)
319
+ self.assertGreaterEqual(out["self_weight"], si.SELF_WEIGHT_FLOOR)
320
+ self.assertLessEqual(len(out["motivation"]), 20)
321
+
322
+ def test_payload_bounds_extra_lists(self):
323
+ out = si.payload(None,
324
+ pending_prompts=["a" * 400, "b", "c", "d"],
325
+ voice_phrases=["x" * 60, "y", "z"],
326
+ metacog={"band": "low" * 20, "confidence": 9,
327
+ "converge": True, "allow_self_correction": True,
328
+ "low_streak": 7, "reason": "r" * 200})
329
+ self.assertEqual(len(out["pending_prompts"]), 3)
330
+ self.assertLessEqual(len(out["pending_prompts"][0]), 60)
331
+ self.assertEqual(len(out["voice_phrases"]), 2)
332
+ self.assertLessEqual(len(out["voice_phrases"][0]), 20)
333
+ self.assertEqual(len(out["metacog"]["band"]), 8)
334
+ self.assertLessEqual(out["metacog"]["confidence"], 1.0)
335
+ self.assertTrue(out["metacog"]["converge"])
336
+ self.assertLessEqual(len(out["metacog"]["reason"]), 40)
337
+
338
+ def test_payload_garbage_safe(self):
339
+ out = si.payload("nope", pending_prompts="x", voice_phrases=3,
340
+ metacog="bad")
341
+ self.assertEqual(out["intents"], [])
342
+ self.assertEqual(out["pending_prompts"], [])
343
+ self.assertEqual(out["voice_phrases"], [])
344
+
345
+
346
+ if __name__ == "__main__":
347
+ unittest.main()
@@ -0,0 +1,183 @@
1
+ # -*- coding: utf-8 -*-
2
+ import unittest
3
+
4
+ from biocore_model import self_interest as si
5
+
6
+ LONELY = {
7
+ "needs": {"belonging": 0.10, "self_esteem": 0.50,
8
+ "control": 0.50, "meaningful_existence": 0.50},
9
+ "energy": 0.70, "closeness": 0.60,
10
+ "values": {"closeness": 0.70}, "self_weight": 0.90,
11
+ }
12
+ TIRED = {"energy": 0.05, "stress": 0.50, "self_weight": 0.90,
13
+ "values": {"autonomy": 0.80}}
14
+ BOUNDARY = {"needs": {"control": 0.10}, "stress": 0.90, "closeness": 0.15,
15
+ "values": {"autonomy": 0.80, "honesty": 0.70},
16
+ "self_weight": 0.90}
17
+ WARM = {"closeness": 0.95, "trust": 0.90,
18
+ "needs": {"belonging": 0.80, "self_esteem": 0.80,
19
+ "control": 0.80, "meaningful_existence": 0.80},
20
+ "values": {"honesty": 0.10, "autonomy": 0.10, "closeness": 0.90},
21
+ "self_weight": 0.10, "other_stress": 0.10, "other_energy": 0.80}
22
+ HONEST = {"values": {"honesty": 0.95, "autonomy": 0.85}, "stress": 0.40,
23
+ "closeness": 0.60, "needs": {"self_esteem": 0.30},
24
+ "self_weight": 0.60, "other_stress": 0.30}
25
+
26
+
27
+ class SelfInterestChoiceTest(unittest.TestCase):
28
+ def test_lonely_picks_company(self):
29
+ out = si.assess(LONELY)
30
+ self.assertEqual(out["chosen"], "要你陪")
31
+ row = [r for r in out["options"] if r["action"] == "要你陪"][0]
32
+ self.assertGreater(row["self_gain"], row["other_gain"])
33
+
34
+ def test_tired_picks_rest(self):
35
+ out = si.assess(TIRED)
36
+ self.assertEqual(out["chosen"], "自己待着")
37
+
38
+ def test_tension_picks_refusal(self):
39
+ out = si.assess(BOUNDARY)
40
+ self.assertEqual(out["chosen"], "拒绝话题")
41
+
42
+ def test_warm_low_self_weight_picks_comply(self):
43
+ out = si.assess(WARM)
44
+ self.assertEqual(out["chosen"], "顺着你")
45
+ row = [r for r in out["options"] if r["action"] == "顺着你"][0]
46
+ self.assertGreater(row["other_gain"], row["self_gain"])
47
+
48
+ def test_honest_value_picks_truth(self):
49
+ out = si.assess(HONEST)
50
+ self.assertEqual(out["chosen"], "说真话")
51
+
52
+ def test_all_five_actions_are_scored(self):
53
+ out = si.assess({})
54
+ self.assertEqual([row["action"] for row in out["options"]],
55
+ list(si.ACTIONS))
56
+
57
+ def test_exactly_one_option_is_chosen(self):
58
+ for context in (LONELY, TIRED, BOUNDARY, WARM, HONEST, {}):
59
+ out = si.assess(context)
60
+ self.assertEqual(sum(1 for row in out["options"]
61
+ if row["chosen"]), 1)
62
+ self.assertEqual(out["chosen"],
63
+ [row["action"] for row in out["options"]
64
+ if row["chosen"]][0])
65
+
66
+ def test_reason_matches_chosen_option(self):
67
+ out = si.assess(TIRED)
68
+ chosen = [row for row in out["options"] if row["chosen"]][0]
69
+ self.assertEqual(out["reason"], chosen["reason"])
70
+ self.assertTrue(out["reason"])
71
+
72
+
73
+ class SelfInterestWeightTest(unittest.TestCase):
74
+ def test_explicit_self_weight_changes_choice(self):
75
+ selfish = si.assess(dict(LONELY, self_weight=0.90))
76
+ generous = si.assess(dict(LONELY, self_weight=0.10))
77
+ self.assertEqual(selfish["chosen"], "要你陪")
78
+ self.assertEqual(generous["chosen"], "顺着你")
79
+ self.assertGreater(selfish["self_weight"], generous["self_weight"])
80
+
81
+ def test_computed_weight_rises_with_own_gaps(self):
82
+ drained = si.self_weight({"needs": {"belonging": 0.10},
83
+ "closeness": 0.20, "trust": 0.20,
84
+ "stress": 0.80})
85
+ full = si.self_weight({
86
+ "needs": {"belonging": 0.90, "self_esteem": 0.90,
87
+ "control": 0.90, "meaningful_existence": 0.90},
88
+ "closeness": 0.90, "trust": 0.90, "stress": 0.10})
89
+ self.assertGreater(drained, full)
90
+
91
+ def test_weight_is_bounded(self):
92
+ for value in (-5, 0, 0.5, 1, 5):
93
+ out = si.assess(dict(LONELY, self_weight=value))
94
+ self.assertGreaterEqual(out["self_weight"], si.SELF_WEIGHT_MIN)
95
+ self.assertLessEqual(out["self_weight"], si.SELF_WEIGHT_MAX)
96
+
97
+ def test_config_weight_override(self):
98
+ out = si.assess({}, config={"self_weight": 0.80})
99
+ self.assertAlmostEqual(out["self_weight"], 0.80)
100
+
101
+
102
+ class SelfInterestPayoffTest(unittest.TestCase):
103
+ def test_payoffs_and_cost_are_bounded(self):
104
+ for context in (LONELY, TIRED, BOUNDARY, WARM, HONEST, {}):
105
+ out = si.assess(context)
106
+ for row in out["options"]:
107
+ self.assertGreaterEqual(row["self_gain"], -1.0)
108
+ self.assertLessEqual(row["self_gain"], 1.0)
109
+ self.assertGreaterEqual(row["other_gain"], -1.0)
110
+ self.assertLessEqual(row["other_gain"], 1.0)
111
+ self.assertGreaterEqual(row["cost"], 0.0)
112
+ self.assertLessEqual(row["cost"], 1.0)
113
+
114
+ def test_comply_costs_more_affective_labour_than_refusal(self):
115
+ table = si.action_scores(dict(HONEST, values={"honesty": 0.95,
116
+ "autonomy": 0.95}))
117
+ self.assertGreater(table["顺着你"]["cost"], table["拒绝话题"]["cost"])
118
+
119
+ def test_belonging_gap_raises_company_gain(self):
120
+ low = si.action_scores({"needs": {"belonging": 0.10}})
121
+ high = si.action_scores({"needs": {"belonging": 0.90}})
122
+ self.assertGreater(low["要你陪"]["self_gain"],
123
+ high["要你陪"]["self_gain"])
124
+
125
+ def test_fatigue_raises_rest_gain(self):
126
+ rested = si.action_scores({"energy": 0.90})
127
+ tired = si.action_scores({"energy": 0.10})
128
+ self.assertGreater(tired["自己待着"]["self_gain"],
129
+ rested["自己待着"]["self_gain"])
130
+
131
+ def test_honesty_value_raises_truth_gain(self):
132
+ low = si.action_scores({"values": {"honesty": 0.10}})
133
+ high = si.action_scores({"values": {"honesty": 0.90}})
134
+ self.assertGreater(high["说真话"]["self_gain"],
135
+ low["说真话"]["self_gain"])
136
+ self.assertGreater(low["顺着你"]["self_gain"],
137
+ high["顺着你"]["self_gain"])
138
+
139
+ def test_other_stress_lowers_company_gain(self):
140
+ calm = si.action_scores({"other_stress": 0.10})
141
+ stressed = si.action_scores({"other_stress": 0.90})
142
+ self.assertGreater(calm["要你陪"]["other_gain"],
143
+ stressed["要你陪"]["other_gain"])
144
+
145
+ def test_refusal_hurts_the_other_side(self):
146
+ table = si.action_scores({"closeness": 0.80})
147
+ self.assertLess(table["拒绝话题"]["other_gain"], 0.0)
148
+
149
+
150
+ class SelfInterestSwitchAndRobustnessTest(unittest.TestCase):
151
+ def test_disabled_switch(self):
152
+ for config in (False, {"enabled": False}):
153
+ out = si.assess(LONELY, config=config)
154
+ self.assertFalse(out["enabled"])
155
+ self.assertEqual(out["options"], [])
156
+ self.assertEqual(out["chosen"], "")
157
+ self.assertTrue(out["reason"])
158
+
159
+ def test_malformed_inputs_do_not_crash(self):
160
+ for context in (None, "bad", [], {"needs": "bad", "values": [],
161
+ "energy": "x", "self_weight": "x"}):
162
+ out = si.assess(context)
163
+ self.assertIn("options", out)
164
+ self.assertEqual(len(out["options"]), len(si.ACTIONS))
165
+ self.assertIn(out["chosen"], si.ACTIONS)
166
+
167
+ def test_deterministic(self):
168
+ first = si.assess(HONEST)
169
+ second = si.assess(dict(HONEST))
170
+ self.assertEqual(first, second)
171
+
172
+ def test_view_fields(self):
173
+ panel = si.view(si.assess(TIRED))
174
+ for key in ("enabled", "self_weight", "options", "chosen", "reason"):
175
+ self.assertIn(key, panel)
176
+ self.assertEqual(len(panel["options"]), len(si.ACTIONS))
177
+ for row in panel["options"]:
178
+ for key in ("action", "self_gain", "other_gain", "cost", "chosen"):
179
+ self.assertIn(key, row)
180
+
181
+
182
+ if __name__ == "__main__":
183
+ unittest.main()