Skip to content

Releases: Devathmaj/VoucherBot

VoucherBot v2.1.0

Choose a tag to compare

@Devathmaj Devathmaj released this 17 Aug 12:31

v2.1.0 — AI-Assisted Deduplication

Deduplication is now more resilient than ever. VoucherBot no longer relies on a purely deterministic score to decide whether two promotions are the same real-world deal — a reasoning model now reviews ambiguous cases, and a new sweeping job reconciles duplicates that were previously missed.

What changed since v2.0.1

Smarter merge decisions

Previously, merge decisions were made by a weighted score over structured fields with hard thresholds. Semantically-similar promotions (different wording, tracking URLs, discount formatting) were often misjudged — causing duplicate events or false merges that destroyed provenance.

Now:

  • qwen-backed matching at ingestion. Candidates that pass a deterministic gate are submitted to the qwen reasoning model, which judges whether two promotions are the same real-world offer. Verdicts map to auto-merge (confidence ≥ 0.8), possible-match (≥ 0.5), or a new event — with a match_reason audit field recording the decision.
  • Resilient fallback. If no Groq key is configured or the model is unavailable, the legacy deterministic scoring path runs unchanged. Parse or model failure never fails the pipeline — it degrades gracefully.

Retroactive consolidation sweep

Two posts for the same promotion processed on different sweeps each used to create their own canonical event, and nothing reconciled them later. That is now fixed:

  • A new scheduled job groups active events by a cheap identity signal (registration URL, voucher code, vendor), gates pairs by the deterministic score, and has qwen confirm whether each pair is the same promotion.
  • Survivor keeps the event with more posts (ties keep the older); the absorbed event is archived, its posts re-pointed, and audit entries appended to both merge logs.
  • The sweep is throttled, cross-instance locked via a Postgres advisory lock, bounded per sweep, and absorption is never double-applied — so it is safe to run on the free Render plan.

Documentation & README

  • Documentation reconciled with the current codebase: API surface, data models, settings tables, module maps, schema revision, and test counts / line references in docs/.
  • README now points to the companion Notification-Bot repository that contains the Discord/Telegram bot code.

Behavior notes

  • New / possible / weakly-confirmed items still notify and share the same qwen judgment; confirmed auto-merges stay silent.
  • When GROQ_API_KEY is unset, everything runs the legacy deterministic path exactly as before.

Testing

  • 418 passed, 15 skipped (12 new test files covering the AI matcher and consolidation sweep).
  • ruff check + ruff format --check clean, mypy --strict clean (81 files).

Full Changelog: v2.0.1...v2.1.0

VoucherBot v2.0.1

Choose a tag to compare

@Devathmaj Devathmaj released this 16 Aug 08:12

v2.0.1 — Bug Fixes

Patch release fixing the two issues that broke the scheduler after deployment.

What changed since v2.0.0

  • Startup migration crash fixed. o9p8q7r6s5t4 no longer fails with
    "column ... already exists" when the database was originally created via
    create_all. The migration is now idempotent — it only adds created_at /
    updated_at to keywords and pipeline_lock when the columns are missing.
  • Scheduler greenlet_spawn has not been called error fixed.
    • The dispatcher success path refreshes the source ORM instance after the
      pipeline's commit before reading it.
    • The failure path now passes pre-captured source_id / source_name
      scalars into _mark_unrecoverable instead of touching the expired ORM
      instance after session.rollback() — accessing any attribute (even the
      primary key) of an expired object raises MissingGreenlet under async
      SQLAlchemy 2.0.51, so sources that 401/403/404 (e.g. a dead RSS feed like
      the TechTarget redirect) were crashing the loop instead of being disabled.
    • Breaking feeds are now properly disabled instead of looping errors every ~60s.

Testing

  • 372 passed, 15 skipped.
  • New regression test: tests/test_dispatcher.py::test_dispatch_tick_disables_unrecoverable_source
    guards that the unrecoverable path passes scalars (not the ORM instance).
  • ruff check and ruff format --check pass.

Full Changelog: v2.0.0...v2.0.1

VoucherBot v2.0.0

Choose a tag to compare

@Devathmaj Devathmaj released this 16 Aug 07:01

v2.0.0 — VoucherBot Notifications: No Self-Hosting Required 🎉

This release is all about one thing: you no longer need to set up or host anything yourself.

We heard your requests — so VoucherBot can now be added directly to your Discord or Telegram, and it will alert you the moment a certification voucher is found. No cloud setup, no .env files, no deployment, no code.

Head over to voucherbot-preview.pages.dev/#notifications to learn all about it and get set up in minutes.


✨ Highlights

🚀 New: Bot Notifications — No Setup Required

  • VoucherBot now POSTs voucher alerts to a bot server (Discord / Telegram) the moment a legitimate voucher is detected — at the same time it sends the email alert (for email, self hosting is required).
  • No self-hosting needed: instead of forking the repo, filling in five API keys, and deploying to Render, you just add the bot to your server and get notified.
  • The webhook is authenticated with a shared secret (Authorization: Bearer ...) configured via the new NOTIFICATION_BOT_SERVER_URL and WEBHOOK_SECRET settings.
  • Best-effort by design: a bot webhook failure never fails the ingestion pipeline, never blocks the email outbox, and never affects the database transaction — the webhook fires only after the outbox is committed.
  • Notifications are sent for new offer posts and possible matches (never for auto-merges or routine updates), matching the existing email behavior.
  • Config options documented in .env.example and the Configuration reference.

🤖 AI Model Change & New 50/50 Split

  • Removed llama-3.3-70b-versatile from the Groq rotation.
  • Primary extraction is now split evenly 50/50 across the two gpt-oss models: openai/gpt-oss-20b and openai/gpt-oss-120b.
  • qwen/qwen3.6-27b joins as a dedicated reasoning tier: any gpt-oss result with confidence below 0.6 is re-analyzed by qwen before the pipeline sees it — catching borderline posts that a single pass might misjudge. If qwen is unavailable or fails, the original result is kept, so confidence never drops.
  • Qwen runs a tuned profile (2048-token completion budget, hidden reasoning, forced JSON output, temp 0.6) that also resolves the failed_generation validation error we hit with the previous 1024-token budget.
  • DB safety preserved: qwen escalation resolves before the pipeline commits, so no post is ever written with an un-escalated answer.

What changed since v1.1.1

Pull requests and commits landing in this release:


Testing

  • Full suite: 371 passed, 15 skipped.
  • All CI quality gates pass: ruff format --check, ruff check, mypy voucherbot tests, and the full pytest suite.
  • New tests: tests/test_bot_notification.py (payload shape, auth, skip-when-unconfigured, HTTP failure) and analyzer escalation tests (routes low confidence to qwen, keeps high-confidence results, qwen unavailable / fails).
  • All new AI calls are mocked — no live API traffic in tests.

Full Changelog: v1.1.1...v2.0.0

VoucherBot v1.1.1

Choose a tag to compare

@Devathmaj Devathmaj released this 14 Aug 11:27

v1.1.1 — Fix startup crash for existing databases

Patch release: restores a deleted Alembic migration that broke alembic upgrade head at app startup on any database stamped before v1.1.0.


What changed since v1.1.0

One pull request landed since v1.1.0:


