Add the my-decisions overview store queries and controller (INFD-WP-0003 T01, T02)
Store.memos_for / dispositions_by list the signed-in person's memos and their own dispositions. ReviewController.overview classifies each row after its own fresh PDP read and a live get-by-id engine status; refused rows keep only the memo id, and no presentation is created. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 359683@bnt-lap001 Assistant-Session: eebdc939-7a9b-4e50-9d39-c8437e8a14ec
This commit is contained in:
parent
1e94835c07
commit
f0a9bb1d4c
5 changed files with 208 additions and 3 deletions
|
|
@ -32,6 +32,22 @@ class ReviewPage:
|
|||
dispositions: tuple = ()
|
||||
|
||||
|
||||
# Overview groups, in display order. "unavailable" rows carry the memo id only.
|
||||
GROUPS = ("attention", "open", "accepted", "declined", "returned", "closed", "unavailable")
|
||||
OPEN_STATUSES = ("requested", "approved")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OverviewRow:
|
||||
memo_id: str
|
||||
group: str
|
||||
memo: object = None
|
||||
engine_status: str | None = None
|
||||
reason: str | None = None
|
||||
intent: dict | None = None
|
||||
history: tuple = ()
|
||||
|
||||
|
||||
class ReviewController:
|
||||
def __init__(self, store, policy, approval_factory, *, clock=time.time):
|
||||
self.store, self.policy, self.approval_factory = store, policy, approval_factory
|
||||
|
|
@ -123,6 +139,57 @@ class ReviewController:
|
|||
self.store.memo(memo.id).version != memo.version, current["status"],
|
||||
tuple(self.store.dispositions_for(p.id)))
|
||||
|
||||
def overview(self, session):
|
||||
"""The signed-in person's memos, classified. Never presents, binds or stores engine state.
|
||||
|
||||
Every row passes its own fresh PDP read before any memo content is
|
||||
returned; a refused or failed row keeps only its id. Engine status is
|
||||
read live by the approval id the memo already carries — never a poll.
|
||||
"""
|
||||
self._session(session)
|
||||
rows = [self._overview_row(session, memo) for memo in self.store.memos_for(session.subject)]
|
||||
return sorted(rows, key=lambda r: (GROUPS.index(r.group), r.memo_id))
|
||||
|
||||
def _overview_row(self, session, memo):
|
||||
try:
|
||||
_, observation = self._authorize(session, memo, "read")
|
||||
except ReviewError as exc:
|
||||
if exc.code == "session_expired":
|
||||
raise
|
||||
return OverviewRow(memo.id, "unavailable", reason=exc.code)
|
||||
status, reason = None, None
|
||||
try:
|
||||
current = self.approval_factory(session).get_approval(memo.approval_id)
|
||||
if current["binding"]["digest"] == memo.approval_binding_digest:
|
||||
status = current["status"]
|
||||
else:
|
||||
reason = "binding_changed"
|
||||
except (ApprovalEngineError, ValueError):
|
||||
reason = "engine_unavailable"
|
||||
try:
|
||||
observation.require_current(self.clock())
|
||||
except PolicyError as exc:
|
||||
return OverviewRow(memo.id, "unavailable", reason=str(exc))
|
||||
self._session(session)
|
||||
intent = self.store.intent_for(memo.approval_id, session.subject)
|
||||
history = tuple(self.store.dispositions_by(session.subject, memo.id))
|
||||
latest = [d.verb for d, _ in history if d.memo_version == memo.version and d.verb is not Verb.ACCEPT]
|
||||
if intent and intent["state"] in ("in_flight", "unresolved"):
|
||||
group = "attention"
|
||||
elif intent and intent["state"] == "confirmed":
|
||||
group = "accepted"
|
||||
elif latest and latest[-1] is Verb.DECLINE:
|
||||
group = "declined"
|
||||
elif latest and latest[-1] in (Verb.RETURN, Verb.DISCUSS):
|
||||
group = "returned"
|
||||
elif status is None or status in OPEN_STATUSES or intent:
|
||||
# Unknown engine status stays open: the review page re-checks
|
||||
# before anything can be bound. A prepared intent is finishable.
|
||||
group = "open"
|
||||
else:
|
||||
group = "closed"
|
||||
return OverviewRow(memo.id, group, memo, status, reason, intent, history)
|
||||
|
||||
def acknowledge(self, session, presentation_id, highlight_ids):
|
||||
memo, p = self._presentation(session, presentation_id, current=True)
|
||||
policy_id, observation = self._authorize(session, memo, "acknowledge")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue