ce:full · interview-unified 5.4 / 10 · ACTION REQUIRED
00 · Executive summary
5.4OUT OF 10
ACTION REQUIRED — full 33-artifact run

Interview Unified Portal

Application review → interview day → consolidated ranking · honest scoring min(rubric 5.8, matrix 5.4) · status PARTIAL (Lighthouse not run; caps applied)

The August remediations held: one system, one auth, cycle management, hardened RLS. What stands between this platform and a smooth season is readiness, not architecture: applicant data lives in last year's records with zero materials attached, one cycle out of date, 28 date rules keyed to old calendar dates, and containers one reboot away from downtime.

📅 Sept 14, 2026🔗 interview.srv — 200🗄️ 12 urology_* tables🐳 nginx :8106 · not in compose✓ backup: 12 tables Sept 14
Scorecard · 11 dimensions

Where the platform stands

6.5
Architecture
Good bones; 3542-line god component, 268 inline query sites
5.5
Correctness
28 hardcoded date rules keyed to last year's dates
5.0
Security
Auth solid; score-INSERT policy scopes to {public}
6.5
Design/UX
Kiosk polish is real; no empty states, token bypasses
1.0
Testing
Zero test files of any kind
4.5
Performance
1.58 MB single bundle; no code splitting
4.0
Ops
No /health, no compose wiring, no error tracking
3.0
Docs
README is a Lovable default template
6.0
Conversion
Journey works; ranking averages unlike scales
4.0
DX
No tests, no CI, type-checking off
6.5
Brand/Emotional
Faculty-facing polish is real; kiosk impresses

Findings heatmap

PhaseP0P1P2P3
Code review1352
Data integrity2010
Config/Deps0211
Deploy/Ops/DR0320
UX/Flow/Reporting0133
Compliance/Obs/Tests0002
P0 · Fix before applications arrive

Critical — 5 data/ops blockers + the flagship build

P0-1 · DATA

Last year's records carry zero materials — and every field was typed by hand

urology_applicants: 0/123 with pdf_url, photo_url, usmle_score, or lors (live SQL) · these are last season's records; new applications arrive in ~1 week

The gap is pipeline readiness, not an emergency import. Last year's manual-entry burden (123 applicants typed one by one) is exactly what this season's build removes.

Fix: build the auto-import pipeline (P0-6) so new PDFs flow straight into complete records. Last-year data is now backed up — 12 tables, verified.

P0-2 · DATA

Cycle still 2025-2026; stray Sept-8 test session pollutes assignments

urology_interview_cycles: 1 row (2025-2026, is_current) · 2026-09-08 session + 2 confirmed assignments created Sept 2

Every cycle-scoped query defaults to last season; the test session can leak into session lists and kiosk.

Fix: archive 2025-2026 → create 2026-2027 at /admin/cycles (15 min); delete the stray session + assignments (5 min).

P0-3 · CODE

28 hardcoded date rules keyed to last year's interview dates

InterviewManagement.tsx:143-148 · InterviewSessionDetail.tsx:276-491 · InterviewerDashboard.tsx:157-183 · InterviewSessions.tsx:72 — InterviewerRoomView.tsx:399 comment admits the fix: "Use configurable flag instead of hardcoded dates"

Einstein-only-Oct-30/31 and special Nov-24/Dec-11 rules live against literal calendar dates. New session dates silently mis-bucket applicants.

Fix: extract to per-session flags/config; 4 hr. InterviewerRoomView already points the way.

P0-4 · OPS

Containers not in docker-compose — one reboot kills interview day

interview-portal-web (8106) + urology-review (8105, deprecated) run manual docker run; compose lists neither

Fix: add to compose, stop the deprecated container entirely (its app was absorbed by /evaluation). 30 min.

P0-5 · SECURITY

Score INSERT policy scoped to {public}; no audit trail on score edits

pg_policies: "Interviewers can insert scores for assigned rooms" → TO {public}; rule body vs policy name intent unverified

Fix: re-verify WITH CHECK enforces room assignment (interviewer_1/2/3_id match); add score-change history. last_edited_by exists; history doesn't.

P0-6 · BUILD

Auto-import: PDF in → complete applicant record out. No AI, no external servers

Privacy rule (locked): extraction runs 100% on the VPS — PyMuPDF/pdfminer + regex anchor parsing + tesseract OCR for scanned pages; all verified installed. Data never leaves the server; no LLM in the pipeline.

The flow: admin drops the applicant PDF → parse → extract name, AAMC/external ID, medical school, USMLE, LOR summaries — and crop the applicant photo directly out of the PDF (embedded-JPEG or portrait-region render) → Storage upload → full applicant row created → review queue shows it instantly. Low-confidence fields route to a "confirm before publish" card, never silent wrongness.

Fix: extend upload-applicant-pdf into a parse-and-create pipeline; deterministic passes only; admin sees which fields were auto-extracted vs hand-confirmed. 1–2 d. Deadline: live before the PDFs land.

P1 · High

High-severity — 9 findings

P1-1

Interview scores are a single 1–5 rating; review uses 7 categories × 1–10 — the unified ranking averages both

InterviewScoreForm.tsx:117 (total_score = rating) vs urology_reviews schema (7 columns) · ConsolidatedRankingBoard.tsx:89 merges both

Fix: pick one scale for both stages before interview day — recommendation: 7 categories, 1–5, matching existing form copy — so ranking numbers become comparable.

P1-2

Reviewers see all 123 applicants — no assignment scoping, no progress tracking

/evaluation loads every applicant; no reviewer→applicant assignment consulted

Fix: per-reviewer applicant assignment table + filter + progress bar. Personnel note (Sept 14): the evaluation committee and interview panels may be different people, and interviewer rosters can differ per session date — reviewer assignments and interviewer assignments are TWO separate models (reviewer→applicants; interviewer→rooms per session as the schema already supports). The single biggest reviewer-experience upgrade for review week.

P1-3

