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
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ acknowledged. It is not a diagnosis product and makes **no accuracy claim**.
## Why this is useful at TSA

- **Guide utility:** one deterministic queue turns attempt-level activity into a short morning plan.
- **Coach-like intervention:** every report includes a concrete opener, not another analytics chart.
- **Coach-like intervention:** every report provides a visible teacher decision map, not another
analytics chart.
- **Responsible student data:** the roster, activity, names, and evaluation corpus are visibly
synthetic; real student data is rejected and remains out of scope.
- **Trustworthy AI boundary:** rules classify and rank without a model. Optional AI may select only
reviewed language-catalog IDs and evidence slots; deterministic fallback always keeps the board
usable.
- **Trustworthy AI boundary:** rules classify and rank without a model. Only a manual synthetic
refresh may draft the explanation, first question, and teaching move from a bounded packet; a
complete deterministic card always keeps the board usable.

## Architecture and trust boundaries

Expand Down Expand Up @@ -139,14 +140,19 @@ walkthrough below; the smoke command does not pretend to replace it.
validate. Call out the separate received/accepted/duplicate/unmapped/rejected counts and that
validation stores no activity.
3. **0:40–0:55 — Commit and refresh.** Commit the same bytes, then choose **Refresh board now**. The
deterministic engine publishes one immutable ranked run; no model key is needed.
4. **0:55–1:30 — Use the Evidence Desk.** Open the top report. Read the cause-specific opener,
compare it with the student's own baseline, then expand **Show exact contributing evidence**.
deterministic engine publishes one immutable ranked run. With `ANTHROPIC_API_KEY`, this manual
synthetic refresh may draft only the explanation, first question, and teaching move; without it,
the complete deterministic card publishes instead. Nightly refresh never invokes AI.
4. **0:55–1:30 — Use the Evidence Desk.** Open the top report. The visible teacher-decision map
presents a hypothesis, first question, confident/struggling fork, listen-for, and teaching move.
Only the three draftable fields are labeled AI-drafted wording; the fork and listen-for are
deterministic teacher support. Expand **Show exact contributing evidence** for the sole detail.
Exact attempt/session results, selected answers, and timing-aware durations remain readable in
seconds without exposing internal record IDs, additional causes, or rule-quality internals.
Severity determines rank; evidence confidence is shown separately.
5. **1:30–1:45 — Show trust behavior.** Point to the concise “Deterministic fallback” wording cue
and the visible “Seen” acknowledgment. Refresh/back navigation keeps the report and seen state.
5. **1:30–1:45 — Show trust behavior.** Point to the teacher-reviewed-draft framing, a visible
`Working hypothesis` when evidence is weak, and the visible “Seen” acknowledgment. Refresh/back
navigation keeps the report and seen state.
6. **1:45–2:00 — Show engineering evidence.** Run `npm run eval:portfolio`: eight fixed cases, hard-fail
grounding injections, zero model calls, and an explicit no-accuracy-claim posture.

