Add utilities and tests for contradiction log

This commit is contained in:
Alexa Amundson
2025-12-02 21:04:13 -06:00
parent 02c3049477
commit 5cdff71361
2 changed files with 74 additions and 4 deletions

View File

@@ -7,10 +7,12 @@ The intent is to build a transparent record for later analysis.
""" """
from __future__ import annotations from __future__ import annotations
import json import json
import time import time
from dataclasses import dataclass, asdict from dataclasses import dataclass, asdict
from typing import List from pathlib import Path
from typing import List, Optional
@dataclass @dataclass
@@ -22,17 +24,19 @@ class ContradictionEntry:
class ContradictionLog: class ContradictionLog:
def __init__(self, path: str = "contradiction_log.jsonl") -> None: def __init__(self, path: str = "contradiction_log.jsonl") -> None:
self.path = path self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
def append(self, prompt: str, reply: str) -> None: def append(self, prompt: str, reply: str) -> None:
entry = ContradictionEntry(time.time(), prompt, reply) entry = ContradictionEntry(time.time(), prompt, reply)
with open(self.path, "a", encoding="utf-8") as f: self.path.parent.mkdir(parents=True, exist_ok=True)
with self.path.open("a", encoding="utf-8") as f:
f.write(json.dumps(asdict(entry)) + "\n") f.write(json.dumps(asdict(entry)) + "\n")
def read_all(self) -> List[ContradictionEntry]: def read_all(self) -> List[ContradictionEntry]:
entries: List[ContradictionEntry] = [] entries: List[ContradictionEntry] = []
try: try:
with open(self.path, "r", encoding="utf-8") as f: with self.path.open("r", encoding="utf-8") as f:
for line in f: for line in f:
data = json.loads(line.strip()) data = json.loads(line.strip())
entries.append(ContradictionEntry(**data)) entries.append(ContradictionEntry(**data))
@@ -40,6 +44,27 @@ class ContradictionLog:
pass pass
return entries return entries
def to_dicts(self) -> List[dict]:
"""Return log entries as dictionaries for serialization or inspection."""
return [asdict(entry) for entry in self.read_all()]
def latest(self) -> Optional[ContradictionEntry]:
"""Return the most recent log entry if available."""
entries = self.read_all()
return entries[-1] if entries else None
def summary(self) -> dict:
"""Provide a small summary about the log contents."""
entries = self.read_all()
return {
"count": len(entries),
"oldest_ts": entries[0].timestamp if entries else None,
"newest_ts": entries[-1].timestamp if entries else None,
}
def __len__(self) -> int: # pragma: no cover - simple delegation
return len(self.read_all())
if __name__ == "__main__": if __name__ == "__main__":
# Example usage: # Example usage:

View File

@@ -0,0 +1,45 @@
from __future__ import annotations
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest import TestCase
from unittest.mock import patch
from lucidia.contradiction_log import ContradictionLog
class ContradictionLogTest(TestCase):
def test_append_and_read_all(self) -> None:
with TemporaryDirectory() as tmp:
log_path = Path(tmp) / "logs" / "contradictions.jsonl"
log = ContradictionLog(str(log_path))
with patch("lucidia.contradiction_log.time.time", side_effect=[1000.0, 1001.5]):
log.append("prompt-one", "reply-one")
log.append("prompt-two", "reply-two")
entries = log.read_all()
self.assertEqual(len(entries), 2)
self.assertEqual(entries[0].prompt, "prompt-one")
self.assertEqual(entries[1].reply, "reply-two")
self.assertEqual(entries[0].timestamp, 1000.0)
self.assertEqual(entries[1].timestamp, 1001.5)
def test_summary_and_latest(self) -> None:
with TemporaryDirectory() as tmp:
log = ContradictionLog(str(Path(tmp) / "nested" / "log.jsonl"))
with patch("lucidia.contradiction_log.time.time", side_effect=[42.0, 84.0]):
log.append("a", "b")
log.append("c", "d")
summary = log.summary()
self.assertEqual(summary["count"], 2)
self.assertEqual(summary["oldest_ts"], 42.0)
self.assertEqual(summary["newest_ts"], 84.0)
latest = log.latest()
assert latest is not None
self.assertEqual(latest.prompt, "c")
self.assertEqual(latest.reply, "d")