<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Brandon Davis</title><link>https://subdavis.com/</link><description>Recent content on Brandon Davis</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Wed, 18 Feb 2026 20:00:00 +0000</lastBuildDate><atom:link href="https://subdavis.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Amtrak and Skiing Whitefish, MT</title><link>https://subdavis.com/posts/2026-02-whitefish-photos/</link><pubDate>Wed, 18 Feb 2026 20:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2026-02-whitefish-photos/</guid><description>Taken on an Amtrak trip in Feb 2026</description><content:encoded>&lt;p&gt;48 hours on the empire builder and 2 days in Whitefish.&lt;/p&gt;</content:encoded></item><item><title>Claude Code SDK is pretty wild</title><link>https://subdavis.com/posts/2026-02-claude-code-sdk/</link><pubDate>Wed, 11 Feb 2026 22:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2026-02-claude-code-sdk/</guid><description>Mixing claude with procedural code beyond one-shot i/o.</description><content:encoded>&lt;p&gt;I&amp;rsquo;ve been using claude code on and off since it launched a year ago. Since the Christmas holiday, it seems &lt;a href="https://www.theargumentmag.com/p/i-cant-stop-yelling-at-claude-code"&gt;a ton of folks outside the software industry have discovered it&lt;/a&gt; as well.&lt;/p&gt;
&lt;p&gt;Despite its popularity, I&amp;rsquo;ve seen almost no discussion of Claude Code SDK. It&amp;rsquo;s essentially a typescript or &lt;a href="https://platform.claude.com/docs/en/agent-sdk/python"&gt;python&lt;/a&gt; wrapper to the agent harness that allows you to fully manage the handoff between agentic and procedural parts of an application.&lt;/p&gt;
&lt;h2 id="beyond---print-mode"&gt;Beyond &lt;code&gt;--print&lt;/code&gt; mode&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;ve discovered the &lt;code&gt;--print&lt;/code&gt; flag, you&amp;rsquo;ve almost certainly scripted up some terrible nonsense to run it as a subprocess.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stop that right now!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://platform.claude.com/docs/en/api/sdks/python"&gt;SDK documentation is dense&lt;/a&gt;. It does not take the time to explain the basics of the turn loop or tool hooks. I suspect that most people who &lt;em&gt;have&lt;/em&gt; attempted to use the SDK have done so in a one-shot sort of way, by giving a prompt and letting Claude cook until it yields.&lt;/p&gt;
&lt;h2 id="control-handoff-and-tool-use"&gt;Control handoff and tool use&lt;/h2&gt;
&lt;p&gt;Claude code is just a while loop.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;until llm says stop:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; give llm some inputs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; do whatever it says (read file, run bash)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you build an &lt;a href="https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview"&gt;agent &lt;code&gt;SKILL.md&lt;/code&gt;&lt;/a&gt;, you hand-wavishly describe how to use an API or function or script. Claude reads those instructions and then uses its existing tools (read/write/bash/grep) to do whatever the skill describes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Claude is like the little dude inside the big dude&amp;rsquo;s head&lt;/p&gt;
&lt;p&gt;&amp;ndash; Will Smith&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="./topreventwar.jpg" alt="To Prevent War"&gt;&lt;/p&gt;
&lt;p&gt;When you build custom tools with the SDK, you must &lt;a href="https://platform.claude.com/docs/en/agent-sdk/custom-tools"&gt;implement an MCP tool interface&lt;/a&gt;, run it synchronously in-process, and hand the output back for the next turn.&lt;/p&gt;
&lt;h2 id="a-toy-example"&gt;A Toy Example&lt;/h2&gt;
&lt;p&gt;The official docs provide &lt;a href="https://platform.claude.com/docs/en/api/sdks/python#tool-helpers"&gt;this example&lt;/a&gt;. It&amp;rsquo;s a bit unsatisfying because it hides the details, so let&amp;rsquo;s reimplement without the automagical &lt;code&gt;tool_runner&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#cf222e"&gt;from&lt;/span&gt; &lt;span style="color:#24292e"&gt;anthropic&lt;/span&gt; &lt;span style="color:#cf222e"&gt;import&lt;/span&gt; Anthropic
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#cf222e"&gt;import&lt;/span&gt; &lt;span style="color:#24292e"&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;client &lt;span style="color:#0550ae"&gt;=&lt;/span&gt; Anthropic&lt;span style="color:#1f2328"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;max_turns &lt;span style="color:#0550ae"&gt;=&lt;/span&gt; &lt;span style="color:#0550ae"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SYSTEM_PROMPT &lt;span style="color:#0550ae"&gt;=&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;System Prompt Here&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MESSAGES &lt;span style="color:#0550ae"&gt;=&lt;/span&gt; &lt;span style="color:#1f2328"&gt;[{&lt;/span&gt;&lt;span style="color:#0a3069"&gt;&amp;#34;role&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;user&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#1f2328"&gt;[{&lt;/span&gt;&lt;span style="color:#0a3069"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;Tell me the weather in San Francisco.&amp;#34;&lt;/span&gt; &lt;span style="color:#1f2328"&gt;}]}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#57606a"&gt;# This is the MCP spec!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;TOOLS &lt;span style="color:#0550ae"&gt;=&lt;/span&gt; &lt;span style="color:#1f2328"&gt;[{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;get_weather&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;Get the current weather in a given location&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;input_schema&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#1f2328"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;object&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;properties&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#1f2328"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;location&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#1f2328"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;string&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;The city and state, e.g. San Francisco, CA&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;required&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#1f2328"&gt;[&lt;/span&gt;&lt;span style="color:#0a3069"&gt;&amp;#34;location&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#1f2328"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#cf222e"&gt;def&lt;/span&gt; &lt;span style="color:#6639ba"&gt;get_weather&lt;/span&gt;&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;location&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#6639ba"&gt;str&lt;/span&gt;&lt;span style="color:#1f2328"&gt;)&lt;/span&gt; &lt;span style="color:#0550ae"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#6639ba"&gt;str&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;return&lt;/span&gt; json&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;dumps&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;location&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; location&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;temperature&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;68°F&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;condition&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;Sunny&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#cf222e"&gt;for&lt;/span&gt; _ &lt;span style="color:#0550ae"&gt;in&lt;/span&gt; &lt;span style="color:#6639ba"&gt;range&lt;/span&gt;&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;max_turns&lt;span style="color:#1f2328"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response &lt;span style="color:#0550ae"&gt;=&lt;/span&gt; client&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;messages&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;create&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; model&lt;span style="color:#0550ae"&gt;=&lt;/span&gt;&lt;span style="color:#0a3069"&gt;&amp;#34;claude-sonnet-4-5&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; max_tokens&lt;span style="color:#0550ae"&gt;=&lt;/span&gt;&lt;span style="color:#0550ae"&gt;4096&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; system&lt;span style="color:#0550ae"&gt;=&lt;/span&gt;SYSTEM_PROMPT&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tools&lt;span style="color:#0550ae"&gt;=&lt;/span&gt;TOOLS&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; messages&lt;span style="color:#0550ae"&gt;=&lt;/span&gt;MESSAGES&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;if&lt;/span&gt; response&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;stop_reason &lt;span style="color:#0550ae"&gt;==&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;tool_use&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tool_results &lt;span style="color:#0550ae"&gt;=&lt;/span&gt; &lt;span style="color:#1f2328"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assistant_content &lt;span style="color:#0550ae"&gt;=&lt;/span&gt; &lt;span style="color:#1f2328"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;for&lt;/span&gt; block &lt;span style="color:#0550ae"&gt;in&lt;/span&gt; response&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;content&lt;span style="color:#1f2328"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assistant_content&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;append&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;block&lt;span style="color:#1f2328"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57606a"&gt;# Do what claude says&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;if&lt;/span&gt; block&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;type &lt;span style="color:#0550ae"&gt;==&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;tool_use&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;if&lt;/span&gt; block&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;name &lt;span style="color:#0550ae"&gt;==&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;get_weather&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result &lt;span style="color:#0550ae"&gt;=&lt;/span&gt; get_weather&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;block&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;input&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;get&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;&lt;span style="color:#0a3069"&gt;&amp;#34;location&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tool_results&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;append&lt;span style="color:#1f2328"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;tool_result&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;tool_use_id&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; block&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;id&lt;span style="color:#1f2328"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; json&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;dumps&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;result&lt;span style="color:#1f2328"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57606a"&gt;# Append the request and response to the session log:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57606a"&gt;# Claude says &amp;#34;Please run this tool&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; messages&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;append&lt;span style="color:#1f2328"&gt;({&lt;/span&gt;&lt;span style="color:#0a3069"&gt;&amp;#34;role&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;assistant&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; assistant_content&lt;span style="color:#1f2328"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#57606a"&gt;# User says &amp;#34;Here you go&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; messages&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;append&lt;span style="color:#1f2328"&gt;({&lt;/span&gt;&lt;span style="color:#0a3069"&gt;&amp;#34;role&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;user&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;,&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt; tool_results&lt;span style="color:#1f2328"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#cf222e"&gt;elif&lt;/span&gt; response&lt;span style="color:#0550ae"&gt;.&lt;/span&gt;stop_reason &lt;span style="color:#0550ae"&gt;==&lt;/span&gt; &lt;span style="color:#0a3069"&gt;&amp;#34;end_turn&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#6639ba"&gt;print&lt;/span&gt;&lt;span style="color:#1f2328"&gt;(&lt;/span&gt;&lt;span style="color:#0a3069"&gt;&amp;#34;Claude exited.&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You&amp;rsquo;d miss it in the &lt;code&gt;beta.tool_runner&lt;/code&gt; wrapper, but tool use blocks append to an ever-growing &lt;code&gt;messages&lt;/code&gt; array that gets fed back into the client every turn.&lt;/p&gt;
&lt;h2 id="takeaway"&gt;Takeaway&lt;/h2&gt;
&lt;p&gt;Using the same harness framework and a completely different set of tools, claude code can do tasks entirely unrelated to writing code.&lt;/p&gt;</content:encoded></item><item><title>Renee Good was killed by ICE agents in Minneapolis yesterday</title><link>https://subdavis.com/posts/2026-01-ice-leave-minneapolis/</link><pubDate>Thu, 08 Jan 2026 22:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2026-01-ice-leave-minneapolis/</guid><description>We cannot countenance these abuses</description><content:encoded>&lt;div class="callout body-text callout-info" role="alert"&gt;
 &lt;strong class="callout-title"&gt;Editorial note&lt;/strong&gt;
 This was unpublished temporarily while I had a think about whether what I wrote is spiritually constructive. I decided to put it back.
&lt;/div&gt;

