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.

See proof of work GitHub: gulp7096

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.

FastAPI · Pydantic · SQLite

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.

Playwright · Pandas · pytest

OpenAPI docs, QA automation & SOPs

OpenAPI 3.1 specs, Playwright/Pytest regression suites, and written SOPs so the system keeps working after handover.

OpenAPI 3.1 · pytest · SOP

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.

Verified 2026-11-12: 6/6 pytest tests passing. 30 rows, no nulls or blanks in rank/title, integer types for rank/points/comment count, submitter populated on every row, and no empty values except domain (Ask HN and text posts have none)

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.

RankTitleDomainPointsSubmitterComments
1Jensen Huang resignsapnews.com1859lldb1879
2Rust 1.100.0github.com/rust-lang107thesuperbigfrog15
3Gemini 4 Hydrogenblog.google184dvaplima109
4Ask HN: Microsoft/Azure Outage?none99nazgulsenpai32
5Abandoned Quake 4 expansion finished by modders and now playablemoddb.com165klaussilveira60

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 2026-11-12: 6/6 pytest tests passing. Valid submissions sync, invalid email and missing fields get a 422, a duplicate email returns the original contact without calling the CRM twice, and a downstream failure returns 502 and is logged as failed

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.

Verified 2026-11-12: 14/14 pytest tests passing. Full CRUD, duplicate SKU blocked with 409, bad data rejected with 422, unknown SKUs 404, list filters, and a spec-conformance test that fails if the spec and the app ever drift apart

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.

Verified 2026-11-12: 7/7 pytest tests passing. 1,000 rows, the 6 columns present, no nulls, prices all positive floats, ratings all integers 1–5, every product URL unique, and every category populated

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.