Building a scoring engine with pure TypeScript functions (no ML, no backend)
<p>We needed to score e-commerce products across multiple dimensions: quality, profitability, market conditions, and risk.</p> <p>The constraints:</p> <ul> <li>Scores must update in real time</li> <li>Must run entirely in the browser (Chrome extension)</li> <li>Must be explainable (not a black box)</li> </ul> <p>We almost built an ML pipeline — training data, model serving, APIs, everything.</p> <p>Then we asked a simple question:</p> <p><strong>Do we actually need machine learning for this?</strong></p> <p>The answer was no.</p> <p>We ended up building several scoring engines in pure TypeScript.<br> Each one is a single function, under 100 lines, zero dependencies, and runs in under a millisecond.</p> <h2> What "pure function" means here </h2> <p>Each scoring engine follows 3 rules:</p> <
We needed to score e-commerce products across multiple dimensions: quality, profitability, market conditions, and risk.
The constraints:
-
Scores must update in real time
-
Must run entirely in the browser (Chrome extension)
-
Must be explainable (not a black box)
We almost built an ML pipeline — training data, model serving, APIs, everything.
Then we asked a simple question:
Do we actually need machine learning for this?
The answer was no.
We ended up building several scoring engines in pure TypeScript. Each one is a single function, under 100 lines, zero dependencies, and runs in under a millisecond.
What "pure function" means here
Each scoring engine follows 3 rules:
-
No I/O → no network, no DB, no files
-
Deterministic → same input = same output
-
No side effects → no global state, no mutations
This makes them:
-
Easy to test
-
Easy to reason about
-
Portable (browser, Node.js, anywhere)
Core pattern: weighted scoring
interface ScoringInput { qualityScore: number | null; profitScore: number | null; marketScore: number | null; riskScore: number | null; }interface ScoringInput { qualityScore: number | null; profitScore: number | null; marketScore: number | null; riskScore: number | null; }type Verdict = 'strong_buy' | 'buy' | 'hold' | 'pass';
function computeScore(input: ScoringInput) { const quality = input.qualityScore ?? 50; const profit = input.profitScore ?? 50; const market = input.marketScore ?? 50; const risk = input.riskScore ?? 50;
const overall = Math.round( quality * 0.3 + profit * 0.3 + market * 0.2 + risk * 0.2 );
let verdict: Verdict; if (overall >= 80) verdict = 'strong_buy'; else if (overall >= 60) verdict = 'buy'; else if (overall >= 40) verdict = 'hold'; else verdict = 'pass';
return { overall, verdict }; }`
Enter fullscreen mode
Exit fullscreen mode
Handling missing data (critical)
All inputs are nullable.
We default to 50 (neutral).
Why not:
-
Skip missing values → breaks comparability
-
Default 0 → unfairly penalizes
-
Default 100 → artificially inflates
Neutral = safest assumption.
Normalization + clamp
All scores must be 0–100.
function clamp(value: number, min: number, max: number) { return Math.max(min, Math.min(max, value)); }function clamp(value: number, min: number, max: number) { return Math.max(min, Math.min(max, value)); }const profitScore = clamp(marginPercent * 2, 0, 100); const marketScore = clamp(100 - saturationPercent, 0, 100); const riskScore = clamp(100 - rawRiskScore, 0, 100);`*
Enter fullscreen mode
Exit fullscreen mode
Without clamp:
-
values can exceed bounds
-
negative values break logic
-
NaN propagates silently
Choosing weights
Not all dimensions are equal.
We weighted:
-
Quality + Profit → higher (controllable)
-
Market + Risk → lower (external factors)
We considered user-configurable weights but dropped it: → too complex for non-technical users
Threshold calibration
Initial thresholds (75 / 50 / 25) were too optimistic.
We:
-
Scored hundreds of products
-
Compared with human judgment
-
Iterated
Lesson: Never guess thresholds — calibrate them.
Composition > monolith
We built multiple small engines:
-
Product score
-
Market score
-
Platform score
Then combine:
function computeFinalVerdict( productScore: number | null, marketScore: number | null, platformScore: number | null ) { const product = productScore ?? 50; const market = marketScore ?? 50; const platform = platformScore ?? 50;function computeFinalVerdict( productScore: number | null, marketScore: number | null, platformScore: number | null ) { const product = productScore ?? 50; const market = marketScore ?? 50; const platform = platformScore ?? 50;const score = Math.round( market * 0.4 + product * 0.35 + platform * 0.25 );*
const confidence = Math.round( Math.min(product, market, platform) * 0.8 + 20 );*
const reasons: string[] = [];
if (market >= 70) reasons.push('Favorable market conditions'); if (market < 40) reasons.push('Challenging market'); if (product >= 70) reasons.push('Strong product'); if (product < 40) reasons.push('Weak product');
return { score, confidence, reasons }; }`
Enter fullscreen mode
Exit fullscreen mode
Key ideas:
-
Confidence = weakest dimension
-
Reasons = explainability
Example
Input:
-
Quality: 75
-
Profit: 84
-
Market: 65
-
Risk: 80
Result:
-
Score: 77
-
Verdict: buy
If profit increases → score crosses 80 → strong_buy
This kind of reasoning is trivial with pure functions, impossible with black-box ML.
When you SHOULD use ML
Use ML if:
-
You analyze images or text
-
You need pattern discovery
-
You have high-dimensional data (50+ features)
Otherwise: → pure functions are simpler, faster, more transparent
Key takeaways
-
Start with pure functions
-
Default missing data to neutral
-
Always clamp values
-
Weight by controllability
-
Compose small engines
-
Calibrate with real data
No training data. No APIs. No latency. Runs in-browser in under 1ms.
Not for every problem — but for structured scoring, it’s hard to beat.
Curious: Have you used similar scoring patterns? Or did you go with ML instead?
DEV Community
https://dev.to/cs_alishopping/building-a-scoring-engine-with-pure-typescript-functions-no-ml-no-backend-3hclSign in to highlight and annotate this article

Conversation starters
Daily AI Digest
Get the top 5 AI stories delivered to your inbox every morning.
More about
modeltrainingupdate
Speculative decoding works great for Gemma 4 31B in llama.cpp
I get a ~11% speed up with Gemma 3 270B as the draft model. Try it by adding: --no-mmproj -hfd unsloth/gemma-3-270m-it-GGUF:Q8_0 Testing with (on a 3090): ./build/bin/llama-cli -hf unsloth/gemma-4-31B-it-GGUF:Q4_1 --jinja --temp 1.0 --top-p 0.95 --top-k 64 -ngl 1000 -st -f prompt.txt --no-mmproj -hfd unsloth/gemma-3-270m-it-GGUF:Q8_0 Gave me: [ Prompt: 607.3 t/s | Generation: 36.6 t/s ] draft acceptance rate = 0.44015 ( 820 accepted / 1863 generated) vs. [ Prompt: 613.8 t/s | Generation: 32.9 t/s ] submitted by /u/Leopold_Boom [link] [comments]

Gemma 4 - 4B vs Qwen 3.5 - 9B ?
Hello! anyone tried the 4B Gemma 4 model and the Qwen 3.5 9B model and can tell us their feedback? On the benchmark Qwen seems to be doing better, but I would appreciate any personal experience on the matter Thanks! submitted by /u/No-Mud-1902 [link] [comments]

Speed difference on Gemma 4 26B-A4B between Bartowski Q4_K_M and Unsloth Q4_K_XL
I've noticed this on Qwen3.5 35B before as well, there is a noticeable speed difference between Unsloth's Q4_K_XL and Bartowski's Q4_K_M on the same model, but Gemma 4 seems particularly harsh in this regard: Bartowski gets 38 tk/s, Unsloth gets 28 tk/s... everything else is the same, settings wise. This is with the latest Unsloth quant update and latest llama.cpp version. Their size is only ~100 MB apart. Anyone have any idea why this speed difference is there? Btw, on Qwen3.5 35B I noticed that Unsloth's own Q4_K_M was also a bit faster than the Q4_K_XL, but there it was more like 39 vs 42 tk/s. submitted by /u/BelgianDramaLlama86 [link] [comments]
Knowledge Map
Connected Articles — Knowledge Graph
This article is connected to other articles through shared AI topics and tags.
More in Products
![Best OCR for template-based form extraction? [D]](https://d2xsxph8kpxj0f.cloudfront.net/310419663032563854/konzwo8nGf8Z4uZsMefwMr/default-img-robot-hand-JvPW6jsLFTCtkgtb97Kys5.webp)
Best OCR for template-based form extraction? [D]
Hi, I’m working on a school project and I’m currently testing OCR tools for forms. The documents are mostly structured or semi-structured forms, similar to application/registration forms with labeled fields and sections. My idea is that an admin uploads a template of the document first, then a user uploads a completed form, and the system extracts the data from it. After extraction, the user reviews the result, checks if the fields are correct, and edits anything that was read incorrectly. So I’m looking for an OCR/document understanding tool that can work well for template-based extraction, but also has some flexibility in case document layouts change later on. Right now I’m trying Google Document AI , and I’m planning to test PaddleOCR next. I wanted to ask what OCR tools you’d recommend

Automating Repetitive Tasks with Workany
Automating the Mundane: An Introduction to Workany Are you tired of the endless cycle of repetitive computer tasks? The constant clicking, copying, and setup procedures can drain your energy and detract from more impactful work. What if you could simply articulate your needs to your computer, and it would autonomously execute the required steps? This is the compelling proposition of Workany. The Promise of Workany Workany is an open-source initiative dedicated to revolutionizing how we approach digital workflows. Its core mission is to automate tedious and repetitive tasks, allowing users to reallocate their cognitive resources towards innovation, strategy, and complex problem-solving. By integrating AI-driven capabilities, Workany aims to create a more seamless and efficient interaction w

AI Code Review Is the New Bottleneck: Why Faster Code Is Not Reaching Production Faster
A developer on my team opened eleven pull requests last Tuesday. Eleven. In a single day. Two years ago, that same developer averaged two or three PRs per week. The difference is not that he suddenly became five times more productive. The difference is Claude Code. He describes a feature, the agent implements it, he reviews the diff, and he opens the PR. The code-writing part of his job accelerated by an order of magnitude. The problem is what happened next. Those eleven PRs sat in review for an average of four days. Three of them took over a week. By the time the last one was approved and merged, the branch had conflicts with main that took another hour to resolve. He shipped more code than ever. The code reached production at roughly the same pace as before. And the two senior engineers



Discussion
Sign in to join the discussion
No comments yet — be the first to share your thoughts!