DEV Community

Cover image for How I Built a Simple Evidence-Scoring System for Health Product Claims
Janwi Mittal
Janwi Mittal

Posted on

How I Built a Simple Evidence-Scoring System for Health Product Claims

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

Notice something important here.

We're not storing:

works: true
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

We might represent sources like this:

const source: EvidenceSource = {
  name: "Peer-reviewed publication",
  quality: 0.9
};
Enter fullscreen mode Exit fullscreen mode

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
  );
}
Enter fullscreen mode Exit fullscreen mode

Let's test it.

const score = calculateScore(exampleClaim);

console.log(score);
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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";
}
Enter fullscreen mode Exit fullscreen mode

Then:

const score = calculateScore(exampleClaim);

console.log({
  score: percentage(score),
  classification: classifyScore(score)
});
Enter fullscreen mode Exit fullscreen mode

We now have a very basic evidence-processing pipeline:

Raw claim
   ↓
Structured data
   ↓
Evidence classification
   ↓
Source weighting
   ↓
Confidence adjustment
   ↓
Score
   ↓
Human-readable result
Enter fullscreen mode Exit fullscreen mode

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
  }
];
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Now we can distinguish:

Tea tree oil
    ↓
Ingredient-level evidence
Enter fullscreen mode Exit fullscreen mode

from:

Nail Refresh
    ↓
Finished-product evidence
Enter fullscreen mode Exit fullscreen mode

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"
];
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Instead:

Ingredient exists
       ↓
Find evidence
       ↓
Classify evidence
       ↓
Record limitations
       ↓
Display result
Enter fullscreen mode Exit fullscreen mode

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[];
}
Enter fullscreen mode Exit fullscreen mode

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"
  ]
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
  };
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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                —
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

rather than simply:

Evidence Score: 82%
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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?"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)