DEV Community

Janwi Mittal
Janwi Mittal

Posted on

Building a TypeScript Evidence Tracker for Supplement Claims: A Burntide Case Study

Health-product pages are an interesting data problem.

You can have a product name, an ingredient list, several claimed benefits, scientific references, and a large amount of marketing copy—all on the same page.

The difficult part isn't collecting the information.

The difficult part is keeping different types of information separate.

For example:

Ingredient research
        ≠
Finished-product research
        ≠
Marketing claim
Enter fullscreen mode Exit fullscreen mode

That distinction is easy to lose when information is presented as normal prose.

So I wanted to approach the problem like a developer.

Instead of asking:

"Does this supplement work?"

we'll build a small TypeScript model that asks better questions:

  • What is the product?
  • What ingredients does it contain?
  • What claims are being made?
  • What type of evidence supports an ingredient?
  • Does that evidence apply to the ingredient or the finished product?
  • How confident should we be?
  • What limitations should be displayed?

For the case study, we'll use Burntide, a metabolism-support gummy whose published product information describes ingredients including apple cider vinegar and BHB salts; some Burntide product pages also list green tea extract, cayenne pepper, L-carnitine, and pomegranate extract. Because different product pages currently show different formulations, a production system should treat the exact label as versioned data rather than assuming every page represents the same formula.

Disclaimer: This is a software/data-modeling exercise, not medical or nutritional advice. The scoring approach below is illustrative and is not a validated clinical evidence-rating system.


The Core Problem

Let's say a supplement page contains:

Apple Cider Vinegar
        ↓
Metabolism-related claim
        ↓
Research mentioned
Enter fullscreen mode Exit fullscreen mode

A weak content system might turn that into:

Ingredient has research
        ↓
Therefore product works
Enter fullscreen mode Exit fullscreen mode

That's a bad data transformation.

A better system preserves the relationship:

Product
   ↓
Claim
   ↓
Ingredient
   ↓
Evidence
   ↓
Source
   ↓
Limitations
Enter fullscreen mode Exit fullscreen mode

Now we can inspect every step.


1. Start With a Data Model

The first thing I'd do is define the entities.

type EvidenceLevel =
  | "laboratory"
  | "animal"
  | "observational"
  | "clinical"
  | "systematic-review";

type EvidenceScope =
  | "ingredient"
  | "formulation"
  | "finished-product";

interface Claim {
  ingredient: string;
  claim: string;
  evidence: EvidenceLevel;
  evidenceScope: EvidenceScope;
  sourceQuality: number;
  confidence: number;
  limitations: string[];
}
Enter fullscreen mode Exit fullscreen mode

The important field here is evidenceScope.

Consider these two statements:

BHB has been studied in humans.
Enter fullscreen mode Exit fullscreen mode

and:

Burntide has been clinically shown to cause weight loss.
Enter fullscreen mode Exit fullscreen mode

They are completely different claims.

Our data model needs to know the difference.


2. Represent Evidence Levels

For the prototype, let's assign illustrative weights.

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 values aren't scientific standards.

They're simply application values that let us demonstrate the mechanics of the system.

In a real research application, the methodology would need to be defined independently and validated before being used for meaningful decisions.


3. Add Source Quality

Evidence type isn't the whole story.

We also need to represent the quality of the source.

interface EvidenceSource {
  title: string;
  url: string;
  quality: number;
}
Enter fullscreen mode Exit fullscreen mode

For example:

const source: EvidenceSource = {
  title: "Example peer-reviewed study",
  url: "https://example.com/research",
  quality: 0.90
};
Enter fullscreen mode Exit fullscreen mode

The goal isn't to pretend that one number can perfectly describe scientific quality.

The goal is to make the assumptions visible.


4. Build the Scoring Function

Now we can combine evidence level, source quality, and confidence.

function calculateScore(claim: Claim): number {
  const evidenceScore = evidenceWeight[claim.evidence];

  return (
    evidenceScore *
    claim.sourceQuality *
    claim.confidence
  );
}
Enter fullscreen mode Exit fullscreen mode

We can convert the result into a percentage:

function formatScore(score: number): string {
  return `${Math.round(score * 100)}%`;
}
Enter fullscreen mode Exit fullscreen mode

And classify it:

function classifyScore(score: number): string {
  if (score >= 0.80) return "High";
  if (score >= 0.60) return "Moderate";
  if (score >= 0.40) return "Limited";

  return "Low";
}
Enter fullscreen mode Exit fullscreen mode

