Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🔥 Hotwash

CI License: Apache-2.0

Turn end-of-course critiques into a ranked after-action worklist — what to sustain, what to improve, and which fixes are quick wins versus structural redesign.

Every course ends with a pile of critiques, and most of them go into a drawer. Hotwash reads that pile and answers the questions an instructor actually has: What worked? What hurt the class the most? And is this the same thing we said we'd fix last cycle? It grades the course, not the student — and because it looks across class iterations, it gets sharper every time you run it.

The analytics are pure and deterministic (no model, no network, no key). An optional narrative step will write the AAR up as prose using any OpenAI-compatible model, with a deterministic memo fallback so it always returns something useful offline.

Install

npm install hotwash

Requires Node 18+. ES modules only.

Quick start

import { hotwash } from 'hotwash';

const critiques = [
  { area: 'Fire-order clarity', kind: 'improve', severity: 4, iteration: '2025-3' },
  { area: 'Fire-order clarity', kind: 'improve', severity: 5, iteration: '2026-1' },
  { area: 'Fire-order clarity', kind: 'improve', severity: 5, iteration: '2026-2' },
  { area: 'Comms lab bench time', kind: 'improve', severity: 3, iteration: '2025-3' },
  { area: 'Comms lab bench time', kind: 'improve', severity: 1, iteration: '2026-2' },
  { area: 'Instructor prep', kind: 'sustain', iteration: '2026-2' },
];

const report = hotwash({ critiques });
// report.improves[0] →
// { area: 'Fire-order clarity', mentions: 3, meanSeverity: 4.667,
//   impact: 14, priority: 1, horizon: 'long_term', trend: 'worsening' }
// report.sustains → [ { area: 'Instructor prep', mentions: 1 } ]
// report.meta → { critiques: 6, areas: 3, iterations: 3, shortTerm: 1, longTerm: 1 }

What a "critique" is

A single piece of end-of-course feedback:

field meaning
area what it's about (also accepts topic / lesson / module) — the grouping key
kind 'sustain' (worked, keep it) or 'improve' (fix it). Defaults to improve.
severity 1..5, how badly it hurt the class (improve only). Defaults to 3, clamped into range. Numeric strings ('4') are coerced; a non-numeric string ('high') throws TypeError; a NaN severity falls back to the default 3.
iteration which class cycle it came from (e.g. '2026-1', '2025-Q4', or 1) — unlocks the trend read. Cycles are ordered chronologically, never by input order (see below).
weight optional multiplier for feedback that speaks for many (e.g. a class-wide vote). Defaults to 1; a finite weight >= 0 is honored (0 legitimately zeroes a critique); a negative or non-numeric weight throws TypeError.

How it ranks

impact = mentions × mean severity — how often it came up, times how bad it was. priority is that impact normalized 0..1 against the worst finding, so it's comparable across courses.

horizon sorts each finding into a fix window:

  • short_term — fix it before the next class. The default.
  • long_term — the area recurs across two or more iterations without improving: a structural problem a single patch won't solve. This is the signal that only shows up because Hotwash looks across cycles.

Trend across iterations

import { trendByArea } from 'hotwash';

trendByArea(critiques);
// [ { area: 'Fire-order clarity', direction: 'worsening', delta: 1,
//     iterations: [ { iteration: '2025-3', improve: 1, meanSeverity: 4 }, ... ] },
//   { area: 'Comms lab bench time', direction: 'improving', delta: -2, ... } ]

The signal is mean improve-severity per iteration. improving means the latest cycle hurt less than the first; worsening the reverse; flat within an epsilon dead-band; single when there isn't enough history to call it.

Iterations are always ordered chronologically, never by the order critiques happen to appear in the input: plain numbers sort numerically, calendar labels YYYY-N and YYYY-QN sort by year then period (so '2025-Q4' before '2026-Q1'), and any other strings sort with a numeric-aware compare ('wk2' before 'wk10'). This keeps the trend direction correct even when the pile is unsorted.

Write it up

import { narrativeAAR } from 'hotwash';

const { memo, source } = await narrativeAAR(report, { course: 'Basic Electronics Course (28xx)' });
// source: 'heuristic' offline, or 'model' when HOTWASH_API_KEY / OPENROUTER_API_KEY is set
console.log(memo);
// AFTER-ACTION REVIEW — Basic Electronics Course (28xx)
//
// SUSTAIN — what worked, keep it
//   • Instructor prep (1)
//
// IMPROVE — before the next class (short-term)
//   1. Comms lab bench time — 2 mentions · severity 2 · improving
//
// IMPROVE — course redesign (long-term)
//   1. Fire-order clarity — 3 mentions · severity 4.667 · worsening — recurring across iterations

Configure the model with HOTWASH_API_KEY (or OPENROUTER_API_KEY), HOTWASH_ENDPOINT, and HOTWASH_MODEL, or pass { apiKey, endpoint, model }. Pass { heuristicOnly: true } to force the offline memo. A hung endpoint is bounded by timeoutMs (default 15000, or HOTWASH_TIMEOUT_MS): on timeout — as on any network error — narrativeAAR degrades to the deterministic heuristic memo and never throws on I/O.

API

export purpose
hotwash({ critiques, epsilon? }) the whole report: sustains, ranked improves, per-area rollup, meta
rankFindings(critiques, opts?) ranked improve findings with impact, priority, horizon, trend
rollup(critiques) raw per-area counts, mean severity, improve rate
trendByArea(critiques, opts?) per-area direction across iterations
sustains(critiques) what worked, most-mentioned first
narrativeAAR(report, opts?) prose memo (model or heuristic); resolves, never throws on I/O
heuristicMemo(report, opts?) the deterministic memo directly

Every analytics function validates its input and throws TypeError on a bad shape.

meta.iterations counts the distinct iteration labels across all critiques (how many class cycles are represented) — sustain and improve alike. The per-area trend read is improve-only, so an individual area's trend may span fewer iterations than this course-wide count.

Run the demo

node example/demo.mjs

License

Apache-2.0.

Releases

Packages

Contributors

Languages