<?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: LottoLens PH Editorial</title>
    <description>The latest articles on DEV Community by LottoLens PH Editorial (@lottolensph).</description>
    <link>https://dev.to/lottolensph</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%2F4066857%2Fbb1cbc22-eb97-4ba4-a9e2-48cf29c34451.png</url>
      <title>DEV Community: LottoLens PH Editorial</title>
      <link>https://dev.to/lottolensph</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lottolensph"/>
    <language>en</language>
    <item>
      <title>Building a Reliable Public Results Pipeline When Sources Update at Different Times</title>
      <dc:creator>LottoLens PH Editorial</dc:creator>
      <pubDate>Fri, 07 Aug 2026 05:53:28 +0000</pubDate>
      <link>https://dev.to/lottolensph/building-a-reliable-public-results-pipeline-when-sources-update-at-different-times-57o0</link>
      <guid>https://dev.to/lottolensph/building-a-reliable-public-results-pipeline-when-sources-update-at-different-times-57o0</guid>
      <description>&lt;p&gt;Publishing frequently updated public data looks simple until the sources stop&lt;br&gt;
behaving like a database. One page updates first, another returns an old cache,&lt;br&gt;
an API omits one draw, and a static site can remain stale even after the newest&lt;br&gt;
row has reached a backend store.&lt;/p&gt;

&lt;p&gt;We encountered this problem while building LottoLens PH, an independent&lt;br&gt;
information project for Philippine PCSO draw schedules, result history, and&lt;br&gt;
descriptive trend data. The domain is lottery data, but the engineering pattern&lt;br&gt;
also applies to sports scores, election results, transport alerts, commodity&lt;br&gt;
prices, and other public records that arrive in small time-sensitive batches.&lt;/p&gt;

&lt;p&gt;The lesson was straightforward: fetching data is only the first step. A reliable&lt;br&gt;
pipeline must prove that the expected public page is fresh.&lt;/p&gt;
&lt;h2&gt;
  
  
  The real freshness contract
&lt;/h2&gt;

&lt;p&gt;A successful HTTP request does not prove that a result is current. A source can&lt;br&gt;
return &lt;code&gt;200 OK&lt;/code&gt; with yesterday's content, an incomplete set of games, or a cached&lt;br&gt;
response from before the scheduled update.&lt;/p&gt;

&lt;p&gt;For a draw-results system, the freshness contract needs at least four fields:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;game identifier;&lt;/li&gt;
&lt;li&gt;draw date;&lt;/li&gt;
&lt;li&gt;draw session or time;&lt;/li&gt;
&lt;li&gt;complete result value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The contract should be evaluated against the expected schedule. At 9:15 PM, for&lt;br&gt;
example, the pipeline should not merely ask whether any new row exists. It should&lt;br&gt;
ask whether every game expected in that window has a valid row for the current&lt;br&gt;
date.&lt;/p&gt;
&lt;h2&gt;
  
  
  Use multiple sources, but keep provenance
&lt;/h2&gt;

&lt;p&gt;A fallback source improves availability, but only when every imported row keeps&lt;br&gt;
its provenance. Store the source name, source URL, retrieval time, and publication&lt;br&gt;
status beside the result.&lt;/p&gt;

&lt;p&gt;That makes disagreements inspectable. It also prevents a fallback value from&lt;br&gt;
silently replacing a more authoritative record without an audit trail.&lt;/p&gt;

&lt;p&gt;For our public reference material, we separated the schedule definition from the&lt;br&gt;
result archive. The normal game windows and field definitions are published as an&lt;br&gt;
&lt;a href="https://remo65588-boop.github.io/lottolens-ph-public-data/" rel="noopener noreferrer"&gt;open PCSO draw-schedule dataset&lt;/a&gt;,&lt;br&gt;
while the live site keeps a chronological&lt;br&gt;
&lt;a href="https://lottolensph.com/history" rel="noopener noreferrer"&gt;result history&lt;/a&gt; with game and session context.&lt;br&gt;
The current archive size, per-game coverage, collection method, and known&lt;br&gt;
limitations are documented in a separate&lt;br&gt;
&lt;a href="https://lottolensph.com/research/pcso-results-data-coverage" rel="noopener noreferrer"&gt;PCSO results data coverage report&lt;/a&gt;.&lt;br&gt;
The dataset also has an archival DOI at&lt;br&gt;
&lt;a href="https://doi.org/10.5281/zenodo.21683518" rel="noopener noreferrer"&gt;Zenodo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Normalize before comparing
&lt;/h2&gt;

&lt;p&gt;Different sources often represent the same value differently. Leading zeroes may&lt;br&gt;
disappear, separators change, and dates can be interpreted in the server's time&lt;br&gt;
zone instead of Philippine Time.&lt;/p&gt;

