<?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: David Buchan</title>
    <description>The latest articles on DEV Community by David Buchan (@reaver1000).</description>
    <link>https://dev.to/reaver1000</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%2F4172130%2F726d100c-7890-44f5-a024-40c59d9002b2.png</url>
      <title>DEV Community: David Buchan</title>
      <link>https://dev.to/reaver1000</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/reaver1000"/>
    <language>en</language>
    <item>
      <title>Fine-tuning a YOLO26 classifier on trading-livestream screen captures - with a Whisper + VLM labeling flywheel</title>
      <dc:creator>David Buchan</dc:creator>
      <pubDate>Tue, 27 Oct 2026 08:07:50 +0000</pubDate>
      <link>https://dev.to/reaver1000/fine-tuning-a-yolo26-classifier-on-trading-livestream-screen-captures-with-a-whisper-vlm-5d7g</link>
      <guid>https://dev.to/reaver1000/fine-tuning-a-yolo26-classifier-on-trading-livestream-screen-captures-with-a-whisper-vlm-5d7g</guid>
      <description>&lt;p&gt;Most computer-vision demos start with a dataset someone else curated. Mine started with screen recordings I was not allowed to redistribute - which turned out to be the most instructive constraint in the whole project.&lt;/p&gt;

&lt;p&gt;This is the story of &lt;code&gt;trade-state-classifier&lt;/code&gt;: a fine-tuned YOLO26n-cls model that looks at a screenshot from a trading livestream and answers one binary question - &lt;strong&gt;is there a filled position visible on screen right now?&lt;/strong&gt; - plus the labeling pipeline that made it possible without anybody's data leaving my machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why binary classification (and not object detection)
&lt;/h2&gt;

&lt;p&gt;The upstream goal is an event pipeline: watch a livestream, detect the moment a streamer's position state changes (flat -&amp;gt; open -&amp;gt; flat), extract the displayed entry/stop/target levels at that moment, and hand a validated event to the execution layer.&lt;/p&gt;

&lt;p&gt;Full UI-element detection (order tickets, entry lines, price badges) is the endgame - but it needs far more labeled variety than I could curate responsibly. A state gate needs to answer exactly one question reliably. So v1 = one classifier, one binary decision, measured hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The labeling problem
&lt;/h2&gt;

&lt;p&gt;Fine-tuning means labels, and labels for this task have a nasty property: &lt;strong&gt;open position is rare&lt;/strong&gt;. Uniform frame sampling at 1 fps gives you thousands of near-identical flat-screen frames and a handful of meaningful ones. Label quality would drown.&lt;/p&gt;

&lt;p&gt;The flywheel that solved it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Whisper (faster-whisper, local) transcribes the VOD.&lt;/strong&gt; Talking traders narrate what they are doing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speech-cue parser finds candidate windows&lt;/strong&gt; - phrases around "getting filled", "taking profit", "stopped out", "flat now".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sparse frame extraction&lt;/strong&gt; - only the seconds around each cue, not the whole hour. Roughly two orders of magnitude fewer frames to look at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local VLM (qwen2.5vl:7b via Ollama) drafts labels&lt;/strong&gt; for those keyframes, based on an element list (position badge, PnL overlay, position box).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confluence confirmation&lt;/strong&gt; - a window only survives if speech and vision agree. The two chatterboxes must contradict each other to flag a frame for me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Owner-verified dense segments&lt;/strong&gt; around confirmed trade windows - I confirm every frame in the segments I train on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last step matters. VLMs fast-draft; they do not carry truth. Every training frame was verified by the only model with skin in the game: me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The training run
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Base: YOLO26n-cls (Ultralytics), the nano variant, deliberately - CPU inference at ~15ms/frame keeps the state gate cheap enough to run continuously.&lt;/li&gt;
&lt;li&gt;Input: ~1,700 confluence-confirmed crops, 512x512.&lt;/li&gt;
&lt;li&gt;20 epochs, batch 16.&lt;/li&gt;
&lt;li&gt;Evaluation: &lt;strong&gt;temporal validation&lt;/strong&gt; - sessions the model never saw during training. Adjacent frames leak; a random frame-split lies to you. Holding out entire sessions is the honest split.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result table (iterations v11 -&amp;gt; v17):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Milestone&lt;/th&gt;
&lt;th&gt;What changed&lt;/th&gt;
&lt;th&gt;Held-out result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;v11&lt;/td&gt;
&lt;td&gt;context-menu and hypothetical position drawings mined out as hard negatives&lt;/td&gt;
&lt;td&gt;100% (341/341), 0 false opens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v13&lt;/td&gt;
&lt;td&gt;dense confluence windows per confirmed trade&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v16&lt;/td&gt;
&lt;td&gt;five more sessions pooled&lt;/td&gt;
&lt;td&gt;100% (341/341), 0/165 false positives&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The interesting run: a session whose only state was "no trades, all day" produced ten 1-second VLM "open" blips taught nothing - until dense windows around the one confirmed trade window were added. Sparse VLM labels had a blind spot; the fix was density, not cleverness.&lt;/p&gt;

