The Autonomous Course Verifier is a robust, local-first Python application designed to automatically extract course information from structurally complex PDFs, cross-reference university rankings, and autonomously navigate the web to verify course details (cost, duration, mode, skills, university, country, language) against official university websites.
This version introduces automatic issue classification β every verification result is now tagged as either a Website Issue (broken link, server error, WAF block, timeout, etc.) or a Course Issue (data mismatch like wrong cost, duration, discontinued course, etc.). It also adds speed optimizations like preflight dead-link detection and domain-health caching to skip repeatedly failing sites instantly.
- Geometric Slicing: Parses PDFs by slicing pages into quadrants to isolate individual course boxes.
- Fuzzy University Matching: Detects university names using a fuzzy-matching mechanism against a global database of over 10,000 institutions.
- Visual Badge Detection: Uses OpenCV mask analysis and Tesseract OCR to identify "QS", "NIRF", and "Free/Scholarship" badges.
- Offline Data: Leverages local ranking data (
rankings.db) to verify university standings without external API calls. - Semantic Normalization: Employs
difflibfor semantic name matching to ensure accuracy across different naming conventions.
- Uses
undetected_chromedriver(uc) to bypass standard bot protections (Cloudflare). - Persistent Chrome Profiles: Automatically saves your cookies and sessions inside the
chrome_profiledirectory, ensuring you stay logged in to platforms like Coursera and NDU without triggering captchas. - NDU Batch Caching: For NDU (National Institute of Electronics and Information Technology) courses, it efficiently navigates to "Browse by Category", scrapes all pages of course cards into memory, and verifies multiple courses rapidly without redundant page loads.
- Executes precise Google Search queries using
site:operators if internal site search fails.
- Analyzes scraped web text against PDF details using local semantic verification.
- Attempts to query a fast API (OpenRouter, Gemini, NVIDIA, Groq, Mistral, SambaNova), and automatically fails over to the next provider if one fails.
- If no LLM is available, falls back to a custom
spaCyNLP Regex Engine to perform sentence-level context parsing.
- Generates a beautifully formatted output PDF (
_AUTONOMOUS_VERIFIED.pdf) detailing each course, highlighting discrepancies in red and matches in green. - Preserves visual proof by saving screenshots of the original PDF quadrants and the scraped websites into the
verification_screenshots/folder.
Every verification outcome is now automatically classified into one of two buckets with specific sub-types:
404_not_foundβ Page does not existssl_errorβ SSL / Privacy errorserver_errorβ HTTP 500 / 503 / 502blocked_by_wafβ Cloudflare, Captcha, WAFtimeoutβ Connection / page-load timeoutdns_failβ Domain unreachable / DNS failurelogin_requiredβ Paywall / login gatesite_downβ Maintenance / temporarily unavailablebrowser_crashβ Selenium driver crashedredirect_loopβ Infinite redirect loop
name_mismatchcost_mismatchduration_mismatchuniversity_mismatchcountry_mismatchmode_mismatchlanguage_mismatchskills_mismatchcourse_discontinuedcourse_replacedwrong_urlmultiple_mismatches
These are stored in every course record as:
{
"issue_category": "website_issue",
"issue_sub_type": "404_not_found",
"retry_count": 0,
"error_screenshot_path": "verification_screenshots/.../course_5_404_not_found.png"
}Before opening the heavy browser, the verifier fires a lightweight HEAD request. If the server returns 404, 500, 502, 503, 403, timeout, or DNS error, the course is fast-failed immediately β saving 10β20 seconds per dead link.
A shared TTL cache tracks how many website issues each domain has experienced. If a domain racks up 5+ failures within 10 minutes, all subsequent courses from that domain are instantly skipped with a fast-fail message. No more wasting 6 Chrome instances on a completely down university site.
If a domain accumulates 3+ website issues, the dashboard warns: "Possible site-wide outage for <domain>". You can then decide whether to retry later or contact the university's IT team.
| Card | Description |
|---|---|
| Website Issues | Count + percentage of courses that failed because the site is broken |
| Course Issues | Count + percentage of courses where the data didn't match |
A doughnut chart shows the split between:
- π΄ Website Issues
- π Course Issues
- π’ Verified
Both the Recent Verifications table and the All Courses table now display the actual issue_sub_type as the badge text:
- π΄ Red badge =
website_issue(e.g., "404 not found", "SSL error") - π Orange badge =
course_issue(e.g., "Cost Mismatch", "Multiple Mismatches") - π’ Green badge =
verified
Hovering over the badge shows the full issue_category.
/api/data.json now includes:
{
"stats": {
"website_issues": 12,
"course_issues": 8
},
"website_sub_counts": { "404_not_found": 7, "timeout": 5 },
"course_sub_counts": { "cost_mismatch": 4, "multiple_mismatches": 4 },
"domain_warnings": [
{ "domain": "Some University", "issue_count": 5 }
],
"website_issue_list": [...],
"course_issue_list": [...]
}AUTONOMOUS_VERIFIED.xlsx now includes 4 new columns:
| Column | Description |
|---|---|
Issue Category |
website_issue / course_issue / verified |
Issue Sub-Type |
Specific sub-type (e.g., 404_not_found, cost_mismatch) |
Retry Count |
How many times the verifier attempted this course |
Error Screenshot |
File path to the captured screenshot on error |
Copy .env.example to .env and add your API keys:
OPENROUTER_KEY_1=...
GEMINI_KEY_1=...
NVIDIA_KEY_1=...
GROQ_API_KEY_1=...
MISTRAL_API_KEY_1=...
SAMBANOVA_API_KEY_1=...pip install -r requirements.txt
playwright install chromiumEnsure rankings.db is in the root directory.
python autonomous_course_verifier.py- Input: Provide the path to your target course PDF.
- Process: The script extracts data, verifies rankings, browses the web, classifies issues, and saves evidence.
- Output:
verification_screenshots/<timestamp>/β Screenshots of PDF quadrants and web pagesverification_screenshots/<timestamp>/website_errors/β Screenshots captured specifically when a Website Issue is detectedAUTONOMOUS_VERIFIED.xlsxβ Full spreadsheet with issue classification_AUTONOMOUS_VERIFIED.pdfβ Human-readable PDF report
python dashboard.pyThen open http://localhost:5000 in your browser.
| File / Folder | Purpose |
|---|---|
autonomous_course_verifier.py |
Core engine (Parser, Scraper, Issue Classifier, Logic) |
llm_manager.py |
Multi-provider LLM routing with key rotation and rate-limiting |
db_manager.py |
SQLite manager for QS / NIRF ranking lookups |
dashboard.py |
Flask web dashboard with analytics and live filtering |
templates/index.html |
Premium dark-themed dashboard UI |
static/app.js |
Frontend charts, tables, modal logic |
static/style.css |
Dashboard styling |
CombinedWork.xlsx |
Supplemental verification data with hyperlinks |
rankings.db |
Offline university ranking database |
public/api/ |
Static JSON exports (courses.json, data.json) for Firebase hosting |
verification_screenshots/ |
Captured screenshots organized by run timestamp |
-
Website Issue detection happens first:
- Preflight
HEADrequest catches HTTP errors instantly - If the browser loads, the title and body text are checked for: 404, "service unavailable", "privacy error", "verify you are human", "under maintenance", etc.
- A screenshot is saved to
website_errors/and the domain health cache is updated
- Preflight
-
Course Issue detection happens when the page loads successfully but verification fails:
- The verifier checks 7 attributes: Name, Cost, Duration, Mode, Language, Skills, University
- If 3+ attributes fail, it's labeled
multiple_mismatches - Otherwise the first failed attribute determines the
issue_sub_type - The result is stored alongside
web_statusandreason
-
Dashboard consumption:
dashboard.pyreadsissue_categorywhen loading courses and maps it tostatus- The frontend renders the appropriate badge color and label
# Shared across all verifier threads
_DOMAIN_HEALTH = DomainHealthCache(ttl_seconds=600)
# Mark a failure:
_DOMAIN_HEALTH.mark_issue("iitm.ac.in", "website_issue", "timeout")
# Query:
_DOMAIN_HEALTH.is_healthy("iitm.ac.in") # False if 3+ issues
_DOMAIN_HEALTH.should_skip("iitm.ac.in") # True if 5+ issues β fast-skipThis means if IIT Madras's site goes down during a batch run, the verifier stops wasting Chrome instances on it after the 5th failure and moves on instantly.
| Problem | Solution |
|---|---|
| Too many timeouts | Domain health cache will auto-skip repeat offenders. Lower ttl_seconds in DomainHealthCache if you want quicker recovery. |
| Preflight marks good links as bad | Some servers reject HEAD. The preflight automatically falls back to a minimal GET with stream, but if a server still blocks it, you can disable preflight by commenting out the _preflight_url_check call. |
| Screenshot folder too large | The website_errors/ subfolder only grows for broken links. You can safely archive or delete old verification_screenshots/ directories. |
| Dashboard not showing issue badges | Ensure dashboard.py has the ISSUE_* constants at the top and load_courses() maps issue_category to status. |
Developed for autonomous educational data verification.