# VOTD — Signals

**Branch:** `feature/supabase-backend`
**Status:** Draft for review — decisions needed (marked ⚡)

Signals are the system's way of surfacing trust and quality information — both for editorial review of incoming proposals and for detecting anomalous behaviour in voting. This document covers what signals exist, how they're generated, how editors action them, and what decisions still need to be made.

---

## What is a signal?

A signal is any piece of information attached to a vote, proposal, or user that helps an editor or the system make a quality or trust decision. Signals are not verdicts — they're inputs. The editor (or eventually a scoring model) weighs them to reach a decision.

There are two broad categories:

**Editorial signals** — quality indicators on proposals and votes. Is this proposal well-formed? Does it have credible sources? Is the topic classification correct?

**Anomaly signals** — trust indicators on users and voting patterns. Is this voter behaving like a real person? Is this vote being gamed?

---

## Editorial Signals

These attach to proposals submitted by users and to scraped agenda items in the editorial queue.

### On proposals (user-submitted)

| Signal | What it means | How it's set |
|---|---|---|
| `clean` | No issues detected | Default on submission |
| `flagged` | One or more concerns raised | Auto or manual |
| `duplicate` | Near-identical proposal already exists | Auto — text similarity check |
| `off_topic` | Proposed topic doesn't match content | Auto or manual |
| `low_quality` | Too vague, too short, or incoherent | Auto — length + readability heuristic |
| `unverified_source` | Linked source URL is dead or untrustworthy | Auto — URL check on submission |

### On scraped agenda items (editorial queue)

These appear when the scraper confidence score falls below 0.85 — meaning the system isn't sure it classified the item correctly.

| Signal | What it means |
|---|---|
| `low_confidence` | Classifier score < 0.85 — needs human review |
| `ambiguous_topic` | Item could belong to multiple topic categories |
| `no_source` | No source URL found for the agenda item |
| `stale` | Agenda item is older than 90 days |

### Editor actions on editorial signals

When a proposal or agenda item has signals attached, the editor sees them in the queue and can:

- **Approve** — accept as-is, signals noted but not blocking
- **Approve with edits** — accept but modify title/question/topic before publishing
- **Return for revision** — send back to submitter with notes (proposals only)
- **Reject** — decline with reason code

**Decision (Mar 2026):** Editors action the whole item — they do not override individual signals. A flagged item can still be approved; the signal is informational only. If a source is dead or a topic is borderline, the editor notes this in the return/reject reason. Individual signal override is deferred to a later phase when volume justifies the UI complexity.

---

## Anomaly Signals

These attach to users and voting patterns. They feed the Flag Queue in the admin.

### Vote-level anomalies

| Signal | What it triggers on |
|---|---|
| `rapid_voting` | User casts > 10 votes within 60 seconds |
| `duplicate_vote` | Same user voted on same item more than once (should be caught by DB constraint, but logged if it somehow occurs) |
| `coordinated_burst` | 50+ votes on a single item within 5 minutes from accounts created within 24 hours of each other |
| `geographic_mismatch` | User voted on items outside their verified jurisdiction |

### User-level anomalies

| Signal | What it triggers on |
|---|---|
| `new_account_high_activity` | Account < 7 days old with > 20 votes |
| `unverified_high_activity` | Unverified user casting votes at high volume |
| `bot_pattern` | Voting interval is suspiciously regular (< 2s variance over 20+ votes) |
| `proxy_ip` | Login IP is a known VPN or data centre range |
| `multiple_accounts` | Same device fingerprint associated with > 1 account |

### How anomaly signals are generated

In the live system, these run as **Supabase Edge Functions** triggered on `user_votes` inserts. Each function checks its specific condition and inserts a row into the `flags` table if the threshold is met.

For the prototype, anomaly signals are simulated — mock flags are seeded into the database to allow the editor workflow to be tested without real user data.

**Decision (Mar 2026):** Anomaly signals always require editor review before any account-level action. A specific vote's weight may be auto-suppressed to 0 pending review (so results aren't skewed while the editor catches up), but the account itself is never suspended automatically. This keeps humans in the loop for all consequential decisions.

