Перейти к содержимому

Companion — MATx (Tom Kabel)

matx-hack учит моделированию — как из словесной задачи получить уравнение. MATx (github.com/tomkabel/MATx) учит решать это уравнение технически. Это два соседних звена одной цепочки, и они стыкуются естественно.

текст ──▶ модель (matx-hack) ──▶ уравнение ──▶ решение (MATx)

Полная карта переходов между микронавыками — на странице Мост в MATx.

Tom Kabel — автор и разработчик. Проект эстонский, для основной школы (7–9 класс). Стек: TypeScript + React (frontend) + Express (API) + Postgres + Drizzle ORM. Хостится на собственной инфраструктуре по адресу matxdev.duckdns.org.

Статус коллаборации. На момент написания репозиторий публичный, но без LICENSE-файла. Использование скриншотов и описаний в этом учебнике согласовано с автором в Slack. Любая техническая интеграция (см. ниже Phase 3) требует отдельного PR с явным согласием.

Три топика с девятью competencies (термин Tom’а) — это его эквивалент наших микронавыков:

Реальные slugs в БД сняты через GET /api/topicsmatxdev.duckdns.org/api/topics (публичный endpoint, без аутентификации).

Õpi ja harjuta kolme abivalemit: summa ruut, vahe ruut ja ruutude vahe.

Competency (id, name)Формула
1 — Summa ruut(a+b)2=a2+2ab+b2(a + b)^2 = a^2 + 2ab + b^2
2 — Vahe ruut(ab)2=a22ab+b2(a - b)^2 = a^2 - 2ab + b^2
3 — Ruutude vahea2b2=(a+b)(ab)a^2 - b^2 = (a + b)(a - b)

Открыть topic →

Lahenda protsentülesandeid ristkorrutise meetodil.

Competency (id, name)Что делает
4 — Osa leidmineLeia osa, kui tervik ja protsent on teada
5 — Terviku leidmineLeia tervik, kui osa ja protsent on teada
6 — Protsendi leidmineLeia protsent, kui tervik ja osa on teada

Открыть topic →

Lahenda võrrandeid samm-sammult, kirjutades välja iga teisenduse.

Competency (id, name)Форма / описание
7 — Lihtsad võrrandidax+b=cax + b = c
8 — Sulgudega võrrandidVõrrandid, kus tuleb esmalt sulud avada
9 — Murdudega võrrandidVõrrandid, kus tuleb esmalt murrud kaotada

Открыть topic →

Дополнительно: docs/06-math-topics.md у Tom’а (теоретическое описание); формальная схема таблиц — shared/schema.ts.

matx-hack (мы)MATx (Tom)
ДоменМоделированиеВычисление
Микронавыки9 (defining T1–T3 × три типа отношений)9 (3 топика × 3 competency)
Что отвечает ученикЗаписать выражения через xxЧисленный/символьный ответ
Что отслеживаетBKT по 9 defining-skillsPer-question results + error categories
СтекAstro + React + Next.js (web/)React + Express + Postgres
АудиторияЭстонская школа, 7 классЭстонская школа, 7–9 классы
Язык контентаET / RU / ENET

Когнитивно — наш ученик завершает фазу «понять задачу и записать уравнение», после чего естественно переходит к фазе «решить уравнение до числа». Без MATx эта вторая фаза в нашей книге остаётся словесной; без нас MATx предполагает, что уравнение уже как-то получено.

Живой dev-инстанс: matxdev.duckdns.org. Публичных страниц нет — нужно зайти под учётной записью (учитель/ученик). Tom разворачивает demo-аккаунт по запросу.

Прямые ссылки на topic-страницы (после логина):