Again, these labels describe our prototype's scoring system, not an established medical rating.


5. Create the Burntide Dataset

Now let's create some structured data based on the product information.

One current Burntide page describes a six-ingredient formula:

Apple Cider Vinegar
BHB Salts
Green Tea Extract
Cayenne Pepper
L-Carnitine
Pomegranate Extract
Enter fullscreen mode Exit fullscreen mode

Another current Burntide page describes a more focused formulation centered on apple cider vinegar and BHB salts, with a 525 mg proprietary blend.

That discrepancy is actually useful for our project.

It demonstrates why data versioning matters.

Instead of blindly hardcoding one list, we can represent a product snapshot:

interface ProductSnapshot {
  product: string;
  capturedAt: string;
  sourceUrl: string;
  ingredients: string[];
}
Enter fullscreen mode Exit fullscreen mode

Example:

const burntideSnapshot: ProductSnapshot = {
  product: "Burntide",
  capturedAt: "2026-08-25",
  sourceUrl: "https://eng-burntide.com",
  ingredients: [
    "Apple Cider Vinegar",
    "BHB Salts"
  ]
};
Enter fullscreen mode Exit fullscreen mode

If the label changes later, we create another snapshot.


6. Why Versioning Matters

This is a problem developers encounter everywhere.

A product page today isn't necessarily the same as a product page six months from now.

The following can change:

Ingredient list
Serving size
Product claims
Packaging
Product URL
Manufacturing information
Suggested use
Enter fullscreen mode Exit fullscreen mode

Therefore, this:

product.ingredients
Enter fullscreen mode Exit fullscreen mode

isn't necessarily permanent truth.

A better model is:

product
   
snapshot
   
ingredients
Enter fullscreen mode Exit fullscreen mode

For example:

Burntide
 ├── Snapshot: August 2026
 │      └── Formula A
 │
 └── Snapshot: Future date
        └── Formula B
Enter fullscreen mode Exit fullscreen mode

That's much closer to how you'd design a real product-data system.


7. Model Apple Cider Vinegar Carefully

Let's take one ingredient from the dataset.

const acvClaim: Claim = {
  ingredient: "Apple Cider Vinegar",
  claim: "Has been studied in nutritional and metabolic contexts",
  evidence: "clinical",
  evidenceScope: "ingredient",
  sourceQuality: 0.80,
  confidence: 0.65,
  limitations: [
    "Evidence applies to the ingredient",
    "Study results can vary",
    "Ingredient evidence does not establish finished-product effectiveness"
  ]
};
Enter fullscreen mode Exit fullscreen mode

Notice that we haven't written:

works: true
Enter fullscreen mode Exit fullscreen mode

That's intentional.

We don't want the application to convert nuanced research into a binary answer.


8. Model BHB Separately

BHB is another interesting example.

BHB stands for beta-hydroxybutyrate, a ketone body that the body naturally produces under certain metabolic conditions.

Burntide product information describes BHB salts in mineral-bound forms, including calcium, magnesium, and sodium BHB.

We can represent the ingredient without jumping to a product-level conclusion:

const bhbClaim: Claim = {
  ingredient: "BHB salts",
  claim: "Provides an exogenous source of beta-hydroxybutyrate",
  evidence: "clinical",
  evidenceScope: "ingredient",
  sourceQuality: 0.85,
  confidence: 0.75,
  limitations: [
    "Ingredient-level evidence",
    "Raising circulating ketones is not equivalent to demonstrating body-fat loss",
    "Finished-product effectiveness requires separate evidence"
  ]
};
Enter fullscreen mode Exit fullscreen mode

This is much closer to what we want our system to represent.


9. Add Multiple Claims

Now let's create an array.

const claims: Claim[] = [
  acvClaim,
  bhbClaim
];
Enter fullscreen mode Exit fullscreen mode

We can process the dataset:

const results = claims.map((claim) => {
  const score = calculateScore(claim);

  return {
    ingredient: claim.ingredient,
    evidence: claim.evidence,
    scope: claim.evidenceScope,
    score: formatScore(score),
    classification: classifyScore(score)
  };
});

console.table(results);
Enter fullscreen mode Exit fullscreen mode

The output might look like:

