feat(moments): mount the corpus by proposal - a pass and an open-after-moment signal, both stopping at the operator (milestone 458 step 7, #4925)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / integration (push) Failing after 43s
CI & Build / Python tests (push) Failing after 46s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 19s
CI & Build / integration (push) Failing after 43s
CI & Build / Python tests (push) Failing after 46s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / Build & push image (push) Skipped
A rule written before moments existed is mounted on nothing. Step 7 records, per (rule, moment), whether it belongs there and who said so: - rule_moment_judgments (migration 0118, backup v23): suggested / confirmed / rejected, from a pass, the signal, or an edit. Moment "" is "no moment fits". - The pass: rules_to_mount lists unjudged rules; propose_rule_moments records suggestions that mount nothing; rule_moment_proposals and judge_rule_moments put them to the operator. A confirm mounts, a reject is kept so the pair is never proposed again. Same service behind REST and a "Waiting on you" panel in Settings > Moments. - Edits are judgments: set_rule_moments, the one mount write path, confirms what was added and rejects what was removed in the same transaction. - The signal: scribe_moment.sh keeps a per-session acts ledger; when a rule is opened, scribe_record_opened.sh sends the last three minutes of it to /api/plugin/rule-opened. The acts resolve through the install's mappings; work.run and work.change are not evidence. Counted per distinct session with lesson_rules' evidence model, and once due the open returns one line asking the reader to offer the mount. - scribe_session_end.sh removes the session's scribe-moment files. Plugin 2026.10.05.2003. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import BigInteger, DateTime, ForeignKey, Text, UniqueConstraint
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from scribe.models import Base
|
||||
from scribe.models.base import CreatedAtMixin, iso
|
||||
from scribe.models.lesson_rule_link import CONFIRMED, LINK_STATES, REJECTED, SUGGESTED
|
||||
|
||||
# The same three states as a lesson's link to a rule, for the same reasons —
|
||||
# re-exported so a reader of this table need not know where they were first
|
||||
# named. CHECK ck_rule_moment_judgments_state (migration 0118, rule 36).
|
||||
JUDGMENT_STATES = LINK_STATES
|
||||
__all__ = [
|
||||
"CONFIRMED", "JUDGMENT_STATES", "NO_MOMENT", "REJECTED", "SUGGESTED",
|
||||
"SOURCES", "RuleMomentJudgment",
|
||||
]
|
||||
|
||||
# The moment recorded for "this rule is about WHAT, not WHEN": no moment in
|
||||
# the catalog is when it applies, and it is right to reach it by meaning. A
|
||||
# row rather than an absence, for the reason LessonNoRule is one — a rule
|
||||
# looked at and found to have no moment must not read as one nobody looked at,
|
||||
# or every pass over the corpus would propose it again.
|
||||
NO_MOMENT = ""
|
||||
|
||||
# Where a judgment came from. `pass` — an agent read the rule and proposed;
|
||||
# `signal` — the rule kept being opened just after a moment fired; `edit` — a
|
||||
# person changed the rule's moments directly, which is a judgment too.
|
||||
SOURCES = ("pass", "signal", "edit")
|
||||
|
||||
|
||||
class RuleMomentJudgment(Base, CreatedAtMixin):
|
||||
"""Whether a rule belongs on a moment, and who said so (milestone 458 step 7).
|
||||
|
||||
A mount (`rule_moments`) is the current answer; this is the reasoning
|
||||
behind it and the answers that are NOT mounts. One row per (rule, moment):
|
||||
|
||||
- ``suggested`` — proposed by a pass, or by repeated opens after the
|
||||
moment fired. Delivers nothing: a suggestion that mounted itself would
|
||||
manufacture the opens it counts.
|
||||
- ``confirmed`` — a judgment put the rule on the moment. The mount exists.
|
||||
- ``rejected`` — a judgment said it does not belong there, kept with its
|
||||
reason so the pair is never proposed again, by either source.
|
||||
|
||||
Moment ``""`` (NO_MOMENT), confirmed, is "no moment fits this rule".
|
||||
"""
|
||||
|
||||
__tablename__ = "rule_moment_judgments"
|
||||
|
||||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
||||
rule_id: Mapped[int] = mapped_column(
|
||||
BigInteger, ForeignKey("rules.id", ondelete="CASCADE"), index=True,
|
||||
)
|
||||
moment: Mapped[str] = mapped_column(Text)
|
||||
state: Mapped[str] = mapped_column(Text, default=SUGGESTED, server_default=SUGGESTED)
|
||||
source: Mapped[str] = mapped_column(Text, default="pass", server_default="pass")
|
||||
# Why — for a suggestion, what the proposer read in the rule; for a
|
||||
# judgment, why it was confirmed or rejected.
|
||||
note: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
# The co-occurrence evidence (signal source), in lesson_rules' shape.
|
||||
evidence: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
||||
judged_at: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True), nullable=True,
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("rule_id", "moment", name="uq_rule_moment_judgments_pair"),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"rule_id": self.rule_id,
|
||||
"moment": self.moment,
|
||||
"state": self.state,
|
||||
"source": self.source,
|
||||
"note": self.note or "",
|
||||
"evidence": self.evidence or {},
|
||||
"judged_at": iso(self.judged_at),
|
||||
"created_at": iso(self.created_at),
|
||||
}
|
||||
Reference in New Issue
Block a user