File size: 6,058 Bytes
c3a3710
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""

Tests for Phase 5.0 Confidence Calibration Module.

Tests ConfidenceEnvelopeGenerator for all confidence levels and edge cases.

"""

import pytest
from datetime import datetime, timezone, timedelta
from unittest.mock import MagicMock

from mnemocore.core.confidence import (
    ConfidenceEnvelopeGenerator,
    build_confidence_envelope,
    LEVEL_HIGH,
    LEVEL_MEDIUM,
    LEVEL_LOW,
    LEVEL_CONTRADICTED,
    LEVEL_STALE,
)
from mnemocore.core.provenance import ProvenanceRecord


# ------------------------------------------------------------------ #
#  Helpers                                                            #
# ------------------------------------------------------------------ #

def _make_node(

    ltp_strength: float = 0.9,

    access_count: int = 10,

    days_old: float = 5.0,

    bayes_mean: float | None = None,

):
    """Create a minimal mock MemoryNode."""
    node = MagicMock()
    node.ltp_strength = ltp_strength
    node.access_count = access_count
    now = datetime.now(timezone.utc)
    node.last_accessed = now - timedelta(days=days_old)
    # Optionally inject a Bayesian state mock
    if bayes_mean is not None:
        bayes = MagicMock()
        bayes.mean = bayes_mean
        node._bayes = bayes
    else:
        # Remove _bayes attribute so hasattr returns False
        del node._bayes
    return node


# ------------------------------------------------------------------ #
#  ConfidenceEnvelopeGenerator                                        #
# ------------------------------------------------------------------ #

class TestConfidenceEnvelopeGenerator:
    def test_high_confidence(self):
        node = _make_node(ltp_strength=0.92, access_count=8, days_old=3)
        prov = ProvenanceRecord.new(origin_type="observation")
        env = ConfidenceEnvelopeGenerator.build(node, prov)
        assert env["level"] == LEVEL_HIGH
        assert env["reliability"] >= 0.80

    def test_medium_confidence_low_reliability(self):
        node = _make_node(ltp_strength=0.65, access_count=3, days_old=5)
        env = ConfidenceEnvelopeGenerator.build(node)
        assert env["level"] == LEVEL_MEDIUM

    def test_low_confidence_insufficient_access(self):
        node = _make_node(ltp_strength=0.75, access_count=1, days_old=2)
        env = ConfidenceEnvelopeGenerator.build(node)
        assert env["level"] == LEVEL_LOW

    def test_low_confidence_poor_reliability(self):
        node = _make_node(ltp_strength=0.35, access_count=10, days_old=2)
        env = ConfidenceEnvelopeGenerator.build(node)
        assert env["level"] == LEVEL_LOW

    def test_stale_overrides_high_reliability(self):
        """A memory last verified 40 days ago should be STALE even if reliable."""
        node = _make_node(ltp_strength=0.98, access_count=20, days_old=40)
        env = ConfidenceEnvelopeGenerator.build(node)
        assert env["level"] == LEVEL_STALE
        assert env["staleness_days"] >= 30

    def test_contradicted_overrides_everything(self):
        """Contradicted memories take priority over any reliability score."""
        node = _make_node(ltp_strength=0.99, access_count=100, days_old=1)
        prov = ProvenanceRecord.new(origin_type="observation")
        prov.mark_contradicted("cg_999")
        env = ConfidenceEnvelopeGenerator.build(node, prov)
        assert env["level"] == LEVEL_CONTRADICTED
        assert env["is_contradicted"] is True

    def test_source_type_observation(self):
        node = _make_node(ltp_strength=0.9, access_count=6)
        prov = ProvenanceRecord.new(origin_type="observation")
        env = ConfidenceEnvelopeGenerator.build(node, prov)
        assert env["source_type"] == "observation"
        assert env["source_trust"] == 1.0

    def test_source_type_dream_lower_trust(self):
        node = _make_node(ltp_strength=0.9, access_count=6)
        prov = ProvenanceRecord.new(origin_type="dream")
        env = ConfidenceEnvelopeGenerator.build(node, prov)
        assert env["source_type"] == "dream"
        # Dream trust is 0.6, should not reach HIGH level
        assert env["level"] != LEVEL_HIGH

    def test_verified_event_resets_staleness(self):
        """A fresh verification event should make staleness very short."""
        node = _make_node(ltp_strength=0.9, access_count=8, days_old=50)
        prov = ProvenanceRecord.new(origin_type="observation")
        prov.mark_verified(success=True)
        env = ConfidenceEnvelopeGenerator.build(node, prov)
        # Verified just now → staleness should be near 0
        assert env["staleness_days"] < 1.0
        assert env["level"] != LEVEL_STALE

    def test_bayesian_state_used_over_ltp(self):
        """If node has _bayes, reliability = bayes.mean, not ltp_strength."""
        node = _make_node(ltp_strength=0.3, access_count=6, bayes_mean=0.95)
        env = ConfidenceEnvelopeGenerator.build(node)
        assert env["reliability"] == pytest.approx(0.95, abs=0.01)

    def test_no_provenance_uses_last_accessed(self):
        node = _make_node(ltp_strength=0.85, access_count=7, days_old=5)
        env = ConfidenceEnvelopeGenerator.build(node, provenance=None)
        assert env["source_type"] == "unknown"
        assert "level" in env

    def test_envelope_keys_present(self):
        node = _make_node()
        env = build_confidence_envelope(node)
        expected_keys = {
            "level", "reliability", "access_count",
            "staleness_days", "source_type", "source_trust", "is_contradicted",
        }
        assert expected_keys.issubset(env.keys())

    def test_module_shortcut_same_result(self):
        node = _make_node(ltp_strength=0.88, access_count=6)
        prov = ProvenanceRecord.new()
        r1 = ConfidenceEnvelopeGenerator.build(node, prov)
        r2 = build_confidence_envelope(node, prov)
        assert r1["level"] == r2["level"]
        assert r1["reliability"] == r2["reliability"]