---

## User Trust Score

Over time, each voter accumulates a trust score based on their behaviour. This isn't shown to users — it's used internally to weight votes and prioritise anomaly review.

### Factors that increase trust

- Address verified against voter roll (+40 points)
- Account age > 30 days (+10 points)
- Consistent voting geography (+10 points)
- Proposals submitted that were accepted (+5 points each, max 20)
- No anomaly flags in last 90 days (+15 points)

### Factors that decrease trust

- Anomaly flag raised (-20 points per flag)
- Flag confirmed by editor (-30 points additional)
- Voting outside verified jurisdiction (-10 points)
- Account age < 7 days (-20 points)
- Unverified address (-15 points)

### Score bands

| Score | Band | Effect |
|---|---|---|
| 80–100 | Trusted | Vote counts at full weight |
| 50–79 | Standard | Vote counts at full weight |
| 20–49 | Caution | Vote counts at 0.5x weight; anomaly checks heightened |
| 0–19 | Restricted | Vote suppressed pending review |
| < 0 | Suspended | Cannot vote; account flagged for review |

**Decision (Mar 2026):** Deferred. Trust scores remain internal for now. A "verified voter" badge concept is appealing long-term but risks creating a visible two-tier system before the platform has established credibility. Revisit once the verification flow (address check against voter roll) is live.

---

## Signal flow — end to end

```
User submits proposal
        ↓
Auto-signals run (duplicate check, source check, quality heuristic)
        ↓
Proposal lands in Proposal Queue with signal badges
        ↓
Editor reviews → approves / returns / rejects
        ↓
If approved → vote draft created → editorial review → published
        ↓
Users vote
        ↓
Anomaly Edge Functions run on each vote insert
        ↓
Anomaly signals land in Flag Queue
        ↓
Editor reviews flags → resolves / dismisses / bans
        ↓
Audit log records every action
```

---

## What's in the admin today

The Flag Queue page exists but shows placeholder content. The Proposal Queue shows mock proposals with `clean` and `flagged` signal badges.

### What needs to be built

- Auto-signal functions on proposal submission (duplicate, source, quality checks)
- Anomaly Edge Functions on `user_votes` inserts
- Trust score calculation and storage (add `trust_score` column to `profiles`)
- Flag Queue wired to real `flags` table data
- Signal badge detail panel — click a signal to see why it was raised
- Editor override UI for individual signals

---

## Workflow and assignment model

Signals inform decisions but do not drive workflow on their own. The following layer sits on top of the signal system to track who is working on what.

### Item states (editorial workflow)

Proposal and queue item status remains clean:

| Status | Meaning |
|---|---|
| `pending` | Submitted, not yet reviewed |
| `approved` | Published |
| `returned` | Sent back to submitter for revision |
| `rejected` | Declined |

Workflow state is a separate lighter layer and does not replace status:

| Workflow indicator | Meaning |
|---|---|
| Yellow dot on Pending | Item has been opened by an editor but not yet actioned |
| Assignee shown | An editor has been tagged as responsible for this item |

The yellow dot signals "in progress / someone has eyes on it" without committing to a status change. It disappears once the item is actioned (approved / returned / rejected).

### Assignment

- Every item defaults to the superuser/owner.
- Any editor can reassign to themselves or another editor via a dropdown on the row.
- Reassignment does not change the item's status.

### My Queue

Each editor has a personal "My Queue" view — an inbox of every item across the whole admin (both Prototype and Live, all queue types) that is assigned to them. It sits above the Prototype/Live split in the sidebar so it is always reachable regardless of which environment the editor is working in.

My Queue is read/write — editors can action items directly from it without navigating to the individual queue page.

---

## Open questions

1. **Signal override granularity** — item-level only, or individual signal override?
2. **Auto-action on anomalies** — always require review, or allow auto-suppression?
3. **Vote weighting visibility** — show verified voter badge to users?
4. **Trust score reset** — can a suspended user appeal and have their score reviewed?
5. **Signal history** — should editors see the full signal history for a user across all their proposals and votes, or just the current item?