Bug Fixes

  • Fixed startup crash on existing databases (PR #17) — v1.1.0 deleted a released Alembic migration, j0k1l2m3n4o5 (the vendor_mappings schema freeze), and rewired the chain around it. Any database stamped at that revision (it was the chain head between 2026-07-23 and 2026-08-13) failed at startup with alembic.util.exc.CommandError: Can't locate revision identified by 'j0k1l2m3n4o5'. The migration file is restored and the chain rewired back to linear, so alembic upgrade head now resolves on existing databases automatically — no manual fix needed.
  • Defensive outbox creation (PR #17) — notification_outbox creation in the k2l3m4n5o6p7 migration now skips when the table already exists, so create_all-built databases upgrade cleanly.

Housekeeping

  • The restored migration's upgrade() is a no-op on fresh databases (h4d5e6f7a8b9 already creates vendor_mappings), so new installs are unaffected.

Testing

  • Full suite: 359 passed, 15 skipped. All CI quality gates pass: ruff format --check, ruff check, mypy voucherbot tests, and the full pytest suite.
  • tests/test_migrations.py enforces the single-head/linear chain invariant; alembic history and alembic heads verify one head (m6n7o8p9q0r1).

Full Changelog: v1.1.0...v1.1.1

VoucherBot v1.1.0

Choose a tag to compare

@Devathmaj Devathmaj released this 14 Aug 11:10

v1.1.0 — Migration-Led Schema, Weighted AI Routing, Reddit RSS-by-Default & Content Retention

Minor release: Alembic migrations are now the single source of truth for the database schema (applied automatically at startup), Groq calls are routed 40/40/20 across the gpt-oss model family with quota-aware fallbacks, Reddit collection defaults to public RSS only (no OAuth calls), the Reddit source catalog is curated, and the scheduler now nulls out the content column of posts older than the retention window.


What changed since v1.0.4

Two pull requests landed since v1.0.4:


New Features

  • Alembic migrations are now authoritative (PR #15) — alembic upgrade head runs automatically at startup when IS_PROD=false, so a brand-new system boots end-to-end (schema + seed data) with zero manual steps. The migration chain fully reproduces the schema the running models define, including tables, enums, indexes, and the voucher_posts view.
  • Weighted Groq model routing (PR #15) — replaced the fixed round-robin batch with weighted 40/40/20 distribution across openai/gpt-oss-20b / openai/gpt-oss-120b / llama-3.3-70b-versatile, each respecting its TPM/TPD/RPD quota. Exhausted models are skipped, failures fall back to remaining models, and Gemini remains the final fallback.
  • Reddit RSS-by-default (PR #15) — REDDIT_INGESTION_ENABLED=false (the default) now collects Reddit purely via public RSS feeds and never calls the OAuth API, regardless of whether credentials are configured. Reddit sources are no longer filtered out of the scheduler when ingestion is disabled.
  • Curated Reddit source catalog (PR #15) — refreshed the default subreddit list (added O365Certification, mcsa, ccnp, linuxadmin, salesforce, vmware; removed MicrosoftLearn, LinuxCertifications, eFreebies, FREE). Stale subreddits no longer in the catalog are auto-disabled.
  • Post content retention (PR #16) — the scheduler now nulls out posts.content for every post older than content_retention_days (default 7), keeping the column bounded. A raw UPDATE ... SET content = NULL is used so only the content column is touched (no accidental updated_at writes).

Reliability / Schema

  • Migration chain rebuilt (PR #15) — the schema-freeze keyword migration now creates its table unconditionally, the redundant vendor-mappings freeze was removed, and two new migrations add the PEARSONVUE/TRAINING_PROVIDER source types and reconcile create_all-built DBs (creates the voucher_posts view, makes vendor_mappings timestamps timezone-aware). alembic check is clean after applying.
  • migrations/env.py hardening (PR #15) — % in the DB URL is escaped (fixes a configparser interpolation crash on percent-encoded passwords), all models are registered in Base.metadata, and view-backed models are excluded from autogenerate.
  • Model/migration alignment (PR #15) — vendor_mappings.url_pattern unique index, keyword server defaults, timezone-aware vendor_mapping timestamps.
  • Retention is configurable (PR #16) — new CONTENT_RETENTION_DAYS setting (default 7); the sweep is idempotent (content IS NOT NULL guard) and runs once per scheduler loop iteration.

Housekeeping

  • Dropped author capture on Reddit (PR #15) — keeps [deleted]-style noise out of the pipeline.
  • Docs & source catalog sync (PR #15) — Reddit RSS behavior, OAuth settings, setup instructions, and contributor checklists clarified; Sources/Subreddit.txt refreshed.

Testing

  • New unit tests for the migration graph, startup lifespan wiring, Reddit source disabling, collectors, and the analyzer (PR #15).
  • New tests/test_retention.py covers the retention SQL shape (SET clause contains exactly content, never updated_at/summary), NULL-skipping, rowcount, and error swallowing (PR #16).
  • Full suite: 359 passed, 15 skipped. All CI quality gates pass: ruff format --check, ruff check, mypy voucherbot tests, and the full pytest suite.

Upgrade notes

On a brand-new empty DB, alembic upgrade head at startup builds the complete schema automatically. For existing create_all-built DBs, apply the new m6n7o8p9q0r1 reconciliation migration (alembic upgrade head) — alembic check passes cleanly afterwards.

Full Changelog: v1.0.4...v1.1.0

VoucherBot v1.0.4

Choose a tag to compare

@Devathmaj Devathmaj released this 14 Aug 07:28
f2cee16

v1.0.4 — Reliability, Test Coverage & New Source

Patch release: transactional (idempotency-safe) email delivery for voucher alerts, a large offline unit-test suite, scheduling/readiness hardening, dependency cleanup, and a new community-wiki source for AWS voucher discounts.


What changed since v1.0.3

Three pull requests landed since v1.0.3:


New Features

  • AWSCertifications wiki vouchers source (PR #14) — the r/AWSCertifications community wiki's Vouchers & Discounts page is now ingested as a WEBSITE source. The page is a single MkDocs document (no per-card wrappers), so it is parsed as one post per crawl (article.md-content__inner / h1 / link_selector=self) to preserve all promo codes, links, tables, and notes. Added a VENDOR_MAPPINGS URL-pattern entry so posts from this source are tagged with the canonical aws vendor.
  • Community wiki are enabled by default — no schema migration required (config-driven catalog).

Reliability

  • Transactional outbox for voucher email delivery (PR #12) — notification delivery intent is persisted to a new notification_outbox table in the same transaction as the pipeline run, so delivery state survives commit failures and is retried until delivered. Every send carries a stable idempotency key (voucher:{post_id}:{content_hash}) passed to Resend as the Idempotency-Key header, so replaying a pending row can never deliver a duplicate email. A successful send marks the row SENT and sets posts.is_notified in one commit; the scheduler sweeps remaining PENDING rows every 60s and marks them FAILED after 5 attempts. (New migration: k2l3m4n5o6p7_add_notification_outbox.)
  • Clamped poll_interval_minutes (PR #12) — config intervals are clamped to [1, 43200] minutes, preventing scheduler busy-loops (0/negative) and timedelta overflow (huge values).
  • Process boot time set at application startup (PR #12) — PROCESS_BOOT_AT was captured at module import (stale under uvicorn --preload); it is now set in the app lifespan so lease-staleness checks reflect the real process start.
  • DB-backed readiness check (PR #12) — /health now executes SELECT count(*) FROM sources via the session dependency instead of returning a static "ok", so it reports unhealthy when critical tables are missing.
  • Collector error-propagation fixes (PR #12) — broad except Exception → return [] was replaced with specific httpx handling across website/pearsonvue/rss/training-provider collectors. Auth-blocked (401/403) and transient errors return []; unexpected errors re-raise so the dispatcher applies backoff/unrecoverable handling instead of silently marking the source successful.
  • Email DB-failure state fix — delivery failure state handling corrected in the ingestion pipeline.

Code Quality / Housekeeping

  • Lazy imports moved to module level (PR #12) — groq, google.genai, resend, urllib.request, soupsieve, asyncpg exceptions, and startup imports in main.py now import at module scope, giving clear early errors for missing dependencies and enabling full static dependency tracking.
  • Dependency cleanup (PR #12) — removed unused apscheduler and pgvector from pyproject.toml.
  • mypy strict compliance in the test suite — tests updated to satisfy strict type-checking.

Testing

  • Unit test suite (PR #13) — new offline, fully-mocked tests for the ingestion pipeline, AI analyzer, bootstrap, Pearson VUE and training-provider collectors, settings, and init_db. Suite grew from 198 passed to 341 passed (15 intentional skips). No live DB, network, or third-party APIs are touched.
  • Transactional outbox tests — new tests/test_notification_outbox.py covers idempotency-key stability, staging, delivery success/failure/retry, and the scheduler sweep; extended tests/test_email_sender.py for Idempotency-Key passthrough; added coverage for poll_interval_minutes clamping, set_process_boot_at, and the DB-backed /health check.
  • All CI quality gates pass: ruff format --check, ruff check, mypy voucherbot tests, and the full pytest suite.

Full Changelog: v1.0.3...v1.0.4

VoucherBot v1.0.3

Choose a tag to compare

@Devathmaj Devathmaj released this 12 Aug 15:46
b1b01c7

v1.0.3 — Hardening & Reliability

Patch release: additional security hardening, CI hardening, and first-startup/database reliability fixes.


Security

  • URL validation & feed content-type checks — collectors reject mismatched content types and malformed/unsafe URLs
  • Trustworthy rate-limit keys — limiter keys keyed on a stable, bounded representation instead of raw client strings; bounded limiter cache to prevent unbounded growth
  • Broader query redaction — sensitive query parameters redacted from logs, including exception strings
  • Rate limiter hardening — exception messages redacted in logs

CI / Supply chain

  • OIDC scoped to docker job — least-privilege token issuance
  • cosign pinned to commit SHA — supply-chain pin for the signer installer
  • Dropped unsupported Resend tracking params — email config cleanup
  • Cosign signing of published Docker images

Reliability

  • Enum migration on fresh DB — runs on an explicit autocommit connection and skips gracefully when the \sourcetype\ type does not exist yet (previously failed with a poisoned transaction)
  • Async connection started properly — fixed \AsyncContextNotStarted\ by entering \engine.connect()\ as an async context before applying connection options
  • vendor_mappings unique constraints — added unique constraints on \url_pattern\ / \source_name_pattern\ so \ON CONFLICT\ upserts in bootstrap target existing indexes (matches migrations)

Testing

  • New tests for DB init (enum migration paths), collector URL/content-type guards, email sender, and rate limiting

  • uff check,
    uff format --check, and \mypy voucherbot tests\ all clean
  • Full suite passes

Full Changelog: v1.0.2...v1.0.3

VoucherBot v1.0.2

Choose a tag to compare

@Devathmaj Devathmaj released this 12 Aug 12:44
585acae

v1.0.2 — Security Hardening

Security and hygiene patch release: closes HIGH-severity findings from the codebase audit, tightens the exposed API surface, and removes dead code.


Security

  • SHA-1 replaced with SHA-256 (H-3) — all four collectors now hash with hashlib.sha256; behavior-neutral since dedup is driven by the pipeline's independent SHA-256 identity_hash
  • Secrets redacted from log output (H-4) — new redact_secrets structlog processor masks sensitive keys and secret-shaped fragments (API keys, Bearer tokens, URL credentials, sensitive query params) across all log sites, including nested config dicts and exception strings
  • Rate limiting on /health (H-5) — minimal in-memory sliding-window limiter (default 60 req/min/IP, configurable via HEALTH_RATE_LIMIT_PER_MINUTE, 0 disables) returning 429 + Retry-After
  • API surface reduced to /health only/ready, /sources, /posts, and /alerts endpoints removed along with their routers, shrinking the unauthenticated attack surface
  • CSS selector validation (H-1) — bootstrap now logs a warning for malformed selectors in source config (log-only, never rejects)

Maintenance

  • Removed vestigial NormalizedPost.external_id field and its now-unused hashlib imports across all collectors
  • Removed unused apscheduler dependency (scheduling is a custom asyncio loop)
  • Applied ruff format fixes

Testing

  • Full suite: 167 passed, 15 skipped
  • ruff check, ruff format --check, and mypy voucherbot tests all clean
  • New tests for log redaction and health rate-limiting (429 + disabled paths)

Full Changelog: v1.0.1...v1.0.2

VoucherBot v1.0.1

Choose a tag to compare

@Devathmaj Devathmaj released this 28 Jul 07:13

v1.0.1 — Documentation & Email Fix

Patch release with an improved deployment guide (including a new Docker image deployment path) and a fix to the email verification flow.


🚀 What's New

Deployment Guide Overhaul

  • Alternate connection methods — The "Connect via Public Git Repository" option was moved out of Step 6 into a dedicated section at the end of the guide. A brand-new "Connect via Existing Docker Image" path with screenshots (render_19–23) lets users deploy directly from the pre-built GHCR image without connecting any repository.
  • Step 4 now links to these alternatives so users discovering them mid-setup can find them easily.
  • Better visual separation between the main guide, alternate methods, and Blueprint deployment sections.

Email Verification

  • The app now sends a fresh verification email on every startup, not just the first launch. This ensures the confirmation/welcome email always arrives after a new deployment or restart.

README Rewrite

  • Simplified language and reorganized into a clear step-by-step flow (fork → download .env → complete setup guides → deploy).
  • Replaced technical jargon with plain English to make the project accessible to non-developers.

🐛 Bug Fixes

  • Email verification email not sent on restartsender.py now resends the initial verification mail every time the app starts, fixing deployments where the welcome email was missing after a re-deploy.

📚 Documentation

  • Reorganized docs/setup/render-deployment.md with alternate connection methods
  • Added 5 new deployment screenshots (render_19.pngrender_23.png)
  • Added VoucherBot preview screenshot to README
  • Rewrote README with beginner-friendly step-by-step setup
  • Corrected LICENSE file content

Full Changelog: https://github.com/Devathmaj/Certification/compare/v1.0.0...v1.0.1

VoucherBot v1.0.0

Choose a tag to compare

@Devathmaj Devathmaj released this 26 Jul 11:11

v1.0.0 — Initial Release

This is the first stable release of VoucherBot — an intelligent certification voucher aggregator that continuously monitors community and official sources for certification discounts, free exam opportunities, beta exams, and promotional campaigns, then delivers them straight to your inbox.


🌟 Highlights

  • AI-powered voucher detection — dual-provider pipeline: Groq (primary) + Google Gemini (fallback)
  • 70+ monitored sources across RSS feeds, blogs, forums, events, Pearson VUE pages, and training providers
  • Automated email notifications via Resend when a voucher is found
  • Seamless cloud deployment — Docker image published to GHCR, ready for Render
  • CI/CD pipeline — lint, type-check, test, build, and release on tag push
  • End-to-end test mode — spin up a local test server to verify the full pipeline

🚀 Features

Core Pipeline

  • Scheduler-driven ingestion loop — sequentially processes all due sources, sleeps until next due time (capped at 6 h)
  • Five-stage pipeline: collect → keyword filter → dedup/upsert → AI extraction → event matching → email notification
  • PostgreSQL-based lease system prevents duplicate processing across app instances
  • Exponential-backoff retry with advisory locking during bootstrap

Data Sources (70+)

Category Count Examples
Vendor blogs 15 AWS Training, Microsoft Learn, Google Cloud, Cisco, Red Hat, Linux Foundation, Oracle, HashiCorp, Docker, Elastic, SUSE, Canonical, Ubuntu, SAS, Neo4j
RSS feeds 16 The Register, TechTarget, Petri IT, InfoQ, Confluent, Tutorials Dojo, Packet Pilot, Microsoft Blog, Cloud Academy (disabled), Certiport, Databricks, CNCF, LF Events, etc.
Pearson VUE pages 10 AWS, Microsoft, Cisco, CompTIA, VMware/Broadcom, Fortinet, Palo Alto Networks, Salesforce, ServiceNow, Splunk
Community forums 2 Microsoft Learn Q&A, Google Cloud Training Group
Events 5 Microsoft Cloud Skills Challenge, AWS events (disabled), AWS re:Invent (disabled), Google Cloud events, Google Cloud Next, Cisco Live (disabled)
Promo pages 3 CompTIA, ISC2 (disabled), Red Hat Training (disabled), MSFT Hub, VladTalksTech
Training providers 2+ Global Knowledge, Linux Foundation Training & Promotions

AI Analysis

  • Primary provider: Groq (llama-3.1-8b-instant)
  • Fallback provider: Google Gemini
  • Structured JSON output — fields: is_voucher, confidence, discount_amount, expiry_date, exam_code, promo_code, provider, summary
  • Token usage estimation and flexible hardening

Email Notifications

  • Resend integration with HTML templates
  • Sends alerts for new vouchers and possible matches
  • Configurable EMAIL_TO recipient

Database

  • PostgreSQL with async SQLAlchemy + asyncpg
  • Full schema: sources, posts, keywords, vendor mappings, pipeline lock, events
  • VoucherPost view for public preview page
  • Deduplication via identity hash (SHA-256 of URL) + content hash

🐛 Bug Fixes

  • PgBouncer connection drops — reduced pool_recycle from 240s → 60s to stay ahead of Supabase pooler server_lifetime kills; all sessions now re-apply statement_timeout = 120s after every commit (PgBouncer resets session settings on DISCARD ALL)
  • Dead sources removed — various non-functional feeds pruned
  • RSS feed URL normalisation — Microsoft Tech Community and Google Cloud Blog redirects handled
  • Pipeline edge cases — stuck post recovery, empty feed handling, keyword filter scoring fixes
  • Scheduler resource constraints — reduced CPU/memory footprint for low-spec systems
  • Advisory lock cleanup — stale leases are reset on every startup
  • Connection refused gracefully handled — WebsiteCollector logs a warning instead of an error when local test server is not running
  • Transient error detection improved_is_transient() now walks the full exception cause chain to catch wrapped asyncpg errors

📚 Documentation

  • Full deployment guide for Render
  • Supabase, Groq, Gemini, Resend, and Reddit API setup guides
  • Architecture overview and data flow diagrams
  • Configuration reference for all env vars
  • Schema documentation
  • Testing guide with local test server walkthrough
  • Source list and descriptions
  • Contributing guidelines and PR templates
  • Shutdown safety guidance

🛠️ Operational

Deployment

  • Dockerfile for containerised deployment
  • Render Blueprint (render.yaml) for one-click deploy
  • UptimeRobot setup guide to prevent Render free-tier spin-down

CI/CD (.github/workflows/release.yml)

Triggered on v* tag push:

  1. Verify — runs against a real PostgreSQL service container:
    • ruff format --check .
    • ruff check .
    • mypy voucherbot tests
    • pytest
  2. Docker — builds and pushes image to ghcr.io
  3. Release — creates a GitHub Release with auto-generated notes

Configuration

All features are toggled via environment variables:

Variable Default Purpose
IS_PROD false DDL-only vs full bootstrap
IS_TEST false Seeds localhost test source
REDDIT_INGESTION_ENABLED Toggles Reddit collection
SCRAPER_RESPECT_ROBOTS true robots.txt compliance
SCRAPER_MIN_DELAY_SECONDS 2.0 Minimum per-host crawl delay

🔜 Known Limitations

  • The Register RSS feed — returns a Proof-of-Work challenge page; marked as unsupported
  • Pearson VUE scraping — HTML structure can change without notice; pages are fetched and parsed, not API-driven
  • Reddit collection — requires manual API credential setup (disabled by default)
  • Local test server — single-threaded http.server; can become stuck if a request is interrupted

🧪 Testing

# Unit / integration tests
pytest

# Lint & formatting
ruff check . && ruff format --check .

# Type checks
mypy voucherbot tests

# End-to-end with local test server
# Terminal 1: python D:\components\server.py
# Terminal 2: uvicorn voucherbot.main:app --port 9000
# Set IS_TEST=true, IS_PROD=false in .env

Full commit log: https://github.com/Devathmaj/Certification/commits/v1.0.0

New Contributors

Full Changelog: https://github.com/Devathmaj/Voucher-Tracker/commits/v1.0.0