<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Damiano</title>
    <description>The latest articles on DEV Community by Damiano (@damiano_builds).</description>
    <link>https://dev.to/damiano_builds</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4184263%2F959d570c-975b-41c6-8482-fec09fd0bf8e.png</url>
      <title>DEV Community: Damiano</title>
      <link>https://dev.to/damiano_builds</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/damiano_builds"/>
    <language>en</language>
    <item>
      <title>Call a local model from plain Python: no SDK, no dependencies</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Fri, 16 Oct 2026 08:55:26 +0000</pubDate>
      <link>https://dev.to/damiano_builds/call-a-local-model-from-plain-python-no-sdk-no-dependencies-3gp5</link>
      <guid>https://dev.to/damiano_builds/call-a-local-model-from-plain-python-no-sdk-no-dependencies-3gp5</guid>
      <description>&lt;p&gt;Most "use a local model" tutorials start with a virtualenv and a dozen packages. For a small tool you may not need any of them. If a local Ollama server is running, Python's standard library is enough to talk to it.&lt;/p&gt;

&lt;p&gt;I used this approach in Sportello chiaro, a small helper I built to explain Swiss administrative letters in plain Italian. It is Python 3.10, standard library only, and about 306 lines in total. No web framework, no HTTP client package, no SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Ollama gives you
&lt;/h2&gt;

&lt;p&gt;Ollama runs a local HTTP server on port 11434. Its API documentation describes a &lt;code&gt;POST /api/generate&lt;/code&gt; endpoint, and a &lt;code&gt;"stream": false&lt;/code&gt; option that returns one JSON object instead of a stream of chunks. The generated text is in the &lt;code&gt;response&lt;/code&gt; field. That is all the plumbing you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  The call, in plain Python
&lt;/h2&gt;

&lt;p&gt;This is an illustrative example written for this article, not an excerpt from the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ask_local_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma3:1b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434/api/generate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter. First, set a generous timeout: a small model on a CPU is slow. Second, keep &lt;code&gt;stream&lt;/code&gt; off unless you actually want to print tokens as they arrive. Without streaming, the function is a plain "string in, string out" call that is easy to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a small model on a CPU is like
&lt;/h2&gt;

&lt;p&gt;In my own test of the project, Gemma 3 1B on a CPU took about 40 seconds to explain a Zahlungsbefehl (a Swiss payment order). That is not fast, but for a letter you read once it is acceptable, and nothing leaves the machine. For documents like these, privacy was the reason to go local in the first place.&lt;/p&gt;

&lt;p&gt;Speed is the price. If you need answers in a second, a 1B model on a CPU is the wrong tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why drop the dependencies
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nothing to install.&lt;/strong&gt; If Python 3.10 and Ollama are there, it runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less to break.&lt;/strong&gt; There is no package that changes its API under you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to read.&lt;/strong&gt; 306 lines can be read in one sitting, which matters when a tool handles personal letters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The standard library is not always the better choice. If you need retries, connection pooling or async calls, a proper client library pays for itself. For a one-shot call per document, &lt;code&gt;urllib&lt;/code&gt; was enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Before adding a dependency for a local model, try the plain HTTP call. For a small, single-purpose tool it may be all you need.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>ollama</category>
    </item>
    <item>
      <title>Your TypeScript types disappear at runtime: validating data at the edges of a Nuxt app</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Thu, 15 Oct 2026 01:29:27 +0000</pubDate>
      <link>https://dev.to/damiano_builds/your-typescript-types-disappear-at-runtime-validating-data-at-the-edges-of-a-nuxt-app-mn6</link>
      <guid>https://dev.to/damiano_builds/your-typescript-types-disappear-at-runtime-validating-data-at-the-edges-of-a-nuxt-app-mn6</guid>
      <description>&lt;p&gt;TypeScript checks your code before it runs. Once it ships, the types are gone. The compiler cannot tell you what a third-party API actually returned at 3 AM on a Sunday.&lt;/p&gt;

&lt;p&gt;I've written full-stack TypeScript with Nuxt and Vue for about six years. The production bugs I remember most were rarely logic errors. Most came from one assumption: "the data has the shape I typed it as."&lt;/p&gt;

&lt;p&gt;The examples below are small and made up for this article. They are not taken from a specific project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: a type assertion is a promise, not a check
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/api/user/[id].get.ts&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;free&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEventHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getRouterParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;$fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compiles. If the API one day returns &lt;code&gt;email: null&lt;/code&gt; for accounts created through a different sign-up flow, &lt;code&gt;user.email.toLowerCase()&lt;/code&gt; throws. The &lt;code&gt;as User&lt;/code&gt; told the compiler to stop asking questions. It checked nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix: parse at the boundary with a schema
&lt;/h2&gt;

&lt;p&gt;A schema library such as Zod keeps the type and the runtime check in one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;free&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEventHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getRouterParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;$fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nf"&gt;createError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;statusMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Upstream returned an unexpected shape&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things change. The &lt;code&gt;null&lt;/code&gt; case is now a type error you have to handle, not a surprise in production. And a bad upstream response fails loudly with a clear status, instead of a vague &lt;code&gt;TypeError&lt;/code&gt; deep in your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to validate, and where not to bother
&lt;/h2&gt;

