docs(01-04): complete auto-trigger plan — SUMMARY, STATE, ROADMAP updated
- 01-04-SUMMARY.md: full plan summary with 7 procedure list, deviations, build status - STATE.md: plan 4/4 complete, decisions recorded, session updated - ROADMAP.md: Phase 1 all 4 plans complete - REQUIREMENTS.md: RANK-09 and RANK-10 marked complete
This commit is contained in:
@@ -18,7 +18,7 @@ Requirements for this milestone. Each maps to roadmap phases.
|
||||
- [x] **RANK-07**: AI ranking service anonymizes project data before sending to OpenAI (follows existing anonymization pattern)
|
||||
- [x] **RANK-08**: Ranking results are stored as snapshots for audit trail (RankingSnapshot model)
|
||||
- [x] **RANK-09**: Ranking auto-triggers when all jury assignments for a round are completed (all evaluations submitted)
|
||||
- [ ] **RANK-10**: Auto-trigger works retroactively for rounds where all assignments are already complete
|
||||
- [x] **RANK-10**: Auto-trigger works retroactively for rounds where all assignments are already complete
|
||||
|
||||
### Ranking Dashboard
|
||||
|
||||
@@ -96,7 +96,7 @@ Which phases cover which requirements. Updated during roadmap creation.
|
||||
| RANK-07 | Phase 1 | Complete |
|
||||
| RANK-08 | Phase 1 | Complete |
|
||||
| RANK-09 | Phase 1 | Complete |
|
||||
| RANK-10 | Phase 1 | Pending |
|
||||
| RANK-10 | Phase 1 | Complete |
|
||||
| DASH-01 | Phase 2 | Pending |
|
||||
| DASH-02 | Phase 2 | Pending |
|
||||
| DASH-03 | Phase 2 | Pending |
|
||||
|
||||
@@ -12,7 +12,7 @@ This milestone extends the MOPC jury voting platform with four capabilities: an
|
||||
|
||||
Decimal phases appear between their surrounding integers in numeric order.
|
||||
|
||||
- [ ] **Phase 1: AI Ranking Backend** - AI service, tRPC router, schema, and auto-trigger for ranking projects per category using natural-language criteria
|
||||
- [x] **Phase 1: AI Ranking Backend** - AI service, tRPC router, schema, and auto-trigger for ranking projects per category using natural-language criteria
|
||||
(completed 2026-02-27)
|
||||
- [ ] **Phase 2: Ranking Dashboard UI** - Drag-and-drop ranking tab on the round detail page with criteria input, preview-confirm dialog, and project detail side panel
|
||||
- [ ] **Phase 3: Advancement + Email** - Bulk advancement flow with reject/advance in one operation, notify-vs-silent toggle, and admin-editable email templates
|
||||
@@ -75,7 +75,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 (Phase 4 can be parallelize
|
||||
Phases execute in numeric order: 1 → 2 → 3 → 4 (Phase 4 can be parallelized with 2-3)
|
||||
|
||||
| Phase | Plans Complete | Status | Completed |
|
||||
| 1. AI Ranking Backend | 3/4 | In Progress| |
|
||||
|-------|----------------|--------|-----------|
|
||||
| 1. AI Ranking Backend | 4/4 | Complete | 2026-02-27 |
|
||||
| 2. Ranking Dashboard UI | 0/TBD | Not started | - |
|
||||
| 3. Advancement + Email | 0/TBD | Not started | - |
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
---
|
||||
gsd_state_version: 1.0
|
||||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: unknown
|
||||
last_updated: "2026-02-27T00:07:04.010Z"
|
||||
progress:
|
||||
total_phases: 1
|
||||
completed_phases: 1
|
||||
total_plans: 4
|
||||
completed_plans: 4
|
||||
---
|
||||
|
||||
# Project State
|
||||
|
||||
## Project Reference
|
||||
@@ -10,30 +23,31 @@ See: .planning/PROJECT.md (updated 2026-02-26)
|
||||
## Current Position
|
||||
|
||||
Phase: 1 of 4 (AI Ranking Backend)
|
||||
Plan: 3 of TBD in current phase
|
||||
Plan: 4 of 4 in current phase (Phase 1 complete)
|
||||
Status: In progress
|
||||
Last activity: 2026-02-27 — Plan 03 complete: tRPC rankingRouter with 5 procedures registered in appRouter
|
||||
Last activity: 2026-02-27 — Plan 04 complete: auto-trigger hook on evaluation submit + triggerAutoRank + retroactiveScan procedures
|
||||
|
||||
Progress: [███░░░░░░░] ~30%
|
||||
Progress: [████░░░░░░] ~40%
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
**Velocity:**
|
||||
- Total plans completed: 3
|
||||
- Average duration: ~3 min
|
||||
- Total execution time: ~10 min
|
||||
- Total plans completed: 4
|
||||
- Average duration: ~5 min
|
||||
- Total execution time: ~18 min
|
||||
|
||||
**By Phase:**
|
||||
|
||||
| Phase | Plans | Total | Avg/Plan |
|
||||
|-------|-------|-------|----------|
|
||||
| 01-ai-ranking-backend | 3 | ~10 min | ~3 min |
|
||||
| 01-ai-ranking-backend | 4 | ~18 min | ~5 min |
|
||||
|
||||
**Recent Trend:**
|
||||
- Last 5 plans: 01-01 (~3 min), 01-02 (~3 min), 01-03 (~4 min)
|
||||
- Last 5 plans: 01-01 (~3 min), 01-02 (~3 min), 01-03 (~4 min), 01-04 (~8 min)
|
||||
- Trend: Fast (service-layer tasks)
|
||||
|
||||
*Updated after each plan completion*
|
||||
| Phase 01-ai-ranking-backend P04 | 8 | 2 tasks | 3 files |
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
@@ -53,6 +67,9 @@ Recent decisions affecting current work:
|
||||
- [01-02]: PrismaClient imported as real type (not import type) so transaction clients are compatible
|
||||
- [01-03]: Typed arrays cast to Prisma.InputJsonValue via `unknown` (direct cast rejected by strict TS — no index signature)
|
||||
- [01-03]: getSnapshot uses findUnique + manual TRPCError NOT_FOUND (findUniqueOrThrow gives INTERNAL_SERVER_ERROR)
|
||||
- [Phase 01-04]: triggerAutoRankIfComplete defined as module-level non-exported function in evaluation.ts — avoids circular imports, colocated with the mutation it serves
|
||||
- [Phase 01-04]: EvaluationConfig null fallback typed as {} as EvaluationConfig — required for TypeScript strict mode to recognize rankingCriteria and autoRankOnComplete fields
|
||||
- [Phase 01-04]: retroactiveScan uses RETROACTIVE triggerType to distinguish from MANUAL/AUTO/QUICK — prevents duplicate re-runs on subsequent scans
|
||||
|
||||
### Pending Todos
|
||||
|
||||
@@ -67,5 +84,5 @@ None yet.
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-02-27
|
||||
Stopped at: Completed 01-03-PLAN.md (tRPC rankingRouter — 5 procedures, registered in appRouter)
|
||||
Stopped at: Completed 01-04-PLAN.md (auto-trigger hook + triggerAutoRank + retroactiveScan + AI ranking notifications)
|
||||
Resume file: None
|
||||
|
||||
134
.planning/phases/01-ai-ranking-backend/01-04-SUMMARY.md
Normal file
134
.planning/phases/01-ai-ranking-backend/01-04-SUMMARY.md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
phase: 01-ai-ranking-backend
|
||||
plan: 04
|
||||
subsystem: ai-ranking
|
||||
tags: [auto-trigger, notifications, ranking, evaluation, tRPC]
|
||||
requirements: [RANK-09, RANK-10]
|
||||
dependency_graph:
|
||||
requires: [01-01, 01-02, 01-03]
|
||||
provides: [auto-trigger-on-evaluation-complete, retroactive-scan, admin-notifications]
|
||||
affects: [evaluation-submit-mutation, ranking-router, in-app-notifications]
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns: [fire-and-forget async, cooldown-guard, module-level-helper-function]
|
||||
key_files:
|
||||
created: []
|
||||
modified:
|
||||
- src/server/services/in-app-notification.ts
|
||||
- src/server/routers/evaluation.ts
|
||||
- src/server/routers/ranking.ts
|
||||
decisions:
|
||||
- "triggerAutoRankIfComplete defined as module-level (non-exported) function in evaluation.ts — avoids circular imports and keeps the auto-trigger logic colocated with the mutation it serves"
|
||||
- "EvaluationConfig null fallback typed as {} as EvaluationConfig rather than just {} — required for TypeScript strict mode to recognize rankingCriteria and autoRankOnComplete fields"
|
||||
- "ParsedRankingRule[] cast via unknown as Prisma.InputJsonValue — Prisma InputJsonValue does not overlap with typed arrays, double-cast is the correct pattern throughout the codebase"
|
||||
- "retroactiveScan uses RETROACTIVE triggerType to distinguish from MANUAL/AUTO/QUICK — prevents duplicate re-runs on subsequent scans"
|
||||
- "triggerAutoRank procedure in ranking.ts uses MANUAL triggerType (not AUTO) because it is admin-initiated, not system-initiated"
|
||||
metrics:
|
||||
duration: ~8min
|
||||
completed_date: "2026-02-27"
|
||||
tasks_completed: 2
|
||||
files_modified: 3
|
||||
---
|
||||
|
||||
# Phase 1 Plan 04: Auto-Trigger + Retroactive Scan Summary
|
||||
|
||||
**One-liner:** Fire-and-forget auto-ranking on evaluation completion with 5-minute cooldown guard, plus retroactive scan procedure for rounds already complete at deploy time.
|
||||
|
||||
## What Was Built
|
||||
|
||||
### Task 1: AI_RANKING_COMPLETE + AI_RANKING_FAILED notification types
|
||||
|
||||
Added two new entries to `src/server/services/in-app-notification.ts`:
|
||||
|
||||
- `NotificationTypes.AI_RANKING_COMPLETE` — type string + `BarChart3` icon + `normal` priority
|
||||
- `NotificationTypes.AI_RANKING_FAILED` — type string + `AlertTriangle` icon + `high` priority
|
||||
|
||||
Pattern follows existing FILTERING_COMPLETE / FILTERING_FAILED entries exactly.
|
||||
|
||||
### Task 2: Auto-trigger hook + new ranking procedures
|
||||
|
||||
**evaluation.ts — `triggerAutoRankIfComplete` function:**
|
||||
|
||||
```typescript
|
||||
async function triggerAutoRankIfComplete(
|
||||
roundId: string,
|
||||
prisma: PrismaClient,
|
||||
userId: string,
|
||||
): Promise<void>
|
||||
```
|
||||
|
||||
Logic flow:
|
||||
1. Count required assignments — skip if not all complete
|
||||
2. Read `round.configJson` → check `autoRankOnComplete` + `rankingCriteria` — skip silently if not configured
|
||||
3. Cooldown guard — skip if AUTO snapshot exists within last 5 minutes
|
||||
4. Call `aiQuickRank()` — executes in 10-30s asynchronously
|
||||
5. Create `RankingSnapshot` with `triggerType: 'AUTO'`, `triggeredById: null`
|
||||
6. Notify admins via `AI_RANKING_COMPLETE` notification
|
||||
7. Catch-all: any error sends `AI_RANKING_FAILED` notification, never rethrows
|
||||
|
||||
**Fire-and-forget call in submit mutation (line 378):**
|
||||
```typescript
|
||||
void triggerAutoRankIfComplete(evaluation.assignment.roundId, ctx.prisma, ctx.user.id)
|
||||
```
|
||||
Placed after `$transaction([evaluation.update, assignment.update])`, before `logAudit`. The submission returns immediately — ranking runs asynchronously.
|
||||
|
||||
**ranking.ts — 7 total procedures:**
|
||||
|
||||
| Procedure | Type | Trigger | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `parseRankingCriteria` | mutation | admin | Preview-only parse (RANK-01, RANK-03) |
|
||||
| `executeRanking` | mutation | admin | Confirmed ranking with pre-parsed rules (RANK-05, RANK-06, RANK-08) |
|
||||
| `quickRank` | mutation | admin | Parse + execute in one step (RANK-04) |
|
||||
| `listSnapshots` | query | admin | List snapshots for round, most recent first |
|
||||
| `getSnapshot` | query | admin | Retrieve single snapshot by ID |
|
||||
| `triggerAutoRank` | mutation | admin | Manual trigger from round config (RANK-09) |
|
||||
| `retroactiveScan` | mutation | admin | Scan all active/closed rounds (RANK-10) |
|
||||
|
||||
**retroactiveScan logic:**
|
||||
- Finds all `ROUND_ACTIVE` and `ROUND_CLOSED` rounds
|
||||
- Checks each for `autoRankOnComplete + rankingCriteria` config
|
||||
- Checks if all required assignments are complete
|
||||
- Skips rounds that already have a `RETROACTIVE` snapshot
|
||||
- Executes ranking sequentially (not parallel) to avoid OpenAI rate limits
|
||||
- Returns `{ results[], total, triggered }` summary
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 1 - Bug] TypeScript strict mode: `?? {}` loses EvaluationConfig type**
|
||||
- **Found during:** Task 2 typecheck
|
||||
- **Issue:** `(configJson as EvaluationConfig | null) ?? {}` — the `{}` fallback widens the type to `{}` which doesn't have `rankingCriteria` / `autoRankOnComplete` fields, causing TS2339 errors
|
||||
- **Fix:** Changed to `?? ({} as EvaluationConfig)` in all three locations (evaluation.ts + two in ranking.ts)
|
||||
- **Files modified:** `src/server/routers/evaluation.ts`, `src/server/routers/ranking.ts`
|
||||
- **Commit:** c310631
|
||||
|
||||
**2. [Rule 1 - Bug] TypeScript: ParsedRankingRule[] requires double-cast to InputJsonValue**
|
||||
- **Found during:** Task 2 typecheck
|
||||
- **Issue:** `result.parsedRules as Prisma.InputJsonValue` produces TS2352 — neither type overlaps
|
||||
- **Fix:** Changed to `result.parsedRules as unknown as Prisma.InputJsonValue` (matching the pattern already used for `rankedProjects` arrays)
|
||||
- **Files modified:** `src/server/routers/ranking.ts` (triggerAutoRank + retroactiveScan)
|
||||
- **Commit:** c310631
|
||||
|
||||
## Build Status
|
||||
|
||||
- `npm run typecheck` — PASSED (0 errors)
|
||||
- `npm run build` — PASSED (full production build)
|
||||
|
||||
## Key Links Implemented
|
||||
|
||||
| From | To | Via |
|
||||
|------|----|-----|
|
||||
| `evaluation.ts submit` | `triggerAutoRankIfComplete` | `void` fire-and-forget after `isCompleted: true` |
|
||||
| `triggerAutoRankIfComplete` | `ai-ranking.ts quickRank` | `aiQuickRank(criteriaText, roundId, prisma, userId)` |
|
||||
| `triggerAutoRankIfComplete` | `in-app-notification.ts` | `notifyAdmins({ type: AI_RANKING_COMPLETE })` |
|
||||
| `ranking.ts triggerAutoRank` | `ai-ranking.ts quickRank` | admin-initiated, creates MANUAL snapshot |
|
||||
| `ranking.ts retroactiveScan` | `ai-ranking.ts quickRank` | sequential per-round, creates RETROACTIVE snapshot |
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- `src/server/routers/evaluation.ts` — modified, contains `void triggerAutoRankIfComplete(...)`
|
||||
- `src/server/routers/ranking.ts` — modified, contains `triggerAutoRank` and `retroactiveScan`
|
||||
- `src/server/services/in-app-notification.ts` — modified, contains `AI_RANKING_COMPLETE` and `AI_RANKING_FAILED`
|
||||
- Commits: 4683bb8 (Task 1), c310631 (Task 2)
|
||||
- Build: PASSED
|
||||
Reference in New Issue
Block a user