<?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>Forem: Alvis Ng</title>
    <description>The latest articles on Forem by Alvis Ng (@iamalvisng).</description>
    <link>https://forem.com/iamalvisng</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F66237%2Fb7baf32b-2aa2-4305-8da9-dc715b406f10.jpg</url>
      <title>Forem: Alvis Ng</title>
      <link>https://forem.com/iamalvisng</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/iamalvisng"/>
    <language>en</language>
    <item>
      <title>What Happens When AI Reads a Page</title>
      <dc:creator>Alvis Ng</dc:creator>
      <pubDate>Tue, 07 Apr 2026 13:30:00 +0000</pubDate>
      <link>https://forem.com/iamalvisng/what-happens-when-ai-reads-a-page-4c59</link>
      <guid>https://forem.com/iamalvisng/what-happens-when-ai-reads-a-page-4c59</guid>
      <description>&lt;p&gt;&lt;em&gt;From Tesseract’s character pipeline to GPT’s visual tokens to LandingAI’s agentic decomposition. I tested all five on the same McKinsey report. Every one got the text right. None of them got the charts right.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I uploaded two pages of a McKinsey consulting report to five different OCR systems last week. Same pages. Same prompt. A bubble chart with floating labels and a heatmap table where the data is color, not numbers.&lt;/p&gt;

&lt;p&gt;Every system got the text paragraphs perfectly. Every one of them failed on the charts.&lt;/p&gt;

&lt;p&gt;But they failed differently. And the differences trace directly to how each system works inside. Not at the API level. At the architecture level. The level where “reading” becomes “predicting” and you can’t tell the difference from the output.&lt;/p&gt;

&lt;p&gt;This article is about that architecture. What’s actually happening when a machine looks at a page of text, a table, or a chart, and turns pixels into structured data. Not an API comparison. A mechanism explainer. Because if you use OCR daily, you should understand how it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Generation 1: The Pipeline That Reads Character by Character
&lt;/h2&gt;

&lt;p&gt;Before transformers, before neural networks dominated OCR, Tesseract was the standard. It still runs inside thousands of production systems. And it works nothing like what you’re using when you call Claude or GPT with a PDF.&lt;/p&gt;

&lt;p&gt;Tesseract (version 4+) processes a page through a multi-stage pipeline:&lt;/p&gt;

&lt;p&gt;**Stage 1: Page Layout Analysis&lt;br&gt;&lt;br&gt;
**The engine scans the image for text regions. Dark pixel clusters on a light background. Group them into lines by vertical alignment. Separate words by horizontal gaps. That’s it. At this stage, the system has no idea what any character says. It only knows WHERE text exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 2: Line-Level Recognition&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
This is the interesting part. Each text line goes to an LSTM (Long Short-Term Memory) network. Not one character at a time. The whole line. Picture yourself squinting at someone’s terrible handwriting. You don’t decode each letter in isolation, right? You look at the shape of the whole word. The LSTM does the same thing. It slides across the line and asks &lt;em&gt;“given everything I’ve seen so far, what character comes next?”&lt;/em&gt; A vertical bar followed by a curve? Probably “b” or “d.” The letters around it break the tie.&lt;/p&gt;
&lt;h3&gt;
  
  
  Stage 3: CTC Decoding
&lt;/h3&gt;

&lt;p&gt;The LSTM spits out a messy probability matrix. Every column is a time step, every row is a possible character. &lt;a href="https://distill.pub/2017/ctc/" rel="noopener noreferrer"&gt;&lt;em&gt;CTC (Connectionist Temporal Classification)&lt;/em&gt;&lt;/a&gt; cleans this up. The network might say “hh-ee-ll-ll-oo” for “hello” because it stutters across time steps. CTC merges the repeated characters, strips out blanks, and gives you the final string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Page Image  
  │  
  ├── Layout Analysis → text regions, lines, words  
  │  
  ├── Per-Line LSTM → probability matrix [timesteps × characters]  
  │  
  ├── CTC Decoder → final character string  
  │  
  └── Post-processing → dictionary lookup, spell correction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline nature is the key insight. Each stage has ONE job. Layout analysis finds WHERE. The LSTM identifies WHAT. CTC decodes HOW to collapse probabilities into text. When any stage fails, the failure is visible. A layout analysis miss means a text region is skipped entirely, and you get a gap. A recognition error gives you garbled characters with low confidence scores. The system KNOWS it’s uncertain, and it can tell you.&lt;/p&gt;

&lt;p&gt;This matters because everything that came after works differently. And fails differently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Generation 2: Deep Learning Detection + Recognition
&lt;/h2&gt;

&lt;p&gt;Solutions like &lt;a href="https://github.com/JaidedAI/EasyOCR" rel="noopener noreferrer"&gt;&lt;em&gt;EasyOCR&lt;/em&gt;&lt;/a&gt; and &lt;a href="https://github.com/PADDLEPADDLE/PADDLEOCR" rel="noopener noreferrer"&gt;&lt;em&gt;PaddleOCR&lt;/em&gt;&lt;/a&gt; split the problem into two neural networks instead of Tesseract’s monolithic pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network 1: Text Detection
&lt;/h3&gt;

