Goal: Build a tiny “Manager Alerts” slice inspired by Optimo. Keep scope small; quality and clarity beat breadth.
GET /api/alerts?manager_id=<id>&scope=direct|subtree[&severity=high,medium&q=alex[&status=open,dismissed]]manager_idrequired;scopedefaults todirectif omitted.- Returns a JSON array of alerts for employees determined by scope.
- Sort by
created_atdescending (ties:idascending).
POST /api/alerts/{id}/dismiss- Sets
status="dismissed"and returns the updated alert. Idempotent — dismissing an already-dismissed alert returns200with the unchanged resource.
- Sets
Return each alert in this shape (array of these objects):
{
"id": "A1",
"employee": { "id": "E3", "name": "Jordan Lee" },
"severity": "high",
"category": "retention",
"created_at": "2025-09-01T09:00:00Z",
"status": "open"
}Use in-memory data, a JSON file, or SQLite. Seed ~20 employees with fields id, name, reports_to (nullable), and ~30 alerts with fields id, employee_id, severity in {low, medium, high}, category, created_at (ISO-8601 UTC), status in {open, dismissed}.
Support direct reports and full subtree. Handle cycles or missing managers gracefully (ignore/break cycles using a visited set). Do not include the manager’s own alerts in the results.
severity: comma-separated list; allowed values:low,medium,high. Unknown →400 { "detail": "invalid severity" }.status: comma-separated list; allowed values:open,dismissed. Unknown →400 { "detail": "invalid status" }.q: case-insensitive substring match on employee name.scope: allowed values:direct,subtree. Unknown →400 { "detail": "invalid scope" }.- Defaults:
scope=directif omitted;statusdefaults to all (open + dismissed).
- Unknown
manager_id→404 { "detail": "manager not found" }. - Unknown alert
idon dismiss →404 { "detail": "alert not found" }.
- Time format: RFC3339/ISO-8601 UTC with trailing
Z. - Dev CORS: use
django-cors-headersand either allow all origins (dev only) or explicitly whitelist common ports (e.g.,http://localhost:3000,http://localhost:5173). Literalhttp://localhost:*is not directly supported without a regex.
Implement the two endpoints above with the response schema and rules. Keep code straightforward and typed where helpful.
Build a single page that:
- Calls the endpoint and displays a table: employee name, category, severity (color chip), status,
created_at. - Includes a toggle for scope (
directvssubtree, defaultdirect). - Filters by severity and text search (employee name).
- Bonus: Persist filters in the URL query string.
- Wire up “dismiss” with optimistic update (revert on failure).
- Use any OSS component library; keep components simple and accessible.
- Backend (pytest): at least one unit test for subtree traversal (cycle prevention) and one for the dismiss mutation (idempotency).
- Frontend (Jest/Vitest): at least one test that filters to “high” severity and one that verifies optimistic update rollback on API failure.
You may copy the following seed_data.json as-is. It includes a cycle among E6–E8 for traversal tests.
Employees
[
{"id":"E1","name":"Taylor Reed","reports_to":null},
{"id":"E2","name":"Alex Morgan","reports_to":"E1"},
{"id":"E3","name":"Jordan Lee","reports_to":"E2"},
{"id":"E4","name":"Casey Kim","reports_to":"E2"},
{"id":"E5","name":"Riley Chen","reports_to":"E3"},
{"id":"E6","name":"Sam Patel","reports_to":"E7"},
{"id":"E7","name":"Jamie Singh","reports_to":"E8"},
{"id":"E8","name":"Morgan Diaz","reports_to":"E6"},
{"id":"E9","name":"Avery Brooks","reports_to":"E2"},
{"id":"E10","name":"Quinn Park","reports_to":"E9"}
]Alerts
[
{"id":"A1","employee_id":"E3","severity":"high","category":"retention","created_at":"2025-09-01T09:00:00Z","status":"open"},
{"id":"A2","employee_id":"E4","severity":"medium","category":"engagement","created_at":"2025-09-02T09:00:00Z","status":"open"},
{"id":"A3","employee_id":"E5","severity":"low","category":"workload","created_at":"2025-09-03T09:00:00Z","status":"open"},
{"id":"A4","employee_id":"E5","severity":"high","category":"retention","created_at":"2025-09-04T09:00:00Z","status":"open"},
{"id":"A5","employee_id":"E9","severity":"medium","category":"engagement","created_at":"2025-09-05T09:00:00Z","status":"open"},
{"id":"A6","employee_id":"E10","severity":"high","category":"retention","created_at":"2025-09-06T09:00:00Z","status":"dismissed"},
{"id":"A7","employee_id":"E3","severity":"low","category":"workload","created_at":"2025-09-07T09:00:00Z","status":"open"},
{"id":"A8","employee_id":"E6","severity":"medium","category":"engagement","created_at":"2025-09-08T09:00:00Z","status":"open"},
{"id":"A9","employee_id":"E7","severity":"high","category":"retention","created_at":"2025-09-09T09:00:00Z","status":"open"},
{"id":"A10","employee_id":"E8","severity":"low","category":"workload","created_at":"2025-09-10T09:00:00Z","status":"open"},
{"id":"A11","employee_id":"E4","severity":"high","category":"retention","created_at":"2025-09-11T09:00:00Z","status":"open"},
{"id":"A12","employee_id":"E9","severity":"low","category":"workload","created_at":"2025-09-12T09:00:00Z","status":"open"},
{"id":"A13","employee_id":"E10","severity":"medium","category":"engagement","created_at":"2025-09-13T09:00:00Z","status":"open"},
{"id":"A14","employee_id":"E2","severity":"low","category":"workload","created_at":"2025-09-14T09:00:00Z","status":"open"}
]- Manager: E2 (direct reports: E3, E4, E9; subtree adds E5, E10)
GET /api/alerts?manager_id=E2&scope=direct→ alerts for E3, E4, E9 only- Counts by severity: high 2 (A1, A11), medium 2 (A2, A5), low 2 (A7, A12)
GET /api/alerts?manager_id=E2&scope=subtree→ adds E5, E10; status defaults to all (open + dismissed)- Counts by severity: high 4 (A1, A4, A6, A11), medium 3 (A2, A5, A13), low 3 (A3, A7, A12)
GET /api/alerts?manager_id=E2&scope=subtree&status=open&severity=high→ A1, A4, A11
- Manager: E7 (cycle with E6 and E8)
GET /api/alerts?manager_id=E7&scope=subtree→ must terminate; expected employees {E6, E8}; alerts {A8, A10}
- Dismissal
POST /api/alerts/A1/dismiss→ returns A1 withstatus:"dismissed"; subsequent calls return200with unchanged resource.
A public Git repository with a short README covering:
- Setup & run (2–3 commands) and test commands.
- Time spent and what you cut for time.
- “How I used AI/LLMs” (if at all).
- Self-review: why your solution is good, what you’d improve next.
- Optional: Deployed URLs (e.g., Vercel/Netlify for UI; Railway/Render/Fly for API).