Software Requirements Specification (SRS)
FLA Event & Awards Platform — plain PHP, modular, subdomain-hosted.
Living document: every feature planned or built gets its use case recorded here
(rule in WORKFLOW.md). View this file in the browser at /docs/srs.
| Field | Value |
|---|
| Product | FLA Event & Awards Platform (working title "Event") |
| URL | https://event.production1.jugaar.ai (plain 443 HTTPS — no ports, no localhost) |
| Stack | PHP 8.3 (plain, no framework) · MariaDB 10.11 · Nginx · GD |
| Version | 0.1.0 (docs + scaffold baseline) |
| Status | Draft — living |
| Last updated | 2026-08-29 |
1. Introduction
1.1 Purpose
Define everything the platform must do, in business use-case language, so that:
- the owner knows exactly what is being built and in which order;
- any (new) development session can resume work from this file +
TODO.md;
- each feature has one authoritative flow description (linked workflow docs).
1.2 Scope
A modular web platform for events whose flagship capability is public-voted, AI-assessed awards. Branding (name, logo, colors, campaign texts) is managed from an admin Control Panel — nothing brand-related is hardcoded. The platform is a single-tenant deployment on its own subdomain.
Out of scope (v1.x): payments/ticketing for paid events (not requested), native apps, multi-tenant SaaS operation.
1.3 Definitions
| Term | Meaning |
|---|
| Applicant | Person registering for an award category |
| GoH | Guest of Honor (C-level: CIO etc.) invited/approved to give awards |
| Voter | Public person who votes via a shared link; verified via LinkedIn |
| Judge | Panel member reviewing applicants in the review board |
| Staff | Team members (comms, manual entries, ticket replies) |
| Admin | Owner-level access; finalize results, veto, all visibility |
| Share link | https://event.production1.jugaar.ai/v/{public_token} per applicant |
| Share card | Generated PNG for social posting (applied / visiting / winner) |
| Mock mode | Running with simulated AI/LinkedIn/WhatsApp until API keys exist |
1.4 References
ROADMAP.md — milestone plan · TODO.md — live session handoff
docs/WORKFLOWS/*.md — step-by-step flows (voter flow, judge flow, …)
WORKFLOW.md — engineering + git + documentation rules
2. Overall Description
2.1 System context
┌─────────────────────────────────────────────────────────┐
Public ───────►│ https://event.production1.jugaar.ai (Nginx :443 → PHP) │
(browser, │ │
mobile) │ ┌─────────┐ ┌───────────┐ ┌────────┐ ┌─────────────┐ │
│ │ Branding│ │ Registration│ │Voting │ │ Judges/ │ │
LinkedIn OAuth │ │ Control │ │ (3 types) │ │ (70%) │ │ Winners │ │
◄─────────────►│ │ Panel │ └─────┬─────┘ └───┬───┘ └─────────────┘ │
│ └─────────┘ │ │ │
MiniMax m3 AI │ ┌─────────────────┴───┐ ┌───┴────────┐ │
◄─────────────►│ │ AI gateway (mock/ │ │ Audit log │ │
│ │ live) │ │ (append-only) │
WhatsApp API │ └──────────┬──────────┘ └────────────┘ │
◄─────────────►│ │ │
(later) │ MariaDB (fla_event) │
└─────────────────────────────────────────────────────────┘
2.2 Key decisions (locked)
| # | Decision | Value |
|---|
| D1 | Stack | Plain PHP 8.3, modular (src/Core + src/Modules/*), no framework |
| D2 | DB | MariaDB fla_event (installed host) |
| D3 | Deployment | Subdomain https://event.production1.jugaar.ai on plain 443 |
| D4 | Branding | DB settings table, edited in Control Panel, defaults until configured |
| D5 | AI | Provider gateway; default driver MiniMax platform model minimax-m3; mock until keyed |
| D6 | LinkedIn | OIDC client; mock fixture in dev |
| D7 | WhatsApp | Abstract gateway; Null driver (logged no-op) until provider + keys |
| D8 | Share cards | GD driver now; AI image driver reserved (ApiCardDriver) |
| D9 | Voting math | 70% aggregate public + 30% board; admin veto absolute (BR-005) |
| D10 | Docs | Repo .md files are the single source; /docs renders them as webpages |
| D11 | Charts | Chart.js via CDN + internal JSON endpoints (no build step); ASCII/empty-state fallback without JS |
2.3 Assumptions & constraints
- One person = one registration per event (BR-001); identity anchor is the LinkedIn profile.
- Voters must authenticate with LinkedIn (other providers may be added later).
- WhatsApp number is mandatory for award/GoH registrations (BR-007).
- The platform must keep working without external keys (NFR-08 mock mode).
- Every sensitive action is audit-logged (NFR-03).
3. Actors
| Actor | Description | Access |
|---|
| Guest | Unauthenticated visitor | Landing, docs, public results, vote links |
| Visitor | Registered attendee | "I'm visiting" card, event info, dashboard-lite |
| Applicant | Award candidate in one category | Own votes + voters, AI interview, tickets, share cards |
| Voter | Authenticated via LinkedIn | Cast vote (+ optional reason) per rules |
| Judge | Assigned reviewer | Review board: profiles, AI stats, approve/disapprove |
| GoH | C-level guest presenting awards | Approval-based registration; profile |
| Staff | Team support | Manual entries, tickets, mass comms, shared dashboards |
| Admin | Owner | Everything incl. toggles, categories, veto, finalize, audit UI |
| System/AI | Automated actors | AI marking, badge generation, notifications, announcements |
4. Business rules
| ID | Rule |
|---|
| BR-001 | One person may hold exactly one registration per event (DB unique (event_id, user_id)). |
| BR-002 | An award applicant cannot also be Guest of Honor in the same event (and vice versa). |
| BR-003 | Voting default: one vote per LinkedIn profile per candidate; admin may relax/change per event — logged. |
| BR-004 | Final score = 70% public-vote share + 30% board score, normalized per category. |
| BR-005 | Admin veto is absolute: admin may exclude any applicant at any stage; audit-logged with reason. |
| BR-006 | An applicant sees own vote count and voter identities only — never others'. Only admin sees the global leaderboard before finalization. |
| BR-007 | WhatsApp number is required for award and GoH registrations. |
| BR-008 | Registration prefers LinkedIn OAuth; voters must authenticate with LinkedIn. |
| BR-009 | Winners are announced by the system only after admin approval + audit lock. |
| BR-010 | AI interview: category-scoped questions drawn randomly from the active bank; answers AI-marked; stats attached to the applicant. |
5. Functional requirements
| ID | Requirement | Module |
|---|
| FR-0101 | Admin edits branding: name, logo, tagline, primary color, favicon → live everywhere | Branding |
| FR-0102 | Admin toggles features on/off (per module) without deploys | Branding/Admin |
| FR-0103 | All repository .md docs rendered as organized webpages under /docs | Docs |
| FR-0104 | Campaign texts for share cards edited in admin (placeholders {event} {name} {category}) | Branding |
| FR-0105 | Base URL = subdomain on plain 443; absolute_url() used for share links | Core |
FR-0200 — Events & Categories
| ID | Requirement |
|---|
| FR-0201 | CRUD events: name, type (normal / ai / award), dates, venue, status lifecycle |
| FR-0202 | CRUD award categories per event: name, description, show/hide (is_active), sort order |
| FR-0203 | Categories shown publicly only when active; admin chooses what displays |
FR-0300 — Registration (v0.4.0)
| ID | Requirement |
|---|
| FR-0301 | Two public entry paths: "Register as Visitor" and "Apply for an Award" (separate links OK) |
| FR-0302 | LinkedIn OAuth signup preferred; system captures id, name, email, picture |
| FR-0303 | Fallback basic form: name, email, WhatsApp (+ passwordless) |
| FR-0304 | Award flow: pick category → info → WhatsApp → photo upload → AI interview |
| FR-0305 | GoH flow: title/company/bio/photo; approval-based; staff may enter manually (incl. government guests) |
| FR-0306 | Staff manual entry "on behalf of" for any type (entered_by_user_id) |
| FR-0307 | Enforce BR-001/BR-002/BR-007 with clear errors |
| FR-0308 | Photo stored under storage/uploads, bound to registration |
FR-0400 — AI Engine (v0.5.0)
| ID | Requirement |
|---|
| FR-0401 | Question bank per category, admin-managed, active flag |
| FR-0402 | Interview session: N random active questions (default 5), one at a time |
| FR-0403 | Each answer AI-marked (score 0–100 + feedback); session total + summary |
| FR-0404 | Stats shown on applicant dashboard + judges board |
| FR-0405 | Non-AI ("normal") events skip the interview entirely (normal registration OK) |
| FR-0406 | Provider-swappable gateway; mock mode until key; cost/logging of calls |
FR-0500 — Public Voting (v0.6.0)
| ID | Requirement |
|---|
| FR-0501 | Applicant gets personal share link + dashboard to copy it |
| FR-0502 | Vote page: candidate card (photo, name, category, AI summary if enabled) |
| FR-0503 | Voter authenticates via LinkedIn before voting (BR-008) |
| FR-0504 | Optional one-line reason ("what is this person good at") — toggleable per event |
| FR-0505 | Enforce voting restrictions; friendly errors; duplicate attempt shown as already-voted |
| FR-0506 | 70/30 weighting engine computes blended score on demand (BR-004) |
| FR-0507 | Admin veto action UI (BR-005) with mandatory reason → audit |
| FR-0508 | Applicant sees own votes + voters (BR-006); public leaderboard optional/admin-only pre-final |
FR-0600 — Share Cards (v0.6.0)
| ID | Requirement |
|---|
| FR-0601 | Generate PNG cards: applied / visiting / winner from photo + campaign text |
| FR-0602 | GD driver default; AI driver pluggable (cards.driver) |
| FR-0603 | Card + share link packaged for LinkedIn/FB posting (Open Graph tags on vote page) |
FR-0700 — Judges Panel (v0.7.0)
| ID | Requirement |
|---|
| FR-0701 | Admin creates judge accounts + assigns event/category scope |
| FR-0702 | Judges board: list contestants in scope w/ profile, photo, AI stats, media |
| FR-0703 | Judge decision per contestant: approve / disapprove / needs info + notes |
| FR-0704 | Compiled report → main admin (per category: votes, AI score, judge tallies) |
FR-0800 — Dashboards (v0.8.0, starter in v0.2.0)
| ID | Requirement |
|---|
| FR-0801 | Applicant: votes over time (chart), voter list (own), AI results, notifications, tickets, share cards |
| FR-0802 | Staff: queues (tickets, pending manual entries), comms tools |
| FR-0803 | Admin: comprehensive — registrations, votes, AI, judges, comms, audit, toggles |
| FR-0804 | Reminders/notifications surface on dashboards (FR-1200) |
| FR-0805 | Admin graphs: votes-over-time, registration funnel, per-category vote shares, top-10 candidates, AI score distribution — Chart.js (CDN) fed by internal JSON endpoints |
| FR-0806 | Admin stats coverage: KPI cards with trend deltas (7d/30d) — users by role, registrations by type/status, votes cast, AI interviews run, judge reviews done, tickets by status/age, comms delivery rates — all filterable by event/category/date |
| FR-0807 | Audit tab on admin dashboard: live audit-log feed with filters (actor, action, entity, date) — FR-1102 UI |
FR-0400 — AI Engine (v0.5.0)
| ID | Requirement |
|---|
| FR-0401 | Question bank per category, admin-managed, active flag |
| FR-0402 | Interview session: N random active questions (default 5), one at a time |
| FR-0403 | Each answer AI-marked (score 0–100 + feedback); session total + summary |
| FR-0404 | Stats shown on applicant dashboard + judges board |
| FR-0405 | Non-AI ("normal") events skip the interview entirely (normal registration OK) |
| FR-0406 | Provider-swappable gateway; mock mode until key; cost/logging of calls |
| FR-0407 | AI admin insights (v0.5.0+): natural-language summaries per applicant ("judge-ready brief"), weekly event summary for admin ("what changed this week"), vote-anomaly flags (bursts/single-IP clustering summarized by AI). All mock-compatible via the AI gateway |
FR-0900 — Ticketing & Mass Comms (v0.8.0)
| ID | Requirement |
|---|
| FR-0901 | Applicants/visitors open tickets; staff reply in thread; statuses tracked |
| FR-0902 | Mass communication to audiences (all/visitors/applicants/goh/category/status) |
| FR-0903 | Channels: email first; WhatsApp when provider keys arrive (numbers already collected) |
| FR-0904 | Public media/gallery sections per event |
FR-1000 — Winners (v0.9.0)
| ID | Requirement |
|---|
| FR-1001 | Finalize per category: blend 70/30 + judge results → shortlist |
| FR-1002 | Admin approves → audit lock → system publishes winners (BR-009) |
| FR-1003 | Winner badges + "Winner" share cards auto-generated |
| FR-1004 | Public results page + notifications to winners |
FR-1100 — Audit (v0.9.0)
| ID | Requirement |
|---|
| FR-1101 | Append-only audit log (actor, action, entity, ip, meta, time) |
| FR-1102 | Admin filter/search UI; no edit/delete of audit rows |
FR-1200 — Notifications (v0.8.0)
| ID | Requirement |
|---|
| FR-1201 | In-app notifications (vote milestones, ticket replies, results) |
| FR-1202 | Reminder surface on dashboards; channel escalation (WhatsApp/email) later |
6. Use-case library
Full step-by-step flows live in docs/WORKFLOWS/. Here: the contract version
(preconditions → main flow → alternates → postconditions).
- Pre: admin logged in.
- Main: open Control Panel → edit name/logo/colors/campaign texts → save → changes live immediately; toggle module features on/off.
- Alt: restore defaults; invalid color rejected.
- Post: settings persisted; audit entry written.
UC-02 Manage event & categories — Admin
- Pre: admin. Main: create event (type award) → add categories → set active/order → announce (status).
- Alt: hide category (existing applicants unaffected); edit anytime.
- Post: public listing reflects active set.
UC-03 Register as visitor — Guest → Visitor · flow
- Pre: event open for registration.
- Main: open
/register/visitor → LinkedIn sign-in (preferred) or basic form (+WhatsApp optional for visitors) → confirm → success page with "I am visiting" card → download/post to social.
- Alt: existing account → resume; event closed → waitlist message.
- Post: registration row
type=visitor; notification.
UC-04 Apply for an award — Applicant + AI · flow
- Pre: active categories; applicant passes BR-001/BR-002 checks.
- Main: pick category → LinkedIn OAuth → profile imported → basic info + WhatsApp → photo upload → AI interview (random category questions, AI-marked) → success page: personal share link + "I have applied" card.
- Alt: skip AI when event type = normal (BR: normal registration OK); re-try photo upload.
- Post: registration
type=award, status=submitted + ai_session; card PNG.
UC-05 Apply as Guest of Honor — GoH / Staff · flow
- Main:
/guest-of-honor form (title e.g. CIO, company, bio, photo, LinkedIn) → pending approval → admin approves → listed.
- Alt (manual): staff uses "enter on behalf" (govt/C-level invitees) — flagged
entered_by_user_id.
- Post: registration
type=goh, status=pending→approved; BR-002 enforced.
UC-06 Staff manual entry — Staff
- Main: staff dashboard → "Add on behalf" → choose type → fill form (+upload photo) → confirm → record marked as manual entry.
- Post: audit entry with staff actor.
UC-07 Cast a vote — Voter · flow
- Pre: share link
v/{token}; candidate approved; voting window open.
- Main: open link → LinkedIn auth → (optional) reason → Vote → confirmation + prompt to share.
- Alt: already voted (per rules) → "already voted" with change-vote policy (default: no duplicates); voting restriction relaxed mode → allowed per admin setting.
- Post: vote row; candidate count +1; notification to applicant; audit.
UC-08 AI marking & stats — System/AI
- Main: for each answer → score + feedback → session total & summary → attach to applicant → judges/admin see stats. Mock mode deterministic until key (NFR-08).
UC-09 Applicant self-view — Applicant
- Main: dashboard → own vote count, voter identities, AI stats, share link, cards, tickets, notifications.
- Alt: attempt to view others' totals → denied (BR-006).
UC-10 Judge review — Judge · flow
- Main: login → assigned scope → open contestant (profile, photo, AI stats, votes-if-enabled) → decision (approve/disapprove/needs-info) + notes → save.
- Post: per-category report aggregated for admin.
UC-11 Finalize & announce winners — Admin · flow
- Pre: voting closed; judges reported.
- Main: review blended ranking (70/30) + judges → optional veto (reason) → select winners per category → approve & lock → system announces (public page + notifications) → winner badges + cards generated → winners share.
- Post:
status=winner rows; audit-locked snapshot; public results.
UC-12 Generate share card — System
- Main: on applied/visiting/winner trigger → GD render (photo + campaign text) → stored → shown for download/share. Alt: AI driver later.
UC-13 Support ticket — Applicant ↔ Staff · flow
- Main: applicant opens ticket (subject, question, media allowed) → staff replies from queue → status flow (open→pending→answered→closed).
UC-14 Mass communication — Staff
- Main: select audience → compose (email; WhatsApp when keyed) → preview recipient count → send → per-recipient status tracked.
UC-15 Review audit log — Admin
- Main: open Audit → filter by actor/action/date → drill into entries (meta JSON).
UC-16 Admin overview & AI insights — Admin
- Pre: admin logged in (event data exists for meaningful numbers; empty states otherwise).
- Main: admin home shows KPI cards with trends (7d/30d) → graphs (votes over time, funnel, per-category shares, top candidates, AI score distribution) → tabs: Audit (live filterable feed) → AI Insights (weekly summary, applicant briefs, vote-anomaly flags) → drill into any figure to its underlying list.
- Alt: AI key missing → insights panel shows mock/sample output with "waiting for AI key" notice (NFR-08).
- Post: no writes; every query scoped to admin role and covered by the JSON feeds used for charts.
7. Non-functional requirements
| ID | Requirement |
|---|
| NFR-01 | Responsive: mobile-first; all flows usable on phones (voting especially). |
| NFR-02 | HTTPS only at https://event.production1.jugaar.ai, plain 443, HSTS once live. |
| NFR-03 | Auditability: sensitive actions (login, vote, veto, finalize, manual entries, comms) logged. |
| NFR-04 | Performance: p95 page load < 2 s on shared hosting; voting/cards cached where possible. |
| NFR-05 | Security: PDO prepared statements, output escaping, CSRF tokens on forms, rate-limit votes/login. |
| NFR-06 | Privacy: minimal PII; LinkedIn data only id/name/email/picture; voter list visible to the candidate only (BR-006). |
| NFR-07 | Extensibility: modules self-contained under src/Modules/<Name>; core stays lean. |
| NFR-08 | Degradability: full flow works in mock mode without any external API key. |
| NFR-09 | Availability: graceful error pages; AI/LinkedIn outage never blocks browsing. |
| NFR-10 | Backups: nightly DB dump (ops; procedure in HELP.md). |
8. Integrations
| Service | Purpose | Mode now | Needed from owner |
|---|
| MiniMax platform (platform.minimax.io) | AI Q&A marking, summaries, insights | Mock | API key (+ confirm exact m3 model id & base URL) |
| LinkedIn OIDC | Registration + voter verification | Mock | App client id/secret (developer.linkedin.com) |
| WhatsApp (Meta Cloud API or Twilio) | Comms to collected numbers | Null (logged) | Provider choice + keys |
| AI image API (TBD) | Fancy share cards | GD fallback | API key → enables cards.driver=ai |
9. Data model (overview)
settings · feature_toggles · users (admin/staff/judge/applicant/visitor/goh/voter) · events · categories · registrations (+public_token, entered_by_user_id) · question_bank · ai_sessions · ai_answers · votes · judge_assignments · judge_reviews · tickets · ticket_messages · audit_log · notifications · communications · communication_recipients
Key uniques: users.linkedin_id, users.email, registrations(event_id,user_id) (BR-001/002), votes(registration_id,voter_user_id) (BR-003), judge_reviews(registration_id,judge_user_id). DDL: database/migrations/001–009.
10. Open questions
- Confirm MiniMax m3 model id + exact base URL; obtain API key (platform.minimax.io).
- LinkedIn App credentials (OIDC scopes
openid profile email).
- WhatsApp provider: Meta Cloud API vs Twilio.
- AI image API for share cards (later).
- Email sender (SMTP host / transactional service) for comms & notifications.
- Allowed vote-change policy (default: votes are final).
- Public leaderboard visibility pre-finalization (default: admin-only, BR-006).
Answers get recorded here + in TODO.md when provided.
11. Version history
| Version | Date | Change |
|---|
| 0.1.0 | 2026-08-29 | Initial SRS (docs + scaffold baseline). |