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
600 changes: 600 additions & 0 deletions codebenders-dashboard/app/discovery/aascu/full/page.module.css

Large diffs are not rendered by default.

332 changes: 332 additions & 0 deletions codebenders-dashboard/app/discovery/aascu/full/page.tsx

Large diffs are not rendered by default.

250 changes: 250 additions & 0 deletions codebenders-dashboard/app/discovery/aascu/page.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
.doc {
--bg: #fafaf7;
--ink: #15181a;
--soft: #4a4f53;
--muted: #7d8489;
--rule: #e3e1da;
--accent: #0f5c4d;
--accent-soft: #e8efe9;
--done: #0f5c4d;
--partial: #9a6d12;
--gap: #8b3a3a;

background: var(--bg);
color: var(--ink);
font-family: var(--font-plex-sans), ui-sans-serif, system-ui, sans-serif;
line-height: 1.55;
-webkit-font-smoothing: antialiased;
min-height: calc(100vh - 48px);
}

.inner {
max-width: 760px;
margin: 0 auto;
padding: 56px 32px 96px;
}

.header {
padding-bottom: 32px;
border-bottom: 1px solid var(--ink);
margin-bottom: 48px;
}

.kicker {
font-family: var(--font-plex-mono), ui-monospace, monospace;
font-size: 11px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 18px;
}

.h1 {
font-family: var(--font-newsreader), ui-serif, Georgia, serif;
font-weight: 400;
font-size: 42px;
line-height: 1.1;
letter-spacing: -0.02em;
color: var(--ink);
margin-bottom: 14px;
}

.lede {
font-family: var(--font-newsreader), ui-serif, Georgia, serif;
font-weight: 400;
font-size: 19px;
line-height: 1.5;
color: var(--soft);
max-width: 620px;
}

.meta {
margin-top: 24px;
display: flex;
gap: 24px;
flex-wrap: wrap;
font-family: var(--font-plex-mono), ui-monospace, monospace;
font-size: 11px;
letter-spacing: 0.08em;
color: var(--muted);
text-transform: uppercase;
}

.meta b { color: var(--ink); font-weight: 500; }

.section { margin-bottom: 56px; }

.h2 {
font-family: var(--font-plex-mono), ui-monospace, monospace;
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 24px;
padding-bottom: 10px;
border-bottom: 1px solid var(--rule);
font-weight: 500;
}

.tldr {
background: var(--accent-soft);
border-left: 3px solid var(--accent);
padding: 24px 28px;
border-radius: 2px;
}

.tldr p {
font-family: var(--font-newsreader), ui-serif, Georgia, serif;
font-size: 18px;
line-height: 1.55;
color: var(--ink);
}

.tldr p + p { margin-top: 14px; }
.tldr strong { color: var(--accent); font-weight: 500; }

.item {
display: grid;
grid-template-columns: 1fr auto;
gap: 24px;
padding: 18px 0;
border-bottom: 1px solid var(--rule);
align-items: start;
}

.item:last-child { border-bottom: none; }

.lbl {
font-family: var(--font-plex-mono), ui-monospace, monospace;
font-size: 10px;
letter-spacing: 0.16em;
color: var(--muted);
text-transform: uppercase;
margin-bottom: 6px;
}

.itemH {
font-family: var(--font-newsreader), ui-serif, Georgia, serif;
font-weight: 500;
font-size: 18px;
line-height: 1.3;
color: var(--ink);
letter-spacing: -0.005em;
margin-bottom: 6px;
}

.itemP {
font-size: 14px;
line-height: 1.55;
color: var(--soft);
max-width: 520px;
}

.stat {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: var(--font-plex-mono), ui-monospace, monospace;
font-size: 10.5px;
letter-spacing: 0.14em;
text-transform: uppercase;
font-weight: 500;
white-space: nowrap;
padding-top: 4px;
}

.stat::before {
content: "";
width: 7px;
height: 7px;
border-radius: 50%;
}

.statDone { color: var(--done); }
.statDone::before { background: var(--done); }
.statPartial { color: var(--partial); }
.statPartial::before { background: var(--partial); }
.statGap { color: var(--gap); }
.statGap::before { background: var(--gap); }

.issue {
display: grid;
grid-template-columns: 72px 1fr auto;
gap: 20px;
padding: 18px 0;
border-bottom: 1px solid var(--rule);
align-items: baseline;
text-decoration: none;
color: inherit;
transition: background 0.15s ease;
}

.issue:last-child { border-bottom: none; }
.issue:hover { background: rgba(15, 92, 77, 0.025); }

