Skip to content

feat(engine): scoring engine with normalised-distance match#3

Merged
KnotzerIO merged 3 commits into
mainfrom
feat/scoring-engine
Jun 27, 2026
Merged

feat(engine): scoring engine with normalised-distance match#3
KnotzerIO merged 3 commits into
mainfrom
feat/scoring-engine

Conversation

@KnotzerIO

Copy link
Copy Markdown
Owner

Summary

Adds @matchachoice/engine — a framework-free, zero-runtime-dependency package with the v1 scoring logic. Type-only imports of the shared domain shapes from @matchachoice/db/schema/types.

Three pure functions:

  • evaluateVisibility(rule, answers)and/or condition groups; operators equals, not_equals, greater_than, less_than, contains (string + multiple-choice string[]), empty. Absent/empty rule = visible.
  • scoreAnswers(check, answers) — per-category sum of the chosen options' scores, over visible questions only.
  • computeResults(check, answers) — normalised-distance match%, hard exclusions, descending stable sort. Returns { resultId, matchPct, excluded }[].

Match% algorithm

  • dist_c = |userScore_c − target_c| / range_c
  • range_c = max achievable score per category, auto-derived from the check definition (sum of the highest option score per question). Not configurable.
  • match% = 100 × (1 − avg(dist_c)), averaged only over the result's target categories, clamped to [0, 100].
  • range_c = 0 → category skipped. No averageable category → match% = 0.

Hard exclusions

A matching exclusion rule flags the result excluded: true, keeps its match%, and sinks it below all non-excluded results (not dropped — the caller decides). Empty/absent exclusion never excludes.

Tests / DoD

  • Vitest, 100% coverage (lines/branches/functions/statements) on the engine logic.
  • Covered: every operator, nested and/or combinations, multiple-choice (string[]) for contains + scoring, missing/empty answers, category with max=0 (skip), result without targets, all results excluded, stable tie ordering, over-range clamp.
  • Developed test-first (red → green → refactor).
  • pnpm check, pnpm check-types, pnpm test, SKIP_ENV_VALIDATION=1 pnpm build — all green locally.
  • Wired a turbo test task + root test script + a CI Test step so coverage is gated on every PR.

Notes for reviewer

  • RankedResult keeps excluded results (flagged) rather than filtering them — the field would be meaningless otherwise.
  • Two documented edge decisions: result with no averageable categories → match% = 0; the shared RuleGroup type is flat, so true nested groups are out of scope for v1.
  • Out of scope: weighted categories, configurable ranges, JSONLogic, rule debugger.

Pure-function package, no runtime deps. Vitest with 100% coverage
thresholds, tsconfig extending the shared base, and framework-free
engine types that reuse the shared domain shapes (type-only).
- evaluateVisibility: and/or condition groups; operators equals,
  not_equals, greater_than, less_than, contains (string + string[]),
  empty. Absent/empty rule = visible.
- scoreAnswers: per-category sum of chosen options' scores over visible
  questions only.
- computeResults: normalised-distance match% (range = max achievable per
  category, auto-derived), averaged over the result's target categories,
  clamped to [0,100]; hard exclusions flagged and sunk; stable sort.

Developed test-first; 100% line/branch/function coverage.
Add a turbo 'test' task and root 'test' script, and a Test step in the
CI workflow so engine coverage is gated on every PR.
@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
matchachoice Ready Ready Preview, Comment Jun 26, 2026 10:58pm

@KnotzerIO KnotzerIO changed the title feat(engine): scoring engine with normalised-distance match% feat(engine): scoring engine with normalised-distance match Jun 27, 2026
@KnotzerIO
KnotzerIO merged commit ec7330c into main Jun 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant