  Package Upgrade Plan
  
  The Core Problem in One Line

  requirements.txt has ~310 packages because 130+ are mypy type stubs (types-*) that were almost certainly bulk-installed by accident,
  plus ~80 more packages that are never imported anywhere in the code.

  ---
  Phase 1 — Prune (do this first, before any upgrades)

  Goal: Get to a minimal, accurate requirements file. Easier to reason about, faster to install, smaller attack surface.

  Packages to remove — confirmed unused by import scan:

  Category: All types-* stubs
  Packages: ~130 packages (lines 169–299) — mypy stubs, nothing in the project uses mypy              
  ────────────────────────────────────────
  Category: Linting/type tools
  Packages: pylint, mypy, mypy-extensions, astroid, isort, mccabe, dill, lazy-object-proxy, wrapt, logilab-common
  ────────────────────────────────────────
  Category: Test tools (belong in test requirements only)
  Packages: pytest, responses, pluggy, iniconfig
  ────────────────────────────────────────
  Category: AWS SDK
  Packages: boto3, botocore, s3transfer, jmespath
  ────────────────────────────────────────
  Category: Scientific stack
  Packages: matplotlib, numpy, scipy, contourpy, cycler, fonttools, kiwisolver
  ────────────────────────────────────────
  Category: GUI / IDE
  Packages: thonny, arandr, PyQt5, PyQt5-sip, pgzero, pygame
  ────────────────────────────────────────
  Category: Camera / video
  Packages: picamera2, pidng, simplejpeg, av, v4l2-python3
  ────────────────────────────────────────
  Category: Printing
  Packages: cupshelpers, pycups, brother-ql
  ────────────────────────────────────────
  Category: Networking not used
  Packages: pyserial, pysmbc, oauthlib, requests-oauthlib, twython, ssh-import-id, maxminddb, ifaddr
  ────────────────────────────────────────
  Category: Dev tools
  Packages: pipreqs, yarg, meson
  ────────────────────────────────────────
  Category: Clearly accidental
  Packages: self (the package literally named "self"), roman, pyinotify, sysv-ipc, libevdev, future, send2trash, pexpect, ptyprocess
  ────────────────────────────────────────
  Category: Doc / markup
  Packages: Markdown, docutils, docopt, Babel, lxml, beautifulsoup4, html5lib, soupsieve
  ────────────────────────────────────────
  Category: DB migrations
  Packages: alembic, Mako
  ────────────────────────────────────────
  Category: Misc unused
  Packages: python-dotenv, python-prctl, reportlab, PyYAML, simplejson, toml, tomli, tomlkit, pypng, piexif, olefile, packbits,
    more-itertools, PyOpenSSL, PyAudio, pyOpenSSL, PyGObject, pycairo, exceptiongroup

  After pruning, the actual runtime dependencies come down to roughly 35–40 packages — much easier to reason about.

  Validation after Phase 1: Run the full unit test suite. It should pass unchanged since we haven't modified any versions.

  ---
  Phase 2 — Upgrade in Risk-Ordered Batches
  
  Upgrade smallest-surface, least-breaking packages first. Run the test suite after each batch. Each batch is its own commit so you can
  bisect if something breaks.

  Batch A — Pure utilities (lowest risk, stable APIs):
  certifi, charset-normalizer, idna, urllib3, six, pycparser, cffi, packaging, python-dateutil, distro, sanitize-filename, qrcode,
  Pygments, colorama

  Batch B — Core networking and system libs:
  requests, psutil, netifaces, getmac, upnpclient, pystemd

  Batch C — Cryptography:
  cryptography — upgrade carefully, review the changelog for deprecated API removals. The code uses hazmat primitives which do
  occasionally change.

  Batch D — Storage (dataset + SQLAlchemy together):
  These must be upgraded together. dataset pins its own SQLAlchemy version constraint — let pip resolve it. Don't force SQLAlchemy
  independently.

  Batch E — Pillow:
  Separate batch because Pillow has had several API removals across major versions (e.g. removed constants, renamed modes). The LCD code
   uses it heavily.

  Batch F — paho-mqtt (highest risk):
  paho-mqtt 2.x has breaking changes from 1.x — the on_connect callback signature changed, CallbackAPIVersion is now required, and
  loop_start/loop_stop behavior changed. The entire MQTT layer in pproxy.py needs to be reviewed against the 2.x migration guide before
  upgrading. Do this batch last and test on a real Pod.

  Batch G — Flask + Flask-API:
  Flask-API is largely unmaintained. Before upgrading Flask, check whether Flask-API still works against the target Flask version. The
  local web server (local_server/api.py) is the thing at risk here.

  ---
  Phase 3 — Tooling to Help

  - pip list --outdated to see current vs. latest for each package
  - pip-audit (or the existing safety pre-commit hook) to flag packages with known CVEs — use this to prioritize which packages in a
  batch matter most
  - pip install --dry-run to preview dependency resolution conflicts before committing
  - For Batch F (paho-mqtt), the official migration guide is the reference

  ---
  What This Doesn't Cover
  
  The unit tests mock out most hardware and external calls, so they verify code structure but not runtime behavior on a real Pod. For
  Batches E–G (Pillow, paho-mqtt, Flask), a smoke test on a dev Pod after upgrading is worth doing — specifically: LCD display renders,
  MQTT connects and receives a heartbeat, and the local web API responds.

  ---
  Summary Order

  Phase 1: Prune → test suite green
  Batch A: Pure utilities → test suite
  Batch B: Networking/system → test suite
  Batch C: cryptography → test suite
  Batch D: dataset + SQLAlchemy → test suite
  Batch E: Pillow → test suite
  Batch F: paho-mqtt → test suite + Pod smoke test
  Batch G: Flask + Flask-API → test suite + Pod smoke test

  Each batch is a separate PR to dev. That way if something breaks in production you can identify the exact package and revert
  surgically.