Публичный API без аутентификации:

  • /api/topics — список топиков (id, slug, title, description)
  • /api/topics/:slug — топик с массивом competencies
  • Skill-bridge mapping — карта 9→9 с обоснованием на трёх языках
  • data/matx-bridge.json — single source of truth для маппинга
  • Эта companion-страница
  • Inline-карточки «Companion: MATx» на ключевых страницах: главная, Multi-skill, Симулятор, Q&A
  • Прямые deep-links на три topic’а через slug + публичный API
  • iframe-embed решили не делать: connect.sid идёт с SameSite=Lax, поэтому залогиненный flow в iframe не работает — без него embed бесполезен. Лучше открыть в новой вкладке.

Идея. Вынести нашу BKT-математику в pure-TypeScript пакет bkt-core (zero runtime deps), Tom добавляет его одной зависимостью и одним вызовом в storage.createResult. Никаких сетевых hop’ов, никакой ML-инфраструктуры. Мерджабельно в два самостоятельных PR’а.

Источник в matx-hackЧтоОбъём
web/lib/microskills.tsТипы BktParams, MicroSkill, Task, StudentState + константа DEFAULT_BKT~25 строк, без deps
web/lib/bkt.tsbktUpdate, pSolve, ensureMastery, applyAttempt, scoreTaskForStudent, recommend~150 строк, импортит только типы

package.json пакета содержит только name, main, types, tsc. Никаких production-зависимостей — это голая математика.

Каждый attempt обновляет закрытую формулу BKT для соответствующей competency. Видимое поведение MATx не меняется — только в БД появляется новое поле.

1. Drizzle migration — новая таблица:

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)
);

Зеро-downtime (новая таблица, ничего не блокирует). Применяется через существующий npm run db:push.

2. shared/schema.ts — добавить определение таблицы (~15 строк):

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 (текущий, строки 150–152) — добавить upsert после insert (~20 строк):

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 на строках 292–345) не трогается — обновление инкапсулировано в storage layer.

Заменить эвристику в getNextRecommendedQuestion (текущая, строки 878–940) на BKT-driven выбор через ZPD-target P(solve)0.7P(solve) \approx 0.7.

Текущая логика — три бакета notStarted / needsWork (mastery < 60%) / progressing (60–90%) с выбором по mastery asc — сохраняется как mapping слой обратно в Tom-овские эстонские объяснения (Harjuta..., Kinnista..., Korda...), чтобы UI/UX не менялся. Под капотом — наш 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
}
  1. Согласие на PR в tomkabel/MATx (можно как experimental branch / feature flag).
  2. Возможность миграции БД — это npm run db:push, zero-downtime.
  3. (Опц.) Tom сам пишет PR, мы отдаём bkt-core как git submodule или npm pack-tarball для локальной установки до публикации в npm.

bktUpdate — закрытая аналитическая формула. В любой момент аудитор может взять (prior, observation, params) из логов и независимо переиграть posterior. Никакого “black-box” обоснования не требуется.

Опционально: добавить audit_trace JSONB в student_skill_state — там накапливается история (timestamp, prior, isCorrect, posterior) тапл’ов. Это закрывает Annex III требования traceability + explainability без отдельной compliance-инфраструктуры.

Education / vocational training — high-risk Annex III категория по Regulation (EU) 2024/1689. Нормы на traceability, documentation, human oversight и explainability вступают в силу поэтапно с августа 2026 по август 2027.

Наша часть конвейера уже сейчас полностью детерминистична — нет ни одного LLM-вызова в production-пути:

  • BKT-обновление — закрытое аналитическое выражение по Байесу.
  • Объяснения — шаблонный движок (web/lib/explain.ts) с фиксированным набором фраз.
  • Селектор — argmax по уверенности модели + правила topology (skill-graph).

После Phase 3 MATx наследует тот же профиль автоматически: одна строка bktUpdate(...) в submit-handler — и audit-trail про принятие решения о next-question строится без отдельной compliance-инфраструктуры.

Дополнительно поверх можно подключить eatf.eu — model attestation, response signing, audit одним API-вызовом — для централизованного хранения artefact’ов.