&lt;p&gt;A CNN (convolutional neural network) like &lt;a href="https://github.com/clovaai/CRAFT-pytorch" rel="noopener noreferrer"&gt;&lt;em&gt;CRAFT&lt;/em&gt;&lt;/a&gt; scans the image and produces two heatmaps. One says “this pixel is probably part of a character.” The other says “these two characters are probably next to each other.” Threshold those heatmaps and you get bounding boxes around text regions. Now you know where the words are.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network 2: Text Recognition
&lt;/h3&gt;

&lt;p&gt;Crop each detected region. Resize it to a standard height. Feed it to a CRNN (Convolutional Recurrent Neural Network). The CNN looks at the pixels and extracts visual features. The RNN (bidirectional LSTM) reads those features as a sequence, left and right. CTC decoding at the end, same as Tesseract. Out comes the text.&lt;/p&gt;

&lt;p&gt;The improvement over Tesseract? The detection network handles rotated text, curved text, text on complex backgrounds. The recognition network is deeper and more accurate. But it’s still a pipeline. Detection first, then recognition. Two networks. Two failure points. Both visible.&lt;/p&gt;

&lt;p&gt;When CRAFT fails to detect a text region, you get no output for that region. When the CRNN misrecognizes a character, the confidence score drops. You can build validation logic around both failure modes because the system’s uncertainty is measurable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Generation 3: End-to-End Transformers (Chandra, TrOCR)
&lt;/h2&gt;

&lt;p&gt;This is where the architecture shift happens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/datalab-to/chandra" rel="noopener noreferrer"&gt;&lt;em&gt;Chandra OCR&lt;/em&gt;&lt;/a&gt; and similar models (TrOCR, Donut, Nougat) abandon the pipeline. Instead of “detect then recognize,” they process the entire page in a single forward pass.&lt;/p&gt;

&lt;p&gt;Chandra is built on a fine-tuned vision-language model (from the Qwen-VL family). It’s not a classic encoder-decoder like T5 or BART. It’s a VLM that was specifically trained to generate structured document output. The architecture:&lt;/p&gt;

&lt;h3&gt;
  
  
  Vision encoder
&lt;/h3&gt;

&lt;p&gt;The page gets chopped into patches. 16x16 pixels each. Each patch is flattened into a vector, projected through a linear layer, and tagged with a positional embedding so the model knows where on the page it came from. Then self-attention: every patch can attend to every other patch. A patch showing the top of a “T” can look at the patch below it to confirm it’s a “T” and not an “I.” The output is a sequence of visual feature vectors, each one aware of the entire page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Language decoder
&lt;/h3&gt;

&lt;p&gt;Takes those visual features and generates text. Token by token. Left to right. Same autoregressive process as a chatbot generating a response, except the “prompt” is a grid of visual features instead of text tokens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Page Image (e.g., 1024×1024 pixels)  
  │  
  ├── Split into patches (e.g., 4,096 patches of 16×16)  
  │  
  ├── Linear projection → patch embeddings  
  │  
  ├── + Positional embeddings (where on the page)  
  │  
  ├── Vision Encoder (self-attention across ALL patches)  
  │   └── Output: visual feature vectors  
  │  
  └── Autoregressive Language Decoder (fine-tuned Qwen-VL)  
      ├── Attends to visual features + previous tokens  
      └── Generates: structured text (Markdown, HTML, JSON)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No detection step. No bounding boxes first. The model learns WHERE and WHAT simultaneously. A patch containing part of a letter attends to adjacent patches to figure out what it’s looking at. The self-attention mechanism is doing the spatial reasoning that CRAFT and Tesseract had to engineer explicitly.&lt;/p&gt;

&lt;p&gt;Chandra specifically uses this approach with what it calls “full-page decoding.” Rather than processing individual text lines, it takes in the entire page and generates structured output (Markdown, HTML, or JSON) that preserves the layout. It also provides spatial grounding: layout blocks are classified into 16+ types (text, section-header, caption, footnote, table, form, list-group, image, figure, diagram, equation-block, code-block, and more), each with bounding box coordinates. It achieved &lt;a href="https://huggingface.co/datalab-to/chandra-ocr-2" rel="noopener noreferrer"&gt;&lt;em&gt;85.9% on the olmocr benchmark&lt;/em&gt;&lt;/a&gt;, state-of-the-art for open-source OCR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The strength: layout preservation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Because the model sees the entire page at once, it can understand that a heading relates to the paragraph below it, that a table cell belongs to a specific row and column, that a footnote marker connects to a footnote at the bottom. The pipeline approaches can’t do this because they process text regions independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The weakness: the decoder is autoregressive&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
It predicts tokens one at a time. And like any autoregressive model, it can hallucinate. If the visual features are ambiguous (a smudged character, a low-resolution scan), the model doesn’t output a low-confidence garbled character like Tesseract would. It outputs a plausible character. Confidently. Silently.&lt;/p&gt;