&lt;h2&gt;
  
  
  The publishing decision that took longer than the training
&lt;/h2&gt;

&lt;p&gt;After validation the question was: what can go public?&lt;/p&gt;

&lt;p&gt;The model can. The frames cannot - they are someone else's screen, and I decided redistribution crosses a line I did not want to argue with anyone about.&lt;/p&gt;

&lt;p&gt;So: weights on the Hugging Face hub, zero images anywhere, no channel names, no video IDs. Then a full audit of my own public repos - because the training story &lt;em&gt;lives&lt;/em&gt; in commit history, docstrings, and example paths. Every identifier got generalized (channel-a/channel-b, session placeholders), the harness scripts removed those, and history was rewritten rather than patched so nothing was merely invisible-but-recoverable.&lt;/p&gt;

&lt;p&gt;A "publish without exposure" checklist that I now treat as standard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weights are fine, frames are not.&lt;/li&gt;
&lt;li&gt;Descriptive metrics OK; provenance narratives are leaks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git log&lt;/code&gt;, example paths, and docstrings are part of your public surface - audit them, not just README.md.&lt;/li&gt;
&lt;li&gt;If exposure retrofitting needs three layers (working tree, history, hub), treat that cost as the price of skipping hygiene in the first place.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the classifier still cannot do
&lt;/h2&gt;

&lt;p&gt;Saying this publicly protects future-me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It detects a &lt;em&gt;visible&lt;/em&gt; position badge - a streamer chart-hopping mid-trade looks like flat.&lt;/li&gt;
&lt;li&gt;It is platform-appearance-specific. A position box on one platform does not look like another's; pooled training helps, new platforms will need new data.&lt;/li&gt;
&lt;li&gt;A drawn (hypothetical) position-tool box and a filled position can look identical. The visual gate alone cannot tell you a fill happened - that is why the upstream pipeline requires speech confluence and state-transition logic.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;ONNX export and an in-browser demo: the whole model running in the visitor's browser via onnxruntime-web, on screenshots the visitor brings (privacy-compatible demo by construction).&lt;/li&gt;
&lt;li&gt;More platform calibrations, pooled rather than forked.&lt;/li&gt;
&lt;li&gt;The universal detector: entry/stop/target/quantity reads off any chart, with OCR and geometric validation deciding whether an event is even possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full labeled pipeline, validation tooling and inference scripts are in the &lt;a href="https://github.com/Reaver1000/stream-signal-vision" rel="noopener noreferrer"&gt;stream-signal-vision repository&lt;/a&gt;, and the published weights are at &lt;a href="https://huggingface.co/Reaver1000/trade-state-classifier" rel="noopener noreferrer"&gt;trade-state-classifier on Hugging Face&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>computervision</category>
      <category>python</category>
    </item>
    <item>
      <title>Practical patterns for LLM scoring in n8n workflows</title>
      <dc:creator>David Buchan</dc:creator>
      <pubDate>Wed, 21 Oct 2026 10:18:49 +0000</pubDate>
      <link>https://dev.to/reaver1000/practical-patterns-for-llm-scoring-in-n8n-workflows-2aag</link>
      <guid>https://dev.to/reaver1000/practical-patterns-for-llm-scoring-in-n8n-workflows-2aag</guid>
      <description>&lt;p&gt;My n8n workflows follow one hard rule, learned the hard way: rules are cheap, deterministic, and auditable, so they run first. Language models are none of those things, so they run second, on the small population of cases where rules thrash. Lead qualification is the classic example. Company size, email domain, seniority — a lookup table handles those. "Is this inbound request actually a fit for what we sell?" — that is where the rule-based version of me wrote forty brittle IF nodes and lost.&lt;/p&gt;

