Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 7 additions & 21 deletions codebenders-dashboard/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -276,11 +276,7 @@ export default function DashboardPage() {
<p><strong>What it shows:</strong> Percentage of students retained year-to-year based on historical data.</p>
<p className="mt-2"><strong>Data source:</strong> Retention field from student cohort records (0=Not Retained, 1=Retained).</p>
<p className="mt-2"><strong>Use for:</strong> Baseline institutional performance metric.</p>
<p className="mt-3">
<Link href={`${GLOSSARY_HREF}#overall-retention-rate`} className="text-primary underline-offset-4 hover:underline text-xs font-medium">
Full glossary entry (PDP / IPEDS cross-walk) →
</Link>
</p>
<GlossaryMetricEntryLink slug="overall-retention-rate" />
</>
}
/>
Expand All @@ -301,11 +297,7 @@ export default function DashboardPage() {
<li>First Year GPA (2.9%)</li>
</ul>
<p className="mt-2"><strong>Use for:</strong> Early identification of at-risk students for proactive intervention.</p>
<p className="mt-3">
<Link href={`${GLOSSARY_HREF}#avg-predicted-retention`} className="text-primary underline-offset-4 hover:underline text-xs font-medium">
Full glossary entry (PDP / IPEDS cross-walk) →
</Link>
</p>
<GlossaryMetricEntryLink slug="avg-predicted-retention" />
</>
}
/>
Expand All @@ -330,11 +322,7 @@ export default function DashboardPage() {
<li><strong>HIGH:</strong> Priority intervention</li>
</ul>
<p className="mt-2"><strong>Recommended actions:</strong> Immediate advisor outreach, financial aid review, tutoring referrals.</p>
<p className="mt-3">
<Link href={`${GLOSSARY_HREF}#students-at-high-critical-risk`} className="text-primary underline-offset-4 hover:underline text-xs font-medium">
Full glossary entry (PDP / IPEDS cross-walk) →
</Link>
</p>
<GlossaryMetricEntryLink slug="students-at-high-critical-risk" />
</>
}
/>
Expand All @@ -355,11 +343,7 @@ export default function DashboardPage() {
<li>&lt;50%: Critical - failing nearly half of courses</li>
</ul>
<p className="mt-2"><strong>Why it matters:</strong> Strong predictor of retention and credential completion.</p>
<p className="mt-3">
<Link href={`${GLOSSARY_HREF}#avg-course-completion`} className="text-primary underline-offset-4 hover:underline text-xs font-medium">
Full glossary entry (PDP / IPEDS cross-walk) →
</Link>
</p>
<GlossaryMetricEntryLink slug="avg-course-completion" />
</>
}
/>
Expand Down Expand Up @@ -389,6 +373,7 @@ export default function DashboardPage() {
</ul>
<p className="mt-2"><strong>Alert severity levels:</strong> Based on composite risk score combining retention probability, GPA, completion rate, and credit progress.</p>
<p className="mt-2"><strong>Use for:</strong> Daily advisor task lists, automated alerts, resource allocation.</p>
<GlossaryMetricEntryLink slug="risk-alert-distribution" />
</>
}
/>
Expand All @@ -407,6 +392,7 @@ export default function DashboardPage() {
<li><strong>Low Risk:</strong> Retention probability &gt;0.7</li>
</ul>
<p className="mt-2"><strong>Note:</strong> These are different from Risk Alerts above. This chart shows pure retention probability, while Risk Alerts combine retention with GPA and completion metrics.</p>
<GlossaryMetricEntryLink slug="retention-risk-funnel" />
</>
}
/>
Expand Down
24 changes: 24 additions & 0 deletions codebenders-dashboard/components/glossary-metric-entry-link.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<p className="mt-3">
<Link href={metricGlossaryEntryHref(slug)} className={LINK_CLASS}>
Full glossary entry (PDP / IPEDS cross-walk) →
</Link>
</p>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -116,7 +117,7 @@
const totalStudents = summary.total_students;
const avgScore = parseFloat(summary.avg_score);
const highPct = totalStudents > 0 ? ((summary.high_count / totalStudents) * 100).toFixed(1) : '0';
const mediumPct = totalStudents > 0 ? ((summary.medium_count / totalStudents) * 100).toFixed(1) : '0';

Check warning on line 120 in codebenders-dashboard/components/readiness-assessment-chart.tsx

View workflow job for this annotation

GitHub Actions / Type check · Lint · Build

'mediumPct' is assigned a value but never used
const lowPct = totalStudents > 0 ? ((summary.low_count / totalStudents) * 100).toFixed(1) : '0';

return (
Expand Down Expand Up @@ -187,7 +188,11 @@
<CardDescription>Student readiness categorization</CardDescription>
</div>
<InfoPopover title="Readiness Assessment">
AI-powered assessment analyzing student preparation, engagement, and success indicators. High readiness indicates students are well-positioned for success.
<p>
AI-powered assessment analyzing student preparation, engagement, and success indicators. High readiness
indicates students are well-positioned for success.
</p>
<GlossaryMetricEntryLink slug="readiness-assessment" />
</InfoPopover>
</div>
</CardHeader>
Expand Down
38 changes: 37 additions & 1 deletion codebenders-dashboard/content/metric-glossary.md
Original file line number Diff line number Diff line change
@@ -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.

---

Expand Down Expand Up @@ -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.
Original file line number Diff line number Diff line change
@@ -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])
})
})
41 changes: 30 additions & 11 deletions codebenders-dashboard/lib/glossary-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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,
Expand Down
Loading