<?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: James Anderson</title>
    <description>The latest articles on DEV Community by James Anderson (@james_anderson_h).</description>
    <link>https://dev.to/james_anderson_h</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%2F3968038%2Fbdd03952-d56a-43ea-b5f6-e903c687dca1.png</url>
      <title>DEV Community: James Anderson</title>
      <link>https://dev.to/james_anderson_h</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/james_anderson_h"/>
    <language>en</language>
    <item>
      <title>Inside the Tokenizer: Why the Same Prompt Costs Different Amounts on Every Model</title>
      <dc:creator>James Anderson</dc:creator>
      <pubDate>Tue, 18 Aug 2026 11:06:50 +0000</pubDate>
      <link>https://dev.to/james_anderson_h/inside-the-tokenizer-why-the-same-prompt-costs-different-amounts-on-every-model-31m5</link>
      <guid>https://dev.to/james_anderson_h/inside-the-tokenizer-why-the-same-prompt-costs-different-amounts-on-every-model-31m5</guid>
      <description>&lt;p&gt;If you build with LLMs, you pay by the token. Not by the word, not by the character — the token. And yet most of us treat the tokenizer as a black box: text goes in, a number comes out, the bill arrives.&lt;/p&gt;

&lt;p&gt;That black box is worth opening. Once you understand how tokenization works, a lot of otherwise-mysterious LLM behavior starts to make sense: why the same sentence costs 3 tokens on Claude and 4 on GPT, why your Spanish chatbot costs more than the English one, why models are weirdly bad at arithmetic, why some prompt styles quietly burn your budget — and, crucially, &lt;strong&gt;how to actually calculate what a feature will cost before you ship it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's open it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Token Actually Is
&lt;/h2&gt;

&lt;p&gt;A token is not a word and not a character. It's a chunk of text — usually a subword — that the model treats as a single unit. Before a model reasons about anything, your text is split into these chunks, and each chunk is mapped to an integer ID. The model only ever sees those integers.&lt;/p&gt;

&lt;p&gt;A rough rule of thumb for English:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;1 token ≈ 4 characters ≈ 0.75 words&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So ~100 tokens is about 75 words, and a page of English prose (~500 words) is roughly 650-750 tokens.&lt;/p&gt;

&lt;p&gt;But that's just an &lt;em&gt;average for English&lt;/em&gt;. The real count depends entirely on &lt;strong&gt;how&lt;/strong&gt; the text gets chunked — and that's decided by an algorithm called BPE.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Tokenizers Are Built: BPE in Plain Terms
&lt;/h2&gt;

&lt;p&gt;Nearly every major model today — GPT, Claude, Gemini, Llama, Mistral — uses some flavor of &lt;strong&gt;Byte-Pair Encoding (BPE)&lt;/strong&gt; or a close relative.&lt;/p&gt;

&lt;p&gt;BPE started life as a data-compression trick in 1994 and was adapted for language models in 2016. The idea is genuinely simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with text broken into the smallest units (bytes/characters).&lt;/li&gt;
&lt;li&gt;Count every adjacent pair of units.&lt;/li&gt;
&lt;li&gt;Merge the single most frequent pair into a new combined unit.&lt;/li&gt;
&lt;li&gt;Repeat until you hit a target vocabulary size.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each merge gets recorded, in order, into a permanent list. At inference time, the tokenizer just replays those merge rules deterministically on your text.&lt;/p&gt;

&lt;p&gt;The key consequence: &lt;strong&gt;frequency during training decides everything.&lt;/strong&gt; Common words become single tokens; rare words get split into pieces. This is why &lt;code&gt;the&lt;/code&gt; is one token but &lt;code&gt;tokenization&lt;/code&gt; might be two or three (&lt;code&gt;token&lt;/code&gt; + &lt;code&gt;ization&lt;/code&gt;), and why the splits don't follow English grammar — they follow whatever was statistically common in the training text.&lt;/p&gt;

&lt;p&gt;Modern tokenizers also start from raw &lt;strong&gt;bytes&lt;/strong&gt; (the 256 possible byte values) rather than characters. That's what lets them handle &lt;em&gt;anything&lt;/em&gt; — emoji, Chinese, symbols, typos — without ever hitting an "unknown word." Worst case, a weird character just falls back to several byte-level tokens.&lt;/p&gt;

&lt;p&gt;One design tension worth knowing: a &lt;strong&gt;bigger vocabulary&lt;/strong&gt; means fewer tokens per sentence (cheaper, shorter sequences) but a larger embedding table and more memory. GPT-2 learned ~50,000 merges; models like GPT-4o's &lt;code&gt;o200k_base&lt;/code&gt; use roughly 200,000. That jump is a big part of why newer models are more token-efficient per word.&lt;/p&gt;




&lt;h2&gt;
  
  
  Count It Yourself: &lt;code&gt;tiktoken&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;For OpenAI models, the tokenizer is open source, so you can get &lt;strong&gt;exact&lt;/strong&gt; counts locally:&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="c1"&gt;# pip install tiktoken
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;

&lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o200k_base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# GPT-4o / newer
&lt;/span&gt;
&lt;span class="n"&gt;samples&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;Hello, world!&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;tokenization&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;The quick brown fox jumps over the lazy dog.&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;12345678901234567890&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;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 42, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: true}&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;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enc&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;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&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;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  4 tokens | Hello, world!
  2 tokens | tokenization
 10 tokens | The quick brown fox jumps over the lazy dog.
  ...       (long digit runs fragment into several tokens)
 ...        (the JSON spends tokens on braces, quotes, and keys)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;strong&gt;Claude&lt;/strong&gt;, the tokenizer isn't published — use Anthropic's token-counting endpoint (&lt;code&gt;POST /v1/messages/count_tokens&lt;/code&gt;), which accepts the same shape as a real request and returns the input token total. It's free to call. For &lt;strong&gt;Gemini&lt;/strong&gt;, use Google's &lt;code&gt;countTokens&lt;/code&gt; API. Don't cross-apply one model's count to another — they diverge.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Same Text Costs Different Amounts on Different Models
