From cee545b17454520efd00ece341e505e7d0dceee8 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 22 Aug 2026 23:11:04 +0000 Subject: [PATCH] Terminal --- supabase/migrations/20260807001500_ratios.sql | 269 ------------------ 1 file changed, 269 deletions(-) delete mode 100644 supabase/migrations/20260807001500_ratios.sql diff --git a/supabase/migrations/20260807001500_ratios.sql b/supabase/migrations/20260807001500_ratios.sql deleted file mode 100644 index 714c5c6..0000000 --- a/supabase/migrations/20260807001500_ratios.sql +++ /dev/null @@ -1,269 +0,0 @@ --- Student-to-staff ratios — spec section 20. --- --- Ratios are a licensing matter and vary by program, age and campus, so the --- required numbers are rows, never constants in code. No default rule is --- seeded: an invented ratio that looks authoritative is worse than an empty --- table, because staff would trust it. --- --- Staff presence needs its own record. Student attendance exists; nothing --- tracked whether an adult was in the building, which is half of every ratio. - --- ============================================================================ --- 1. STAFF ATTENDANCE --- ============================================================================ - -CREATE TABLE IF NOT EXISTS public.staff_attendance ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, - campus_id UUID NOT NULL REFERENCES public.campuses(id) ON DELETE CASCADE, - date DATE NOT NULL, - - status TEXT NOT NULL DEFAULT 'present', - check_in_at TIMESTAMPTZ, - check_out_at TIMESTAMPTZ, - -- Counts toward ratio: a site administrator may be present but not - -- ratio-bearing, depending on licensing. - counts_toward_ratio BOOLEAN NOT NULL DEFAULT TRUE, - classroom_id UUID REFERENCES public.classes(id) ON DELETE SET NULL, - - note TEXT, - recorded_by UUID REFERENCES auth.users(id) ON DELETE SET NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), - - UNIQUE (user_id, campus_id, date), - CONSTRAINT sa_status_valid CHECK (status IN ('present','absent','late','leave','training')) -); - -GRANT SELECT, INSERT, UPDATE, DELETE ON public.staff_attendance TO authenticated; -GRANT ALL ON public.staff_attendance TO service_role; -ALTER TABLE public.staff_attendance ENABLE ROW LEVEL SECURITY; -CREATE INDEX IF NOT EXISTS sta_campus_date_idx ON public.staff_attendance (campus_id, date); -CREATE INDEX IF NOT EXISTS sta_user_idx ON public.staff_attendance (user_id, date DESC); -CREATE INDEX IF NOT EXISTS sta_classroom_idx ON public.staff_attendance (classroom_id); -CREATE INDEX IF NOT EXISTS sta_recorded_by_idx ON public.staff_attendance (recorded_by); - -DROP TRIGGER IF EXISTS trg_staff_att_upd ON public.staff_attendance; -CREATE TRIGGER trg_staff_att_upd BEFORE UPDATE ON public.staff_attendance - FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); - --- ============================================================================ --- 2. RATIO RULES --- ============================================================================ - -CREATE TABLE IF NOT EXISTS public.ratio_rules ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name TEXT NOT NULL, - - campus_id UUID REFERENCES public.campuses(id) ON DELETE CASCADE, - program_id UUID REFERENCES public.campus_programs(id) ON DELETE CASCADE, - classroom_id UUID REFERENCES public.classes(id) ON DELETE CASCADE, - - -- Age band the rule governs, in months. NULL bounds are open-ended. - min_age_months INTEGER, - max_age_months INTEGER, - grade_level TEXT, - - -- The licensing number: how many students one qualifying adult may supervise. - students_per_staff NUMERIC(5,2) NOT NULL CHECK (students_per_staff > 0), - max_group_size INTEGER, - - licensing_reference TEXT, - effective_start DATE NOT NULL DEFAULT CURRENT_DATE, - effective_end DATE, - is_active BOOLEAN NOT NULL DEFAULT TRUE, - priority INTEGER NOT NULL DEFAULT 100, - notes TEXT, - - created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), - - CONSTRAINT rr_ages_ordered - CHECK (min_age_months IS NULL OR max_age_months IS NULL OR min_age_months <= max_age_months), - CONSTRAINT rr_dates_ordered - CHECK (effective_end IS NULL OR effective_start <= effective_end) -); - -GRANT SELECT, INSERT, UPDATE, DELETE ON public.ratio_rules TO authenticated; -GRANT ALL ON public.ratio_rules TO service_role; -ALTER TABLE public.ratio_rules ENABLE ROW LEVEL SECURITY; -CREATE INDEX IF NOT EXISTS rr_campus_idx ON public.ratio_rules (campus_id); -CREATE INDEX IF NOT EXISTS rr_program_idx ON public.ratio_rules (program_id); -CREATE INDEX IF NOT EXISTS rr_classroom_idx ON public.ratio_rules (classroom_id); -CREATE INDEX IF NOT EXISTS rr_active_idx ON public.ratio_rules (effective_start, priority DESC) - WHERE is_active; - -DROP TRIGGER IF EXISTS trg_rr_upd ON public.ratio_rules; -CREATE TRIGGER trg_rr_upd BEFORE UPDATE ON public.ratio_rules - FOR EACH ROW EXECUTE FUNCTION public.set_updated_at(); - --- Strictest rule wins where several match: understaffing is the failure mode --- worth guarding against, so the lowest students_per_staff breaks ties. -CREATE OR REPLACE FUNCTION public.resolve_ratio_rule( - _campus UUID, _on DATE, _age_months INTEGER DEFAULT NULL, _classroom UUID DEFAULT NULL -) -RETURNS public.ratio_rules -LANGUAGE SQL STABLE SECURITY DEFINER SET search_path = public -AS $$ - SELECT r.* - FROM public.ratio_rules r - WHERE r.is_active - AND r.effective_start <= _on - AND (r.effective_end IS NULL OR r.effective_end >= _on) - AND (r.campus_id IS NULL OR r.campus_id = _campus) - AND (r.classroom_id IS NULL OR r.classroom_id = _classroom) - AND (_age_months IS NULL OR r.min_age_months IS NULL OR _age_months >= r.min_age_months) - AND (_age_months IS NULL OR r.max_age_months IS NULL OR _age_months <= r.max_age_months) - ORDER BY r.priority DESC, r.students_per_staff ASC - LIMIT 1 -$$; - --- ============================================================================ --- 3. RATIO CALCULATION --- ============================================================================ --- Reports scheduled and actual counts side by side, because a campus can be --- compliant on paper and short-staffed in practice. - -CREATE OR REPLACE FUNCTION public.campus_ratio(_campus UUID, _date DATE) -RETURNS TABLE ( - campus_id UUID, - campus_name TEXT, - scheduled_students BIGINT, - present_students BIGINT, - scheduled_staff BIGINT, - present_staff BIGINT, - required_students_per_staff NUMERIC, - actual_students_per_staff NUMERIC, - staff_required NUMERIC, - is_compliant BOOLEAN, - rule_id UUID -) -LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = public AS $$ -DECLARE - rule public.ratio_rules%ROWTYPE; - sched_students BIGINT; - pres_students BIGINT; - sched_staff BIGINT; - pres_staff BIGINT; - cname TEXT; -BEGIN - SELECT name INTO cname FROM public.campuses WHERE id = _campus; - - SELECT COUNT(*) INTO sched_students - FROM public.campus_roll_call(_campus, _date) rc - WHERE rc.was_scheduled; - - SELECT COUNT(*) INTO pres_students - FROM public.attendance a - WHERE a.date = _date AND a.campus_id = _campus AND a.status IN ('present','late'); - - SELECT COUNT(*) INTO sched_staff - FROM public.staff_campus_assignments sca - WHERE sca.campus_id = _campus - AND (sca.start_date IS NULL OR sca.start_date <= _date) - AND (sca.end_date IS NULL OR sca.end_date >= _date); - - SELECT COUNT(*) INTO pres_staff - FROM public.staff_attendance sa - WHERE sa.campus_id = _campus AND sa.date = _date - AND sa.status IN ('present','late') AND sa.counts_toward_ratio; - - SELECT * INTO rule FROM public.resolve_ratio_rule(_campus, _date); - - RETURN QUERY SELECT - _campus, - cname, - sched_students, - pres_students, - sched_staff, - pres_staff, - rule.students_per_staff, - CASE WHEN pres_staff > 0 THEN ROUND(pres_students::numeric / pres_staff, 2) END, - CASE WHEN rule.students_per_staff IS NOT NULL - THEN CEIL(pres_students::numeric / rule.students_per_staff) END, - -- No rule configured means "unknown", not "compliant". - CASE WHEN rule.students_per_staff IS NULL THEN NULL - WHEN pres_students = 0 THEN TRUE - WHEN pres_staff = 0 THEN FALSE - ELSE (pres_students::numeric / pres_staff) <= rule.students_per_staff END, - rule.id; -END; -$$; - --- ============================================================================ --- 4. SNAPSHOTS --- ============================================================================ --- Section 20 asks for "times when the ratio was exceeded", which needs a record --- taken through the day, not a figure computed on demand after the fact. - -CREATE TABLE IF NOT EXISTS public.ratio_snapshots ( - id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - campus_id UUID NOT NULL REFERENCES public.campuses(id) ON DELETE CASCADE, - taken_at TIMESTAMPTZ NOT NULL DEFAULT now(), - date DATE NOT NULL DEFAULT CURRENT_DATE, - - present_students INTEGER NOT NULL, - present_staff INTEGER NOT NULL, - required_students_per_staff NUMERIC(5,2), - actual_students_per_staff NUMERIC(6,2), - is_compliant BOOLEAN, - rule_id UUID REFERENCES public.ratio_rules(id) ON DELETE SET NULL, - note TEXT -); - -GRANT SELECT, INSERT ON public.ratio_snapshots TO authenticated; -GRANT ALL ON public.ratio_snapshots TO service_role; -ALTER TABLE public.ratio_snapshots ENABLE ROW LEVEL SECURITY; -CREATE INDEX IF NOT EXISTS rs_campus_idx ON public.ratio_snapshots (campus_id, taken_at DESC); -CREATE INDEX IF NOT EXISTS rs_rule_idx ON public.ratio_snapshots (rule_id); --- Compliance review looks for the breaches, not the quiet hours. -CREATE INDEX IF NOT EXISTS rs_breach_idx ON public.ratio_snapshots (campus_id, date) - WHERE is_compliant IS FALSE; - -CREATE OR REPLACE FUNCTION public.take_ratio_snapshot(_campus UUID) -RETURNS BIGINT -LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ -DECLARE - r RECORD; - new_id BIGINT; -BEGIN - SELECT * INTO r FROM public.campus_ratio(_campus, CURRENT_DATE); - INSERT INTO public.ratio_snapshots - (campus_id, present_students, present_staff, required_students_per_staff, - actual_students_per_staff, is_compliant, rule_id) - VALUES (_campus, r.present_students, r.present_staff, r.required_students_per_staff, - r.actual_students_per_staff, r.is_compliant, r.rule_id) - RETURNING id INTO new_id; - RETURN new_id; -END; -$$; - --- ============================================================================ --- 5. POLICIES --- ============================================================================ - -DROP POLICY IF EXISTS "staff attendance read" ON public.staff_attendance; -CREATE POLICY "staff attendance read" ON public.staff_attendance FOR SELECT TO authenticated - USING ( - user_id = (SELECT auth.uid()) - OR public.has_campus_access(campus_id) - OR public.is_management() OR public.is_auditor() - ); - -DROP POLICY IF EXISTS "staff attendance write" ON public.staff_attendance; -CREATE POLICY "staff attendance write" ON public.staff_attendance FOR ALL TO authenticated - USING (public.has_campus_access(campus_id)) WITH CHECK (public.has_campus_access(campus_id)); - -DROP POLICY IF EXISTS "ratio rules read" ON public.ratio_rules; -CREATE POLICY "ratio rules read" ON public.ratio_rules FOR SELECT TO authenticated USING (TRUE); -DROP POLICY IF EXISTS "ratio rules manage" ON public.ratio_rules; -CREATE POLICY "ratio rules manage" ON public.ratio_rules FOR ALL TO authenticated - USING (public.is_org_admin()) WITH CHECK (public.is_org_admin()); - -DROP POLICY IF EXISTS "ratio snapshots read" ON public.ratio_snapshots; -CREATE POLICY "ratio snapshots read" ON public.ratio_snapshots FOR SELECT TO authenticated - USING (public.has_campus_access(campus_id) OR public.is_auditor()); -DROP POLICY IF EXISTS "ratio snapshots insert" ON public.ratio_snapshots; -CREATE POLICY "ratio snapshots insert" ON public.ratio_snapshots FOR INSERT TO authenticated - WITH CHECK (public.has_campus_access(campus_id));