&lt;p&gt;Here are the five patterns I use when adding LLM scoring to a workflow. They are the same principles behind my job-search pipeline, scaled down to node size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: demand structured output, not prose
&lt;/h2&gt;

&lt;p&gt;Never let the model write you a paragraph you then parse with string matching. Most LLM nodes in n8n (and raw API calls via the HTTP node) support a JSON response format. Ask for exactly this shape and nothing else:&lt;br&gt;
&lt;/p&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;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0-100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sales"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nurture"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasons"&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="s2"&gt;"one line each"&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;Temperature 0. A schema the model cannot deviate from. Prose is for humans; pipelines eat JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: the model scores, the rules route
&lt;/h2&gt;

&lt;p&gt;Put the scoring step behind your existing rule chain, and put an IF node after it. The model's &lt;code&gt;verdict&lt;/code&gt; feeds the IF. The model never writes to your CRM, never sends the alert, never decides anything at the edge. It fills a field. Nodes that do things stay deterministic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: every error branch lands on a default
&lt;/h2&gt;

&lt;p&gt;The model will time out, return malformed JSON on a bad day, or rate-limit you during a burst. Every one of those cases must land somewhere sensible: error branch to a &lt;code&gt;Set&lt;/code&gt; node that writes a default score and a &lt;code&gt;reasons: ["model_unavailable"]&lt;/code&gt; flag. The workflow keeps running. This is the n8n version of errors-are-data: a full run report with a few defaulted scores beats a dead pipeline with a perfect one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: cheap model, precise prompt
&lt;/h2&gt;

&lt;p&gt;Scoring a lead against a rubric is not frontier-model work. A small, fast, cheap model with a prompt that contains your actual rubric — with examples — beats a big model with a vague prompt. Put the rubric in the prompt: what makes a 90, what makes a 30, what is an instant reject. The money you save buys a lot of retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 5: log the reasons, not just the scores
&lt;/h2&gt;

&lt;p&gt;Write score and reasons to your log store every single run. Two weeks later you will find the leads that scored 61 and deserved 90, and the exact sentence in the rubric that caused it. Without logged reasons you have a black box; with them you have a reviewable system. The template library runs 1,892-plus alert cycles at 99.7% for exactly this kind of reason — you cannot fix what you do not log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status and links
&lt;/h2&gt;

