Skip to content
← All projects
Case study

decision-engine

TypeScriptOpen repository ↗

Problem

Big decisions — changing jobs, doing a master's, moving city — are usually made on intuition, and afterwards nobody remembers which assumptions drove them. That was the problem I wanted to address: a structured record layer that captures a decision's reasoning, its numeric estimates and its alternatives, and that you can come back to later. The real point is not "let an AI decide" but getting the decision's inputs into a schema.

Approach

On Next.js 16 App Router I built a nine-table data model with Prisma + PostgreSQL: users, user_preferences, decisions, decision_options, decision_inputs, analyses, analysis_scores, analysis_checkpoints, decision_history. The heart of it is decision_inputs: seven free-text fields (financial, career, emotional and social impact, alternatives, unknowns, constraints) and seven numeric ones (estimated gain and loss, success probability, uncertainty, stress, growth potential, difficulty of reversal). Analysis output keeps its three scenarios (optimistic, realistic, pessimistic) in Json columns, while scores live as integer columns in a separate analysis_scores table so they stay queryable. Every REST endpoint returns one envelope: {success, message, data}. Auth is a JWT in an httpOnly cookie; each endpoint checks the session with getSession and then whether the record belongs to that user (findFirst scoped by userId), so authorisation is embedded in the data query itself. Input validation is Zod.

Decisions and trade-offs

  • I put every endpoint behind one {success, message, data} envelope, because I wanted the client to read every response through the same contract, produced centrally by ok()/fail() helpers; the alternative — each route shaping its own response — meant fifteen endpoints and fifteen parsing branches.
  • I built sessions as a JWT inside an httpOnly, sameSite=strict cookie (secure in production), because at MVP stage I didn't want to operate a separate auth service or a session table; the alternatives were NextAuth or database-backed sessions, and the trade-off I knowingly accepted is that I can't revoke an individual token server-side, in exchange for no extra infrastructure.
  • I put authorisation inside the query rather than in a separate layer: records are fetched with findFirst({ where: { id, userId: session.userId } }), so someone else's record comes back as "not found" rather than "unauthorised". In a fetch-then-check pattern the check is easy to skip; the alternative of loading the record and comparing ownership by hand was easy to forget.
  • I kept the API vocabulary separate from the database enum: the time horizon is '6_ay' / '1_yil' / '3_yil' on the wire but 'ay_6' / 'yil_1' / 'yil_3' in Prisma, mapped explicitly at the boundary. Prisma enum identifiers cannot begin with a digit, so readable API values and valid enum names could not be the same string. The cost I accepted: that mapping chain is duplicated in both the POST and PUT routes rather than extracted into a shared helper — debt to collect on the next pass.
  • I split the analysis result in two: scenarios in a Json column, scores as integer columns in their own table. Scenario text may change shape, but scores get sorted and filtered; the alternative of one big Json blob would have pushed even a simple query like "decisions with the highest total score" up into the application layer.

Outcome

This is a Phase 1 scaffold and should be presented as one. The plain status: the analysis endpoints are currently MOCKS — /analyze, /compare and /devils-advocate return fixed data with a "(Phase 1 mock)" message, with no real scoring and no model call. So the product is called a decision engine, but the engine isn't written yet; what exists is the frame meant to carry it. What genuinely works: the nine-table Prisma schema, fifteen REST route files, the Zod validation schemas, cookie-JWT auth (register/login), ownership checks embedded in queries, an eight-piece UI component set (Button, Input, Card, ProgressBar, StatBox, EmptyState, ResultCard, ComparisonCard), the route skeleton (splash, onboarding, auth, dashboard, decisions, history, profile), and a seed script that creates a demo user plus four decisions. NO MEASUREMENT: no tests, no users, no performance data. The repo holds two commits, both from 2026-04-12, with no progress since — the honest status is "started, Phase 1 done, Phase 2 not begun".

Stack

Next.js 16 (App Router)React 19TypeScript 5Prisma 6PostgreSQLZod 4Tailwind CSS 4jsonwebtoken + bcryptjs

Sources

Every technical claim on this page was derived from the sources below.

  • https://github.com/msgxr/decision-engine
  • https://github.com/msgxr/decision-engine/blob/main/README.md
  • https://github.com/msgxr/decision-engine/blob/main/docs/phase1-architecture.md
  • https://github.com/msgxr/decision-engine/blob/main/prisma/schema.prisma
  • https://github.com/msgxr/decision-engine/blob/main/prisma/seed.ts
  • https://github.com/msgxr/decision-engine/blob/main/package.json
  • https://github.com/msgxr/decision-engine/blob/main/src/lib/validation.ts
  • https://github.com/msgxr/decision-engine/blob/main/src/lib/auth.ts
  • https://github.com/msgxr/decision-engine/blob/main/src/lib/api-response.ts
  • https://github.com/msgxr/decision-engine/blob/main/src/lib/constants.ts
  • https://github.com/msgxr/decision-engine/blob/main/src/app/api/decisions/route.ts
  • https://github.com/msgxr/decision-engine/blob/main/src/app/api/decisions/%5Bid%5D/analyze/route.ts
  • https://github.com/msgxr/decision-engine/blob/main/src/app/api/decisions/%5Bid%5D/devils-advocate/route.ts

What I could not verify

Points I could not confirm while writing. I keep them visible rather than asserting them silently.

  • lib/projects.ts stack'i ['TypeScript','Node.js','Rule Engine'] diyor — 'Rule Engine' etiketi YANLIŞ: depoda kural motoru yok, analiz uçları sabit mock döndürüyor. Gerçek yığın Next.js 16 + React 19 + Prisma + PostgreSQL + Zod + Tailwind 4. Etiket düzeltilmeli.
  • ÖNEMLİ ÇERÇEVE: constants.ts'teki SUB_SLOGAN "AI destekli senaryo analizi, risk puanlama ve stratejik karar desteği" diyor ve README "dark-theme-first bir SaaS MVP" diyor. Depoda LLM/AI entegrasyonu HİÇ YOK (bağımlılıklarda yapay zekâ SDK'sı bulunmuyor). Bu ifadeler ürün vizyonu; vaka sayfası bunları mevcut yetenek gibi sunmamalı.
  • DOGRULANAMADI: Veritabanının gerçekten migrate edilip çalıştırıldığı. prisma/ altında schema.prisma ve seed.ts var ama migrations dizini depoda YOK; şemanın canlı bir Postgres'e uygulandığına dair kanıt bulunmuyor.
  • Canlı dağıtım YOK (GitHub Pages API 404, package.json'da dağıtım betiği yok, homepage tanımsız). Yerel çalıştırma bir PostgreSQL bağlantısı gerektiriyor.
  • Kodda doğrulanmış güvenlik notu (övgüye çevrilmemeli): JWT imzası process.env.JWT_SECRET yokken "dev-secret" varsayılanına düşüyor. MVP iskeletinde anlaşılır, üretim duruşu olarak sunulamaz.
  • DOGRULANAMADI: docs/phase1-architecture.md "Phase 1 kapsamı" listesini veriyor ama Phase 2 planı, yol haritası ya da analiz algoritmasının nasıl kurulacağına dair belge depoda yok. "Sonraki adım" anlatısı depoya dayandırılamaz.