<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Artur Azevedo</title>
    <description>The latest articles on DEV Community by Artur Azevedo (@indiasilvernotes).</description>
    <link>https://dev.to/indiasilvernotes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4229802%2F37defb40-9af1-4adc-a4c0-a335868c073d.png</url>
      <title>DEV Community: Artur Azevedo</title>
      <link>https://dev.to/indiasilvernotes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/indiasilvernotes"/>
    <language>en</language>
    <item>
      <title>Building a State-Level Rollup From OpenStreetMap Surveillance Tags</title>
      <dc:creator>Artur Azevedo</dc:creator>
      <pubDate>Fri, 06 Nov 2026 11:40:37 +0000</pubDate>
      <link>https://dev.to/indiasilvernotes/-building-a-state-level-rollup-from-openstreetmap-surveillance-tags-2ob5</link>
      <guid>https://dev.to/indiasilvernotes/-building-a-state-level-rollup-from-openstreetmap-surveillance-tags-2ob5</guid>
      <description>&lt;p&gt;If you have ever built a "counts by state" page on top of OpenStreetMap data, you learn quickly that the hard part is not the query. It is deciding what a count means, and then making every derived number carry that meaning with it.&lt;/p&gt;

&lt;p&gt;This is a write-up of the invariants I found useful. No numbers here, because any total I printed would be stale by the time you read it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start with the tag contract, not the map
&lt;/h3&gt;

&lt;p&gt;The source is a shared, volunteer-maintained dataset. Contributors place a node at the physical location of a device and tag it as a surveillance feature, with a further tag identifying the device type as ALPR so it can be separated from generic CCTV. Where someone could tell, they also record the manufacturer, the compass bearing, the mounting, and a check date.&lt;/p&gt;

&lt;p&gt;That is a loose schema. Fields are optional, values are free text, and contributors do not coordinate. Your pipeline has to assume that any field can be missing or malformed. A record with no bearing is still a valid record. A record with a free-text manufacturer you cannot normalize is still a valid record. Do not filter those out unless you are explicit that your page shows a subset.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://flock-camera.com/methodology/" rel="noopener noreferrer"&gt;data methodology page for this map&lt;/a&gt; documents which records are included and why, which is the first thing a reader should be able to check.&lt;/p&gt;

&lt;h3&gt;
  
  
  The invariant that catches most bugs
&lt;/h3&gt;

&lt;p&gt;Once you derive per-state rows from a single extract, every row must be traceable to the same source snapshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;every derived row has the same as_of date as its source extract
sum of per_state counts == count of included nodes in that extract
count of state pages == count of distinct state keys in that extract
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If those three do not hold, you either double-counted nodes that sit on a boundary, dropped rows with a missing state key, or mixed two extracts. Assert all three in the build step and fail loudly. It is a boring check and it catches nearly everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Boundaries are a data problem, not a geometry problem
&lt;/h3&gt;

&lt;p&gt;Assigning a point to a state sounds like a point-in-polygon call until you hit the edges: water, roads that run along a border, and nodes tagged slightly off-position by a contributor standing on the opposite kerb. Pick one rule, write it down, and apply it identically to every node. Whether you use administrative boundaries or a nearest-centroid fallback matters less than being consistent and saying which one you used.&lt;/p&gt;

&lt;p&gt;A point near a border that you assign differently from a competing map is not a bug in either project. It is a different documented rule. That is one reason published totals disagree.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why totals disagree between projects
&lt;/h3&gt;

&lt;p&gt;If two sites built on the same open dataset show different numbers, the usual causes are, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Different extract dates. The dataset changes daily as volunteers add entries.&lt;/li&gt;
&lt;li&gt;Different inclusion rules: all surveillance nodes, or only those with the ALPR type tag; all vendors, or one manufacturer.&lt;/li&gt;
&lt;li&gt;Different geography: US only, or worldwide; whether the District of Columbia is its own row.&lt;/li&gt;
&lt;li&gt;Different deduplication, especially where a contributor added both a node and a way.&lt;/li&gt;
&lt;li&gt;Different boundary assignment, as above.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these implies someone is wrong. It means "count of documented cameras" is a derived metric with a definition attached. Print the definition next to the number, and print the extract date with it. A count without an as-of date is not a fact you can compare.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://flock-camera.com/cameras/" rel="noopener noreferrer"&gt;state-level breakdown&lt;/a&gt; is an example of putting the rollup next to its own footnote instead of letting the number stand alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  License and attribution are part of the schema
&lt;/h3&gt;

&lt;p&gt;The dataset is distributed under the Open Database License. If you republish a derived extract, attribution and share-alike obligations travel with it. Put the credit and the license in the page template, not in a changelog nobody reads, and keep a plain link back to the source project.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I would do differently
&lt;/h3&gt;