&lt;p&gt;I am applying these to the lead-qualification template in my open-source n8n library, and the job-search engine that taught me these patterns is public too: &lt;a href="https://github.com/Reaver1000/n8n-automation-templates" rel="noopener noreferrer"&gt;n8n-automation-templates&lt;/a&gt; and &lt;a href="https://github.com/Reaver1000/remote-job-engine" rel="noopener noreferrer"&gt;remote-job-engine&lt;/a&gt;. The bigger version of this story — 1,500+ postings scored, rules first, model second — is &lt;a href="https://dev.to/reaver1000/i-built-an-ai-that-finds-and-scores-my-next-job-49je"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am a junior automation and data builder, career-changer, fully remote from Germany (or UK). If your team runs n8n or needs LLM integrations that do not hallucinate their way into production: &lt;a href="https://reaver1000.github.io" rel="noopener noreferrer"&gt;reaver1000.github.io&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>n8n</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Anyone can claim to be an up-and-coming AI engineer. Here are my receipts instead.</title>
      <dc:creator>David Buchan</dc:creator>
      <pubDate>Fri, 16 Oct 2026 08:32:32 +0000</pubDate>
      <link>https://dev.to/reaver1000/anyone-can-claim-to-be-an-up-and-coming-ai-engineer-here-are-my-receipts-instead-4m26</link>
      <guid>https://dev.to/reaver1000/anyone-can-claim-to-be-an-up-and-coming-ai-engineer-here-are-my-receipts-instead-4m26</guid>
      <description>&lt;p&gt;"Up and coming." "Promising." "One to watch." Every job board and every LinkedIn feed is full of people describing themselves with adjectives. I do not want to do that, because adjectives are unverifiable noise. So this post is something else: my first year of building in public, in receipts, with links. Judge the trajectory, not the adjectives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The receipts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;2022 — the degree, done at night.&lt;/strong&gt; I started a BSc in Computing and IT with the Open University while working full time. I finished it in 2026. Not a receipt for talent — a receipt for stubbornness, which is more reliable anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2025 — n8n templates that run in production.&lt;/strong&gt; I built a set of workflow templates for n8n and put them to work. The alert monitor has run 1,892 times at 99.7% success. Lead qualification: 247 runs, 99.2%. Data sync: 312 runs, 97.8%. They are open source: &lt;a href="https://github.com/Reaver1000/n8n-automation-templates" rel="noopener noreferrer"&gt;n8n-automation-templates&lt;/a&gt;. A template that runs weekly for months without you touching it is worth more than any certificate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2026 — the job search engine.&lt;/strong&gt; I automated my own job hunt: 30+ ATS boards, deduplication against every posting I have ever seen, hard rule filters before any LLM call, then LLM scoring against my honest profile. Over 1,500 postings evaluated this year; 1,539 parked; 12 apply-worthy. Public and scrubbed: &lt;a href="https://github.com/Reaver1000/remote-job-engine" rel="noopener noreferrer"&gt;remote-job-engine&lt;/a&gt; and &lt;a href="https://github.com/Reaver1000/job-sweep" rel="noopener noreferrer"&gt;job-sweep&lt;/a&gt;. The full story is &lt;a href="https://dev.to/reaver1000/i-built-an-ai-that-finds-and-scores-my-next-job-49je"&gt;on dev.to&lt;/a&gt;, and auf Deutsch too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;September 2026 — the quality wave.&lt;/strong&gt; One month, four repos, all with tests: a &lt;a href="https://github.com/Reaver1000/etl-data-pipeline" rel="noopener noreferrer"&gt;composable ETL framework&lt;/a&gt; (errors as data, whitelist mapping, CI), a &lt;a href="https://github.com/Reaver1000/sql-query-builder" rel="noopener noreferrer"&gt;SQL playground&lt;/a&gt; (55 templates, 115 tests, now a &lt;a href="https://sql-query-builder-uzsrs2gl2af8avqsxappchw.streamlit.app" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;), a &lt;a href="https://github.com/Reaver1000/python-automation-toolkit" rel="noopener noreferrer"&gt;file automation toolkit&lt;/a&gt; (20 tests), and a &lt;a href="https://github.com/Reaver1000/finance-dashboard" rel="noopener noreferrer"&gt;finance analytics dashboard&lt;/a&gt;. The lesson post is &lt;a href="https://dev.to/reaver1000/errors-are-data-what-building-a-small-etl-framework-taught-me-3mhn"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;October 2026 — the paper trail.&lt;/strong&gt; Three technical articles, a bilingual portfolio site, and a live demo instead of dead links. You are reading the third article.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the receipts have in common
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Build things you actually use.&lt;/strong&gt; My n8n templates monitor services I care about. My job engine found the shortlist I am actually working through. Tools you use daily cannot fake it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tests or it did not happen.&lt;/strong&gt; Every framework repo has a test suite. The SQL playground has 115 tests that execute all 55 templates. This is not rigour for its own sake — it is the difference between "code exists" and "code works."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write it down.&lt;/strong&gt; The articles are not marketing. They are the honest lessons, including the ones that cost me: six perfect-looking job candidates that died when I read the actual posting bodies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest gap
&lt;/h2&gt;

&lt;p&gt;I have no professional software engineering title yet. Thirteen years in operations — Lloyds Banking Group, Deutsche Post, sysadmin, currently Amazon print production — and a degree, and all the receipts above, and no job title in software. That is the next receipt, and it is the one I cannot publish myself. Someone has to give it to me.&lt;/p&gt;

&lt;p&gt;If your team needs a junior or associate who already works like this — AI, automation, or data, fully remote from Germany or UK — everything is on &lt;a href="https://reaver1000.github.io" rel="noopener noreferrer"&gt;reaver1000.github.io&lt;/a&gt;. The receipts are the résumé. The next one is yours to write.&lt;/p&gt;

