Companion — MATx (Tom Kabel)
matx-hack teaches modeling — how to derive an equation from a word problem. MATx (github.com/tomkabel/MATx) teaches the technical solving of that equation. Two adjacent links in one chain that fit together naturally.
text ──▶ model (matx-hack) ──▶ equation ──▶ solution (MATx)The full microskill-transition map is on the page Bridge to MATx.
Who’s behind MATx
Section titled “Who’s behind MATx”Tom Kabel — author and developer. Estonian project for lower-secondary school (grades 7–9). Stack: TypeScript + React (frontend) + Express (API) + Postgres + Drizzle ORM. Self-hosted at matxdev.duckdns.org.
Collaboration status. At the time of writing the repository is public but without a LICENSE file. Use of screenshots and descriptions in this guide is agreed with the author over Slack. Any technical integration (see Phase 3 below) requires a separate PR with explicit consent.
What’s inside MATx
Section titled “What’s inside MATx”The actual slugs in the database come from GET /api/topics —
matxdev.duckdns.org/api/topics
(public endpoint, no auth required).
1. abivalemid — Multiplication formulas
Section titled “1. abivalemid — Multiplication formulas”Learn and practise three formulas: square of sum, square of difference, difference of squares.
| Competency (id, name) | Formula |
|---|---|
| 1 — Summa ruut | |
| 2 — Vahe ruut | |
| 3 — Ruutude vahe |
2. protsendid — Percentages
Section titled “2. protsendid — Percentages”Solve percentage problems via the cross-multiplication method.
| Competency (id, name) | What it does |
|---|---|
| 4 — Osa leidmine | Find the part given whole and percentage |
| 5 — Terviku leidmine | Find the whole given part and percentage |
| 6 — Protsendi leidmine | Find the percentage given whole and part |
3. vorrandid — Linear equations with one unknown
Section titled “3. vorrandid — Linear equations with one unknown”Solve equations step by step, writing out each transformation.
| Competency (id, name) | Form / description |
|---|---|
| 7 — Lihtsad võrrandid | |
| 8 — Sulgudega võrrandid | Equations where you must first expand parentheses |
| 9 — Murdudega võrrandid | Equations where you must first eliminate fractions |
Additionally: docs/06-math-topics.md
in Tom’s repo (theoretical description); the formal table schema —
shared/schema.ts.
Why stronger together
Section titled “Why stronger together”| matx-hack (us) | MATx (Tom) | |
|---|---|---|
| Domain | Modeling | Computation |
| Microskills | 9 (defining T1–T3 × three relation types) | 9 (3 topics × 3 competencies) |
| What the student answers | Quantity expressions through | Numeric / symbolic answer |
| What’s tracked | BKT over 9 defining skills | Per-question results + error categories |
| Stack | Astro + React + Next.js (web/) | React + Express + Postgres |
| Audience | Estonian school, grade 7 | Estonian school, grades 7–9 |
| Content language | ET / RU / EN | ET |
Cognitively — our student finishes the phase “understand the problem and write down the equation” and naturally moves on to “solve the equation to a number”. Without MATx that second phase stays verbal in our book; without us, MATx assumes the equation has somehow already been derived.
Where to try it
Section titled “Where to try it”Live dev instance: matxdev.duckdns.org. There are no public pages — sign-in is required (teacher/student role). Tom can spin up a demo account on request.
The most informative views (after login)
Section titled “The most informative views (after login)”/dashboard— Your learning path Student home. Three topic cards (Multiplication formulas / Percentages / Linear equations), each with its ownmastery%and progress bar. The Recommended-tag marks the current ZPD target. After PR-1, that tag will be driven by thestudent_skill_stateposterior — today it’s a plain% correct./teacher/analytics— Competency heatmap Teacher’s overview: each row a student, each column a microskill, colors<50% / 50-74% / 75-89% / 90%+. Today the % is computed directly fromresults. After PR-1, every cell is a Bayesian posteriorpKnown— smoother (doesn’t bounce on individual attempts) and auditable (anyone can replay(prior, isCorrect, params)from the log). TabsHeatmap / Students / Alertscover different analytics angles./topic/3/practice-flow— practice tutorial Step-by-step walkthrough of clearing fractions out of an equation. Gives a feel for how a problem looks on screen before the student enters an answer. PR-1’screateResulthooks into exactly that endpoint, where the answer is persisted and the BKT update runs.
Direct topic links
Section titled “Direct topic links”- matxdev.duckdns.org/topics/abivalemid — Multiplication formulas
- matxdev.duckdns.org/topics/protsendid — Percentages
- matxdev.duckdns.org/topics/vorrandid — Linear equations
Public API without authentication
Section titled “Public API without authentication”/api/topics— list of topics (id, slug, title, description)/api/topics/:slug— topic with the competencies array
Integration roadmap
Section titled “Integration roadmap”Phase 1 — book pages (done in this release)
Section titled “Phase 1 — book pages (done in this release)”- Skill-bridge mapping — 9→9 map with rationale in three languages
-
data/matx-bridge.json— single source of truth for the mapping - This companion page
Phase 2 — cross-links and deep-links (done)
Section titled “Phase 2 — cross-links and deep-links (done)”- Inline “Companion: MATx” cards on key pages: home, Multi-skill, Simulator, Q&A
- Direct deep-links to the three topics by slug + the public API
- iframe embed decided not to be done:
connect.sidships withSameSite=Lax, so the logged-in flow doesn’t survive in an iframe — without it the embed is useless. Better to open in a new tab.
Phase 3 — BKT model in MATx submit-handler (PR plan)
Section titled “Phase 3 — BKT model in MATx submit-handler (PR plan)”Idea. Lift our BKT math into a pure-TypeScript package bkt-core
(zero runtime deps), Tom adds it as a single dependency and a single
call inside storage.createResult. No network hops, no ML
infrastructure. Mergeable as two independent PRs.
What ports into bkt-core
Section titled “What ports into bkt-core”| Source in matx-hack | What | Size |
|---|---|---|
web/lib/microskills.ts | Types BktParams, MicroSkill, Task, StudentState + the DEFAULT_BKT constant | ~25 lines, no deps |
web/lib/bkt.ts | bktUpdate, pSolve, ensureMastery, applyAttempt, scoreTaskForStudent, recommend | ~150 lines, imports types only |
The package’s package.json carries only name, main, types, tsc.
Zero production dependencies — pure math.
PR 1 — bookkeeping (minimum, ~70 lines, UI unchanged)
Section titled “PR 1 — bookkeeping (minimum, ~70 lines, UI unchanged)”Each attempt updates the closed-form BKT for the corresponding competency. MATx’s user-visible behavior does not change — only a new field appears in the database.
1. Drizzle migration — new table:
CREATE TABLE student_skill_state ( user_id text NOT NULL REFERENCES users(id) ON DELETE CASCADE, competency_id integer NOT NULL REFERENCES competencies(id) ON DELETE CASCADE, p_known double precision NOT NULL DEFAULT 0.2, attempts integer NOT NULL DEFAULT 0, last_updated timestamp DEFAULT now(), PRIMARY KEY (user_id, competency_id));Zero-downtime (new table, nothing locks). Applied via the existing
npm run db:push.
2. shared/schema.ts — add the table definition (~15 lines):
export const studentSkillState = pgTable("student_skill_state", { userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), competencyId: integer("competency_id").notNull().references(() => competencies.id, { onDelete: "cascade" }), pKnown: doublePrecision("p_known").notNull().default(0.2), attempts: integer("attempts").notNull().default(0), lastUpdated: timestamp("last_updated").defaultNow(),}, (t) => ({ pk: primaryKey({ columns: [t.userId, t.competencyId] }) }));3. server/storage.ts.createResult (current, lines 150–152) — add an upsert after insert (~20 lines):
import { bktUpdate, DEFAULT_BKT } from "@matx-hack/bkt-core";import { studentSkillState } from "@shared/schema";
async createResult(result: CreateResultRequest) { const [savedResult] = await db.insert(results).values(result).returning();
// — BKT update — const q = await this.getQuestion(result.questionId); if (q?.competencyId) { const [cur] = await db.select().from(studentSkillState).where(and( eq(studentSkillState.userId, result.userId), eq(studentSkillState.competencyId, q.competencyId), )).limit(1); const prior = cur?.pKnown ?? DEFAULT_BKT.pInit; const posterior = bktUpdate(prior, result.isCorrect, DEFAULT_BKT); await db.insert(studentSkillState).values({ userId: result.userId, competencyId: q.competencyId, pKnown: posterior, attempts: 1, lastUpdated: new Date(), }).onConflictDoUpdate({ target: [studentSkillState.userId, studentSkillState.competencyId], set: { pKnown: posterior, attempts: sql`${studentSkillState.attempts} + 1`, lastUpdated: new Date() }, }); }
return savedResult;}server/routes.ts (submit-handler at lines 292–345) is not touched — the update is encapsulated in the storage layer.
PR 2 — smarter recommendation (~80 lines, optional)
Section titled “PR 2 — smarter recommendation (~80 lines, optional)”Replace the heuristic in getNextRecommendedQuestion (current, lines 878–940) with BKT-driven selection via the ZPD target .
The current logic — three buckets notStarted / needsWork (mastery < 60%) / progressing (60–90%) with selection by mastery asc — survives as a mapping layer back to Tom’s Estonian explanations (Harjuta..., Kinnista..., Korda...) so the UI/UX stays put. Under the hood — our recommend(state, tasks):
import { recommend } from "@matx-hack/bkt-core";
async getNextRecommendedQuestion(userId, topicId) { const skillStates = await db.select().from(studentSkillState) .innerJoin(competencies, eq(studentSkillState.competencyId, competencies.id)) .where(eq(competencies.topicId, topicId)); const state = { student_id: userId, mastery: Object.fromEntries(skillStates.map(s => [String(s.competency_id), s.p_known])), history: [], }; const qs = await db.select().from(questions).where(eq(questions.topicId, topicId)); const tasks = qs.map(q => ({ id: String(q.id), topic: String(topicId), microskills: q.competencyId ? [String(q.competencyId)] : [], difficulty: 0.5, prompt_et: q.text, answer: "", })); const top = recommend(state, tasks, 1)[0]; // … map score+pSolve back to existing reason buckets for UX continuity}What’s done, what’s left
Section titled “What’s done, what’s left”| Piece | Status |
|---|---|
Extract bkt-core as a package | ✅ Done |
Add submodule into tomkabel/MATx | ✅ Tom did this himself |
| PR-1 BKT bookkeeping | ✅ Merged + deployed |
| PR-2 ZPD-targeted recommendation | ✅ Merged + deployed |
| PR-3 Defineerimine + 28 tasks | ✅ Merged + deployed |
| PR-4 UI: heatmap pKnown, “Why recommended?”, 10 mock students | ✅ Merged + deployed |
| PR-5 audit_trace + sparkline + column sort | ✅ Merged + deployed |
| PR-6 student detail page + BKT alerts + microskill bookkeeping | ✅ Merged + deployed |
| PR-7 microskill UI (detail-page section + new heatmap tab + mock seed) | ✅ Merged + deployed |
| Teacher feedback iteration | ⏳ In progress |
Audit trail for the EU AI Act
Section titled “Audit trail for the EU AI Act”bktUpdate is a closed analytical formula. At any point an auditor can
take (prior, observation, params) from the log and independently
replay the posterior. No “black box” justification needed.
Optionally: add an audit_trace JSONB column to student_skill_state —
it accumulates a history of (timestamp, prior, isCorrect, posterior)
tuples. This closes Annex III traceability + explainability
requirements without separate compliance infrastructure.
EU AI Act compliance
Section titled “EU AI Act compliance”Education / vocational training is a high-risk Annex III category under Regulation (EU) 2024/1689. Requirements on traceability, documentation, human oversight and explainability come into force in stages from August 2026 to August 2027.
Our part of the pipeline is already fully deterministic — no LLM call exists in the production path:
- BKT update — closed analytical Bayes expression.
- Explanations — a template engine (
web/lib/explain.ts) with a fixed set of phrases. - Selector — argmax over model confidence + topology rules (skill graph).
After Phase 3 MATx inherits the same profile automatically: a single
line bktUpdate(...) in the submit-handler — and the audit trail for
the next-question decision is built without separate compliance
infrastructure.
On top of that, eatf.eu — model attestation, response signing, audit in one API call — can be layered for centralised artefact storage.
Related pages
Section titled “Related pages”- Progression matrix — our 9-microskill defining-skills model.
- Bridge to MATx — the microskill ↔ MATx-competency table and the Mermaid transition graph.
- Selector simulator — how BKT picks the next defining microskill; after Phase 3 it will also reference MATx competencies.
- Glossary — terms (microskill, competency, BKT etc).