The D11 deviation is a single-row pgvector upsert, not a full vectorstore resync.
Ports memory.py + session/{store,artifacts} + textutil (deps of memory), renamed
psdwp3->nsp. Decision import paths rewired from nsp.session.decisions to nsp.decisions
(our A4 port lives at the top level). session/models.py left UNCHANGED to preserve
the Phase-A ThothII additions (D12/D15 author/summary, D14a grounded_values,
D14b concept_formulas).
New in memory.py:
- memory_vector_record_for_decision(records, decision_seq): the single VectorRecord
for a chosen decision (reuses memory_vector_records, filtered to one).
- save_one_memory(records, decision_seq, writer, embedder): embeds one record and
calls writer.upsert_records('memory', [row]) -- NEVER writer.sync (that's the
full-resync, server-side-only path). Returns the upsert count.
L1: test_memory_save_one (5 tests) pins the contract -- single row, one upsert
call, sync never called, None/0 for unknown seq.
Deferred: the full nsp memory save-one CLI command (config/session loading + the
workstation write-guard) lands when memory_cmd.py is ported alongside the other
CLI commands. The pure D11 core is what L1 can honestly cover here.
40 lines
1.4 KiB
Python
40 lines
1.4 KiB
Python
from pathlib import Path
|
|
|
|
from nsp.decisions import DecisionRecord
|
|
from nsp.session.models import SchemaLinking
|
|
|
|
|
|
def _find_evidence_file(evidence_root: Path, evidence_id: str) -> str:
|
|
for match in evidence_root.rglob(f"{evidence_id}.md"):
|
|
return str(match)
|
|
return ""
|
|
|
|
|
|
def build_evidence_entries(
|
|
decisions: list[DecisionRecord],
|
|
linking: SchemaLinking,
|
|
evidence_root: Path,
|
|
) -> list[dict]:
|
|
"""Elenco {id, file, esito, decision_seq}: evidence citate nello schema linking
|
|
(esito 'usata') e decisioni esplicite del reviewer (accettata/scartata, che
|
|
prevalgono sul linking)."""
|
|
entries: dict[str, dict] = {}
|
|
for candidate in linking.candidates:
|
|
for evidence_id in candidate.evidence:
|
|
entries.setdefault(evidence_id, {
|
|
"id": evidence_id,
|
|
"file": _find_evidence_file(evidence_root, evidence_id),
|
|
"esito": "usata",
|
|
"decision_seq": candidate.decision_seq,
|
|
})
|
|
for d in decisions:
|
|
if d.type not in ("evidence_accepted", "evidence_rejected"):
|
|
continue
|
|
entries[d.subject] = {
|
|
"id": d.subject,
|
|
"file": _find_evidence_file(evidence_root, d.subject),
|
|
"esito": "accettata" if d.type == "evidence_accepted" else "scartata",
|
|
"decision_seq": d.seq,
|
|
}
|
|
return list(entries.values())
|