skip to content
rajneeshsoftware developer · coding since 2014
~/writing/

ai · 3 min read

Spotting risk over time: let code do the maths and AI do the explaining

One lab report tells you little. Ten reports over two years tell a story. Here is how I build systems that track values over time, flag sudden changes and rising risk early, and explain them in plain language.

Look at one blood test and every number might be "normal". Look at the same test over two years and you might see a value slowly climbing toward the edge of the range. No single report shows it. The trend does.

Most people never see that trend. Their reports are PDFs in a drawer or a phone gallery. The idea behind this kind of system is simple: read every report, line them up over time, and notice what a person would miss.

The most important design choice: the model does not decide what is risky. Code does. The model explains it.

The architecture

  1. Reportsmany, over years
  2. Structured valuesone timeline per test
  3. Featurestrend, change, distance to limit
  4. Risk scorerules + model
  5. Explainplain language
  6. Alertwith the numbers behind it
from many reports to one clear, explained alert

The first step, turning PDFs into clean structured values, is its own post: From a scanned PDF to clean JSON. Here I start from the clean data.

Step 1: one timeline per value

Every test result becomes a point on a timeline: the date, the value, the unit and the normal range. Because the values were normalised earlier, results from different labs line up on the same line.

Step 2: turn timelines into features

A feature is a number that describes the timeline. These are the ones that matter most:

  • Trend: is the value going up or down over the last few results?
  • Sudden change: how big is the jump since the last result, compared to normal variation?
  • Distance to the limit: how close is it to the edge of the normal range?
  • Speed: at the current trend, when would it cross that edge?
typescript
type Point = { date: Date; value: number };

const MS_PER_YEAR = 365.25 * 24 * 60 * 60 * 1000;

function slopePerYear(points: Point[]): number {
  if (points.length < 2) return 0; // one result is not a trend
  // simple linear regression: value change per year
  const xs = points.map((p) => p.date.getTime() / MS_PER_YEAR);
  const ys = points.map((p) => p.value);
  const mx = xs.reduce((a, b) => a + b) / xs.length;
  const my = ys.reduce((a, b) => a + b) / ys.length;
  const num = xs.reduce((s, x, i) => s + (x - mx) * (ys[i] - my), 0);
  const den = xs.reduce((s, x) => s + (x - mx) ** 2, 0);
  return den ? num / den : 0;
}

This is plain maths. It is fast, it gives the same answer every time, and you can explain exactly how it works. That is why it should not be done by a language model.

Step 3: score the risk

Features go into a risk score. It starts with clear rules, like "inside the range but rising fast toward the limit". Learned patterns sit on top of the rules: combinations of values that tend to move together before a problem.

Medicines are checked here too. If a new prescription is known to interact with a current one, that is flagged right away. That is a lookup, not a guess.

Step 4: explain it like a human would

Now the model comes in. It gets the facts, already calculated, and one job: explain them simply.

text
Facts:
- Fasting glucose: 92 → 99 → 104 → 109 mg/dL over 26 months
- Normal range: 70 - 99. Last two results above range.
- Trend: +7.8 mg/dL per year.
Explain this to the patient in 3 short sentences. Do not diagnose.
Suggest what to ask their doctor.

The model cannot invent a trend, because it never calculated one. It can only explain the numbers it was given. Every alert shows those numbers next to the explanation, so the person can check it themselves.

codelanguage model
Calculates trendsExplains what they mean
Scores the riskWrites it in plain words
Checks drug interactionsSuggests questions for the doctor
Same answer every timeFriendly and readable
who does what

Reminders that adapt

The same timeline idea works for medicine. If someone often misses their evening dose but rarely the morning one, the reminder moves to a time that works for them. Systems that learn from what people actually do work better than systems that assume a perfect routine.

Summaries for the doctor

The last output is a one-page summary a doctor can read in a minute: what changed, the trends, current medicines and the open questions. Doctors are short on time. Giving them the timeline, instead of a stack of PDFs, is often the most useful thing the whole system does.

What I learned

  • One data point means little. Build timelines.
  • Calculate risk with code you can explain. Never let a language model do the maths.
  • Give the model facts and a narrow job: explain, do not decide.
  • Always show the numbers behind an alert.
  • This kind of system supports doctors. It never replaces them, and it should say so.

This split, code for decisions and AI for language, is the pattern I trust most. It works far beyond health: anywhere a number changes over time and a person needs to understand why it matters.