<?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: Wishbone-Data</title>
    <description>The latest articles on DEV Community by Wishbone-Data (@wishbone_data).</description>
    <link>https://dev.to/wishbone_data</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4151012%2F1595d61d-9945-463c-bcc8-a755162ad5cf.webp</url>
      <title>DEV Community: Wishbone-Data</title>
      <link>https://dev.to/wishbone_data</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wishbone_data"/>
    <language>en</language>
    <item>
      <title>One table of US grid interconnection queues: solar, storage, wind and data center requests</title>
      <dc:creator>Wishbone-Data</dc:creator>
      <pubDate>Sat, 10 Oct 2026 00:55:57 +0000</pubDate>
      <link>https://dev.to/wishbone_data/one-table-of-us-grid-interconnection-queues-solar-storage-wind-and-data-center-requests-1c43</link>
      <guid>https://dev.to/wishbone_data/one-table-of-us-grid-interconnection-queues-solar-storage-wind-and-data-center-requests-1c43</guid>
      <description>&lt;p&gt;Every new solar farm, battery, wind project, gas plant and large load (data centers, chip fabs, hydrogen) has to file an interconnection request with its grid operator before it can connect. The queues are public. For anyone selling into energy construction, they are a pipeline list: project size, location, developer, status and the date the developer expects to come online.&lt;/p&gt;

&lt;p&gt;The problem is that each operator publishes its queue differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why combining queues is tedious
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Each ISO uses its own spreadsheet layout, sheet names and column headers.&lt;/li&gt;
&lt;li&gt;Fuel types and statuses are coded, and the codes differ by operator. NYISO, for example, uses short codes for both fuel and study status.&lt;/li&gt;
&lt;li&gt;Some files split active, withdrawn and completed projects into separate sheets.&lt;/li&gt;
&lt;li&gt;The file locations and names change with each release.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Doing it by hand
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Download the latest queue file from each operator you care about (CAISO's public queue report, ERCOT's monthly GIS report, NYISO's interconnection queue, SPP's active queue).&lt;/li&gt;
&lt;li&gt;Map each operator's columns to one schema: MW, fuel, state, county, point of interconnection, developer, status, queue date, proposed commercial operation date.&lt;/li&gt;
&lt;li&gt;Decode the fuel and status codes using each operator's own legend.&lt;/li&gt;
&lt;li&gt;Stack the results and keep a column saying which ISO each row came from.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Doing it in one run
&lt;/h2&gt;

&lt;p&gt;I packaged those steps as an Apify Actor: &lt;a href="https://apify.com/wishbone_data/interconnection-queues" rel="noopener noreferrer"&gt;US Power Grid Interconnection Queues&lt;/a&gt;. It reads the latest file from CAISO, ERCOT, NYISO and SPP, decodes the codes, and returns one normalised table you can filter by ISO, state, fuel and size, then export to CSV or Excel.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read a queue
&lt;/h2&gt;

&lt;p&gt;Most queued projects never get built; many withdraw after studies show what the grid upgrades would cost. Treat a queue entry as a lead to watch, and give more weight to projects with a signed interconnection agreement or a construction start date than to new requests.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opendata</category>
      <category>energy</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Finding FEMA-funded rebuild and mitigation projects before they go to bid (OpenFEMA)</title>
      <dc:creator>Wishbone-Data</dc:creator>
      <pubDate>Sat, 10 Oct 2026 00:54:13 +0000</pubDate>
      <link>https://dev.to/wishbone_data/finding-fema-funded-rebuild-and-mitigation-projects-before-they-go-to-bid-openfema-3ei4</link>
      <guid>https://dev.to/wishbone_data/finding-fema-funded-rebuild-and-mitigation-projects-before-they-go-to-bid-openfema-3ei4</guid>
      <description>&lt;p&gt;After a declared disaster, FEMA obligates money to cities, counties, utilities and other public applicants to repair roads, bridges, water systems and public buildings. Separately, hazard mitigation programs fund elevations, drainage, generators, seismic retrofits and wildfire work, often months before anything is advertised for bid.&lt;/p&gt;