&lt;h2&gt;
  
  
  Generation 4: Vision Language Models (Claude, GPT, Gemini)
&lt;/h2&gt;

&lt;p&gt;When you upload a PDF to Claude or GPT and ask it to extract the text, you’re using a vision language model. This is architecturally different from both traditional OCR and end-to-end transformers like Chandra. And the three major providers do it differently from each other.&lt;/p&gt;

&lt;p&gt;The general pattern is the same, which is to convert the image into visual tokens, combine them with your text prompt, and let the language model generate a response. But the HOW varies:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://openai.com/index/introducing-gpt-5/" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;GPT-5&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;is natively multimodal.&lt;/strong&gt; It was trained from scratch on text and images simultaneously. The visual and language capabilities developed together during training, not bolted on after the fact. It supports configurable resolution: a &lt;code&gt;[_detail_](https://developers.openai.com/cookbook/examples/multimodal/document_and_multimodal_understanding_tips)&lt;/code&gt; &lt;a href="https://developers.openai.com/cookbook/examples/multimodal/document_and_multimodal_understanding_tips" rel="noopener noreferrer"&gt;&lt;em&gt;parameter&lt;/em&gt;&lt;/a&gt; controls whether the model processes your image at standard or original resolution. For documents with small labels, dense tables, or fine print, switching to &lt;code&gt;detail="original"&lt;/code&gt; lets the model see individual pixels up to 10 million pixels without compression. This architectural choice means GPT-5 doesn't have a separate "vision module." Images and text live in the same representational space from the start.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://platform.claude.com/docs/en/docs/build-with-claude/vision" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;Claude&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;takes a different approach.&lt;/strong&gt; The image is converted to tokens using a pixel-area formula: &lt;code&gt;tokens = (width × height) / 750&lt;/code&gt;. Images with a long edge over 1,568 pixels are downscaled before processing. The visual tokens are then processed alongside text tokens by the language model. Claude's vision docs note specific limitations: limited spatial reasoning, approximate (not precise) object counting, and potential hallucinations on low-quality or rotated images. These limitations map directly to the failure modes we saw in the experiment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/image-understanding" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;Gemini&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;uses a&lt;/strong&gt; &lt;a href="https://storage.googleapis.com/deepmind-media/gemini/gemini_v2_5_report.pdf" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;sparse Mixture-of-Experts (MoE) transformer architecture&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; trained to be natively multimodal from the ground up. Google’s docs describe it as “built to be multimodal from the ground up.” The MoE design activates only a subset of model parameters per input token, routing each token to specialized “experts” within the network. This means Gemini can scale total model capacity without proportionally increasing compute cost per token. For vision, Gemini also supports bounding box detection with normalized coordinates (0–1000), giving it spatial awareness that the other two providers don’t expose at the API level.&lt;/p&gt;

&lt;p&gt;Despite these architectural differences, all three follow the same high-level flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document Image  
  │  
  ├── Visual Encoding (architecture-specific)  
  │   ├── GPT-5: native multimodal, configurable resolution up to 10M pixels  
  │   ├── Claude: pixel-area tokenization (w×h/750), 1568px downscale  
  │   └── Gemini: sparse MoE, unified multimodal space  
  │  
  ├── [visual tokens] + [prompt tokens: "extract all text..."]  
  │  
  └── Language Model generates text output  
      └── Not structured OCR. Text completion conditioned on visual tokens.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The critical difference from Chandra (Generation 3) is that none of these models were trained specifically for OCR. GPT-5 and Gemini are natively multimodal, but they’re general-purpose models trained on everything, not document specialists. Claude adds vision to a language-first architecture. All three CAN extract text from images. None were BUILT to do it. Chandra was fine-tuned specifically on document extraction tasks. That’s a different optimization target, and it shows in the results.&lt;/p&gt;

&lt;p&gt;Recent research has mapped what’s happening inside this process at a granular level. Earlier work by Baek et al. (2025) identified “OCR heads,” specific attention heads that specialize in text recognition. A paper from February 2026, &lt;a href="https://arxiv.org/pdf/2602.22918" rel="noopener noreferrer"&gt;&lt;em&gt;“Where Vision Becomes Text”&lt;/em&gt;&lt;/a&gt;, used causal interventions to locate exactly where these OCR heads operate and how the text signal routes through different architectures. These heads are qualitatively different from general retrieval heads. They concentrate in specific layers (L12, L16-L20 in the models tested) and have less sparse activation patterns than other attention heads.&lt;/p&gt;

&lt;p&gt;The OCR signal is remarkably low-dimensional: the first principal component captures 72.9% of the variance in how these heads process text. This means the model processes text through a narrow bottleneck, and that bottleneck’s location depends on the architecture. In DeepStack models (like Qwen3-VL), the bottleneck appears at mid-depth (~50% of layers). In single-stage projection models (like Phi-4), it peaks at early layers (6–25%).&lt;/p&gt;