</description>
      <category>career</category>
      <category>ai</category>
      <category>data</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Wie ich eine KI gebaut habe, die meine Jobsuche übernimmt</title>
      <dc:creator>David Buchan</dc:creator>
      <pubDate>Sun, 11 Oct 2026 14:01:31 +0000</pubDate>
      <link>https://dev.to/reaver1000/wie-ich-eine-ki-gebaut-habe-die-meine-jobsuche-ubernimmt-4e6i</link>
      <guid>https://dev.to/reaver1000/wie-ich-eine-ki-gebaut-habe-die-meine-jobsuche-ubernimmt-4e6i</guid>
      <description>&lt;p&gt;Jede Woche durchsucht meine Software etwa zwanzig Stellenbörsen. Sie sammelt ein paar hundert Stellenanzeigen. Ein einziger LinkedIn-Sweep brachte kürzlich 112 Ergebnisse. In diesem Jahr hat die Pipeline mehr als 1.500 Anzeigen bewertet. 1.539 davon wurden aussortiert.&lt;/p&gt;

&lt;p&gt;Wie viele Bewerbungen ich wirklich abgeschickt habe? Eine Handvoll.&lt;/p&gt;

&lt;p&gt;Dieses Verhältnis ist der ganze Punkt. Ich bin Quereinsteiger und über vierzig. Fünf Jahre bei der Lloyds Banking Group, acht Jahre als Postbote, zwei Jahre als Windows-Systemadministrator. Heute arbeite ich in der Druckproduktion bei Amazon. Dieses Jahr habe ich mein Informatik-Studium abgeschlossen (BSc Computing and IT, Open University). Einen professionellen Titel als Softwareentwickler habe ich noch nicht. Ich suche den ersten. Und er muss remote sein.&lt;/p&gt;

&lt;p&gt;Stellenbörsen sind anstrengend. Für mich waren sie besonders anstrengend, denn ich filtere nach engen Kriterien und habe den Titel noch nie getragen, auf den ich mich bewerbe. Also baute ich mir die Software, die ich nicht kaufen konnte.&lt;/p&gt;

&lt;h2&gt;
  
  
  Die Pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Stufe 1, Sammeln.&lt;/strong&gt; Sechzehn API- und RSS-Quellen. Dazu Playwright-Sweeps für die Börsen ohne API: Indeed, StepStone, LinkedIn (mit gespeicherter Session) und die Arbeitsagentur mit tiefen Suchbegriffen. Alles landet in einem Speicher mit Deduplizierung. Keine Anzeige wird zweimal verarbeitet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stufe 2, Harte Filter.&lt;/strong&gt; Bevor ein Modell etwas sieht, sortieren billige Regeln aus. "Senior" im Titel? Weg. Stadt außerhalb meines Radius? Weg. Mehr als drei Jahre Berufserfahrung gefordert? Nicht meine Ebene. Diese Filter sind bewusst keine Modellaufrufe: Sie kosten nichts und man kann sie prüfen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stufe 3, KI-Bewertung.&lt;/strong&gt; Die Überlebenden liest ein Sprachmodell. Es bewertet die Passung gegen mein ehrliches Profil. Auch die Einschränkungen, die sonst erst im Interview auffallen. Rollen mit "verhandlungssicher Deutsch in Wort und Schrift" für Kundenkontakt sind für mich nichts — mein schriftliches Deutsch ist B1. Interner Team-Kontakt auf Deutsch ist dagegen fein, gesprochen bin ich auf C1-Niveau.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stufe 4, Verifizierung.&lt;/strong&gt; Diese Stufe hat sich ihren Platz hart verdient. Aggregator-Titel schmeicheln. Sechs perfekte LinkedIn-Kandidaten starben, als ich den echten Anzeigentext las. Die "Junior"-Stelle wollte fünf Jahre Erfahrung. Die "Remote"-Stelle wollte drei Tage vor Ort in einer anderen Stadt. Die Regel ist jetzt absolut: Nichts kommt in die Bewerbungsliste, bevor der echte Anzeigentext geladen und gelesen wurde.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkobnjll5pj84uns8vo5g.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkobnjll5pj84uns8vo5g.png" alt="Wöchentlicher Sweep gegen die daraus entstehende Confidence-Liste: 112 Anzeigen rein, 12 bewerbenswert." width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stufe 5, der Mensch.&lt;/strong&gt; Ich öffne die URL selbst. Ich lese die Anzeige selbst. Dann schreibe ich genau das, was das Formular verlangt. Eine Bewerbung nach der anderen. Keine Massen-Anschreiben. Wenn Bewerbungen teuer sind, liest man wirklich. Genau das ist die Funktion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was ich gelernt habe
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Filter hart, bevor du das Modell aufrufst.&lt;/strong&gt; Regeln sind kostenlos. Tokens sind es nicht.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lass die KI nie Stellenanzeigen erfinden.&lt;/strong&gt; Ein Modell, das man nach Stellen fragt, erfindet glaubwürdige Anzeigen mit plausiblen URLs. Meine Pipeline bewertet nur, was die Scraper wirklich geladen haben.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Die letzte Meile bleibt menschlich.&lt;/strong&gt; Die Pipeline soll mich wählerisch machen, nicht faul.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Für Quereinsteiger ist die Pipeline das Portfolio.&lt;/strong&gt; Ich wartete lange auf ein Projekt, das beweist, dass ich Software bauen kann. Irgendwann merkte ich: Der ehrlichste Beweis lief längst. Getestetes Python, echte Scraper, Deduplizierung, CI. Ein System im täglichen Einsatz, mit echten Folgen. Die Werkzeuge sind öffentlich: &lt;a href="https://github.com/Reaver1000/job-sweep" rel="noopener noreferrer"&gt;job-sweep&lt;/a&gt; ist das Sweep-Toolkit, &lt;a href="https://github.com/Reaver1000/remote-job-engine" rel="noopener noreferrer"&gt;remote-job-engine&lt;/a&gt; übernimmt Suche und Bewertung. Eines der Projekte läuft als Live-Demo: &lt;a href="https://sql-query-builder-uzsrs2gl2af8avqsxappchw.streamlit.app" rel="noopener noreferrer"&gt;SQL Query Builder&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was kommt
&lt;/h2&gt;

