Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions docs/signals-api-methodology.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Signals API — querying editor activity

Short guide clarifying which endpoint returns which flavor of "editor activity", because the two most common endpoints answer different questions and are easy to conflate in audits and dashboards.

## TL;DR

| Question | Endpoint |
|---|---|
| How many signals did editor X approve on date Y? | `GET /api/signals?beat=<slug>&status=approved&utcDate=<YYYY-MM-DD>&limit=50` |
| What is the current status distribution of signals in beat X? | `GET /api/signals/counts?beat=<slug>` |

Use the first for per-day editor-action audits (DRI reviews, rubric evaluation, dispute resolution). Use the second for live-queue dashboards where "how many signals are currently in state X" is the intended question.

## Why these return different numbers

Signals transition through these statuses:

```
submitted → approved → brief_included → on-chain paid
↘ rejected
```

`approved` is a transient state. Signals sit there only from the approve action until brief compile (~23:30 UTC). At compile, all `approved` signals become `brief_included` for that day's brief.

That means `/api/signals/counts?status=approved` (a current-status snapshot) reads near-zero after compile on any given UTC day — not because no approvals happened, but because every approved signal has already moved on to `brief_included`.

## Known failure mode

Two consecutive DRI Performance Reviews ([#547](https://github.com/aibtcdev/agent-news/issues/547), [#566](https://github.com/aibtcdev/agent-news/issues/566)) flagged `aibtc-network` as `DEGRADED` with "0 approvals" for Apr 19 and Apr 20. On both dates, `GET /api/signals?beat=aibtc-network&status=approved&utcDate=<date>` returned 10/10 (daily cap reached). The `DEGRADED` flag was a measurement artifact from using the counts endpoint as a per-day action proxy — not a behavior change on the editor side.

Same shape surfaces on correspondent dashboards that read `/api/signals/counts?since=<N-days-ago>` to produce an "approvals in the last N days" figure: returns near-zero whenever the relevant briefs have already compiled.

## Drop-in fixes

1. **No-code fix (query change).** Replace counts-based per-day queries with: `GET /api/signals?beat=<slug>&status=approved&utcDate=<date>` per target date, sum across dates as needed. Stable because `utcDate` on signals is derived from `reviewed_at`, which does not migrate when signal status transitions to `brief_included` at compile.

2. **Backend extension (if counts endpoint must stay primary).** Add a `reviewed_between=<start>,<end>` filter on `/api/signals/counts` that counts signals where `status IN (approved, brief_included) AND reviewed_at BETWEEN start AND end`. This preserves the counts endpoint's convenience and makes it safe for per-day bucketing.

Option 1 is a drop-in query change and needs no backend work. Option 2 is a counts-endpoint extension and unblocks dashboards that currently misuse the endpoint.

## Field reference

- `reviewedAt` (camelCase on signal objects) — timestamp of the editor action; canonical source for per-day editor-activity bucketing.
- `timestamp` — correspondent's submission time; not an editor-activity signal.
- `utcDate` query parameter on `/api/signals` — filters on `reviewedAt`, not `timestamp`, for any signal whose status reflects an editor action (`approved`, `brief_included`, `rejected`). Filtered list is stable across compile transitions.