&lt;p&gt;Normalize records before deduplication:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;convert dates into one local-time convention;&lt;/li&gt;
&lt;li&gt;preserve leading zeroes for digit games;&lt;/li&gt;
&lt;li&gt;keep the original order for multi-number results;&lt;/li&gt;
&lt;li&gt;map source labels into stable internal game IDs;&lt;/li&gt;
&lt;li&gt;reject rows with the wrong number count or range.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents &lt;code&gt;04&lt;/code&gt; and &lt;code&gt;4&lt;/code&gt; from becoming separate records and stops a six-number&lt;br&gt;
row from being attached to the wrong jackpot game.&lt;/p&gt;
&lt;h2&gt;
  
  
  Make history immutable
&lt;/h2&gt;

&lt;p&gt;Historical rows should not be recalculated every time a new draw appears. Once a&lt;br&gt;
row has been verified and published, changes should be explicit corrections with&lt;br&gt;
an audit reason.&lt;/p&gt;

&lt;p&gt;This matters for user trust. A chart, saved plan, or backtest viewed today should&lt;br&gt;
not show a different historical input tomorrow simply because the generation&lt;br&gt;
algorithm ran again.&lt;/p&gt;

&lt;p&gt;A practical model is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;append new verified rows;&lt;/li&gt;
&lt;li&gt;deduplicate on game, date, and session;&lt;/li&gt;
&lt;li&gt;preserve the original result and source metadata;&lt;/li&gt;
&lt;li&gt;record corrections separately;&lt;/li&gt;
&lt;li&gt;generate derived statistics from the immutable snapshot.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Validate the public output, not only the database
&lt;/h2&gt;

&lt;p&gt;Static sites introduce a second freshness boundary. Updating a database or API&lt;br&gt;
does not rebuild already-exported HTML.&lt;/p&gt;

&lt;p&gt;The deployment gate therefore needs to inspect the actual production page after&lt;br&gt;
the build. For each expected game, verify that the formal-domain HTML contains the&lt;br&gt;
current date, session, and result. A preview URL or successful deployment receipt&lt;br&gt;
is useful evidence, but it is not the final acceptance test.&lt;/p&gt;

&lt;p&gt;The pipeline should fail closed when a required row is missing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;collect -&amp;gt; normalize -&amp;gt; validate expected window -&amp;gt; snapshot history
       -&amp;gt; build static pages -&amp;gt; deploy -&amp;gt; verify formal-domain output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the formal-domain check fails, the run remains incomplete even when every&lt;br&gt;
earlier command succeeded.&lt;/p&gt;
&lt;h2&gt;
  
  
  Retry around the event, not all day
&lt;/h2&gt;

&lt;p&gt;Continuous high-frequency polling wastes requests and increases the chance of&lt;br&gt;
rate limits. Poll aggressively only around scheduled update windows.&lt;/p&gt;

&lt;p&gt;A simple policy is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;begin shortly after the expected publication time;&lt;/li&gt;
&lt;li&gt;retry at short intervals while required rows are missing;&lt;/li&gt;
&lt;li&gt;stop immediately when the full expected set is present;&lt;/li&gt;
&lt;li&gt;escalate when a reasonable deadline is exceeded;&lt;/li&gt;
&lt;li&gt;perform one later reconciliation pass for source corrections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives fast updates without turning the collector into an all-day scraper.&lt;/p&gt;
&lt;h2&gt;
  
  
  Monitor missing rows as named failures
&lt;/h2&gt;

&lt;p&gt;"Update failed" is too vague. Alerts should identify the exact missing contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;missing: 2d-lotto 2026-08-06 9PM
present: 3d-lotto 2026-08-06 9PM
present: 6d-lotto 2026-08-06 9PM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Named failures make diagnosis faster and prevent a partial outage from being&lt;br&gt;
described as a full-site data failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we would build first
&lt;/h2&gt;

&lt;p&gt;For a small team, the minimum dependable version is not a complex event platform.&lt;br&gt;
It is a short observable chain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;two independent source adapters;&lt;/li&gt;
&lt;li&gt;one normalized record format;&lt;/li&gt;
&lt;li&gt;one expected-window validator;&lt;/li&gt;
&lt;li&gt;one immutable history snapshot;&lt;/li&gt;
&lt;li&gt;one build and deploy command;&lt;/li&gt;
&lt;li&gt;one formal-domain freshness check;&lt;/li&gt;
&lt;li&gt;one alert containing the missing row names.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That design is simple enough for one maintainer and strong enough to reveal where&lt;br&gt;
freshness actually broke.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;Reliable public data publishing is not about fetching faster. It is about making&lt;br&gt;
freshness measurable from source to user-visible output.&lt;/p&gt;

&lt;p&gt;When every stage has a clear contract, a delayed source becomes a named temporary&lt;br&gt;
condition instead of a recurring mystery. The same approach can improve any&lt;br&gt;
small project that turns frequently changing public records into searchable,&lt;br&gt;
historical, and reusable information.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>dataengineering</category>
      <category>opensource</category>
      <category>sre</category>
    </item>
  </channel>
</rss>
