Skip to content

Commit 29af105

Browse files
committed
docs: add design spec for SIS deep-link feature (#78)
1 parent ae971e4 commit 29af105

1 file changed

Lines changed: 119 additions & 0 deletions

File tree

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# SIS Deep-Link from Student Detail View — Design Spec
2+
3+
**Date:** 2026-04-01
4+
**Issue:** #78
5+
**Scope:** Proof of concept / demo
6+
**Branch:** `feature/sis-deep-link` (from `main`)
7+
8+
## Summary
9+
10+
Add a FERPA-compliant "Open in SIS" button to the student detail page that constructs a deep-link URL to the institution's Student Information System. Identity resolution happens server-side — the browser never receives the SIS student ID. This POC validates the architecture with sample data and a configurable demo URL.
11+
12+
## Architecture
13+
14+
```
15+
Browser (student detail page)
16+
17+
├─ GET /api/students/[guid]/sis-link
18+
│ │
19+
│ ├─ Role check (x-user-role header, admin/advisor/ir only)
20+
│ ├─ Query guid_sis_map table for sis_id
21+
│ ├─ Build URL: SIS_BASE_URL?SIS_ID_PARAM=<sis_id>
22+
│ ├─ Append audit log entry (GUID + role, never sis_id)
23+
│ └─ Return { url } or 404
24+
25+
└─ window.open(url, "_blank")
26+
```
27+
28+
The SIS ID never reaches the client. The audit log records access by GUID and role only.
29+
30+
## 1. Database — `guid_sis_map` Table
31+
32+
Table in the existing Postgres database:
33+
34+
```sql
35+
CREATE TABLE guid_sis_map (
36+
student_guid TEXT PRIMARY KEY,
37+
sis_id TEXT NOT NULL
38+
);
39+
```
40+
41+
A seed script picks ~20 random GUIDs from `student_level_with_predictions` and assigns fake SIS IDs (`BSC-100001` through `BSC-100020`). This demonstrates both the happy path (button works) and the fallback (no mapping → disabled button with tooltip).
42+
43+
## 2. Environment Configuration
44+
45+
Two server-only env vars in `.env.local` (no `NEXT_PUBLIC_` prefix):
46+
47+
```env
48+
# SIS deep-link (leave blank to hide the button entirely)
49+
SIS_BASE_URL=https://sis-demo.example.com/students
50+
# Query param name the SIS expects (default: id)
51+
SIS_ID_PARAM=id
52+
```
53+
54+
When `SIS_BASE_URL` is unset, the API returns 404 and the UI hides the button — the feature is effectively disabled.
55+
56+
## 3. API Route — `GET /api/students/[guid]/sis-link`
57+
58+
**File:** `codebenders-dashboard/app/api/students/[guid]/sis-link/route.ts`
59+
60+
**Behavior:**
61+
62+
| Condition | Response |
63+
|-----------|----------|
64+
| `SIS_BASE_URL` unset | 404 `{ url: null }` |
65+
| Role not in `admin, advisor, ir` | 403 `{ error: "Forbidden" }` |
66+
| No mapping in `guid_sis_map` | 404 `{ url: null }` |
67+
| Mapping found | 200 `{ url: "https://sis-demo.example.com/students?id=BSC-100001" }` |
68+
69+
**Role gating:** Reads `x-user-role` header injected by existing middleware. No changes to `lib/roles.ts` needed — the `/api/students` prefix is already gated to `admin`, `advisor`, `ir`.
70+
71+
**Audit logging:** Appends to `logs/query-history.jsonl`:
72+
73+
```json
74+
{ "event": "sis_link_accessed", "guid": "<guid>", "role": "advisor", "timestamp": "2026-04-01T12:00:00.000Z" }
75+
```
76+
77+
The `sis_id` is never logged.
78+
79+
## 4. UI — "Open in SIS" Button
80+
81+
**File:** `codebenders-dashboard/app/students/[guid]/page.tsx`
82+
83+
**Placement:** In the student header card, alongside the existing alert/readiness badges.
84+
85+
**Visibility logic** (determined by the API response on page load):
86+
87+
| API result | Button state |
88+
|------------|--------------|
89+
| 200 with URL | Visible and clickable — opens URL in new tab |
90+
| 404 (no mapping) | Visible but disabled — tooltip: "No SIS record linked for this student" |
91+
| 403 or fetch error | Hidden entirely |
92+
93+
Uses existing `Button` from shadcn/ui and `ExternalLink` icon from lucide-react. No new component file needed.
94+
95+
## Files Changed
96+
97+
| File | Change |
98+
|------|--------|
99+
| `operations/seed_guid_sis_map.py` | New — seed script for demo data |
100+
| `codebenders-dashboard/.env.local` | Add `SIS_BASE_URL`, `SIS_ID_PARAM` |
101+
| `codebenders-dashboard/app/api/students/[guid]/sis-link/route.ts` | New — server-side SIS URL builder |
102+
| `codebenders-dashboard/app/students/[guid]/page.tsx` | Add "Open in SIS" button with fetch logic |
103+
104+
## Out of Scope
105+
106+
- Row-Level Security on `guid_sis_map` (not needed for POC)
107+
- Real institution SIS integration (demo uses placeholder URL)
108+
- `.env.example` file (can be added later)
109+
- Supabase Edge Function alternative
110+
111+
## Acceptance Criteria (from issue #78)
112+
113+
- [x] `SIS_BASE_URL` env var controls whether the button appears (hidden when blank)
114+
- [x] Button only visible to Advisor + IR + Admin roles
115+
- [x] SIS ID is never stored in `student_level_with_predictions` or `llm_recommendations`
116+
- [x] SIS ID is never returned by any public API endpoint (only pre-built URL returned)
117+
- [x] Every deep-link access is logged (GUID + role, not SIS ID)
118+
- [x] Button shows a graceful fallback if no mapping exists for a GUID
119+
- [x] Works with any SIS that accepts a query-param student ID in a URL

0 commit comments

Comments
 (0)