&lt;p&gt;On a first pass I treated "unknown manufacturer" as a category to display. It is not a category, it is an absence, and readers consistently misread it as "a different vendor". Same for missing bearings: rendering a cone only for nodes that have one is fine, but the page has to say that cones represent recorded bearings, not an estimate.&lt;/p&gt;

&lt;p&gt;The general lesson: every derived field should either carry its provenance or be visibly marked as unknown. In a volunteer dataset, absence of evidence is the most common value you will ever handle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;p&gt;If you want to see these ideas applied to a live public map, the &lt;a href="https://flock-camera.com/methodology/" rel="noopener noreferrer"&gt;methodology page&lt;/a&gt; describes the extract pipeline, and the &lt;a href="https://flock-camera.com/cameras/" rel="noopener noreferrer"&gt;state rollup&lt;/a&gt; shows how the per-state rows are presented.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>data</category>
      <category>database</category>
    </item>
    <item>
      <title>How a Daily Silver Benchmark Is Computed (And Why Unit Conversion Is the Hard Part)</title>
      <dc:creator>Artur Azevedo</dc:creator>
      <pubDate>Fri, 06 Nov 2026 06:34:37 +0000</pubDate>
      <link>https://dev.to/indiasilvernotes/how-a-daily-silver-benchmark-is-computed-and-why-unit-conversion-is-the-hard-part-5e8n</link>
      <guid>https://dev.to/indiasilvernotes/how-a-daily-silver-benchmark-is-computed-and-why-unit-conversion-is-the-hard-part-5e8n</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start from one fixing, not from a scraped list
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;If you instead scrape several "silver price today" widgets and average them, you have silently mixed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;different fineness assumptions,&lt;/li&gt;
&lt;li&gt;different units (per gram vs per 10 grams vs per kilogram),&lt;/li&gt;
&lt;li&gt;wholesale versus retail quotes,&lt;/li&gt;
&lt;li&gt;and different as-of dates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You cannot reconcile those after the fact. Pick one input, document it, and derive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The conversion table is the real schema
&lt;/h2&gt;

&lt;p&gt;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: &lt;strong&gt;unit&lt;/strong&gt;, &lt;strong&gt;fineness&lt;/strong&gt;, &lt;strong&gt;quote type&lt;/strong&gt;, &lt;strong&gt;as-of date&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a base expressed per kilogram of 999 fine metal:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Published unit&lt;/th&gt;
&lt;th&gt;Divisor from the base&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;per 100 grams&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;per 10 grams&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;per gram&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;per tola&lt;/td&gt;
&lt;td&gt;1000 / grams-per-tola&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two traps worth writing tests for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fineness is a multiplier, not a new source.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Making charges are not part of the metal.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the invariant to assert in code is boring but effective: &lt;code&gt;per_kg == per_g * 1000 == per_10g * 100&lt;/code&gt;, and every derived row must carry the same as-of date as its source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a retail quote never equals the benchmark
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;That distinction belongs in the UI, not in a footnote. The most useful thing a price page can show is the &lt;strong&gt;basis&lt;/strong&gt; of each number it prints: what unit, what fineness, what quote type, what date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data quality problems that actually bite
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trading day vs calendar day.&lt;/strong&gt; A fixing published on Monday may be Friday's session after a holiday. Store both the publication date and the reference date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AM vs PM sessions.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time zones.&lt;/strong&gt; "Today" in the source's locale is not "today" for the reader. Normalise to a single zone before generating slugs or filenames.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching and staleness.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anti-bot responses.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent unit changes upstream.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A small validation checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One documented base source, one fineness, one unit, one as-of date.&lt;/li&gt;
&lt;li&gt;Derived values recomputed from the base, never re-scraped.&lt;/li&gt;
&lt;li&gt;Unit-consistency assertions in the build step.&lt;/li&gt;
&lt;li&gt;Explicit separation of benchmark, spot-derived conversion, and retail quote.&lt;/li&gt;
&lt;li&gt;Reference date displayed next to every table, not just in the page footer.&lt;/li&gt;
&lt;li&gt;A visible "no current data" state instead of a stale number.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;General methodology notes, not financial, tax or purchase advice. Rules and costs change; verify against primary source documents before relying on any figure.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;p&gt;If you want to see these ideas applied to a live data page, the &lt;a href="https://silverrate-today.com/data-methodology/" rel="noopener noreferrer"&gt;data methodology page for India's daily silver benchmark&lt;/a&gt; documents the base source, reference dates and unit conversions, and the &lt;a href="https://silverrate-today.com/articles/silver-price-per-gram-10g-1kg-india/" rel="noopener noreferrer"&gt;per-gram, 10-gram and 1kg explainer&lt;/a&gt; shows the arithmetic without any live rate assumptions.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>data</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