&lt;p&gt;&lt;img src="./beautiful-vigil-for-renee-good-tonight-v0-zbw5765qk2cg1.webp" alt="Renee Good Vigil"&gt;&lt;/p&gt;
&lt;p&gt;I live in Minneapolis, where on Jan 7 Renee Good was killed by an ICE officer while trying to flee from another officer attempting to force her car door open. Moments before, she is seen on camera waving out her window for the ICE vehicle to pass using the open lane in front of her.&lt;/p&gt;
&lt;p&gt;Neither the footage nor a single eyewitness account agrees with the &lt;a href="https://www.reddit.com/r/Minneapolis/comments/1q6mg02"&gt;DHS official statement&lt;/a&gt;. &lt;strong&gt;All bystanders&lt;/strong&gt; perceived that she was scared and trying to escape.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.mprnews.org/story/2026/01/08/fbi-will-investigate-after-ice-agent-shoots-renee-good-in-minneapolis"&gt;Now, the FBI have forced MN state authorities out of the case&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="the-reality"&gt;The reality&lt;/h2&gt;
&lt;p&gt;There are many different angles. &lt;a href="https://drive.google.com/drive/folders/1Gb_IkGVK7WvsTAXfMvQUoM4a1P3VwXiu"&gt;Here they are in a Google Drive folder. Watch with caution.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Kristi Noem said &lt;a href="https://www.foxnews.com/politics/noem-condemns-alleged-attack-ice-agents-stuck-snow-minneapolis-act-domestic-terrorism"&gt;at a conference&lt;/a&gt; that&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As they were attempting to push the vehicle [out of the snow], she said a woman &amp;ldquo;attacked them and those surrounding them&amp;rdquo; and &amp;ldquo;attempted to run them over and ram them with her vehicle.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;These videos show no such thing. The officers get out of a running vehicle, and could have chosen to drive by without stopping to confront her at all. Turn the sound on and you&amp;rsquo;ll hear how alarmed bystanders are by the sudden escalation: nobody on either side of the conflict had clocked Renee as a threat or someone who was behaving erratically.&lt;/p&gt;
&lt;p&gt;&lt;img src="./exact-frames-of-each-of-the-3-shots-fired-showing-visible.jpg" alt="Frame by frame"&gt;&lt;/p&gt;
&lt;h2 id="other-information"&gt;Other information&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.huffpost.com/entry/latest-news-trump-maduro_n_695bb603e4b0d6beb5fd9469/liveblog_695ebd75e4b0b3db6e4d7e52"&gt;Video: ICE blocks doctor from assisting the victim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://x.com/tradinglara/status/2009279272188248438"&gt;Video: DHS officer stomps on memorial to Renee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mprnews.org/story/2026/01/20/chongly-scott-thao-says-ice-removed-him-from-home-in-his-underwear-after-warrantless-search"&gt;ICE continues to illegally arrest citizens without cause&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-final-note"&gt;A final note&lt;/h2&gt;
&lt;p&gt;If this feels out of place or uncomforable, that is sort of the point. It is not possible to compartmentalize between our professional selves, our political selves, and who we are in our comunities and families.&lt;/p&gt;
&lt;p&gt;I want these people out of my home.&lt;/p&gt;</content:encoded></item><item><title>Thoughts for 2026</title><link>https://subdavis.com/posts/2026-01-2026-thoughts/</link><pubDate>Sun, 04 Jan 2026 15:30:00 +0000</pubDate><guid>https://subdavis.com/posts/2026-01-2026-thoughts/</guid><description>Bottom text.</description><content:encoded>&lt;p&gt;&lt;img src="./hennepin-and-8th.jpg" alt="Hennepin and 8th"&gt;&lt;/p&gt;
&lt;p&gt;I celebrated the new year by having covid and coughing a lot. It caused us to cancel a lot of plans with friends and family and cut holiday travel short.&lt;/p&gt;
&lt;p&gt;2025 was good to me, though.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I married my wonderful partner of 10 years in June. The joy and gratitude I feel overwhelms everything else that happend this year.&lt;/li&gt;
&lt;li&gt;My favorite place to be is still on a bicycle in the woods. The consistency of this fact is comforting.&lt;/li&gt;
&lt;li&gt;I want to build a writing habit. Got a jump start on this last November, but I&amp;rsquo;d be satisfied with producing 2-3 short posts a month consistently.&lt;/li&gt;
&lt;li&gt;I over-indexed this past year on trying to solve my device addiction by selectively cutting out specific websites. Live and learn. I&amp;rsquo;ll try something else and try to be patient about it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;My favorite book this year was the novella &lt;a href="https://www.goodreads.com/book/show/211004176-the-river-has-roots"&gt;The River Has Roots&lt;/a&gt; by Amal El-Mohtar (who also wrote another favorite, &lt;a href="https://en.wikipedia.org/wiki/This_Is_How_You_Lose_the_Time_War"&gt;This is How You Lose the Time War&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;I also especially enjoyed &lt;a href="https://www.goodreads.com/book/show/50358038-the-cold-millions"&gt;The Cold Millions&lt;/a&gt; by Jess Walter, an novel about turn-of-the-century labor organizing across the pacific northwest and midwest.&lt;/p&gt;
&lt;p&gt;I listened pretty incessantly to &lt;a href="https://mewithoutyou.bandcamp.com/album/ten-stories"&gt;&lt;em&gt;Ten Stories&lt;/em&gt;&lt;/a&gt; by mewithoutYou.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ten Stories is an allegorical collection of songs that, at first listen, follows a winding narrative about a circus train crash in 19th century Montana.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The song &lt;em&gt;Nine Stories&lt;/em&gt; feels most vivid in heavy Minnesota snow.&lt;/p&gt;
&lt;p&gt;Late 1800s midwestern Americana has loomed large in my imagination for some reason this year.&lt;/p&gt;
&lt;p&gt;&lt;img src="./Ten_Stories.jpg" alt="Ten Stories"&gt;&lt;/p&gt;</content:encoded></item><item><title>$6 IKEA Kapplake lights make excellent desk backlighting</title><link>https://subdavis.com/posts/2025-12-kaplake-lights/</link><pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2025-12-kaplake-lights/</guid><description>I don't like RGB.</description><content:encoded>&lt;p&gt;The &lt;a href="https://www.ikea.com/us/en/p/kapplake-led-spotlight-white-30543166/"&gt;IKEA Kapplake accent light&lt;/a&gt; works really well as a backlight.&lt;/p&gt;
&lt;p&gt;&lt;img src="./desk.webp" alt="Desk"&gt;&lt;/p&gt;
&lt;p&gt;Compared with the picture above, it looks a bit softer and warmer in person. The entire light is a toggle button, so I&amp;rsquo;ve put them toward the top and right side so they&amp;rsquo;re easy to reach.&lt;/p&gt;
&lt;p&gt;(Yes, that is a &lt;a href="https://uhk.io/"&gt;UHK&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src="./back_1.webp" alt="Behind the laptop"&gt;&lt;/p&gt;
&lt;p&gt;Behind the laptop stand&lt;/p&gt;
&lt;p&gt;&lt;img src="./back_2.webp" alt="Behind the monitor"&gt;&lt;/p&gt;
&lt;p&gt;Behind the monitor&lt;/p&gt;
&lt;p&gt;&lt;img src="./package.webp" alt="Package"&gt;&lt;/p&gt;
&lt;p&gt;Box does not include a USB A power source, but surely you have a dozen of those by now.&lt;/p&gt;</content:encoded></item><item><title>Generate live albums from YouTube videos with ffmpeg</title><link>https://subdavis.com/posts/2025-12-youtube-album-generator/</link><pubDate>Fri, 19 Dec 2025 00:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2025-12-youtube-album-generator/</guid><description>I love live albums, but it can be difficult to find digital copies.</description><content:encoded>&lt;p&gt;&lt;img src="./navidrome.png" alt="navidrome.png"&gt;&lt;/p&gt;
&lt;p&gt;There are plenty of high-quality full length concerts available on youtube. It only recenlty occurred to me that I could just turn these into my own bootleg live albums.&lt;/p&gt;
&lt;h2 id="step-1-manifest-file"&gt;Step 1: Manifest file&lt;/h2&gt;
&lt;p&gt;Make a new text file that follows this template.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;artist: CHVRCHES
album: Live at Glastonbury 2016
date: 2016
source: https://www.youtube.com/watch?v=jIbRto7TQ30
---
02:45 Never Ending Circles
05:56 We Sink
09:54 Keep You on My Side
14:24 Make Them Gold
19:31 Gun
23:21 Bury It
26:59 High Enough to Carry You Over
31:32 Empty Threat
35:59 Recover
40:09 Clearest Blue
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="step-2-run-the-script"&gt;Step 2: Run the script&lt;/h2&gt;
&lt;p&gt;The script downloads the video with &lt;a href="https://github.com/yt-dlp/yt-dlp"&gt;yt-dlp&lt;/a&gt; and transforms it using ffmpeg. ffmpeg even supports &lt;a href="https://id3.org/"&gt;ID3 metadata&lt;/a&gt; tagging.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;./annotate_v2.py my_manifest.txt&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="results"&gt;Results&lt;/h2&gt;
&lt;p&gt;Now you&amp;rsquo;ve got an album directory. You can drop a file called &lt;code&gt;cover.jpg&lt;/code&gt; in if you want, but it&amp;rsquo;s optional.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;➜ output git:(main) ✗ ls -T ./CHVRCHES/
./CHVRCHES
└── &amp;#39;Live at Glastonbury 2016&amp;#39;
 ├── &amp;#39;01 - Never Ending Circles.mp3&amp;#39;
 ├── &amp;#39;02 - We Sink.mp3&amp;#39;
 ├── &amp;#39;03 - Keep You on My Side.mp3&amp;#39;
 ├── &amp;#39;04 - Make Them Gold.mp3&amp;#39;
 ├── &amp;#39;05 - Gun.mp3&amp;#39;
 ├── &amp;#39;06 - Bury It.mp3&amp;#39;
 ├── &amp;#39;07 - High Enough to Carry You Over.mp3&amp;#39;
 ├── &amp;#39;08 - Empty Threat.mp3&amp;#39;
 ├── &amp;#39;09 - Recover.mp3&amp;#39;
 ├── &amp;#39;10 - Clearest Blue.mp3&amp;#39;
 └── cover.png
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Done. This was a one-shot with Claude Opus 4.5 and I&amp;rsquo;ve now made a dozen new live albums. I&amp;rsquo;m aware that you could embed the art direclty into the mp3 files, but I&amp;rsquo;m too lazy for that.&lt;/p&gt;
&lt;h2 id="navidrome-setup"&gt;Navidrome setup&lt;/h2&gt;
&lt;p&gt;The &amp;ldquo;album&amp;rdquo; wont match anything on MusicBrainz, so IMO it&amp;rsquo;s best to keep them in their own siloed library away from Lidarr or any other metadata automation you might have running. Navidrome supports serving multiple root directories.&lt;/p&gt;
&lt;h2 id="github-repository"&gt;GitHub repository&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/subdavis/youtube-album-generator"&gt;https://github.com/subdavis/youtube-album-generator&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="rsync-it-to-navidrome"&gt;rsync it to Navidrome&lt;/h2&gt;
&lt;p&gt;Alright, now push it to the sever.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rsync -avz --ignore-errors --no-o --no-g --exclude data/output/ user@host:/media/4tb/music_manual
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="bonus-audiotree"&gt;Bonus: Audiotree&lt;/h2&gt;
&lt;p&gt;In case you didn&amp;rsquo;t know, you actually &lt;em&gt;can&lt;/em&gt; buy &lt;a href="https://audiotree.bandcamp.com/"&gt;audiotree sessions on bandcamp&lt;/a&gt;! My favorite so far is &lt;a href="https://www.youtube.com/watch?v=Kfn57_QqprE"&gt;mewithoutYou from 2016&lt;/a&gt; during the Pale Horse tour.&lt;/p&gt;</content:encoded></item><item><title>Using the Linkding bookmark manager as a CMS for Hugo blogrolls</title><link>https://subdavis.com/posts/2025-12-retweets/</link><pubDate>Sat, 13 Dec 2025 00:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2025-12-retweets/</guid><description>Approximating the convenience of a retweet without a backend.</description><content:encoded>&lt;p&gt;&lt;img src="./linkding.png" alt="linkding"&gt;&lt;/p&gt;
&lt;p&gt;I want the ability to easily share tagged links to this website, inspired by the blogrolls of yore (shoutout to &lt;a href="https://sethmlarson.dev/blogroll"&gt;sethmlarson&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;This is a static hugo site, though. I have no interest in creating a commit just to make new links appear.&lt;/p&gt;
&lt;h2 id="linkding-my-bookmark-manager"&gt;Linkding, my bookmark manager&lt;/h2&gt;
&lt;p&gt;Incidentally, I recently spun up a &lt;a href="https://linkding.link"&gt;linkding&lt;/a&gt; instance to unify my bookmark collection and was delighted to discover that it has a &lt;strong&gt;public sharing&lt;/strong&gt; feature. You can see &lt;a href="https://links.subdavis.com/bookmarks/shared"&gt;my shared instance here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is the perfect tool for creating structured link data legible to Hugo!&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s &lt;a href="https://linkding.link/api/#bookmarks"&gt;a convenient API&lt;/a&gt; and &lt;code&gt;/api/bookmarks/shared&lt;/code&gt; does not require an auth token.&lt;/p&gt;
&lt;p&gt;The same thing could probably be &lt;a href="https://docs.linkwarden.app/api/api-introduction"&gt;achieved with Linkwarden&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="adding-a-link"&gt;Adding a link&lt;/h2&gt;
&lt;p&gt;To share a link to this site, all I need to do is bookmark it with Linkding and check the &amp;ldquo;Share&amp;rdquo; option. The chrome extension is shown below.&lt;/p&gt;
&lt;p&gt;&lt;img src="./linkding-retweet.png" alt="Linkding extension"&gt;&lt;/p&gt;
&lt;h2 id="generating-a-hugo-page-from-linkding-data"&gt;Generating a Hugo page from Linkding data&lt;/h2&gt;
&lt;p&gt;Hugo generates pages from markdown and structured yaml data. I can curl my public bookmarks and write them out as json in the hugo data directory.&lt;/p&gt;
&lt;h3 id="dataretweetsjson"&gt;&lt;code&gt;./data/retweets.json&lt;/code&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl https://links.subdavis.com/api/bookmarks/shared/ &lt;span style="color:#1f2328"&gt;|&lt;/span&gt; jq &amp;gt; data/retweets.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="contentretweetsmd"&gt;&lt;code&gt;content/retweets.md&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;A static page definition&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;---
title: Retweets
type: retweets
---

Page footer content goes here.
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="layoutsretweetssinglehtml"&gt;&lt;code&gt;./layouts/retweets/single.html&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;A page render template. Mine can be &lt;a href="https://github.com/subdavis/subdavis.github.io/blob/main/layouts/retweets/single.html"&gt;seen here&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{{ define &amp;#34;main&amp;#34; }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#1f2328"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#0550ae"&gt;article&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#0550ae"&gt;h1&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;{{ .Title }}*&lt;span style="color:#1f2328"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color:#0550ae"&gt;h1&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ if .Site.Data.retweets }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#0550ae"&gt;ul&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ range .Site.Data.retweets.retweets }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#0550ae"&gt;li&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#0550ae"&gt;a&lt;/span&gt; &lt;span style="color:#1f2328"&gt;href&lt;/span&gt;&lt;span style="color:#0550ae"&gt;=&lt;/span&gt;&lt;span style="color:#0a3069"&gt;&amp;#34;{{ .url }}&amp;#34;&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;{{ .title }}&lt;span style="color:#1f2328"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color:#0550ae"&gt;a&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ if .tag_names }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#0550ae"&gt;span&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ range .tag_names }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#0550ae"&gt;span&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;{{ . }}&lt;span style="color:#1f2328"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color:#0550ae"&gt;span&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color:#0550ae"&gt;span&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ if .description}}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#0550ae"&gt;span&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;{{ .description }}&lt;span style="color:#1f2328"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color:#0550ae"&gt;span&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color:#0550ae"&gt;li&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color:#0550ae"&gt;ul&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ else }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#1f2328"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#0550ae"&gt;p&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;No retweets yet.&lt;span style="color:#1f2328"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color:#0550ae"&gt;p&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#1f2328"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color:#0550ae"&gt;article&lt;/span&gt;&lt;span style="color:#1f2328"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="nightly-github-action"&gt;Nightly GitHub Action&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Action definition: &lt;a href="https://github.com/subdavis/subdavis.github.io/blob/main/.github/workflows/fetch-retweets.yml"&gt;https://github.com/subdavis/subdavis.github.io/blob/main/.github/workflows/fetch-retweets.yml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Run example: &lt;a href="https://github.com/subdavis/subdavis.github.io/actions/workflows/fetch-retweets.yml"&gt;https://github.com/subdavis/subdavis.github.io/actions/workflows/fetch-retweets.yml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Runs nightly, and if a change is committed, a new build in CloudFlare Pages is triggered.&lt;/p&gt;
&lt;h2 id="the-result"&gt;The result&lt;/h2&gt;
&lt;p&gt;I now have a &amp;ldquo;blogroll&amp;rdquo; style page thing at &lt;a href="https://subdavis.com/retweets"&gt;/retweets&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>The one-click Navidrome bandcamp purchase sync</title><link>https://subdavis.com/posts/2025-12-navidrome-bandcamp/</link><pubDate>Sat, 06 Dec 2025 00:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2025-12-navidrome-bandcamp/</guid><description>I made it so buying music is (almost) as easy as stealing it.</description><content:encoded>&lt;p&gt;&lt;img src="./diagram.svg" alt="My setup"&gt;&lt;/p&gt;
&lt;h2 id="why-is-buying-music-so-inconvenient"&gt;Why is buying music so inconvenient?&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s kind of insane that setting up a piracy stack is somehow easier than regularly purchasing and downloading music in a way that supports the artist.&lt;/p&gt;
&lt;p&gt;Read &lt;a href="https://ndeast.com/posts/death-to-spotify/#"&gt;this setup guide&lt;/a&gt; for example. Toward the bottom of that article, Nik documents his end-to-end purchase workflow. It&amp;rsquo;s horrible. It takes ~10 manual steps and 4 different websites to&amp;hellip; buy an album.&lt;/p&gt;
&lt;p&gt;There are powerful, convenient tools for torrenting. But if you want to buy things, the only platform not aggressively doing vendor ecosystem lock-in is Bandcamp. It has no consumer-facing API, its app kinda sucks, and there&amp;rsquo;s no good way to grab your purchases except one-at-a-time through the browser.&lt;/p&gt;
&lt;h2 id="navidrome-and-lidarr"&gt;Navidrome and Lidarr&lt;/h2&gt;
&lt;p&gt;I do use Lidarr for some things, but I keep the lidarr library completely separate from my bandcamp purchases.&lt;/p&gt;
&lt;p&gt;I ran into trouble trying to download bandcamp albums into a directory that lidarr controls becuase the file structure and naming lidarr picks are different. This breaks bandcampsync&amp;rsquo;s ability to determine whether or not it has already downloaded something to skip it.&lt;/p&gt;
&lt;p&gt;Navidrome supports &lt;strong&gt;indexing multiple library folders&lt;/strong&gt;, and will serve them all as if they were one librar. This works perfectly, so I keep them separate now. See the diagram above.&lt;/p&gt;
&lt;h2 id="the-one-click-workflow"&gt;The one-click workflow&lt;/h2&gt;
&lt;p&gt;&lt;img src="./sync.png" alt="Sync screenshot"&gt;&lt;/p&gt;
&lt;p&gt;Until bandcamp build webhooks (fat chance), you need at least 1 click to trigger a re-scan of your purchases.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Buy an album on Bandcamp&lt;/li&gt;
&lt;li&gt;Swap over to my Bandcamp Sync Flask instance and click &amp;ldquo;Run sync&amp;rdquo;. It pulls every purchase into &lt;code&gt;/music_other&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Navidrome discovers the new files. Lidarr is not involved.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stuff should appear in your media player instantly. Bandcamp&amp;rsquo;s file downloads are already well-tagged so Lidarr would only get in the way.&lt;/p&gt;
&lt;h2 id="the-repo-bandcamp-sync-flask"&gt;The repo: Bandcamp Sync Flask&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/subdavis/bandcamp-sync-flask"&gt;https://github.com/subdavis/bandcamp-sync-flask&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Credit and kudos to &lt;a href="https://github.com/subdavis/bandcamp-sync-flask?tab=readme-ov-file"&gt;BandcampSync&lt;/a&gt; which is the script this app wraps.&lt;/p&gt;
&lt;h2 id="comments"&gt;Comments&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;All this fuss about metadata seems like unneeded headache. A music library should be listened to on global shuffle, and if the RNG takes you straight from &amp;ldquo;Carolina in my Mind&amp;rdquo; to &amp;ldquo;Stupid Horse&amp;rdquo; then is that so bad? - Hastings Greer&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>How is our winter changing in the Twin Cities?</title><link>https://subdavis.com/posts/2025-12-twin-cities-climate/</link><pubDate>Tue, 02 Dec 2025 00:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2025-12-twin-cities-climate/</guid><description>A scientific(ish) look at historical winter climate trends in the Twin Cities beyond average temperature increases.</description><content:encoded>&lt;h2 id="what-does-winter-feel-like"&gt;What does winter feel like&lt;/h2&gt;
&lt;p&gt;I sometimes hear about how winters are &lt;a href="https://www.reddit.com/r/TwinCities/comments/1oh38gt/winters_are_not_what_they_used_to_be/"&gt;nothing like they used to be&lt;/a&gt;. Were they really &lt;a href="ttps://1037theloon.com/minnesota-winters-really-were-worse-in-the-70s-80s/"&gt;worse in the 80s&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;Memory is foggy. Averages and trendlines can be hard to relate to subjective personal experience. The average annual temperature in Minnesota has &lt;a href="https://climate.umn.edu/MNclimate"&gt;increased by 3.2°F&lt;/a&gt; between 1895 and 2024, but a ~0.5 degree per decade increase is not easy to feel year to year.&lt;/p&gt;
&lt;p&gt;Still, the Twin Cities are experiencing climate change. That mostly means more variable weather patterns. Bigger swings, not just steady warming. Can we see that in the data?&lt;/p&gt;
&lt;h2 id="snowfall-trends"&gt;Snowfall trends&lt;/h2&gt;
&lt;p&gt;Snowfall is probably the biggest factor for of how a winter is remembered.&lt;/p&gt;
&lt;p&gt;There are some trends in first-order measures of snowfall, but they may not be what you expect. Both total snowfall and variability from winter to winter are &lt;strong&gt;increasing&lt;/strong&gt;. Variability is actually down somewhat since an elevated period from about 1970 to 2010.&lt;/p&gt;
&lt;p&gt;My rolling standard deviation is somewhat arbitrary, so I included both a 20 year and a 30 year to show sensitivity to the window size.&lt;/p&gt;
&lt;div
 class="chart"
 style="
 position: relative;
 height: 400px;
 width: 100%;
 margin-bottom: calc(var(--spacing) * 6);
 "
&gt;
 &lt;canvas id="dfcda5922474b681"&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;script&gt;
document.addEventListener('DOMContentLoaded', () =&gt; {
 var ctx = document.getElementById('dfcda5922474b681')
 var externalOptions = {"type": "line", "data": {"labels": ["1884-85", "1885-86", "1886-87", "1887-88", "1888-89", "1889-90", "1890-91", "1891-92", "1892-93", "1893-94", "1894-95", "1895-96", "1896-97", "1897-98", "1898-99", "1899-00", "1900-01", "1901-02", "1902-03", "1903-04", "1904-05", "1905-06", "1906-07", "1907-08", "1908-09", "1909-10", "1910-11", "1911-12", "1912-13", "1913-14", "1914-15", "1915-16", "1916-17", "1917-18", "1918-19", "1919-20", "1920-21", "1921-22", "1922-23", "1923-24", "1924-25", "1925-26", "1926-27", "1927-28", "1928-29", "1929-30", "1930-31", "1931-32", "1932-33", "1933-34", "1934-35", "1935-36", "1936-37", "1937-38", "1938-39", "1939-40", "1940-41", "1941-42", "1942-43", "1943-44", "1944-45", "1945-46", "1946-47", "1947-48", "1948-49", "1949-50", "1950-51", "1951-52", "1952-53", "1953-54", "1954-55", "1955-56", "1956-57", "1957-58", "1958-59", "1959-60", "1960-61", "1961-62", "1962-63", "1963-64", "1964-65", "1965-66", "1966-67", "1967-68", "1968-69", "1969-70", "1970-71", "1971-72", "1972-73", "1973-74", "1974-75", "1975-76", "1976-77", "1977-78", "1978-79", "1979-80", "1980-81", "1981-82", "1982-83", "1983-84", "1984-85", "1985-86", "1986-87", "1987-88", "1988-89", "1989-90", "1990-91", "1991-92", "1992-93", "1993-94", "1994-95", "1995-96", "1996-97", "1997-98", "1998-99", "1999-00", "2000-01", "2001-02", "2002-03", "2003-04", "2004-05", "2005-06", "2006-07", "2007-08", "2008-09", "2009-10", "2010-11", "2011-12", "2012-13", "2013-14", "2014-15", "2015-16", "2016-17", "2017-18", "2018-19", "2019-20", "2020-21", "2021-22", "2022-23", "2023-24", "2024-25"], "datasets": [{"label": "Total Snowfall (inches)", "data": [32.0, 39.1, 61.3, 43.8, 14.2, 35.0, 30.6, 29.7, 46.1, 44.0, 16.2, 19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8, 32.1, 36.2, 31.5, 52.1, 67.0, 44.2, 38.9, 48.6, 86.1, 26.8, 27.1], "fill": false, "borderColor": "rgb(54, 162, 235)", "tension": 0.1, "borderWidth": 1.5, "yAxisID": "y"}, {"label": "20-Year Rolling Std Dev", "data": [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 13.3, 13.4, 13.4, 12.1, 12.0, 11.7, 12.0, 12.1, 12.0, 12.0, 12.8, 11.9, 11.1, 14.0, 14.1, 13.8, 14.2, 14.9, 14.3, 14.5, 14.7, 15.0, 15.2, 15.3, 15.3, 14.9, 14.7, 15.5, 15.6, 15.4, 15.1, 14.9, 15.3, 11.4, 11.3, 11.0, 10.0, 10.4, 10.5, 10.5, 10.6, 10.4, 10.4, 10.6, 11.0, 11.0, 11.1, 14.9, 17.2, 17.1, 17.2, 17.2, 16.9, 17.0, 17.5, 18.0, 18.1, 17.7, 19.4, 19.5, 19.6, 20.8, 20.8, 21.9, 22.6, 23.1, 23.4, 21.4, 20.0, 20.0, 19.6, 19.9, 20.0, 19.7, 18.8, 18.2, 17.5, 18.4, 19.7, 19.1, 19.7, 19.8, 19.5, 20.6, 19.1, 19.1, 19.6, 19.8, 20.4, 20.3, 20.2, 20.8, 20.8, 21.0, 21.1, 20.8, 21.1, 20.1, 18.6, 19.1, 17.4, 17.6, 17.0, 15.8, 15.7, 15.0, 14.8, 16.9, 16.8, 16.8, 17.2, 17.0, 17.2, 16.4, 16.5, 17.0, 16.8, 15.5, 15.6, 17.8, 17.8, 17.7], "fill": false, "borderColor": "rgb(255, 159, 64)", "tension": 0.3, "yAxisID": "y1", "pointRadius": 0}, {"label": "30-Year Rolling Std Dev", "data": [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 12.5, 12.5, 12.7, 14.2, 14.3, 13.9, 14.3, 14.6, 14.5, 14.5, 14.6, 14.3, 13.9, 13.8, 13.7, 12.9, 13.1, 13.8, 13.5, 13.6, 13.7, 13.7, 14.2, 14.2, 14.2, 13.9, 13.7, 13.9, 14.1, 14.0, 13.7, 13.6, 13.4, 10.6, 10.9, 10.7, 10.1, 13.6, 15.6, 15.5, 15.7, 15.5, 15.4, 15.4, 15.8, 16.1, 16.1, 15.5, 16.9, 16.9, 17.0, 18.0, 17.8, 19.1, 19.5, 20.1, 20.4, 20.4, 20.3, 20.3, 20.0, 20.1, 20.1, 19.7, 19.7, 19.9, 19.9, 19.0, 19.8, 19.8, 20.9, 21.3, 21.5, 22.0, 21.4, 20.9, 20.7, 20.5, 20.5, 20.1, 19.3, 19.5, 19.2, 19.0, 17.9, 17.7, 17.9, 18.4, 18.4, 18.6, 18.7, 19.3, 19.3, 19.5, 19.5, 19.3, 19.4, 19.5, 19.0, 19.0, 17.7, 17.4, 17.0, 16.4, 16.3, 16.2, 16.0, 16.1, 15.2, 16.8, 17.3, 17.3], "fill": false, "borderColor": "rgb(153, 102, 255)", "tension": 0.3, "yAxisID": "y1", "pointRadius": 0}]}, "options": {"scales": {"y": {"type": "linear", "position": "left", "title": {"display": true, "text": "Snowfall (inches)"}}, "y1": {"type": "linear", "position": "right", "title": {"display": true, "text": "Std Dev (inches)"}, "grid": {"drawOnChartArea": false}}}}, "annotation": {"annotations": {"snowfall_post_line": {"type": "line", "yMin": 54.28, "yMax": 44.48, "xMin": 86, "xMax": 140, "borderColor": "rgb(54, 162, 235)", "borderWidth": 2, "borderDash": [5, 5]}, "snowfall_post_label": {"type": "label", "xValue": 99.5, "yValue": 51.8290909090909, "backgroundColor": "rgba(245,245,245,0.6)", "content": ["-1.81/decade [R: -0.161]"], "font": {"size": 12}}}}};
 console.log(externalOptions);
 new Chart(ctx, {
 ...externalOptions,
 plugins: [],
 options: {
 responsive: true,
 maintainAspectRatio: false,
 interaction: {
 mode: 'index',
 intersect: false,
 },
 plugins: {
 annotation: externalOptions.annotation,
 },
 }
 });
});
&lt;/script&gt;

&lt;p&gt;Something to keep in mind here is that regional seasonal snowfall is &lt;strong&gt;not normally distributed&lt;/strong&gt;. The curve is skewed right, with a longer tail of very snowy winters.&lt;/p&gt;
&lt;div
 class="chart"
 style="
 position: relative;
 height: 400px;
 width: 100%;
 margin-bottom: calc(var(--spacing) * 6);
 "
&gt;
 &lt;canvas id="0f84d9e13a0319a2"&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;script&gt;
document.addEventListener('DOMContentLoaded', () =&gt; {
 var ctx = document.getElementById('0f84d9e13a0319a2')
 var externalOptions = {"type": "bar", "data": {"labels": ["10-15", "15-20", "20-25", "25-30", "30-35", "35-40", "40-45", "45-50", "50-55", "55-60", "60-65", "65-70", "70-75", "75-80", "80-85", "85-90", "90-95"], "datasets": [{"label": "Number of Seasons", "data": [2, 8, 10, 18, 17, 13, 22, 12, 10, 5, 4, 6, 5, 3, 2, 4, 0], "backgroundColor": "rgba(54, 162, 235, 0.7)", "borderColor": "rgb(54, 162, 235)", "borderWidth": 1}]}, "options": {"plugins": {"title": {"display": true, "text": "Mean: 42.8 in, Std Dev: 17.5 in"}}, "scales": {"x": {"title": {"display": true, "text": "Seasonal Snowfall (inches)"}}, "y": {"title": {"display": true, "text": "Number of Seasons"}}}}, "annotation": {"annotations": {"shapiro_label": {"type": "label", "xValue": 16, "yValue": 22, "backgroundColor": "rgba(245,245,245,0.8)", "content": ["Shapiro-Wilk Test", "W = 0.946", "p = 0.0000", "Result: Non-normal"], "font": {"size": 12}, "position": {"x": "end", "y": "start"}}}}};
 console.log(externalOptions);
 new Chart(ctx, {
 ...externalOptions,
 plugins: [],
 options: {
 responsive: true,
 maintainAspectRatio: false,
 interaction: {
 mode: 'index',
 intersect: false,
 },
 plugins: {
 annotation: externalOptions.annotation,
 },
 }
 });
});
&lt;/script&gt;

&lt;p&gt;My use of standard deviation above isn&amp;rsquo;t quite correct, so let&amp;rsquo;s try a box plot instead.&lt;/p&gt;
&lt;div
 class="chart"
 style="
 position: relative;
 height: 400px;
 width: 100%;
 margin-bottom: calc(var(--spacing) * 6);
 "
&gt;
 &lt;canvas id="6c37b06288315b3a"&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;script&gt;
document.addEventListener('DOMContentLoaded', () =&gt; {
 var ctx = document.getElementById('6c37b06288315b3a')
 var externalOptions = {"type": "boxplot", "data": {"labels": ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "1884-04", "1885-05", "1886-06", "1887-07", "1888-08", "1889-09", "1890-10", "1891-11", "1892-12", "1893-13", "1894-14", "1895-15", "1896-16", "1897-17", "1898-18", "1899-19", "1900-20", "1901-21", "1902-22", "1903-23", "1904-24", "1905-25", "1906-26", "1907-27", "1908-28", "1909-29", "1910-30", "1911-31", "1912-32", "1913-33", "1914-34", "1915-35", "1916-36", "1917-37", "1918-38", "1919-39", "1920-40", "1921-41", "1922-42", "1923-43", "1924-44", "1925-45", "1926-46", "1927-47", "1928-48", "1929-49", "1930-50", "1931-51", "1932-52", "1933-53", "1934-54", "1935-55", "1936-56", "1937-57", "1938-58", "1939-59", "1940-60", "1941-61", "1942-62", "1943-63", "1944-64", "1945-65", "1946-66", "1947-67", "1948-68", "1949-69", "1950-70", "1951-71", "1952-72", "1953-73", "1954-74", "1955-75", "1956-76", "1957-77", "1958-78", "1959-79", "1960-80", "1961-81", "1962-82", "1963-83", "1964-84", "1965-85", "1966-86", "1967-87", "1968-88", "1969-89", "1970-90", "1971-91", "1972-92", "1973-93", "1974-94", "1975-95", "1976-96", "1977-97", "1978-98", "1979-99", "1980-00", "1981-01", "1982-02", "1983-03", "1984-04", "1985-05", "1986-06", "1987-07", "1988-08", "1989-09", "1990-10", "1991-11", "1992-12", "1993-13", "1994-14", "1995-15", "1996-16", "1997-17", "1998-18", "1999-19", "2000-20", "2001-21", "2002-22", "2003-23", "2004-24", "2005-25"], "datasets": [{"label": "20-Year Rolling Snowfall Distribution", "data": [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, [32.0, 39.1, 61.3, 43.8, 14.2, 35.0, 30.6, 29.7, 46.1, 44.0, 16.2, 19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3], [39.1, 61.3, 43.8, 14.2, 35.0, 30.6, 29.7, 46.1, 44.0, 16.2, 19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1], [61.3, 43.8, 14.2, 35.0, 30.6, 29.7, 46.1, 44.0, 16.2, 19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6], [43.8, 14.2, 35.0, 30.6, 29.7, 46.1, 44.0, 16.2, 19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8], [14.2, 35.0, 30.6, 29.7, 46.1, 44.0, 16.2, 19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5], [35.0, 30.6, 29.7, 46.1, 44.0, 16.2, 19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4], [30.6, 29.7, 46.1, 44.0, 16.2, 19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3], [29.7, 46.1, 44.0, 16.2, 19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6], [46.1, 44.0, 16.2, 19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2], [44.0, 16.2, 19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9], [16.2, 19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0], [19.2, 52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9], [52.9, 29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5], [29.2, 62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1], [62.4, 34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5], [34.8, 41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9], [41.5, 21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8], [21.6, 37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5], [37.3, 38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8], [38.3, 31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3], [31.1, 39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9], [39.6, 41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1], [41.8, 40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9], [40.5, 54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9], [54.4, 51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2], [51.3, 28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0], [28.6, 42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6], [42.2, 45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7], [45.9, 18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4], [18.0, 45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2], [45.9, 49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9], [49.5, 80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7], [80.1, 28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2], [28.5, 22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9], [22.9, 56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5], [56.8, 20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0], [20.5, 41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5], [41.8, 30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5], [30.3, 29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9], [29.9, 23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4], [23.1, 30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6], [30.9, 28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9], [28.9, 40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9], [40.2, 40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8], [40.0, 25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8], [25.6, 13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1], [13.7, 44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2], [44.4, 32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4], [32.2, 24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6], [24.9, 41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3], [41.7, 56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1], [56.2, 40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5], [40.9, 29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6], [29.5, 34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5], [34.0, 44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6], [44.5, 52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1], [52.5, 23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1], [23.9, 32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5], [32.4, 26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8], [26.6, 27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0], [27.9, 36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3], [36.9, 22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7], [22.8, 48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7], [48.8, 29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7], [29.1, 45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8], [45.2, 86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8], [86.4, 77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7], [77.6, 42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6], [42.3, 23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4], [23.1, 33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7], [33.5, 41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9], [41.6, 29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0], [29.5, 19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3], [19.6, 19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5], [19.1, 28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8], [28.1, 32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7], [32.5, 74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8], [74.8, 29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4], [29.0, 23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3], [23.3, 71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2], [71.7, 35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8], [35.7, 77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4], [77.7, 16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7], [16.8, 67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4], [67.8, 59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7], [59.7, 52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0], [52.6, 56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3], [56.4, 39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8], [39.7, 43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3], [43.9, 62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6], [62.0, 53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2], [53.3, 39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4], [39.5, 45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5], [45.8, 67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0], [67.7, 44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0], [44.8, 19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5], [19.4, 89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9], [89.3, 51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5], [51.2, 88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7], [88.8, 72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4], [72.4, 68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3], [68.7, 17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5], [17.4, 39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4], [39.7, 69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6], [69.0, 33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3], [33.3, 41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5], [41.8, 75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9], [75.3, 45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7], [45.6, 50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3], [50.2, 29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3], [29.4, 52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8], [52.5, 73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8, 32.1], [73.0, 45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8, 32.1, 36.2], [45.0, 56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8, 32.1, 36.2, 31.5], [56.5, 34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8, 32.1, 36.2, 31.5, 52.1], [34.9, 74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8, 32.1, 36.2, 31.5, 52.1, 67.0], [74.5, 45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8, 32.1, 36.2, 31.5, 52.1, 67.0, 44.2], [45.7, 33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8, 32.1, 36.2, 31.5, 52.1, 67.0, 44.2, 38.9], [33.4, 66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8, 32.1, 36.2, 31.5, 52.1, 67.0, 44.2, 38.9, 48.6], [66.3, 25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8, 32.1, 36.2, 31.5, 52.1, 67.0, 44.2, 38.9, 48.6, 86.1], [25.5, 44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8, 32.1, 36.2, 31.5, 52.1, 67.0, 44.2, 38.9, 48.6, 86.1, 26.8], [44.4, 33.6, 43.3, 42.5, 37.9, 84.7, 22.3, 49.3, 62.8, 32.1, 36.2, 31.5, 52.1, 67.0, 44.2, 38.9, 48.6, 86.1, 26.8, 27.1]], "backgroundColor": "rgba(54, 162, 235, 0.5)", "borderColor": "rgb(54, 162, 235)", "borderWidth": 1}]}, "options": {"scales": {"x": {"title": {"display": true, "text": "Winter Season (end of 20-year window)"}}, "y": {"title": {"display": true, "text": "Seasonal Snowfall (inches)"}}}}};
 console.log(externalOptions);
 new Chart(ctx, {
 ...externalOptions,
 plugins: [],
 options: {
 responsive: true,
 maintainAspectRatio: false,
 interaction: {
 mode: 'index',
 intersect: false,
 },
 plugins: {
 annotation: externalOptions.annotation,
 },
 }
 });
});
&lt;/script&gt;

&lt;p&gt;Let&amp;rsquo;s check how many snow days there were, and how much of the winter included ground cover.&lt;/p&gt;
&lt;p&gt;There is a clear &lt;strong&gt;decreasing trend&lt;/strong&gt; of days with ground cover, but it&amp;rsquo;s actually falling back toward levels from the early 20th century from a peak in the 50s.&lt;/p&gt;
&lt;div
 class="chart"
 style="
 position: relative;
 height: 400px;
 width: 100%;
 margin-bottom: calc(var(--spacing) * 6);
 "
&gt;
 &lt;canvas id="e9f8be1ca9e4a283"&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;script&gt;
document.addEventListener('DOMContentLoaded', () =&gt; {
 var ctx = document.getElementById('e9f8be1ca9e4a283')
 var externalOptions = {"type": "line", "data": {"labels": ["1884-85", "1885-86", "1886-87", "1887-88", "1888-89", "1889-90", "1890-91", "1891-92", "1892-93", "1893-94", "1894-95", "1895-96", "1896-97", "1897-98", "1898-99", "1899-00", "1900-01", "1901-02", "1902-03", "1903-04", "1904-05", "1905-06", "1906-07", "1907-08", "1908-09", "1909-10", "1910-11", "1911-12", "1912-13", "1913-14", "1914-15", "1915-16", "1916-17", "1917-18", "1918-19", "1919-20", "1920-21", "1921-22", "1922-23", "1923-24", "1924-25", "1925-26", "1926-27", "1927-28", "1928-29", "1929-30", "1930-31", "1931-32", "1932-33", "1933-34", "1934-35", "1935-36", "1936-37", "1937-38", "1938-39", "1939-40", "1940-41", "1941-42", "1942-43", "1943-44", "1944-45", "1945-46", "1946-47", "1947-48", "1948-49", "1949-50", "1950-51", "1951-52", "1952-53", "1953-54", "1954-55", "1955-56", "1956-57", "1957-58", "1958-59", "1959-60", "1960-61", "1961-62", "1962-63", "1963-64", "1964-65", "1965-66", "1966-67", "1967-68", "1968-69", "1969-70", "1970-71", "1971-72", "1972-73", "1973-74", "1974-75", "1975-76", "1976-77", "1977-78", "1978-79", "1979-80", "1980-81", "1981-82", "1982-83", "1983-84", "1984-85", "1985-86", "1986-87", "1987-88", "1988-89", "1989-90", "1990-91", "1991-92", "1992-93", "1993-94", "1994-95", "1995-96", "1996-97", "1997-98", "1998-99", "1999-00", "2000-01", "2001-02", "2002-03", "2003-04", "2004-05", "2005-06", "2006-07", "2007-08", "2008-09", "2009-10", "2010-11", "2011-12", "2012-13", "2013-14", "2014-15", "2015-16", "2016-17", "2017-18", "2018-19", "2019-20", "2020-21", "2021-22", "2022-23", "2023-24", "2024-25"], "datasets": [{"label": "Days with Snow on Ground", "data": [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 90, 125, 88, 107, 125, 85, 122, 107, 105, 120, 108, 81, 132, 79, 73, 120, 123, 116, 105, 112, 135, 95, 145, 110, 93, 106, 118, 130, 133, 109, 87, 46, 88, 89, 60, 90, 107, 98, 89, 109, 103, 139, 51, 118, 43, 88, 119, 101, 136, 114, 117, 135, 141, 118, 88, 126, 143, 115, 107, 95, 122, 88, 119, 106, 105, 130, 94, 128, 94, 126, 133, 131, 133, 111, 114, 128, 120, 117, 129, 137, 121, 72, 132, 118, 131, 109, 135, 80, 107, 137, 89, 109, 141, 126, 106, 91, 126, 126, 78, 86, 65, 142, 95, 82, 105, 83, 105, 74, 117, 101, 99, 136, 64, 115, 120, 114, 76, 80, 116, 122, 116, 93, 106, 135, 56, 74], "fill": false, "borderColor": "rgb(75, 192, 192)", "tension": 0.1}, {"label": "Days with Snowfall", "data": [66, 63, 56, 70, 44, 58, 51, 41, 66, 62, 46, 51, 68, 61, 58, 46, 67, 42, 47, 62, 44, 50, 56, 53, 66, 56, 45, 60, 55, 49, 67, 63, 61, 59, 48, 69, 58, 61, 63, 42, 58, 62, 65, 57, 69, 74, 48, 60, 59, 81, 70, 89, 76, 63, 73, 64, 77, 67, 81, 62, 78, 85, 79, 88, 99, 79, 102, 88, 84, 62, 83, 83, 74, 61, 67, 74, 63, 96, 80, 64, 74, 75, 82, 68, 84, 84, 92, 80, 67, 69, 83, 68, 82, 81, 85, 85, 55, 59, 72, 74, 66, 84, 51, 66, 58, 64, 61, 72, 80, 79, 62, 79, 83, 89, 55, 39, 79, 56, 53, 63, 58, 71, 50, 71, 74, 48, 81, 49, 57, 71, 58, 58, 51, 56, 73, 60, 49, 61, 73, 40, 48], "fill": false, "borderColor": "rgb(54, 162, 235)", "tension": 0.1}]}, "annotation": {"annotations": {"depth_post_line": {"type": "line", "yMin": 122.24, "yMax": 93.43, "xMin": 86, "xMax": 140, "borderColor": "rgb(75, 192, 192)", "borderWidth": 2, "borderDash": [5, 5]}, "depth_post_label": {"type": "label", "xValue": 113.0, "yValue": 107.83636363636364, "backgroundColor": "rgba(245,245,245,0.6)", "content": ["-5.34/decade [R: -0.375]"], "font": {"size": 12}}, "fall_post_line": {"type": "line", "yMin": 77.41, "yMax": 55.24, "xMin": 86, "xMax": 140, "borderColor": "rgb(54, 162, 235)", "borderWidth": 2, "borderDash": [5, 5]}, "fall_post_label": {"type": "label", "xValue": 123.8, "yValue": 61.8935064935065, "backgroundColor": "rgba(245,245,245,0.6)", "content": ["-4.11/decade [R: -0.502]"], "font": {"size": 12}}}}};
 console.log(externalOptions);
 new Chart(ctx, {
 ...externalOptions,
 plugins: [],
 options: {
 responsive: true,
 maintainAspectRatio: false,
 interaction: {
 mode: 'index',
 intersect: false,
 },
 plugins: {
 annotation: externalOptions.annotation,
 },
 }
 });
});
&lt;/script&gt;

&lt;p&gt;I also looked at freeze-thaw cycles and &amp;ldquo;snow cycles&amp;rdquo;, the number of times snow cover appears and disappears completely, which I just made up. These have both remained &lt;strong&gt;relatively stable&lt;/strong&gt; and &lt;strong&gt;very weakly correlated&lt;/strong&gt;.&lt;/p&gt;
&lt;div
 class="chart"
 style="
 position: relative;
 height: 400px;
 width: 100%;
 margin-bottom: calc(var(--spacing) * 6);
 "
&gt;
 &lt;canvas id="8901819740c722cc"&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;script&gt;
document.addEventListener('DOMContentLoaded', () =&gt; {
 var ctx = document.getElementById('8901819740c722cc')
 var externalOptions = {"type": "line", "data": {"labels": ["1884-85", "1885-86", "1886-87", "1887-88", "1888-89", "1889-90", "1890-91", "1891-92", "1892-93", "1893-94", "1894-95", "1895-96", "1896-97", "1897-98", "1898-99", "1899-00", "1900-01", "1901-02", "1902-03", "1903-04", "1904-05", "1905-06", "1906-07", "1907-08", "1908-09", "1909-10", "1910-11", "1911-12", "1912-13", "1913-14", "1914-15", "1915-16", "1916-17", "1917-18", "1918-19", "1919-20", "1920-21", "1921-22", "1922-23", "1923-24", "1924-25", "1925-26", "1926-27", "1927-28", "1928-29", "1929-30", "1930-31", "1931-32", "1932-33", "1933-34", "1934-35", "1935-36", "1936-37", "1937-38", "1938-39", "1939-40", "1940-41", "1941-42", "1942-43", "1943-44", "1944-45", "1945-46", "1946-47", "1947-48", "1948-49", "1949-50", "1950-51", "1951-52", "1952-53", "1953-54", "1954-55", "1955-56", "1956-57", "1957-58", "1958-59", "1959-60", "1960-61", "1961-62", "1962-63", "1963-64", "1964-65", "1965-66", "1966-67", "1967-68", "1968-69", "1969-70", "1970-71", "1971-72", "1972-73", "1973-74", "1974-75", "1975-76", "1976-77", "1977-78", "1978-79", "1979-80", "1980-81", "1981-82", "1982-83", "1983-84", "1984-85", "1985-86", "1986-87", "1987-88", "1988-89", "1989-90", "1990-91", "1991-92", "1992-93", "1993-94", "1994-95", "1995-96", "1996-97", "1997-98", "1998-99", "1999-00", "2000-01", "2001-02", "2002-03", "2003-04", "2004-05", "2005-06", "2006-07", "2007-08", "2008-09", "2009-10", "2010-11", "2011-12", "2012-13", "2013-14", "2014-15", "2015-16", "2016-17", "2017-18", "2018-19", "2019-20", "2020-21", "2021-22", "2022-23", "2023-24", "2024-25"], "datasets": [{"label": "Snow Cycles (Melt Events)", "data": [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 6, 6, 6, 4, 5, 8, 1, 8, 7, 5, 3, 4, 7, 9, 8, 3, 4, 4, 6, 4, 4, 8, 0, 3, 3, 6, 2, 6, 2, 4, 5, 10, 5, 8, 11, 4, 5, 6, 6, 2, 1, 1, 10, 1, 7, 4, 2, 8, 2, 5, 7, 2, 2, 3, 10, 2, 2, 8, 7, 8, 6, 7, 3, 8, 8, 1, 10, 6, 9, 4, 2, 3, 6, 7, 5, 2, 6, 5, 3, 1, 6, 9, 1, 8, 3, 3, 4, 10, 6, 3, 12, 5, 3, 9, 10, 6, 6, 4, 8, 5, 3, 2, 9, 7, 5, 12, 7, 6, 2, 7, 2, 1, 10, 5, 4, 4, 9, 8, 2, 6, 7, 6, 5, 2, 9, 8], "fill": false, "borderColor": "rgb(153, 102, 255)", "tension": 0.1}, {"label": "Freeze-Thaw Cycles", "data": [48, 56, 39, 51, 64, 68, 58, 62, 44, 50, 50, 64, 49, 60, 36, 55, 53, 58, 52, 40, 47, 62, 51, 61, 57, 32, 57, 47, 55, 57, 51, 51, 41, 39, 67, 40, 58, 51, 42, 70, 41, 52, 50, 55, 44, 53, 65, 47, 66, 64, 49, 46, 56, 40, 54, 61, 59, 79, 43, 78, 40, 43, 66, 39, 51, 52, 36, 43, 46, 71, 39, 47, 65, 64, 55, 50, 80, 56, 56, 68, 35, 57, 50, 65, 49, 44, 47, 45, 47, 65, 44, 54, 51, 35, 37, 66, 63, 47, 57, 41, 60, 46, 85, 61, 61, 81, 69, 76, 53, 61, 61, 60, 45, 58, 75, 62, 44, 62, 53, 63, 60, 61, 61, 48, 48, 42, 38, 67, 54, 44, 38, 56, 45, 68, 46, 66, 69, 58, 65, 75, 47], "fill": false, "borderColor": "rgb(255, 159, 64)", "tension": 0.1}]}, "annotation": {"annotations": {"snow_cycle_line": {"type": "line", "yMin": 5.0, "yMax": 6.17, "xMin": 86, "xMax": 140, "borderColor": "rgb(153, 102, 255)", "borderWidth": 2, "borderDash": [5, 5]}, "snow_cycle_label": {"type": "label", "xValue": 113.0, "yValue": 5.581818181818182, "backgroundColor": "rgba(245,245,245,0.6)", "content": ["+0.22/decade [R: 0.122]"], "font": {"size": 12}}, "freeze_thaw_line": {"type": "line", "yMin": 54.08, "yMax": 58.32, "xMin": 86, "xMax": 140, "borderColor": "rgb(255, 159, 64)", "borderWidth": 2, "borderDash": [5, 5]}, "freeze_thaw_label": {"type": "label", "xValue": 126.5, "yValue": 57.260714285714286, "backgroundColor": "rgba(245,245,245,0.6)", "content": ["+0.79/decade [R: 0.108]"], "font": {"size": 12}}}}};
 console.log(externalOptions);
 new Chart(ctx, {
 ...externalOptions,
 plugins: [],
 options: {
 responsive: true,
 maintainAspectRatio: false,
 interaction: {
 mode: 'index',
 intersect: false,
 },
 plugins: {
 annotation: externalOptions.annotation,
 },
 }
 });
});
&lt;/script&gt;

&lt;p&gt;This is actually pretty interesting. Fewer days with snow on the ground, and fewer total days with snowfall, but the same number of melt cycles, implies that snow used to just&amp;hellip; last longer? But with the same-ish number of snowstorms?&lt;/p&gt;
&lt;div
 class="chart"
 style="
 position: relative;
 height: 400px;
 width: 100%;
 margin-bottom: calc(var(--spacing) * 6);
 "
&gt;
 &lt;canvas id="adbcf470cc86091c"&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;script&gt;
document.addEventListener('DOMContentLoaded', () =&gt; {
 var ctx = document.getElementById('adbcf470cc86091c')
 var externalOptions = {"type": "line", "data": {"labels": ["1899-00", "1900-01", "1901-02", "1902-03", "1903-04", "1904-05", "1905-06", "1906-07", "1907-08", "1908-09", "1909-10", "1910-11", "1911-12", "1912-13", "1913-14", "1914-15", "1915-16", "1916-17", "1917-18", "1918-19", "1919-20", "1920-21", "1921-22", "1922-23", "1923-24", "1924-25", "1925-26", "1926-27", "1927-28", "1928-29", "1929-30", "1930-31", "1931-32", "1932-33", "1933-34", "1934-35", "1935-36", "1936-37", "1937-38", "1938-39", "1939-40", "1940-41", "1941-42", "1942-43", "1943-44", "1944-45", "1945-46", "1946-47", "1947-48", "1948-49", "1949-50", "1950-51", "1951-52", "1952-53", "1953-54", "1954-55", "1955-56", "1956-57", "1957-58", "1958-59", "1959-60", "1960-61", "1961-62", "1962-63", "1963-64", "1964-65", "1965-66", "1966-67", "1967-68", "1968-69", "1969-70", "1970-71", "1971-72", "1972-73", "1973-74", "1974-75", "1975-76", "1976-77", "1977-78", "1978-79", "1979-80", "1980-81", "1981-82", "1982-83", "1983-84", "1984-85", "1985-86", "1986-87", "1987-88", "1988-89", "1989-90", "1990-91", "1991-92", "1992-93", "1993-94", "1994-95", "1995-96", "1996-97", "1997-98", "1998-99", "1999-00", "2000-01", "2001-02", "2002-03", "2003-04", "2004-05", "2005-06", "2006-07", "2007-08", "2008-09", "2009-10", "2010-11", "2011-12", "2012-13", "2013-14", "2014-15", "2015-16", "2016-17", "2017-18", "2018-19", "2019-20", "2020-21", "2021-22", "2022-23", "2023-24", "2024-25"], "datasets": [{"label": "Avg Snow Event Duration (days)", "data": [15.0, 25.0, 14.7, 26.8, 25.0, 10.6, 122.0, 13.4, 15.0, 20.0, 54.0, 16.2, 22.0, 8.8, 9.1, 30.0, 30.8, 29.0, 21.0, 28.0, 33.8, 11.9, 145.0, 36.7, 31.0, 21.2, 59.0, 21.7, 66.5, 21.8, 21.8, 4.6, 17.6, 11.1, 5.0, 30.0, 21.4, 14.0, 17.8, 54.5, 103.0, 139.0, 5.1, 118.0, 5.4, 29.3, 59.5, 12.6, 68.0, 19.0, 19.5, 48.3, 65.5, 59.0, 8.3, 123.0, 47.7, 14.4, 17.8, 11.9, 20.3, 12.6, 29.8, 15.1, 13.1, 65.0, 9.5, 25.4, 10.4, 25.2, 66.5, 43.7, 22.2, 18.5, 22.8, 42.7, 24.0, 19.5, 64.5, 68.5, 24.2, 8.0, 132.0, 13.1, 65.5, 27.2, 45.0, 8.0, 17.8, 45.7, 7.4, 21.8, 47.0, 14.0, 10.6, 15.2, 18.0, 42.0, 9.8, 17.2, 21.7, 47.3, 11.9, 11.7, 21.0, 6.9, 15.0, 12.3, 58.5, 14.4, 49.5, 68.0, 7.1, 19.2, 40.0, 28.5, 8.4, 10.0, 38.7, 24.4, 16.6, 15.5, 17.7, 68.5, 6.8, 8.2], "fill": false, "borderColor": "rgb(75, 192, 192)", "tension": 0.1, "pointRadius": 2}]}, "options": {"scales": {"y": {"title": {"display": true, "text": "Average Duration (days)"}}}}, "annotation": {"annotations": {"duration_post_line": {"type": "line", "yMin": 36.31, "yMax": 20.55, "xMin": 71, "xMax": 125, "borderColor": "rgb(75, 192, 192)", "borderWidth": 2, "borderDash": [5, 5]}, "duration_post_label": {"type": "label", "xValue": 111.5, "yValue": 24.491948051948057, "backgroundColor": "rgba(245,245,245,0.6)", "content": ["-2.92/decade [R: -0.201]"], "font": {"size": 12}}}}};
 console.log(externalOptions);
 new Chart(ctx, {
 ...externalOptions,
 plugins: [],
 options: {
 responsive: true,
 maintainAspectRatio: false,
 interaction: {
 mode: 'index',
 intersect: false,
 },
 plugins: {
 annotation: externalOptions.annotation,
 },
 }
 });
});
&lt;/script&gt;

&lt;p&gt;Ok yeah that seem right.&lt;/p&gt;
&lt;h2 id="temperature-variability"&gt;Temperature variability&lt;/h2&gt;
&lt;p&gt;Winter &lt;strong&gt;low&lt;/strong&gt; temperatures are less stable and &lt;strong&gt;warming faster&lt;/strong&gt; than other parts of the year.&lt;/p&gt;
&lt;div
 class="chart"
 style="
 position: relative;
 height: 400px;
 width: 100%;
 margin-bottom: calc(var(--spacing) * 6);
 "
&gt;
 &lt;canvas id="a71a62bd1e42fad1"&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;script&gt;
document.addEventListener('DOMContentLoaded', () =&gt; {
 var ctx = document.getElementById('a71a62bd1e42fad1')
 var externalOptions = {"type": "line", "data": {"labels": ["1872", "1873", "1874", "1875", "1876", "1877", "1878", "1879", "1880", "1881", "1882", "1883", "1884", "1885", "1886", "1887", "1888", "1889", "1890", "1891", "1892", "1893", "1894", "1895", "1896", "1897", "1898", "1899", "1900", "1901", "1902", "1903", "1904", "1905", "1906", "1907", "1908", "1909", "1910", "1911", "1912", "1913", "1914", "1915", "1916", "1917", "1918", "1919", "1920", "1921", "1922", "1923", "1924", "1925", "1926", "1927", "1928", "1929", "1930", "1931", "1932", "1933", "1934", "1935", "1936", "1937", "1938", "1939", "1940", "1941", "1942", "1943", "1944", "1945", "1946", "1947", "1948", "1949", "1950", "1951", "1952", "1953", "1954", "1955", "1956", "1957", "1958", "1959", "1960", "1961", "1962", "1963", "1964", "1965", "1966", "1967", "1968", "1969", "1970", "1971", "1972", "1973", "1974", "1975", "1976", "1977", "1978", "1979", "1980", "1981", "1982", "1983", "1984", "1985", "1986", "1987", "1988", "1989", "1990", "1991", "1992", "1993", "1994", "1995", "1996", "1997", "1998", "1999", "2000", "2001", "2002", "2003", "2004", "2005", "2006", "2007", "2008", "2009", "2010", "2011", "2012", "2013", "2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025"], "datasets": [{"label": "Summer Avg Daily Low (\u00b0F)", "data": [null, 60.1, 60.7, 56.8, 59.8, 59.8, 61.4, 60.5, 60.6, 62.6, 59.1, 59.0, 60.1, 59.6, 59.6, 59.7, 59.6, 58.6, 59.7, 57.0, 59.5, 60.9, 62.5, 58.8, 60.1, 59.2, 60.8, 61.8, 61.7, 62.3, 58.4, 58.5, 58.5, 59.9, 60.7, 59.2, 59.8, 61.7, 60.9, 60.4, 58.9, 61.9, 61.1, 56.3, 62.4, 58.7, 60.4, 62.4, 60.1, 64.6, 61.2, 61.5, 57.8, 60.3, 59.6, 57.6, 59.2, 60.2, 62.9, 63.7, 63.5, 64.6, 62.6, 63.1, 63.8, 64.4, 61.4, 61.1, 61.2, 62.0, 60.1, 62.8, 61.2, 57.9, 59.3, 61.5, 61.2, 63.5, 57.9, 59.0, 60.6, 62.4, 62.0, 64.1, 61.5, 62.1, 58.4, 63.5, 58.2, 59.0, 56.8, 59.4, 59.8, 58.4, 60.1, 57.0, 59.7, 59.7, 62.1, 58.9, 57.8, 62.1, 59.1, 62.6, 61.6, 59.6, 60.2, 60.5, 60.3, 59.7, 59.4, 64.0, 62.2, 58.4, 60.2, 62.7, 63.7, 61.6, 60.8, 62.5, 55.9, 59.8, 58.8, 63.1, 58.4, 60.2, 60.0, 61.6, 61.3, 63.6, 64.4, 62.9, 58.9, 64.6, 64.7, 64.3, 62.5, 60.0, 65.4, 65.0, 64.9, 64.0, 62.8, 62.4, 64.4, 62.5, 65.1, 63.3, 64.5, 65.7, 64.6, 64.6, 63.1, 64.6], "fill": false, "borderColor": "rgb(255, 99, 132)", "tension": 0.1, "pointRadius": 0}, {"label": "Fall Avg Daily Low (\u00b0F)", "data": [26.9, 31.2, 37.4, 32.8, 35.9, 40.1, 38.9, 40.3, 33.4, 38.5, 41.7, 36.5, 40.7, 37.1, 36.6, 35.0, 37.7, 34.8, 38.2, 38.7, 39.3, 37.7, 38.2, 37.7, 33.6, 42.0, 37.6, 42.1, 41.1, 38.5, 39.8, 38.0, 41.3, 39.9, 41.5, 38.8, 42.3, 40.2, 38.9, 35.0, 40.5, 40.8, 42.3, 41.2, 38.7, 38.1, 40.0, 37.2, 43.7, 40.1, 44.8, 41.4, 40.5, 37.5, 36.7, 40.4, 40.3, 38.0, 40.2, 46.0, 37.4, 39.5, 42.0, 37.6, 38.5, 39.0, 40.0, 39.4, 39.8, 41.2, 37.7, 36.9, 41.7, 37.6, 38.6, 40.7, 41.6, 40.5, 38.4, 35.2, 37.3, 42.2, 41.0, 37.4, 39.9, 38.9, 40.7, 36.1, 37.7, 38.5, 37.1, 41.8, 37.3, 36.3, 35.8, 35.5, 40.1, 38.7, 39.4, 40.4, 36.5, 40.7, 36.5, 38.8, 34.1, 37.3, 40.0, 37.9, 37.8, 38.5, 39.3, 41.0, 38.4, 35.5, 37.1, 39.0, 36.9, 35.5, 39.4, 34.7, 37.9, 35.2, 43.2, 36.8, 36.1, 38.0, 42.1, 40.7, 40.3, 43.2, 38.9, 40.0, 43.4, 42.9, 38.8, 42.9, 41.1, 42.8, 41.2, 43.5, 39.6, 41.2, 37.3, 45.3, 46.4, 42.3, 39.2, 41.0, 39.5, 44.3, 41.8, 44.8, 46.5, 47.2], "fill": false, "borderColor": "rgb(255, 159, 64)", "tension": 0.1, "pointRadius": 0}, {"label": "Winter Avg Daily Low (\u00b0F)", "data": [-2.2, 2.7, 4.8, -2.6, 3.0, 15.8, 15.9, 3.3, 10.4, 9.3, 12.1, 0.8, 1.8, 3.0, -1.0, -1.3, 3.1, 12.1, 9.4, 13.0, 9.4, 0.7, 10.4, 5.9, 13.0, 7.8, 11.1, 6.7, 8.8, 6.4, 9.4, 4.9, 3.2, 6.7, 10.5, 8.2, 12.3, 7.6, 7.1, 10.9, 3.6, 10.4, 6.1, 13.4, 2.2, -1.0, 9.3, 9.5, 9.0, 14.5, 6.3, 11.5, 4.8, 10.0, 10.4, 6.9, 14.3, 2.7, 11.4, 22.3, 9.9, 9.2, 10.3, 10.8, 1.2, 3.8, 11.2, 9.9, 8.4, 11.4, 11.2, 6.9, 13.9, 6.4, 8.6, 9.8, 5.8, 6.9, 2.2, 6.0, 12.1, 10.4, 13.7, 5.1, 8.4, 10.0, 10.2, 11.0, 9.0, 6.4, 4.0, -1.0, 10.4, 8.1, 4.0, 5.4, 7.2, 8.3, 3.7, 6.1, 0.4, 10.9, 9.5, 9.4, 7.7, 3.7, 1.9, 4.6, 8.8, 10.5, 6.1, 9.6, 11.3, 1.8, 12.4, 18.0, 5.9, 4.6, 12.9, 10.2, 16.6, 10.3, 5.6, 11.3, 5.4, 11.3, 17.2, 14.1, 9.0, 12.1, 19.0, 11.2, 11.1, 13.6, 19.1, 9.7, 5.4, 7.7, 9.2, 12.5, 17.5, 8.4, 5.6, 13.8, 14.6, 16.3, 11.9, 9.2, 14.6, 12.6, 5.7, 18.5, 19.7, 7.6], "fill": false, "borderColor": "rgb(54, 162, 235)", "tension": 0.1, "pointRadius": 0}, {"label": "Spring Avg Daily Low (\u00b0F)", "data": [null, 31.3, 29.9, 28.6, 32.5, 33.8, 40.5, 36.6, 35.3, 37.5, 34.8, 31.0, 35.0, 34.2, 35.8, 34.8, 28.2, 36.9, 31.1, 33.7, 32.7, 30.6, 38.7, 36.7, 37.1, 33.2, 37.1, 31.9, 36.5, 36.3, 37.2, 37.5, 33.8, 35.7, 33.9, 29.8, 35.0, 33.1, 39.1, 38.5, 34.2, 33.8, 36.7, 36.8, 33.6, 32.6, 37.9, 36.1, 33.7, 38.7, 39.0, 32.0, 33.1, 37.8, 34.0, 37.3, 35.5, 36.2, 36.8, 36.7, 33.8, 36.3, 37.3, 36.3, 36.4, 35.5, 37.8, 34.4, 32.0, 38.4, 38.7, 31.4, 35.3, 35.5, 39.4, 33.1, 34.9, 36.3, 30.7, 33.1, 35.8, 34.7, 33.2, 36.6, 32.6, 35.3, 37.2, 37.4, 30.4, 31.7, 33.0, 34.7, 34.2, 31.4, 33.9, 32.8, 36.2, 34.5, 34.5, 33.2, 33.9, 37.8, 34.2, 32.3, 36.0, 42.1, 35.9, 33.7, 34.8, 37.2, 36.2, 35.3, 33.2, 40.0, 38.1, 40.7, 37.7, 33.3, 36.1, 39.1, 36.3, 34.1, 36.4, 35.1, 31.0, 31.4, 38.3, 38.0, 40.0, 36.5, 32.9, 36.3, 37.7, 37.6, 40.8, 40.0, 33.8, 37.0, 42.4, 36.1, 44.5, 34.1, 33.5, 38.4, 41.3, 38.9, 36.6, 34.2, 38.4, 39.9, 36.3, 37.5, 40.3, 39.2], "fill": false, "borderColor": "rgb(75, 192, 192)", "tension": 0.1, "pointRadius": 0}]}, "annotation": {"annotations": {"summer_line": {"type": "line", "yMin": 59.58, "yMax": 64.41, "xMin": 98, "xMax": 153, "borderColor": "rgb(255, 99, 132)", "borderWidth": 2, "borderDash": [5, 5]}, "summer_label": {"type": "label", "xValue": 109.0, "yValue": 60.548571428571435, "backgroundColor": "rgba(245,245,245,0.6)", "content": ["+0.88/decade [R: 0.617]"], "font": {"size": 12}}, "fall_line": {"type": "line", "yMin": 36.68, "yMax": 43.32, "xMin": 98, "xMax": 153, "borderColor": "rgb(255, 159, 64)", "borderWidth": 2, "borderDash": [5, 5]}, "fall_label": {"type": "label", "xValue": 114.5, "yValue": 38.67453007518797, "backgroundColor": "rgba(245,245,245,0.6)", "content": ["+1.21/decade [R: 0.632]"], "font": {"size": 12}}, "winter_line": {"type": "line", "yMin": 6.65, "yMax": 13.98, "xMin": 98, "xMax": 153, "borderColor": "rgb(54, 162, 235)", "borderWidth": 2, "borderDash": [5, 5]}, "winter_label": {"type": "label", "xValue": 131.0, "yValue": 11.043759398496242, "backgroundColor": "rgba(245,245,245,0.6)", "content": ["+1.33/decade [R: 0.457]"], "font": {"size": 12}}, "spring_line": {"type": "line", "yMin": 35.12, "yMax": 38.45, "xMin": 98, "xMax": 153, "borderColor": "rgb(75, 192, 192)", "borderWidth": 2, "borderDash": [5, 5]}, "spring_label": {"type": "label", "xValue": 125.5, "yValue": 36.7875, "backgroundColor": "rgba(245,245,245,0.6)", "content": ["+0.61/decade [R: 0.337]"], "font": {"size": 12}}}}};
 console.log(externalOptions);
 new Chart(ctx, {
 ...externalOptions,
 plugins: [],
 options: {
 responsive: true,
 maintainAspectRatio: false,
 interaction: {
 mode: 'index',
 intersect: false,
 },
 plugins: {
 annotation: externalOptions.annotation,
 },
 }
 });
});
&lt;/script&gt;

&lt;p&gt;In context, the global warming rate is &lt;a href="https://www.climate.gov/news-features/understanding-climate/climate-change-global-temperature"&gt;about +0.36°F per decade since 1982&lt;/a&gt;. Northern Minnesota is warming even faster.&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/climate-change-northern-minnesota-warming-faster.png" alt="Northern Minnesota is warming faster"&gt;&lt;/p&gt;
&lt;h2 id="lake-ice"&gt;Lake Ice&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Minnesota’s lake ice season decreased by up to 14 days over the last ~50 years. Compared to 50 years ago, the average July-August surface water temperatures on Minnesota lakes are 3.0 – 3.9 degrees warmer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://www.pca.state.mn.us/news-and-stories/mpca-dnr-minnesotas-lake-ice-season-decreased-by-up-to-14-days-due-to-climate-change"&gt;Source: Minnesota Pollution Control Agency (PCA)&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Winter is changing in ways you might not expect.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Temperatures are rising at rates I would describe as both alarmingly fast and difficult to perceive.&lt;/li&gt;
&lt;li&gt;The lake ice season is shrinking.&lt;/li&gt;
&lt;li&gt;Snow patterns are highly variable since tracking began in 1880. A big bump in latter half the 20th century makes the last 30 years of decline seem steeper than it would be in absolute historic terms.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="footnotes-and-methods"&gt;Footnotes and methods&lt;/h2&gt;
&lt;p&gt;You can do a lot using the &lt;a href="https://climate-explorer.dnr.state.mn.us/main/historical"&gt;DNR Minnesota Climate Explorer&lt;/a&gt;. For some of my own analysis, I verified my numbers against their tool.&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/climate-explorer-2.png" alt="Climate Explorer"&gt;&lt;/p&gt;
&lt;h3 id="data-and-analysis-tools"&gt;Data and Analysis Tools&lt;/h3&gt;
&lt;p&gt;All the data and code used for this post is available on &lt;a href="https://github.com/subdavis/subdavis.github.io/tree/main/content/posts/2025-12-twin-cities-climate"&gt;GitHub&lt;/a&gt;. I&amp;rsquo;ve mainly used Python with Pandas and Chart.js for the visualizations.&lt;/p&gt;
&lt;p&gt;The Minnesota DNR provides histoirical daily climate data going back to the 1870s &lt;a href="https://www.dnr.state.mn.us/climate/twin_cities/listings.html"&gt;on their website&lt;/a&gt;. The downloads are all broken, but I was able to scrape them directly and explain how in the code repository &lt;code&gt;README.md&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="the-1970s-pivot"&gt;The 1970s pivot&lt;/h3&gt;
&lt;p&gt;Climate trend lines (like the ones above) intended to show deviation from historical patterns often begin in the 1970s without explaining why.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1950(ish) is considered the inflection point of human-caused greenhouse gas emmisions.&lt;/li&gt;
&lt;li&gt;Until the 1970s, the effects of arisols in the atmosphere artifically suppressed those trends a bit.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The amount of CO2 in the atmosphere, &lt;a href="https://gml.noaa.gov/ccgg/trends/data.html"&gt;measured continuously at Mauna Loa Observatory since 1958&lt;/a&gt;, is a startlingly smooth curve.&lt;/p&gt;
&lt;div
 class="chart"
 style="
 position: relative;
 height: 400px;
 width: 100%;
 margin-bottom: calc(var(--spacing) * 6);
 "
&gt;
 &lt;canvas id="3a427f4d3ab90bde"&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;script&gt;
document.addEventListener('DOMContentLoaded', () =&gt; {
 var ctx = document.getElementById('3a427f4d3ab90bde')
 var externalOptions = {"type": "bar", "data": {"labels": ["1959", "1960", "1961", "1962", "1963", "1964", "1965", "1966", "1967", "1968", "1969", "1970", "1971", "1972", "1973", "1974", "1975", "1976", "1977", "1978", "1979", "1980", "1981", "1982", "1983", "1984", "1985", "1986", "1987", "1988", "1989", "1990", "1991", "1992", "1993", "1994", "1995", "1996", "1997", "1998", "1999", "2000", "2001", "2002", "2003", "2004", "2005", "2006", "2007", "2008", "2009", "2010", "2011", "2012", "2013", "2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024"], "datasets": [{"type": "line", "label": "Atmospheric CO\u2082 (ppm)", "data": [315.98, 316.91, 317.64, 318.45, 318.99, 319.62, 320.04, 321.37, 322.18, 323.05, 324.62, 325.68, 326.32, 327.46, 329.68, 330.19, 331.13, 332.03, 333.84, 335.41, 336.84, 338.76, 340.12, 341.48, 343.15, 344.87, 346.35, 347.61, 349.31, 351.69, 353.2, 354.45, 355.7, 356.54, 357.21, 358.96, 360.97, 362.74, 363.88, 366.84, 368.54, 369.71, 371.32, 373.45, 375.98, 377.7, 379.98, 382.09, 384.02, 385.83, 387.64, 390.1, 391.85, 394.06, 396.74, 398.81, 401.01, 404.41, 406.76, 408.72, 411.65, 414.21, 416.41, 418.53, 421.08, 424.61], "fill": false, "borderColor": "rgb(75, 192, 192)", "tension": 0.1, "pointRadius": 0, "yAxisID": "y"}, {"type": "bar", "label": "Year-over-Year Change (ppm)", "data": [null, 0.93, 0.73, 0.81, 0.54, 0.63, 0.42, 1.33, 0.81, 0.87, 1.57, 1.06, 0.64, 1.14, 2.22, 0.51, 0.94, 0.9, 1.81, 1.57, 1.43, 1.92, 1.36, 1.36, 1.67, 1.72, 1.48, 1.26, 1.7, 2.38, 1.51, 1.25, 1.25, 0.84, 0.67, 1.75, 2.01, 1.77, 1.14, 2.96, 1.7, 1.17, 1.61, 2.13, 2.53, 1.72, 2.28, 2.11, 1.93, 1.81, 1.81, 2.46, 1.75, 2.21, 2.68, 2.07, 2.2, 3.4, 2.35, 1.96, 2.93, 2.56, 2.2, 2.12, 2.55, 3.53], "backgroundColor": "rgba(255, 99, 132, 0.7)", "borderColor": "rgb(255, 99, 132)", "borderWidth": 1, "yAxisID": "y1"}]}, "options": {"scales": {"y": {"type": "linear", "position": "left", "title": {"display": true, "text": "CO\u2082 Concentration (ppm)"}}, "y1": {"type": "linear", "position": "right", "title": {"display": true, "text": "Year-over-Year Change (ppm)"}, "grid": {"drawOnChartArea": false}}, "x": {"title": {"display": true, "text": "Year"}}}}};
 console.log(externalOptions);
 new Chart(ctx, {
 ...externalOptions,
 plugins: [],
 options: {
 responsive: true,
 maintainAspectRatio: false,
 interaction: {
 mode: 'index',
 intersect: false,
 },
 plugins: {
 annotation: externalOptions.annotation,
 },
 }
 });
});
&lt;/script&gt;

&lt;h3 id="disclaimer"&gt;Disclaimer&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m not a climate scientist or a statistician. I was interested in seeing second-order measures of variability that I&amp;rsquo;ve never really seen discussed in an emperical way.&lt;/p&gt;</content:encoded></item><item><title>Thinking about Generative AI</title><link>https://subdavis.com/posts/2025-11-generative-ai/</link><pubDate>Sun, 23 Nov 2025 00:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2025-11-generative-ai/</guid><description>A time capsule journal entry</description><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Long before we get to the point where a machine is a moral agent, we will have machines that are capable of suffering. - Ted Chiang&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I want to write out some thoughts to revisit later. It can be easy to get revisionist about what you believed and when, so here is a list of things I thought were true in late 2025 after ~2 years of pretty extensive experience with frontier models, especially Anthropic&amp;rsquo;s Claude.&lt;/p&gt;
&lt;h2 id="some-takes"&gt;Some takes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;AI is alarmingly good at all sorts of things. It is good at writing code, dealing with legal documents, talking through household repairs, and many other tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We are not in a bubble comparable to previous bubbles, but the market is overheated. Many participants will suffer when their bad bets fail. A sector-wide collapse followed by another &amp;ldquo;AI winter&amp;rdquo; is not likely, but a recession that affects regular American families feels very possible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Environmental concerns about AI and data centers can be &lt;a href="https://andymasley.substack.com/p/individual-ai-use-is-not-bad-for"&gt;pretty misleading&lt;/a&gt;. Environmental harms are important to mitigate, but environmental advocates seem overly focused on data centers compared to their relative impact or the marginal cost of reducing that impact.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As the US government actively sabotages renewable energy infrastructure projects, power demands from AI companies is both &lt;a href="https://investors.constellationenergy.com/news-releases/news-release-details/constellation-meta-sign-20-year-deal-clean-reliable-nuclear"&gt;applying positive pressure toward renewable energy transition&lt;/a&gt; and &lt;a href="https://stateimpactcenter.org/insights/data-centers-straining-the-grid-and-your-wallet"&gt;keeping fossil fuel plants online longer&lt;/a&gt; than they otherwise would be.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using AI to produce art, especially that which supplants human artists, is bad. Artists have a unique and important role in a healthy society. It&amp;rsquo;s clear that this is already happening, and I&amp;rsquo;m very worried about it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Playing around and making art for personal use or amusement is mostly benign, though.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AI boosters are pretty universally untrustworthy. Most of the experts on this technology have conflicts of interest, and their incentives are not aligned with public good. We need more independent experts outside the AI industry.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AI skeptics often confuse material debates about the merits and capabilities of AI systems with larger philosophical or moral debates about capitalism. These are both important, but conflating them can result in motivated reasoning.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AI will, in the near term, accelerate wealth inequality. Skeptics are correct about this. Longer term effects depend on what we decide to do as a society.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AI will cause (mostly psychological) harm to humans at higher rates than other digital technologies. It will make harms from social media or smartphone use seem quaint. Society will mostly tolerate these harms and ignore them, just as it ignores the 1 million annual worldwide deaths from automobiles (40,000 in the US).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Large language models are not on the cusp of becoming self-aware or developing the capacity to suffer, but &lt;a href="https://alleninstitute.org/news/one-of-worlds-most-detailed-virtual-brain-simulations-is-changing-how-we-study-the-brain/"&gt;I am still worried that we might cause machines to experience suffering in the future.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We have avoided nuclear holocaust thus far by luck. Nuclear disarmament should be an urgent priority. All sides of the AI discourse should agree on this.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="disclaimers"&gt;Disclaimers&lt;/h2&gt;
&lt;p&gt;Qualifiers like &amp;ldquo;maybe&amp;rdquo; or &amp;ldquo;likely&amp;rdquo; were omitted because I am uncertain about everything, and otherwise I would have placed them everywhere. My goal was to stake out some positions, not hedge against being wrong.&lt;/p&gt;
&lt;p&gt;I am neither a skeptic nor a booster. I think these technologies are exceedingly &lt;em&gt;interesting&lt;/em&gt;. The producers of LLMs are mostly untrustworthy, but the nuance has been lost. We usually don&amp;rsquo;t blame Facebook users when Meta does something shitty.&lt;/p&gt;
&lt;p&gt;We exist in a dynamic period where challengers across domains could leverage AI against more lethargic incumbents. Civic and social good might result if good people would be willing to selectively wield AI in service of their desired outcomes. There&amp;rsquo;s plenty of reason to be angry at Meta, for example, but it doesn&amp;rsquo;t make sense to stubbornly refuse to use &lt;a href="https://www.llama.com/"&gt;Llama&lt;/a&gt;, &lt;a href="https://pytorch.org/"&gt;PyTorch&lt;/a&gt;, &lt;a href="https://ai.meta.com/sam3/"&gt;sam3&lt;/a&gt;, or React in pursuit of a better future.&lt;/p&gt;</content:encoded></item><item><title>Self-hosting Sustainably</title><link>https://subdavis.com/posts/2025-11-selfhosting/</link><pubDate>Sun, 16 Nov 2025 00:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2025-11-selfhosting/</guid><description>With docker compose and not much else.</description><content:encoded>&lt;p&gt;At some point, every home server operator loses steam. After 6+ years running ~20 applications on a home server, I wish only for things to work properly and not waste my time.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re anything like me, &lt;strong&gt;docker compose is probably all you need.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/subdavis/selfhosted"&gt;Here is my full configuration&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="my-needs"&gt;My needs&lt;/h2&gt;
&lt;p&gt;Any conversation about the &amp;ldquo;right&amp;rdquo; setup should be grounded in goals. These are mine.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A bunch of network-reachable applications&lt;/li&gt;
&lt;li&gt;Some reachable over public internet&lt;/li&gt;
&lt;li&gt;Some behind SSO, IP Whitelist, or both&lt;/li&gt;
&lt;li&gt;Running as &lt;code&gt;&amp;lt;app&amp;gt;.subdavis.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Plus DNS filtering via AdGuardHome&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I no longer value arcane wisdom earned through &lt;a href="https://immich.app/cursed-knowledge"&gt;suffering&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="10-year-old-hardware"&gt;10 year old hardware&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;An old 2010s Dell Optiplex Core i3, 8GB RAM&lt;/li&gt;
&lt;li&gt;Rehoused in a Fractal Design Node 304 Mini-ITX&lt;/li&gt;
&lt;li&gt;With a 4TB media HDD&lt;/li&gt;
&lt;li&gt;And a 2-Drive 2TB RAID-1 MDADM array for more important data&lt;/li&gt;
&lt;li&gt;And another 1TB HDD for backups&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="everything-is-computer-docker-compose"&gt;Everything is &lt;del&gt;computer&lt;/del&gt; docker compose&lt;/h2&gt;
&lt;p&gt;My basic philosophy is that I should be able to get this junk running on new or reimaged hardware in a couple hours if needed. I can &lt;code&gt;apt dist-upgrade&lt;/code&gt; worry-free. Docker&amp;rsquo;s restart policy has never betrayed me the way &lt;a href="https://nosystemd.org/"&gt;systemd repeatedly has.&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="a-little-bit-of-systemd-though"&gt;A little bit of systemd, though&lt;/h2&gt;
&lt;p&gt;systemd starts dockerd, which takes care of bringing up the compose stack on restart or container failure. Docker handles dependency constraints with grace.&lt;/p&gt;
&lt;p&gt;One extra need is for the external disks to be mounted properly before docker even tries to start. fstab can create race conditions, but systemd allows you to set mount dependencies on services, which is quite handy.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-conf" data-lang="conf"&gt;[Unit]
Description=RAID1

[Mount]
What=/dev/md0
Where=/media/primary
Type=ext4
Options=defaults,auto,noatime,exec

[Install]
WantedBy=docker.service
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="traefik-is-whatever-man"&gt;Traefik is whatever, man&lt;/h2&gt;
&lt;p&gt;I use &lt;a href="https://traefik.io/traefik"&gt;traefik&lt;/a&gt;. I learned it back when I had more energy for such things. I geniunely like its tag-based config because it keeps my &lt;code&gt;docker-compose.yml&lt;/code&gt; modular and clean, but traefik config is verbose and annoying to troubleshoot. If I were starting over today I might try &lt;a href="https://caddyserver.com/"&gt;caddy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Still, the Letsencrypt support and Cloudflare companion have never let me down. Often, my new hostname has registered and is ready to serve traffic before the new app has even booted.&lt;/p&gt;
&lt;h2 id="tired-of-fighting-dns"&gt;Tired of fighting DNS&lt;/h2&gt;
&lt;p&gt;Maybe 6-8x per year, AdGuardHome goes down. Last year a slowly failing power supply lead to a lot of random system crashes. On this matter I have moved past depression to acceptance.&lt;/p&gt;
&lt;p&gt;If you, too, feel traumatized by &lt;code&gt;dhcp-option=6&lt;/code&gt;, I recommend giving up on your end user device discovery dreams and dealing with DNS at the router instead.&lt;/p&gt;
&lt;p&gt;I run dnsmasq on my Edgerouter, redirect all DNS queries to my adguard instance wit NAT, and have a NAT rule enable/disable switch I can throw to take my AdGuardHome instance out of the critical path of internet access. &lt;a href="https://github.com/subdavis/selfhosted/blob/main/docs/pihole-dnsmasq.md"&gt;I wrote about that here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(The NAT rule is necessary regardless of your config because plenty of IoT devices hard-code their DNS anyway. Looking at you, Philips Hue Bridge)&lt;/p&gt;
&lt;h2 id="wireguard--tailscale-"&gt;Wireguard? 🙅 Tailscale? 👍&lt;/h2&gt;
&lt;p&gt;Wireguard config has consumed many hours of my life. The final straw was tweaking MTU while dealing with packet loss over mobile networks (T Mobile). I noped out and set up Tailscale.&lt;/p&gt;
&lt;h2 id="no-more-innovation-tokens"&gt;No more innovation tokens&lt;/h2&gt;
&lt;p&gt;I started out using &lt;a href="https://en.wikipedia.org/wiki/Container_Linux"&gt;CoreOS&lt;/a&gt; which turned out to be foolish because it was abandoned about 2 years later.&lt;/p&gt;
&lt;p&gt;I also used to do &lt;a href="https://github.com/subdavis/selfhosted/blob/10f6aef418ffadcb3cefca16f3fad604abfb97a2/minio.service"&gt;insane, truly unhinged shit&lt;/a&gt; with &lt;code&gt;systemd&lt;/code&gt;. I believed that &lt;code&gt;docker compose&lt;/code&gt; was not a valid substitue for an init system, whatever that means. Don&amp;rsquo;t be like me.&lt;/p&gt;
&lt;p&gt;Unnecessary complexity is fine for learning. A particular choice may emerge out of a desire for novelty or fun or curiosity, but be clear-eyed, lest you confuse these indulgences with actual improvements. It&amp;rsquo;s perfectly reasonable to tire of tinkering with complicated setups. Using docker compose isn&amp;rsquo;t admitting defeat.&lt;/p&gt;
&lt;p&gt;Keep it simple, stupid.&lt;/p&gt;</content:encoded></item><item><title>RSS - Really Simple Syndication</title><link>https://subdavis.com/posts/2025-11-rss-is-good/</link><pubDate>Sat, 08 Nov 2025 00:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2025-11-rss-is-good/</guid><description>The internet of the future is from 1999</description><content:encoded>&lt;p&gt;Podcasts are nice. They show up in chronological order, download in the background, and work the same in every app. In a world of algorithmic walled garden social &amp;ldquo;platforms&amp;rdquo;, this is a small miracle.&lt;/p&gt;
&lt;p&gt;Why can&amp;rsquo;t more of the internet be like that? As &lt;a href="https://www.theatlantic.com/ideas/archive/2023/03/social-media-algorithms-twitter-meta-rss-reader/673282/"&gt;Yair Rosenberg wrote in 2023&lt;/a&gt;, you can take back control of what you see on the internet.&lt;/p&gt;
&lt;h2 id="its-just-rss"&gt;It&amp;rsquo;s just RSS&lt;/h2&gt;
&lt;p&gt;The technology of a hopeful future actually came out in 1999. It&amp;rsquo;s just &lt;a href="https://en.wikipedia.org/wiki/RSS"&gt;RSS&lt;/a&gt;. Every morning I fire up &lt;a href="https://netnewswire.com/"&gt;NetNewsWire&lt;/a&gt;. I pick something, open it up, and read it. Nobody tracks me. There are no cookie policies to accept. There&amp;rsquo;s no like button or comment section.&lt;/p&gt;
&lt;p&gt;In the days before profit motives overwhelmed product design, humans understood that the media you wanted to access had very little to do with the &amp;ldquo;platform&amp;rdquo; it was distributed by.&lt;/p&gt;
&lt;p&gt;Imagine Youtube videos and Instagram comments and new issues of your local newsblog all just appear in one chronological feed. The obvious problem with all this control is that nobody can sell ads or harvest your personal data if you&amp;rsquo;re safe in your own third party app (Outside of a web browser! Offline, even!).&lt;/p&gt;
&lt;h2 id="podcast-freedom-fleeting"&gt;Podcast freedom: fleeting&lt;/h2&gt;
&lt;p&gt;Even podcasts may not have this freedom forever as companies try to capture the remaining frontier. The New York Times is taking steps to force listeners into their own app. Spotify made major plays in the last few years to get exclusive podcasts behind their paywall. Podcasters now implore that you watch their episodes on YouTube, where they can more effectively monetize and dissect a larger stream of analytic data. Eyeballs, baby.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s perfectly reasonable to want subscribers to pay for your content. Substack and Patreon are positive role models for how this should work: You give them cash and they give you an individualized subscription link to import the feed in your own app. The ads you hear are at the discretion of the creator, not the platform.&lt;/p&gt;
&lt;h2 id="hunting-for-ghost-feeds"&gt;Hunting for ghost feeds&lt;/h2&gt;
&lt;p&gt;Despite the incentives for companies to keep you on their site, looking at their ads and contributing to their engagement metrics, RSS doggedly persists. Idealistic developers have built RSS into most of the content management platforms (like Wordpress) that media use (mostly without paying). Some, like The Atlantic, even publish articles paywall-free, though the feeds are tough to find.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using a chrome extension called &lt;a href="https://chromewebstore.google.com/detail/get-rss-feed-url/kfghpdldaipanmkhfpdcjglncmilendn"&gt;Get RSS Feed URL&lt;/a&gt; that helps surface hidden feeds. It works in places you might not even expect, like Bluesky.&lt;/p&gt;
&lt;p&gt;&lt;img src="./bluesky.png" alt="it even works on bluesky"&gt;&lt;/p&gt;
&lt;h2 id="conscious-of-content"&gt;Conscious of content&lt;/h2&gt;
&lt;p&gt;Adopting an RSS-first means of consuming the web takes work, and the setup user experience can be rough. The exercise, though, can be enlightening. You might have a sense of how much &lt;em&gt;time&lt;/em&gt; you spend on your phone in a day, but where is that time spent?&lt;/p&gt;
&lt;p&gt;The content of my media diet have changed a bit in the last 6 months since I went all-in on RSS. It took real effort, but now, when I open my phone, I reach for my RSS reader rather than a browser or a social app. It feels like an improvement.&lt;/p&gt;</content:encoded></item><item><title>A few photos from Pittsburgh</title><link>https://subdavis.com/posts/2025-11-pittsburgh-photos/</link><pubDate>Tue, 04 Nov 2025 00:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2025-11-pittsburgh-photos/</guid><description>Taken on a recent visit in October 2025</description><content:encoded>&lt;p&gt;Photos from RandyLand, Bicycle Heaven, and around town in Pittsburgh, PA. October 2025.&lt;/p&gt;</content:encoded></item><item><title>I want to get off Mr. Bones' Wild Ride</title><link>https://subdavis.com/posts/2025-11-introduction/</link><pubDate>Sat, 01 Nov 2025 00:00:00 +0000</pubDate><guid>https://subdavis.com/posts/2025-11-introduction/</guid><description>We've given Meta too much and asked for too little.</description><content:encoded>&lt;p&gt;In June of &amp;lsquo;25 I shut off most of my social media accounts: Facebook, Instagram, and LinkedIn. I kept reddit under the happy delusion that I could learn to use it responsibly (lol) but I&amp;rsquo;ve made strides to curtail that as well.&lt;/p&gt;
&lt;p&gt;Since then, I&amp;rsquo;ve often thought about what the experience of being &amp;ldquo;on the internet&amp;rdquo; is like, and how that&amp;rsquo;s changed since I began forming digital habits back in High School. My memory is relatively poor, so a habit-forming tool for capturing a bit of life is wonderful. Having my good intentions packaged up to generate ad revenue and give teenagers an anxiety disorder is less so. &lt;a href="https://www.reuters.com/investigations/meta-is-earning-fortune-deluge-fraudulent-ads-documents-show-2025-11-06/"&gt;10% of Meta&amp;rsquo;s revenue for 2024 came from fraud.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Still, I enjoy reading posts by people I&amp;rsquo;ll never meet. Putting stuff out into the world is fun. The &lt;a href="https://bearblog.dev/discover/"&gt;Bear Blog discover feed&lt;/a&gt; is usually pretty wholesome, HackerNews is still rockin&amp;rsquo;, and my RSS app is chock full of honest work by decent humans. In the near future I&amp;rsquo;d even like to recommend some of them.&lt;/p&gt;
&lt;p&gt;The prevailing form and style of most social platforms lack honesty and curiosity.&lt;/p&gt;</content:encoded></item></channel></rss>