.issueNum {
font-family: var(--font-plex-mono), ui-monospace, monospace;
font-size: 13px;
font-weight: 500;
color: var(--accent);
letter-spacing: 0.04em;
}

.issueH {
font-family: var(--font-newsreader), ui-serif, Georgia, serif;
font-weight: 500;
font-size: 17px;
line-height: 1.3;
color: var(--ink);
margin-bottom: 4px;
letter-spacing: -0.005em;
}

.issueP {
font-size: 13.5px;
line-height: 1.5;
color: var(--soft);
}

.pri {
font-family: var(--font-plex-mono), ui-monospace, monospace;
font-size: 10px;
letter-spacing: 0.14em;
color: var(--muted);
text-transform: uppercase;
padding: 3px 9px;
border: 1px solid var(--rule);
border-radius: 2px;
white-space: nowrap;
font-weight: 500;
}

.pri0 { color: var(--gap); border-color: var(--gap); }
.pri1 { color: var(--partial); border-color: var(--partial); }
.pri2 { color: var(--muted); }

.footer {
margin-top: 48px;
padding-top: 20px;
border-top: 1px solid var(--rule);
font-family: var(--font-plex-mono), ui-monospace, monospace;
font-size: 10.5px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--muted);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 14px;
}

.footer a { color: var(--accent); text-decoration: none; }

@media (max-width: 640px) {
.inner { padding: 36px 20px 64px; }
.h1 { font-size: 32px; }
.lede { font-size: 17px; }
.item { grid-template-columns: 1fr; }
.issue { grid-template-columns: 1fr; gap: 6px; }
.issueNum { font-size: 12px; }
}
145 changes: 145 additions & 0 deletions codebenders-dashboard/app/discovery/aascu/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import type { Metadata } from "next"
import Link from "next/link"
import { Newsreader, IBM_Plex_Sans, IBM_Plex_Mono } from "next/font/google"
import styles from "./page.module.css"

const newsreader = Newsreader({
variable: "--font-newsreader",
subsets: ["latin"],
weight: ["400", "500"],
})

const plexSans = IBM_Plex_Sans({
variable: "--font-plex-sans",
subsets: ["latin"],
weight: ["400", "500", "600"],
})

const plexMono = IBM_Plex_Mono({
variable: "--font-plex-mono",
subsets: ["latin"],
weight: ["400", "500"],
})

export const metadata: Metadata = {
title: "AASCU Gap Analysis — Brief",
description: "Two-page condensation of the AASCU intermediary discovery session and the issues filed against the dashboard backlog.",
}

const ISSUE_BASE = "https://github.com/devcolor/codebenders-datathon/issues"

const ISSUES: Array<{
num: number
title: string
desc: string
pri: "p0" | "p1" | "p2"
}> = [
{ num: 105, pri: "p0", title: "Metric definitions glossary with IPEDS / state cross-walks", desc: "Hover tooltips on every KPI · centralized /glossary page · markdown source-of-truth." },
{ num: 106, pri: "p0", title: "Presentation-ready chart export (PNG / PDF)", desc: "Title, definition, source, date stamp baked in. Eliminates the manual Excel rebuild." },
{ num: 107, pri: "p0", title: "Data lineage view — “where did this number come from”", desc: "Click any number → source rows, upload event, transformations, timestamps. Highest-leverage gap." },
{ num: 108, pri: "p1", title: "AI Transparency Page", desc: "Per-model disclosure — features, training data, provider, data flow, retention. Reviewable independently." },
{ num: 109, pri: "p1", title: "Sensitive-population safeguards", desc: "Per-institution feature exclusion · low-sample-size context warnings · audit log entries." },
{ num: 110, pri: "p1", title: "Upload validation report — diff vs. last upload", desc: "Row-level errors, coercions, dedup decisions, dropped-campus flags. Readable by non-technical IR staff." },
{ num: 111, pri: "p2", title: "Submission runbook generator", desc: "Capture what worked → printable runbook · replayable on new files · survives staff turnover." },
{ num: 112, pri: "p2", title: "Datathon institution grouping (SIS + goal)", desc: "Operational artifact — cross-reference AASCU's SIS list with stated goals; output cohort matrix." },
]