┌────────────────────┬────────────┬────────────┬───────┬─────────────┐
│ ingredient         │ evidence   │ scope      │ score │ class       │
├────────────────────┼────────────┼────────────┼───────┼─────────────┤
│ Apple Cider Vinegar│ clinical   │ ingredient │ 42%   │ Limited     │
│ BHB salts          │ clinical   │ ingredient │ 51%   │ Limited     │
└────────────────────┴────────────┴────────────┴───────┴─────────────┘
Enter fullscreen mode Exit fullscreen mode

The exact output depends on our arbitrary prototype weights.

And that's the point.

The number isn't the conclusion.

It's a prompt to inspect the underlying evidence.


10. Don't Hide the Limitations

I'd actually make limitations a first-class property.

interface Claim {
  ingredient: string;
  claim: string;
  evidence: EvidenceLevel;
  evidenceScope: EvidenceScope;
  sourceQuality: number;
  confidence: number;
  limitations: string[];
}
Enter fullscreen mode Exit fullscreen mode

Then the UI could display:

BHB Salts

Evidence: Clinical
Scope: Ingredient
Confidence: 75%

Limitations:
• Ingredient-level evidence
• Not finished-product evidence
• Outcome depends on study context
Enter fullscreen mode Exit fullscreen mode

This is much better than:

BHB Salts = 75% effective
Enter fullscreen mode Exit fullscreen mode

The second statement creates a level of certainty that the data doesn't support.


11. Separate Marketing Claims From Research Data

This is another important architectural decision.

I'd create two separate entities:

interface ProductClaim {
  product: string;
  text: string;
  source: string;
}

interface ResearchEvidence {
  ingredient: string;
  evidenceLevel: EvidenceLevel;
  source: string;
  findings: string;
}
Enter fullscreen mode Exit fullscreen mode

Then we can connect them:

Burntide claim
      ↓
Relevant ingredient
      ↓
Research evidence
      ↓
Evidence limitations
Enter fullscreen mode Exit fullscreen mode

This prevents a product page from becoming the "scientific source" for its own claims.


12. Add an Evidence Chain

Now we can combine everything.

interface EvidenceChain {
  product: string;
  claim: string;
  ingredient: string;
  evidenceLevel: EvidenceLevel;
  evidenceScope: EvidenceScope;
  source: EvidenceSource;
  confidence: number;
  limitations: string[];
}
Enter fullscreen mode Exit fullscreen mode

A record could look like:

const chain: EvidenceChain = {
  product: "Burntide",
  claim: "Supports metabolic wellness",
  ingredient: "Apple Cider Vinegar",
  evidenceLevel: "clinical",
  evidenceScope: "ingredient",
  source: {
    title: "Research source",
    url: "https://example.com",
    quality: 0.80
  },
  confidence: 0.65,
  limitations: [
    "Ingredient evidence",
    "Not finished-product evidence"
  ]
};
Enter fullscreen mode Exit fullscreen mode

Now we've got a traceable chain from claim to evidence.


13. Turn It Into an API

Once the data model works, the next step is an API.

For example:

GET /api/products/burntide
GET /api/products/burntide/snapshots
GET /api/products/burntide/ingredients
GET /api/products/burntide/claims
GET /api/evidence?ingredient=bhb
GET /api/evidence?ingredient=apple-cider-vinegar
Enter fullscreen mode Exit fullscreen mode

A TypeScript frontend could consume the API:

async function getBurntideData() {
  const response = await fetch(
    "/api/products/burntide"
  );

  if (!response.ok) {
    throw new Error("Failed to fetch product data");
  }

  return response.json();
}
Enter fullscreen mode Exit fullscreen mode

Now the project starts looking like an actual application instead of a static article.


14. Database Schema

For a production version, I'd probably use PostgreSQL.

A basic schema could be:

products
--------
id
name

product_snapshots
-----------------
id
product_id
captured_at
source_url

ingredients
-----------
id
name

product_ingredients
-------------------
snapshot_id
ingredient_id

product_claims
--------------
id
snapshot_id
claim

evidence
--------
id
claim_id
ingredient_id
evidence_level
evidence_scope
confidence

sources
-------
id
evidence_id
title
url
quality
Enter fullscreen mode Exit fullscreen mode

This gives us an important capability:

historical comparison.

We can ask:

What did the product claim last month?
What ingredients were listed then?
Did the formula change?
Did the evidence record change?
Enter fullscreen mode Exit fullscreen mode