&lt;p&gt;Why does this matter for you? Because the model isn’t doing OCR in the way you think. It doesn’t have a dedicated text-reading module. It has attention heads that LEARNED to read text as a byproduct of training on massive datasets of images paired with text descriptions. The “reading” is emergent, not engineered.&lt;/p&gt;

&lt;p&gt;And that’s why it fails differently than everything before it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Vision Models Fail Silently
&lt;/h2&gt;

&lt;p&gt;When Tesseract encounters a character it can’t read, the confidence score drops. You get a garbled output or a low-confidence warning. The failure is noisy. You can catch it.&lt;/p&gt;

&lt;p&gt;When a vision language model encounters a chart with floating labels, something different happens. The model sees visual tokens for the label “Smartphones” near visual tokens for both the Electronics row and the Other Manufacturing row. It needs to decide which row the label belongs to. It makes this decision the same way it decides anything: by predicting what token would most plausibly come next, given the visual context and the text generated so far.&lt;/p&gt;

&lt;p&gt;If the model is generating a list of products under “Electronics” and it has already listed several items, the next-token probability for “Smartphones” might be high simply because smartphones are associated with electronics in the training data. Not because of the spatial position of the label on the page. The model is predicting what SHOULD be there based on world knowledge, not reading what IS there based on pixel positions.&lt;/p&gt;

&lt;p&gt;This is why my experiment produced the results it did. But first, one more approach to understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hybrid: Agentic Document Extraction (LandingAI)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://landing.ai/developers/going-beyond-ocrllm-introducing-agentic-document-extraction" rel="noopener noreferrer"&gt;&lt;em&gt;LandingAI’s Agentic Document Extraction&lt;/em&gt;&lt;/a&gt; isn’t a new generation of OCR. The model underneath, &lt;a href="https://landing.ai/news/landingai-expands-agentic-document-intelligence-with-a-document-pre-trained-transformer" rel="noopener noreferrer"&gt;&lt;em&gt;DPT-2&lt;/em&gt;&lt;/a&gt;, is still a transformer. What’s different is what happens BEFORE the model reads anything.&lt;/p&gt;

&lt;p&gt;Every other system on this list looks at the whole page and says “what text is here?” LandingAI looks at the page and says “what KIND of things are on this page?” first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: Document Decomposition
&lt;/h3&gt;

&lt;p&gt;Before reading a single character, DPT-2 classifies every region on the page. That block of text? Paragraph. That grid? Table. That scatter plot? Chart. That squiggle in the corner? Signature. It identifies text, tables, charts, images, headers, footers, captions, logos, barcodes, QR codes, forms, even handwritten margin notes. Each one gets a bounding box with pixel coordinates. The system knows WHAT it’s looking at before it tries to read it. That’s the difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2: Table Structure Prediction
&lt;/h3&gt;

&lt;p&gt;Tables get special treatment. DPT-2 maps the geometry first: where do rows start, where do columns end, are any cells merged? It breaks the table into individual cell regions before extracting text from them. This is why it doesn’t hallucinate rows or shift columns the way vision models do. It understands the grid before reading the content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3: Cell-Level Extraction with Visual Grounding
&lt;/h3&gt;

&lt;p&gt;Each cell’s contents are extracted and paired with a bounding box. This is the key architectural difference: every extracted value traces back to exact pixel coordinates on the source page. If DPT-2 says a cell contains “330,” you can verify that by checking the bounding box against the original image. Vision models give you text with no coordinates. LandingAI gives you text with a return address.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 4: Agentic Reasoning
&lt;/h3&gt;

&lt;p&gt;An AI agent coordinates the outputs from different components, resolving cross-element references. If a text block says “see the table above,” the agent links them. Because each region is processed independently, the system can parallelize extraction across components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document Image  
  │  
  ├── Document Decomposition (multiple element types)  
  │   ├── Text blocks     → with bounding boxes  
  │   ├── Tables           → with structure prediction (rows, cols, merged cells)  
  │   ├── Charts           → with chart-type classification  
  │   ├── Images           → with captioning  
  │   └── Forms, headers, signatures, barcodes...  
  │  
  ├── Component-Specific Processing  
  │   ├── Text → text extraction + coordinates  
  │   ├── Table → cell-level extraction + per-cell bounding boxes  
  │   ├── Chart → data extraction  
  │   └── Image → captioning  
  │  
  ├── Agentic Reasoning (cross-component linking)  
  │  
  └── Structured Output with Visual Grounding  
      └── Every element has: content + page number + bounding box coordinates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you know how all five approaches work. Let’s see what happens when they meet the same document.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Experiment: Same Pages, Five Systems, Five Architectures
&lt;/h2&gt;

