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,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()
|