That's much more interesting from a software perspective than a simple product-review page.


15. A Simple Frontend

The UI could be deliberately minimal.

For example:

┌─────────────────────────────────────────┐
│ Burntide Evidence Explorer              │
├─────────────────────────────────────────┤
│                                         │
│ Ingredient: BHB Salts                   │
│                                         │
│ Evidence: Clinical                      │
│ Scope: Ingredient                       │
│ Confidence: 75%                         │
│                                         │
│ Limitations                             │
│ • Ingredient-level evidence             │
│ • Product-specific evidence unavailable │
│ • Context matters                       │
│                                         │
└─────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

A user could then click through:

Product
  ↓
Ingredient
  ↓
Claim
  ↓
Study
  ↓
Source
Enter fullscreen mode Exit fullscreen mode

That is the kind of transparency I'd want from a health-information application.


16. The Most Important Limitation

There is a fundamental problem with this entire idea.

A score can look more scientific than it really is.

If my application displays:

Evidence Score: 82%
Enter fullscreen mode Exit fullscreen mode

a user may assume that 82% represents an established scientific probability.

It doesn't.

It's simply the output of an algorithm.

That's why I'd avoid presenting a single "effectiveness percentage."

Instead, I'd show the components:

Evidence type
Source quality
Confidence
Scope
Limitations
Enter fullscreen mode Exit fullscreen mode

The system should expose uncertainty rather than hide it.


17. What I'd Add Next

The prototype could eventually become a much more capable research tool.

Source validation

Check whether cited URLs are still accessible.

DOI support

Store DOI identifiers for academic publications.

Citation metadata

Store:

Title
Authors
Journal
Publication date
DOI
Study type
Population
Outcome
Enter fullscreen mode Exit fullscreen mode

Formula versioning

Track product changes over time.

Search

Search evidence by ingredient.

Human review

Allow an editor to approve or reject extracted claims.

Audit logs

Record who changed a claim and when.

Confidence explanations

Show exactly why a confidence value was assigned.


18. Why Burntide Works as a Case Study

Burntide illustrates a common structure found across consumer health products:

Product
   ↓
Ingredients
   ↓
Ingredient properties
   ↓
Potential mechanisms
   ↓
Marketing claims
Enter fullscreen mode Exit fullscreen mode

Our job isn't to automatically approve or reject that chain.

Our job is to make the chain inspectable.

For example, product information describes Burntide as a metabolism-support gummy and, depending on the current product page, lists either a six-ingredient formula or a more focused apple-cider-vinegar/BHB formulation.

That alone demonstrates why a good information system should preserve:

source
timestamp
formula
claim
Enter fullscreen mode Exit fullscreen mode

instead of treating the latest scraped text as permanent truth.


The Broader Developer Lesson

This project isn't really about supplements.

It's about data modeling under uncertainty.

The same pattern appears in many other domains.

For example:

News article
    ↓
Claim
    ↓
Source
    ↓
Evidence
    ↓
Confidence
Enter fullscreen mode Exit fullscreen mode

Or:

Security report
    ↓
Finding
    ↓
CVE
    ↓
Evidence
    ↓
Severity
Enter fullscreen mode Exit fullscreen mode

Or:

Product
    ↓
Specification
    ↓
Source
    ↓
Verification
    ↓
Confidence
Enter fullscreen mode Exit fullscreen mode

The domain changes.

The underlying engineering problem remains remarkably similar.


Final Thoughts

A conventional product article tends to compress everything into a conclusion:

"This product is good."

A useful information system should do the opposite.

It should expand the conclusion into its underlying components:

What is being claimed?
        ↓
Which ingredient is relevant?
        ↓
What evidence exists?
        ↓
What kind of evidence is it?
        ↓
Does it apply to the ingredient or product?
        ↓
What are the limitations?
Enter fullscreen mode Exit fullscreen mode

Using Burntide as the example makes the architecture concrete, but the same model could be used for many categories of consumer health information.

The interesting part isn't creating another rating.

The interesting part is building a system that makes the reasoning behind the rating visible.

That's the kind of health-data tooling I'd actually want to build.


Note: Burntide is used here as a case-study dataset, not as an endorsement. Product formulations and published claims can change, so a production application should always store the source URL, capture date, and exact label/version. Current product pages show differing formula descriptions, which is precisely why versioned data is important.

Top comments (0)