feat(6254): moment_usage says which rule each moment delivered, and whether it was opened
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 1m5s
CI & Build / Python tests (push) Successful in 1m57s
CI & Build / Build & push image (push) Successful in 33s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / integration (push) Successful in 1m5s
CI & Build / Python tests (push) Successful in 1m57s
CI & Build / Build & push image (push) Successful in 33s
Each moment gains by_rule: {rule_id, title, delivered, opened}, read from
the (moment, rule) grouping the readout already did. The moment's totals are
now summed from those rows, so the two cannot disagree. The Moments settings
page shows the list under each moment, closed by default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,14 @@ export interface RemovedDefault {
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
export interface MomentRuleUsage {
|
||||
rule_id: number;
|
||||
/** Null for a rule deleted since it was delivered. */
|
||||
title: string | null;
|
||||
delivered: number;
|
||||
opened: boolean;
|
||||
}
|
||||
|
||||
export interface MomentUsage {
|
||||
/** How many times a mounted rule arrived at this moment. */
|
||||
delivered: number;
|
||||
@@ -53,6 +61,8 @@ export interface MomentUsage {
|
||||
* the door, not the line that prompted it. */
|
||||
opened: number;
|
||||
last_delivered_at: string | null;
|
||||
/** Each rule delivered here, most delivered first. */
|
||||
by_rule: MomentRuleUsage[];
|
||||
}
|
||||
|
||||
export interface MomentsPayload {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import type { MomentRuleUsage } from "@/api/moments";
|
||||
|
||||
/**
|
||||
* Which rules a moment delivered, and whether an agent went on to read each.
|
||||
* The moment's own counts cannot say this once two rules are mounted there.
|
||||
* Closed by default: the counts in the row above are what most visits want.
|
||||
*/
|
||||
defineProps<{ rules: MomentRuleUsage[] }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<details v-if="rules.length" class="moment-rules">
|
||||
<summary>Rules delivered here</summary>
|
||||
<ul class="delivered-list">
|
||||
<li v-for="r in rules" :key="r.rule_id" class="delivered-row">
|
||||
<span class="delivered-title">{{ r.title ?? `Rule ${r.rule_id} (deleted)` }}</span>
|
||||
<span class="delivered-count">delivered {{ r.delivered }}</span>
|
||||
<span :class="r.opened ? 'delivered-opened' : 'delivered-unopened'">
|
||||
{{ r.opened ? "opened" : "not opened" }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.moment-rules { margin-top: var(--fs-space-2); font-size: var(--fs-size-tiny); }
|
||||
.moment-rules summary { cursor: pointer; color: var(--fs-text-tertiary); }
|
||||
.delivered-list { list-style: none; margin: var(--fs-space-1) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--fs-space-1); }
|
||||
.delivered-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--fs-space-2); font-variant-numeric: tabular-nums; }
|
||||
.delivered-title { color: var(--fs-text-primary); font-size: var(--fs-size-body-sm); }
|
||||
.delivered-count { color: var(--fs-text-secondary); }
|
||||
.delivered-opened { color: var(--fs-text-secondary); }
|
||||
/* Not read is a fact about this window, not a fault: quiet, like "nothing mounted". */
|
||||
.delivered-unopened { color: var(--fs-text-tertiary); font-style: italic; }
|
||||
</style>
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useMomentsStore } from "@/stores/moments";
|
||||
import MomentProposals from "@/components/MomentProposals.vue";
|
||||
import MomentRuleUsage from "@/components/MomentRuleUsage.vue";
|
||||
import type { MomentAction, MomentUsage } from "@/api/moments";
|
||||
import { fmtDate } from "@/utils/dateFormat";
|
||||
|
||||
@@ -167,6 +168,7 @@ onMounted(() => store.load(true));
|
||||
>×</button>
|
||||
</span>
|
||||
</div>
|
||||
<MomentRuleUsage v-if="usage(m.name)" :rules="usage(m.name)!.by_rule" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -186,6 +188,7 @@ onMounted(() => store.load(true));
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<MomentRuleUsage v-if="usage(name)" :rules="usage(name)!.by_rule" />
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
@@ -238,9 +238,10 @@ async def test_the_delivery_lookup_answers_by_home(world):
|
||||
|
||||
|
||||
async def test_moment_usage_counts_deliveries_and_the_agent_opens_that_followed(world):
|
||||
"""The per-moment readout, on real SQL: an EXISTS inside a
|
||||
COUNT(CASE …) is a shape no mock can vouch for, and the block's own guard
|
||||
would turn a database refusal into a quiet `moment_usage_failed`."""
|
||||
"""The per-moment readout, on real SQL: a correlated EXISTS selected as a
|
||||
column beside an outer join is a shape no mock can vouch for, and the
|
||||
block's own guard would turn a database refusal into a quiet
|
||||
`moment_usage_failed`."""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from sqlalchemy import delete
|
||||
@@ -289,3 +290,13 @@ async def test_moment_usage_counts_deliveries_and_the_agent_opens_that_followed(
|
||||
report = block["by_moment"]["reply.report"]
|
||||
assert (report["delivered"], report["rules"], report["opened"]) == (1, 1, 0)
|
||||
assert report["last_delivered_at"]
|
||||
|
||||
# Two rules share work.finish: which one arrived, and which was opened.
|
||||
assert finish["by_rule"] == [
|
||||
{"rule_id": rule.id, "title": rule.title, "delivered": 2, "opened": True},
|
||||
{"rule_id": other.id, "title": "Close the task with the push",
|
||||
"delivered": 1, "opened": False},
|
||||
]
|
||||
assert report["by_rule"] == [
|
||||
{"rule_id": rule.id, "title": rule.title, "delivered": 1, "opened": False},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user