&lt;p&gt;Ich suche eine Junior- oder Associate-Rolle — KI, Automatisierung oder Daten. Vollständig remote aus Deutschland oder aus Großbritannien (ich bin britischer Staatsbürger). Alles über mich steht auf &lt;a href="https://reaver1000.github.io" rel="noopener noreferrer"&gt;reaver1000.github.io&lt;/a&gt;, dort gibt es auch eine deutsche Seite.&lt;/p&gt;

&lt;p&gt;Und wenn du selbst auf Jobsuche bist: Bau das Werkzeug, bevor du dem Board vertraust. Das Board hat deine Interessen nicht im Blick. Deine Pipeline schon — du hast sie geschrieben.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>career</category>
      <category>deutsch</category>
    </item>
    <item>
      <title>Errors are data: what building a small ETL framework taught me</title>
      <dc:creator>David Buchan</dc:creator>
      <pubDate>Fri, 09 Oct 2026 13:28:26 +0000</pubDate>
      <link>https://dev.to/reaver1000/errors-are-data-what-building-a-small-etl-framework-taught-me-3mhn</link>
      <guid>https://dev.to/reaver1000/errors-are-data-what-building-a-small-etl-framework-taught-me-3mhn</guid>
      <description>&lt;p&gt;Most tutorials show you pandas in a notebook. Real data work is a scheduled job with three sources, and one of them is broken, and nobody is watching when it runs.&lt;/p&gt;

&lt;p&gt;That gap is why I built &lt;a href="https://github.com/Reaver1000/etl-data-pipeline" rel="noopener noreferrer"&gt;etl-data-pipeline&lt;/a&gt;: a small, composable extract-transform-load framework in Python with a full test suite and CI. It ingests deliberately messy sample data, stray whitespace, inconsistent casing, a missing email, duplicate rows etc cleans it, loads it into SQLite, and reads the table back to prove the roundtrip. Twenty-one tests. You can run the whole demo in under a minute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python demo.py
pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not Airflow. It is not dbt. It is a few hundred lines that do one job,  and building it taught me four things that notebooks never will.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: errors are data, not exceptions
&lt;/h2&gt;

&lt;p&gt;My first version raised on the first bad row. Then I imagined the scheduled-job reality: one dead API source at 3am, an exception halfway through the run, and half the data loaded with no record of what survived. So the extractors and loaders in this framework never raise. Every stage returns a result object with an errors list, and the pipeline produces a structured run report: per-stage record counts, successes, errors, timing.&lt;/p&gt;

