Python automation for small businesses that holds up in production
Scrapers, API integrations, and QA suites, delivered with tests that prove they work. I replace fragile manual work and no-code workarounds with clean, documented Python.
30 Hacker News rows, verified · 1,000 books scraped, all 50 pages · 14/14 inventory tests passing · 8 portfolio pieces, all verified
Aurora / Denver, Colorado · $40/hr · Fixed-price and hourly contracts
What I do
Start here
API & webhook middleware
Replacing Zapier/Make with FastAPI microservices: custom integrations, webhook handlers, and data sync between the tools a business already runs.
Web scraping & data QA
Playwright scrapers for JavaScript-heavy and legacy sites, Pandas cleaning and strict type enforcement, deduplication, and pytest suites that validate every column. Selectors are verified against the live DOM, not from memory.
OpenAPI docs, QA automation & SOPs
OpenAPI 3.1 specs, Playwright/Pytest regression suites, and written SOPs so the system keeps working after handover.
Skills
PythonPlaywrightFastAPIPandasPytestWeb ScrapingData ExtractionAPI IntegrationWebhooksOpenAPIDockerPostgreSQLData CleaningETLQA Automation
Proof of work: Hacker News front-page pipeline
Headless Playwright extraction of the live Hacker News top 30 (rank, title, domain, points, submitter, and comment count), pairing title rows and detail rows in the DOM without fragile XPaths. Pandas enforces strict types before exporting hn_top30.csv, ready to load into PostgreSQL.
The part that matters: independent verification caught a defect the first test suite missed. The original submitter selector relied on a CSS class (.hnuser) no longer present on live Hacker News, leaving the submitter column empty. I fixed it with a structural attribute selector (a[href^="user?id="]) and added tests that catch that failure.
| Rank | Title | Domain | Points | Submitter | Comments |
|---|---|---|---|---|---|
| 1 | Jensen Huang resigns | apnews.com | 1859 | lldb | 1879 |
| 2 | Rust 1.100.0 | github.com/rust-lang | 107 | thesuperbigfrog | 15 |
| 3 | Gemini 4 Hydrogen | blog.google | 184 | dvaplima | 109 |
| 4 | Ask HN: Microsoft/Azure Outage? | none | 99 | nazgulsenpai | 32 |
| 5 | Abandoned Quake 4 expansion finished by modders and now playable | moddb.com | 165 | klaussilveira | 60 |
Full files, as run: hn_top30.csv (30 rows) · scraper.py · verify_deliverable.py (6 tests) · sample proposal
Run it yourself:
python scraper.py pytest verify_deliverable.py -v
Proof of work: Webhook-to-CRM sync microservice
A FastAPI microservice that replaces a fragile Zapier zap: it receives contact-form webhooks, validates them with Pydantic, deduplicates by email, forwards each new contact to the downstream CRM, and logs every sync to SQLite. Every lead lands in the log.
Verified live against the real JSONPlaceholder CRM endpoint: a live submission returned 201 with crm_id 11, a repeat of the same email returned 200 duplicate, and the sync log held one row. The part that matters: verification caught a real bug the first test run missed. Failed submissions never reached the log, because the error path rolled the insert back. I fixed it with an explicit commit before raising the 502, and the live run proved the fix.
Full files, as run: webhook_app.py · webhook_test_app.py (6 tests) · README · verification log
Run it yourself: bash run_demo.sh then pytest tests/test_app.py -v (see the README for the project layout).
Proof of work: OpenAPI spec + regression suite + SOP for an inventory API
Demo brief: an inventory API with no documentation, no regression suite, and no runbook. I delivered the full package: a hand-written OpenAPI 3.1 spec, a 14-test pytest regression suite, and a plain-English SOP for the owner.
The part that matters: I verified the spec against the live app itself, beyond its own test. I fetched the server's real /openapi.json and compared it field by field. That caught a real gap the suite missed: the spec's 422 responses were empty placeholders while the live schema uses a proper HTTPValidationError definition. I fixed it, and the two now match.
Full files, as run: inventory_app.py · inventory_openapi.yaml · inventory_test_api.py (14 tests) · SOP · verification log
Run it yourself: bash run_demo.sh then pytest tests/test_api.py -v (see the README for the project layout).
Proof of work: Paginated catalogue scraper
Demo brief: a full online catalogue in a spreadsheet. I scraped all 1,000 books from books.toscrape.com (title, price, star rating, availability, category, and product URL), following the next-page link through all 50 pages until it was gone, with no hardcoded page count.
The part that matters: I spot-checked rows against the live site. The first book's page shows Poetry and £51.77, the last shows Travel and £26.08, matching the CSV. The scrape took 25 minutes because each book's category comes from its own detail page; slow, but the data is real.
Full files, as run: books.csv (1,000 rows) · books_scraper.py · books_verify_deliverable.py (7 tests) · README · verification log
Run it yourself: bash run_demo.sh then pytest tests/verify_deliverable.py -v (see the README for the project layout).
Proof of work: Research report, L&G Cleaning Services (Aurora)
A family-owned cleaning company in Aurora has a great reputation (4.8 stars, 60 Google reviews), but the owner confirms every quote and appointment himself, so he is the bottleneck on all intake. I researched the business from its own public site and reviews, documented the bottleneck in its own words, and proposed a specific product: an automated intake, quoting & scheduling service that turns standard bookings into a zero-touch flow and routes the complex jobs to the owner.
The part that matters: the report proves the issue is an owner-time and growth-ceiling problem, not a reputation problem. The data needed to automate already exists in structured form on their quote form. Every claim in it traces to a public source; the unknowns are listed as questions for the owner, not filled in by guessing.
Full report: lg_research_report.md · Try the concept demo: instant-quote demo (illustrative rules; exact pricing locks with the owner before launch)
Proof of work: Research report, All World Automotive (Aurora)
An independent auto repair shop in Aurora does good work at fair prices (long-time customers say so), but it takes in more cars than it can handle and then goes silent: 3.6 stars, 50 reviews, an unclaimed profile, and no website of its own. I researched the business from its OpenBay listing and public reviews, documented the overbooking pattern in the reviewers’ own words, and proposed a specific product: a capacity-aware booking and status-communication service that caps daily intake by real bay capacity and texts customers at every stage of the repair.
The part that matters: the report proves the issue is operations and communication, not workmanship. The unknowns (bay count, daily intake, how bookings arrive today) are listed as questions for the owner, not filled in by guessing.
Full report: allworld_research_report.md · Try the concept demo: booking & status demo (illustrative capacity; real numbers lock with the owner before launch)
Proof of work: Research report, Denver Ducts Corp (Aurora)
A family-owned duct and dryer-vent cleaning company in Aurora does good work (1,259 reviews, 4.5 stars, technicians named by first name in the praise), but customers are ghosted after no-shows: two documented the exact pattern in the same month, one waiting through two no-shows and a call after the window, the other booked two weeks out and never heard back. I researched the business from its own site, Trustindex, and its BBB profile, documented the no-show pattern in the reviewers’ own words, and proposed a specific product: an SMS confirmation and missed-call recovery system that confirms the window, texts the customer when the tech runs late or the window is missed, and logs every missed call so nothing falls out of the process unnoticed.
The part that matters: the report proves the issue is scheduling and communication, not workmanship, and a second source agrees: the BBB profile shows an F rating with 4 complaints in the last 12 months. Every claim in it traces to a public source; the unknowns are listed as questions for the owner, not filled in by guessing.
Full report: denver_ducts_research_report.md · Try the concept demo: appointment confirmation demo (14/14 Playwright tests, desktop and mobile)
Proof of work: Research report, Berrett Pest Control (Denver)
A family-owned pest control company in Denver has a strong reputation (4.8 stars, 7,714 reviews), but customers are left waiting in silence: three documented no-shows and uncommunicated cancellations in a single month, and the company's own staff admitted their system drops return visits. I researched the business from its own site, Angi, and Birdeye, documented the pattern in the reviewers’ own words, and proposed a specific product: an SMS confirmation and return-visit scheduling system that confirms the window, texts the customer when the tech runs late or the window is missed, schedules the return visit under the recurring 4-month agreement so it cannot fall out of the system, and logs every missed call so nothing goes unfollowed up.
The part that matters: the report proves the issue is scheduling and communication, not workmanship, and the recurring model makes it worse: a dropped return visit is a dropped renewal. Every claim in it traces to a public source; the unknowns are listed as questions for the owner, not filled in by guessing.
Full report: berrett_research_report.md · Try the concept demo: appointment confirmation & return-visit demo (25/25 Playwright tests, desktop and mobile)
How I work
Fixed scope, milestone-based: 35% on kickoff and schema lock, 40% on a working demo you can test, 25% on final delivery with the repo and Docker setup. IP transfers on final payment. Every deliverable ships with a verification log, not a claim that it works. Optional $300/month maintenance retainer after handover.
Contact
Chris · chris@milehighwebplug.xyz · +1 (970) 458-5251
Reach me through my Upwork profile, or on GitHub at gulp7096. Tell me what your pipeline or integration needs to do and I’ll tell you whether I can build it.