Releases: Devathmaj/VoucherBot
Release list
VoucherBot v2.1.0
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 amatch_reasonaudit 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_KEYis 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 --checkclean,mypy --strictclean (81 files).
Full Changelog: v2.0.1...v2.1.0
VoucherBot v2.0.1
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.
o9p8q7r6s5t4no longer fails with
"column ... already exists" when the database was originally created via
create_all. The migration is now idempotent — it only addscreated_at/
updated_attokeywordsandpipeline_lockwhen the columns are missing. - Scheduler
greenlet_spawn has not been callederror 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_unrecoverableinstead of touching the expired ORM
instance aftersession.rollback()— accessing any attribute (even the
primary key) of an expired object raisesMissingGreenletunder 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.
- The dispatcher success path refreshes the source ORM instance after the
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 checkandruff format --checkpass.
Full Changelog: v2.0.0...v2.0.1
VoucherBot v2.0.0
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 newNOTIFICATION_BOT_SERVER_URLandWEBHOOK_SECRETsettings. - 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.exampleand the Configuration reference.
🤖 AI Model Change & New 50/50 Split
- Removed
llama-3.3-70b-versatilefrom the Groq rotation. - Primary extraction is now split evenly 50/50 across the two gpt-oss models:
openai/gpt-oss-20bandopenai/gpt-oss-120b. qwen/qwen3.6-27bjoins 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_generationvalidation 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:
- feat: send voucher alerts to bot webhook alongside email
- AI model update — 50/50 gpt-oss split + qwen reasoning escalation
- mypy test updates
- README update — announce bot notifications
Testing
- Full suite: 371 passed, 15 skipped.
- All CI quality gates pass:
ruff format --check,ruff check,mypy voucherbot tests, and the fullpytestsuite. - 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
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(thevendor_mappingsschema 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 withalembic.util.exc.CommandError: Can't locate revision identified by 'j0k1l2m3n4o5'. The migration file is restored and the chain rewired back to linear, soalembic upgrade headnow resolves on existing databases automatically — no manual fix needed. - Defensive outbox creation (PR #17) —
notification_outboxcreation in thek2l3m4n5o6p7migration now skips when the table already exists, socreate_all-built databases upgrade cleanly.
Housekeeping
- The restored migration's
upgrade()is a no-op on fresh databases (h4d5e6f7a8b9already createsvendor_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 fullpytestsuite. tests/test_migrations.pyenforces the single-head/linear chain invariant;alembic historyandalembic headsverify one head (m6n7o8p9q0r1).
Full Changelog: v1.1.0...v1.1.1
VoucherBot v1.1.0
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:
- feat: migration-led schema, weighted Groq routing, Reddit RSS-by-default
- feat: purge content of posts past retention window in scheduler
New Features
- Alembic migrations are now authoritative (PR #15) —
alembic upgrade headruns automatically at startup whenIS_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 thevoucher_postsview. - 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; removedMicrosoftLearn,LinuxCertifications,eFreebies,FREE). Stale subreddits no longer in the catalog are auto-disabled. - Post content retention (PR #16) — the scheduler now nulls out
posts.contentfor every post older thancontent_retention_days(default 7), keeping the column bounded. A rawUPDATE ... SET content = NULLis used so only the content column is touched (no accidentalupdated_atwrites).
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_PROVIDERsource types and reconcilecreate_all-built DBs (creates thevoucher_postsview, makesvendor_mappingstimestamps timezone-aware).alembic checkis clean after applying. migrations/env.pyhardening (PR #15) —%in the DB URL is escaped (fixes a configparser interpolation crash on percent-encoded passwords), all models are registered inBase.metadata, and view-backed models are excluded from autogenerate.- Model/migration alignment (PR #15) —
vendor_mappings.url_patternunique index,keywordserver defaults, timezone-awarevendor_mappingtimestamps. - Retention is configurable (PR #16) — new
CONTENT_RETENTION_DAYSsetting (default 7); the sweep is idempotent (content IS NOT NULLguard) 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.txtrefreshed.
Testing
- New unit tests for the migration graph, startup lifespan wiring, Reddit source disabling, collectors, and the analyzer (PR #15).
- New
tests/test_retention.pycovers the retention SQL shape (SET clause contains exactlycontent, neverupdated_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 fullpytestsuite.
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
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:
- Reliability & code-quality hardening (M-2, M-4, M-7, M-8, L-9, deps)
- test: add unit tests for pipeline, analyzer, bootstrap, and collectors
- feat: add AWSCertifications wiki vouchers source
New Features
- AWSCertifications wiki vouchers source (PR #14) — the r/AWSCertifications community wiki's Vouchers & Discounts page is now ingested as a
WEBSITEsource. 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 aVENDOR_MAPPINGSURL-pattern entry so posts from this source are tagged with the canonicalawsvendor. - 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_outboxtable 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 theIdempotency-Keyheader, so replaying a pending row can never deliver a duplicate email. A successful send marks the rowSENTand setsposts.is_notifiedin one commit; the scheduler sweeps remainingPENDINGrows every 60s and marks themFAILEDafter 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) andtimedeltaoverflow (huge values). - Process boot time set at application startup (PR #12) —
PROCESS_BOOT_ATwas 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) —
/healthnow executesSELECT count(*) FROM sourcesvia 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 inmain.pynow import at module scope, giving clear early errors for missing dependencies and enabling full static dependency tracking. - Dependency cleanup (PR #12) — removed unused
apschedulerandpgvectorfrompyproject.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.pycovers idempotency-key stability, staging, delivery success/failure/retry, and the scheduler sweep; extendedtests/test_email_sender.pyforIdempotency-Keypassthrough; added coverage forpoll_interval_minutesclamping,set_process_boot_at, and the DB-backed/healthcheck. - All CI quality gates pass:
ruff format --check,ruff check,mypy voucherbot tests, and the fullpytestsuite.
Full Changelog: v1.0.3...v1.0.4
VoucherBot v1.0.3
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
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-256identity_hash - Secrets redacted from log output (H-4) — new
redact_secretsstructlog 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 viaHEALTH_RATE_LIMIT_PER_MINUTE,0disables) returning429+Retry-After - API surface reduced to
/healthonly —/ready,/sources,/posts, and/alertsendpoints 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_idfield and its now-unusedhashlibimports across all collectors - Removed unused
apschedulerdependency (scheduling is a custom asyncio loop) - Applied
ruff formatfixes
Testing
- Full suite: 167 passed, 15 skipped
ruff check,ruff format --check, andmypy voucherbot testsall 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
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 restart —
sender.pynow 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.mdwith alternate connection methods - Added 5 new deployment screenshots (
render_19.png–render_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
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_TOrecipient
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_recyclefrom 240s → 60s to stay ahead of Supabase poolerserver_lifetimekills; all sessions now re-applystatement_timeout = 120safter every commit (PgBouncer resets session settings onDISCARD 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:
- Verify — runs against a real PostgreSQL service container:
ruff format --check .ruff check .mypy voucherbot testspytest
- Docker — builds and pushes image to
ghcr.io - 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 .envFull commit log: https://github.com/Devathmaj/Certification/commits/v1.0.0
New Contributors
- @Devathmaj made their first contribution in #1
Full Changelog: https://github.com/Devathmaj/Voucher-Tracker/commits/v1.0.0