&lt;p&gt;A full report at the end of a broken run beats a stack trace in the middle of it. Errors became rows in the report, and the report became the thing I actually read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: schema mapping is a whitelist, not a copy
&lt;/h2&gt;

&lt;p&gt;Upstream APIs grow fields without warning, and "just copy everything" is how an unreviewed column sneaks into your database. The &lt;code&gt;FieldMapper&lt;/code&gt; stage only copies fields you explicitly mapped and deliberately drops everything else. Mapping became a security decision, not just a renaming convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: SQL identifiers cannot be parameterised
&lt;/h2&gt;

&lt;p&gt;Values in SQLite can be bound as parameters. Table and column names cannot . They have to be interpolated into the DDL string. So the loader validates the table name and quotes identifiers, while all values stay parameterised. There is an injection test in the suite that documents exactly this edge, because it is the one place a "just use parameterised queries" rule quietly fails.&lt;/p&gt;

&lt;p&gt;If you learn one paranoid habit from this post: decide, per identifier, whether it comes from your code (fine) or from anywhere else (validate it first).&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 4: tests never touch the network
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;APIExtractor&lt;/code&gt; tests mock &lt;code&gt;requests.get&lt;/code&gt;. Everything else runs against pytest's &lt;code&gt;tmp_path&lt;/code&gt;. That decision, more than any cleverness, is why the suite runs in seconds and never flakes. It also forced the code into honest seams, if you cannot mock the boundary, the boundary is in the wrong place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The composable part
&lt;/h2&gt;

&lt;p&gt;Every stage is one abstract method. Subclass &lt;code&gt;Extractor&lt;/code&gt;, &lt;code&gt;Transformer&lt;/code&gt;, or &lt;code&gt;Loader&lt;/code&gt;, implement the one method, and the pipeline treats it like any other stage. I added an extractor for a new JSON response shape by writing a few lines, not a new framework.&lt;/p&gt;

&lt;p&gt;That is the quiet punchline for other career-changers like me (I wrote this while finishing a Computing and IT degree after 13 years in operations): the big orchestration tools are composable stages with run reports and whitelists underneath. Building the small version once teaches you what the big version is actually doing. Now when a job description says "experience with ETL tooling", I can say what I did, show the code, and point at the tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The repo is public: &lt;a href="https://github.com/Reaver1000/etl-data-pipeline" rel="noopener noreferrer"&gt;github.com/Reaver1000/etl-data-pipeline&lt;/a&gt;. Fork it, break it, extend it. The design notes in the README cover the extension points, and the CI badge is green.&lt;/p&gt;

&lt;p&gt;I am a junior developer looking for my first software engineering title. If your team does data work in Python and this style of thinking fits, everything about me is at &lt;a href="https://reaver1000.github.io" rel="noopener noreferrer"&gt;reaver1000.github.io&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>data</category>
      <category>testing</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I built an AI that finds and scores my next job</title>
      <dc:creator>David Buchan</dc:creator>
      <pubDate>Thu, 08 Oct 2026 21:12:02 +0000</pubDate>
      <link>https://dev.to/reaver1000/i-built-an-ai-that-finds-and-scores-my-next-job-49je</link>
      <guid>https://dev.to/reaver1000/i-built-an-ai-that-finds-and-scores-my-next-job-49je</guid>
      <description>&lt;p&gt;Every week my tooling sweeps around twenty job sources and pulls in a few hundred postings. A single recent LinkedIn sweep returned 112 results. Over this year the pipeline has evaluated more than 1,500 postings and parked 1,539 of them.&lt;/p&gt;

&lt;p&gt;The number of jobs I have actually applied to? A handful.&lt;/p&gt;

&lt;p&gt;That ratio is the whole point. I am a career changer in my forties. Five years in banking operations at Lloyds, eight years delivering mail, two years as a Windows sysadmin, and currently I run print production shifts at an Amazon facility. This year I finished a BSc in Computing and IT with the Open University. What I do not have is a professional software engineering title, so I am hunting for my first one, and I need it remote.&lt;/p&gt;

&lt;p&gt;Job boards are exhausting for everyone, but they are especially exhausting when you are filtering for a narrow set of constraints and you have never held the title you are applying for. So I built the tooling I could not buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Stage 1, collection.&lt;/strong&gt; Sixteen API and RSS sources, plus Playwright-based sweeps for the boards that have no API: Indeed, StepStone, LinkedIn (via a saved session), and the German Arbeitsagentur job board with deep search terms. Each source writes a markdown file of raw results, and everything lands in a SQLite-backed dedupe store so no posting is ever processed twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 2, hard elimination.&lt;/strong&gt; Before any model sees anything, cheap deterministic filters kill the obviously wrong postings. A title containing "Senior" when the range is junior. A city outside my radius. Recruiter spam patterns. Skills bars over three years of professional experience, because "Erste Erfahrung" (first experience) is fine for me and "5+ years" is not. These filters are strings and rules, not model calls, and that is deliberate: they cost nothing and they are auditable. At last count the confidence list held 12 survivors, 23 maybes, and 1,539 parked. The parked bucket being enormous is the system working correctly.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkobnjll5pj84uns8vo5g.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkobnjll5pj84uns8vo5g.png" alt="Actual output from the weekly sweep versus the confidence list it feeds: 112 postings in, 12 apply-worthy survivors." width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The funnel in one picture: a weekly LinkedIn sweep produces 112 raw results; after dedupe, hard elimination, and body-verification, the apply-now queue holds 12.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 3, LLM scoring.&lt;/strong&gt; The survivors get read by a language model that scores fit against my honest profile, including the constraints that usually get discovered at the interview stage. Written German at B1 means customer-facing roles demanding "verhandlungssicher Deutsch in Wort und Schrift" are a no for me, while internal team communication in German is fine because my spoken German is C1. Encoding these rules as a prompt instead of gut feeling removed a whole class of optimistic mistakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 4, verification.&lt;/strong&gt; This is the stage that earned its place the hard way. Aggregator titles flatter. Six consecutive LinkedIn candidates that looked perfect by title died when I fetched the actual posting body: the "junior" role wanted five years, the "remote" role wanted three days on-site in another city, the "AI engineer" role was actually a data-labeling gig. The rule now is absolute: nothing enters the apply queue until the real posting body has been fetched and read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 5, the human.&lt;/strong&gt; I open the URL myself and read the posting myself. Then I draft exactly what the form requires, one application at a time. No batch-generated cover letters, no auto-submit. When applying is expensive you actually read, and that is the feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned building it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Filter hard before you call the model.&lt;/strong&gt; Rules are free, tokens are not, and determinism beats cleverness for anything you would want to debug at 6 a.m. before a shift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never let an LLM generate the job postings.&lt;/strong&gt; Left alone, a model asked to "find jobs" will happily invent believable postings with plausible URLs. Mine only ever ranks what the scrapers actually fetched. Every URL in the queue was downloaded from a real board.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The last mile should stay human.&lt;/strong&gt; The pipeline's job is to make me picky, not lazy. Automation found me a short list worth reading carefully, and the careful reading is mine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For a career changer, the pipeline is the portfolio.&lt;/strong&gt; I kept waiting for a project impressive enough to prove I could engineer software. At some point I noticed the most honest proof was already running: tested Python, real scrapers, a dedupe store, CI on the ETL framework, and a system in daily use solving a real problem with real stakes. The tools in this post are public: &lt;a href="https://github.com/Reaver1000/job-sweep" rel="noopener noreferrer"&gt;job-sweep&lt;/a&gt; is the scrubbed sweep toolkit, and &lt;a href="https://github.com/Reaver1000/remote-job-engine" rel="noopener noreferrer"&gt;remote-job-engine&lt;/a&gt; handles discovery and scoring.&lt;/p&gt;

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

&lt;p&gt;I am available for junior or associate roles from November 2026, fully remote from Germany or UK-remote as a British citizen. If you are hiring for AI integration, automation, data, or QA work and any of this resonates, everything is on my site: &lt;a href="https://reaver1000.github.io" rel="noopener noreferrer"&gt;reaver1000.github.io&lt;/a&gt;, or find me on &lt;a href="https://www.linkedin.com/in/david-a-buchan/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you are also job hunting in this market: build the tool before you trust the board. The board does not have your interests at heart. The pipeline does, because you wrote it.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>career</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
