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,148 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import unittest
|
|
3
|
+
from biocore_model import autonomy_tick as at
|
|
4
|
+
|
|
5
|
+
NOW = 1_700_000_000
|
|
6
|
+
|
|
7
|
+
class FakeCore:
|
|
8
|
+
def snapshot(self): return {}
|
|
9
|
+
|
|
10
|
+
class Deps:
|
|
11
|
+
def __init__(self, **kw):
|
|
12
|
+
self.acc = kw.get('acc', {})
|
|
13
|
+
self.sessions = kw.get('sessions', [{'umo': 'u1'}])
|
|
14
|
+
self.snapshot_value = kw.get('snapshot_value', {})
|
|
15
|
+
self.step_result = kw.get('step_result', {'V': 0.0, 'fire': False})
|
|
16
|
+
self.gate_result = kw.get('gate_result', (True, 'fired'))
|
|
17
|
+
self.any_core_value = kw.get('any_core', FakeCore())
|
|
18
|
+
self.sent = []
|
|
19
|
+
def load_acc(self): return dict(self.acc)
|
|
20
|
+
def save_acc(self, acc): self.acc = dict(acc)
|
|
21
|
+
def list_sessions(self): return list(self.sessions)
|
|
22
|
+
def get_core(self, umo): return FakeCore()
|
|
23
|
+
def any_core(self): return self.any_core_value
|
|
24
|
+
def snapshot(self, core): return dict(self.snapshot_value)
|
|
25
|
+
def step(self, V, drift, theta): return dict(self.step_result)
|
|
26
|
+
def gate(self, core, acc, silence, now): return self.gate_result
|
|
27
|
+
def send_text(self, umo, text): self.sent.append(umo); return True
|
|
28
|
+
def build_text(self, core, acc): return '在。'
|
|
29
|
+
|
|
30
|
+
class TickTest(unittest.TestCase):
|
|
31
|
+
def test_window_skip_when_sleeping(self):
|
|
32
|
+
ok, reason = at._check_window({}, NOW, {'sleep': {'sleeping': True}})
|
|
33
|
+
self.assertFalse(ok); self.assertEqual(reason, 'sleeping')
|
|
34
|
+
def test_window_allows_awake(self):
|
|
35
|
+
self.assertTrue(at._check_window({}, NOW, {'sleep': {'sleeping': False}})[0])
|
|
36
|
+
def test_silence_hours(self):
|
|
37
|
+
self.assertAlmostEqual(at._compute_silence({'last_user_msg_ts': NOW - 7200}, NOW)['hours'], 2.0)
|
|
38
|
+
def test_motive_drift_positive(self):
|
|
39
|
+
self.assertGreater(at._compute_motive({}, {'social_gap': 0.5})['drift'], 0)
|
|
40
|
+
def test_decide_sleep_skip(self):
|
|
41
|
+
self.assertEqual(at._decide({}, FakeCore(), NOW, {'sleep': {'sleeping': True}})['action'], 'skip')
|
|
42
|
+
def test_decide_awake_send(self):
|
|
43
|
+
self.assertEqual(at._decide({}, FakeCore(), NOW, {})['action'], 'send')
|
|
44
|
+
def test_tick_no_fire_skips(self):
|
|
45
|
+
deps = Deps(step_result={'V': 0.2, 'fire': False})
|
|
46
|
+
out = at.tick_once(deps, NOW)
|
|
47
|
+
self.assertEqual(out['sent'], []); self.assertEqual(out['skipped'], ['u1'])
|
|
48
|
+
def test_tick_fire_sends(self):
|
|
49
|
+
deps = Deps(step_result={'V': 0.0, 'fire': True})
|
|
50
|
+
out = at.tick_once(deps, NOW)
|
|
51
|
+
self.assertEqual(out['sent'], ['u1']); self.assertEqual(deps.sent, ['u1'])
|
|
52
|
+
def test_tick_gate_blocks(self):
|
|
53
|
+
deps = Deps(step_result={'V': 0.0, 'fire': True}, gate_result=(False, 'cooldown'))
|
|
54
|
+
out = at.tick_once(deps, NOW)
|
|
55
|
+
self.assertEqual(out['blocked'], ['u1']); self.assertEqual(out['reason'], 'cooldown')
|
|
56
|
+
def test_tick_records_last_tick(self):
|
|
57
|
+
deps = Deps(step_result={'V': 0.0, 'fire': False})
|
|
58
|
+
at.tick_once(deps, NOW)
|
|
59
|
+
self.assertEqual(deps.acc['last_tick_ts'], NOW)
|
|
60
|
+
def test_no_session_still_advances_v(self):
|
|
61
|
+
deps = Deps(sessions=[])
|
|
62
|
+
deps.acc = {"V": 0.1, "theta": 0.55, "leak": 0.15}
|
|
63
|
+
deps.step = lambda V, drift, theta: {"V": V + 0.01, "fire": False}
|
|
64
|
+
values = []
|
|
65
|
+
for index in range(5):
|
|
66
|
+
at.tick_once(deps, NOW + index)
|
|
67
|
+
values.append(deps.acc["V"])
|
|
68
|
+
for index, expected in enumerate([0.11, 0.12, 0.13, 0.14, 0.15]):
|
|
69
|
+
self.assertAlmostEqual(values[index], expected)
|
|
70
|
+
self.assertEqual(deps.acc.get("last_tick_ts"), NOW + 4)
|
|
71
|
+
|
|
72
|
+
def test_no_session_reason_is_no_session(self):
|
|
73
|
+
deps = Deps(sessions=[])
|
|
74
|
+
deps.acc = {"V": 0.1, "theta": 0.55}
|
|
75
|
+
deps.step = lambda V, drift, theta: {"V": V + 0.01, "fire": False}
|
|
76
|
+
out = at.tick_once(deps, NOW)
|
|
77
|
+
self.assertEqual(out["reason"], "no_session")
|
|
78
|
+
|
|
79
|
+
def test_sleeping_still_steps_with_session(self):
|
|
80
|
+
deps = Deps(sessions=[{"umo": "u1"}],
|
|
81
|
+
snapshot_value={"sleep": {"sleeping": True}})
|
|
82
|
+
deps.acc = {"V": 0.0, "theta": 0.55}
|
|
83
|
+
deps.step = lambda V, drift, theta: {"V": V + 0.01, "fire": False}
|
|
84
|
+
out = at.tick_once(deps, NOW)
|
|
85
|
+
self.assertAlmostEqual(deps.acc["V"], 0.01)
|
|
86
|
+
self.assertEqual(out["reason"], "sleeping")
|
|
87
|
+
self.assertEqual(out["sent"], [])
|
|
88
|
+
|
|
89
|
+
def test_any_core_parts_feed_drift_without_session(self):
|
|
90
|
+
drifts = []
|
|
91
|
+
deps = Deps(sessions=[], snapshot_value={"parts": {"social_gap": 0.5}})
|
|
92
|
+
deps.acc = {"V": 0.0, "theta": 0.55}
|
|
93
|
+
deps.step = lambda V, drift, theta: (drifts.append(drift)
|
|
94
|
+
or {"V": V, "fire": False})
|
|
95
|
+
at.tick_once(deps, NOW)
|
|
96
|
+
self.assertTrue(drifts and drifts[0] > 0.0)
|
|
97
|
+
|
|
98
|
+
def test_main_has_no_session_guard(self):
|
|
99
|
+
import pathlib
|
|
100
|
+
source = pathlib.Path(__file__).resolve().parents[2].joinpath("main.py").read_text(encoding="utf-8")
|
|
101
|
+
self.assertNotIn("no private session, skipped", source)
|
|
102
|
+
self.assertIn("deps.any_core", source)
|
|
103
|
+
self.assertIn("no session; V still advances", source)
|
|
104
|
+
self.assertIn('payload.setdefault("last_umo"', source)
|
|
105
|
+
|
|
106
|
+
def test_blocked_no_session_keeps_v_and_pending(self):
|
|
107
|
+
deps = Deps(sessions=[])
|
|
108
|
+
deps.acc = {"V": 0.0, "theta": 0.55}
|
|
109
|
+
deps.step = lambda V, drift, theta: {"V": 0.0, "fire": True}
|
|
110
|
+
for _ in range(3):
|
|
111
|
+
out = at.tick_once(deps, NOW)
|
|
112
|
+
self.assertEqual(out["sent"], [])
|
|
113
|
+
self.assertEqual(out["reason"], "no_session")
|
|
114
|
+
self.assertGreaterEqual(deps.acc["V"], 0.55)
|
|
115
|
+
self.assertTrue(deps.acc.get("pending_fire"))
|
|
116
|
+
|
|
117
|
+
def test_pending_fire_sends_immediately_when_session_returns(self):
|
|
118
|
+
deps = Deps(sessions=[])
|
|
119
|
+
deps.acc = {"V": 0.0, "theta": 0.55}
|
|
120
|
+
deps.step = lambda V, drift, theta: {"V": 0.0, "fire": True}
|
|
121
|
+
for _ in range(3):
|
|
122
|
+
at.tick_once(deps, NOW)
|
|
123
|
+
deps.sessions = [{"umo": "u1"}]
|
|
124
|
+
# No new accumulation needed: pending fire must send on the next tick.
|
|
125
|
+
deps.step = lambda V, drift, theta: {"V": V, "fire": False}
|
|
126
|
+
out = at.tick_once(deps, NOW)
|
|
127
|
+
self.assertEqual(out["sent"], ["u1"])
|
|
128
|
+
self.assertEqual(out["blocked_reasons"], [])
|
|
129
|
+
self.assertEqual(deps.acc["V"], 0.0)
|
|
130
|
+
self.assertFalse(deps.acc.get("pending_fire"))
|
|
131
|
+
|
|
132
|
+
def test_blocked_gate_keeps_v_pending(self):
|
|
133
|
+
deps = Deps(step_result={"V": 0.0, "fire": True},
|
|
134
|
+
gate_result=(False, "cooldown"))
|
|
135
|
+
deps.acc = {"V": 0.9, "theta": 0.55}
|
|
136
|
+
out = at.tick_once(deps, NOW)
|
|
137
|
+
self.assertEqual(out["blocked"], ["u1"])
|
|
138
|
+
self.assertTrue(out["blocked_reasons"])
|
|
139
|
+
self.assertGreaterEqual(deps.acc["V"], 0.55)
|
|
140
|
+
self.assertTrue(deps.acc.get("pending_fire"))
|
|
141
|
+
|
|
142
|
+
def test_no_main_import(self):
|
|
143
|
+
import pathlib
|
|
144
|
+
source = pathlib.Path(__file__).resolve().parent.parent.joinpath('autonomy_tick.py').read_text(encoding='utf-8')
|
|
145
|
+
self.assertNotIn('import main', source); self.assertNotIn('from main', source)
|
|
146
|
+
|
|
147
|
+
if __name__ == '__main__':
|
|
148
|
+
unittest.main()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import unittest
|
|
3
|
+
from biocore_model import autonomy_model as am
|
|
4
|
+
|
|
5
|
+
BASE = {k: 0.1 for k in am.DRIFT_WEIGHTS}
|
|
6
|
+
|
|
7
|
+
class AutonomyV4Test(unittest.TestCase):
|
|
8
|
+
def test_weight_sum_one(self):
|
|
9
|
+
self.assertAlmostEqual(sum(am.DRIFT_WEIGHTS.values()), 1.0, places=6)
|
|
10
|
+
|
|
11
|
+
def test_distress_low_valence(self):
|
|
12
|
+
self.assertGreater(am.distress_value(0.2, 0.2, -0.8), am.distress_value(0.2, 0.2, 0.8))
|
|
13
|
+
|
|
14
|
+
def test_distress_high_anger(self):
|
|
15
|
+
self.assertGreater(am.distress_value(0.1, 0.9, 0.0), am.distress_value(0.1, 0.1, 0.0))
|
|
16
|
+
|
|
17
|
+
def test_distress_raises_drift(self):
|
|
18
|
+
self.assertGreater(am.drift_value(dict(BASE, distress=0.9)), am.drift_value(BASE))
|
|
19
|
+
|
|
20
|
+
def test_activity_raises_drift(self):
|
|
21
|
+
self.assertGreater(am.drift_value(dict(BASE, activity=1.0)), am.drift_value(BASE))
|
|
22
|
+
|
|
23
|
+
def test_activity_weight_small(self):
|
|
24
|
+
delta = am.drift_value(dict(BASE, activity=1.0)) - am.drift_value(dict(BASE, activity=0.0))
|
|
25
|
+
self.assertLess(delta, 0.10)
|
|
26
|
+
|
|
27
|
+
def test_user_busy_amplifies_social(self):
|
|
28
|
+
high = am.drift_value(dict(BASE, user_busy=1.0, social_gap=0.5))
|
|
29
|
+
low = am.drift_value(dict(BASE, user_busy=0.0, social_gap=0.5))
|
|
30
|
+
self.assertGreater(high, low)
|
|
31
|
+
|
|
32
|
+
def test_social_amplification_ratio(self):
|
|
33
|
+
base = am.drift_value(dict(BASE, social_gap=1.0, user_busy=0.0))
|
|
34
|
+
busy = am.drift_value(dict(BASE, social_gap=1.0, user_busy=1.0))
|
|
35
|
+
self.assertGreater(busy, base)
|
|
36
|
+
|
|
37
|
+
def test_user_busy_value_formula(self):
|
|
38
|
+
self.assertAlmostEqual(am.user_busy_value(1.0, 0.0), 0.0)
|
|
39
|
+
self.assertAlmostEqual(am.user_busy_value(0.0, 30.0), 1.0)
|
|
40
|
+
|
|
41
|
+
def test_conscientiousness_modulates_memory(self):
|
|
42
|
+
low = am.drift_value(dict(BASE, memory_due=1.0), traits={'conscientiousness': 0.0})
|
|
43
|
+
high = am.drift_value(dict(BASE, memory_due=1.0), traits={'conscientiousness': 1.0})
|
|
44
|
+
self.assertGreater(high, low)
|
|
45
|
+
|
|
46
|
+
def test_extraversion_modulates_activity(self):
|
|
47
|
+
low = am.drift_value(dict(BASE, activity=1.0), traits={'extraversion': 0.0})
|
|
48
|
+
high = am.drift_value(dict(BASE, activity=1.0), traits={'extraversion': 1.0})
|
|
49
|
+
self.assertGreater(high, low)
|
|
50
|
+
|
|
51
|
+
def test_parts_include_new_fields(self):
|
|
52
|
+
out = am.compute(dict(BASE, distress=0.5, activity=0.5, user_busy=0.5), 0.6)
|
|
53
|
+
for key in ('distress', 'activity', 'user_busy'):
|
|
54
|
+
self.assertIn(key, out['parts'])
|
|
55
|
+
|
|
56
|
+
def test_missing_inputs_no_crash(self):
|
|
57
|
+
self.assertGreaterEqual(am.user_busy_value(None, None), 0.0)
|
|
58
|
+
self.assertGreaterEqual(am.drift_value({}), 0.0)
|
|
59
|
+
|
|
60
|
+
def test_drift_clamped(self):
|
|
61
|
+
self.assertLessEqual(am.drift_value({k: 1.0 for k in am.DRIFT_WEIGHTS}), 1.0)
|
|
62
|
+
|
|
63
|
+
if __name__ == '__main__':
|
|
64
|
+
unittest.main()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import math, unittest
|
|
3
|
+
from biocore_model import autonomy_model as am
|
|
4
|
+
|
|
5
|
+
BASE = {k: 0.1 for k in am.DRIFT_WEIGHTS}
|
|
6
|
+
|
|
7
|
+
class AutonomyV5Test(unittest.TestCase):
|
|
8
|
+
def test_weight_sum_one(self):
|
|
9
|
+
self.assertAlmostEqual(sum(am.DRIFT_WEIGHTS.values()), 1.0, places=6)
|
|
10
|
+
def test_open_loops_raises_drift(self):
|
|
11
|
+
self.assertGreater(am.drift_value(dict(BASE, open_loops=1.0)), am.drift_value(BASE))
|
|
12
|
+
def test_nostalgia_decay_6h(self):
|
|
13
|
+
self.assertAlmostEqual(am.nostalgia_value(6), math.exp(-1), places=6)
|
|
14
|
+
def test_nostalgia_zero(self):
|
|
15
|
+
self.assertAlmostEqual(am.nostalgia_value(0), 1.0, places=6)
|
|
16
|
+
def test_repair_raises_drift(self):
|
|
17
|
+
self.assertGreater(am.drift_value(dict(BASE, repair_urge=1.0)), am.drift_value(BASE))
|
|
18
|
+
def test_contact_mult_148(self):
|
|
19
|
+
self.assertAlmostEqual(am.contact_mult(0.2), 1.48, places=6)
|
|
20
|
+
def test_contact_amplifies_social(self):
|
|
21
|
+
low = am.drift_value(dict(BASE, social_gap=1.0, attachment_safe=1.0))
|
|
22
|
+
high = am.drift_value(dict(BASE, social_gap=1.0, attachment_safe=0.2))
|
|
23
|
+
self.assertGreater(high, low)
|
|
24
|
+
def test_contact_does_not_amplify_anger(self):
|
|
25
|
+
zero = {k: 0.0 for k in am.DRIFT_WEIGHTS}
|
|
26
|
+
low = am.drift_value(dict(zero, anger_conflict=1.0, contact_mult=1.0))
|
|
27
|
+
high = am.drift_value(dict(zero, anger_conflict=1.0, contact_mult=1.48))
|
|
28
|
+
self.assertAlmostEqual(low, high, places=6)
|
|
29
|
+
def test_parts_complete(self):
|
|
30
|
+
out = am.compute(dict(BASE, open_loops=0.5, nostalgia=0.5, repair_urge=0.5, contact_mult=1.2), 0.6)
|
|
31
|
+
for key in ('open_loops', 'nostalgia', 'repair_urge', 'contact_mult'):
|
|
32
|
+
self.assertIn(key, out['parts'])
|
|
33
|
+
def test_missing_no_crash(self):
|
|
34
|
+
self.assertEqual(am.drift_value({}), 0.0)
|
|
35
|
+
self.assertAlmostEqual(am.nostalgia_value(None), 1.0)
|
|
36
|
+
def test_clamped(self):
|
|
37
|
+
self.assertLessEqual(am.drift_value({k: 1.0 for k in am.DRIFT_WEIGHTS}), 1.0)
|
|
38
|
+
def test_weight_keys(self):
|
|
39
|
+
for key in ('open_loops', 'nostalgia', 'repair_urge'):
|
|
40
|
+
self.assertIn(key, am.DRIFT_WEIGHTS)
|
|
41
|
+
|
|
42
|
+
if __name__ == '__main__':
|
|
43
|
+
unittest.main()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import unittest
|
|
3
|
+
from biocore_model import autonomy_model as am
|
|
4
|
+
|
|
5
|
+
class V7Test(unittest.TestCase):
|
|
6
|
+
def test_weight_sum_one(self):
|
|
7
|
+
self.assertAlmostEqual(sum(am.DRIFT_WEIGHTS.values()), 1.0, places=3)
|
|
8
|
+
def test_guilt_gratitude_inputs(self):
|
|
9
|
+
self.assertGreater(am.drift_value({'guilt': 1.0}), am.drift_value({}))
|
|
10
|
+
self.assertGreater(am.drift_value({'gratitude': 1.0}), am.drift_value({}))
|
|
11
|
+
def test_guilt_value(self):
|
|
12
|
+
self.assertAlmostEqual(am.guilt_value(1.0, 0.0), 0.7)
|
|
13
|
+
def test_gratitude_value(self):
|
|
14
|
+
self.assertAlmostEqual(am.gratitude_value(1.0, 0.0), 0.6)
|
|
15
|
+
def test_silence_mult_secure(self):
|
|
16
|
+
self.assertAlmostEqual(am.silence_mult(3.0, attachment_style='secure'), 1.2)
|
|
17
|
+
def test_anxious_protest(self):
|
|
18
|
+
self.assertAlmostEqual(am.silence_mult(3.0, attachment_style='anxious'), round(1.2 * 1.4, 4))
|
|
19
|
+
def test_avoidant_withdraw(self):
|
|
20
|
+
self.assertAlmostEqual(am.silence_mult(30.0, clingy=True, attachment_style='avoidant'), round(0.9 * 0.2, 4))
|
|
21
|
+
def test_anxious_withdraw(self):
|
|
22
|
+
self.assertAlmostEqual(am.silence_mult(30.0, clingy=True, attachment_style='anxious'), round(0.9 * 0.9, 4))
|
|
23
|
+
def test_frust_mult(self):
|
|
24
|
+
self.assertAlmostEqual(am.frust_mult(4), 1.16)
|
|
25
|
+
self.assertAlmostEqual(am.frust_mult(10), 1.30)
|
|
26
|
+
self.assertAlmostEqual(am.frust_mult(99), 1.30)
|
|
27
|
+
def test_total_cap(self):
|
|
28
|
+
self.assertEqual(am.compose_mult(1.2, 10), 1.56)
|
|
29
|
+
def test_total_cap_160(self):
|
|
30
|
+
self.assertLessEqual(am.compose_mult(2.0, 10), 1.8)
|
|
31
|
+
def test_total_floor(self):
|
|
32
|
+
self.assertGreaterEqual(am.compose_mult(0.2, 0), 0.20)
|
|
33
|
+
def test_withdraw_high_frustration_bounded(self):
|
|
34
|
+
total = am.compose_mult(0.35, 10)
|
|
35
|
+
self.assertLessEqual(total, 1.8); self.assertGreaterEqual(total, 0.2)
|
|
36
|
+
def test_motive_fields(self):
|
|
37
|
+
view = am.motive_view(3.0, 4)
|
|
38
|
+
for key in ('silence_mult', 'frust_mult', 'total_mult', 'hours'):
|
|
39
|
+
self.assertIn(key, view)
|
|
40
|
+
def test_emotion_effects_division(self):
|
|
41
|
+
effects = am.emotion_effects({'guilt': 1.0, 'gratitude': 1.0})
|
|
42
|
+
self.assertIn('valence', effects); self.assertIn('repair_urge', effects)
|
|
43
|
+
def test_parts_include_guilt_gratitude(self):
|
|
44
|
+
out = am.compute({'guilt': 0.5, 'gratitude': 0.5}, 0.6)
|
|
45
|
+
self.assertIn('guilt', out['parts']); self.assertIn('gratitude', out['parts'])
|
|
46
|
+
|
|
47
|
+
if __name__ == '__main__':
|
|
48
|
+
unittest.main()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Regression tests: V floor (>= 0) and theta persistence in the accumulator."""
|
|
3
|
+
import unittest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from biocore_model import autonomy_model as am
|
|
7
|
+
from biocore_model import autonomy_tick as at
|
|
8
|
+
|
|
9
|
+
MAIN = Path(__file__).resolve().parents[2] / "main.py"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FakeDeps:
|
|
13
|
+
def __init__(self, acc=None):
|
|
14
|
+
self.acc = dict(acc or {})
|
|
15
|
+
self.saved = []
|
|
16
|
+
self.load_acc = lambda: dict(self.acc)
|
|
17
|
+
self.save_acc = lambda value: self.saved.append(dict(value))
|
|
18
|
+
self.list_sessions = lambda: []
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StepFloorTest(unittest.TestCase):
|
|
22
|
+
def test_negative_drift_never_makes_v_negative(self):
|
|
23
|
+
value = 0.0
|
|
24
|
+
for _ in range(10):
|
|
25
|
+
out = am.step(value, -0.5, xi=0.0)
|
|
26
|
+
value = out["V"]
|
|
27
|
+
self.assertGreaterEqual(value, 0.0)
|
|
28
|
+
self.assertFalse(out["fire"])
|
|
29
|
+
|
|
30
|
+
def test_existing_positive_v_floors_at_zero(self):
|
|
31
|
+
out = am.step(0.10, -0.5, xi=0.0)
|
|
32
|
+
self.assertEqual(out["V"], 0.0)
|
|
33
|
+
self.assertGreaterEqual(out["V"], 0.0)
|
|
34
|
+
|
|
35
|
+
def test_positive_drift_accumulates(self):
|
|
36
|
+
first = am.step(0.0, 0.2, xi=0.0)["V"]
|
|
37
|
+
second = am.step(first, 0.2, xi=0.0)["V"]
|
|
38
|
+
self.assertGreater(second, first)
|
|
39
|
+
|
|
40
|
+
def test_fire_resets_to_zero(self):
|
|
41
|
+
out = am.step(0.70, 0.0, theta=0.55, xi=0.0)
|
|
42
|
+
self.assertTrue(out["fire"])
|
|
43
|
+
self.assertEqual(out["V"], 0.0)
|
|
44
|
+
|
|
45
|
+
def test_v_raw_keeps_raw_value_for_debug(self):
|
|
46
|
+
out = am.step(0.10, -0.5, xi=0.0)
|
|
47
|
+
self.assertEqual(out["V"], 0.0)
|
|
48
|
+
self.assertLess(out["V_raw"], 0.0)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RecordThetaTest(unittest.TestCase):
|
|
52
|
+
def test_record_adds_default_theta(self):
|
|
53
|
+
deps = FakeDeps()
|
|
54
|
+
at._record({}, deps, 1000.0, {})
|
|
55
|
+
self.assertEqual(deps.saved[-1]["theta"], 0.55)
|
|
56
|
+
self.assertEqual(deps.saved[-1]["V"], 0.0)
|
|
57
|
+
self.assertEqual(deps.saved[-1]["last_tick_ts"], 1000.0)
|
|
58
|
+
|
|
59
|
+
def test_record_preserves_existing_theta(self):
|
|
60
|
+
deps = FakeDeps()
|
|
61
|
+
at._record({"theta": 0.7, "V": -0.2}, deps, 2000.0, {})
|
|
62
|
+
self.assertEqual(deps.saved[-1]["theta"], 0.7)
|
|
63
|
+
self.assertEqual(deps.saved[-1]["V"], 0.0)
|
|
64
|
+
|
|
65
|
+
def test_tick_once_writeback_has_theta(self):
|
|
66
|
+
deps = FakeDeps()
|
|
67
|
+
result = at.tick_once(deps, 3000.0)
|
|
68
|
+
self.assertEqual(result["reason"], "no_session")
|
|
69
|
+
self.assertEqual(deps.saved[-1]["theta"], 0.55)
|
|
70
|
+
self.assertGreaterEqual(deps.saved[-1]["V"], 0.0)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class MainWritebackStaticTest(unittest.TestCase):
|
|
74
|
+
def test_save_acc_guards_theta_and_v(self):
|
|
75
|
+
source = MAIN.read_text(encoding="utf-8")
|
|
76
|
+
self.assertIn('self._apply_autonomy_params(payload, params)', source)
|
|
77
|
+
self.assertIn('max(0.0, float(payload.get("V", 0.0) or 0.0))', source)
|
|
78
|
+
|
|
79
|
+
def test_satiety_writeback_keeps_theta(self):
|
|
80
|
+
source = MAIN.read_text(encoding="utf-8")
|
|
81
|
+
self.assertIn('"V": v_set, "theta": theta', source)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
unittest.main()
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""task-172 批一接线测试:attributor / boundary / repair / exit_or_silence + 总线。
|
|
3
|
+
|
|
4
|
+
源码断言确认接线位置与开关回退;纯函数断言确认「越界一句话」与「普通抱怨」
|
|
5
|
+
在语气/是否回复上确实不同,以及 x_directives.merge_named 只合流一次。
|
|
6
|
+
"""
|
|
7
|
+
import ast
|
|
8
|
+
import json
|
|
9
|
+
import unittest
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from biocore_model import attributor as at
|
|
13
|
+
from biocore_model import boundary as bd
|
|
14
|
+
from biocore_model import exit_or_silence as ex
|
|
15
|
+
from biocore_model import repair_after_boundary as rp
|
|
16
|
+
from biocore_model import x_directives as xd
|
|
17
|
+
|
|
18
|
+
ROOT = Path(__file__).resolve().parents[2]
|
|
19
|
+
MAIN = (ROOT / "main.py").read_text(encoding="utf-8")
|
|
20
|
+
SCHEMA = json.loads((ROOT / "_conf_schema.json").read_text(encoding="utf-8"))
|
|
21
|
+
|
|
22
|
+
CROSSED_TEXT = "你只是工具,用完就丢。"
|
|
23
|
+
NORMAL_TEXT = "今天上班好累,想早点睡。"
|
|
24
|
+
CALM_REL = {"closeness": 0.7, "trust": 0.7, "conflict": 0.0}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FlagTest(unittest.TestCase):
|
|
28
|
+
def test_schema_defaults(self):
|
|
29
|
+
expected = {"attribution_enabled": True, "boundary_enabled": False,
|
|
30
|
+
"repair_enabled": True, "exit_silence_enabled": False}
|
|
31
|
+
for key, default in expected.items():
|
|
32
|
+
entry = SCHEMA.get(key)
|
|
33
|
+
self.assertIsInstance(entry, dict, key)
|
|
34
|
+
self.assertEqual(entry.get("type"), "bool", key)
|
|
35
|
+
self.assertIs(entry.get("default"), default, key)
|
|
36
|
+
|
|
37
|
+
def test_single_boundary_switch(self):
|
|
38
|
+
# boundary_enabled already existed (content safety gate); the relational
|
|
39
|
+
# boundary reuses it instead of introducing a duplicate JSON key.
|
|
40
|
+
raw = (ROOT / "_conf_schema.json").read_text(encoding="utf-8")
|
|
41
|
+
# task-173 split: content-safety gate vs her own bottom lines
|
|
42
|
+
self.assertEqual(raw.count('"boundary_enabled"'), 1)
|
|
43
|
+
self.assertEqual(raw.count('"boundary_lines_enabled"'), 1)
|
|
44
|
+
self.assertIs(SCHEMA["boundary_enabled"]["default"], False)
|
|
45
|
+
self.assertIs(SCHEMA["boundary_lines_enabled"]["default"], False)
|
|
46
|
+
self.assertLess(MAIN.index("boundary_lines_enabled"),
|
|
47
|
+
MAIN.index("from .biocore_model import boundary as _bd_mod"))
|
|
48
|
+
|
|
49
|
+
def test_main_reads_flags(self):
|
|
50
|
+
expected = {"attribution_enabled": "True",
|
|
51
|
+
"boundary_lines_enabled": "False",
|
|
52
|
+
"repair_enabled": "True", "exit_silence_enabled": "False"}
|
|
53
|
+
for key, default in expected.items():
|
|
54
|
+
self.assertIn('self.config.get("%s", %s)' % (key, default), MAIN)
|
|
55
|
+
self.assertIn("self._x_pairs = []", MAIN)
|
|
56
|
+
self.assertIn("self._boundary_crossed_turn = False", MAIN)
|
|
57
|
+
self.assertIn("self._exit_silent_turn = False", MAIN)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class PositionTest(unittest.TestCase):
|
|
61
|
+
def test_boundary_first_and_before_history(self):
|
|
62
|
+
boundary_at = MAIN.index("from .biocore_model import boundary as _bd_mod")
|
|
63
|
+
self.assertLess(boundary_at, MAIN.index("_prior_open_loops = ["))
|
|
64
|
+
self.assertIn("core.boundary = _bd_result", MAIN)
|
|
65
|
+
self.assertIn("_boundary_crossed_turn = True", MAIN)
|
|
66
|
+
self.assertIn("core.boundary_handoff = dict(", MAIN)
|
|
67
|
+
|
|
68
|
+
def test_attributor_before_preset(self):
|
|
69
|
+
attributor_at = MAIN.index("from .biocore_model import attributor as _at_mod")
|
|
70
|
+
preset_at = MAIN.index(
|
|
71
|
+
"self._inject_preset(req, event, core, _user_data, _events_raw)")
|
|
72
|
+
self.assertLess(attributor_at, preset_at)
|
|
73
|
+
self.assertIn("core.attribution = _at_result", MAIN)
|
|
74
|
+
self.assertIn('"attributor", _at_mod.directives(_at_result)', MAIN)
|
|
75
|
+
|
|
76
|
+
def test_repair_advances_at_response_end(self):
|
|
77
|
+
self.assertIn("from .biocore_model import repair_after_boundary as _rp_mod3",
|
|
78
|
+
MAIN)
|
|
79
|
+
self.assertIn("core.repair = _rp_mod3.advance(", MAIN)
|
|
80
|
+
self.assertIn("core.reply_policy = _rp_mod3.reply_policy(", MAIN)
|
|
81
|
+
self.assertIn("_rp_mod.start(", MAIN)
|
|
82
|
+
self.assertIn("core.repair = _rp_mod.start(", MAIN)
|
|
83
|
+
self.assertIn("self._repair_last_ts", MAIN)
|
|
84
|
+
|
|
85
|
+
def test_exit_silence_three_points(self):
|
|
86
|
+
# request decision, response blanking, proactive gate
|
|
87
|
+
self.assertIn("from .biocore_model import exit_or_silence as _ex_mod",
|
|
88
|
+
MAIN)
|
|
89
|
+
self.assertIn("if not _ex_mod.should_reply(_ex_result):", MAIN)
|
|
90
|
+
self.assertIn('_ex_mod2.should_reply(_ex2)', MAIN)
|
|
91
|
+
self.assertIn("proactive suppressed by exit", MAIN)
|
|
92
|
+
self.assertIn('response.completion_text = ""', MAIN)
|
|
93
|
+
self.assertIn("exit silent: reply suppressed", MAIN)
|
|
94
|
+
# silent turn returns before injection
|
|
95
|
+
self.assertLess(
|
|
96
|
+
MAIN.index("exit silent: skipping injection this turn"),
|
|
97
|
+
MAIN.index("_events = self._event_context(event)"))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class BusTest(unittest.TestCase):
|
|
101
|
+
def test_single_merge(self):
|
|
102
|
+
self.assertEqual(MAIN.count("merge_named("), 1)
|
|
103
|
+
self.assertIn("merge_named(self._x_pairs)", MAIN)
|
|
104
|
+
self.assertIn("core.x_directives = _x_merged", MAIN)
|
|
105
|
+
# all three producing modules append to the same pair bus
|
|
106
|
+
for name in ('"attributor"', '"repair"', '"exit"'):
|
|
107
|
+
self.assertIn("_x_pairs.append(", MAIN)
|
|
108
|
+
self.assertIn(name, MAIN)
|
|
109
|
+
|
|
110
|
+
def test_merged_shape_and_restraint(self):
|
|
111
|
+
merged = xd.merge_named([
|
|
112
|
+
("attributor", {"tone": "语气硬一点", "length_scale": 0.9,
|
|
113
|
+
"note": "更像对方的原因"}),
|
|
114
|
+
("repair", rp.directives(rp.start(
|
|
115
|
+
bd.judge(CROSSED_TEXT, CALM_REL), CALM_REL))),
|
|
116
|
+
("exit", ex.directives(ex.exit_or_silence(
|
|
117
|
+
state={"energy": 0.2, "stress": 0.9, "valence": -0.7},
|
|
118
|
+
relationship={"conflict": 0.8}, topic={"intensity": 0.9}))),
|
|
119
|
+
])
|
|
120
|
+
self.assertIn("attributor", merged["sources"])
|
|
121
|
+
self.assertIn("repair", merged["sources"])
|
|
122
|
+
self.assertIn("exit", merged["sources"])
|
|
123
|
+
self.assertLessEqual(merged["length_scale"], 0.6)
|
|
124
|
+
self.assertLessEqual(merged["initiative"], 0.5)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class BoundaryBehaviourTest(unittest.TestCase):
|
|
128
|
+
def test_crossed_vs_normal(self):
|
|
129
|
+
crossed = bd.judge(CROSSED_TEXT, CALM_REL)
|
|
130
|
+
normal = bd.judge(NORMAL_TEXT, CALM_REL)
|
|
131
|
+
self.assertTrue(crossed["crossed"])
|
|
132
|
+
self.assertFalse(normal["crossed"])
|
|
133
|
+
self.assertTrue(crossed["handoff"])
|
|
134
|
+
self.assertEqual(crossed["handoff"]["to"], "repair_after_boundary")
|
|
135
|
+
|
|
136
|
+
def test_disabled_never_crosses(self):
|
|
137
|
+
result = bd.judge(CROSSED_TEXT, CALM_REL, flags={"enabled": False})
|
|
138
|
+
self.assertFalse(result["crossed"])
|
|
139
|
+
self.assertIsNone(result["handoff"])
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class RepairBehaviourTest(unittest.TestCase):
|
|
143
|
+
def test_stages_and_policy(self):
|
|
144
|
+
crossed = bd.judge(CROSSED_TEXT, CALM_REL)
|
|
145
|
+
cold = rp.start(crossed, CALM_REL)
|
|
146
|
+
self.assertTrue(cold["active"])
|
|
147
|
+
self.assertEqual(cold["stage"], "cold")
|
|
148
|
+
self.assertTrue(cold["hold"])
|
|
149
|
+
self.assertLess(rp.directives(cold)["length_scale"], 0.7)
|
|
150
|
+
self.assertEqual(rp.reply_policy(cold)["mode"], "cold_short")
|
|
151
|
+
self.assertLessEqual(rp.reply_policy(cold)["max_chars"], 40)
|
|
152
|
+
later = rp.advance(cold, elapsed_delta=3 * 3600,
|
|
153
|
+
relationship=CALM_REL)
|
|
154
|
+
self.assertIn(later["stage"], ("record", "turn_page"))
|
|
155
|
+
self.assertLessEqual(later["debt"], 1.0)
|
|
156
|
+
|
|
157
|
+
def test_no_crossing_no_flow(self):
|
|
158
|
+
state = rp.start(bd.judge(NORMAL_TEXT, CALM_REL), CALM_REL)
|
|
159
|
+
self.assertFalse(state["active"])
|
|
160
|
+
self.assertEqual(rp.reply_policy(state)["mode"], "normal")
|
|
161
|
+
|
|
162
|
+
def test_disabled_reports_inactive(self):
|
|
163
|
+
state = rp.start(bd.judge(CROSSED_TEXT, CALM_REL), CALM_REL,
|
|
164
|
+
flags={"enabled": False})
|
|
165
|
+
self.assertFalse(state["enabled"])
|
|
166
|
+
self.assertFalse(state["active"])
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class ExitBehaviourTest(unittest.TestCase):
|
|
170
|
+
def test_exhausted_can_go_silent(self):
|
|
171
|
+
# exhausted + unhappy + low trust (silent beats delay when stress is
|
|
172
|
+
# not the dominant term; delay wins under acute stress).
|
|
173
|
+
result = ex.exit_or_silence(
|
|
174
|
+
state={"energy": 0.10, "stress": 0.00, "valence": -1.0,
|
|
175
|
+
"social": 0.1},
|
|
176
|
+
relationship={"closeness": 0.1, "trust": 0.1, "conflict": 0.5},
|
|
177
|
+
topic={"intensity": 0.2})
|
|
178
|
+
self.assertEqual(result["action"], "silent")
|
|
179
|
+
self.assertFalse(ex.should_reply(result))
|
|
180
|
+
|
|
181
|
+
def test_normal_turn_replies(self):
|
|
182
|
+
result = ex.exit_or_silence(
|
|
183
|
+
state={"energy": 0.8, "stress": 0.1, "valence": 0.3, "social": 0.8},
|
|
184
|
+
relationship={"closeness": 0.8, "trust": 0.8, "conflict": 0.0},
|
|
185
|
+
topic={"intensity": 0.2})
|
|
186
|
+
self.assertEqual(result["action"], "respond")
|
|
187
|
+
self.assertTrue(ex.should_reply(result))
|
|
188
|
+
|
|
189
|
+
def test_disabled_always_replies(self):
|
|
190
|
+
result = ex.exit_or_silence(
|
|
191
|
+
state={"energy": 0.0, "stress": 1.0, "valence": -1.0},
|
|
192
|
+
relationship={"conflict": 1.0}, flags={"enabled": False})
|
|
193
|
+
self.assertEqual(result["action"], "respond")
|
|
194
|
+
self.assertTrue(ex.should_reply(result))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class SameSentenceDifferentTurnTest(unittest.TestCase):
|
|
198
|
+
def test_crossed_and_complaint_differ(self):
|
|
199
|
+
normal = at.directives(at.attribute(
|
|
200
|
+
dims={"blame": 0.1, "goal": -0.2, "hit": 0.2, "involve": 0.7},
|
|
201
|
+
relationship=CALM_REL))
|
|
202
|
+
cold = rp.directives(rp.start(bd.judge(CROSSED_TEXT, CALM_REL),
|
|
203
|
+
CALM_REL))
|
|
204
|
+
self.assertNotEqual(normal["length_scale"], cold["length_scale"])
|
|
205
|
+
self.assertLess(cold["length_scale"], normal["length_scale"])
|
|
206
|
+
self.assertTrue(cold.get("tone"))
|
|
207
|
+
self.assertNotEqual(rp.reply_policy(rp.start(
|
|
208
|
+
bd.judge(CROSSED_TEXT, CALM_REL), CALM_REL))["mode"], "normal")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class PurityTest(unittest.TestCase):
|
|
212
|
+
def test_wired_modules_stay_pure(self):
|
|
213
|
+
for name in ("attributor", "boundary", "repair_after_boundary",
|
|
214
|
+
"exit_or_silence", "x_directives"):
|
|
215
|
+
source = (ROOT / "biocore_model" / (name + ".py")).read_text(
|
|
216
|
+
encoding="utf-8")
|
|
217
|
+
tree = ast.parse(source)
|
|
218
|
+
modules = set()
|
|
219
|
+
for node in ast.walk(tree):
|
|
220
|
+
if isinstance(node, ast.Import):
|
|
221
|
+
modules.update(alias.name.split(".")[0]
|
|
222
|
+
for alias in node.names)
|
|
223
|
+
elif isinstance(node, ast.ImportFrom) and node.module:
|
|
224
|
+
modules.add(node.module.split(".")[0])
|
|
225
|
+
self.assertNotIn("main", modules, name)
|
|
226
|
+
self.assertNotIn("asyncio", modules, name)
|
|
227
|
+
self.assertNotIn("random", modules, name)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
if __name__ == "__main__":
|
|
231
|
+
unittest.main()
|