&lt;p&gt;I took two pages from the &lt;a href="https://www.mckinsey.com/mgi/our-research/mckinsey-global-institute-2025-in-charts" rel="noopener noreferrer"&gt;&lt;em&gt;McKinsey Global Institute: 2025 in Charts&lt;/em&gt;&lt;/a&gt; report. &lt;strong&gt;Page 12:&lt;/strong&gt; a bubble scatter chart mapping US-China trade rearrangement ratios across 13 industry sectors, with circles of varying sizes, floating product labels, diamond markers for sector averages, and a continuous X axis from 0 to 1.25+.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbtpg2nv2yaytkvusqwvi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbtpg2nv2yaytkvusqwvi.png" alt="Page 12 of McKinsey Global Institute: 2025 in Charts. The bubble chart that broke every OCR system I tested." width="800" height="726"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Page 12 of McKinsey Global Institute: 2025 in Charts. The bubble chart that broke every OCR system I tested.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Page 22:&lt;/strong&gt; a heatmap table showing economic empowerment factors across 11 countries, with 9 columns of color-coded cells (light to dark blue), rotated column headers, three income-group sections, and numerical columns for population and empowerment share.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqz19z8uiwb5lycphsj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqz19z8uiwb5lycphsj3.png" alt="Page 22. The “data” here is color, not numbers. None of the five systems could reliably read it." width="800" height="618"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Page 22. The “data” here is color, not numbers. None of the five systems could reliably read it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I chose these pages because they combine text (left-column prose that any system should handle) with visual data encoding (spatial position, bubble size, color intensity) that forces each system to do more than read characters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The five systems and how I ran each:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcqinyburf0b8rox4tq0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcqinyburf0b8rox4tq0.png" alt="comparison table of the five systems" width="800" height="743"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same prompt was used for all three vision models to keep the comparison fair. LandingAI and Chandra don’t accept prompts, which is itself a finding: they’re opinionated about HOW to extract, not waiting for you to tell them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timing and cost:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System Wall time Tokens consumed  
──────────────────────────────────────────────  
GPT-5.1 38.6s 2,717 tokens  
Claude Sonnet 43.4s 5,126 tokens  
Claude Opus 60.3s 6,385 tokens  
LandingAI 19.9s N/A (proprietary, 6 credits)  
Chandra ~15s N/A (playground, free tier)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chandra was fastest (~15s), followed by LandingAI (19.9s). Claude Opus consumed the most tokens because it attempted the most detailed output (including estimated ratio values and a 44-cell color assessment).&lt;/p&gt;

&lt;h3&gt;
  
  
  Results: Page 12 (Bubble Chart)
&lt;/h3&gt;

&lt;p&gt;On the bubble chart, every system extracted the prose text in the left column perfectly. The chart data diverged:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPT-5.1&lt;/strong&gt; listed products as flat text with no spatial structure. Products ended up assigned to wrong sectors (Video game consoles under “Other manufacturing,” Charcoal barbecues under “Other manufacturing”). No ratio values. No indication of where anything sat on the X axis. The model read every label but couldn’t determine which label belongs to which row.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Sonnet&lt;/strong&gt; made similar sector misassignments: Plastic footwear under Textiles, Tungsten carbide under Transportation. It extracted the chart title, legend, and axis labels cleanly, but the spatial mapping from product label to sector row was wrong in multiple places. No ratio values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Opus&lt;/strong&gt; was the most ambitious. It estimated ratio values for each product: “Logic chips (small, ~0.05),” “Smartphones (large, ~0.5),” “Laptops (large, ~1.0).” It was the only system that tried to read WHERE on the axis each bubble sat. But these values were approximations (the model was guessing position from pixel proximity), and several product-to-sector mappings were still wrong. Cotton T-shirts appeared under “Other manufacturing” instead of Textiles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LandingAI&lt;/strong&gt; produced the most structured output. It explicitly annotated the chart as a chart block with axis definitions, legend items, and products grouped by sector. Most sector assignments were correct. But it didn’t attempt ratio values. It described the structure faithfully without reading the quantitative data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chandra&lt;/strong&gt; (with Chart Understanding enabled) extracted every label but with zero spatial structure. Products listed flat, not mapped to sectors, not positioned on the axis. Chart Understanding did not produce a structured table for this bubble chart type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results: Page 22 (Heatmap Table)
&lt;/h3&gt;

&lt;p&gt;On the heatmap, the failure was more stark. The “data” is the shade of blue in each cell. A human reads it instantly: Japan’s working-age population cell is darkest (most important factor). Brazil’s job opportunities cell is darkest. India’s food cell is dark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPT-5.1&lt;/strong&gt; gave up entirely. For every country row, it output: “[Row of 9 colored squares].” It extracted the numbers on the right perfectly (330, 79 for the United States; 1,420, 29 for India). But the entire heatmap, the insight of the visualization, was replaced with a placeholder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Sonnet&lt;/strong&gt; tried. It used labels like “light,” “medium,” “[dark],” “[darkest].” It got Japan’s working-age population right as “[darkest].” But it called South Africa’s labor participation “medium” when it’s one of the darkest cells. Inconsistent, with no confidence scores.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Opus&lt;/strong&gt; tried hardest. It used a five-level scale: “medium,” “medium-high,” “high,” “highest/darkest.” It produced a detailed breakdown for every country and every column, 44 individual color assessments, plus a separate “Chart Detail Notes” section repeating the analysis. Some assessments were right. Some were wrong. All stated with equal confidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LandingAI&lt;/strong&gt; took a different approach. Instead of describing colors, it inferred importance levels and expressed them as ratings: “Medium (3/5),” “High (4/5),” “Highest (5/5).” It produced a full structured markdown table with every cell filled. The most usable output for downstream processing. But also the most interpretive: it wasn’t reading color, it was inferring meaning from visual intensity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chandra&lt;/strong&gt; got the numbers right and the text structure. But the heatmap data was completely absent. No colors, no levels, no mapping of the 9 columns to country rows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Page 22 Heatmap Extraction Summary  
═══════════════════════════════════════════════════════════  
System          Numbers  Color data       Column mapping  
──────────────────────────────────────────────────────────  
GPT-5.1           ✓      ✗ (gave up)        ✗  
Claude Sonnet     ✓      Partial            ✓ (table)  
Claude Opus       ✓      Best attempt       ✓ (table + notes)  
LandingAI         ✓      Inferred (3/5-5/5) ✓ (structured)  
Chandra           ✓      ✗                  ✗  
──────────────────────────────────────────────────────────  
Nobody got it fully right. Nobody told you they were uncertain.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What the Experiment Proves
&lt;/h2&gt;

