The starting line
Provincial HR managed civil servants the way most LGUs in the Philippines do — paper 201 files in cabinets, plus Excel sheets tracking deductions, leaves, and assignments. Reports for the Civil Service Commission and the Commission on Audit were handcrafted every cycle. Tourism data came in as field-officer-typed forms and was summarized by counting rows.
The brief from the office: get the records and the reports off paper, and make tourism numbers something the planning office could look at without a meeting.
Constraints
- On-premise hosting. Government data, no cloud. Everything ran on provincial servers under the IT office.
- Mixed database reality. Existing legacy applications used SQL Server. New work used MySQL. Some queries had to span both worlds.
- Mixed audience. HR staff who had never used a CRUD app. Tourism officers in the field with patchy connectivity. Planning officers who wanted clean charts.
- A team learning as we built. Most of my colleagues had shipped PHP, but not with Laravel, Git, or a code review workflow. Production deployments were FTP uploads.
The platform
E-PDS — digitizing the 201 file
The first deliverable was a digital Personal Data Sheet. The paper PDS is a multi-page CSC form with around 200 fields, family composition, work history, eligibility, training. I modeled it carefully — not as one giant table, but as a personal-record aggregate with relations for family members, employment, civil service eligibilities, learning and development, and references.
Critically, every change to a record was version-tracked. A civil servant who updated their employment history did not erase the previous entry — they appended. The audit trail is what made the data trustworthy enough for HR to retire the cabinet.
Tourism analytics dashboard
Tourism officers collected visitor arrivals at municipal entry points. The old workflow ended in a spreadsheet attached to an email. The new workflow ended in a Vue.js dashboard pulling aggregates straight from the database, sliced by municipality, month, country of origin, and purpose of visit.
The dashboard was deliberately small. Three views, no filters beyond date range and municipality, and a CSV export. Planning officers wanted a thing they could glance at, not a BI tool to learn.
Internal tooling suite
The records platform was the headline deliverable. The unglamorous deliverable, which moved the office more, was the smaller tools.
- GSIS deduction processor — generated the monthly insurance deduction file the GSIS office consumed. The old workflow took half a day. The new one is a button.
- BIR tax reporting integration — produced the report files required by the Bureau of Internal Revenue in their exact expected format. The fragile part was always the format, not the math.
- SMS notification service — small Laravel service that sent payroll, leave, and announcement messages. SMS was the channel field officers actually read.
- Android employee app for 201 files — Java app letting employees view their own record, request edits, and see leave balances. Built specifically so employees did not have to walk to the HR office for everything.
The performance work
The reporting views on the legacy applications had a familiar smell: response times measured in tens of seconds for endpoints hit by the whole office during cutoff weeks. I profiled, fixed the obvious N+1s, added the missing indexes on the high-traffic queries, and consolidated a few report-generation loops into batched queries.
High-traffic reporting endpoints came down by around 60% in response time. The wins were not exotic. They were the standard set of database hygiene tasks that legacy applications never get because nobody is paid to revisit them.
The workflow upgrade nobody asked for
The most durable contribution from this stretch was not a system. It was the change to how the team shipped software.
When I joined, the production deployment story was: copy files via FileZilla, hope nothing on the server changed. There was no version control of substance. A bug fix could overwrite a colleague's in-flight change.
main and ran
composer install and php artisan migrate.
Boring. Transformational.
I paired with three of my colleagues through real changes — not tutorial code — until they were comfortable opening pull requests and rolling back failed deployments. By the time I left for full-time freelance work, the team was operating their own workflow without me in the loop.
What I would do differently
- Standardize on one database earlier. The cross-engine queries between MySQL and SQL Server were the worst-feeling code in the project. A migration plan to land on one engine should have been the first roadmap item, not the last.
- Invest in seed data and integration tests. A government HR system is a forms project. Forms projects rot when nobody owns them. A seed dataset plus a handful of HTTP integration tests on the boring CRUD paths would have made the platform survivable past my tenure with less ceremony.
- Build the field officer Android app as a PWA. Native Java was overkill for what was, in the end, a thin client over the same API. A PWA would have been faster to ship and faster to update.
What it does today
The records platform, the dashboard, and the internal tools are still in production use across provincial offices. The workflow changes — Git, pull requests, code review — outlived my time on the team. That second outcome matters more in the long run than any one feature I shipped.