The context
The client runs a platform that aggregates public procurement tenders from across Argentina — a tool that thousands of businesses depend on to find government contracts to bid on. The platform was built about five years ago on a legacy PHP stack with a proprietary in-house framework, no formal documentation, and none of the modern PHP conventions (no Composer autoload discipline, no PSR-4, custom ORM patterns).
I joined as a freelance developer to deliver three concrete things — while starting an ongoing audit of the codebase.
1. Fixing a broken production scraper
One of the government portals the platform scrapes had migrated from a static HTML listing to a JavaScript-driven single-page app backed by an AJAX API. The old scraper, which parsed the HTML directly, was completely broken. New tenders from that province were no longer being ingested.
Instead of patching the fragile HTML parsing, I reverse-engineered the new backend using browser developer tools, identified the internal JSON API the SPA uses, and rewrote the scraper to consume it directly. The detail pages still return HTML, so I adapted the field extraction to the new page structure.
Result: from 100% failure rate to fully operational, currently ingesting around 120 tenders per run reliably.
2. Building a tiered access system for AI features
The platform was rolling out two new premium AI features (document summarization + assistant chat) and needed a full access control system with three user tiers: Base, Premium, and Premium AI. This required work across the entire stack:
- Database: new plan entry, an audit table for AI usage with indexes optimized for both "lifetime count" and "rolling 30-day count" queries, plus routing changes.
- Backend: extended the session system to cache plan info in-memory (avoiding a database query on every permission check), and added the helper methods and endpoints the new features needed.
- Frontend: a JavaScript layer that checks permissions before opening any AI feature, plus accessible modals that adapt their message to the specific feature the user was trying to use.
- Deployment: I prepared a self-contained deployment package (files + SQL + rollback plan) so the client's deploy operator could ship it safely without me being present.
3. Performance audit
While the modernization work was in progress, I started a broader performance review. I identified several classes of anti-patterns typical of legacy PHP apps:
- N+1 queries inside listing loops — one of the top causes of slow pages.
- Static reference tables (plans, currencies, translations) being reloaded on every request instead of being cached.
- Derived session data being recomputed from the database on every check instead of being memoized. The plan-caching I introduced during the AI features work is one small example of this pattern applied surgically.
The audit is ongoing, with each finding delivered as a small, low-risk change rather than a big-bang refactor.
What this project demonstrates
- Reverse-engineering unfamiliar legacy code without documentation to identify what's safe to change and what will break.
- Extending a proprietary framework by learning its conventions from usage, not from docs (there aren't any).
- Incremental, low-risk delivery to a production system — small commits, DB-first migrations, rollback plans, isolated backend testing before frontend integration.
- Handoff-ready documentation so non-developer operators can deploy safely.
- Structural performance diagnosis instead of throwing infrastructure at slow pages.
This is exactly the kind of work most developers avoid — and exactly the kind of work that keeps businesses running while they figure out their long-term modernization strategy.