&lt;p&gt;The results map directly to the architectures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vision language models&lt;/strong&gt; (GPT, Claude) treated the whole page as one image and predicted text from visual tokens. Text? Easy. Left-to-right, consistent spacing, clear patterns. Charts? A mess. Which label belongs to which bubble? The model doesn’t know. It guesses based on what it’s seen in training data, not based on pixel coordinates. And color-as-data (light blue vs dark blue meaning different things)? These models weren’t trained to interpret shade as a category. So they either skip it or hallucinate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LandingAI&lt;/strong&gt; knew it was looking at a chart before it tried to read it. The decomposition step gave it a head start. It classified the region, applied chart-specific extraction, and produced structured output. Not perfect, but structured. That’s the architectural advantage of classifying first, reading second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chandra&lt;/strong&gt; is optimized for text extraction with layout preservation. It’s state-of-the-art for reading text from documents. But when the “data” is color or spatial position rather than characters, it has the same blind spot as the general vision models. Full-page decoding helps with layout, not with visual data encoding.&lt;/p&gt;

&lt;p&gt;In both cases, across all five systems: clean, confident, structured output. No uncertainty markers. No confidence scores. No indication that the chart data was guessed rather than read.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Choose: A Decision Framework
&lt;/h2&gt;

&lt;p&gt;After running this experiment and digging into the architectures, the decision isn’t “which tool is best.” It’s “which tool matches what’s on your page.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiuzq3oee10txmfbrfe9g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiuzq3oee10txmfbrfe9g.png" alt="decision framework table" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The worst mistake you can make is trusting one tool for everything. The second worst is trusting any tool on chart data without validation. Every system I tested produced confident, clean output on chart data that was partially or fully wrong. None told me they were uncertain.&lt;/p&gt;

&lt;p&gt;If you’re building a document processing pipeline, the architecture should match the document:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Classify first.&lt;/strong&gt; Before extraction, classify each page: is it mostly text, a table, a chart, or mixed? Route different page types to different tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate chart data.&lt;/strong&gt; Any data extracted from charts, heatmaps, or visual encodings needs a human check or a secondary verification source. The data exists somewhere else (a spreadsheet, a database) before it becomes a chart. Get the source data when possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build ground truth assertions.&lt;/strong&gt; I created JSON files with expected values per page: titles, dates, specific numbers, opening sentences. Each assertion has a match type and a category. Run every OCR output against these assertions automatically.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="nl"&gt;"pdf_filename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mckinsey-global-institute-2025-in-charts.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="nl"&gt;"assertions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ease of rearrangement varies across products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chart_title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"match_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"present"&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Frozen tilapia fillets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data_label"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"match_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"present"&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"330"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"table_value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"match_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"exact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"United States population in millions"&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the model says “December 2025” but your ground truth says “December 2024,” you catch it before it hits your vector database or production. If it misses “Frozen tilapia fillets” entirely, you know it dropped a data label. This is how you turn silent failures into noisy ones. This is a simplified example. My full validation workflow runs assertions across an entire data pipeline with automated scoring per provider. That’s a story for another article.&lt;/p&gt;

&lt;p&gt;The model will never tell you it’s unsure. That’s your job.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>datascience</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Nobody Wants to Learn AI</title>
      <dc:creator>Alvis Ng</dc:creator>
      <pubDate>Fri, 03 Apr 2026 02:04:09 +0000</pubDate>
      <link>https://forem.com/iamalvisng/nobody-wants-to-learn-ai-1gf</link>
      <guid>https://forem.com/iamalvisng/nobody-wants-to-learn-ai-1gf</guid>
      <description>&lt;p&gt;&lt;em&gt;The “lifelong learner” identity isn’t aspiration. It’s a subscription you can’t cancel.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Last week, someone in your feed posted about being "thrilled to build AI agents this weekend." Forty-seven likes. Fire emojis. "Love the growth mindset!" You stared at it and felt something you couldn't name. Not jealousy. Not inspiration. Something closer to recognition. The way you recognize a mask because you're wearing the same one.&lt;/p&gt;