&lt;p&gt;You don't need a schema around every object. Validate where data crosses a trust boundary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Responses from APIs you don't control&lt;/li&gt;
&lt;li&gt;Request bodies and query params in your own server routes (&lt;code&gt;readValidatedBody&lt;/code&gt; and &lt;code&gt;getValidatedQuery&lt;/code&gt; in Nitro/H3 take a validator for this)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;localStorage&lt;/code&gt;, cookies and anything else the user can edit&lt;/li&gt;
&lt;li&gt;Output of any model or service whose format isn't guaranteed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside your own code, after the boundary, trust the types.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tradeoff to be aware of
&lt;/h2&gt;

&lt;p&gt;Schemas cost a little bundle size and a little time per parse. On the server this rarely matters. In a hot client path, parse once when the data arrives, not on every render.&lt;/p&gt;

&lt;p&gt;Also, strict schemas can make you reject responses that were fine in practice. Decide per field: &lt;code&gt;.passthrough()&lt;/code&gt; or &lt;code&gt;.strip()&lt;/code&gt; for unknown keys, and be careful with strict enums on upstreams that add values without warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;as SomeType&lt;/code&gt; is documentation. A schema is a check. Use assertions for things you actually know, and parse the rest at the edge.&lt;/p&gt;

</description>
      <category>typescript</category>
    </item>
    <item>
      <title>Let the model explain, let the code count: a local LLM helper for Swiss administrative letters</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Thu, 15 Oct 2026 01:26:09 +0000</pubDate>
      <link>https://dev.to/damiano_builds/let-the-model-explain-let-the-code-count-a-local-llm-helper-for-swiss-administrative-letters-54je</link>
      <guid>https://dev.to/damiano_builds/let-the-model-explain-let-the-code-count-a-local-llm-helper-for-swiss-administrative-letters-54je</guid>
      <description>&lt;p&gt;In Switzerland, a letter from a public office can start a clock. A &lt;em&gt;Zahlungsbefehl&lt;/em&gt; (payment order) gives you 10 days to object. An &lt;em&gt;Einsprache&lt;/em&gt; period is often 30. Miss the date and you lose a right. Many people read these letters in a language that isn't their first.&lt;/p&gt;

&lt;p&gt;I built a small local tool, "Sportello chiaro", to explain such a letter in plain Italian and point out the deadline. It's an MVP. I want to describe the one design decision that I think matters, and be clear about what is and isn't tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule: the model never produces the critical facts
&lt;/h2&gt;

&lt;p&gt;A language model is good at explaining. It is not reliable at date arithmetic or at quoting the right number of days. So the deadline does not come from the model. It comes from code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SERVED_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(?:zugestellt am|zustellung am|zugestellt:|notifié le|notifiée le|reçu le)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\s*(\d{1,2})[.\/](\d{1,2})[.\/](\d{4})&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool finds the notification date with this pattern, picks the number of days from the document type (10 for a Zahlungsbefehl or Strafbefehl, 30 for an Einsprache), and computes &lt;code&gt;end = served + timedelta(days=days)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The model then gets the letter and a system prompt that says, in short: use only information present in the letter, and do not invent dates, amounts or law articles. Its job is the readable explanation, in four fixed sections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Graceful failure
&lt;/h2&gt;

&lt;p&gt;The call to the local model is plain &lt;code&gt;urllib&lt;/code&gt;, and it returns the text of the first choice. If the model call fails, the analysis falls back to the rule-based output and records the error. The user still sees the deadline and a response template, just without the prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is tested
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Stack: Python 3.10 standard library only (&lt;code&gt;urllib&lt;/code&gt;, &lt;code&gt;http.server&lt;/code&gt;, &lt;code&gt;re&lt;/code&gt;, &lt;code&gt;datetime&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt;), about 300 lines. A CLI and a local web page on &lt;code&gt;127.0.0.1:8080&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Model: Gemma 3 1B (Q4_K_M) through &lt;code&gt;llama-cpp-python&lt;/code&gt; for my tests. The intended default is a larger model through Ollama, which I have &lt;strong&gt;not&lt;/strong&gt; tested.&lt;/li&gt;
&lt;li&gt;Six pytest tests, rules level only. One checks that a notification on 12.10 plus 10 days gives 22.10.2026. Another checks that a response template is selected. A third checks that an unrelated text gets low urgency.&lt;/li&gt;
&lt;li&gt;One end-to-end run on one invented letter. About 40 seconds with the 1B model.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is not done
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No schema check on the model's output. The tool does not verify that the answer contains the four sections, or that the deadline in the prose matches the computed one. That's the next thing I'd add.&lt;/li&gt;
&lt;li&gt;Weekends and public holidays are not handled in the deadline calculation.&lt;/li&gt;
&lt;li&gt;No OCR and no PDF input. You paste text.&lt;/li&gt;
&lt;li&gt;No users yet. The repository is not public.&lt;/li&gt;
&lt;li&gt;It is not legal advice, and I wouldn't present it as such.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why local
&lt;/h2&gt;

&lt;p&gt;These letters contain names, addresses and debts. Keeping the model on the user's machine means the text never leaves it. A small model is slower and less fluent, which is why the rules carry the parts that must be right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;When a task mixes language and facts, split it. Let code produce what must be exact and let the model do the explaining. Then say plainly which parts you have tested.&lt;/p&gt;

</description>
      <category>python</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
