Skip to content

naufaldi/teacher-exam

Repository files navigation

Ujian SD

AI-generated, print-ready exam sheets for Indonesian elementary-school teachers — pair-built with Opus 4.7, GPT 5.5, and Composer 2.5; core exam generation runs on OpenAI GPT-5.4-mini.

Live API Built with Opus 4.7 Built with GPT 5.5 Built with Composer 2.5 Exam AI Stack License: MIT

Ujian SD turns one sentence ("PPKn kelas 5, sumatif tengah semester, sila ke-3, 20 soal") into an A4-ready exam sheet — mixed item types, answer key, and a teacher-facing pembahasan (explanation) for every question. One click → PDF on the photocopier.

Indonesian SD teachers spend 4–8 hours a week hand-writing exams in Word. Ujian SD compresses that into a 30-second generate + a 2-minute review.

Screenshots

Captured during local dev verification (source: .agent-browser/; committed copies in screenshots/).

Landing Review + Penjaga Kurikulum Fast review Preview & cetak
Landing — masuk & contoh lembar A4 Review — konfirmasi paket & periksa kurikulum Review — mode cepat, soal perlu review Preview — soal, LJ, kunci, pembahasan

Why this exists

Generic LLM chatbots produce text that sounds like an exam but fails on the constraints that matter to a teacher:

  • the right Kurikulum Merdeka Capaian Pembelajaran + Tujuan Pembelajaran codes,
  • vocabulary appropriate for a 10-year-old reader,
  • pembahasan a teacher can actually read aloud in class,
  • consistent item types and weights across all 20 soal in a single sheet.

This project bakes those constraints into the prompt and the schema so what comes out is what a guru would print, not what an LLM would freestyle.

Features

  • Curriculum-grounded generation — pulls from parsed Kurikulum Merdeka PDFs (per CP/TP), not free-form.
  • Mixed item typesmcq, mcq_multi, true_false, short_answer, essay in one sheet, validated by Effect Schema.
  • Pembahasan for every soal — written for a 10-year-old listener, not for an answer key.
  • Vocabulary blacklist — academic Indonesian words that shouldn't appear in 5th–6th grade material are rejected up front.
  • A4 print preview — same JSON drives the on-screen sheet and the PDF; what you preview is what hits the photocopier.
  • History + duplicate-as-draft — fork last term's sheet, swap the topic, re-generate.
  • Google sign-in — better-auth, single click, no passwords.

Stack

Layer Tech
Frontend React 19, Vite 8, TanStack Router (file-based), Tailwind v4, Radix primitives
Backend Hono v4 on Node 22, Effect-TS service layers, Drizzle ORM (Postgres), better-auth
AI OpenAI GPT-5.4-mini for core exam generate + pembahasan/validation when AI_PROVIDER=openai; Effect Schema structured output; also supports Anthropic Claude and MiniMax via AI_PROVIDER (see .env.example)
Shared types @teacher-exam/shared Effect Schemas — single source of truth between API and web
Tooling pnpm 10, Turborepo, Vitest, TypeScript 6 (strict + exactOptionalPropertyTypes)
Infra Single VPS, Docker Compose, Caddy reverse-proxy with auto Let's Encrypt

Monorepo layout

apps/
  api/              Hono REST API + Effect-TS layers + better-auth
  web/              React 19 SPA (TanStack Router)
packages/
  shared/           Effect Schema validation contracts (the contract between api and web)
  db/               Drizzle ORM schemas + migrations
  ui/               Radix + CVA + tailwind-merge component library
docs/
  PRD-v2-final.md   Product requirements & scope
  ops/              Deployment, infra, and incident notes

Local dev

Requires Node ≥ 22 and pnpm 10.15+.

pnpm install
cp .env.example .env          # then fill in DATABASE_URL, GOOGLE_*, set AI_PROVIDER, and paste your AI key(s)
pnpm db:migrate
pnpm dev                      # web :5173  ·  api :3000
Task Command
Dev all pnpm dev
Build all pnpm build
Type-check pnpm type-check
Test (Vitest) pnpm test
New migration pnpm db:generate
Run migration pnpm db:migrate

After pulling changes that add a new mapel (subject enum), run pnpm db:migrate before testing generate for that subject.

See apps/api/CLAUDE.md and apps/web/CLAUDE.md for per-package conventions, and docs/ops/PRODUCTION.md for the deploy reference (Caddy, Docker labels, Cloudflare DNS, env vars).

Architecture notes

  • Effect-TS everywhere a boundary exists. API service layers, error handling (Data.TaggedError), and validation (Schema) are mandatory across apps/api, apps/web, and packages/shared. No Zod, no try/catch inside Effect code.
  • Schema-first. Every entity is defined as an Effect Schema in packages/shared, the type is derived (type X = typeof XSchema.Type), and both ends of the wire validate with Schema.decodeUnknownEither.
  • Subdomain-split deploy. Web on ujian-sekolah.faldi.xyz, API on api-ujian-sekolah.faldi.xyz, single Caddy reverse-proxy reading Docker labels. Better-auth sits on the API host with cross-subdomain session cookies.

Built with AI coding agents

This project was pair-built end-to-end with Claude Code + Opus 4.7, GPT 5.5, and Composer 2.5 in Cursor, using a mix of specialised skills (agent-browser, effect-ts-expert, frontend-design, test-driven-development) and the Plan workflow for non-trivial changes. Every frontend task ends with an agent-browser loop — drive the running app, capture console errors, fix, re-drive — so "did this actually work in the browser?" is verified, not assumed.

Originally submitted for the Cerebral Valley · Built with Opus 4.7 hackathon; later iteration and shipping also used GPT 5.5 and Composer 2.5 in Cursor.

License

MIT © 2026 Naufaldi Rafif

Releases

No releases published

Packages

 
 
 

Contributors

Languages