&lt;p&gt;Nobody is thrilled to learn agent frameworks on a Saturday. They're afraid of what happens if they don't.&lt;/p&gt;

&lt;p&gt;You probably have your own version of this. A browser tab you keep meaning to open. A course you bookmarked during a sale and never started. A Slack thread about "AI readiness" that you skimmed and closed. The quiet admission that you don't know enough to stay relevant, and the quieter admission that bookmarking the resource let you feel like you were doing something about it.&lt;/p&gt;

&lt;p&gt;You've been writing production code for years. By most reasonable measures, you know what you're doing.&lt;/p&gt;

&lt;p&gt;And yet.&lt;/p&gt;

&lt;p&gt;That tab sits there. Glowing faintly. A talisman against obsolescence.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Same Activity, Two Different Meanings
&lt;/h3&gt;

&lt;p&gt;Early in your career, you learned a framework because it was genuinely exciting. A new mental model for building interfaces. You stayed up late not because you were anxious but because you couldn't stop. The learning felt like building. Like adding rooms to a house you were just beginning to inhabit.&lt;/p&gt;

&lt;p&gt;Now you open a course tab because your company started an "AI readiness" initiative. There's no mandate, technically. Just a Slack message from leadership about "staying ahead of the curve" and a shared spreadsheet where you can log your upskilling hours. Voluntary, of course. The way salary negotiations are voluntary.&lt;/p&gt;

&lt;p&gt;The distinction matters because the industry doesn't acknowledge it. Every conference talk, every LinkedIn post, every corporate learning initiative treats all learning as the same species. Growth. Development. Curiosity. Whether you're a new grad exploring your first language or a career-changer using Coursera to escape a dead-end job or a 15-year veteran trying to prove you're not a dinosaur, it's all filed under the same aspirational banner.&lt;/p&gt;

&lt;p&gt;But the body knows the difference. Curiosity feels like leaning forward. Defense feels like bracing.&lt;/p&gt;

&lt;p&gt;I want to be clear about something: for some people, these platforms are genuinely life-changing. The self-taught developer who used a Coursera scholarship to move from data entry to engineering, the career-changer who learned Python on free YouTube tutorials. That's real. That matters. This isn't about those people. This is about a system that takes their stories and uses them as marketing for something very different: the perpetual anxiety machine that tells experienced professionals their decade of work might expire next quarter.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Thing You Bought vs. The Thing You Got
&lt;/h3&gt;

&lt;p&gt;Here's where the economics get clarifying.&lt;/p&gt;

&lt;p&gt;The average MOOC completion rate is &lt;a href="https://www.insidehighered.com/digital-learning/article/2019/01/16/study-offers-data-show-moocs-didnt-achieve-their-goals" rel="noopener noreferrer"&gt;below 10% for free-track learners&lt;/a&gt;, with some studies putting it as low as 3%. More than nine out of ten people who enroll in an online course never finish it. They buy access. They don't buy knowledge. They buy the feeling of progress, the same way a gym membership on January 2nd buys the feeling of fitness.&lt;/p&gt;

&lt;p&gt;The corporate side isn't better. &lt;a href="https://blog.elblearning.com/what-is-scrap-learning-and-how-much-is-it-costing-your-ld-department" rel="noopener noreferrer"&gt;A 2014 study by Gartner&lt;/a&gt; found that 45% of corporate training qualifies as "scrap learning," content employees complete but never apply on the job. That Kubernetes course you watched on double-speed during lunch breaks? By performance review season, you'll have retained the certificate and almost none of the knowledge.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The upskilling industry doesn't sell knowledge. It sells the feeling of doing something, and even that feeling has a completion rate of 3%.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  The Skill That Expires vs. The Skill That Compounds
&lt;/h3&gt;

&lt;p&gt;This is the part nobody talks about, because talking about it would break the business model.&lt;/p&gt;

&lt;p&gt;Not all skills depreciate at the same rate. Your knowledge of a specific framework has a half-life of about two to three years. React hooks, Kubernetes configurations, the agent framework API you learned last month: the specifics you picked up this quarter will be partially obsolete by next year and largely irrelevant in three. This isn't a failure of learning. It's the nature of implementation knowledge. It's perishable by design.&lt;/p&gt;

&lt;p&gt;Your ability to debug a system you've never seen before has a half-life measured in decades. Architectural judgment, the instinct for where complexity will hurt you later, the capacity to read a codebase and understand not just what it does but why someone built it that way: these are durable skills. They compound with experience instead of depreciating with time.&lt;/p&gt;

&lt;p&gt;Think about the SRE on your team who has spent eight years keeping a payments system alive through migrations, acquisitions, and near-catastrophic failovers. Who knows where every brittleness hides. Who can diagnose cascading failures from the shape of a latency graph the way a cardiologist reads an EKG. Now imagine their manager asking what they're doing to "stay current with AI." Their entire career is a durable skill. The system that evaluates them can't see it.&lt;/p&gt;