Zero tests on scoring math, assignment generation, rotation timers, date filters

find *.test.* → 0 files

Fix: add vitest; start with the extracted date-helper (pairs with P0-3 fix) and score-aggregation tests.

P1-4

1.58 MB single JS bundle; no route-level lazy loading

vite build: index.js 1577.57 kB (gzip 419 kB); zero lazy() calls; 101 kB CSS also single

Fix: lazy() per domain (admin / interviewer / kiosk / evaluation) — same pattern that cut an Admin chunk on Unified Platform from 450 KB to ~80 KB.

P1-5

No backup automation for season data; rollback is a manual dist.bak

one-time backup done Sept 14 ✓; automation absent

Fix: nightly pg_dump of urology_* to a VPS volume. 2 hr.

P1-7

Interviewer day-of flow doesn't surface paper-stage context by default

ApplicantProfileDialog (776 lines, reads urology_reviews) exists but sits one click outside the default room view

Fix: render 3-line paper-stage highlights on the room card — makes "one system" feel real to faculty in the room.

P1-8

Cycle archive captures counts, not data exports

Fix: archive step writes a CSV/JSON export of the closing cycle.

P1-9

No 48h interviewer prep email pipeline

no email automation in repo for session reminders

Fix: cron-driven email from that session's own interviewer roster (rosters can differ between dates) + assigned applicants + 3-line paper highlights — prefaced "from the evaluation committee" since interviewers may not be reviewers. 4–6 hr.

P2 · P3

Medium / low — 20 findings

