Add 504 / IEP plans with tiered confidentiality

Plans, accommodations, related services, annual goals with progress
monitoring, meetings/team, and signed documents — plus a compliance
list and dashboard alerts for annual-review and triennial re-evaluation
dates (overdue in red, due-within-30-days in amber).

Access is tiered because special-education records are need-to-know
under FERPA:

  FULL  admin, the plan's case manager, the student's parents
  IMPL  the above, plus any teacher of the student — accommodations
        and services only, never eligibility or meeting notes

RLS is row-level and every app role is the same Postgres role
(`authenticated`), so column grants cannot separate the tiers. The
split is therefore physical: confidential fields live in plan_details,
plan_goals, plan_meetings and plan_documents rather than as columns on
student_plans.

The UI asks the database which tier applies via the same predicates the
policies use (can_view_plan_full / can_edit_plan) instead of re-deriving
the rules client-side.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 09:14:40 -04:00
co-authored by Claude Opus 5
parent 872eacefc5
commit 9d7d18669d
9 changed files with 2905 additions and 3 deletions
+444
View File
@@ -620,6 +620,358 @@ export type Database = {
},
]
}
plan_accommodations: {
Row: {
category: string
created_at: string
description: string
id: string
plan_id: string
setting: string | null
sort_order: number
}
Insert: {
category?: string
created_at?: string
description: string
id?: string
plan_id: string
setting?: string | null
sort_order?: number
}
Update: {
category?: string
created_at?: string
description?: string
id?: string
plan_id?: string
setting?: string | null
sort_order?: number
}
Relationships: [
{
foreignKeyName: "plan_accommodations_plan_id_fkey"
columns: ["plan_id"]
isOneToOne: false
referencedRelation: "student_plans"
referencedColumns: ["id"]
},
]
}
plan_details: {
Row: {
confidential_notes: string | null
consent_date: string | null
eligibility_category: string | null
eligibility_notes: string | null
evaluation_summary: string | null
last_evaluation_date: string | null
parent_concerns: string | null
plan_id: string
student_strengths: string | null
updated_at: string
}
Insert: {
confidential_notes?: string | null
consent_date?: string | null
eligibility_category?: string | null
eligibility_notes?: string | null
evaluation_summary?: string | null
last_evaluation_date?: string | null
parent_concerns?: string | null
plan_id: string
student_strengths?: string | null
updated_at?: string
}
Update: {
confidential_notes?: string | null
consent_date?: string | null
eligibility_category?: string | null
eligibility_notes?: string | null
evaluation_summary?: string | null
last_evaluation_date?: string | null
parent_concerns?: string | null
plan_id?: string
student_strengths?: string | null
updated_at?: string
}
Relationships: [
{
foreignKeyName: "plan_details_plan_id_fkey"
columns: ["plan_id"]
isOneToOne: true
referencedRelation: "student_plans"
referencedColumns: ["id"]
},
]
}
plan_documents: {
Row: {
created_at: string
file_path: string
id: string
plan_id: string
title: string | null
uploaded_by: string | null
}
Insert: {
created_at?: string
file_path: string
id?: string
plan_id: string
title?: string | null
uploaded_by?: string | null
}
Update: {
created_at?: string
file_path?: string
id?: string
plan_id?: string
title?: string | null
uploaded_by?: string | null
}
Relationships: [
{
foreignKeyName: "plan_documents_plan_id_fkey"
columns: ["plan_id"]
isOneToOne: false
referencedRelation: "student_plans"
referencedColumns: ["id"]
},
]
}
plan_goal_progress: {
Row: {
created_at: string
date: string
goal_id: string
id: string
narrative: string | null
recorded_by: string | null
value: number | null
}
Insert: {
created_at?: string
date?: string
goal_id: string
id?: string
narrative?: string | null
recorded_by?: string | null
value?: number | null
}
Update: {
created_at?: string
date?: string
goal_id?: string
id?: string
narrative?: string | null
recorded_by?: string | null
value?: number | null
}
Relationships: [
{
foreignKeyName: "plan_goal_progress_goal_id_fkey"
columns: ["goal_id"]
isOneToOne: false
referencedRelation: "plan_goals"
referencedColumns: ["id"]
},
]
}
plan_goals: {
Row: {
area: string | null
baseline: string | null
created_at: string
id: string
plan_id: string
sort_order: number
statement: string
status: Database["public"]["Enums"]["plan_goal_status"]
target_criteria: string | null
target_date: string | null
target_value: number | null
unit: string | null
updated_at: string
}
Insert: {
area?: string | null
baseline?: string | null
created_at?: string
id?: string
plan_id: string
sort_order?: number
statement: string
status?: Database["public"]["Enums"]["plan_goal_status"]
target_criteria?: string | null
target_date?: string | null
target_value?: number | null
unit?: string | null
updated_at?: string
}
Update: {
area?: string | null
baseline?: string | null
created_at?: string
id?: string
plan_id?: string
sort_order?: number
statement?: string
status?: Database["public"]["Enums"]["plan_goal_status"]
target_criteria?: string | null
target_date?: string | null
target_value?: number | null
unit?: string | null
updated_at?: string
}
Relationships: [
{
foreignKeyName: "plan_goals_plan_id_fkey"
columns: ["plan_id"]
isOneToOne: false
referencedRelation: "student_plans"
referencedColumns: ["id"]
},
]
}
plan_meeting_attendees: {
Row: {
attended: boolean
created_at: string
id: string
meeting_id: string
name: string
team_role: string | null
user_id: string | null
}
Insert: {
attended?: boolean
created_at?: string
id?: string
meeting_id: string
name: string
team_role?: string | null
user_id?: string | null
}
Update: {
attended?: boolean
created_at?: string
id?: string
meeting_id?: string
name?: string
team_role?: string | null
user_id?: string | null
}
Relationships: [
{
foreignKeyName: "plan_meeting_attendees_meeting_id_fkey"
columns: ["meeting_id"]
isOneToOne: false
referencedRelation: "plan_meetings"
referencedColumns: ["id"]
},
]
}
plan_meetings: {
Row: {
created_at: string
created_by: string | null
id: string
location: string | null
meeting_date: string
meeting_type: Database["public"]["Enums"]["plan_meeting_type"]
notes: string | null
outcome: string | null
plan_id: string
}
Insert: {
created_at?: string
created_by?: string | null
id?: string
location?: string | null
meeting_date: string
meeting_type?: Database["public"]["Enums"]["plan_meeting_type"]
notes?: string | null
outcome?: string | null
plan_id: string
}
Update: {
created_at?: string
created_by?: string | null
id?: string
location?: string | null
meeting_date?: string
meeting_type?: Database["public"]["Enums"]["plan_meeting_type"]
notes?: string | null
outcome?: string | null
plan_id?: string
}
Relationships: [
{
foreignKeyName: "plan_meetings_plan_id_fkey"
columns: ["plan_id"]
isOneToOne: false
referencedRelation: "student_plans"
referencedColumns: ["id"]
},
]
}
plan_services: {
Row: {
created_at: string
delivery: string | null
end_date: string | null
id: string
location: string | null
minutes_per_session: number | null
period: string
plan_id: string
provider_id: string | null
provider_name: string | null
service_type: string
sessions_per_period: number | null
start_date: string | null
}
Insert: {
created_at?: string
delivery?: string | null
end_date?: string | null
id?: string
location?: string | null
minutes_per_session?: number | null
period?: string
plan_id: string
provider_id?: string | null
provider_name?: string | null
service_type: string
sessions_per_period?: number | null
start_date?: string | null
}
Update: {
created_at?: string
delivery?: string | null
end_date?: string | null
id?: string
location?: string | null
minutes_per_session?: number | null
period?: string
plan_id?: string
provider_id?: string | null
provider_name?: string | null
service_type?: string
sessions_per_period?: number | null
start_date?: string | null
}
Relationships: [
{
foreignKeyName: "plan_services_plan_id_fkey"
columns: ["plan_id"]
isOneToOne: false
referencedRelation: "student_plans"
referencedColumns: ["id"]
},
]
}
profiles: {
Row: {
created_at: string
@@ -744,6 +1096,59 @@ export type Database = {
},
]
}
student_plans: {
Row: {
case_manager_id: string | null
created_at: string
created_by: string | null
effective_date: string | null
end_date: string | null
id: string
next_annual_review_date: string | null
next_reevaluation_date: string | null
plan_type: Database["public"]["Enums"]["plan_type"]
status: Database["public"]["Enums"]["plan_status"]
student_id: string
updated_at: string
}
Insert: {
case_manager_id?: string | null
created_at?: string
created_by?: string | null
effective_date?: string | null
end_date?: string | null
id?: string
next_annual_review_date?: string | null
next_reevaluation_date?: string | null
plan_type: Database["public"]["Enums"]["plan_type"]
status?: Database["public"]["Enums"]["plan_status"]
student_id: string
updated_at?: string
}
Update: {
case_manager_id?: string | null
created_at?: string
created_by?: string | null
effective_date?: string | null
end_date?: string | null
id?: string
next_annual_review_date?: string | null
next_reevaluation_date?: string | null
plan_type?: Database["public"]["Enums"]["plan_type"]
status?: Database["public"]["Enums"]["plan_status"]
student_id?: string
updated_at?: string
}
Relationships: [
{
foreignKeyName: "student_plans_student_id_fkey"
columns: ["student_id"]
isOneToOne: false
referencedRelation: "students"
referencedColumns: ["id"]
},
]
}
students: {
Row: {
agreement_signed_by: string | null
@@ -906,6 +1311,13 @@ export type Database = {
[_ in never]: never
}
Functions: {
can_edit_goal: { Args: { _goal: string }; Returns: boolean }
can_edit_meeting: { Args: { _meeting: string }; Returns: boolean }
can_edit_plan: { Args: { _plan: string }; Returns: boolean }
can_view_goal: { Args: { _goal: string }; Returns: boolean }
can_view_meeting: { Args: { _meeting: string }; Returns: boolean }
can_view_plan_full: { Args: { _plan: string }; Returns: boolean }
can_view_plan_impl: { Args: { _plan: string }; Returns: boolean }
current_user_has_role: {
Args: { _role: Database["public"]["Enums"]["app_role"] }
Returns: boolean
@@ -928,6 +1340,21 @@ export type Database = {
attendance_status: "present" | "absent" | "late" | "excused"
ledger_category: "tuition" | "late_pickup" | "activity" | "other"
ledger_kind: "charge" | "payment"
plan_goal_status:
| "not_started"
| "in_progress"
| "met"
| "not_met"
| "discontinued"
plan_meeting_type:
| "initial"
| "annual_review"
| "reevaluation"
| "eligibility"
| "amendment"
| "other"
plan_status: "draft" | "active" | "archived"
plan_type: "iep" | "section_504"
}
CompositeTypes: {
[_ in never]: never
@@ -1062,6 +1489,23 @@ export const Constants = {
attendance_status: ["present", "absent", "late", "excused"],
ledger_category: ["tuition", "late_pickup", "activity", "other"],
ledger_kind: ["charge", "payment"],
plan_goal_status: [
"not_started",
"in_progress",
"met",
"not_met",
"discontinued",
],
plan_meeting_type: [
"initial",
"annual_review",
"reevaluation",
"eligibility",
"amendment",
"other",
],
plan_status: ["draft", "active", "archived"],
plan_type: ["iep", "section_504"],
},
},
} as const