&lt;p&gt;The upskilling industry has no product for that kind of expertise. You can't package it into a six-week course with a certificate. It develops slowly, through years of building and maintaining real systems, through the painful process of watching your own architectural decisions age and learning which ones held. There is no shortcut. There is no 90%-off sale.&lt;/p&gt;

&lt;p&gt;So the industry sells you the perishable kind. Over and over. React today, agent frameworks tomorrow, whatever tool emerges next quarter. Each course addresses a skill with a two-year shelf life, which means you'll need another course in two years. And another after that. The business model depends on your knowledge expiring.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your React knowledge has a half-life of about two years. Your ability to debug a system you've never seen has a half-life of decades. The industry charges you quarterly for the first and has no idea how to price the second.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I should say the obvious thing: sometimes you do need the perishable skill. Sometimes the new framework is the right tool and learning it is the right call. And if you're four years in, still building your foundation, the cruelest part is this: you're being told the perishable stuff is all that matters before you've had time to develop anything durable. The system is pricing your potential at zero while demanding you pay full rate for currency.&lt;/p&gt;

&lt;p&gt;The problem isn't learning new things. The problem is an economy that treats perishable and durable skills as interchangeable, prices them identically at hiring time, and then acts surprised when experienced engineers feel like they're running in place. Durable skills make you a terrible customer. Perishable skills make you a subscription.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Confession I Keep Editing
&lt;/h3&gt;

&lt;p&gt;I should be honest about something, and more honest than I usually am.&lt;/p&gt;

&lt;p&gt;I build AI systems. I know what these tools can and can't do. And I still feel it. The pressure to signal that I'm keeping up. The impulse to post about the latest framework on LinkedIn not because I learned something meaningful, but because the keyword matters more than the knowledge. I know it, and I've done it anyway.&lt;/p&gt;

&lt;p&gt;I've watched engineers I respect get evaluated on whether they "adopted AI tools" instead of whether they built systems that worked. I've seen performance rubrics that reward course completion and penalize the kind of deep, quiet expertise that keeps production systems alive. The rubric measures compliance, not capability. It rewards the perishable and ignores the durable. And the people filling out those rubrics know it. They tighten their jaws and check the boxes because the alternative is admitting the system they're enforcing is broken.&lt;/p&gt;

&lt;p&gt;I know because I've been on both sides of that table. The feeling is the same: you're performing competence instead of practicing it.&lt;/p&gt;




&lt;h3&gt;
  
  
  The One Question That Tells You Everything
&lt;/h3&gt;

&lt;p&gt;Next time you're about to open a course, a tutorial, a weekend project with a new framework, ask yourself one question:&lt;/p&gt;

&lt;p&gt;Will I use this in the next six months, or am I learning it because having it on my profile makes me feel safer?&lt;/p&gt;

&lt;p&gt;The first is investment. The second is a minimum payment on a debt that keeps growing.&lt;/p&gt;

&lt;p&gt;If the answer is "I'll use it," learn it. Learn it deeply. Build something real with it. That's not anxiety. That's craft.&lt;/p&gt;

&lt;p&gt;If the answer is "it makes me feel safer," close the tab. Spend that hour reading the codebase you already maintain. Understand the system you already own. Build the durable skill that no course can teach and no certificate can prove. The thing that makes you the person in the room who says "this will break in six months" and is right.&lt;/p&gt;

&lt;p&gt;The upskilling industry can't sell you that. Which is exactly why it's valuable.&lt;/p&gt;




&lt;h3&gt;
  
  
  Competence Debt
&lt;/h3&gt;

&lt;p&gt;That tab is still open.&lt;/p&gt;

&lt;p&gt;Some mornings I open my browser and it's there, between Jira and Slack, glowing with the particular patience of things that know they've already won. I'll click it eventually. Not because the course will teach me something durable. Not because it will make me meaningfully better at the work that actually matters. But because the credential economy demands proof of currency, and currency is what expires.&lt;/p&gt;

&lt;p&gt;Somewhere, someone is genuinely excited about AI agents, staying up late because the possibilities feel electric. Not because of a performance review. Not because of a Slack message from leadership. Because the thing itself is interesting. That feeling still exists. Most of us just can't reach it through the anxiety anymore.&lt;/p&gt;

&lt;p&gt;Here's what I've started calling it: competence debt. The accumulation of perishable certifications while your durable skills atrophy from neglect. Every hour spent on a course that teaches you the API of the moment is an hour not spent understanding the system you've been maintaining for three years. Every certificate is a minimum payment on a debt that keeps growing. You feel productive. Your profile looks current. And underneath, the skills that would actually make you irreplaceable are quietly compounding interest in the wrong direction.&lt;/p&gt;

&lt;p&gt;The industry taught us to call this growth.&lt;/p&gt;

&lt;p&gt;The rubric was designed to measure it.&lt;/p&gt;

&lt;p&gt;The word we were all looking for is depreciation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;—Viz&lt;/em&gt;&lt;/p&gt;

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