const COVERAGE: Array<{
lbl: string
title: string
desc: string
status: "done" | "partial" | "gap"
}> = [
{ lbl: "A · Data accuracy", status: "partial", title: "Numbers don’t add up; PDP pulls from wrong dataset", desc: "Validation report on upload addresses the institutional side; PDP-side accuracy is out of scope." },
{ lbl: "B · Definitions", status: "gap", title: "Metrics undefined in-context; mismatch with IPEDS / state", desc: "Tooltip primitive exists. Centralized glossary and cross-walks not yet built." },
{ lbl: "C · Visualization", status: "done", title: "Wrong chart types in PDP; ours are sane", desc: "Recharts components, types chosen per metric. Done." },
{ lbl: "C · Export", status: "partial", title: "CSV from dashboard; no presentation-ready chart export", desc: "CSV shipped (#15). PNG/PDF export with embedded definitions is the next step." },
{ lbl: "D · FERPA", status: "done", title: "RBAC, audit log, FERPA-compliant identity resolution", desc: "Issues #67, #75, #77, #78 closed. FERPA basics covered." },
{ lbl: "D · AI governance", status: "gap", title: "Transparency page, lineage view, sensitive-population safeguards", desc: "Methodology page exists. SHAP narrator in flight. Lineage and transparency disclosures unbuilt." },
{ lbl: "E · Process", status: "partial", title: "Knowledge siloed; submission rituals vary per campus", desc: "Self-service upload (#86) helps. Runbook generator would close the loop." },
]

const statClass = (s: "done" | "partial" | "gap") =>
s === "done" ? styles.statDone : s === "partial" ? styles.statPartial : styles.statGap

const priClass = (p: "p0" | "p1" | "p2") =>
p === "p0" ? styles.pri0 : p === "p1" ? styles.pri1 : styles.pri2

export default function AASCUBriefPage() {
return (
<div className={`${newsreader.variable} ${plexSans.variable} ${plexMono.variable} ${styles.doc}`}>
<div className={styles.inner}>

<header className={styles.header}>
<div className={styles.kicker}>AASCU Discovery · Gap Analysis · Brief</div>
<h1 className={styles.h1}>What the tool already does, what it&rsquo;s missing, what to build next.</h1>
<p className={styles.lede}>
A two-page condensation of the AASCU intermediary discovery session and the eight issues filed against the codebenders-dashboard backlog.
</p>
<div className={styles.meta}>
<span><b>2026·04·29</b></span>
<span>2 intermediaries</span>
<span>21 min source</span>
<span>8 issues filed</span>
</div>
</header>

<section className={styles.section}>
<h2 className={styles.h2}>The take</h2>
<div className={styles.tldr}>
<p>
Intermediaries describe pain in three layers: <strong>PDP dashboard quality</strong> (charts, definitions, exports, accuracy), <strong>AI &amp; data governance</strong> (lineage, transparency, sensitive populations), and <strong>institutional process</strong> (knowledge silos, inconsistent submission rituals).
</p>
<p>
The tool already addresses meaningful parts of layer one and is in-flight on layer two via the SHAP narrator. The biggest unaddressed gaps are <strong>a definitions glossary, presentation-ready chart export, a data-lineage view, and AI transparency + sensitive-population safeguards</strong>.
</p>
</div>
</section>

<section className={styles.section}>
<h2 className={styles.h2}>Pain × Coverage</h2>
{COVERAGE.map((row) => (
<div key={row.lbl} className={styles.item}>
<div>
<div className={styles.lbl}>{row.lbl}</div>
<h3 className={styles.itemH}>{row.title}</h3>
<p className={styles.itemP}>{row.desc}</p>
</div>
<span className={`${styles.stat} ${statClass(row.status)}`}>
{row.status === "done" ? "Done" : row.status === "partial" ? "Partial" : "Gap"}
</span>
</div>
))}
</section>

<section className={styles.section}>
<h2 className={styles.h2}>Issues filed (#105 — #112)</h2>
{ISSUES.map((iss) => (
<a
key={iss.num}
className={styles.issue}
href={`${ISSUE_BASE}/${iss.num}`}
target="_blank"
rel="noreferrer"
>
<div className={styles.issueNum}>#{iss.num}</div>
<div>
<h3 className={styles.issueH}>{iss.title}</h3>
<p className={styles.issueP}>{iss.desc}</p>
</div>
<span className={`${styles.pri} ${priClass(iss.pri)}`}>{iss.pri.toUpperCase()}</span>
</a>
))}
</section>

<footer className={styles.footer}>
<span>Brief · Bishop State CC · Codebenders Datathon</span>
<span>
Full report → <Link href="/discovery/aascu/full">aascu / full</Link>
</span>
</footer>

</div>
</div>
)
}
Loading
Loading