&lt;/h2&gt;

&lt;p&gt;Send &lt;code&gt;Hello, world!&lt;/code&gt; to GPT and you might pay 4 tokens; send it to Claude and you might pay 3. Same text, different integers out. Why?&lt;/p&gt;

&lt;p&gt;Because each provider trained its own tokenizer on its own data, with its own vocabulary size, merge tables, and rules for whitespace and non-Latin scripts. They share a family resemblance but diverge in the details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPT (OpenAI)&lt;/strong&gt; — &lt;code&gt;tiktoken&lt;/code&gt;, &lt;strong&gt;open source&lt;/strong&gt;. &lt;code&gt;cl100k_base&lt;/code&gt; (~100k vocab) on older models; &lt;code&gt;o200k_base&lt;/code&gt; (~200k vocab) on newer ones. Exact local counts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude (Anthropic)&lt;/strong&gt; — proprietary BPE, not published. Use the &lt;code&gt;count_tokens&lt;/code&gt; endpoint. Note: newer Claude models use a tokenizer that can produce &lt;strong&gt;~30% more tokens&lt;/strong&gt; for the same text than older ones — which matters when you compare sticker prices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini (Google)&lt;/strong&gt; — SentencePiece-based, not published. Use &lt;code&gt;countTokens&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; a token count measured on one model does not transfer to another — not even between generations of the same model. Budget with the exact model you'll deploy.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes Token Usage Worse (and Better)
&lt;/h2&gt;

