diff --git a/codebenders-dashboard/app/page.tsx b/codebenders-dashboard/app/page.tsx index 53f516d..a0a2b2f 100644 --- a/codebenders-dashboard/app/page.tsx +++ b/codebenders-dashboard/app/page.tsx @@ -16,7 +16,7 @@ import { } from "@/components/ui/select" import { TrendingUp, Users, AlertTriangle, BookOpen, Search, Table2, X } from "lucide-react" import Link from "next/link" -import { GLOSSARY_HREF } from "@/lib/glossary-constants" +import { GlossaryMetricEntryLink } from "@/components/glossary-metric-entry-link" interface KPIData { overallRetentionRate: string @@ -276,11 +276,7 @@ export default function DashboardPage() {

What it shows: Percentage of students retained year-to-year based on historical data.

Data source: Retention field from student cohort records (0=Not Retained, 1=Retained).

Use for: Baseline institutional performance metric.

-

- - Full glossary entry (PDP / IPEDS cross-walk) → - -

+ } /> @@ -301,11 +297,7 @@ export default function DashboardPage() {
  • First Year GPA (2.9%)
  • Use for: Early identification of at-risk students for proactive intervention.

    -

    - - Full glossary entry (PDP / IPEDS cross-walk) → - -

    + } /> @@ -330,11 +322,7 @@ export default function DashboardPage() {
  • HIGH: Priority intervention
  • Recommended actions: Immediate advisor outreach, financial aid review, tutoring referrals.

    -

    - - Full glossary entry (PDP / IPEDS cross-walk) → - -

    + } /> @@ -355,11 +343,7 @@ export default function DashboardPage() {
  • <50%: Critical - failing nearly half of courses
  • Why it matters: Strong predictor of retention and credential completion.

    -

    - - Full glossary entry (PDP / IPEDS cross-walk) → - -

    + } /> @@ -389,6 +373,7 @@ export default function DashboardPage() {

    Alert severity levels: Based on composite risk score combining retention probability, GPA, completion rate, and credit progress.

    Use for: Daily advisor task lists, automated alerts, resource allocation.

    + } /> @@ -407,6 +392,7 @@ export default function DashboardPage() {
  • Low Risk: Retention probability >0.7
  • Note: These are different from Risk Alerts above. This chart shows pure retention probability, while Risk Alerts combine retention with GPA and completion metrics.

    + } /> diff --git a/codebenders-dashboard/components/glossary-metric-entry-link.tsx b/codebenders-dashboard/components/glossary-metric-entry-link.tsx new file mode 100644 index 0000000..23f2ee3 --- /dev/null +++ b/codebenders-dashboard/components/glossary-metric-entry-link.tsx @@ -0,0 +1,24 @@ +"use client" + +import Link from "next/link" +import { + metricGlossaryEntryHref, + type MetricGlossarySlug, +} from "@/lib/glossary-constants" + +const LINK_CLASS = + "text-primary underline-offset-4 hover:underline text-xs font-medium" + +interface GlossaryMetricEntryLinkProps { + slug: MetricGlossarySlug +} + +export function GlossaryMetricEntryLink({ slug }: GlossaryMetricEntryLinkProps) { + return ( +

    + + Full glossary entry (PDP / IPEDS cross-walk) → + +

    + ) +} diff --git a/codebenders-dashboard/components/readiness-assessment-chart.tsx b/codebenders-dashboard/components/readiness-assessment-chart.tsx index 6f580f5..13c6650 100644 --- a/codebenders-dashboard/components/readiness-assessment-chart.tsx +++ b/codebenders-dashboard/components/readiness-assessment-chart.tsx @@ -3,8 +3,9 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Alert, AlertDescription } from '@/components/ui/alert'; -import { AlertCircle, TrendingUp, TrendingDown, Users, Target, AlertTriangle } from 'lucide-react'; +import { AlertCircle, TrendingUp, Users, Target, AlertTriangle } from 'lucide-react'; import { InfoPopover } from '@/components/info-popover'; +import { GlossaryMetricEntryLink } from '@/components/glossary-metric-entry-link'; interface ReadinessData { summary: { @@ -187,7 +188,11 @@ export function ReadinessAssessmentChart({ data, isLoading, error }: ReadinessAs Student readiness categorization - AI-powered assessment analyzing student preparation, engagement, and success indicators. High readiness indicates students are well-positioned for success. +

    + AI-powered assessment analyzing student preparation, engagement, and success indicators. High readiness + indicates students are well-positioned for success. +

    +
    diff --git a/codebenders-dashboard/content/metric-glossary.md b/codebenders-dashboard/content/metric-glossary.md index 2c733ec..c9ad94b 100644 --- a/codebenders-dashboard/content/metric-glossary.md +++ b/codebenders-dashboard/content/metric-glossary.md @@ -1,6 +1,6 @@ # Metric glossary -Single source of truth for dashboard KPI definitions. Each section is keyed by its URL anchor (e.g. `/glossary#overall-retention-rate`). Cross-walks are indicative — institutions should confirm against their PDP documentation, IPEDS submission manuals, and state reporting rules. +Single source of truth for dashboard KPIs and chart views. Each section is keyed by its URL anchor (e.g. `/glossary#overall-retention-rate`). Cross-walks are indicative — institutions should confirm against their PDP documentation, IPEDS submission manuals, and state reporting rules. --- @@ -49,3 +49,39 @@ Single source of truth for dashboard KPI definitions. Each section is keyed by i **IPEDS:** Conceptually related to success rates and progression, but IPEDS collects many distinct measures (e.g., completions by award) — do not assume identity without a written cross-walk. **State compliance:** Often parallels “success rate” or “credit completion ratio” in performance funding models; verify denominator rules match your state formula. + +--- + +## risk-alert-distribution + +**Plain English:** Pie chart of students grouped by **composite risk alert** band (LOW, MODERATE, HIGH, URGENT) used for triage — not the same as the retention-probability-only funnel chart. + +**PDP / analysis-ready:** Counts come from `at_risk_alert` on `student_level_with_predictions` after dashboard filters. The composite rule combines retention probability, GPA bands, course completion, and credit progress (see in-chart tooltip and methodology). + +**IPEDS:** No direct IPEDS collection; analogous to internal early-alert or “at risk” population counts if your institution defines them similarly. + +**State compliance:** Operational metric; if reported externally, align labels and thresholds with your state’s definitions and suppress small cells where required. + +--- + +## retention-risk-funnel + +**Plain English:** Horizontal bar chart of students by **model retention risk category** (Critical / High / Moderate / Low) from predicted retention probability alone. + +**PDP / analysis-ready:** Buckets are derived from `retention_probability` (XGBoost) using fixed cut points on `student_level_with_predictions`. + +**IPEDS:** Not an IPEDS measure; related conceptually to retention outcome reporting but this view is **predictive**, not audited cohort outcomes. + +**State compliance:** Use for advising and planning; do not substitute for official retention rates in external reporting without documentation. + +--- + +## readiness-assessment + +**Plain English:** PDP-aligned **readiness index** — composite score and High / Medium / Low bands summarizing academic, engagement, and ML-risk components for the filtered cohort. + +**PDP / analysis-ready:** Scores and levels are computed server-side (see `/api/dashboard/readiness` and methodology) from fields on `student_level_with_predictions` and related logic. + +**IPEDS:** No IPEDS analog; closest institutional parallels are internal “student success” or “momentum” indices if your IR office publishes them. + +**State compliance:** Treat as an internal diagnostic; confirm any export or public use against institutional policy and small-N rules. diff --git a/codebenders-dashboard/lib/__tests__/metric-glossary-coverage.test.ts b/codebenders-dashboard/lib/__tests__/metric-glossary-coverage.test.ts index debf689..de47fd7 100644 --- a/codebenders-dashboard/lib/__tests__/metric-glossary-coverage.test.ts +++ b/codebenders-dashboard/lib/__tests__/metric-glossary-coverage.test.ts @@ -1,26 +1,19 @@ import { describe, expect, it } from "vitest" -import { - DASHBOARD_KPI_GLOSSARY_SLUGS, - GLOSSARY_TOPIC_SECTIONS, -} from "@/lib/glossary-constants" +import { GLOSSARY_TOPIC_SECTIONS, METRIC_GLOSSARY_INDEX_SLUGS } from "@/lib/glossary-constants" import { parseGlossaryEntries, readMetricGlossaryMarkdown } from "@/lib/metric-glossary" -describe("metric glossary coverage (#105)", () => { - it("includes every dashboard KPI slug in metric-glossary.md", () => { +describe("metric glossary coverage (#105 / #124)", () => { + it("includes every indexed slug in metric-glossary.md", () => { const md = readMetricGlossaryMarkdown() const entries = parseGlossaryEntries(md) - for (const slug of DASHBOARD_KPI_GLOSSARY_SLUGS) { + for (const slug of METRIC_GLOSSARY_INDEX_SLUGS) { expect(entries[slug], `missing ## ${slug} in content/metric-glossary.md`).toBeTruthy() expect(entries[slug]!.length).toBeGreaterThan(20) } }) - it("topic sections list each KPI slug exactly once", () => { - const listed = GLOSSARY_TOPIC_SECTIONS.flatMap((t) => [...t.slugOrder]) - expect(listed.length).toBe(DASHBOARD_KPI_GLOSSARY_SLUGS.length) - for (const slug of DASHBOARD_KPI_GLOSSARY_SLUGS) { - const n = listed.filter((s) => s === slug).length - expect(n, `slug ${slug} should appear once in GLOSSARY_TOPIC_SECTIONS`).toBe(1) - } + it("topic sections list each indexed slug exactly once in index order", () => { + const listedSlugs = GLOSSARY_TOPIC_SECTIONS.flatMap((t) => [...t.slugOrder]) + expect(listedSlugs).toEqual([...METRIC_GLOSSARY_INDEX_SLUGS]) }) }) diff --git a/codebenders-dashboard/lib/glossary-constants.ts b/codebenders-dashboard/lib/glossary-constants.ts index 2a86003..e0295c0 100644 --- a/codebenders-dashboard/lib/glossary-constants.ts +++ b/codebenders-dashboard/lib/glossary-constants.ts @@ -3,12 +3,12 @@ export const GLOSSARY_HREF = "/glossary" as const /** - * Source of truth for topic groupings and KPI slug order on the glossary page. - * `DASHBOARD_KPI_GLOSSARY_SLUGS` is derived from this list; every `##` section in - * `content/metric-glossary.md` must stay in sync (see - * `lib/__tests__/metric-glossary-coverage.test.ts`). + * Source of truth for topic groupings on the glossary page. + * `METRIC_GLOSSARY_INDEX_SLUGS` (and deprecated `DASHBOARD_KPI_GLOSSARY_SLUGS`) are + * derived from this list; every `##` section in `content/metric-glossary.md` must + * stay in sync (see `lib/__tests__/metric-glossary-coverage.test.ts`). */ -const GLOSSARY_TOPIC_SECTIONS_RAW = [ +const METRIC_GLOSSARY_TOPIC_SECTIONS_SOURCE = [ { id: "retention", label: "Retention, risk & predictions", @@ -23,19 +23,38 @@ const GLOSSARY_TOPIC_SECTIONS_RAW = [ label: "Completion & course success", slugOrder: ["avg-course-completion"] as const, }, + { + id: "charts", + label: "Charts & composite views", + slugOrder: [ + "risk-alert-distribution", + "retention-risk-funnel", + "readiness-assessment", + ] as const, + }, ] as const -export type DashboardKpiGlossarySlug = - (typeof GLOSSARY_TOPIC_SECTIONS_RAW)[number]["slugOrder"][number] +export type MetricGlossarySlug = + (typeof METRIC_GLOSSARY_TOPIC_SECTIONS_SOURCE)[number]["slugOrder"][number] + +/** @deprecated Use `MetricGlossarySlug` */ +export type DashboardKpiGlossarySlug = MetricGlossarySlug + +export function metricGlossaryEntryHref(slug: MetricGlossarySlug): string { + return `${GLOSSARY_HREF}#${slug}` +} + +export const METRIC_GLOSSARY_INDEX_SLUGS: readonly MetricGlossarySlug[] = + METRIC_GLOSSARY_TOPIC_SECTIONS_SOURCE.flatMap((topic) => [...topic.slugOrder]) -export const DASHBOARD_KPI_GLOSSARY_SLUGS: readonly DashboardKpiGlossarySlug[] = - GLOSSARY_TOPIC_SECTIONS_RAW.flatMap((topic) => [...topic.slugOrder]) +/** @deprecated Use `METRIC_GLOSSARY_INDEX_SLUGS` */ +export const DASHBOARD_KPI_GLOSSARY_SLUGS = METRIC_GLOSSARY_INDEX_SLUGS export const GLOSSARY_TOPIC_SECTIONS: { id: string label: string - slugOrder: readonly DashboardKpiGlossarySlug[] -}[] = GLOSSARY_TOPIC_SECTIONS_RAW.map((topic) => ({ + slugOrder: readonly MetricGlossarySlug[] +}[] = METRIC_GLOSSARY_TOPIC_SECTIONS_SOURCE.map((topic) => ({ id: topic.id, label: topic.label, slugOrder: topic.slugOrder,