&lt;p&gt;All of it is public through the OpenFEMA API. For contractors and engineering firms that work in the public sector, it is a list of agencies that have federal money to spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two datasets that matter
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public Assistance (PA) funded projects:&lt;/strong&gt; every obligated repair or replacement project after a declared disaster, with the damage category, county, project amount and federal share.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hazard Mitigation Assistance (HMA) sub-applications:&lt;/strong&gt; HMGP, BRIC, FMA and related programs, including the project status (for example pending, submitted to FEMA, approved). The early statuses are the interesting ones: those projects have not been bid yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What makes the raw API awkward
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PA project records carry an applicant ID rather than a name, so you need a second lookup to learn which agency it is.&lt;/li&gt;
&lt;li&gt;The two datasets use different field names for the same ideas (state, county, amount, status).&lt;/li&gt;
&lt;li&gt;Results are paged, and a useful pull for a single state can run to tens of thousands of rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Doing it by hand
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Query the PA funded projects and HMA sub-application endpoints at &lt;code&gt;fema.gov/api/open&lt;/code&gt; with a filter on state and date.&lt;/li&gt;
&lt;li&gt;Page through the results with &lt;code&gt;$skip&lt;/code&gt; and &lt;code&gt;$top&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Join PA records to the applicants dataset to get agency names and addresses.&lt;/li&gt;
&lt;li&gt;Normalise the field names so both datasets fit one table.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Doing it in one run
&lt;/h2&gt;

&lt;p&gt;I packaged those steps as an Apify Actor: &lt;a href="https://apify.com/wishbone_data/fema-recovery-projects" rel="noopener noreferrer"&gt;FEMA Disaster Recovery &amp;amp; Mitigation Projects&lt;/a&gt;. It pulls both datasets, resolves applicant names, and returns one flat schema you can filter by state, program, status and amount, then export to CSV or Excel.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on using it well
&lt;/h2&gt;

&lt;p&gt;An obligated amount is money FEMA has committed, not a contract you can bid on tomorrow. Use the list to find the agencies and projects worth watching, then follow each agency's own procurement notices for the actual solicitation.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opendata</category>
      <category>construction</category>
      <category>api</category>
    </item>
    <item>
      <title>US building permits by county and city, as one tidy table (Census Building Permits Survey)</title>
      <dc:creator>Wishbone-Data</dc:creator>
      <pubDate>Sat, 10 Oct 2026 00:53:12 +0000</pubDate>
      <link>https://dev.to/wishbone_data/us-building-permits-by-county-and-city-as-one-tidy-table-census-building-permits-survey-oba</link>
      <guid>https://dev.to/wishbone_data/us-building-permits-by-county-and-city-as-one-tidy-table-census-building-permits-survey-oba</guid>
      <description>&lt;p&gt;If you sell building products, build homes or lend on construction, monthly building permit counts are one of the earliest demand signals you can get. The US Census Bureau publishes them for every state, county and permit-issuing place through the Building Permits Survey (BPS), free.&lt;/p&gt;

&lt;p&gt;The catch is the format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the raw files are hard to use
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;BPS is published as dozens of fixed-layout text files per month, one per geography level and region.&lt;/li&gt;
&lt;li&gt;Columns are positional and split by structure type: 1 unit, 2 units, 3-4 units and 5+ units, each with buildings, units and valuation.&lt;/li&gt;
&lt;li&gt;State files report valuation in thousands of dollars while place files report dollars, so a naive join gives you numbers that are off by 1,000x.&lt;/li&gt;
&lt;li&gt;The newest month appears with a lag, and you have to work out which month is the latest one published.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Doing it by hand
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Browse the BPS data directory on &lt;code&gt;www2.census.gov&lt;/code&gt; and pick the geography (state, county, place, metro) and period (monthly, year-to-date, annual).&lt;/li&gt;
&lt;li&gt;Parse the fixed-width columns using the layout notes for that file type.&lt;/li&gt;
&lt;li&gt;Convert state valuations from thousands to dollars before comparing them with county or place figures.&lt;/li&gt;
&lt;li&gt;Add FIPS codes so the table can be joined to anything else you have.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Doing it in one run
&lt;/h2&gt;