&lt;p&gt;Token count isn't just "length of text." Several factors push it up or down — and these are things you can actually control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inflates your token count 📈
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Numbers.&lt;/strong&gt; Digits tokenize unpredictably. &lt;code&gt;127&lt;/code&gt; might be one token; &lt;code&gt;677&lt;/code&gt; can split into two; long numbers fragment into several. (Also a big reason LLMs are shaky at arithmetic.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whitespace &amp;amp; indentation.&lt;/strong&gt; Spaces, tabs, and newlines are tokens too. Deeply indented code spends real tokens on whitespace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rare / made-up words.&lt;/strong&gt; Jargon, UUIDs, hashes, and base64 fragment into many small pieces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavy formatting.&lt;/strong&gt; JSON — with its braces, quotes, and repeated keys — is heavier than leaner formats. This is why YAML often tokenizes cheaper than JSON for the same data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template overhead.&lt;/strong&gt; Every request is wrapped in role markers and template tokens. Same overhead in every language, so on short messages it's a proportionally bigger tax.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reduces your token count 📉
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Concise, plain prose over verbose phrasing.&lt;/li&gt;
&lt;li&gt;Common words over rare synonyms.&lt;/li&gt;
&lt;li&gt;Removing repeated context — &lt;strong&gt;cache&lt;/strong&gt; it or reference it instead of re-sending.&lt;/li&gt;
&lt;li&gt;Leaner data formats where structure allows.&lt;/li&gt;
&lt;li&gt;Capping output length (see cost math below — output is where the money goes).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This doesn't mean mangle your prompts into unreadable shorthand. It means the obvious wins — don't re-send a giant system prompt on every call, don't pad with filler, cap &lt;code&gt;max_tokens&lt;/code&gt; — are real money.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Language Tax: Why Non-English Costs 2-15x More
&lt;/h2&gt;

&lt;p&gt;The biggest and least-known factor. Tokenizers are trained on &lt;strong&gt;English-heavy&lt;/strong&gt; text, so they learn big efficient tokens for English and few for everything else. This is measured as &lt;strong&gt;fertility&lt;/strong&gt; — tokens per word. English sits ~1.2-1.4; other languages run far higher.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;English-relative cost&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;~1.2-1.3×&lt;/td&gt;
&lt;td&gt;Baseline (best case)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spanish / French&lt;/td&gt;
&lt;td&gt;~1.5-2×&lt;/td&gt;
&lt;td&gt;Accents + morphology&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hindi&lt;/td&gt;
&lt;td&gt;~1.6-2.7×&lt;/td&gt;
&lt;td&gt;Non-Latin script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chinese / Japanese / Korean&lt;/td&gt;
&lt;td&gt;~2-3×+&lt;/td&gt;
&lt;td&gt;CJK fragments heavily&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arabic&lt;/td&gt;
&lt;td&gt;~3-4×&lt;/td&gt;
&lt;td&gt;Script + morphology&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turkish / Finnish&lt;/td&gt;
&lt;td&gt;~2-3×&lt;/td&gt;
&lt;td&gt;Agglutinative words&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tamil / Telugu / Malayalam&lt;/td&gt;
&lt;td&gt;up to ~12-16×&lt;/td&gt;
&lt;td&gt;Worst-hit; some tokenizers explode these&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(Figures are approximate and vary by tokenizer — measure your own.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two drivers: &lt;strong&gt;script/encoding&lt;/strong&gt; (English is 1 UTF-8 byte per char thanks to ASCII; other scripts need 2-4 bytes) and &lt;strong&gt;word frequency&lt;/strong&gt; (underrepresented languages never earned efficient tokens).&lt;/p&gt;

&lt;p&gt;The consequences are &lt;strong&gt;structural&lt;/strong&gt; — you can't prompt them away:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; 3x the tokens ≈ 3x the bill for the same meaning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context window:&lt;/strong&gt; the same 200k window holds far less non-English content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency:&lt;/strong&gt; more tokens = slower responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your usage is global, &lt;strong&gt;estimate cost and context per language, not once in English.&lt;/strong&gt; The English number is the best case, not the average.&lt;/p&gt;




&lt;h2&gt;
  
  
  Now the Money: How to Actually Calculate Cost
&lt;/h2&gt;

&lt;p&gt;APIs price &lt;strong&gt;input&lt;/strong&gt; and &lt;strong&gt;output&lt;/strong&gt; tokens &lt;em&gt;separately&lt;/em&gt;, and output is almost always far more expensive. The core formula:&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;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_tokens&lt;/span&gt;  &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="n"&gt;input_price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_tokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="n"&gt;output_price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Representative flagship prices (per 1M tokens)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Prices change constantly and vary by exact model/tier — treat these as an &lt;strong&gt;illustrative August 2026 snapshot&lt;/strong&gt;, and always confirm on the provider's current pricing page before budgeting. Some models also &lt;strong&gt;double rates past ~200k-token context&lt;/strong&gt;, and newer Claude tokenizers emit ~30% more tokens for the same text.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model (illustrative)&lt;/th&gt;
&lt;th&gt;Input / 1M&lt;/th&gt;
&lt;th&gt;Output / 1M&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Haiku (small)&lt;/td&gt;
&lt;td&gt;~$1&lt;/td&gt;
&lt;td&gt;~$5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet (mid)&lt;/td&gt;
&lt;td&gt;~$2-3&lt;/td&gt;
&lt;td&gt;~$10-15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus (flagship)&lt;/td&gt;
&lt;td&gt;~$5&lt;/td&gt;
&lt;td&gt;~$25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5-class (flagship)&lt;/td&gt;
&lt;td&gt;~$1.75-5&lt;/td&gt;
&lt;td&gt;~$14-30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT mini (small)&lt;/td&gt;
&lt;td&gt;~$0.25&lt;/td&gt;
&lt;td&gt;~$2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini Pro (flagship)&lt;/td&gt;
&lt;td&gt;~$2&lt;/td&gt;
&lt;td&gt;~$12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini Flash (small)&lt;/td&gt;
&lt;td&gt;~$0.10-0.50&lt;/td&gt;
&lt;td&gt;~$0.40-3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Worked example: one chatbot request
&lt;/h3&gt;

&lt;p&gt;Say a single request has &lt;strong&gt;1,500 input tokens&lt;/strong&gt; (system prompt + history + user message) and &lt;strong&gt;500 output tokens&lt;/strong&gt;, on a mid-tier model at &lt;strong&gt;$3 / $15&lt;/strong&gt; per 1M:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input  = 1,500  / 1,000,000 × $3   = $0.0045
output =   500  / 1,000,000 × $15  = $0.0075
------------------------------------------------
total per request                  ≈ $0.012
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just over &lt;strong&gt;one cent per request&lt;/strong&gt;. Feels trivial — until you scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaling it out (same request, 100,000/day)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;per day   = 100,000 × $0.012        = $1,200
per month = $1,200 × 30             ≈ $36,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That "one cent" is now a &lt;strong&gt;$36k/month&lt;/strong&gt; line item. This is why the math matters &lt;em&gt;before&lt;/em&gt; launch, not after the invoice.&lt;/p&gt;

&lt;h3&gt;
  
  
  The output tax, made concrete
&lt;/h3&gt;

&lt;p&gt;Notice that in the example above, &lt;strong&gt;output cost more than input&lt;/strong&gt; despite being one-third the tokens ($0.0075 vs $0.0045). If you let &lt;code&gt;max_tokens&lt;/code&gt; default to a huge buffer and the model rambles, output balloons. Capping output length is often the single highest-leverage cost lever you have.&lt;/p&gt;

&lt;h3&gt;
  
  
  The language tax, in dollars
&lt;/h3&gt;

&lt;p&gt;Take that same request, but the user writes in a language with &lt;strong&gt;3x fertility&lt;/strong&gt;. Input and output token counts roughly triple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input  = 4,500  / 1,000,000 × $3   = $0.0135
output = 1,500  / 1,000,000 × $15  = $0.0225
------------------------------------------------
total per request                  ≈ $0.036   (3× the English cost)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same feature, same user intent, &lt;strong&gt;triple the bill&lt;/strong&gt; — purely from tokenization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blended rate: comparing providers honestly
&lt;/h3&gt;

&lt;p&gt;Input and output prices differ, so don't compare "input price" in your head. Compute a &lt;strong&gt;blended rate&lt;/strong&gt; using your real traffic mix. For an 80% input / 20% output workload:&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;blended&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="n"&gt;input_price&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="n"&gt;output_price&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a $3/$15 model: &lt;code&gt;0.8×3 + 0.2×15 = 2.4 + 3.0 = $5.40 per 1M blended&lt;/code&gt;. Run that for each candidate model with &lt;em&gt;your&lt;/em&gt; ratio — it often reorders the "cheapest" ranking versus headline input prices.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cost Levers That Actually Work
&lt;/h2&gt;

&lt;p&gt;In rough order of impact:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prompt caching.&lt;/strong&gt; Cached input tokens often cost ~10% of the normal rate. If you re-send a big fixed system prompt every call, this can cut costs up to ~90% on that portion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route by difficulty.&lt;/strong&gt; Send trivial requests (greetings, formatting, lookups) to a small/cheap model; reserve flagships for hard reasoning. A tiny classifier in front of your router commonly cuts mixed-workload cost 60-80%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cap &lt;code&gt;max_tokens&lt;/code&gt; aggressively.&lt;/strong&gt; The default output buffer is usually far bigger than you need, and output is the pricey side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch API for non-realtime work.&lt;/strong&gt; Typically ~50% off for jobs that don't need instant responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trim and cache context.&lt;/strong&gt; Don't re-send history or documents you can reference or summarize.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget per language.&lt;/strong&gt; Weight your cost model by the token cost of each language you serve.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Practical Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You pay per token, not per word&lt;/strong&gt; — and the exchange rate shifts with language, format, and content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count with the real tokenizer:&lt;/strong&gt; &lt;code&gt;tiktoken&lt;/code&gt; (OpenAI, exact/local/free), &lt;code&gt;count_tokens&lt;/code&gt; (Claude), &lt;code&gt;countTokens&lt;/code&gt; (Gemini). Never cross-apply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count the final request body&lt;/strong&gt; you actually send — system prompt, tool schemas, history, all of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output tokens usually dominate cost.&lt;/strong&gt; Cap them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-English can cost 2-15x more.&lt;/strong&gt; Budget per language.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute a blended rate&lt;/strong&gt; with your real input/output mix before picking a model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-verify prices&lt;/strong&gt; — they change often and can double past long-context thresholds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tokenizer isn't an implementation footnote. It's the layer where the economics of your app are quietly set — the interface between human language and the model's math, and the exact place your bill is decided. Understanding it turns a mysterious invoice into something you can reason about, forecast, and control.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you hit a surprising token bill or a weird tokenization bug in production? Drop the story in the comments — I collect these.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A Claude Code skill fixed my app's UI — here's what broke and how to use it yourself</title>
      <dc:creator>James Anderson</dc:creator>
      <pubDate>Mon, 17 Aug 2026 12:03:47 +0000</pubDate>
      <link>https://dev.to/james_anderson_h/a-claude-code-skill-fixed-my-apps-ui-heres-what-broke-and-how-to-use-it-yourself-1k0g</link>
      <guid>https://dev.to/james_anderson_h/a-claude-code-skill-fixed-my-apps-ui-heres-what-broke-and-how-to-use-it-yourself-1k0g</guid>
      <description>&lt;p&gt;My app worked. It just looked like three different apps stitched together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BrandMeld&lt;/strong&gt; (Flutter, AI brand generator) had grown screen by screen: some had plain &lt;code&gt;AppBar&lt;/code&gt;s, some had gradient heroes, settings was a gray &lt;code&gt;ListTile&lt;/code&gt; dump, spacing was random, and the "create brand" form asked for 8 fields before you could do anything. Every screen was &lt;em&gt;fine&lt;/em&gt; in isolation and inconsistent as a whole.&lt;/p&gt;

&lt;p&gt;Instead of hand-fixing 14 screens, I installed a &lt;strong&gt;Claude Code skill&lt;/strong&gt; built for mobile UI/UX and pointed it at them. This post is the concrete before/after — the actual problems it fixed — plus how you install and use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problems it fixed (real, not vibes)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. No shared header language.&lt;/strong&gt; Every screen reinvented its top bar. The skill replaced them with one full-width gradient header pattern (back button + actions merged in), so the app reads as one product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A settings screen that was a wall of gray rows.&lt;/strong&gt; Became iOS-style grouped cards with tinted icon badges — scannable in a glance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. An 8-field create form.&lt;/strong&gt; Reworked to &lt;em&gt;name-only required&lt;/em&gt;, with the rest behind an "Add more details" expander. Then it added a smart default I didn't ask for: pick a preset industry → target audience + keywords auto-fill (without overwriting anything you typed). Cognitive load dropped from "fill a form" to "type a name."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. A dead-end paywall.&lt;/strong&gt; "Your trial ended." → a card that lists &lt;strong&gt;what you lose access to&lt;/strong&gt; (names, palettes, logos, brand guides, saved brands) with lock icons. Loss aversion instead of a shrug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Actual rendering bugs — caught from screenshots.&lt;/strong&gt; This surprised me. I'd send a screenshot and it diagnosed things code review misses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a selected card's border &lt;strong&gt;clipped at the top corners&lt;/strong&gt; (border + &lt;code&gt;Clip.antiAlias&lt;/code&gt; on the same container),&lt;/li&gt;
&lt;li&gt;an &lt;strong&gt;avatar hidden&lt;/strong&gt; behind a floating stats card (header too short, card overlapped it),&lt;/li&gt;
&lt;li&gt;weird &lt;strong&gt;blur blobs&lt;/strong&gt; behind the header (translucent circles rendering as hard-edged shapes).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The screenshot &lt;em&gt;is&lt;/em&gt; the debugger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a "skill" and not just "make it nicer"
&lt;/h2&gt;

&lt;p&gt;"Redesign this" gives you noise. The skill encodes a rubric the model follows every time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8-pt spacing grid&lt;/li&gt;
&lt;li&gt;&lt;p&gt;60/30/10 color system&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;named patterns (floating stat strips, grouped cards, gradient headers)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the difference between one-off prettiness and a &lt;strong&gt;consistent design system&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you do with it
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install it
&lt;/h3&gt;

&lt;p&gt;A skill is just a folder with a &lt;code&gt;SKILL.md&lt;/code&gt;. Drop it in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project: &lt;code&gt;.claude/skills/&amp;lt;name&amp;gt;/SKILL.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Personal (all projects): &lt;code&gt;~/.claude/skills/&amp;lt;name&amp;gt;/SKILL.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clone the repo into that folder, restart Claude Code, done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use it
&lt;/h3&gt;

&lt;p&gt;Point it at a screen:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Redesign the settings screen. Apply the design principles."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then iterate with screenshots:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The avatar's hidden — fix it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It keeps your logic/routing and rewrites the layout. You review UI, not re-test features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Or build your own
&lt;/h3&gt;

&lt;p&gt;The real lesson: &lt;strong&gt;you can package any discipline into a skill.&lt;/strong&gt; A &lt;code&gt;SKILL.md&lt;/code&gt; is tiny:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
markdown
---
name: my-skill
description: "What it does AND when to use it. This is how Claude decides to load it."
---

# Instructions
The rules/steps Claude should follow when this runs.

Encode your conventions — API patterns, test style, commit format — once, and get consistent output forever.

Takeaway

The win isn't "AI writes UI." It's that a rubric-in-a-file tura cohesive one in an afternoon, and caught rendering bugs fromscreenshots along the way.

Skill (clone + drop into .claude/skills/): https://github.com/ceorkm/mobile-app-ui-design

If you try it, send me a before/after — those are the fun ones.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>flutter</category>
      <category>ai</category>
      <category>claude</category>
      <category>designsystem</category>
    </item>
    <item>
      <title>How PDF &amp; Document Parsers Actually Work Under the Hood</title>
      <dc:creator>James Anderson</dc:creator>
      <pubDate>Sun, 16 Aug 2026 10:52:46 +0000</pubDate>
      <link>https://dev.to/james_anderson_h/how-pdf-document-parsers-actually-work-under-the-hood-16mp</link>
      <guid>https://dev.to/james_anderson_h/how-pdf-document-parsers-actually-work-under-the-hood-16mp</guid>
      <description>&lt;p&gt;To the average user, opening a PDF feels no different than viewing a webpage or reading a Word document. You see headers, paragraphs, multi-column articles, and neatly bordered tables. To a developer trying to extract structured data from that same PDF, it feels like staring into an abyss. That is because a PDF has no concept of a paragraph, a column, or a table. In fact, a PDF doesn't even store text in reading order. A PDF is essentially a set of vector drawing instructions for a printer canvas.  Here is a look under the hood at how document parsers turn raw, chaotic drawing streams into structured, machine-readable data. &lt;br&gt;
&lt;strong&gt;1. The Core Illusion: PDFs Are Canvas Commands&lt;/strong&gt;&lt;br&gt;
When a word processor exports a document to PDF, it discards semantic document structure (DOM) in favor of visual position.&lt;/p&gt;

&lt;p&gt;Instead of saving &lt;code&gt;&amp;lt;h2&amp;gt;Title&amp;lt;/h2&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;, the PDF encoder generates low-level drawing operators:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BT                   % Begin Text object
/F1 12 Tf            % Set font F1 at size 12pt
72 712 Td            % Move cursor to coordinate (x: 72pt, y: 712pt)
(Hello ) Tj          % Draw character stream "Hello "
12.5 0 Td            % Move cursor right by 12.5pt
(World) Tj           % Draw character stream "World"
ET                   % End Text object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Spatial Order Nightmare &lt;br&gt;
Notice what is missing? No space characters, no paragraph breaks, and no reading order. Because glyphs are placed at hardcoded $(x, y)$ coordinate points on a Cartesian canvas, a PDF generator might draw the footer first, the header second, the right-hand sidebar third, and the main content paragraph last. If you naive-dump the raw text stream, your output becomes a scrambled mess of interleaved columns.&lt;br&gt;
&lt;strong&gt;Phase 1: Decoding Binary Anatomy (pypdf)&lt;/strong&gt;&lt;br&gt;
This snippet inspects low-level PDF primitives, trailer metadata, decompressed byte streams, and font mappings.&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;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PdfReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;What happens under the hood: PdfReader opens the binary file and instantly seeks to the very end of the file. It reads the last 1,024 bytes to locate the startxref keyword. From there, it parses the XRef (Cross-Reference) Table, which builds an internal index mapping Object IDs (e.g., 12 0 R) to byte offsets in the file.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;trailer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trailer&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Root Catalog keys: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trailer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/Root&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="si"&gt;}&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;reader.trailer: Returns the PDF Trailer Dictionary, which contains top-level pointers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Points to the Document Catalog object (the root node of the PDF's structural tree).&lt;/li&gt;
&lt;li&gt;Points to metadata (Author, CreationDate, Producer).&lt;/li&gt;
&lt;li&gt;Total number of entries in the XRef table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;trailer['/Root'].keys(): Resolves the indirect object reference for the Root catalog and exposes top-level keys like /Pages (the root of the page tree), /Names, or /AcroForm (interactive form fields).&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_contents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;page.get_contents(): A page object delegates its visual drawing commands to a stream object (or an array of stream objects) specified under the /Contents key.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;raw_stream_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;raw_operators&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw_stream_bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latin1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore&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;ul&gt;
&lt;li&gt;&lt;p&gt;contents.get_data(): In raw PDF files, stream data is compressed using the FlateDecode filter (standard zlib compression). get_data() inflates the compressed stream back into uncompressed PDF drawing operators in RAM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;decode("latin1"): PDF stream strings do not use UTF-8 by default; they use 8-bit string encodings (often WinAnsiEncoding or PDFDocEncoding). Using latin1 prevents Python decoding exceptions while preserving byte values.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/Resources&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;fonts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/Font&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get_object&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;/Resources: Every page has a resource dictionary containing assets required to draw it, including /Font, /XObject (embedded images or sub-canvases), and /ColorSpace.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/Font Lookup: Maps font alias identifiers used inside the drawing stream (like /F1 or /F2) to indirect Font dictionary objects containing /BaseFont names and embedded /ToUnicode CMaps (Character Maps).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: Spatial Geometry &amp;amp; Layout Analysis (PyMuPDF / fitz)&lt;/strong&gt;&lt;br&gt;
This snippet extracts coordinate-level word tokens and groups them spatially to solve multi-column reading order problems.&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;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fitz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;words&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;ul&gt;
&lt;li&gt;fitz.open(): Uses MuPDF's C engine to parse the document structure into memory.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;page.get_text("words"): Executes the page stream and returns a list of 8-element tuples for every detected word:&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text = (x0,y0, x1, y1,"wurd_text", block_no,word_no,line_no)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(x0,y0 ) represents the Top-Left coordinate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(x1, y1) represents the Bottom-Right coordinate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Note: In PDF geometry, (0,0) is at the top-left corner of the page, measured in PDF points (1 pt= 1/72 inch)&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;COLUMN_THRESHOLD_PTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;
&lt;span class="n"&gt;left_column_words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;COLUMN_THRESHOLD_PTS&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;right_column_words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;COLUMN_THRESHOLD_PTS&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Spatial Partitioning: Instead of relying on the raw PDF stream order, this splits words into two distinct groups based on their left coordinate ($x_0$). Words starting before 250 pt belong to Column 1; words starting at or after 250 pt belong to Column 2.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sorted_left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left_column_words&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;w[1] is the vertical top position (y0).&lt;/li&gt;
&lt;li&gt;round(w[1], -1) rounds the Y-coordinate to the nearest 10 points (e.g.,       102.3 to 100). This creates vertical alignment buckets (lines). Without this rounding, tiny sub-pixel baseline variations (y0 = 100.1 vs y0 = 100.3) would cause words on the same line to be treated as different vertical rows.&lt;/li&gt;
&lt;li&gt;w&lt;a href="https://dev.toX-coordinate"&gt;0&lt;/a&gt; is the secondary tie-breaker. Once words are placed into the same line bucket, they are ordered strictly from left to right.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 3: Table Extraction (pdfplumber)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This snippet demonstrates two contrasting programmatic techniques for extracting tabular data from PDFs: vector parsing vs. whitespace channel projection.&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="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pdfplumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pdf_path&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;pdf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;pdfplumber Architecture: Built on top of pdfminer.six, pdfplumber builds a rich spatial engine by turning every character, line, rectangle, and path into a Python dictionary with explicit geometry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Strategy 1: Lattice (Vector Path Analysis)&lt;/strong&gt;&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;lattice_settings&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;vertical_strategy&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;lines&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;horizontal_strategy&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;lines&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;snap_tolerance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&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;ul&gt;
&lt;li&gt;"vertical_strategy": "lines" &amp;amp; "horizontal_strategy": "lines": Directs pdfplumber to ignore text initially and look exclusively at vector graphics paths (m for moveto, l for lineto, re for rectangle) drawn on the page canvas.&lt;/li&gt;
&lt;li&gt;snap_tolerance: 3: PDF line paths often do not connect perfectly at corners due to rendering rounding errors. A snap_tolerance of 3 points automatically snaps endpoints together if they are within 3 points of each other, forming closed grid cells.&lt;/li&gt;
&lt;li&gt;Intersection Algorithm: The parser finds all points where horizontal vector lines intersect vertical vector lines. These intersection points form bounding boxes (cells). Text character coordinates are then mapped inside these boxes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Strategy 2: Stream (Borderless Table Whitespace Projection)&lt;/strong&gt;&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;stream_settings&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;vertical_strategy&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;text&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;horizontal_strategy&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;text&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;min_words_vertical&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&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;ul&gt;
&lt;li&gt;"vertical_strategy": "text": Used when there are no vector lines (borderless tables). The engine projects the x0 and x1 coordinates of all text elements onto a horizontal axis histogram. Continuous vertical "blank spaces" (channels without text) are inferred as column dividers.min_words_vertical: 3: Specifies that at least 3 vertically aligned text elements must share the same horizontal alignment to establish a valid column boundary, preventing single stray words from triggering false column divisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 4: Scanned PDFs &amp;amp; OCR Pipeline (pytesseract + PyMuPDF)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This snippet bridges raster graphics rendering and optical character recognition for documents that lack text vectors entirely.&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;pix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_pixmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dpi&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="n"&gt;img_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tobytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_bytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;page.get_pixmap(dpi=300): Rasterizes the vector PDF page object into an uncompressed pixel matrix (bitmap) in memory.&lt;/li&gt;
&lt;li&gt;Why 300 DPI matters: Standard screen resolution (72 DPI) causes character edges to become pixelated, severely degrading OCR accuracy. 300 DPI provides the ideal pixel density for OCR algorithms to perform edge detection.&lt;/li&gt;
&lt;li&gt;io.BytesIO(img_bytes): Converts raw in-memory PNG byte buffers into a file-like stream object so Pillow (PIL) can open it without writing an intermediate file to disk.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ocr_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pytesseract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image_to_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pytesseract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DICT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;image_to_data(): Rather than returning plain string output, this function calls Tesseract's C++ library to output granular spatial data for every detected text bounding box.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Returned Dictionary Structure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ocr_data['text']: List of detected strings.&lt;/li&gt;
&lt;li&gt;ocr_data['left'], ['top']: (x, y) pixel coordinates of the top-left bounding box corner.&lt;/li&gt;
&lt;li&gt;ocr_data['width'], ['height']: Pixel dimensions of the text block.&lt;/li&gt;
&lt;li&gt;ocr_data['conf']: Confidence score (0 to 100) returned by Tesseract's LSTM neural network model for each token prediction.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ocr_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;left&lt;/span&gt;&lt;span class="sh"&gt;'&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;span class="n"&gt;ocr_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;top&lt;/span&gt;&lt;span class="sh"&gt;'&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;span class="n"&gt;ocr_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;width&lt;/span&gt;&lt;span class="sh"&gt;'&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;span class="n"&gt;ocr_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;height&lt;/span&gt;&lt;span class="sh"&gt;'&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;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Filtering and Bounding Box Normalization:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;conf &amp;gt; 60: Filters out low-confidence OCR predictions, noise artifacts, scan smudges, or faint line marks that are falsely detected as text.&lt;/li&gt;
&lt;li&gt;Converts the raw pixel coordinates into standard bounding box representation (x0, y0, x1, y1) where x1 = x + w and y1 = y + h.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>coding</category>
      <category>computerscience</category>
      <category>software</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>How I Stopped Burning Out as a Developer</title>
      <dc:creator>James Anderson</dc:creator>
      <pubDate>Mon, 08 Jun 2026 10:42:11 +0000</pubDate>
      <link>https://dev.to/james_anderson_h/how-i-stopped-burning-out-as-a-developer-584d</link>
      <guid>https://dev.to/james_anderson_h/how-i-stopped-burning-out-as-a-developer-584d</guid>
      <description>&lt;p&gt;For a long time, I thought burnout was something that happened to other people. People who didn't love coding as much as I did. People who weren't committed enough to push through.&lt;br&gt;
Then I became one of those people.&lt;br&gt;
Not in a dramatic way. There was no breakdown, no quitting on the spot. It was slower and quieter than that — a gradual draining of the thing that used to make me excited to open my editor. Code that used to feel like play started to feel like lifting weights I never got to put down.&lt;br&gt;
This is what I learned getting out of it, and the warning signs I wish I'd taken seriously sooner.&lt;br&gt;
The Signs I Ignored&lt;br&gt;
Burnout didn't announce itself. It crept in through small things:&lt;/p&gt;

&lt;p&gt;I stopped wanting to build side projects, then stopped wanting to read about tech at all.&lt;br&gt;
Small bugs that used to be fun puzzles started to feel infuriating.&lt;br&gt;
I was "working" more hours but shipping less.&lt;br&gt;
I felt tired after a full night's sleep.&lt;br&gt;
I was irritable about things that didn't deserve it.&lt;br&gt;
I kept telling myself I just needed to push through one more sprint.&lt;/p&gt;

&lt;p&gt;That last one was the real trap. "Just push through" is great advice for a hard afternoon. It's terrible advice for a pattern that's lasted months.&lt;br&gt;
What Actually Helped&lt;br&gt;
Here's what made a real difference. None of it was a magic fix — it was a stack of small changes that added up.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I separated "tired" from "done"
I used to treat finishing my work and stopping work as the same event. They're not. There's always more to do. The codebase is never finished. If I waited until everything was done to stop, I'd never stop.
So I started defining the end of my workday by time and energy, not by an empty task list. When the day's planned work was done — or when my focus was clearly gone — I stopped. The remaining tasks would still be there tomorrow, and I'd handle them better with a working brain.&lt;/li&gt;
&lt;li&gt;I made real boundaries, not vague intentions
"I'll try to log off earlier" never worked. Specific rules did:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No code editor open after a set time in the evening.&lt;br&gt;
Notifications off outside work hours — actually off, not "I'll just glance."&lt;br&gt;
One full day a week with zero programming, including side projects.&lt;/p&gt;

&lt;p&gt;The day off was the hardest and the most important. Rest isn't a reward you earn after finishing everything. It's part of how the work gets sustainable.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I stopped tying my identity entirely to my output
This one was subtle but huge. When my entire sense of worth was "how much I shipped," every slow day felt like a personal failure. That's an exhausting way to live.
I'm a developer, but I'm also other things. Building a life outside of code — hobbies, people, time that has nothing to do with a screen — gave me somewhere to stand when work was hard. It also, ironically, made me better at work.&lt;/li&gt;
&lt;li&gt;I asked for help instead of grinding in silence
A lot of my exhaustion came from carrying things alone: an overloaded sprint, an unrealistic deadline, a problem I was too stubborn to ask about. Saying "this scope isn't realistic" or "I'm stuck, can we pair on this?" felt like admitting weakness. It wasn't. It was the thing that lightened the load.
If your workload is genuinely unsustainable, no personal productivity hack will fix that. Sometimes the honest conversation with a manager is the real solution.&lt;/li&gt;
&lt;li&gt;I protected the parts of coding I actually loved
Burnout had made all coding feel the same shade of gray. Part of recovery was deliberately reconnecting with the parts that drew me in originally — a tiny project with no deadline, a language I was curious about, building something silly just because. No pressure to ship, no audience, no metrics. Just the thing that made it fun in the first place.
What I'd Tell Past Me
If I could go back, I'd say this:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Burnout is information, not weakness. It's your capacity telling you something is off. Listen earlier.&lt;br&gt;
Rest is part of the work, not a break from it. You don't do good engineering on an empty tank.&lt;br&gt;
"Push through" has a shelf life. Useful for a day. Dangerous for a season.&lt;br&gt;
The work will still be there tomorrow. It genuinely always is.&lt;/p&gt;

&lt;p&gt;A Note on the Serious End of This&lt;br&gt;
What I've described is the everyday, manageable kind of burnout — the kind better habits and boundaries can address. But burnout can also blur into something heavier: persistent hopelessness, depression, the sense that nothing will get better.&lt;br&gt;
If you're somewhere in that territory, the advice in this post isn't enough, and that's not a personal failing. Talking to a doctor, a therapist, or someone you trust is a reasonable and worthwhile step. You don't have to debug that one alone.&lt;br&gt;
Closing&lt;br&gt;
I still love building software. Maybe more now than before, because I'm not running on fumes to do it. The difference wasn't working harder or caring more — it was building a way of working I could actually sustain.&lt;br&gt;
If you're reading this while feeling some of the signs I described: you're not lazy, and you're not failing. You're a person with limits, like everyone else. The sooner you work with those limits instead of against them, the longer you get to keep doing the thing you love.&lt;/p&gt;

&lt;p&gt;What's helped you avoid or recover from burnout? I'd love to hear what worked for you in the comments.&lt;/p&gt;

</description>
      <category>career</category>
      <category>productivity</category>
      <category>mentalhealth</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How AI Is Reshaping Software Development (and Where It's Heading)</title>
      <dc:creator>James Anderson</dc:creator>
      <pubDate>Thu, 04 Jun 2026 10:31:29 +0000</pubDate>
      <link>https://dev.to/james_anderson_h/how-ai-is-reshaping-software-development-and-where-its-heading-ldb</link>
      <guid>https://dev.to/james_anderson_h/how-ai-is-reshaping-software-development-and-where-its-heading-ldb</guid>
      <description>&lt;h1&gt;
  
  
  How AI Is Reshaping Software Development (and Where It's Heading)
&lt;/h1&gt;

&lt;p&gt;A few years ago, "AI in software development" mostly meant autocomplete that guessed your next variable name. Today it writes functions, reviews pull requests, generates tests, explains unfamiliar codebases, and occasionally argues with you about architecture. The shift has been fast, and it's still accelerating.&lt;/p&gt;

&lt;p&gt;This post is a practical look at what's actually changing, what's hype, and where things are likely headed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Actually Changing Right Now
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Writing code is no longer the bottleneck
&lt;/h3&gt;

&lt;p&gt;For most of the history of programming, the slow part was typing the right thing. AI assistants have quietly removed a lot of that friction. Boilerplate, glue code, config files, and one-off scripts now take seconds instead of minutes.&lt;/p&gt;

&lt;p&gt;The interesting consequence: the bottleneck moves &lt;em&gt;upstream&lt;/em&gt;. The hard part becomes knowing &lt;strong&gt;what&lt;/strong&gt; to build and &lt;strong&gt;how&lt;/strong&gt; to structure it, not the act of writing each line.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Reading and understanding code got easier
&lt;/h3&gt;

&lt;p&gt;Onboarding to a new codebase used to mean days of confused scrolling. Now you can ask an AI to summarize a module, trace a function's call path, or explain why some gnarly regex exists. This is arguably more valuable than code &lt;em&gt;generation&lt;/em&gt;, because developers spend far more time reading code than writing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Testing and review are becoming AI-assisted
&lt;/h3&gt;

&lt;p&gt;Generating unit tests, catching obvious bugs, suggesting edge cases, and doing a first pass on pull requests are all things AI handles reasonably well. It doesn't replace a senior reviewer, but it removes the trivial back-and-forth so humans can focus on the judgment calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The barrier to entry dropped
&lt;/h3&gt;

&lt;p&gt;People who couldn't previously ship software now can. A designer can build a working prototype. A data analyst can wire up a small tool. This expands who participates in building software, which is both exciting and messy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Still Overhyped
&lt;/h2&gt;

&lt;p&gt;It's worth being honest here, because the hype cycle is loud.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"AI will replace developers."&lt;/strong&gt; It won't, at least not in any near-term sense. AI is great at local, well-specified tasks and unreliable at large, ambiguous, system-level decisions. Software engineering has always been mostly about managing complexity and ambiguity — exactly the part AI is weakest at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"You don't need to learn fundamentals anymore."&lt;/strong&gt; The opposite is closer to true. When AI generates plausible-but-wrong code, you need &lt;em&gt;more&lt;/em&gt; understanding to catch it, not less.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"It just works."&lt;/strong&gt; Anyone who has shipped AI-generated code to production knows the failure modes: subtle bugs, outdated patterns, confident hallucinations, and security holes that look fine at a glance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the Developer Role Is Shifting
&lt;/h2&gt;

&lt;p&gt;The job isn't disappearing — it's changing shape. A few trends worth watching:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;From writing to reviewing.&lt;/strong&gt; More of your time goes into evaluating generated output rather than producing it from scratch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;From syntax to systems.&lt;/strong&gt; Knowing how to express a loop matters less; knowing how components interact, scale, and fail matters more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;From solo craft to orchestration.&lt;/strong&gt; Increasingly the skill is directing tools — describing intent clearly, breaking work into verifiable pieces, and validating results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Taste becomes a differentiator.&lt;/strong&gt; When generating code is cheap, knowing what &lt;em&gt;good&lt;/em&gt; looks like becomes the scarce, valuable thing.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where This Is Likely Heading
&lt;/h2&gt;

&lt;p&gt;Predictions are risky, but here are some reasonable bets for the next few years.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agents that do multi-step work
&lt;/h3&gt;

&lt;p&gt;We're moving past single-suggestion autocomplete toward agents that can take a task, plan it, make changes across files, run tests, and iterate. These already exist in early form. They'll get more reliable, but "reliable enough to trust unsupervised" is a high and slow-moving bar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification becomes the centerpiece
&lt;/h3&gt;

&lt;p&gt;As generation gets cheaper, the value shifts to &lt;em&gt;checking&lt;/em&gt;. Expect more tooling around automated testing, formal-ish verification, sandboxed execution, and ways to prove that generated code does what it claims.&lt;/p&gt;

&lt;h3&gt;
  
  
  Specs and intent become the source of truth
&lt;/h3&gt;

&lt;p&gt;If code is increasingly generated, the durable artifact becomes the clear specification of intent — well-written requirements, types, contracts, and tests. The skill of expressing intent precisely will only grow in importance.&lt;/p&gt;

&lt;h3&gt;
  
  
  A widening gap based on judgment
&lt;/h3&gt;

&lt;p&gt;Tools will be roughly equally available to everyone. The differentiator won't be access; it'll be the judgment to use them well — knowing when to trust output, when to throw it away, and how to architect something that survives contact with reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for You
&lt;/h2&gt;

&lt;p&gt;If you're a developer, a few practical takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use the tools, but stay skeptical.&lt;/strong&gt; Treat AI output like code from a fast, confident junior dev: useful, but verify everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double down on fundamentals.&lt;/strong&gt; Systems design, debugging, data modeling, and reasoning about tradeoffs are getting &lt;em&gt;more&lt;/em&gt; valuable, not less.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get good at specifying intent.&lt;/strong&gt; Clear thinking and clear writing are now directly productivity multipliers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learn to verify, not just to produce.&lt;/strong&gt; Testing and review skills are quietly becoming core competencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;AI isn't making software development obsolete — it's removing the parts that were never the point. Typing was never the job. Thinking was. The developers who thrive will be the ones who lean into the judgment, design, and verification that machines still can't do well.&lt;/p&gt;

&lt;p&gt;The tools will keep getting better. The question worth asking isn't "will AI take my job?" but "what's the most valuable thing I can do once the routine parts are handled?"&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's your experience been? Has AI changed your workflow more than you expected, or less? Drop a comment.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>software</category>
      <category>career</category>
    </item>
  </channel>
</rss>
