How I Built a Simple Evidence-Scoring System for Health Product Claims
TL;DR: Health-product pages contain lots of claims, but those claims are difficult to compare consistently. In this post, we'll turn claims into structured data and build a small TypeScript scoring system that considers evidence type, source quality, and confidence.
I've spent a fair amount of time looking at product pages where the ingredient list is easy to find, but the actual evidence behind individual claims is much harder to evaluate.
A typical page might contain:
Ingredient → claimed benefit → scientific-sounding explanation
The problem is that these three things aren't necessarily equivalent.
An ingredient can have laboratory research behind it without proving that a particular finished product produces the same result.
That sounded like a good candidate for a small programming project.
Instead of manually asking whether every claim "sounds credible", why not represent the information as structured data and create a transparent scoring model?
That's what we're going to build.
The Problem
Suppose we have a topical nail-care product containing several botanical ingredients.
For example, Nail Refresh publicly describes a formula containing ingredients including tea tree oil, thyme oil, oregano oil, clove bud oil, jojoba oil, sweet almond oil, rosehip oil, aloe vera, glycerin, cedarwood oil, menthol, bearberry extract, jasmine, and lavender.
The product information can be reviewed directly at nailrefrsh.com.
The interesting programming question isn't:
"Does this product work?"
That's too broad.
Instead, let's ask:
"How can software represent the claims, ingredients, and evidence associated with a product without pretending that the data is more certain than it actually is?"
That's a much more interesting engineering problem.
Step 1: Define the Data Model
The first thing I want is a structure that represents an individual claim.
Here's a simple TypeScript interface:
type EvidenceLevel =
| "laboratory"
| "animal"
| "observational"
| "clinical"
| "systematic-review";
interface Claim {
ingredient: string;
claim: string;
evidence: EvidenceLevel;
sourceQuality: number;
confidence: number;
}
Now a claim can be represented as data rather than prose.
For example:
const exampleClaim: Claim = {
ingredient: "Tea tree oil",
claim: "Has been investigated for antimicrobial activity",
evidence: "laboratory",
sourceQuality: 0.8,
confidence: 0.65
};
Notice something important here.
We're not storing:
works: true
That would be far too simplistic.
Instead, we're storing information about the type and strength of evidence.
Step 2: Don't Treat All Evidence as Equal
This is where the scoring system becomes useful.
A laboratory experiment, observational study, randomized clinical trial, and systematic review should not receive identical weights.
We can create a simple mapping:
const evidenceWeight: Record<EvidenceLevel, number> = {
laboratory: 0.25,
animal: 0.35,
observational: 0.50,
clinical: 0.80,
"systematic-review": 1.00
};
These numbers aren't medical standards.
They're simply an example scoring model for demonstrating how the software works.
That's an important distinction.
We're building an information-ranking tool, not a clinical decision system.
Step 3: Add Source Quality
Evidence type isn't the only variable.
Two sources can discuss similar research while having very different levels of reliability.
So let's add a source-quality factor.
interface EvidenceSource {
name: string;
quality: number;
url?: string;
}
We might represent sources like this:
const source: EvidenceSource = {
name: "Peer-reviewed publication",
quality: 0.9
};
Again, this is an application-level classification rather than a universal scientific scoring standard.
The goal is transparency.
Someone looking at the result should be able to understand why a claim received a particular score.
Step 4: Build the Scoring Function
Now we can combine the variables.
function calculateScore(claim: Claim): number {
const evidenceScore = evidenceWeight[claim.evidence];
return (
evidenceScore *
claim.sourceQuality *
claim.confidence
);
}
Let's test it.
const score = calculateScore(exampleClaim);
console.log(score);
The output will be a number between 0 and 1.
We can convert that into a percentage:
function percentage(score: number): string {
return `${Math.round(score * 100)}%`;
}
console.log(percentage(score));
Now the data becomes easier to display in a UI.
Step 5: Add a Human-Readable Classification
A raw number isn't particularly useful to most users.
Let's turn the score into categories.
function classifyScore(score: number): string {
if (score >= 0.8) return "High";
if (score >= 0.6) return "Moderate";
if (score >= 0.4) return "Limited";
return "Low";
}
Then:
const score = calculateScore(exampleClaim);
console.log({
score: percentage(score),
classification: classifyScore(score)
});
We now have a very basic evidence-processing pipeline:
Raw claim
↓
Structured data
↓
Evidence classification
↓
Source weighting
↓
Confidence adjustment
↓
Score
↓
Human-readable result
Step 6: Model Multiple Ingredients
A real product isn't represented by one claim.
Let's create an array.
const claims: Claim[] = [
{
ingredient: "Tea tree oil",
claim: "Investigated for antimicrobial activity",
evidence: "laboratory",
sourceQuality: 0.8,
confidence: 0.65
},
{
ingredient: "Clove oil",
claim: "Contains compounds studied for biological activity",
evidence: "laboratory",
sourceQuality: 0.75,
confidence: 0.60
},
{
ingredient: "Aloe vera",
claim: "Used in topical formulations for skin conditioning",
evidence: "clinical",
sourceQuality: 0.80,
confidence: 0.70
}
];
We can process everything with map():
const results = claims.map(claim => {
const score = calculateScore(claim);
return {
ingredient: claim.ingredient,
score: percentage(score),
classification: classifyScore(score)
};
});
console.table(results);
That's already enough to build a basic browser interface.
Step 7: Keep Ingredient Evidence Separate From Product Evidence
This is arguably the most important part of the project.
Imagine that research exists for tea tree oil.
That does not automatically prove that Nail Refresh, or any other commercial product containing tea tree oil, produces the same outcome.
So let's introduce another field:
type EvidenceScope =
| "ingredient"
| "formulation"
| "finished-product";
interface Claim {
ingredient: string;
claim: string;
evidence: EvidenceLevel;
evidenceScope: EvidenceScope;
sourceQuality: number;
confidence: number;
}
Now we can distinguish:
Tea tree oil
↓
Ingredient-level evidence
from:
Nail Refresh
↓
Finished-product evidence
Those are different datasets.
This prevents one of the most common errors in automated health-content systems:
Taking evidence about an ingredient and presenting it as evidence for an entire product.
Step 8: Add the Case Study
Now we can use Nail Refresh as a practical test case.
The product's public information describes a topical formula containing botanical oils and conditioning ingredients.
Instead of turning those ingredients into medical conclusions, we can simply store them:
const nailRefreshIngredients = [
"Tea tree oil",
"Thyme oil",
"Oregano oil",
"Clove bud oil",
"Jojoba oil",
"Sweet almond oil",
"Rosehip oil",
"Aloe vera",
"Glycerin",
"Cedarwood oil",
"Menthol",
"Bearberry extract",
"Jasmine",
"Lavender"
];
Now the same data-processing system can be used to investigate each ingredient independently.
The important point is that the program isn't saying:
Ingredient exists → product works
Instead:
Ingredient exists
↓
Find evidence
↓
Classify evidence
↓
Record limitations
↓
Display result
That's a much safer architecture.
Step 9: Store the Limitations
One thing I'd add to a production version is an explicit limitations field.
interface Claim {
ingredient: string;
claim: string;
evidence: EvidenceLevel;
evidenceScope: EvidenceScope;
sourceQuality: number;
confidence: number;
limitations: string[];
}
Example:
const claim: Claim = {
ingredient: "Tea tree oil",
claim: "Investigated for antimicrobial activity",
evidence: "laboratory",
evidenceScope: "ingredient",
sourceQuality: 0.8,
confidence: 0.65,
limitations: [
"Ingredient-level evidence",
"Laboratory findings may not predict clinical outcomes",
"Finished-product evidence may differ"
]
};
This is much better than displaying a single green "verified" badge.
Health information is rarely binary.
What I Would Build Next
The prototype above is intentionally simple.
A production version could become much more interesting.
1. Add a Database
Instead of hardcoding the claims, store them in PostgreSQL or SQLite.
A possible schema:
products
--------
id
name
url
ingredients
-----------
id
name
claims
------
id
product_id
ingredient_id
claim
evidence_level
evidence_scope
confidence
sources
-------
id
claim_id
title
url
source_quality
Now the application can support hundreds of products.
2. Add Source Verification
A future version could validate whether a source URL is accessible.
For example:
async function checkSource(url: string) {
const response = await fetch(url);
return {
url,
status: response.status,
accessible: response.ok
};
}
In a real application, I'd also add:
- timeout handling
- retries
- redirects
- rate limiting
- caching
- robots.txt considerations
- logging
3. Add an Evidence API
The next step could be a backend service.
Something like:
GET /api/products/nail-refresh
GET /api/products/nail-refresh/ingredients
GET /api/claims?ingredient=tea-tree-oil
GET /api/evidence?ingredient=tea-tree-oil
The frontend could then render an evidence dashboard.
What the UI Could Look Like
A simple interface could display:
Nail Refresh
────────────────────────────
Ingredient Evidence
Tea tree oil Limited
Thyme oil Limited
Oregano oil Limited
Clove oil Limited
Jojoba oil —
Aloe vera Moderate
Glycerin —
The dash is intentional.
Not every ingredient needs a biological "effectiveness score."
Some ingredients exist primarily for formulation, texture, moisturizing, stability, or other practical purposes.
That distinction makes the data more useful.
The Biggest Problem With Automated Health Scoring
There's a trap here.
Once we have a number, people will want to believe the number is authoritative.
It isn't.
A score of 82% can look scientific even when the underlying model is subjective.
That's why I'd expose the inputs:
Evidence: Clinical
Source quality: 0.82
Confidence: 0.71
Scope: Ingredient
Limitations: 3
rather than simply:
Evidence Score: 82%
Explainability is more valuable than fake precision.
Why This Project Is Useful Beyond Nail Care
The same architecture can be used to analyze many types of consumer products.
For example:
Product
↓
Ingredients
↓
Claims
↓
Evidence
↓
Sources
↓
Confidence
↓
Limitations
That could eventually become a general-purpose research tool.
It could work with:
- skincare products
- supplements
- fitness products
- nutrition products
- personal-care products
- wellness devices
The underlying engineering problem stays similar.
A Better Mental Model
I started this project thinking the main challenge would be scoring.
It isn't.
The difficult part is modeling uncertainty correctly.
A good system shouldn't try to turn complicated scientific evidence into a magical number.
It should help users answer better questions.
For example:
Bad question:
"Does this product work?"
Better questions:
"What does the product claim?"
"What evidence supports that claim?"
"What type of evidence is it?"
"Does the evidence apply to the ingredient or the finished product?"
"What are the limitations?"
That change in thinking is probably more valuable than the scoring algorithm itself.
Final Thoughts
A health-product page is essentially an unstructured information source.
There are claims, ingredients, studies, marketing language, citations, and limitations mixed together.
As developers, we can make that information easier to analyze by turning it into structured data.
The Nail Refresh example demonstrates the concept nicely: instead of blindly repeating product claims, we can model ingredients, evidence levels, source quality, confidence, and limitations separately.
The result isn't a medical diagnosis engine.
It's something much simpler—and potentially more useful:
a transparent framework for asking better questions about online health information.
If I were taking this project further, my next step would be building a small TypeScript + PostgreSQL API and a dashboard that lets users inspect the evidence chain from:
Product
↓
Claim
↓
Ingredient
↓
Evidence
↓
Source
↓
Limitations
That's where the project starts becoming genuinely interesting from an engineering perspective.
Disclaimer: This post is an educational programming example, not medical advice. The Nail Refresh information used here is based on publicly available product information and should not be interpreted as proof that the product diagnoses, treats, cures, or prevents a medical condition.
Top comments (0)