A daily metal "rate" looks like one number on a page. Under the hood it is a small data pipeline, and in my experience most of the bugs are not in the fetch step. They are in the units.
This is a purely methodological note. I am not quoting any live prices, tax rates or dealer margins here, because those change and are exactly the parts people copy-paste without checking.
Start from one fixing, not from a scraped list
The cleanest design is to pick a single authoritative wholesale fixing for a defined fineness, in a defined unit, for a defined trading day. Everything else on the page is derived from that one value.
If you instead scrape several "silver price today" widgets and average them, you have silently mixed:
- different fineness assumptions,
- different units (per gram vs per 10 grams vs per kilogram),
- wholesale versus retail quotes,
- and different as-of dates.
You cannot reconcile those after the fact. Pick one input, document it, and derive.
The conversion table is the real schema
Once the base is fixed, the derived numbers are arithmetic. A useful mental model is to treat every published figure as a row with four columns: unit, fineness, quote type, as-of date.
For a base expressed per kilogram of 999 fine metal:
| Published unit | Divisor from the base |
|---|---|
| per 100 grams | 10 |
| per 10 grams | 100 |
| per gram | 1000 |
| per tola | 1000 / grams-per-tola |
Two traps worth writing tests for:
- Fineness is a multiplier, not a new source. 925 and 999 describe the metal content of an object, not a different market price. Applying the fineness fraction to a number that is already retail-quoted double counts it.
- Making charges are not part of the metal. A jewellery bill contains a craftsmanship component that resale usually does not return. If you fold that into a "price per gram" and then compare it with a wholesale fixing, the comparison is meaningless.
So the invariant to assert in code is boring but effective: per_kg == per_g * 1000 == per_10g * 100, and every derived row must carry the same as-of date as its source.
Why a retail quote never equals the benchmark
Readers often treat the difference as an error. It is not. A retail quote can legitimately include a dealer premium, a making charge, applicable tax, and a buyback policy that prices the same object differently on the way out. A benchmark is a reference for the metal content, not a price you can transact at.
That distinction belongs in the UI, not in a footnote. The most useful thing a price page can show is the basis of each number it prints: what unit, what fineness, what quote type, what date.
Data quality problems that actually bite
- Trading day vs calendar day. A fixing published on Monday may be Friday's session after a holiday. Store both the publication date and the reference date.
- AM vs PM sessions. Some sources publish more than one fixing per day. If you pick one, record which one; if you switch, you have silently changed the series.
- Time zones. "Today" in the source's locale is not "today" for the reader. Normalise to a single zone before generating slugs or filenames.
- Caching and staleness. A cached HTML widget can look perfectly valid and be a day old. Persist the fetched timestamp next to the value, and refuse to render if it is older than your threshold.
- Anti-bot responses. A 403 is not a missing price. Treat "blocked" as a distinct state from "no data", because retrying a blocked fetch with a different parser will not help.
- Silent unit changes upstream. If a source switches from per-kilogram to per-10-grams, an unconverted pipeline will happily publish a number 100× too small. Assert plausible ranges.
A small validation checklist
- One documented base source, one fineness, one unit, one as-of date.
- Derived values recomputed from the base, never re-scraped.
- Unit-consistency assertions in the build step.
- Explicit separation of benchmark, spot-derived conversion, and retail quote.
- Reference date displayed next to every table, not just in the page footer.
- A visible "no current data" state instead of a stale number.
None of this is glamorous, but it is the difference between a page that survives an audit and one that quietly publishes the wrong number for weeks.
Wrapping up
If you maintain any kind of daily price or rate page, the highest-value work is almost never adding another source. It is making the units, the fineness and the as-of date impossible to misunderstand — for the reader and for your own future self.
General methodology notes, not financial, tax or purchase advice. Rules and costs change; verify against primary source documents before relying on any figure.
Further reading
If you want to see these ideas applied to a live data page, the data methodology page for India's daily silver benchmark documents the base source, reference dates and unit conversions, and the per-gram, 10-gram and 1kg explainer shows the arithmetic without any live rate assumptions.