feat(inbox): surface adjudication state and its dwell time
This commit is contained in:
parent
233cdc5671
commit
a60312a5f3
9 changed files with 219 additions and 9 deletions
|
|
@ -194,6 +194,19 @@ def render_inbox_concept(
|
|||
frontmatter[policy.offset_key] = _render_flow_list([str(offset) for offset in segment.span])
|
||||
if segment.parent_id is not None:
|
||||
frontmatter["parent"] = segment.parent_id
|
||||
if policy.adjudication_key is not None:
|
||||
# The per-entry verdict IS the discriminator. A plan-level
|
||||
# `adjudicated: true` with no record for THIS entry leaves it
|
||||
# `proposed`: B2 forbids the ratified state without the three keys
|
||||
# beside it, so a flag alone cannot promote a segment.
|
||||
verdict = segment.adjudication
|
||||
if verdict is None:
|
||||
frontmatter[policy.adjudication_key] = ADJUDICATION_PROPOSED
|
||||
else:
|
||||
frontmatter[policy.adjudication_key] = ADJUDICATION_ADJUDICATED
|
||||
frontmatter["adjudicated_by"] = verdict.adjudicated_by
|
||||
frontmatter["adjudicated_at"] = verdict.adjudicated_at
|
||||
frontmatter["adjudication_dwell_s"] = str(verdict.adjudication_dwell_s)
|
||||
return f"---\n{profile.frontmatter.emit(frontmatter)}\n---\n\n{_normalize_body(text)}"
|
||||
|
||||
|
||||
|
|
@ -847,6 +860,23 @@ def process_inbox(
|
|||
)
|
||||
|
||||
|
||||
#: The adjudication states a segment concept may carry, and the whole set.
|
||||
#: CLOSED on purpose (PM decision B2, `docs/plan/office-intake.md` § 5): a
|
||||
#: value outside it is an error, not an extension point. The consumer's half of
|
||||
#: the same contract is that ABSENCE of the key means `unknown` -- an older
|
||||
#: bundle -- never a collapse to `absent`, so a producer emitting the key
|
||||
#: inconsistently would make that distinction unmeasurable on their side.
|
||||
ADJUDICATION_PROPOSED = "proposed"
|
||||
ADJUDICATION_ADJUDICATED = "adjudicated"
|
||||
ADJUDICATION_STATES = (ADJUDICATION_PROPOSED, ADJUDICATION_ADJUDICATED)
|
||||
|
||||
#: Written beside the state when, and only when, it is `adjudicated`. The dwell
|
||||
#: time travels WITH the verdict: a ratified flag carrying no per-item time is
|
||||
#: unfalsifiable, and it is the same number that makes adjudication throughput
|
||||
#: measurable at all.
|
||||
ADJUDICATION_COMPANION_KEYS = ("adjudicated_by", "adjudicated_at", "adjudication_dwell_s")
|
||||
|
||||
|
||||
def _validate_facets(structure: DocumentStructure, profile: BundleProfile) -> None:
|
||||
"""Refuse a document whose values cannot be rendered as index facets.
|
||||
|
||||
|
|
@ -859,8 +889,22 @@ def _validate_facets(structure: DocumentStructure, profile: BundleProfile) -> No
|
|||
markers are this library's own tokens.
|
||||
"""
|
||||
assert profile.index.facets is not None
|
||||
values = structure_frontmatter(structure, profile.index.facets.keys)
|
||||
policy = profile.segmentation
|
||||
key = None if policy is None else policy.adjudication_key
|
||||
# Refused HERE rather than at reprojection, for the reason above: the only
|
||||
# adjudication value that can be wrong is one the producer declared in the
|
||||
# dropped document itself, and refusing it per file keeps a single bad
|
||||
# document from failing the index for every other one.
|
||||
if key is not None and key in values and values[key] not in ADJUDICATION_STATES:
|
||||
raise MaterializationError(
|
||||
f"frontmatter key {key!r} carries {values[key]!r}, which is outside the closed "
|
||||
f"set {ADJUDICATION_STATES} — the adjudication state is a contract, not an "
|
||||
"extension point",
|
||||
code="index_facet_invalid",
|
||||
)
|
||||
try:
|
||||
profile.index.facets.render(structure_frontmatter(structure, profile.index.facets.keys))
|
||||
profile.index.facets.render(values)
|
||||
except ValueError as exc:
|
||||
raise MaterializationError(str(exc), code="index_facet_invalid") from exc
|
||||
|
||||
|
|
|
|||
|
|
@ -1233,6 +1233,12 @@ SEGMENTED_V1 = BundleProfile(
|
|||
)
|
||||
|
||||
|
||||
# Bound so the sixth profile's facet extension is typed: `IndexPolicy.facets`
|
||||
# is `FacetPolicy | None`, and `SEGMENTED_V1` is known here to carry one.
|
||||
_SEGMENTED_FACETS = SEGMENTED_V1.index.facets
|
||||
assert _SEGMENTED_FACETS is not None
|
||||
|
||||
|
||||
# The sixth profile. A segmented bundle could not declare which upstream spec
|
||||
# it targets: `SEGMENTED_V1` names `bundle_id`, `OKF_V0_2` names `okf_version`,
|
||||
# and the two never intersected. Additive, as upstream support always is here --
|
||||
|
|
@ -1255,7 +1261,21 @@ SEGMENTED_OKF_V0_2 = BundleProfile(
|
|||
types=OKF_V0_2.types,
|
||||
frontmatter=OKF_V0_2.frontmatter,
|
||||
paths=SEGMENTED_V1.paths,
|
||||
index=replace(SEGMENTED_V1.index, root_frontmatter=("okf_version", "bundle_id")),
|
||||
# The facet tuple is EXTENDED here rather than shared, and that is the
|
||||
# discriminator doing its job: `SEGMENTED_V1.index.facets` is one object
|
||||
# both profiles would otherwise point at, so appending `adjudication` to it
|
||||
# would surface the state under the older profile too and move a
|
||||
# byte-pinned golden. `FacetPolicy.render` refuses any key a policy does
|
||||
# not name, which is why the key has to live here and cannot be added by
|
||||
# the door at write time.
|
||||
index=replace(
|
||||
SEGMENTED_V1.index,
|
||||
root_frontmatter=("okf_version", "bundle_id"),
|
||||
facets=replace(
|
||||
_SEGMENTED_FACETS,
|
||||
keys=_SEGMENTED_FACETS.keys + ("adjudication",),
|
||||
),
|
||||
),
|
||||
ownership=OKF_V0_2.ownership,
|
||||
# Constructed rather than `replace`d off `SEGMENTED_V1.segmentation`: that
|
||||
# attribute is typed `| None`, and the equality is asserted in the suite so
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue