<?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: Teske Systemtechnik</title>
    <description>The latest articles on DEV Community by Teske Systemtechnik (@teske-systemtechnik).</description>
    <link>https://dev.to/teske-systemtechnik</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%2F3987661%2F91cb2ded-66d5-4dfa-83a0-d845e6f34f99.png</url>
      <title>DEV Community: Teske Systemtechnik</title>
      <link>https://dev.to/teske-systemtechnik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/teske-systemtechnik"/>
    <language>en</language>
    <item>
      <title>AWS Cost Optimization</title>
      <dc:creator>Teske Systemtechnik</dc:creator>
      <pubDate>Tue, 16 Jun 2026 15:58:30 +0000</pubDate>
      <link>https://dev.to/teske-systemtechnik/aws-cost-optimization-3f5l</link>
      <guid>https://dev.to/teske-systemtechnik/aws-cost-optimization-3f5l</guid>
      <description>&lt;p&gt;65% AWS cost reduction ($3,850 → $1,330 / month) via safe legacy decommissioning, zero downtime for production systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Blender Networks Inc.&lt;/strong&gt;, an advertising agency based in Bedford, Canada, had a bloated AWS bill, root cause: a legacy, proprietary ad-serving platform nobody seriously used anymore, yet still burning compute, load balancers and storage. Goal: &lt;strong&gt;decommission it cleanly without the production WordPress sites or email infrastructure (MX) going down for even a second.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The approach
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Log analysis, not guesswork.&lt;/strong&gt; Deep dive into ALB access logs to separate legitimate traffic from noise, uncovered hidden dependencies between the old EKS cluster and live production and cleanly decoupled them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8 "zombie ALBs" eliminated.&lt;/strong&gt; Load balancers that served no real traffic anymore, just magnets for bot scans (PHPUnit exploits, credential stuffing). Expensive noise with no business value. Shut down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EKS cluster phased down.&lt;/strong&gt; Controlled shutdown of the entire Kubernetes cluster including worker nodes; orphaned target groups and load balancers removed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage cleanup.&lt;/strong&gt; 1.4 TB of obsolete EBS snapshots and system logs deleted. 1 TB of historical RDS backups moved to Glacier Deep Archive via an S3 lifecycle rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EC2 right-sizing.&lt;/strong&gt; Once bot traffic and legacy overhead were gone, the main server could be safely scaled down, the final push on compute cost.&lt;/li&gt;
&lt;/ul&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%2F9o213iz9b1pwdn95gdmt.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%2F9o213iz9b1pwdn95gdmt.png" alt="AWS infrastructure before/after: $3,850 with zombie ALBs, bot traffic and oversized engine → $1,330 after legacy shutdown, storage migration and right-sizing" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Before / after: what drove the old bill and the steps that brought it down.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;−65% AWS cost&lt;/strong&gt;, from ~$3,850 to $1,330 per month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero downtime&lt;/strong&gt; for production WordPress and email throughout the entire decommissioning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1.4 TB storage&lt;/strong&gt; freed, 1 TB migrated to cold storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8 attack vectors&lt;/strong&gt; eliminated via zombie-ALB shutdown.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>eks</category>
      <category>cloudwatch</category>
      <category>s3glacier</category>
    </item>
    <item>
      <title>Book Lister AI</title>
      <dc:creator>Teske Systemtechnik</dc:creator>
      <pubDate>Tue, 16 Jun 2026 15:58:16 +0000</pubDate>
      <link>https://dev.to/teske-systemtechnik/book-lister-ai-5b14</link>
      <guid>https://dev.to/teske-systemtechnik/book-lister-ai-5b14</guid>
      <description>&lt;p&gt;Desktop app that scans used books in under 30 seconds, extracts data via Gemini vision, live-prices, and lists on eBay, +400% throughput via computer vision and GenAI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge
&lt;/h2&gt;

&lt;p&gt;In the used-book trade the bottleneck isn't sales, it's data entry. Per book, staff used to spend &lt;strong&gt;3 to 5 minutes&lt;/strong&gt; photographing, transcribing (title, author, ISBN), researching prices, SEO-optimising, and uploading. At thousands of books per month that's enormous labour cost, &lt;strong&gt;before a single euro is earned.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: hardware meets AI
&lt;/h2&gt;

&lt;p&gt;An end-to-end pipeline that ties the physical scan process to multimodal AI and live APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smart scanning.&lt;/strong&gt; The book sits on a mat calibrated with ArUco markers. The webcam corrects perspective in real time, physically measures dimensions (for automatic shipping classes) and scans the barcode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI data extraction.&lt;/strong&gt; Two high-resolution scans (cover + back) go to &lt;code&gt;Gemini 2.5 Flash&lt;/code&gt;. A strict JSON schema extracts title, author, publisher, year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic pricing.&lt;/strong&gt; Cross-checks against the Google Books API, queries the eBay Browse API for live competitor listings, and calculates a competitive price with profit-margin protection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background upload.&lt;/strong&gt; One operator click, a background worker pushes the listing live via the eBay Trading API while the next book is already being scanned.&lt;/li&gt;
&lt;/ul&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%2Fuxzgi2zdtif2ud8d2un5.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%2Fuxzgi2zdtif2ud8d2un5.png" alt="Book Lister AI — before/after infographic" width="800" height="712"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Engineering highlights &amp;amp; fail-safe architecture
&lt;/h2&gt;

&lt;p&gt;Absolute reliability was the core focus, the app runs in warehouse operations; downtime directly costs money:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trust-but-verify on AI data.&lt;/strong&gt; Since LLMs occasionally hallucinate ISBNs, the architecture treats AI output as a hypothesis only. The ISBN is forcibly validated against the hardware barcode scan and a Google Books fuzzy match (&lt;code&gt;thefuzz&lt;/code&gt;). Bad data is blocked before it corrupts the listing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid computer vision.&lt;/strong&gt; Dual barcode-decoding system (&lt;code&gt;ZBar&lt;/code&gt; for clean, &lt;code&gt;zxing-cpp&lt;/code&gt; for damaged codes), maximum recognition rates even on old, scratched books.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thread-safe capture pipeline.&lt;/strong&gt; To prevent Windows &lt;code&gt;STATUS_HEAP_CORRUPTION&lt;/code&gt; crashes from competing camera restarts: strict VideoCapture ownership inside a dedicated, watchdog-monitored capture thread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-touch database migrations.&lt;/strong&gt; SQLite in WAL mode with automatic schema migration at app start. Every migration locked in by an explicit &lt;code&gt;pytest&lt;/code&gt; suite. Updates roll without customer intervention.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;+400% throughput.&lt;/strong&gt; From 3–5 minutes per book to under 30 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold start → first frame:&lt;/strong&gt; 2–4 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scan → price:&lt;/strong&gt; 4–6 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~6,450 LOC production code&lt;/strong&gt;, locked in by ~2,720 LOC of unit tests (260+ pytest tests) + GitHub Actions CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; 165 MB monolithic PyInstaller executable, double-click, done.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opencv</category>
      <category>gemini</category>
      <category>ebayapi</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>Multi-Process Browser Automation Framework</title>
      <dc:creator>Teske Systemtechnik</dc:creator>
      <pubDate>Tue, 16 Jun 2026 15:57:56 +0000</pubDate>
      <link>https://dev.to/teske-systemtechnik/multi-process-browser-automation-framework-38j6</link>
      <guid>https://dev.to/teske-systemtechnik/multi-process-browser-automation-framework-38j6</guid>
      <description>&lt;p&gt;17k LOC Python framework for parallel, fault-tolerant browser workflows, race-safe worker coordination, cross-process crash bridge, per-phase timeouts, and full operator UX through Streamlit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge
&lt;/h2&gt;

&lt;p&gt;A private client needed a permanently operational backend for browser-based automation workflows. The requirements were engineering-first from day one, not feature-first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple parallel browser sessions, cleanly isolated from each other.&lt;/li&gt;
&lt;li&gt;Subprocess architecture, a crash in one session must not take others down with it, and a hung workflow must not block the entire process tree.&lt;/li&gt;
&lt;li&gt;Full observability, every phase transition logs its status; every crash carries a unique phase marker.&lt;/li&gt;
&lt;li&gt;Operator UX through a dashboard rather than the CLI, the end user is the client, not the developer.&lt;/li&gt;
&lt;li&gt;100 % type hinting, clear layer separation, full pytest setup with GitHub Actions CI from day one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trivial that's not on Windows: parallel Chrome instances are a minefield of race conditions (port collisions on dynamic CDP allocation, profile locks in the user-data-dir, zombie processes on Streamlit restart). And a subprocess that dies &lt;em&gt;before&lt;/em&gt; its own crash handler can even run means, without a protection mechanism, a silently lost error report, exactly the class of bug that stays undetected in production for six weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The approach
&lt;/h2&gt;

&lt;p&gt;The result is a &lt;strong&gt;17,461 LOC Python codebase&lt;/strong&gt; across 25 cleanly modularized files, fully type-annotated, organized into three clearly decoupled layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Presentation → Streamlit UI Control → Scheduler + CLI orchestrator Execution → Browser workers (as subprocesses)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cross-layer communication runs exclusively through SQLite and atomically written JSON files; no worker ever imports another.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Race-safe multi-worker coordination.&lt;/strong&gt; With N parallel asyncio tasks, workers share an &lt;code&gt;asyncio.Lock&lt;/code&gt;-based round-robin: only one worker at a time runs the expensive discovery step, the others wait at the lock and pick up the result from a shared dict. Halves outgoing output without losing speed and avoids all workers duplicating the same operation in parallel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best-result aggregation with coordinated cancellation.&lt;/strong&gt; As soon as one worker hits the target result, an &lt;code&gt;asyncio.Event&lt;/code&gt; fires and a watchdog task calls &lt;code&gt;task.cancel()&lt;/code&gt; on all sibling tasks. Clean &lt;code&gt;CancelledError&lt;/code&gt; propagation instead of polling. Additionally, a class-global &lt;code&gt;_completed_jobs&lt;/code&gt; set suppresses late reports from the cancelled tasks, no notification spam, even when 10 siblings simultaneously walk their cleanup paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subprocess isolation at Windows level.&lt;/strong&gt; Each worker gets a fully isolated Chrome instance: race-safe port allocation via socket bind (&lt;code&gt;_PortLock&lt;/code&gt; holds the port reserved until Chrome takes it over, no TOCTOU race), its own user-data-dir (&lt;code&gt;chrome_&amp;lt;uuid&amp;gt;&lt;/code&gt;), its own crash dump path, its own CDP session. No shared resources, no lock conflicts between parallel sessions, no leaking browser state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDP-based auth configuration.&lt;/strong&gt; Instead of a classic Manifest V2 browser extension, auth configuration runs directly through Chrome DevTools Protocol via &lt;code&gt;Fetch.authRequired&lt;/code&gt;. Auth events propagate automatically onto popup pages via a &lt;code&gt;context.on("page", …)&lt;/code&gt; handler. A lean class replaces the traditional extension workaround with significantly less surface area.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-process crash file bridge.&lt;/strong&gt; Workers run as subprocesses, spawned by the scheduler via &lt;code&gt;subprocess.Popen&lt;/code&gt;. On a crash, the subprocess writes a structured JSON file to &lt;code&gt;data/crashes/job_&amp;lt;id&amp;gt;_&amp;lt;ts&amp;gt;.json&lt;/code&gt; AND attempts a direct Telegram notification in parallel. The scheduler reads the file back after subprocess exit, deduplicates against the already-sent notification, fills in missing reports, or quietly cleans up the file when everything was already reported. A global &lt;code&gt;sys.excepthook&lt;/code&gt; as last line of defence guarantees: &lt;em&gt;no&lt;/em&gt; crash gets lost, even if the subprocess dies so early that its own crash handler never runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-phase timeouts with live phase tracking.&lt;/strong&gt; Every workflow phase runs inside a dedicated &lt;code&gt;asyncio.timeout()&lt;/code&gt; block; every phase updates a central context object with its current sub-step. On a crash, the Telegram report says exactly which phase of which worker failed, not "somewhere in main()" but "4/6 add_step: concrete UI element X". Debugging time drops from "first scan the logs" to "jump straight to the function".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typed error hierarchy + swarm deduplication.&lt;/strong&gt; A dedicated exception class per failure mode (&lt;code&gt;ProxyError&lt;/code&gt;, &lt;code&gt;NavigateError&lt;/code&gt;, &lt;code&gt;SessionExpiredError&lt;/code&gt;, …), each with its own recovery policy (close the browser vs. leave it open, retry with a different proxy, hard fail). When 10 workers crash in parallel with the same root cause, &lt;code&gt;report_grouped_errors&lt;/code&gt; groups the messages by (exception type, first stack frame) and sends a single aggregated Telegram message with worker IDs and affected phases, no 10 redundant pings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite with WAL + BEGIN IMMEDIATE for race safety.&lt;/strong&gt; Counters and state in the tables are updated in read-modify-write transactions. With N parallel workers incrementing a counter simultaneously, naive UPDATE counter logic would increment by 1 instead of N, &lt;code&gt;BEGIN IMMEDIATE&lt;/code&gt; serializes the updates correctly and prevents the race at the SQLite level before it ever reaches Python. Plus WAL mode + 64 MB cache + 256 MB mmap for read performance under parallel write pressure. Auto-migrations on first connect, with pytest tests verifying every migration individually.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atomic file IPC.&lt;/strong&gt; All inter-process state files (job status snapshots, live state, run reports) are written atomically, write to &lt;code&gt;.tmp&lt;/code&gt;, then &lt;code&gt;os.rename()&lt;/code&gt;. No worker ever reads a half-written JSON file, even under concurrent access from multiple subprocesses. POSIX semantics, works on Windows too since &lt;code&gt;Path.replace()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Date-versioned logs.&lt;/strong&gt; &lt;code&gt;logs/&amp;lt;DD-MM-YYYY&amp;gt;/{chrome,traces,screenshots,…}/&lt;/code&gt;, every phase of every worker produces clearly attributed artefacts (Chrome stdout, Playwright trace, failure screenshot). On a production issue, &lt;code&gt;ls logs/23-03-2026/screenshots/&lt;/code&gt; finds the exact failure phase of every affected worker in five seconds, plus the full Playwright trace ready to replay in the browser trace viewer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streamlit operations UI with Windows hard cleanup.&lt;/strong&gt; Multi-page dashboard with service lifecycle (start/stop/restart of all subsystems), DB CRUD, live logs, EN/IT localization across 300+ string pairs. Streamlit is finicky on Windows, process-tree cleanup is not guaranteed on shutdown, children become zombies. Solved via a Windows Job Object with &lt;code&gt;JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE&lt;/code&gt;: every subprocess gets assigned to the job handle on spawn, and on Streamlit exit the OS automatically terminates all children cascadingly. Works even on hard-kill via Task Manager, no orphaned browser processes left behind.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralized Telegram reporter.&lt;/strong&gt; A single &lt;code&gt;ErrorReporter&lt;/code&gt; class as the single entry point for all notifications. Fire-and-forget by contract: never throws an exception, never blocks longer than the HTTP roundtrip, fails silently on connection errors (Windows 10054 ConnectionReset, …) and retries with a fresh session. Direct connection without proxy (&lt;code&gt;session.trust_env = False&lt;/code&gt;), so system proxy vars don't silently kill reports, plus global suppression logic against notification storms.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Engineering highlights &amp;amp; fail-safe architecture
&lt;/h2&gt;

&lt;p&gt;Reliability was absolutely non-negotiable, the system runs unattended 24/7 and the end user is not a developer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;9 pytest test suites with GitHub Actions CI.&lt;/strong&gt; Database migrations (idempotent, runnable multiple times), error reporter (260+ tests including suppression logic and crash-file roundtrip), coordination patterns, proxy layer, shared helpers, config resolution, all validated automatically on every push against Ubuntu Python 3.13. Migration bugs get caught before deploy, not at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict layer separation without circular imports.&lt;/strong&gt; Presentation layer imports only the control layer; control layer imports only the execution layer + utils. Every subprocess can be brought up standalone, without Streamlit even being installed, relevant for CI runs and debugging sessions without UI overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Singleton path resolution.&lt;/strong&gt; A &lt;code&gt;PATHS&lt;/code&gt; singleton class with auto-root detection (walks up looking for marker files like &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;requirements.txt&lt;/code&gt;) and automatic directory creation on property access. Code called from any working directory consistently finds the same absolute paths, not a single &lt;code&gt;os.path.join(os.path.dirname(__file__), …)&lt;/code&gt; in the entire codebase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataclass-first domain model.&lt;/strong&gt; All workflow inputs and outputs are dataclasses with type hints, validation logic, and clean &lt;code&gt;from_dict&lt;/code&gt;/&lt;code&gt;to_dict&lt;/code&gt; roundtrips. Clean interfaces between layers, IDE autocomplete works, refactorings raise compile-time errors instead of runtime AttributeErrors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test-first for IPC-critical components.&lt;/strong&gt; Crash file bridge, suppression gate, and aggregator logic are the riskiest spots, they run exactly when everything else is broken. The test suite is correspondingly dense: a custom &lt;code&gt;_clear_*&lt;/code&gt; fixture pattern resets class-global state between tests, every edge case (subprocess crashed BEFORE crash-file write, crash file without Telegram flag, Telegram after crash file, both in parallel) has an explicit test case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency-injection layer for tests.&lt;/strong&gt; Every external dependency (DB, Telegram, proxy provider, filesystem) sits behind a thin interface that can be swapped for an in-memory equivalent in test mode. The SQLite tests, however, run against a real SQLite DB in pytest's &lt;code&gt;tmp_path&lt;/code&gt;, not a mock, migration bugs would systematically not be detected by mocks.&lt;/li&gt;
&lt;/ul&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%2F9kacl751e1te9oyw668c.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%2F9kacl751e1te9oyw668c.png" alt="Multi-Process Browser-Automation Framework — codebase treemap" width="800" height="707"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Volume breakdown of the entire codebase across Presentation, Control, Execution Core, and Utility layers, the Execution Core (8,228 LOC) dominates visually as the largest block, while the utility layer shows modularity across 11 small helper modules.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;17,461 LOC of production Python&lt;/strong&gt; across 25 cleanly modularized files, clean layer separation, no circular imports, every stage standalone runnable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;260+ pytest tests with GitHub Actions CI&lt;/strong&gt; on every push, migration bugs, IPC race conditions, and suppression logic all get caught before deploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-process crash bridge&lt;/strong&gt;, no crash gets lost, even when a subprocess dies before its own crash handler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Race-safe coordination&lt;/strong&gt; across N parallel browser workers on Windows, no port collisions, no profile locks, no zombie processes on shutdown.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full operator UX through Streamlit&lt;/strong&gt;, the end client toggles services with one click, sees live status and live logs, without ever touching the CLI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modularly extensible&lt;/strong&gt;, new workflow types are a new module against the existing coordination and reporting infrastructure, without touching the core.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>asyncio</category>
      <category>playwright</category>
      <category>cdp</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>Legacy-DB Reverse Engineering &amp; Migration</title>
      <dc:creator>Teske Systemtechnik</dc:creator>
      <pubDate>Tue, 16 Jun 2026 15:57:44 +0000</pubDate>
      <link>https://dev.to/teske-systemtechnik/legacy-db-reverse-engineering-migration-1hf9</link>
      <guid>https://dev.to/teske-systemtechnik/legacy-db-reverse-engineering-migration-1hf9</guid>
      <description>&lt;p&gt;1.47 million parts liberated from a 1.2 GB password-protected manufacturer database and migrated into the client's new system, incl. 82,076 converted exploded-view drawings, validated to zero rule violations, fully auditable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge
&lt;/h2&gt;

&lt;p&gt;The client runs the after-market spare-parts business for an international Tier-1 construction-machinery manufacturer and holds a valid license for that manufacturer's maintenance database, a 1.2 GB password-protected MS Access file (&lt;code&gt;.mdb&lt;/code&gt;) from the early 2000s. The lawfully acquired data needs to migrate into the client's new ERP / inventory system. Three walls in the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First:&lt;/strong&gt; the database's original configuration parameters, specifically the access credentials, got lost internally over the years, and the legacy manufacturer tool will open the DB in the background but exports no raw data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second:&lt;/strong&gt; the schema. Around 30 interlocked tables with cryptic column names, n:m relations between catalogues and sales models, part names spread across three tables plus a language field, none of it readable without structural analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third:&lt;/strong&gt; roughly 82,000 exploded-view drawings in the obscure DjVu format of the early 2000s, which the new system doesn't render.&lt;/p&gt;

&lt;p&gt;And all of it at a scale (&lt;strong&gt;~1.9 million raw rows&lt;/strong&gt;) where Excel would silently truncate at 1,048,576 rows, effectively swallowing half the machinery fleet. The job: tear down all three walls and migrate the full, licensed dataset cleanly into the new system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The approach
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Forensic recovery of lost credentials.&lt;/strong&gt; Rather than treating the legacy maintenance tool as a black box, the client's installation environment is forensically analysed: a targeted scan across the entire local install directory (&lt;code&gt;.exe&lt;/code&gt;, &lt;code&gt;.dll&lt;/code&gt;, &lt;code&gt;.ini&lt;/code&gt;, &lt;code&gt;.cfg&lt;/code&gt;, &lt;code&gt;.xml&lt;/code&gt;) walks the configuration files and runtime artifacts looking for the classic indicators of persisted connection parameters, Jet OLEDB strings, PWD/UID references, &lt;code&gt;.mdw&lt;/code&gt; paths, with a context window around every match. The configuration leftovers reconstruct the original credentials, which the client owns by virtue of his license anyway. From that point on, the DB is readable directly at the data layer via &lt;code&gt;pyodbc&lt;/code&gt; + Microsoft Access Driver.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structural schema reconstruction.&lt;/strong&gt; The DB has no docs, no ER diagrams, only table names and column codes. A schema scanner pulls 10-row samples plus full column headers from each table and surfaces the relationships between the ~30 tables, which table holds replacement numbers, which holds image file IDs, where the language variants of part names live. That's the foundation for the later single-source join. Result: three laterally scattered sources for replacement-part numbers (one live mapping table plus two master-data tables with historical replacements), two for image files (with fallback chain), and one language-filtered source for English plain-text names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-shot cold storage in SQLite.&lt;/strong&gt; The entire Access DB is cloned in a single take into a local SQLite file, every column as TEXT (the safest defence against the inconsistent typing of the source), in 10k chunks via &lt;code&gt;fetchmany&lt;/code&gt;. Benefit: from then on every analysis runs locally, any number of times, without ODBC overhead, the 1.2 GB millstone becomes a 400 MB read-only source decoupled from the migration pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-table join as single source of truth.&lt;/strong&gt; A central query bundles all the schema knowledge into one statement. The parts catalogue joins with the catalogue-to-model mapping table and the sales-model table (because maintenance catalogues are named differently internally than the end-product models), the figures table (group / subgroup / image file via &lt;code&gt;COALESCE&lt;/code&gt; as a fallback chain), the language-filtered plain-text-name table, and a CTE that condenses all replacement-part relationships per part into a comma-separated list with &lt;code&gt;GROUP_CONCAT&lt;/code&gt;. Defensive LEFT JOINs with &lt;code&gt;IFNULL&lt;/code&gt; catch empty key fields, the target system needs a complete tuple per row, not a sparse one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-healing part names.&lt;/strong&gt; The raw data often had only the placeholder &lt;code&gt;"PART"&lt;/code&gt;, pure alphanumeric codes, or junk like &lt;code&gt;"(OPTIONAL)"&lt;/code&gt; in the "part name" field, values any modern ERP / shop system would flag as garbage immediately. Solution: an in-RAM dictionary built from the English-language master-data table maps part numbers to correct plain-text names. Heuristics (min length 3, not pure code string, not "PART" placeholder) decide when to look up, on a hit, the name gets healed, otherwise the record is marked &lt;code&gt;UNKNOWN_NAME_REQUIRES_CHECK&lt;/code&gt; and hard-filtered in a later stage. The healing step alone rescues tens of thousands of rows from the trash filter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DjVu → JPG pipeline with multithreading.&lt;/strong&gt; ~82,000 exploded-view drawings sat in the obscure DjVu format, a format the new system can't render and for which there's no modern standard library. Pipeline: &lt;code&gt;ddjvu&lt;/code&gt; (DjVuLibre) converts each file into a temporary PDF, PyMuPDF (&lt;code&gt;fitz&lt;/code&gt;) renders the first page at 150 DPI in greyscale as JPG (massive disk-space win without legibility loss), the temp PDF is deleted immediately. &lt;code&gt;ThreadPoolExecutor&lt;/code&gt; runs with &lt;code&gt;os.cpu_count()&lt;/code&gt; workers. Idempotent skip logic (existing targets get skipped) makes the run re-runnable, a crash at file 50,000 doesn't cost 50,000 reconversions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System-aware hyperlinks.&lt;/strong&gt; Instead of bare paths, every image cell is written as &lt;code&gt;=HYPERLINK("…\&amp;lt;path&amp;gt;.jpg", "&amp;lt;name&amp;gt;.jpg")&lt;/code&gt;, a format the target system understands directly as a clickable image link. A recursive index of all JPGs (filename → relative path) resolves the images before write; not-found images get defensively flagged as &lt;code&gt;MISSING_JPG: &amp;lt;stem&amp;gt;&lt;/code&gt; rather than silently polluting the column or aborting the migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterative cleanup stages with audit trail.&lt;/strong&gt; Raw → v2 (hyperlinks injected) → Pristine_v2 (UNKNOWN names + manually blocked regional special variants removed) → Pristine_v3 (string cleanup: semicolons, leading special chars, double whitespace) → Final_Delivered (replacement-part numbers re-loaded from both master-data sources, deduplicated) → Perfect (three last problem rows with polluted SubGroup removed). Every stage is its own file and its own script, debuggable, reproducible, with a clear audit trail. When someone later asks "why isn't row X in the new system?", there's an answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict-mode validation against the target schema.&lt;/strong&gt; A dedicated validator checks the final CSV against a 5-rule schema contract that mirrors 1:1 what the target system accepts: header (exactly 10 columns in fixed order), required fields (catalogue + part number set), no illegal control characters (regex &lt;code&gt;\x00-\x1f&lt;/code&gt;), part-number format (no parentheses), part-name format (uppercase, no leading special character, no UNKNOWN_NAME), Image_File format (&lt;code&gt;=HYPERLINK&lt;/code&gt; + &lt;code&gt;.jpg&lt;/code&gt; or explicit &lt;code&gt;MISSING_JPG&lt;/code&gt;). Result after several iterations: &lt;strong&gt;1,473,210 rows, zero rule violations&lt;/strong&gt;. Every violation lands in a log file with row number + catalogue + part number + reason, surgical, not "something is broken".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust by transparency.&lt;/strong&gt; Two separate reports ship alongside the data. &lt;em&gt;Coverage report:&lt;/em&gt; all 386 master catalogues from the source DB with mapping to sales model and row count in the final CSV, the client sees, per catalogue, whether and how many parts were migrated into the new system. &lt;em&gt;Trash analysis:&lt;/em&gt; all 470,276 filtered-out rows with reason, missing English name, manually blocked regional special variant, part number under four characters, other cleanup filter. The client gets not just the migration but every single filter decision documented.&lt;/li&gt;
&lt;/ul&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%2Fzksofbyh1yo4jq0o220l.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%2Fzksofbyh1yo4jq0o220l.png" alt="Legacy DB Reverse Engineering &amp;amp; Migration — infographic" width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diagram shows the full migration flow: from forensic credential recovery through the opened 1.2 GB MS Access source database, the SQLite clone, the central multi-table join query, and the parallel DjVu-to-JPG conversion pipeline, all the way to hyperlink injection and the strict-mode validation against the new target system's schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vendor lock-in lifted.&lt;/strong&gt; A 1.2 GB legacy database the client had legally licensed but could no longer practically use was forensically opened, its schema structurally reconstructed, and the full dataset migrated into his ownership, without further dependence on the legacy maintenance tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1,473,210 migrated parts records&lt;/strong&gt; across 339 sales models (from 386 master catalogues including n:1 mappings, where one catalogue covers several model variants), ready for ingestion into the new system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~82,000 original exploded-view drawings&lt;/strong&gt; converted from old DjVu format into modern JPG and referenced via &lt;code&gt;=HYPERLINK&lt;/code&gt;, defensively flagged where conversion failed, rather than blocking delivery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero rule violations&lt;/strong&gt; in the final CSV against a 5-rule strict-mode validation contract, the target system's acceptance criterion was binary, the result is binary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1.2 GB legacy MDB → 400 MB SQLite&lt;/strong&gt; (cold storage) → &lt;strong&gt;274 MB CSV&lt;/strong&gt; (migration delivery). Full data sovereignty beyond the proprietary manufacturer tool, in a format every modern ERP, shop, or BI system understands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full audit trail&lt;/strong&gt; over every single one of the 470,276 dropped raw rows plus over each of the 386 catalogues, not a black-box ETL but a migration contract the client can trace row by row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excel hard limit dodged.&lt;/strong&gt; The CSV stays streamable, the new system ingests sequentially, nothing gets silently cut off at 1,048,576 rows the way a naive Excel re-save would have done.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>pyodbc</category>
      <category>msaccess</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>Microsoft Shopping Feed Pipeline</title>
      <dc:creator>Teske Systemtechnik</dc:creator>
      <pubDate>Tue, 16 Jun 2026 15:57:31 +0000</pubDate>
      <link>https://dev.to/teske-systemtechnik/microsoft-shopping-feed-pipeline-563f</link>
      <guid>https://dev.to/teske-systemtechnik/microsoft-shopping-feed-pipeline-563f</guid>
      <description>&lt;p&gt;Fully automated daily sync of high-volume affiliate feeds (Connexity, Shopping24) into Microsoft Merchant Center, OOM-safe, chunked upload, 100 % compliant.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Blender Networks Inc.&lt;/strong&gt; runs a large price-comparison portal whose entire monetization is exclusively built on Microsoft Advertising Product Listing Ads (PLAs). The previous in-house feed solution was unstable and had to be replaced.&lt;/p&gt;

&lt;p&gt;The complication came from the heterogeneous third-party sources: Connexity delivers zipped JSON bundles via an API index, Shopping24 (S24) provides a CSV master file plus fragment updates over FTP. Both formats had to be flawlessly translated into Microsoft Merchant Center's strictly defined TSV schema. Every format error, every "item drop" = direct revenue loss.&lt;/p&gt;

&lt;p&gt;Additional difficulty: Connexity data routinely exceeds 2 GB per publisher account, the mapped output runs into double-digit gigabytes, and the full daily upload into Microsoft Merchant Center sits at around 200 GB. Naive in-memory processing was off the table, the pipeline had to stay OOM-safe even on modest hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  The approach
&lt;/h2&gt;

&lt;p&gt;Three heterogeneous sources, one unified pipeline. The architecture follows a strict three-stage model, &lt;strong&gt;Ingest → Map → Upload&lt;/strong&gt;, where each affiliate network sits behind the same interface but internally taps its own stack (see pipeline diagram below).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Streaming-first ingestion.&lt;/strong&gt; A Python pipeline using &lt;code&gt;ijson&lt;/code&gt; parses the zipped JSON bundles item-by-item directly off the stream instead of deserializing the whole document into RAM. Memory usage stays constant regardless of feed size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disk-backed deduplication.&lt;/strong&gt; A SQLite table with &lt;code&gt;PRAGMA&lt;/code&gt; tuning handles cross-account deduplication on disk. Multi-million-item feeds stay clean without blowing up the heap, the dedup state survives even on abort, ready for inspection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict mapping to the MS spec.&lt;/strong&gt; Every source record gets deterministically mapped onto the required &lt;code&gt;id/title/description/link/image_link/price/...&lt;/code&gt; schema. Validation logic filters unusable brand strings (purely numeric, too long, too many words), checks GTINs for valid lengths (8/12/13/14) and sets &lt;code&gt;identifier_exists&lt;/code&gt; consistently, no more "partial identifier" warnings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15 GB chunking &amp;amp; chunk-aware upload.&lt;/strong&gt; Mapping outputs are auto-split at 15 GB into &lt;code&gt;_0.txt&lt;/code&gt;, &lt;code&gt;_1.txt&lt;/code&gt;, … to match the Microsoft upload limit. The uploader detects these chunks via pattern matching and numbers them remotely correctly (&lt;code&gt;TipDigest_US_0.txt&lt;/code&gt;, &lt;code&gt;TipDigest_US_1.txt&lt;/code&gt;, …).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-speed SFTP via LFTP.&lt;/strong&gt; Instead of Python SFTP libraries (paramiko), the system LFTP binary is driven with enlarged TCP socket buffer and a reconnect strategy. Significantly faster and more stable than any Python implementation on multi-GB files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-account orchestration.&lt;/strong&gt; Multiple Connexity publishers and multiple Merchant Center accounts (US/DE) are processed in parallel; each account writes to its own output path and is correctly routed via a store-mapping config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilient daily sync.&lt;/strong&gt; A cron lockfile prevents overlapping double-runs. Pipeline stages can be toggled individually (&lt;code&gt;--skip-ingest&lt;/code&gt;, &lt;code&gt;--skip-map&lt;/code&gt;, &lt;code&gt;--skip-upload&lt;/code&gt;) for targeted re-runs after partial failures, without re-pulling the entire 2 GB download.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proactive error handling &amp;amp; diagnostics.&lt;/strong&gt; Hybrid logging (Rich console with emojis for humans + RotatingFileHandler for the machine), per-stage execution-report table, per-account isolation. A single truncated JSON stream doesn't stop the overall run, errors get logged locally and the run continues with the rest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modular architecture.&lt;/strong&gt; Each affiliate network lives in its own module (&lt;code&gt;ingest_*.py&lt;/code&gt; + &lt;code&gt;mapper_*.py&lt;/code&gt;) behind a unified pipeline interface. A third source (Kelkoo) was added without touching Connexity or S24, proof the abstraction holds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acceptance criterion.&lt;/strong&gt; The hard acceptance bar (5+ consecutive days of error-free automation) was passed on the first attempt, secured by clean module boundaries and consistent validation layering.&lt;/li&gt;
&lt;/ul&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%2Fqb1wsqt45s8epu5gf268.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%2Fqb1wsqt45s8epu5gf268.png" alt="Microsoft Shopping Feed Pipeline — infographic" width="800" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diagram shows the full data flow: from heterogeneous source systems (zipped JSON streams, REST API, FTP dumps), through the OOM-safe ingest layer, the validation-driven mapping with 15 GB chunking, all the way to the chunk-aware LFTP upload into Microsoft Merchant Center.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100 % feed compliance&lt;/strong&gt;, no more disapprovals from format errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero errors&lt;/strong&gt; in the daily sync, acceptance criterion passed first try.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ROI protected&lt;/strong&gt;, no revenue loss from broken feed updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OOM-safe on multi-GB feeds&lt;/strong&gt;, pipeline runs on modest hardware without memory pressure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modularly extensible&lt;/strong&gt;, new affiliate networks integrate without touching the core.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>sqlite</category>
      <category>lftp</category>
      <category>msmerchantcenter</category>
    </item>
    <item>
      <title>Math Engine, eval()-free expression interpreter for Python</title>
      <dc:creator>Teske Systemtechnik</dc:creator>
      <pubDate>Tue, 16 Jun 2026 15:57:16 +0000</pubDate>
      <link>https://dev.to/teske-systemtechnik/math-engine-eval-free-expression-interpreter-for-python-426p</link>
      <guid>https://dev.to/teske-systemtechnik/math-engine-eval-free-expression-interpreter-for-python-426p</guid>
      <description>&lt;p&gt;A safe evaluation engine for mathematical expressions, built from scratch: tokenizer, recursive-descent parser, AST, linear equation solver and a type-safe output system, entirely without Python's eval(). Live on PyPI, 399 tests, 90% coverage, green across five Python versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge
&lt;/h2&gt;

&lt;p&gt;The obvious way to evaluate an expression like &lt;code&gt;3 + 4 * 2&lt;/code&gt; in Python is a single line: &lt;code&gt;eval("3 + 4 * 2")&lt;/code&gt;. That very line is the problem. &lt;code&gt;eval()&lt;/code&gt; executes arbitrary Python code, a string disguised as numeric input such as &lt;code&gt;__import__('os').system('rm -rf …')&lt;/code&gt; runs without complaint. For any application that takes expressions from a file, a form field, an API or a configuration string, &lt;code&gt;eval()&lt;/code&gt; is therefore a direct code-execution vector, not a calculator.&lt;/p&gt;

&lt;p&gt;The second, quieter defect is correctness. &lt;code&gt;eval()&lt;/code&gt; and Python's &lt;code&gt;float&lt;/code&gt; compute in binary: &lt;code&gt;0.1 + 0.2&lt;/code&gt; yields &lt;code&gt;0.30000000000000004&lt;/code&gt;, &lt;code&gt;1/3&lt;/code&gt; is truncated, large integers tip over into scientific notation. For a calculator, a financial formula or an educational context, that is not "almost right", it is wrong.&lt;/p&gt;

&lt;p&gt;The third defect is diagnostics. Hand &lt;code&gt;eval()&lt;/code&gt; a broken expression and you get a Python traceback at an internal line number, not the spot in the input string where the problem sits. For a tool that processes end-user input, that is useless.&lt;/p&gt;

&lt;p&gt;The task, then: a complete evaluation engine from scratch that &lt;strong&gt;(1) never executes foreign code, (2) computes exactly rather than binary-approximately, (3) pinpoints every error to the exact character&lt;/strong&gt;, and (4) does all of that at library quality, tested, documented, versioned and installable from PyPI. Not a weekend parser, but an engine with the discipline of a small compiler.&lt;/p&gt;

&lt;h2&gt;
  
  
  The implementation
&lt;/h2&gt;

&lt;h2&gt;
  
  
  eval()-free by construction
&lt;/h2&gt;

&lt;p&gt;The entire library never calls Python's &lt;code&gt;eval()&lt;/code&gt;, &lt;code&gt;exec()&lt;/code&gt; or &lt;code&gt;compile()&lt;/code&gt; anywhere, this is not an after-the-fact filter but the architecture itself. Input strings pass through a closed pipeline (Input → Tokenizer → Parser → Evaluator/Solver → Formatter → Output Converter), whose alphabet is a finite set of numbers, operators, parentheses and a whitelist of function names. At worst, an attacker-controlled string can trigger a typed &lt;code&gt;MathError&lt;/code&gt;, never code execution. Even the single place that parses a user-supplied data structure uses the safe &lt;code&gt;ast.literal_eval&lt;/code&gt;, which accepts literals only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recursive-descent parser with a 10-level precedence chain
&lt;/h2&gt;

&lt;p&gt;Operator precedence is not hacked in via regex or a shunting-yard table, but encoded structurally as ten nested parser closures, each with exactly one precedence level: from &lt;code&gt;parse_gleichung&lt;/code&gt; (=) through bitwise operators, shift operations, sum and term, down to &lt;code&gt;parse_power&lt;/code&gt; (&lt;strong&gt;) and &lt;code&gt;parse_factor&lt;/code&gt;. Left- vs. right-associativity falls out of the structure: whatever consumes in a loop is left-associative (&lt;code&gt;a - b - c = (a - b) - c&lt;/code&gt;); &lt;code&gt;parse_power&lt;/code&gt; recurses to the right and makes `&lt;/strong&gt;&lt;code&gt; correctly right-associative. A deliberate decision: &lt;/code&gt;^` is bitwise XOR, not exponentiation, exactly as in C and Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decimal precision with dynamic scaling
&lt;/h2&gt;

&lt;p&gt;Every number is a &lt;code&gt;decimal.Decimal&lt;/code&gt; from the tokenizer through to the output, never a &lt;code&gt;float&lt;/code&gt;, which is why &lt;code&gt;0.1 + 0.2&lt;/code&gt; is exactly &lt;code&gt;0.3&lt;/code&gt;. The precision of the Decimal context is determined anew for each calculation (between 100 and 10,000 digits, depending on the input), plus a hard input ceiling of 20,000 digits. The point: a long result is never silently truncated, a short one never wastes memory. Exactly the class of correctness that float-based calculators quietly lose here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Character-exact error positioning
&lt;/h2&gt;

&lt;p&gt;Alongside the token list, the tokenizer keeps a span list: for each token a &lt;code&gt;(start_col, end_col, original_text)&lt;/code&gt; triple. Every AST node and every &lt;code&gt;MathError&lt;/code&gt; carries &lt;code&gt;position_start&lt;/code&gt; / &lt;code&gt;position_end&lt;/code&gt;. The payoff: an error does not say "syntax error somewhere", it points at the exact character. This bookkeeping is the reason the engine is debuggable across an API. Via a single setting (&lt;code&gt;readable_error&lt;/code&gt;), the same position info switches between two contracts: typed exceptions for the library, a visual diagnostic with a &lt;code&gt;^&lt;/code&gt; pointer under the faulty column for the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed, catalogued error system
&lt;/h2&gt;

&lt;p&gt;A base class &lt;code&gt;MathError&lt;/code&gt; plus exactly seven domain subclasses, including a catalogue of 78 unique, four-digit error codes across nine families. The digits are structured: first digit = family, second = component, the rest = sequence number. Code &lt;code&gt;3008&lt;/code&gt; therefore means "Calculator family, core parser, more than one '.' in a number". These codes are deliberately never renumbered, they are a contract toward the UI and external log parsers. The public &lt;code&gt;calculate()&lt;/code&gt; function wraps the whole pipeline in a layered &lt;code&gt;except&lt;/code&gt; block, so that no raw &lt;code&gt;ZeroDivisionError&lt;/code&gt; or &lt;code&gt;ValueError&lt;/code&gt; ever reaches the caller, everything lands typed in the &lt;code&gt;MathError&lt;/code&gt; hierarchy.&lt;/p&gt;

&lt;h2&gt;
  
  
  More than a calculator
&lt;/h2&gt;

&lt;p&gt;Two further capabilities sit on the same AST. If an expression contains an &lt;code&gt;=&lt;/code&gt; and a variable, the engine solves the linear equation symbolically: each node returns a &lt;code&gt;(factor, constant)&lt;/code&gt; pair, the solver brings both sides into the form &lt;code&gt;A·x + B = C·x + D&lt;/code&gt; and computes &lt;code&gt;x&lt;/code&gt;. Non-linearity is caught structurally (variable·variable, variable in the denominator, variable in the exponent), degenerate cases named cleanly ("No Solution", "Inf. Solutions"). On top of that, a programmer's-calculator mode with fixed word width (8/16/32/64 bit), two's complement and bitwise operators, so that &lt;code&gt;127 + 1&lt;/code&gt; in 8-bit signed mode correctly overflows to &lt;code&gt;-128&lt;/code&gt;. A prefix-driven output system (&lt;code&gt;dec:&lt;/code&gt;, &lt;code&gt;int:&lt;/code&gt;, &lt;code&gt;hex:&lt;/code&gt; …) determines the Python return type and refuses lossy conversions instead of silently truncating.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engineering highlights &amp;amp; test discipline
&lt;/h2&gt;

&lt;p&gt;Reliability was not a feature here but the reason for being, a safe engine you cannot trust is useless.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;399 pytest tests, 90% coverage.&lt;/strong&gt; The suite was grown from 234 to 399 tests, coverage raised from 69% to 90%. A dedicated helper &lt;code&gt;assert_error_location(expr, code, start, end)&lt;/code&gt; checks not only that an expression fails, but that it fails with the exact error code at the exact character position, the position data is itself part of the test contract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI matrix across five Python versions.&lt;/strong&gt; GitHub Actions runs the full suite on every push and pull request against Python 3.8, 3.9, 3.10, 3.11 and 3.12; the coverage report goes to Codecov. Dead and work-in-progress code is honestly excluded from coverage rather than padding the number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean layering, broken cycles.&lt;/strong&gt; Clearly separated modules (calculator / utility / cli / plugins); circular imports are resolved via deliberately deferred imports. Every class and function carries a docstring, a standalone &lt;code&gt;DOCUMENTATION.md&lt;/code&gt; captures the architecture, the full API, parser internals and the complete error-code catalogue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Library quality on delivery.&lt;/strong&gt; Pure-Python wheel, three console entry points, exactly two runtime dependencies (&lt;code&gt;rich&lt;/code&gt;, &lt;code&gt;prompt_toolkit&lt;/code&gt;). The interactive REPL offers persistent history and tab completion. Six minor releases (0.1.0 → 0.6.7) in roughly five months, throughout following Semantic Versioning.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live on PyPI&lt;/strong&gt; as &lt;code&gt;math-engine&lt;/code&gt;, installable via &lt;code&gt;pip install math-engine&lt;/code&gt;, MIT-licensed, pure-Python wheel for Python 3.8+, with three console commands out of the box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;eval()-free by construction.&lt;/strong&gt; Closed input alphabet, the worst case of a hostile input is a typed error, never code execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;399 tests, 90% coverage&lt;/strong&gt;, green across five Python versions (3.8–3.12) on every push, with test cases that pin exact error codes to exact character positions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact Decimal arithmetic&lt;/strong&gt; with adaptive precision (100 … 10,000 digits) and a 20,000-digit input limit, no silent float drift, no silent truncation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Character-exact diagnostics:&lt;/strong&gt; 78 error codes across nine families, an eight-class typed exception hierarchy, &lt;code&gt;position_start&lt;/code&gt; / &lt;code&gt;position_end&lt;/code&gt; on every error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roughly 4,200 LOC of production code&lt;/strong&gt; in cleanly layered modules, backed by ~2,400 LOC of tests, plus full technical documentation and a catalogued error system.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ast</category>
      <category>recursivedescentparser</category>
    </item>
  </channel>
</rss>
