Register Signal

Three public registers. 39,130 records. One of them says nothing.

Regulatory-services business development has no trigger events. Software sales has renewal dates, recruiting has job postings; this has conferences and relationships. So you call to say “we do regulatory services”, which is what everyone says, and there is no reason to call today. Public medicines registers are full of reasons nobody reads.

The code → Why Tanzania returns nothing

DuckDB  ·  35 commits  ·  10 test files  ·  public regulatory data only

What it produces

Dated, named reasons to contact a specific company.

Four trigger types, ranked in this order because a loss already taken outranks a future obligation, which outranks a sign of momentum.

ExpiredMarket access already lost. Recoverable only by re-filing.
Renewal dueA dated obligation. The filing happens or the product leaves the market.
Recently grantedThe company is actively spending on regulatory work.
First entryA company’s first registration in this market. They are expanding.

The sources

Every figure traces to a hashed, dated snapshot of the bytes it came from.

Nothing downstream reads a live URL. A number on this page can be tied to a specific retrieval, which is the whole reason the snapshot layer exists.

9,008Nigeria, NAFDAC Greenbook4,147 triggers
29,274Brazil, ANVISA2,752 triggers
848Tanzania, TMDA0 triggers
6,899Triggers in totalacross three registers

The zero

Tanzania returning nothing is the system working, not failing.

TMDA publishes neither expiry dates nor reliable dates of any kind. Every trigger this system emits carries a date and the evidence for it, so a source that cannot support a dated claim does not get to make one. The alternative was to infer dates from registration numbers, which would have produced 848 confident rows that no one could check.

It is the smallest source and the only one that produces nothing, which makes it the honest test of whether the rule is real.

What a gap in a register means

Exactly one reading is supportable, and it is not the interesting one.

What the data supports

  • not found in TMDA source, retrieved 2026-08-30
  • registration expired 2026-03-14; recorded inactive in NAFDAC, retrieved 2026-09-01

What it does not

  • not registered in Tanzania
  • failed to renew

Companies discontinue products deliberately. The register records the date and never the reason, so a report that says “failed to renew” has invented a motive. src/rri/language.py blocks eight phrasings, the generators run it against their own output before returning, and the test suite runs it over every generated artifact. A report that overclaims raises rather than being written.

Decisions worth defending

Four places where the obvious approach produces a plausible wrong answer.

01

Company matching is token-based, never substring

Searching for "sun pharma" inside applicant names matches Anisun Pharmaceutical Company Limited, inventing a corporate relationship that does not exist. Substring matching on company names is the single easiest way to fabricate a lead.

02

Diffing keys on the register’s row id, not the registration number

In NAFDAC, 263 registration numbers are shared across 538 records, and one number covers two unrelated products. Keying a diff on the registration number silently merges them and reports movement that never happened.

03

No vector database

Structured extraction into DuckDB is auditable line by line. A RAG stack would add embedding drift to explain and buy nothing, on a problem whose entire output is dates and company names.

04

TLS verification is never disabled

ANVISA serves an incomplete certificate chain. The fix was to supply the missing intermediate, not to stop checking. A pipeline whose claim is traceability cannot make unauthenticated fetches.

How it is built

Every source is declared in config/sources.yaml; nothing is fetched that is not there. Adding market N+1 is one adapter class, not a new codebase.

config/sources.yaml     every source declared
        
  adapters              fetch, map, and declare limits
        
  snapshot              raw bytes + sha256 + retrieval date
        
  normalise             INN · strength · form · company
        
  triggers              expired · renewal due · granted · first entry
        
  diff                  suppressed entirely if a snapshot shrank
        
  output                self-checks its own language before returning

The diff suppression earned its place: a partial fetch looks exactly like a register that removed a thousand products overnight. When a verified run did remove records, 36 registrations came out and reconciled exactly with the record-count shortfall, which is the check that tells the two cases apart.

What it is not

Registered is not marketed. A product can hold an active registration and never reach a shelf, so a trigger is a reason to make a call and not evidence of revenue. Five further sources were assessed and rejected, and docs/decisions.md records each one and why.

Read the code →

← Back to portfolio