Expand Down
72 changes: 60 additions & 12 deletions apps/web/app/board/evidence-desk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function Freshness({
const narration =
state.narration.status === 'degraded'
? `${state.narration.degradedCount} deterministic fallback${state.narration.degradedCount === 1 ? '' : 's'}`
: 'Conversation openers ready';
: 'Conversation plans ready';
return (
<div className="desk-freshness" role="status">
<span className={state.kind === 'stale' ? 'status-dot status-dot-warning' : 'status-dot'} />
Expand Down Expand Up @@ -326,6 +326,64 @@ export function ReportContext({
);
}

export function TeacherGuidanceCard({ entry }: { entry: BoardEntryView }) {
const weakEvidence = entry.finalConfidence < 0.55;
return (
<section className="teacher-guidance-card" aria-labelledby="teacher-guidance-heading">
<header className="teacher-guidance-heading">
<div>
<h3 id="teacher-guidance-heading">AI-assisted conversation plan</h3>
<p>
Teacher-reviewed draft. Use your professional judgment before speaking with the student.
</p>
</div>
</header>
<section
className={`guidance-field guidance-explanation${weakEvidence ? ' working-hypothesis' : ''}`}
>
<h4>
Likely explanation to test <span>AI-drafted wording</span>
</h4>
<p>{entry.diagnosis}</p>
</section>
<section className="guidance-field guidance-ask">
<h4>
Ask first <span>AI-drafted wording</span>
</h4>
<p>{entry.opener}</p>
</section>
<div className="guidance-fork">
<section className="guidance-field guidance-confident">
<h4>
If they explain confidently <span>Teacher support</span>
</h4>
<p>{entry.confidentFollowUp}</p>
</section>
<section className="guidance-field guidance-struggle">
<h4>
If they struggle <span>Teacher support</span>
</h4>
<p>{entry.strugglingFollowUp}</p>
</section>
</div>
<div className="guidance-footer">
<section className="guidance-field">
<h4>
Listen for <span>Teacher support</span>
</h4>
<p>{entry.listenFor}</p>
</section>
<section className="guidance-field guidance-move">
<h4>
Suggested teaching move <span>AI-drafted wording</span>
</h4>
<p>{entry.teachingMove}</p>
</section>
</div>
</section>
);
}

function Workspace({
evidence,
grant,
Expand All @@ -347,17 +405,7 @@ function Workspace({
<article className="evidence-workspace" aria-labelledby="report-title">
<ReturnToRail />
<ReportHeading entry={entry} />
<section className="finding-intro">
<h3>Why Huddle ranked this</h3>
<p>{entry.diagnosis}</p>
<blockquote>
<strong>Conversation opener</strong>
<br />
{entry.opener}
</blockquote>
</section>
<NarrationTrust entry={entry} />
<ReportContext evidence={evidence} report={report} />
<TeacherGuidanceCard entry={entry} />
<TeacherEvidenceList studentName={entry.student.firstName} report={report} />
</article>
);
Expand Down
161 changes: 161 additions & 0 deletions apps/web/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,144 @@ button:hover {
border-top: 1px solid var(--huddle-line);
}

.teacher-guidance-card {
overflow: hidden;
margin-bottom: 20px;
border: 1px solid var(--huddle-line);
border-radius: 12px;
}

.teacher-guidance-heading {
display: flex;
justify-content: space-between;
gap: 16px;
padding: 16px;
border-bottom: 1px solid var(--huddle-line);
background: var(--huddle-surface);
}

.teacher-guidance-heading h3,
.guidance-field h4 {
margin: 0;
}

.teacher-guidance-heading h3 {
font-size: 1.1rem;
}

.teacher-guidance-heading p {
margin: 5px 0 0;
color: var(--huddle-muted);
font-size: 0.84rem;
}

.guidance-field {
min-width: 0;
padding: 15px 16px;
}

.guidance-field h4 {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
color: var(--huddle-muted);
font-size: 0.72rem;
letter-spacing: 0.055em;
text-transform: uppercase;
}

.guidance-field h4 span {
padding: 2px 5px;
border-radius: 4px;
background: #efedf0;
color: #554b4f;
font-size: 0.58rem;
letter-spacing: 0.02em;
text-transform: none;
}

.guidance-explanation h4 span,
.guidance-ask h4 span,
.guidance-move h4 span {
background: #f2dbe3;
color: var(--huddle-brand-dark);
}

.guidance-field > p {
margin: 8px 0 0;
overflow-wrap: anywhere;
}

.guidance-explanation,
.guidance-ask {
border-bottom: 1px solid var(--huddle-line);
}

.guidance-explanation.working-hypothesis {
border-left: 4px solid var(--huddle-warn);
background: #fff7e7;
}

.guidance-ask {
background: #fff2f6;
}

.guidance-ask > p,
.guidance-move > p {
font-weight: 760;
}

.guidance-fork,
.guidance-footer {
display: grid;
grid-template-columns: 1fr 1fr;
}

.guidance-fork {
position: relative;
gap: 20px;
padding: 34px 16px 16px;
border-bottom: 1px solid var(--huddle-line);
}

.guidance-fork::before {
position: absolute;
top: 13px;
right: 25%;
left: 25%;
height: 18px;
border-top: 2px solid #c6a5b0;
border-right: 2px solid #c6a5b0;
border-left: 2px solid #c6a5b0;
content: '';
}

.guidance-fork .guidance-field,
.guidance-footer {
border: 1px solid var(--huddle-line);
border-radius: 9px;
}

.guidance-confident {
background: var(--huddle-green-soft);
}
.guidance-struggle {
background: var(--huddle-blue-soft);
}
.guidance-footer {
overflow: hidden;
margin: 16px;
}

.guidance-footer .guidance-field + .guidance-field {
border-left: 1px solid var(--huddle-line);
}

.guidance-move {
background: #f5eef1;
}

.finding-intro h3,
.report-context h3,
.teacher-evidence h3 {
Expand Down Expand Up @@ -957,6 +1095,29 @@ blockquote {
grid-template-columns: 1fr;
}

.guidance-fork,
.guidance-footer {
grid-template-columns: 1fr;
}

.guidance-fork {
gap: 10px;
padding: 14px;
}

.guidance-fork::before {
display: none;
}

.guidance-footer {
margin: 14px;
}

.guidance-footer .guidance-field + .guidance-field {
border-top: 1px solid var(--huddle-line);
border-left: 0;
}

.teacher-evidence-item {
grid-template-columns: 1fr;
gap: 6px;
Expand Down
Loading
Loading