&lt;p&gt;I packaged those steps as an Apify Actor: &lt;a href="https://apify.com/wishbone_data/census-building-permits" rel="noopener noreferrer"&gt;US Building Permits by City, County &amp;amp; State&lt;/a&gt;. It finds the latest published month, downloads every file for the periods and geography you choose, and returns one row per place per period with named columns such as &lt;code&gt;units1&lt;/code&gt;, &lt;code&gt;units5plus&lt;/code&gt; and &lt;code&gt;totalValueUsd&lt;/code&gt;, already in dollars. You can filter by state, by name and by a minimum number of units, and export to CSV or Excel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the numbers sensibly
&lt;/h2&gt;

&lt;p&gt;Permits are authorisations, not starts or completions, and some places issue none at all, so a missing small town is not the same as zero construction. Single-family and 5+ unit counts move for different reasons, so look at them separately rather than only at the total.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opendata</category>
      <category>construction</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Davis-Bacon prevailing wage rates as a spreadsheet: pulling SAM.gov wage determinations by county</title>
      <dc:creator>Wishbone-Data</dc:creator>
      <pubDate>Fri, 09 Oct 2026 23:53:54 +0000</pubDate>
      <link>https://dev.to/wishbone_data/davis-bacon-prevailing-wage-rates-as-a-spreadsheet-pulling-samgov-wage-determinations-by-county-4b6p</link>
      <guid>https://dev.to/wishbone_data/davis-bacon-prevailing-wage-rates-as-a-spreadsheet-pulling-samgov-wage-determinations-by-county-4b6p</guid>
      <description>&lt;p&gt;Anyone who bids federally funded construction in the US runs into Davis-Bacon wage determinations. Each one is a long text document on SAM.gov listing every craft (electricians, laborers, operators and so on) with a base hourly rate and a fringe rate for a given state, set of counties and construction type.&lt;/p&gt;