SevFindingWhere / effort
P2tsconfig strict=false + strictNullChecks=false; 93 anyenable strict on interview/* first · 3 hr
P298 console.log in src — payload leaks to user consolesstrip + structured logger · 2 hr
P2Destructive overwrite-assignments with no undoconfirm + snapshot · 2 hr
P2reviewer_name TEXT, no FK to reviewers/interviewersmigration + backfill · 3 hr
P2Legacy duplicate login pages shipped in bundledelete 3 legacy pages · 1 hr
P2No error tracking; console-based "logging"lightweight tracker · 2 hr
P2No WCAG run; contrast untestedaxe/Lighthouse pass · 2 hr
P2temp-applicant-pdf.pdf ships in prod bundledelete · 5 min
P2No per-reviewer completion report (needed daily in review week)AdminReports extension · 4 hr
P3Dual lockfiles (bun.lockb + package-lock.json)pick one · 15 min
P3README Lovable defaultrewrite · 2 hr
P3InterviewSystem.tsx 3542-line god componentmulti-session decomposition · 2–3 d
P3Applicant-facing itinerary portalfuture season · P3
P3No CI (lint/build/tests)GitHub Actions basic · 3 hr
P3Rotation-start concurrent-trigger race (no optimistic locking)admin-only + confirm · 1 hr
11 · Quick wins (time-boxed)

Highest-value, lowest-effort

Purge stray Sept-8 test session + assignments; delete temp-applicant-pdf.pdf from public/
Archive 2025-2026 → create 2026-2027 cycle — every cycle-scoped query keys off it
Compose wiring for interview-portal-web; stop the deprecated urology-review container
Delete 3 legacy login pages; swap console.log for a structured logger stub
Nightly pg_dump of urology_* to a VPS volume
Extract the 28 date rules to per-session config + 5 unit tests on the new helper
Reviewer assignment scoping + progress bar (P1-2)
6.34 · Page flow + roles

The four journeys, as built

REVIEWER

Sees all 123 applicants, self-tracks progress

/evaluation → raw list, no assignment scoping, no progress indicator

Needed this week: assigned-only view + progress bar (P1-2) — review week is 5–10 reviewers × ~12 applicants.

INTERVIEWER

Best surface in the app — timed rotations, realtime updates

dashboard → schedule → room → score

Paper-stage context exists in ApplicantProfileDialog but sits one click outside the default flow (P1-7) — surface it on the room card.

ADMIN

Tooling-rich, but cross-stage work feels like six separate tools

6 admin pages + interview mgmt + ranking, no progress ribbon

Admin ribbon (Review % → Interview Day → Ranking %) is the design fix.

APPLICANT

Nothing — no portal, no itinerary

planned P3

Applicant itinerary portal (arrival instructions, room rotation, day timeline) is on the 6-month roadmap.

Phase 6.2 · Architecture

Architecture 6.5

Shape

Single SPA → Supabase direct (no BFF). Auth via UnifiedAuthProvider + has_urology_role() RPC; three roles (admin/interviewer/evaluation). Cycle context (InterviewCycleContext) defaults to hardcoded '2025-2026'.

Component inventory

  • ·Pages: Home, Unified/eval/admin/interviewer logins (legacy redirects), /evaluation (InterviewSystem.tsx 3542L — DOES review scoring, merges the absorbed review.srv), admin dashboard/applicants/cycles/reports/rooms/interviewers/score-mgmt/setup-wizard, interview mgmt + session detail, kiosk, room monitor, room comments, consolidated ranking, interviewer dashboard/view/schedule
  • ·Realtime: Supabase channels for kiosk rotation display
  • ·Data: 12 tables, all cycle-scoped; FK constraints added Aug; urology_reviews.reviewer_name TEXT (no FK) vs urology_interview_scores.interviewer_id UUID — schema split persists
  • Boundary problems

    1. InterviewSystem.tsx = 3542L god component — evaluation UI, review API calls, categories, DNR logic all in one file.

    2. No service/repository layer — 26 components call supabase.from('urology_*') inline (268 total call sites), no central query builders.

    3. Hardcoded session-date logic (28 sites) in 5 files — business rule "Einstein-only on Oct 30/31; special flow Nov 24/Dec 11" is buried in components against literal calendar dates. Breaks the moment interview dates change (i.e. this season).

    4. Legacy login pages (3 login flows + unified) still in bundle.

    Lifecycle data flow (verified in code)

    urology_reviews (paper stage, reviewer_name TEXT) → read in ApplicantProfileDialog + ConsolidatedRankingBoard + InterviewerRoomView (paper context before interview) → urology_interview_scores (single rating 1–5 per room, notes, question_comments jsonb) → ConsolidatedRankingBoard merges avg review + avg interview per applicant → urology_final_selections (rank_order, decision). Chain EXISTS but interview scale ≠ review scale (see 32).

    Phase 6.3 · Code review

    Code Review — full findings + after-lenses 5.5

    Method: source sync from VPS (312 files), grep sweeps, build/tsc probes in throwaway copy.

    Findings

    IDSevFindingFile:line evidence
    CR-01P0Hardcoded session-date business rules: 28 sites across 5 files filter Einstein applicants and special scheduling against literal dates Oct 30/31, Nov 24, Dec 12 — last year's dates. New season dates will silently mismatch logic.InterviewManagement.tsx:143-148, InterviewSessionDetail.tsx:276-491, InterviewerDashboard.tsx:157-183, InterviewSessions.tsx:72, (InterviewerRoomView.tsx:399 comment says "use configurable flag instead of hardcoded dates" — half-migrated)
    CR-02P1Zero tests — 0 unit, 0 integration, 0 E2E. Scoring math + assignments logic untested.find *.test.* = empty
    CR-03P1InterviewSystem.tsx 3542L god component; 3 more >1000L.wc -l: InterviewSystem 3542, InterviewerRoomView 1304, RankingApplicantCard 1065, ConsolidatedRankingBoard 1009
    CR-04P198 console.log left in src (leaks query payloads to browser console on any user's machine).grep console.log = 98
    CR-05P293 : any types.grep
    CR-06P2tsconfig strict=false, strictNullChecks=false, noUnused*=false across app configs. Build is clean only because type-checking is off.tsconfig.app.json/tsconfig.json
    CR-07P2Destructive copy with no undo: "Any existing assignments in other sessions will be overwritten."bundle copy
    CR-08P2Scale copy mismatch: UI text says "1 = Poor…5 = Excellent" (single-rating form writes total_score=rating) while review table schema has 7 separate 1–10 columns. Numeric/graphic scale labels from dead code could confuse.InterviewSystem.tsx:2817, InterviewScoreForm.tsx:117 total_score: rating
    CR-09P2142 catch blocks; several .catch(()=>{}) patterns risk silent failures on admin flows.grep
    CR-10P3Legacy Lovable README ("Welcome to your Lovable project") — zero operational docs.README.md
    CR-11P3No Ceslint pre-commit; eslint config exists but no CI to run it.package.json scripts
    CR-12P2Magic cycle fallback '2025-2026' baked in 4 sites of EditApplicantDetailsDialog + context default.EditApplicantDetailsDialog.tsx:67-81, InterviewCycleContext.tsx:23

    After-lens verdicts

  • ·design: greenfield + tailwind tokens, kiosk themes light/dark, animation classes — 6.5
  • ·integrity: FKs exist, reviewer_name TEXT no FK — see 08a
  • ·production: no /health route in SPA (static); container health = HTTP 200 check only
  • ·gap: applicant-facing portal absent (planned P3 last year)
  • ·vibe: faculty-facing is clean; dev-facing (console logs, temp-applicant-pdf.pdf in public/) is sloppy
  • Phase 6.8a · Data integrity

    Data Integrity (live DB) 4.5

    FindingSevEvidence
    urology_reviews = 0, urology_final_selections = 0, urology_interview_scores = 0 — no data from last season survives in tables; the "last-year data" is raw applicants onlyP0 (data reality, not code)live counts
    All 123 applicants: pdf_url, photo_url, usmle_score, lors ALL null — these are LAST year's records; everything was typed manually last year. New applications arrive in ~1 week → the fix is the auto-import pipeline (P0-6), not a manual data dumpreframed Sept 14live SQL + user confirmation
    Cycle stuck at 2025-2026 (is_current=t) + stray 2026-09-08 session created Sep 2 with 2 confirmed assignments — leftover test artifactP1sessions table
    seed-vs-live drift: FK constraints + cycle scoping added Aug, but data was never exercised end-to-endP20 rows end tables
    Category data: 115 regular, 5 i-sub, 2 sub-i, 1 einstein — matches Einstein-only date logicinfo
  • ·✅ Sept 14: one-time full backup of all 12 urology_* tables (schema+data) → VPS /var/backups/interview-season/ + /workspace/backups/ — last-year data protected during upgrades
  • Phase 6.4 · Dependencies

    Dependencies 6.0

  • ·package.json ~60 deps (radix suite, dnd-kit, supabase-js, react-query, recharts?, date-fns)
  • ·npm ci clean-install PASSED in throwaway VPS copy (0 missing) — declared-vs-installed OK
  • ·No unpinned risk deps; major runtime = @supabase/supabase-js ^2.58
  • ·No npm audit run (offline constraint of session) — flagged PARTIAL
  • ·bun.lockb + package-lock.json both present (dual lockfiles, P3)
  • Phase 6.5 · Tests

    Test Health 1.0

    Unit/integration/E2E0
    CInone
    Score anchortesting = 1.0/10 (mechanical: zero test files)

    High-risk untested logic: assignment generation+overwrite, rotation timers/pause math, score aggregation (rounded avg formulas), Einstein date filters, RLS-protected admin flows.

    Phase 6.6 · Documentation

    Documentation 3.0

  • ·README.md = default Lovable template (73L, no runbook value).
  • ·WORKFLOW.md referenced by skill but not in this tree (find = absent) — exists only in GitHub repo docs? Verify.
  • ·No .env.example, no API/DB data dictionary beyond migrations, no interviewer day-of quick-guide.
  • ·Ops docs live outside the app (Hermes skills) — not discoverable by a new dev.
  • Phase 6.8 · API surface + DB

    API Surface & Data Layer 6.0

    No REST backend: all data ops = Supabase client calls direct.

    Edge functions (10)

    import-applicants, import-new-applicants, clean-and-import-applicants, add-single-applicant, upload-applicant-pdf, upload-single-pdf, pdf-proxy, bootstrap-admin, create-interviewer-auth, reset-interviewer-password

    Client query surface (verified counts)

    Tablecall sites
    urology_interview_scores45
    urology_interview_rotations39
    urology_applicants32
    urology_interview_assignments26
    urology_interview_rooms25
    urology_interview_sessions19
    urology_reviews18
    urology_interviewers15
    urology_final_selections13
    urology_interview_cycles6
    urology_user_roles2
    urology_reviewers1

    Cross-ref issues

  • ·interview_cycle filter present in 15/17 files that touch cycle tables — mostly scoped; EditApplicantDetailsDialog has fallback magic string (CR-12)
  • ·reviewer_name TEXT (no FK) — reviews cannot be joined/cleaned → permanent P2
  • Phase 6.9 · Mobile

    Mobile & Responsiveness 5.0

  • ·Interviewer submits scores mid-rotation — expected on phone. Bundle includes responsive tailwind, kiosk card system; score form has no swipe/tap-optimized quick rating (cap noted in 16).
  • ·No dedicated mobile walkthrough performed in this run → cap A11y/UX findings at surfaced evidence only. PARTIAL.
  • Phase 6.10 · Design quality & strategy

    Design Quality & Strategy 6.5

    Radix/shadcn primitives + a genuinely strong kiosk visual system (glow-success, kiosk-stat-card-compact, animate-scale-in, dark/light kiosk themes); tailwind token usage mostly present; large-type day UI reads well at distance.

    Weak points: raw slate-* classes bypass tokens in interview components; AdminReports (939L) density risks clutter; the zero-PDF empty state is not designed (immediately relevant given materials gap).

    Design strategy — this season

    ONE applicant card across all 3 stages (review → interview → ranking): same card, stage-scoped sections. Faculty feel one product. ② Kill raw slate-* → consolidate tokens. ③ Admin progress ribbon: Review % → Interview Day → Ranking %, persistent across admin pages. ④ Designed empty states for the zero-materials review queue ("Waiting on materials — 123 applicants pending") instead of blank cards. ⑤ Kiosk stays photo-free (anon SELECT surface — keep it that way when photos arrive).

    Phase 6.17 + 6.21 · IaC, Deploy & DR

    Production, Deploy & Disaster Recovery 4.0

  • ·Multi-stage Dockerfile + docker-compose.yml — REPO has compose, but running containers are NOT in it (verified via docker ps + compose services list from Aug audit). VPS reboot = interview day down.
  • ·No CI/CD (deploy = manual build+scp+restart documented in skill).
  • ·.git on VPS for state tracking.
  • Score: 4.0

  • ·Sept 14: one-time full backup completedpg_dump of all 12 urology_* tables (schema+data) → /var/backups/interview-season/urology_full_20260914_pre_season.sql (VPS) + /workspace/backups/urology_full_20260914_pre_season.sql (local copy). Verified: 12 COPY sections, restorable with a single psql command.
  • ·Still missing: AUTOMATION. No backup automation found for interview season data on this system (Supabase self-hosted enterprise backup not in scope of this repo).
  • ·Deploy rollback = git-on-VPS manual cp of dist.bak (documented in skill, not in repo).
  • ·VPS reboot = containers offline (not in compose). DR for OP STUDENT data until season end = unverified.
  • Score: 2.0 (P1)

    Phase 6.7 · TypeScript

    TypeScript Audit 4.0

  • ·tsc --noEmit exit 0 (throwaway VPS copy) = clean against CURRENT (loose) config
  • ·strict=false, noUnusedLocals=noUnusedParameters=false, strictNullChecks=false (root tsconfig) = type system OFF in practice
  • ·93 any; generics for Supabase rows untyped via generated Database types not present
  • ·Recommendation: enable strict + strictNullChecks FIRST for interview components (scoring math), then tally
  • Phase 6.22 · Performance

    Performance Audit 6.0

  • ·1.58MB single JS bundle every page → everything eager, heavy round trip.
  • ·N+1 risk: 268 supabase call sites often sequential per-component (loops per applicant).
  • ·Realtime channels fine for kiosk.
  • Score: 6.0 (no live Lighthouse — negotiate cap 8.0; mechanical evidence supports 6)

    Auto-import note (Sept 14)

    Extraction pipeline is LOCAL-ONLY: PyMuPDF/pdfminer + regex + tesseract OCR, all verified installed on the VPS — no AI provider, no external API, sensitive applicant data never leaves the server.

    Phase 6.34 · Page flow

    Page Flow Audit (journeys end-to-end) 5.5

    Key journeys traced in code + bundle (no live-browser walkthrough this run — PARTIAL):

    1. Reviewer review: login → /evaluation → pick applicant → 7-cat score + notes → save. Friction: no assignment-scoping → reviewer sees all 123.

    2. Interviewer day: login → interviewer dashboard → schedule → room view → timed rotation → 1–5 rating + notes → submit. Timer + reset working. Paper-stage context only if admin opens profile dialog — not in the default interviewer flow (opportunity).

    3. Admin ranking: /consolidated-ranking → drag-rank (dnd-kit) → final_selections write. avg review + avg interview MERGED but scale mismatch makes averages lie (CR-08).

    4. No applicant-facing portal (planned gap).

    Dead ends: legacy login routes (/admin-login etc) redirect to /login — good.

    Phase 6.35 · Integration probes

    Integration Probes (live/build evidence) 5.0

    ProbeResultEvidence
    Live site200curl root
    npm ci clean-installPASS 0 missingthrowaway /tmp/ce-build on VPS
    Ambient build (vite)PASS 14.7s, 1.58MB chunkbuild log
    tsc --noEmitPASS exit 0same
    Unit testsnone (0)repo find
    LighthouseNOT RUNno browser session available — caps applied
    /api/healthn/a (SPA, no BFF)route table
    Compose coverage of running containersFAIL — interview-portal-web + urology-review run manual docker runAug audit verified + current docker ps unchanged
    urology_* backup (Sept 14)PASS — 12 tables dumped (schema+data), VPS + local copy, COPY rows verified
    Edge functions deployed?claimed live; import-applicants verify needed during PDF import
    Lens 31 · Role experience

    Role Experience Audit 6.0

    PersonaJourneyExperience verdict
    Reviewer (faculty)/evaluation, all 123 applicants, must self-track progressRaw list; no assignment scoping; no progress indicator. Needs P1-6 (own-applicants) urgently — institutional scale = 5-10 reviewers × 12 applicants each
    Interviewer (faculty)dashboard → schedule → room → scoreSolid timed-rotation UX, realtime updates. Missing: paper-stage context in DEFAULT flow (it exists in profile dialog, one click away) + should see assigned applicants only
    Admin (you)6 admin pages + interview mgmt + rankingTooling-rich; all 3 stages in one URL now; progress-ribbon absence makes cross-stage admin feel like 6 separate tools
    ApplicantnothingNo portal, no itinerary (P3 roadmap item, matches season plan)
    34 · August baseline — every finding preserved & dispositioned

    August 2026 report — what happened to every finding since

    The accepted August report recorded 21 P0/P1 findings, a 10-item ROI list, a 2-week sprint and a 4-quarter vision. Nothing is dropped: each item below is dispositioned against Sept-14 live evidence — resolved items kept as proven fixes, open items carried into today's severity lists.

    Every finding the Aug 7-8 ce:full recorded is listed below with TODAY'S status (verified by the Sept 14 probes). Nothing from the accepted report is dropped: resolved items are kept as proven fixes, open items stay live findings.

    P0 — Critical (6, August)

    Aug findingStatus Sept 14Evidence
    P0-1 Two separate auth systems — no SSO (Review used hardcoded "Urology2026" visible in client JS; isAuthenticated defaulted true)RESOLVEDreview.srv absorbed into portal /evaluation; single Supabase Auth; deprecated container still running but unused (retire = P0-4 today)
    P0-2 Neither container in docker-compose.ymlSTILL OPENcompose services ≠ running containers (verified Sept 14)
    P0-3 No interview cycles in database (0 rows)RESOLVED (residual)1 cycle exists (2025-2026, is_current=t) — but stuck on last season → P0-2 today
    P0-4 Review system doesn't filter by interview_cycleRESOLVEDcycle scoping active in 15/17 files; magic fallback remains (CR-12)
    P0-5 Hardcoded password exposed in client bundleRESOLVEDauth now Supabase + role RPC; old app deprecated
    P0-6 No yearly setup workflowRESOLVED (unused)/admin/cycles wizard exists — not yet run for 2026-2027

    P1 — High Impact (7, August)

    Aug findingStatus Sept 14
    Any authenticated user — incl. test accounts from other systems — can read/modify ALL urology data (47 auth users; permissive RLS)PARTIALLY RESOLVED — Aug hardening rewrote 6 critical policies (44 live) but score INSERT still {public} (P0-5 today); 50 auth users now
    Review API bypasses ALL RLS via service role keyRESOLVED BY DEPRECATION — app no longer the evaluation surface; key only matters if deprecated container stays up (retire it)
    No cycle dropdown / archive indicator; importing 2026-2027 would blend cyclesRESOLVED (cycle context) but cycle not yet advanced
    No loading states / Suspense boundariesOPEN (P2-class)
    Consolidated ranking route exists but data flow not wiredRESOLVED THEN REBROKEN: code reads both sources but averages incommensurable scales (P1-1 today)
    No design consistency across 2 systemsRESOLVED — one system now
    Reviewer email pipeline missingSTILL OPEN = P1-9 today; and departed reviewer (Dr. Reese) removed; reviewer_name TEXT FK issue open (P2)

    P2 / P3 (from August — still-relevant items retained)

    Aug findingStatus
    Reviewer name TEXT not FK — no join possibleSTILL OPEN (P2)
    Sessions created Oct–Dec 2025 never populated; no cleanup for stale dataPARTIAL — archive exists; stray 9/8 test session still in DB (P0-2 today)
    Cycle context used sessionStorage not localStorageRESOLVED (Supabase-backed context now)
    No CSV/bulk import for real applicant dataRESOLVED-BUT-UNUSED — edge functions exist; the Sept 14 plan is auto-import (P0-6) so no manual entry at all
    No health endpoint, no backups before cycle transitionSTILL OPEN (18/21) — except the one-time urology_* backup completed Sept 14
    15+ Vercel-focused DEPLOYMENT files in Review (wrong platform)MOOT — app deprecated
    20 unauthenticated rate-limited Review API routesMOOT — deprecated
    Zero tests in either systemSTILL OPEN = P1-3 (0 test files)
    Admin tables overflow on mobile (Review side)MOOT; portal responsive grids good (09)

    August Roadmap — as written then, fulfilled (for the record)

    - Week 1 (Stabilize & Secure): D1 compose · D2 create historical cycle row · D2 cycle filtering · D3 RLS harden · D4-5 remove departed reviewer + clean auth users + remove service-role key → all executed Aug 7-8 except compose (still open)

    - Week 2 (Integrate & Launch): D6 yearly setup wizard · D7 CSV import tool · D8-9 merge Review into /evaluation · D9 wire kiosk/interviewer to real data · D10 cycle selector → executed Aug 7-8 (merge verified; wizard + cycle rows exist)

    August 6-Month Vision (quarters as accepted then)

    Q4 2025 (as planned): Stabilize & Integrate — DONE. Q1 2026: Unified portal + cycle mgmt — DONE. Q2 2026: Real-time interview day + consolidated ranking + analytics — built, data-empty (this season will exercise it). Q3 2026: Archive cycle · analytics dashboard · reviewer email pipeline — email pipeline still open (P1-9). Q3–Q4 2026: automation + year-2 wizard — superseded by Sept 14 season plan (P0-6 auto-import etc.).

    August Feature Upgrades + ROI Ranking (status Sept 14)

    ROI item (as accepted: effort · payoff)Status
    Add cycle filtering — 2 hrs, unblocks yearly isolationDONE Aug
    Add containers to compose — 30 min, prevents silent loss on rebootOPEN = P0-4
    Remove Dr. Reese — 5 minDONE
    Build setup wizard — 4-6 hrs, enables launchDONE (unused — run it)
    Build CSV import — 3 hrs, enables real applicant dataSUPERSEDED by P0-6 auto-import (no AI, local-only)
    Harden RLS — 3 hrsDONE Aug; gap remains (INSERT {public})
    Merge Review into Portal — 8-12 hrs, eliminates dual maintenanceDONE Aug
    Wire kiosk + interviewer — 4 hrsDONE Aug
    Consolidated ranking — 4 hrsDONE-but-scale-broken (P1-1)
    Reviewer email pipeline — 3 hrsOPEN = P1-9

    August Feature Upgrades (the forward-looking list, then → now)

    August ideaSept 14 disposition
    Merge app + interview scores in unified ranking with drag-and-drop reorderExists; needs scale fix (P1-1)
    Supabase Realtime for kiosk countdown, room monitor, live scoringWORKING (verified in code)
    Post-cycle analytics: score distributions, reviewer agreement (Cohen's kappa), cross-cycle comparisonOn 6-month vision (Q1 2027)
    Reviewer email pipeline for launch/assignmentsOPEN (P1-9)
    AI-assisted applicant screening (parse PDFs, flag criteria, suggest distribution)SUPERSEDED & CONSTRAINED: P0-6 auto-import is deterministic + 100% local — no AI, no external servers (user privacy rule Sept 14)
    Mobile-first interview-day scoringExists (rotation-timed scoring UI); perf + a11y gaps documented (09/16)
    Lens 32 · Logic & policy

    Business Logic & Policy Audit 5.0

    Edge-case matrix: whom does the date filter include/exclude?

    Session dateLogic saysCategory filter
    Oct 30/31Einstein onlycategory === 'einstein'
    Nov 24, Dec 11, Dec 12special flowcategory !== 'einstein'
    Any other dateNO RULE — default path = regular applicants included, einstein excluded? (Option A in code depends on branch) — inactive rule = silent mis-bucketing if 2025-2026 dates shift

    Rating/averaging math (verified)

  • ·Interview score: SINGLE rating 1–5 → total_score (form says 1=Poor…5=Excellent). Round-to-1-decimal averages elsewhere (Math.round(x*10)/10).
  • ·Review score (paper): 7 categorical columns (preference…personal), each presumably 1–10, one total_score.
  • ·ConsolidatedRankingBoard averages BOTH into one ranking number → incommensurable scales pollute rank order. Verified code reads urology_reviews + urology_interview_scores into one combined list.
  • Policy: what happens when…

  • ·A reviewer rates and moves on mid-review (no draft/partial save state detection) → partial row risk
  • ·Interviewer assigned to room AFTER session starts → INSERT policy allows {public} but checks nothing about assignment validity
  • ·Score edited by admin (score-management) → last_edited_by set but no change log
  • Lifecycle integration (the "one system" goal, in code)

  • ·reviews-application interviewers CAN see paper reviews (ApplicantProfileDialog) — linkage EXISTS ✓
  • ·BUT same scale conversation unresolved — CR-08/32 = what makes the ranking meaningful
  • ·final_selections writes exist (13 call sites) — rank chain is wired ✓, just empty data
  • Lens 33 · Operational reporting

    Operational Reporting Audit 6.0

    ReportAnswers real question?Trustworthy?Export?
    AdminReports (CSV)Yes (review progress)Yes (once data exists)CSV ✓
    Applicant Performance Rankingsavg score across sessionsMixed — combines incommensurable scales?
    RoomMonitorreal-time opsYes (realtime)n/a
    Interviewer progress (avg + count)YesYes?

    Missing: per-reviewer reviewer-assignment completeness report (who hasn't submitted what) — the one admin report needed daily during review week. Per-session day-of summary with quotes (competing feedback for the PD) — flagged as design-opportunity, not bug.

    12 · Backlog

    Backlog — epics, stories, story points 42 SP

    EpicStoriesSP
    Season Readiness513
    Unify Review/Interview Scale38
    Reliability & Tests413
    Faculty Experience38
    Total1542 SP

    User stories

    Season Readiness (13 SP): admin — current cycle 2026-2027 (0.5) · reviewer — PDFs visible inline (5) · dev — date rules config-driven (2) · admin — stray test data purged (0.5) · ops — containers in compose (5).
    Unify Scale (8 SP): one scale across 7-cat review and day-of interview so ranking compares like with like.
    Reliability & Tests (13 SP): vitest on date-helper, score aggregation, assignment overwrite; CI basics.

    Root cause found + fixed · Sept 14

    Why the live interview dashboard failed last year — and the fix, already applied

    ROOT CAUSE

    Realtime publication excluded every interview table — subscriptions ran but never received events

    supabase_realtime publication contained only 4 tables from OTHER systems (absence_requests, float_assignments, notification_jobs, shifts) — zero urology_* tables

    All 13 live components (room monitor, kiosk, interviewer room view, leaderboard, scoring progress) subscribe via postgres_changes. The subscriptions opened fine but got no events — the "live" dashboard looked dead because, downstream of the socket layer, it was.

    Fix (applied to the live DB Sept 14): added the 5 interview tables to the publication (rotations, scores, assignments, rooms, sessions) — verified in pg_publication_tables; realtime container restarted healthy. This is a configuration fix, minutes not days.

    Validation: before interview day, run one end-to-end socket test (insert a test rotation, observe a subscribed client react). That closes the loop with proof instead of hope.

    FALLBACK — not a skip

    If realtime misbehaves on the day: 5-second polling is already built in

    RoomMonitor already has a 5s polling backup alongside its subscription (RoomMonitor.tsx:243). The built plan: extend that poll-fallback to the other day-of surfaces behind a toggle — live = realtime when healthy, degraded = 5s polling when not. Either way the room monitor and kiosk reflect the room.

    Recommendation: don't skip the live dashboard. The root cause was configuration and is now fixed; the safest posture is realtime primary + polling fallback, validated with a live socket test before the first session.

    Constraint · Sept 14

    Personnel are disjoint — review ≠ interview

    DESIGN RULE

    Evaluation committee and interview panels are separate people — and interviewer rosters can differ between session dates

    User-confirmed constraint, Sept 14, 2026

    Two separate assignment models: reviewer→applicants (paper stage) and interviewer→rooms (per session, via the existing room interviewer slots). No global season roster is assumed anywhere.

    Interviewer context: for an interviewer who wasn't on the review committee, the paper-stage highlights on their room card are prefaced "from the evaluation committee" — it's their first exposure to the applicant's paper record, not a reminder.

    Per-session rosters: the 48h prep email and session views pull that session's own interviewer list. A person can interview on one date and not the other — the schema (per-room interviewer slots) already supports this; the tooling just can't assume continuity.

    Reporting stays role-split: reviewer completeness (who hasn't finished paper reviews) and interviewer coverage (who's assigned to which rooms/rotations) are reported separately — a session-only interviewer having zero review rows is correct, not a red flag.

    12 · Strategic roadmap

    2-Week Sprint

    • D1–2: P0-2 cycle purge (30 min) → P0-6 auto-import build begins — deadline: before PDFs land · 4 SP
    • D3: P0-3 date-rule extraction + unit tests (4 hr) · 3 SP
    • D4–5: P1-1 scale unification + P1-2 reviewer scoping · 5 SP
    • D6–7: P1-7 paper-context on room cards + P1-9 48h prep email · 4 SP
    • D8–10: P2 sweep: console.log, temp PDF, legacy logins, strict mode on interview/* · 4 SP

    6-Month Vision

    QuarterTheme
    Q4 2026Scale parity · assignment scoping · per-reviewer reporting
    Q1 2027Applicant itinerary portal · pulse surveys · end-of-day top-10 · post-cycle analytics
    13 · Recommendations

    Make interview day feel 100% better

    • One applicant card across all 3 stages — review → interview → ranking share the same card, stage-scoped sections. Faculty feel one system, not three screens.
    • Paper-stage highlights on room cards — "what the committee thought on paper" without leaving the rotation flow (the code already reads reviews; render it).
    • 48h prep email — room + applicants + 3-line highlights per interviewer, auto-generated. You stop hand-prepping; faculty walk in prepared.
    • End-of-day top-10 in room monitor within minutes of the final score — faculty see the product of their input same-day.
    • Reviewer progress bar + own-applicants-only — review becomes a manageable assignment, not a 123-row gauntlet.
    • Post-session pulse survey — 2 questions within an hour; the feedback loop that tunes the next session while the season is young.

    Feature ideas

    Auto-import (P0-6)

    PDF in → full applicant record + photo out. Zero manual typing this year; 100% local extraction.

    Per-reviewer completeness report

    Daily review-week view: who's behind, on what.

    Post-session pulse survey

    2 questions ≤1 hr after each session; ops tuning while the season is young.

    End-of-day top-10

    Auto-computed minutes after the final score lands.

    Applicant itinerary portal

    Arrival instructions, room rotation, day timeline (6-month roadmap).

    Admin progress ribbon

    Review % → Interview Day → Ranking % across every admin page.

    UI/UX & design polish

    • Designed empty states for the zero-materials review queue
    • Token cleanup: raw slate-* classes → design tokens
    • Bundle lazy-loading — faster kiosk screens and interviewer phones
    • Contrast/a11y pass before the season (footer text, small labels)
    35 · Design · motion · rendered UX (live walkthrough)

    Design, Motion & Rendered-UX audit — 16 routes walked in a real browser

    Authenticated headless-Chromium walkthrough (login → every route, screenshots + computed-style harvest) + per-page vision review. This is the missing rendered-lens pass: fonts, animations, density, empty states, and the kiosk — with per-page verdicts and scores.

    P0 — FOUND & FIXED DURING WALKTHROUGH

    Login was completely broken in production — auth pointed at a dead Supabase Cloud host

    bundle contained fsxzgkyiutpjobpmfgtk.supabase.co (DNS NXDOMAIN) while the real backend is supabase.srv1738752.hstgr.cloud — "Failed to fetch" on every sign-in attempt

    Three hardcoded cloud URLs lived in src (AdminSetupInterviewers.tsx ×2, PDFViewer.tsx). Fixed Sept 14: repointed to the self-hosted stack, rebuilt clean, container restarted, verified login → /admin/dashboard. The portal could not be logged into at all before this audit.

    Method: authenticated headless Chromium walkthrough of all 16 routes (login → route loop, screenshots + computed-style harvest), page-by-page vision review, animation counts from getComputedStyle. Complements source-level 10a/10b — this is RENDERED evidence.

    P0 discovered during the walkthrough (fixed same day)

    Login was completely broken in production. The deployed bundle pointed auth at fsxzgkyiutpjobpmfgtk.supabase.co (an unresolvable Supabase Cloud host — DNS NXDOMAIN), while the self-hosted stack is supabase.srv1738752.hstgr.cloud. Symptom: "Failed to fetch" on every Sign In. Three hardcoded cloud URLs lived in src (AdminSetupInterviewers.tsx ×2, PDFViewer.tsx). Fixed: sed to self-hosted host, rebuilt (clean), restarted container, verified login → /admin/dashboard. This is the single highest-impact finding of the whole audit: the portal could not be logged into at all before today.

    Font system (rendered)

    SurfaceDisplayBodyNotes
    Admin/portalDM SansDM Sansconsistent; SF Mono for codes/IDs
    Kiosk displayOswald (condensed caps)DM Sansgood display choice for distance
    Code/IDsSF Monomonospace used sparingly and correctly

    Verdict: font choices are coherent and modern; NOT a font problem. The problem is rendered SIZE and weight on kiosk labels (10–12px — unreadable at distance).

    Motion / animation inventory (computed, not claimed)

    SurfaceAnimated elementsVerdict
    All admin pages0static; fine for admin, but zero feedback animation (no toast/skeleton/transition presence)
    Kiosk2only the clock ticks; Live pill, counters, progress bars are static → reads "frozen/broken" from a wall
    Interviewer views0 detected (source has animate-scale-in on some cards but not observed rendering)motion mostly theoretical

    The app is effectively animation-free in rendered output. Kiosk NEEDS motion (pulse, counter roll, bar fill) because "live" must be visible.

    Per-page verdicts (rendered)

    RouteVerdictScoreTop issues
    /loginClean / Acceptable7(was broken — P0 fixed); form fine, no password reset link, no error toasts beyond inline "Failed to fetch"
    /admin/dashboardNeeds Work5.5Quick-actions 5+1 orphan row; truncated season selector ("2025–…" — critical context lost); "Go →" microcopy ×6; two 0-value KPIs not muted/explained; no primary CTA
    /evaluationNeeds Work5.5KPI math mismatch (115+2+1=118 vs 123 total — trust breaker); "0/12" AND "0% complete" redundant; gray-on-gray progress bars invisible; no search/filter/drill-down; generic template feel
    /admin/applicantsAcceptable w/ gaps6No search, no sort, no pagination (123 rows ≈ 12 viewport-scrolls), no bulk select/actions, per-row dropdowns for Decision + Date = 246+ individual interactions to schedule a season; delete has no confirm; Photo/PDF icons are unlabeled/ambiguous
    /admin/reportsAcceptable6.52 tables render; limited filters; no export visible in walkthrough
    /admin/cyclesClean7clear create/archive affordances
    /admin/interviewersThin5near-empty (0 rows); no empty-state guidance to add interviewers
    /admin/roomsThin5same — empty grid with no "set up rooms" CTA
    /admin/score-managementAcceptable6functional; dense labels small
    /admin/setup-interviewersClean7wizard steps clear
    /interviewsAcceptable6.5session list functional
    /interview-sessionsAcceptable6.5fine
    /room-monitorNeeds Work5.5empty state thin; live-data path was broken until today's realtime publication fix
    /room-commentsBroken-feeling4renders 25 chars of text — effectively blank page
    /consolidated-rankingNeeds Work5empty-state conflates two different errors ("no scores OR not authenticated") + "refresh if you just logged in" = known-flaky workaround; no column/score explanation; NOTE: also reveals scale-mismatch caveat buried in small print
    /kioskNeeds Redesign4.570% dead canvas; labels 10–12px unreadable at distance; zero-everything w/ no empty state (looks crashed); date contradiction (9/8 vs Sep 14); DUPLICATE session tabs (12:45/08:15 twice); color-only state encoding; dead motion; non-interactive chrome (tabs: brightness, X) on a wall display

    Cross-cutting design themes

    1. Admin surfaces are clean-but-generic (Bootstrap-era card+pill vocabulary). Acceptable for internal use; not a differentiator. The quickest premium lift: density toggle, sticky headers, search/filters, semantic color on KPIs.

    2. Kiosk is the one screen with a real design concept (dark display) — and it's the least finished. Fixing kiosk (scale ×2.5, live motion, empty states, dedup tabs) is the highest-value design deliverable for interview day.

    3. Empty states are the systemic gap. Rooms, interviewers, comments, ranking, kiosk all fail the "is the system alive or broken?" test. A shared EmptyState component (icon + reason + one CTA) fixes six screens.

    4. Data-trust UI issues undermine otherwise-good screens: the 118-vs-123 KPI mismatch and the ranking empty-state copy are the two places a faculty user loses confidence in 5 seconds.

    5. Motion: the design system has animation classes in source, but rendered output is static. Either wire them (kiosk first) or stop shipping them.

    Design sprint recommendations (interview-season priorities)

  • ·K-1 (interview day, high): kiosk redesign pass — bigger type (2.5×), live pulse + counter roll, session dedup, single session line, next-up panel, real empty state. 4–6 hr.
  • ·K-2: empty-state component adopted across 6 screens (rooms/interviewers/comments/ranking/kiosk/reports). 3 hr.
  • ·K-3: applicants table: search + sort + sticky header + bulk select (assign session/date in bulk). 6–8 hr — biggest review-week admin win.
  • ·K-4: evaluation page: fix KPI math display (categories shouldn't sum to total), kill redundant "0% complete", color the progress bars semantically, add evaluator drill-down. 3 hr.
  • ·K-5: dashboard: fix 6-card grid (3×2), un-truncate season selector, replace "Go →" with verbs, mark 0-value KPIs with empty-state cues. 2 hr.
  • ACTION REQUIRED — 5.4 / 10

    verified by ce:full v3.0.0 · Sept 14, 2026 · honest scoring min(rubric 5.8, matrix 5.4) · status PARTIAL (Lighthouse + network-auth probes not run; caps applied)

    5 P09 P112 P28 P3✓ data backed up42 SP backlog0 tests
    Phases · artifact index

    Full artifact set lives in /workspace/.hermes/ce/full/2026-09-14-interview-portal/ — 00 executive summary · 01 project profile · 02 architecture · 03 code review + after-lenses · 04 dependencies · 05 test health · 06 documentation · 07 TypeScript audit · 08 API surface · 08a data integrity · 09 mobile · 10 bundle · 10a/10b design · 11 roadmap · 12 backlog · 13–22 advanced phases · 23 remediation log · 24 perfection loop · 25 quality-gate matrix · 26–28 UX phases · 29 page flow · 30 integration probes · 31 role experience · 32 logic & policy · 33 operational reporting.