176 lines
6.3 KiB
Python
176 lines
6.3 KiB
Python
"""Swappable retrieval interface. Specs name what to select; recency/SQL is implementation."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from db import get_db, row_to_dict
|
|
from journal_body import plain_text
|
|
|
|
# Implementation caps. Not part of the selection-spec contract.
|
|
_DAY_MESSAGE_CAP = 80
|
|
_SPACE_ENTRY_LIMIT = 5
|
|
_SPACE_ENTRY_EXCERPT_CHARS = 400
|
|
_SPACE_SOURCE_CONV_LIMIT = 3
|
|
_SPACE_SOURCE_EXCERPT_CHARS = 400
|
|
_SPACE_SOURCE_MESSAGE_CAP = 8
|
|
|
|
|
|
def retrieve(profile_id: str, spec: dict[str, Any]) -> list[dict]:
|
|
kind = spec.get("kind")
|
|
if kind == "day_messages":
|
|
return _day_messages(profile_id, spec)
|
|
if kind == "space_entries":
|
|
return _space_entries(profile_id, spec)
|
|
if kind == "space_recent_sources":
|
|
return _space_recent_sources(profile_id, spec)
|
|
if kind == "writing_profile":
|
|
return _writing_profile(profile_id)
|
|
return []
|
|
|
|
|
|
def _day_messages(profile_id: str, spec: dict[str, Any]) -> list[dict]:
|
|
journal_day_id = spec.get("journal_day_id")
|
|
conversation_ids = spec.get("conversation_ids") or []
|
|
if not journal_day_id:
|
|
return []
|
|
with get_db() as conn:
|
|
if conversation_ids:
|
|
placeholders = ",".join("?" * len(conversation_ids))
|
|
rows = conn.execute(
|
|
f"""
|
|
SELECT m.*, c.title AS conversation_title
|
|
FROM messages m
|
|
JOIN conversations c ON c.id = m.conversation_id
|
|
WHERE m.profile_id = ? AND c.journal_day_id = ? AND m.conversation_id IN ({placeholders})
|
|
ORDER BY c.created, m.seq
|
|
LIMIT ?
|
|
""",
|
|
(profile_id, journal_day_id, *conversation_ids, _DAY_MESSAGE_CAP),
|
|
).fetchall()
|
|
else:
|
|
rows = conn.execute(
|
|
"""
|
|
SELECT m.*, c.title AS conversation_title
|
|
FROM messages m
|
|
JOIN conversations c ON c.id = m.conversation_id
|
|
WHERE m.profile_id = ? AND c.journal_day_id = ?
|
|
ORDER BY c.created, m.seq
|
|
LIMIT ?
|
|
""",
|
|
(profile_id, journal_day_id, _DAY_MESSAGE_CAP),
|
|
).fetchall()
|
|
return [row_to_dict(row) for row in rows]
|
|
|
|
|
|
def _space_entries(profile_id: str, spec: dict[str, Any]) -> list[dict]:
|
|
space_id = spec.get("space_id")
|
|
exclude_day_id = spec.get("exclude_day_id")
|
|
if not space_id:
|
|
return []
|
|
params: list[Any] = [profile_id, space_id]
|
|
exclude_sql = ""
|
|
if exclude_day_id:
|
|
exclude_sql = "AND e.journal_day_id != ?"
|
|
params.append(exclude_day_id)
|
|
params.append(_SPACE_ENTRY_LIMIT)
|
|
with get_db() as conn:
|
|
rows = conn.execute(
|
|
f"""
|
|
SELECT e.id AS entry_id, e.journal_day_id, d.calendar_date, v.title, v.body, v.origin
|
|
FROM journal_entries e
|
|
JOIN journal_days d ON d.id = e.journal_day_id
|
|
JOIN journal_entry_versions v ON v.id = e.current_version_id
|
|
WHERE e.profile_id = ? AND e.space_id = ? AND e.deleted_at IS NULL {exclude_sql}
|
|
ORDER BY d.calendar_date DESC, e.updated DESC
|
|
LIMIT ?
|
|
""",
|
|
params,
|
|
).fetchall()
|
|
result = []
|
|
for row in rows:
|
|
item = row_to_dict(row)
|
|
item["excerpt"] = plain_text(item.get("body") or "")[:_SPACE_ENTRY_EXCERPT_CHARS]
|
|
result.append(item)
|
|
return result
|
|
|
|
|
|
def _space_recent_sources(profile_id: str, spec: dict[str, Any]) -> list[dict]:
|
|
"""Recent original conversations in the same space. Recency is the current heuristic."""
|
|
space_id = spec.get("space_id")
|
|
exclude_conversation_id = spec.get("exclude_conversation_id")
|
|
exclude_journal_day_id = spec.get("exclude_journal_day_id")
|
|
if not space_id:
|
|
return []
|
|
params: list[Any] = [profile_id, space_id]
|
|
extra = ""
|
|
if exclude_conversation_id:
|
|
extra += " AND c.id != ?"
|
|
params.append(exclude_conversation_id)
|
|
if exclude_journal_day_id:
|
|
extra += " AND (c.journal_day_id IS NULL OR c.journal_day_id != ?)"
|
|
params.append(exclude_journal_day_id)
|
|
params.append(_SPACE_SOURCE_CONV_LIMIT)
|
|
with get_db() as conn:
|
|
conversations = conn.execute(
|
|
f"""
|
|
SELECT c.id, c.updated, c.journal_day_id
|
|
FROM conversations c
|
|
WHERE c.profile_id = ? AND c.space_id = ? {extra}
|
|
ORDER BY c.updated DESC, c.created DESC
|
|
LIMIT ?
|
|
""",
|
|
params,
|
|
).fetchall()
|
|
result = []
|
|
for conv in conversations:
|
|
messages = conn.execute(
|
|
"""
|
|
SELECT body FROM messages
|
|
WHERE profile_id = ? AND conversation_id = ? AND role = 'user'
|
|
ORDER BY seq
|
|
LIMIT ?
|
|
""",
|
|
(profile_id, conv["id"], _SPACE_SOURCE_MESSAGE_CAP),
|
|
).fetchall()
|
|
excerpt = " ".join((row["body"] or "").strip() for row in messages if (row["body"] or "").strip())
|
|
excerpt = excerpt[:_SPACE_SOURCE_EXCERPT_CHARS]
|
|
if not excerpt:
|
|
continue
|
|
result.append(
|
|
{
|
|
"conversation_id": conv["id"],
|
|
"journal_day_id": conv["journal_day_id"],
|
|
"excerpt": excerpt,
|
|
}
|
|
)
|
|
return result
|
|
|
|
|
|
def _writing_profile(profile_id: str) -> list[dict]:
|
|
with get_db() as conn:
|
|
row = row_to_dict(
|
|
conn.execute(
|
|
"SELECT compiled_brief, updated FROM writing_profiles WHERE profile_id = ?",
|
|
(profile_id,),
|
|
).fetchone()
|
|
)
|
|
sources = [
|
|
row_to_dict(item)
|
|
for item in conn.execute(
|
|
"""
|
|
SELECT id, kind, body, entry_id, weight, created
|
|
FROM writing_profile_sources
|
|
WHERE profile_id = ?
|
|
ORDER BY CASE kind
|
|
WHEN 'journal_entry' THEN 0
|
|
WHEN 'imported_text' THEN 1
|
|
ELSE 2
|
|
END, created DESC
|
|
""",
|
|
(profile_id,),
|
|
).fetchall()
|
|
]
|
|
if not row and not sources:
|
|
return []
|
|
return [{"compiled_brief": (row or {}).get("compiled_brief") or "", "sources": sources}]
|