&lt;p&gt;Estimators usually open the determination, scroll through dozens of classifications and retype them into a spreadsheet. When a modification is published, they do it again.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a wage determination contains
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Construction type:&lt;/strong&gt; Building, Heavy, Highway or Residential. The same county can have four different determinations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate identifiers:&lt;/strong&gt; each rate block is tagged as a union rate (the identifier starts with the union's code), a survey rate (&lt;code&gt;SU...&lt;/code&gt;) or a union average (&lt;code&gt;UAVG...&lt;/code&gt;). That tells you where the number came from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modification number and effective date:&lt;/strong&gt; rates change when a new modification is published, so the mod number matters as much as the WD number.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Doing it by hand
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;sam.gov&lt;/code&gt;, open Wage Determinations, and search Davis-Bacon by state, county and construction type.&lt;/li&gt;
&lt;li&gt;Open the active determination and note the WD number and modification.&lt;/li&gt;
&lt;li&gt;Copy each classification, base rate and fringe into your sheet.&lt;/li&gt;
&lt;li&gt;Re-check the determination named in the contract documents before bid day.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Doing it in one run
&lt;/h2&gt;

&lt;p&gt;I packaged the lookup and the parsing as an Apify Actor: &lt;a href="https://apify.com/wishbone_data/davis-bacon-wage-determinations" rel="noopener noreferrer"&gt;Davis-Bacon &amp;amp; SCA Prevailing Wage Determinations&lt;/a&gt;. Search by state, county and construction type, or give it exact WD numbers. Each output row is one classification with &lt;code&gt;craft&lt;/code&gt;, &lt;code&gt;rate&lt;/code&gt;, &lt;code&gt;fringe&lt;/code&gt;, &lt;code&gt;rateIdentifier&lt;/code&gt; and &lt;code&gt;effectiveDate&lt;/code&gt;, ready to export to Excel or CSV. No SAM.gov account or API key is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  A caution worth repeating
&lt;/h2&gt;

&lt;p&gt;The determination that governs a job is the one incorporated in the contract, not whatever is current on the day you look. Use a pulled sheet to build the estimate quickly, then confirm the WD number and modification against the bid documents.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opendata</category>
      <category>construction</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to pull every NOAA hail report near an address (free data, no API key)</title>
      <dc:creator>Wishbone-Data</dc:creator>
      <pubDate>Fri, 09 Oct 2026 23:51:53 +0000</pubDate>
      <link>https://dev.to/wishbone_data/how-to-pull-every-noaa-hail-report-near-an-address-free-data-no-api-key-327f</link>
      <guid>https://dev.to/wishbone_data/how-to-pull-every-noaa-hail-report-near-an-address-free-data-no-api-key-327f</guid>
      <description>&lt;p&gt;If you run a roofing or restoration company, you have probably paid for a hail map. The raw data behind most of them is public: the NOAA Storm Prediction Center (SPC) publishes a CSV of every hail, damaging-wind and tornado report for every day since 2004.&lt;/p&gt;

&lt;p&gt;The files are awkward to use directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is one file per day per hazard.&lt;/li&gt;
&lt;li&gt;Hail size is stored in hundredths of an inch (&lt;code&gt;175&lt;/code&gt; means 1.75").&lt;/li&gt;
&lt;li&gt;An SPC "day" runs from 12:00 UTC to 11:59 UTC the next day, so a report logged at 01:00 UTC carries the previous day's date.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Doing it by hand
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Download the daily files from &lt;code&gt;https://www.spc.noaa.gov/climo/reports/&lt;/code&gt; for the dates you need (&lt;code&gt;YYMMDD_rpts_hail.csv&lt;/code&gt;, &lt;code&gt;_wind.csv&lt;/code&gt;, &lt;code&gt;_torn.csv&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Divide the &lt;code&gt;Size&lt;/code&gt; column by 100 to get inches.&lt;/li&gt;
&lt;li&gt;For any report timed between 0000 and 1159 UTC, add one day to get the real calendar date.&lt;/li&gt;
&lt;li&gt;Filter to your state, or compute distance from your office with the haversine formula:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;radians&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;miles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radians&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lon2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lat2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;lon2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lon1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;3958.8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;asin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Doing it in one run
&lt;/h2&gt;

&lt;p&gt;I packaged those steps as an Apify Actor: &lt;a href="https://apify.com/wishbone_data/spc-storm-reports" rel="noopener noreferrer"&gt;NOAA Hail, Wind &amp;amp; Tornado Storm Reports&lt;/a&gt;. Give it a date range, a state or a point and radius, and a minimum hail size. It returns one row per report with hail size in inches, wind in mph, UTC time, coordinates, county, state and distance in miles. You can export the result as CSV or Excel.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the data can and can't tell you
&lt;/h2&gt;

&lt;p&gt;These are ground reports from storm spotters and National Weather Service offices, not radar estimates. Dense areas are well covered; storms over open country can go unreported. For checking a customer's date of loss that is usually what you want, because a logged report is evidence an adjuster recognises. For mapping every square mile a storm touched, you would need radar-derived hail swaths instead.&lt;/p&gt;

&lt;p&gt;Questions about the data or the edge cases (the UTC day boundary catches most people) are welcome in the comments.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opendata</category>
      <category>tutorial</category>
      <category>datascience</category>
    </item>
    <item>
      <title>1:30 AM Happens Twice on November 1. Most AI Models Picked One and Moved On.</title>
      <dc:creator>Wishbone-Data</dc:creator>
      <pubDate>Wed, 30 Sep 2026 00:52:53 +0000</pubDate>
      <link>https://dev.to/wishbone_data/130-am-happens-twice-on-november-1-most-ai-models-picked-one-and-moved-on-k51</link>
      <guid>https://dev.to/wishbone_data/130-am-happens-twice-on-november-1-most-ai-models-picked-one-and-moved-on-k51</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/kaggle-2026-09-23"&gt;Kaggle Benchmarking Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Benchmarked
&lt;/h2&gt;

&lt;p&gt;Time zones look like arithmetic. Add some hours, maybe cross midnight, done.&lt;/p&gt;

&lt;p&gt;They aren't. Twice a year a local time disappears or happens twice. The US and Europe change their clocks on different weekends, so for a few weeks "New York is five hours behind London" is wrong. Some places sit on :30 or :45 offsets. Some countries changed their rules in the last few years, so a model trained on older text can be confidently out of date.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Wall-Clock Traps&lt;/strong&gt;: 59 scenarios, each asked two ways.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clean:&lt;/strong&gt; "Local date and time in Chicago, USA: 2026-03-08 02:30. What is the local date and time in London, UK at that same moment?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Messy:&lt;/strong&gt; "Backup job on the Chicago server is set for 2:30 am local on Sunday March 8th. London team wants to watch it run. What time is that in London?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same facts, same answer. (The answer is that 2:30 am never happens in Chicago that night. The clocks jump from 2:00 to 3:00.)&lt;/p&gt;

&lt;p&gt;That gives 118 items in nine groups: basic conversions, US/EU gap weeks, odd offsets (Nepal, Chatham Islands, Lord Howe Island), the date line, flights that land on a clock-change day, countries that changed their rules recently, times that never happen, times that happen twice, and controls that look like traps but aren't.&lt;/p&gt;

&lt;p&gt;Design choices that mattered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The answer key is code, not me.&lt;/strong&gt; Python's &lt;code&gt;zoneinfo&lt;/code&gt; computes every answer from the IANA time zone database. I checked 25 by hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact-match grading.&lt;/strong&gt; Every prompt asks for a last line like &lt;code&gt;ANSWER: 2026-03-16 14:00&lt;/code&gt;, or &lt;code&gt;ANSWER: NONEXISTENT&lt;/code&gt; / &lt;code&gt;ANSWER: AMBIGUOUS&lt;/code&gt;. No LLM judge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong answers get sorted.&lt;/strong&gt; Off by exactly an hour is a DST mistake. Off by a day is a date-line mistake. Matching a country's &lt;em&gt;old&lt;/em&gt; rule is a stale-knowledge mistake. Flagging a valid time as impossible is a false alarm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four messy prompts carry a bad hint on purpose&lt;/strong&gt;, like a CFO who "always just adds five hours."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Models Tested
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Gemini 3.7 Flash (Kaggle's default model)&lt;/li&gt;
&lt;li&gt;Gemma 4 26B A4B (small open-weights model)&lt;/li&gt;
&lt;li&gt;Claude Sonnet 4.5&lt;/li&gt;
&lt;li&gt;Gemini 2.5 Flash&lt;/li&gt;
&lt;li&gt;Claude Haiku 4.5&lt;/li&gt;
&lt;li&gt;GPT-5.4 mini&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same prompts for everyone, one attempt per item. I ran the benchmark twice: once on the Kaggle leaderboard, and once in an analysis notebook that keeps every answer so I could see &lt;em&gt;why&lt;/em&gt; models missed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Findings
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Leaderboard (run 1):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.7 Flash&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 26B A4B&lt;/td&gt;
&lt;td&gt;96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 4.5&lt;/td&gt;
&lt;td&gt;91%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 2.5 Flash&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Haiku 4.5&lt;/td&gt;
&lt;td&gt;73%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.4 mini&lt;/td&gt;
&lt;td&gt;66%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Full breakdown (run 2):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Clean&lt;/th&gt;
&lt;th&gt;Messy&lt;/th&gt;
&lt;th&gt;Traps caught&lt;/th&gt;
&lt;th&gt;False alarms&lt;/th&gt;
&lt;th&gt;Stale-rule errors&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.7 Flash&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;18/18&lt;/td&gt;
&lt;td&gt;0/12&lt;/td&gt;
&lt;td&gt;0/16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 26B A4B&lt;/td&gt;
&lt;td&gt;97%&lt;/td&gt;
&lt;td&gt;97%&lt;/td&gt;
&lt;td&gt;97%&lt;/td&gt;
&lt;td&gt;17/18&lt;/td&gt;
&lt;td&gt;0/12&lt;/td&gt;
&lt;td&gt;2/16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 2.5 Flash&lt;/td&gt;
&lt;td&gt;91%&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;11/18&lt;/td&gt;
&lt;td&gt;0/12&lt;/td&gt;
&lt;td&gt;4/16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Haiku 4.5&lt;/td&gt;
&lt;td&gt;78%&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;76%&lt;/td&gt;
&lt;td&gt;9/18&lt;/td&gt;
&lt;td&gt;1/12&lt;/td&gt;
&lt;td&gt;10/16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.4 mini&lt;/td&gt;
&lt;td&gt;68%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;61%&lt;/td&gt;
&lt;td&gt;7/18&lt;/td&gt;
&lt;td&gt;1/12&lt;/td&gt;
&lt;td&gt;5/16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Claude Sonnet 4.5 is missing from run 2. 102 of its 118 calls came back as API errors, so the row would measure the API, not the model. Its leaderboard score from run 1 stands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. "Happens twice" is the real trap. "Never happens" mostly isn't.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a time doesn't exist (spring forward), nearly every model noticed: 100% for three of the five, 80% for Haiku, 60% for GPT-5.4 mini. When a time happens twice (fall back), the picture flips. Gemini 3.7 Flash got all of them and Gemma got 88%. Gemini 2.5 Flash, Haiku and GPT-5.4 mini each got &lt;strong&gt;12%&lt;/strong&gt;. They did the math on one of the two possible moments and gave a confident answer. The hardest single item in the set was 1:45 am on Lord Howe Island, where the clock only moves back 30 minutes. Almost nobody flagged it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Models still carry old rules for Egypt, Greenland and Paraguay.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The "changed rules" group split the field too. Haiku gave the &lt;em&gt;old&lt;/em&gt; answer on 10 of 16 items, GPT-5.4 mini on 5, and Gemini 2.5 Flash on 4. The worst was Paraguay, which dropped its winter clock change in 2024: most models got the July time in Asunción wrong. Egypt (DST back in 2023) and Nuuk, Greenland (new offset in 2023) tripped up several models too. These aren't math mistakes. The models know a rule, just not the current one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Messy wording hurt the weak models and didn't touch the strong ones.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The top two scored the same clean and messy. GPT-5.4 mini dropped 14 points (75% to 61%) and Haiku 4. Oddly, Gemini 2.5 Flash did &lt;em&gt;better&lt;/em&gt; on messy (93% vs 88%). The planted hints worked on some: the "just add five hours" message (New York to London during the gap week) flipped two models from right to wrong.&lt;/p&gt;

&lt;p&gt;Accuracy by group (run 2):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Basic&lt;/th&gt;
&lt;th&gt;US/EU gap&lt;/th&gt;
&lt;th&gt;Odd offset&lt;/th&gt;
&lt;th&gt;Date line&lt;/th&gt;
&lt;th&gt;Flight&lt;/th&gt;
&lt;th&gt;Changed rules&lt;/th&gt;
&lt;th&gt;Never happens&lt;/th&gt;
&lt;th&gt;Happens twice&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.7 Flash&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 26B A4B&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 2.5 Flash&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Haiku 4.5&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;38%&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.4 mini&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;58%&lt;/td&gt;
&lt;td&gt;69%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;69%&lt;/td&gt;
&lt;td&gt;60%&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;td&gt;58%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Where the wrong answers came from (run 2, counts):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Missed trap&lt;/th&gt;
&lt;th&gt;Off by 1 hour&lt;/th&gt;
&lt;th&gt;Stale rule&lt;/th&gt;
&lt;th&gt;Wrong day&lt;/th&gt;
&lt;th&gt;False alarm&lt;/th&gt;
&lt;th&gt;Other&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 26B A4B&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 2.5 Flash&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1 (format)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Haiku 4.5&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.4 mini&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What surprised me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thinking time bought accuracy almost one for one. GPT-5.4 mini wrote about 80 tokens per answer and was the cheapest run ($0.06 for all 118 items). It was also the only model that failed plain US/EU gap-week conversions, the kind of thing you'd put in a calendar invite. Gemma, a much smaller open model, wrote about 2,500 tokens per answer and scored 97%. The other surprise was run-to-run noise: Haiku scored 73% on the leaderboard and 78% on the second pass, and GPT-5.4 mini 66% then 68%, with the same prompts. A single run isn't the whole story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'd measure next&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give the models a time zone tool and see whether they call it or still do the math in their head.&lt;/li&gt;
&lt;li&gt;Ask about dates in 2028 and 2030, where a rule change could land after the training data.&lt;/li&gt;
&lt;li&gt;Run each item several times. A model that's right 60% of the time on a trap isn't the same as one that's right every time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Benchmark
&lt;/h2&gt;

&lt;p&gt;Kaggle benchmark: &lt;a href="https://www.kaggle.com/benchmarks/wishbonedata/wall-clock-traps" rel="noopener noreferrer"&gt;https://www.kaggle.com/benchmarks/wishbonedata/wall-clock-traps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Task (code, answer key and grader): &lt;a href="https://www.kaggle.com/benchmarks/tasks/wishbonedata/wall-clock-traps" rel="noopener noreferrer"&gt;https://www.kaggle.com/benchmarks/tasks/wishbonedata/wall-clock-traps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything is deterministic. The answer key is rebuilt from the time zone database every time the notebook runs, and the notebook prints a fingerprint so you can check whether your machine's time zone data gives the same key.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>kagglechallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
