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,111 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Memory reconstruction: bounded drift, direction, conservation."""
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from biocore_model import reconstruction as rc
|
|
6
|
+
|
|
7
|
+
ENTRY = {"text": "那天一起看了海", "importance": 0.5, "valence": 0.2}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MagnitudeTest(unittest.TestCase):
|
|
11
|
+
def test_zero_recalls_no_drift(self):
|
|
12
|
+
self.assertEqual(rc.magnitude(0), 0.0)
|
|
13
|
+
|
|
14
|
+
def test_grows_with_recalls(self):
|
|
15
|
+
self.assertGreater(rc.magnitude(5), rc.magnitude(1))
|
|
16
|
+
|
|
17
|
+
def test_capped(self):
|
|
18
|
+
self.assertLessEqual(rc.magnitude(10 ** 6), rc.DRIFT_CAP)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DirectionTest(unittest.TestCase):
|
|
22
|
+
def test_warm_mood_positive(self):
|
|
23
|
+
self.assertEqual(rc.direction(0.6), 1)
|
|
24
|
+
|
|
25
|
+
def test_low_mood_negative(self):
|
|
26
|
+
self.assertEqual(rc.direction(-0.5), -1)
|
|
27
|
+
|
|
28
|
+
def test_neutral(self):
|
|
29
|
+
self.assertEqual(rc.direction(0.0), 0)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DriftTest(unittest.TestCase):
|
|
33
|
+
def test_no_recall_no_change(self):
|
|
34
|
+
out = rc.drift(ENTRY, mood=0.8, recalls=0)
|
|
35
|
+
self.assertEqual(out["delta"], 0.0)
|
|
36
|
+
|
|
37
|
+
def test_warm_mood_moves_positive(self):
|
|
38
|
+
out = rc.drift(ENTRY, mood=0.8, recalls=3)
|
|
39
|
+
self.assertGreater(out["delta"], 0.0)
|
|
40
|
+
|
|
41
|
+
def test_low_mood_moves_negative(self):
|
|
42
|
+
out = rc.drift(ENTRY, mood=-0.8, recalls=3)
|
|
43
|
+
self.assertLess(out["delta"], 0.0)
|
|
44
|
+
|
|
45
|
+
def test_neutral_mood_no_change(self):
|
|
46
|
+
out = rc.drift(ENTRY, mood=0.0, recalls=3)
|
|
47
|
+
self.assertEqual(out["delta"], 0.0)
|
|
48
|
+
|
|
49
|
+
def test_important_memory_never_drifts(self):
|
|
50
|
+
row = dict(ENTRY, importance=0.95)
|
|
51
|
+
out = rc.drift(row, mood=0.9, recalls=20)
|
|
52
|
+
self.assertEqual(out["delta"], 0.0)
|
|
53
|
+
self.assertEqual(out["reason"], "重要记忆不漂")
|
|
54
|
+
|
|
55
|
+
def test_empty_text_no_change(self):
|
|
56
|
+
out = rc.drift({"importance": 0.3}, mood=0.9, recalls=3)
|
|
57
|
+
self.assertEqual(out["delta"], 0.0)
|
|
58
|
+
|
|
59
|
+
def test_cap_respected(self):
|
|
60
|
+
out = rc.drift(ENTRY, mood=1.0, recalls=1000, prior_drift=0.10)
|
|
61
|
+
self.assertLessEqual(abs(out["total"]), rc.DRIFT_CAP)
|
|
62
|
+
self.assertTrue(out["capped"])
|
|
63
|
+
|
|
64
|
+
def test_conservation_positive_side(self):
|
|
65
|
+
out = rc.drift(ENTRY, mood=1.0, recalls=50, prior_drift=0.10)
|
|
66
|
+
self.assertAlmostEqual(out["total"], rc.DRIFT_CAP, places=3)
|
|
67
|
+
|
|
68
|
+
def test_conservation_negative_side(self):
|
|
69
|
+
out = rc.drift(ENTRY, mood=-1.0, recalls=50, prior_drift=-0.10)
|
|
70
|
+
self.assertAlmostEqual(out["total"], -rc.DRIFT_CAP, places=3)
|
|
71
|
+
|
|
72
|
+
def test_disabled(self):
|
|
73
|
+
out = rc.drift(ENTRY, mood=0.9, recalls=5, flags={"enabled": False})
|
|
74
|
+
self.assertEqual(out["delta"], 0.0)
|
|
75
|
+
self.assertFalse(out["enabled"])
|
|
76
|
+
|
|
77
|
+
def test_malformed_entry(self):
|
|
78
|
+
out = rc.drift("nope", mood="x", recalls="y")
|
|
79
|
+
self.assertEqual(out["delta"], 0.0)
|
|
80
|
+
|
|
81
|
+
def test_bounded_without_prior(self):
|
|
82
|
+
out = rc.drift(ENTRY, mood=-1.0, recalls=5)
|
|
83
|
+
self.assertGreaterEqual(out["total"], -rc.DRIFT_CAP)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class HintTest(unittest.TestCase):
|
|
87
|
+
def test_no_hint_when_tiny(self):
|
|
88
|
+
self.assertEqual(rc.text_hint({"enabled": True, "total": 0.0}), "")
|
|
89
|
+
|
|
90
|
+
def test_warm_hint(self):
|
|
91
|
+
self.assertIn("暖", rc.text_hint({"enabled": True, "total": 0.05}))
|
|
92
|
+
|
|
93
|
+
def test_cold_hint(self):
|
|
94
|
+
self.assertIn("凉", rc.text_hint({"enabled": True, "total": -0.05}))
|
|
95
|
+
|
|
96
|
+
def test_disabled_hint(self):
|
|
97
|
+
self.assertEqual(rc.text_hint({"enabled": False, "total": 0.05}), "")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ConserveTest(unittest.TestCase):
|
|
101
|
+
def test_within_cap_untouched(self):
|
|
102
|
+
out = rc.conserve([{"drift": 0.03}, {"drift": -0.02}])
|
|
103
|
+
self.assertEqual(out["scale"], 1.0)
|
|
104
|
+
|
|
105
|
+
def test_scales_down(self):
|
|
106
|
+
out = rc.conserve([{"drift": 0.3}, {"drift": 0.3}])
|
|
107
|
+
self.assertLessEqual(abs(out["total"]), rc.DRIFT_CAP)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
unittest.main()
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""task-175 调节策略库单测:Gross 五策略命中、成本/效果、关系调制、关闭。
|
|
3
|
+
|
|
4
|
+
只依赖 biocore_model.regulation_strategies 的纯函数;不导入 main.py。
|
|
5
|
+
"""
|
|
6
|
+
import unittest
|
|
7
|
+
|
|
8
|
+
from biocore_model import regulation_strategies as rs
|
|
9
|
+
|
|
10
|
+
CALM = {"closeness": 0.5, "trust": 0.5, "conflict": 0.0}
|
|
11
|
+
|
|
12
|
+
SELECT_STATE = {"stress": 0.2, "energy": 0.7}
|
|
13
|
+
SELECT_EVENT = {"when": "before", "controllable": 0.9, "intensity": 0.4}
|
|
14
|
+
|
|
15
|
+
MODIFY_STATE = {"stress": 0.3, "energy": 0.6}
|
|
16
|
+
MODIFY_EVENT = {"when": "after", "controllable": 0.8, "intensity": 0.5}
|
|
17
|
+
MODIFY_REL = {"closeness": 0.7, "trust": 0.6}
|
|
18
|
+
|
|
19
|
+
ATTENTION_STATE = {"stress": 0.8, "energy": 0.2, "valence": -0.3}
|
|
20
|
+
ATTENTION_EVENT = {"when": "after", "controllable": 0.1, "intensity": 0.9}
|
|
21
|
+
|
|
22
|
+
REAPPRAISE_STATE = {"stress": 0.2, "energy": 0.8, "valence": 0.4}
|
|
23
|
+
REAPPRAISE_EVENT = {"when": "after", "controllable": 0.7, "intensity": 0.4}
|
|
24
|
+
REAPPRAISE_REL = {"closeness": 0.9, "trust": 0.9}
|
|
25
|
+
|
|
26
|
+
MODULATE_STATE = {"stress": 0.7, "energy": 0.4}
|
|
27
|
+
MODULATE_EVENT = {"when": "after", "controllable": 0.5, "intensity": 0.6,
|
|
28
|
+
"public": True}
|
|
29
|
+
MODULATE_REL = {"closeness": 0.2, "trust": 0.2, "conflict": 0.8}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ContractTest(unittest.TestCase):
|
|
33
|
+
def test_result_keys(self):
|
|
34
|
+
out = rs.evaluate(SELECT_STATE, SELECT_EVENT, CALM)
|
|
35
|
+
for key in ("enabled", "strategy", "label", "cost", "effect", "reason",
|
|
36
|
+
"self_talk", "coping_tier", "confidence", "fit", "scores",
|
|
37
|
+
"profiles"):
|
|
38
|
+
self.assertIn(key, out)
|
|
39
|
+
|
|
40
|
+
def test_five_strategies_and_labels(self):
|
|
41
|
+
self.assertEqual(len(rs.STRATEGIES), 5)
|
|
42
|
+
self.assertEqual(set(rs.LABELS), set(rs.STRATEGIES))
|
|
43
|
+
self.assertEqual(rs.LABELS["situation_selection"], "情境选择")
|
|
44
|
+
self.assertEqual(rs.LABELS["situation_modification"], "情境修正")
|
|
45
|
+
self.assertEqual(rs.LABELS["attentional_deployment"], "注意部署")
|
|
46
|
+
self.assertEqual(rs.LABELS["cognitive_reappraisal"], "认知重评")
|
|
47
|
+
self.assertEqual(rs.LABELS["response_modulation"], "反应调节")
|
|
48
|
+
|
|
49
|
+
def test_old_coping_tiers_coexist(self):
|
|
50
|
+
self.assertEqual(rs.COPING_TIERS,
|
|
51
|
+
("problem_focused", "emotion_focused", "avoidance"))
|
|
52
|
+
self.assertEqual(set(rs.TIER_OF), set(rs.STRATEGIES))
|
|
53
|
+
for tier in rs.TIER_OF.values():
|
|
54
|
+
self.assertIn(tier, rs.COPING_TIERS)
|
|
55
|
+
|
|
56
|
+
def test_values_bounded(self):
|
|
57
|
+
for args in (
|
|
58
|
+
(SELECT_STATE, SELECT_EVENT, CALM),
|
|
59
|
+
(ATTENTION_STATE, ATTENTION_EVENT, CALM),
|
|
60
|
+
(MODULATE_STATE, MODULATE_EVENT, MODULATE_REL),
|
|
61
|
+
):
|
|
62
|
+
out = rs.evaluate(*args)
|
|
63
|
+
for key in ("cost", "effect", "fit", "confidence"):
|
|
64
|
+
self.assertGreaterEqual(out[key], 0.0)
|
|
65
|
+
self.assertLessEqual(out[key], 1.0)
|
|
66
|
+
self.assertEqual(set(out["scores"]), set(rs.STRATEGIES))
|
|
67
|
+
|
|
68
|
+
def test_deterministic(self):
|
|
69
|
+
first = rs.evaluate(MODULATE_STATE, MODULATE_EVENT, MODULATE_REL)
|
|
70
|
+
second = rs.evaluate(MODULATE_STATE, MODULATE_EVENT, MODULATE_REL)
|
|
71
|
+
self.assertEqual(first, second)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class FiveStrategiesTest(unittest.TestCase):
|
|
75
|
+
def test_situation_selection(self):
|
|
76
|
+
out = rs.evaluate(SELECT_STATE, SELECT_EVENT, CALM)
|
|
77
|
+
self.assertEqual(out["strategy"], "situation_selection")
|
|
78
|
+
self.assertEqual(out["coping_tier"], "problem_focused")
|
|
79
|
+
|
|
80
|
+
def test_situation_modification(self):
|
|
81
|
+
out = rs.evaluate(MODIFY_STATE, MODIFY_EVENT, MODIFY_REL)
|
|
82
|
+
self.assertEqual(out["strategy"], "situation_modification")
|
|
83
|
+
self.assertEqual(out["coping_tier"], "problem_focused")
|
|
84
|
+
|
|
85
|
+
def test_attentional_deployment(self):
|
|
86
|
+
out = rs.evaluate(ATTENTION_STATE, ATTENTION_EVENT, CALM)
|
|
87
|
+
self.assertEqual(out["strategy"], "attentional_deployment")
|
|
88
|
+
self.assertEqual(out["coping_tier"], "avoidance")
|
|
89
|
+
|
|
90
|
+
def test_cognitive_reappraisal(self):
|
|
91
|
+
out = rs.evaluate(REAPPRAISE_STATE, REAPPRAISE_EVENT, REAPPRAISE_REL)
|
|
92
|
+
self.assertEqual(out["strategy"], "cognitive_reappraisal")
|
|
93
|
+
self.assertEqual(out["coping_tier"], "emotion_focused")
|
|
94
|
+
|
|
95
|
+
def test_response_modulation(self):
|
|
96
|
+
out = rs.evaluate(MODULATE_STATE, MODULATE_EVENT, MODULATE_REL)
|
|
97
|
+
self.assertEqual(out["strategy"], "response_modulation")
|
|
98
|
+
self.assertEqual(out["coping_tier"], "emotion_focused")
|
|
99
|
+
self.assertGreater(out["cost"], 0.6)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class CostEffectTest(unittest.TestCase):
|
|
103
|
+
def test_suppression_costs_more_than_selection(self):
|
|
104
|
+
selection = rs.profile("situation_selection", SELECT_STATE,
|
|
105
|
+
SELECT_EVENT, CALM)
|
|
106
|
+
modulation = rs.profile("response_modulation", MODULATE_STATE,
|
|
107
|
+
MODULATE_EVENT, MODULATE_REL)
|
|
108
|
+
self.assertGreater(modulation["cost"], selection["cost"])
|
|
109
|
+
|
|
110
|
+
def test_suppression_costs_more_than_reappraisal(self):
|
|
111
|
+
reappraisal = rs.profile("cognitive_reappraisal", MODULATE_STATE,
|
|
112
|
+
MODULATE_EVENT, MODULATE_REL)
|
|
113
|
+
modulation = rs.profile("response_modulation", MODULATE_STATE,
|
|
114
|
+
MODULATE_EVENT, MODULATE_REL)
|
|
115
|
+
self.assertGreater(modulation["cost"], reappraisal["cost"])
|
|
116
|
+
|
|
117
|
+
def test_reappraisal_costs_more_than_distraction(self):
|
|
118
|
+
state = {"energy": 0.5, "stress": 0.5}
|
|
119
|
+
event = {"intensity": 0.5}
|
|
120
|
+
reappraisal = rs.profile("cognitive_reappraisal", state, event, CALM)
|
|
121
|
+
attention = rs.profile("attentional_deployment", state, event, CALM)
|
|
122
|
+
self.assertGreater(reappraisal["cost"], attention["cost"])
|
|
123
|
+
|
|
124
|
+
def test_effect_rises_with_control(self):
|
|
125
|
+
low = rs.profile("situation_selection", {}, {"controllable": 0.1})
|
|
126
|
+
high = rs.profile("situation_selection", {}, {"controllable": 0.9})
|
|
127
|
+
self.assertGreater(high["effect"], low["effect"])
|
|
128
|
+
|
|
129
|
+
def test_profiles_cover_all_strategies(self):
|
|
130
|
+
rows = rs.profiles(SELECT_STATE, SELECT_EVENT, CALM)
|
|
131
|
+
self.assertEqual({row["strategy"] for row in rows},
|
|
132
|
+
set(rs.STRATEGIES))
|
|
133
|
+
for first, second in zip(rows, rows[1:]):
|
|
134
|
+
self.assertGreaterEqual(first["score"], second["score"])
|
|
135
|
+
for row in rows:
|
|
136
|
+
for key in ("cost", "effect", "fit", "score"):
|
|
137
|
+
self.assertGreaterEqual(row[key], 0.0)
|
|
138
|
+
self.assertLessEqual(row[key], 1.0)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class RelationshipTest(unittest.TestCase):
|
|
142
|
+
def test_trust_raises_reappraisal_fit(self):
|
|
143
|
+
near = rs.profile("cognitive_reappraisal", REAPPRAISE_STATE,
|
|
144
|
+
REAPPRAISE_EVENT, {"closeness": 0.9, "trust": 0.9})
|
|
145
|
+
far = rs.profile("cognitive_reappraisal", REAPPRAISE_STATE,
|
|
146
|
+
REAPPRAISE_EVENT, {"closeness": 0.1, "trust": 0.1})
|
|
147
|
+
self.assertGreater(near["fit"], far["fit"])
|
|
148
|
+
|
|
149
|
+
def test_close_relationship_prefers_reappraisal(self):
|
|
150
|
+
near = rs.evaluate(REAPPRAISE_STATE, REAPPRAISE_EVENT,
|
|
151
|
+
{"closeness": 0.9, "trust": 0.9})
|
|
152
|
+
far = rs.evaluate(REAPPRAISE_STATE, REAPPRAISE_EVENT,
|
|
153
|
+
{"closeness": 0.1, "trust": 0.1})
|
|
154
|
+
self.assertEqual(near["strategy"], "cognitive_reappraisal")
|
|
155
|
+
self.assertNotEqual(far["strategy"], near["strategy"])
|
|
156
|
+
|
|
157
|
+
def test_public_conflict_prefers_suppression(self):
|
|
158
|
+
out = rs.evaluate(MODULATE_STATE, MODULATE_EVENT, MODULATE_REL)
|
|
159
|
+
self.assertEqual(out["strategy"], "response_modulation")
|
|
160
|
+
reappraisal_fit = out["scores"]["cognitive_reappraisal"]
|
|
161
|
+
self.assertLess(reappraisal_fit, out["scores"]["response_modulation"])
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class SwitchTest(unittest.TestCase):
|
|
165
|
+
def test_disabled_returns_old_coping(self):
|
|
166
|
+
out = rs.evaluate(SELECT_STATE, SELECT_EVENT, CALM,
|
|
167
|
+
flags={"enabled": False})
|
|
168
|
+
self.assertFalse(out["enabled"])
|
|
169
|
+
self.assertEqual(out["strategy"], "none")
|
|
170
|
+
self.assertEqual(out["cost"], 0.0)
|
|
171
|
+
self.assertEqual(out["effect"], 0.0)
|
|
172
|
+
self.assertIsNone(out["scores"].get("situation_selection"))
|
|
173
|
+
self.assertIn("关闭", out["reason"])
|
|
174
|
+
|
|
175
|
+
def test_directives_disabled_is_neutral(self):
|
|
176
|
+
out = rs.directives(rs.evaluate(flags={"enabled": False}))
|
|
177
|
+
self.assertEqual(out["strategy"], "none")
|
|
178
|
+
self.assertEqual(out["tone"], "")
|
|
179
|
+
self.assertEqual(out["coping_tier"], "")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class SelfTalkTest(unittest.TestCase):
|
|
183
|
+
def test_self_talk_exists_for_every_strategy(self):
|
|
184
|
+
for strategy in rs.STRATEGIES:
|
|
185
|
+
row = rs.profile(strategy)
|
|
186
|
+
self.assertTrue(row["self_talk"])
|
|
187
|
+
self.assertLessEqual(len(row["self_talk"]), 40)
|
|
188
|
+
self.assertTrue(row["reason"])
|
|
189
|
+
|
|
190
|
+
def test_directives_carry_strategy_and_tone(self):
|
|
191
|
+
out = rs.directives(rs.evaluate(SELECT_STATE, SELECT_EVENT, CALM))
|
|
192
|
+
self.assertEqual(out["strategy"], "situation_selection")
|
|
193
|
+
self.assertIn(out["coping_tier"], rs.COPING_TIERS)
|
|
194
|
+
self.assertTrue(out["tone"])
|
|
195
|
+
self.assertGreaterEqual(out["cost"], 0.0)
|
|
196
|
+
self.assertLessEqual(out["effect"], 1.0)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class RobustnessTest(unittest.TestCase):
|
|
200
|
+
def test_odd_inputs_still_choose(self):
|
|
201
|
+
for state in (None, {}, "x", 3, [], {"stress": float("nan")}):
|
|
202
|
+
out = rs.evaluate(state=state)
|
|
203
|
+
self.assertIn(out["strategy"], rs.STRATEGIES)
|
|
204
|
+
self.assertTrue(out["reason"])
|
|
205
|
+
for event in (None, "x", 3, [], {"controllable": "?"}):
|
|
206
|
+
self.assertIn(rs.evaluate(SELECT_STATE, event)["strategy"],
|
|
207
|
+
rs.STRATEGIES)
|
|
208
|
+
for relationship in (None, "x", 3, []):
|
|
209
|
+
self.assertIn(rs.evaluate(relationship=relationship)["strategy"],
|
|
210
|
+
rs.STRATEGIES)
|
|
211
|
+
|
|
212
|
+
def test_unknown_profile_is_neutral(self):
|
|
213
|
+
out = rs.profile("nope")
|
|
214
|
+
self.assertEqual(out["strategy"], "none")
|
|
215
|
+
self.assertEqual(out["cost"], 0.0)
|
|
216
|
+
self.assertEqual(out["score"], 0.0)
|
|
217
|
+
|
|
218
|
+
def test_confidence_within_bounds(self):
|
|
219
|
+
for args in ((SELECT_STATE, SELECT_EVENT, CALM),
|
|
220
|
+
(ATTENTION_STATE, ATTENTION_EVENT, CALM),
|
|
221
|
+
({}, {}, {})):
|
|
222
|
+
out = rs.evaluate(*args)
|
|
223
|
+
self.assertGreaterEqual(out["confidence"], 0.0)
|
|
224
|
+
self.assertLessEqual(out["confidence"], 1.0)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
if __name__ == "__main__":
|
|
228
|
+
unittest.main()
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Relationship stage: progress, thresholds, actions, fairness."""
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from biocore_model import relation_stage as rs
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def state(**kwargs):
|
|
9
|
+
base = {"interactions": 0, "disclosures": 0, "repairs": 0,
|
|
10
|
+
"days_known": 0, "conflict": 0.0}
|
|
11
|
+
base.update(kwargs)
|
|
12
|
+
return base
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ProgressTest(unittest.TestCase):
|
|
16
|
+
def test_empty_is_zero(self):
|
|
17
|
+
self.assertEqual(rs.progress({}), 0.0)
|
|
18
|
+
|
|
19
|
+
def test_more_interactions_more_progress(self):
|
|
20
|
+
low = rs.progress(state(interactions=2))
|
|
21
|
+
high = rs.progress(state(interactions=40))
|
|
22
|
+
self.assertGreater(high, low)
|
|
23
|
+
|
|
24
|
+
def test_disclosure_moves_stage_fast(self):
|
|
25
|
+
low = rs.progress(state(interactions=5))
|
|
26
|
+
high = rs.progress(state(interactions=5, disclosures=10))
|
|
27
|
+
self.assertGreater(high, low)
|
|
28
|
+
|
|
29
|
+
def test_repairs_add(self):
|
|
30
|
+
low = rs.progress(state(interactions=10, disclosures=4))
|
|
31
|
+
high = rs.progress(state(interactions=10, disclosures=4, repairs=3))
|
|
32
|
+
self.assertGreater(high, low)
|
|
33
|
+
|
|
34
|
+
def test_conflict_subtracts(self):
|
|
35
|
+
calm = rs.progress(state(interactions=20, disclosures=6))
|
|
36
|
+
tense = rs.progress(state(interactions=20, disclosures=6, conflict=0.9))
|
|
37
|
+
self.assertLess(tense, calm)
|
|
38
|
+
|
|
39
|
+
def test_progress_bounded(self):
|
|
40
|
+
value = rs.progress(state(interactions=9999, disclosures=9999, repairs=9999,
|
|
41
|
+
days_known=9999))
|
|
42
|
+
self.assertLessEqual(value, 1.0)
|
|
43
|
+
self.assertGreaterEqual(value, 0.0)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class StageTest(unittest.TestCase):
|
|
47
|
+
def test_thresholds(self):
|
|
48
|
+
self.assertEqual(rs.stage_of(0.1), "陌生")
|
|
49
|
+
self.assertEqual(rs.stage_of(0.5), "熟悉")
|
|
50
|
+
self.assertEqual(rs.stage_of(0.7), "亲近")
|
|
51
|
+
self.assertEqual(rs.stage_of(0.9), "稳定")
|
|
52
|
+
|
|
53
|
+
def test_actions_match_stages(self):
|
|
54
|
+
for stage in rs.STAGES:
|
|
55
|
+
self.assertIn(stage, rs.ACTIONS)
|
|
56
|
+
|
|
57
|
+
def test_evaluate_basic(self):
|
|
58
|
+
out = rs.evaluate(state(interactions=50, disclosures=12))
|
|
59
|
+
self.assertIn(out["stage"], rs.STAGES)
|
|
60
|
+
self.assertTrue(out["action"])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class FairnessTest(unittest.TestCase):
|
|
64
|
+
def test_balanced(self):
|
|
65
|
+
out = rs.comparison({"his_investment": 0.5, "hers_investment": 0.5})
|
|
66
|
+
self.assertEqual(out["share"], 0.5)
|
|
67
|
+
self.assertEqual(out["balance"], 0.0)
|
|
68
|
+
|
|
69
|
+
def test_one_sided(self):
|
|
70
|
+
out = rs.comparison({"his_investment": 0.9, "hers_investment": 0.1})
|
|
71
|
+
self.assertGreater(out["balance"], 0.5)
|
|
72
|
+
|
|
73
|
+
def test_one_sided_marks_tentative(self):
|
|
74
|
+
out = rs.evaluate(state(interactions=50, disclosures=12,
|
|
75
|
+
social_comparison={"his_investment": 0.9,
|
|
76
|
+
"hers_investment": 0.1}))
|
|
77
|
+
self.assertTrue(out["tentative"])
|
|
78
|
+
|
|
79
|
+
def test_unfairness_slows_progress(self):
|
|
80
|
+
fair = rs.progress(state(interactions=30, disclosures=8,
|
|
81
|
+
social_comparison={"his_investment": 0.5,
|
|
82
|
+
"hers_investment": 0.5}))
|
|
83
|
+
unfair = rs.progress(state(interactions=30, disclosures=8,
|
|
84
|
+
social_comparison={"his_investment": 0.95,
|
|
85
|
+
"hers_investment": 0.05}))
|
|
86
|
+
self.assertLess(unfair, fair)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class FallbackTest(unittest.TestCase):
|
|
90
|
+
def test_disabled(self):
|
|
91
|
+
out = rs.evaluate(state(interactions=100), flags={"enabled": False})
|
|
92
|
+
self.assertFalse(out["enabled"])
|
|
93
|
+
self.assertEqual(out["progress"], 0.0)
|
|
94
|
+
|
|
95
|
+
def test_malformed(self):
|
|
96
|
+
out = rs.evaluate("nope")
|
|
97
|
+
self.assertIn(out["stage"], rs.STAGES)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class DirectiveTest(unittest.TestCase):
|
|
101
|
+
def test_stage_tone(self):
|
|
102
|
+
out = rs.directives({"enabled": True, "stage": "亲近"})
|
|
103
|
+
self.assertIn("主动", out["tone"])
|
|
104
|
+
|
|
105
|
+
def test_tentative_note(self):
|
|
106
|
+
out = rs.directives({"enabled": True, "stage": "熟悉", "tentative": True})
|
|
107
|
+
self.assertIn("不对等", out["tone"])
|
|
108
|
+
|
|
109
|
+
def test_disabled(self):
|
|
110
|
+
self.assertEqual(rs.directives({"enabled": False})["tone"], "")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
unittest.main()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import random, unittest
|
|
3
|
+
from biocore_model import relationship_stage as rs
|
|
4
|
+
|
|
5
|
+
NOW = 1_700_000_000
|
|
6
|
+
|
|
7
|
+
class StageTest(unittest.TestCase):
|
|
8
|
+
def test_stranger(self):
|
|
9
|
+
self.assertEqual(rs.stage_for(5), 'stranger')
|
|
10
|
+
def test_acquaintance(self):
|
|
11
|
+
self.assertEqual(rs.stage_for(10), 'acquaintance')
|
|
12
|
+
def test_friend(self):
|
|
13
|
+
self.assertEqual(rs.stage_for(55), 'friend')
|
|
14
|
+
def test_close(self):
|
|
15
|
+
self.assertEqual(rs.stage_for(91), 'close')
|
|
16
|
+
def test_boundary_50(self):
|
|
17
|
+
self.assertEqual(rs.stage_for(50), 'friend')
|
|
18
|
+
def test_boundary_90(self):
|
|
19
|
+
self.assertEqual(rs.stage_for(90), 'close')
|
|
20
|
+
def test_friend_config(self):
|
|
21
|
+
cfg = rs.stage_config(55)
|
|
22
|
+
self.assertEqual(cfg['tau_hours'], 24); self.assertEqual(cfg['daily_cap'], 5)
|
|
23
|
+
def test_stranger_config(self):
|
|
24
|
+
cfg = rs.stage_config(0)
|
|
25
|
+
self.assertEqual(cfg['tau_hours'], 8); self.assertEqual(cfg['daily_cap'], 1)
|
|
26
|
+
def test_min_interval(self):
|
|
27
|
+
cfg = rs.stage_config(55)
|
|
28
|
+
self.assertAlmostEqual(cfg['min_interval_hours'], round(24 / (5 * 0.6), 2))
|
|
29
|
+
|
|
30
|
+
class HabituationTest(unittest.TestCase):
|
|
31
|
+
def test_novelty_decay(self):
|
|
32
|
+
self.assertAlmostEqual(rs.novelty(0), 1.0)
|
|
33
|
+
self.assertAlmostEqual(rs.novelty(2), round(0.85 ** 2, 4))
|
|
34
|
+
def test_disabled_below_threshold(self):
|
|
35
|
+
usage = {'t1': {'count': 8, 'last_ts': NOW - 60}}
|
|
36
|
+
info = rs.template_weight('t1', usage, NOW)
|
|
37
|
+
self.assertTrue(info['disabled']); self.assertLess(info['weight'], 0.3)
|
|
38
|
+
def test_not_disabled_after_24h(self):
|
|
39
|
+
usage = {'t1': {'count': 8, 'last_ts': NOW - 25 * 3600}}
|
|
40
|
+
self.assertFalse(rs.template_weight('t1', usage, NOW)['disabled'])
|
|
41
|
+
def test_window_reset(self):
|
|
42
|
+
usage = {'t1': {'count': 8, 'last_ts': NOW - 8 * 86400}}
|
|
43
|
+
info = rs.template_weight('t1', usage, NOW)
|
|
44
|
+
self.assertEqual(info['count'], 0)
|
|
45
|
+
def test_record_template(self):
|
|
46
|
+
usage = rs.record_template({}, 't1', NOW)
|
|
47
|
+
self.assertEqual(usage['t1']['count'], 1)
|
|
48
|
+
def test_templates_view(self):
|
|
49
|
+
usage = {'t1': {'count': 9, 'last_ts': NOW - 10}, 't2': {'count': 1, 'last_ts': NOW - 10}}
|
|
50
|
+
view = rs.templates_view(usage, NOW)
|
|
51
|
+
self.assertIn('t1', view['disabled']); self.assertEqual(view['top_used'][0]['template_id'], 't1')
|
|
52
|
+
def test_pick_weighted_skips_disabled(self):
|
|
53
|
+
usage = {'t1': {'count': 9, 'last_ts': NOW - 10}}
|
|
54
|
+
self.assertEqual(rs.pick_weighted(['t1'], usage, NOW, random.Random(0)), None)
|
|
55
|
+
def test_pick_weighted_returns_candidate(self):
|
|
56
|
+
self.assertEqual(rs.pick_weighted(['t1', 't2'], {}, NOW, random.Random(0)) in ('t1', 't2'), True)
|
|
57
|
+
|
|
58
|
+
if __name__ == '__main__':
|
|
59
|
+
unittest.main()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Release consistency + autonomy defaults (version taken from metadata)."""
|
|
3
|
+
import json
|
|
4
|
+
import unittest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
ROOT = Path(__file__).resolve().parents[2]
|
|
8
|
+
PLUG = ROOT
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VersionTest(unittest.TestCase):
|
|
12
|
+
def test_all_version_sources_match_metadata(self):
|
|
13
|
+
meta = (PLUG / "metadata.yaml").read_text(encoding="utf-8")
|
|
14
|
+
version = None
|
|
15
|
+
for line in meta.splitlines():
|
|
16
|
+
if line.startswith("version:"):
|
|
17
|
+
version = line.split(":", 1)[1].strip()
|
|
18
|
+
break
|
|
19
|
+
self.assertIsNotNone(version)
|
|
20
|
+
main = (PLUG / "main.py").read_text(encoding="utf-8")
|
|
21
|
+
self.assertIn('"%s")' % version, main)
|
|
22
|
+
app = (PLUG / "pages" / "dashboard" / "app.js").read_text(encoding="utf-8")
|
|
23
|
+
self.assertIn('BIO_VERSION = "v%s"' % version, app)
|
|
24
|
+
index = (PLUG / "pages" / "dashboard" / "index.html").read_text(encoding="utf-8")
|
|
25
|
+
self.assertIn('BIO_VERSION = "v%s"' % version, index)
|
|
26
|
+
readme = (PLUG / "README.md").read_text(encoding="utf-8")
|
|
27
|
+
self.assertIn(version, readme)
|
|
28
|
+
record = json.loads((ROOT.parent / "docs" / "BIOCORE-MARKET-RECORD-v1.json")
|
|
29
|
+
.read_text(encoding="utf-8"))
|
|
30
|
+
rec = record["Johnhmzm/astrbot_plugin_biocore"]
|
|
31
|
+
self.assertEqual(rec["version"], version)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AutonomyDefaultTest(unittest.TestCase):
|
|
35
|
+
def test_schema_defaults_false(self):
|
|
36
|
+
schema = json.loads((PLUG / "_conf_schema.json").read_text(encoding="utf-8"))
|
|
37
|
+
self.assertIs(schema["proactive_autonomy"]["default"], False)
|
|
38
|
+
self.assertIs(schema["proactive_send"]["default"], False)
|
|
39
|
+
self.assertIs(schema["autonomy_loop_enabled"]["default"], False)
|
|
40
|
+
|
|
41
|
+
def test_code_reads_config_with_false_default(self):
|
|
42
|
+
main = (PLUG / "main.py").read_text(encoding="utf-8")
|
|
43
|
+
self.assertIn('self.proactive_autonomy = bool(self.config.get("proactive_autonomy", False))', main)
|
|
44
|
+
self.assertIn('self.proactive_send = bool(self.config.get("proactive_send", False))', main)
|
|
45
|
+
|
|
46
|
+
def test_changelog_warnings_and_rollback(self):
|
|
47
|
+
text = (PLUG / "CHANGELOG.md").read_text(encoding="utf-8")
|
|
48
|
+
self.assertIn("本版本改动较大", text)
|
|
49
|
+
self.assertIn("自主主动回复默认关闭", text)
|
|
50
|
+
self.assertIn("升级失败可回滚", text)
|
|
51
|
+
self.assertIn("docs/MIGRATION.md", text)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
unittest.main()
|