Browse

Laravel rebuild · 2026

Live

Competition Operations Platform

  • Laravel 12
  • PHP 8.2
  • Filament 5
  • MariaDB InnoDB
  • Tailwind 4
record
projects/biljartv2
status
live
type
operational_rebuild
role
solo -- product + backend
constraint
non_developer_operator
stack
Laravel 12, PHP 8.2, Filament 5, MariaDB InnoDB, Tailwind 4
A typed import pipeline and purpose-built operator admin let the club secretary run the complete seasonal lifecycle without filing a request, and every data change traces back to a file upload, an approval action, and a named account.

"Rondom het Stroomkanaal" is a regional billiards competition running a two-division league and a cup bracket annually, across 10–20 teams. The competition is run by club volunteers, and the club secretary manages the full operational calendar.

A 2022 admin rebuild made weekly operations safe to run: results moved into typed JSON files, public pages regenerated automatically, and the club secretary stopped editing HTML. But the flat-file model stayed in place. Results and standings lived in separate files with no enforced consistency, no audit trail, and no canonical record to diff against. Tracing an error still meant reading page content by hand.

Three years of running that system made the remaining gaps explicit. Structural operations such as loading a new fixture calendar, correcting a rescheduled match, or rolling over to a new season still required someone with server access. After three seasons, the limits were clear enough to justify a full rebuild.


The rebuild is a data operations system built for the club secretary, not the developer. The question it answers: what does it take for the club secretary to run the full seasonal lifecycle, including teams, fixtures, results, and archive management, without filing a ticket or touching a database?

Every season starts with an Excel workbook. That file goes through a five-step typed pipeline: proof, stage, review, approve, publish. Validation runs before any database write. A diff step classifies every row before publication and blocks if any existing result would be silently overwritten. Publication is transactional, so partial writes are not possible.

In-season updates go through purpose-built bulk entry forms, not re-imports. Public pages read directly from canonical tables with no manual deployment step. Season visibility is controlled independently, so next year's fixtures can be loaded and approved without affecting what the public currently sees.

The scope is deliberate. This is not a CMS or a general admin tool. It is a purpose-built system for one operational task, with hard constraints on what can be changed and a full record of everything that was.

The import batch diff view showing staged fixture rows classified by change type before publication is allowed. Columns show home team, away team, round, and the classified diff status per row.
/admin/importoverzicht shows per-row diff classification before any fixture publication. Overwrite-risk rows block publication until the operator explicitly acknowledges.
The bulk result entry admin page showing match rows for a competition round. Each row has team names, score input fields, and a Gespeeld (played) status badge. A bye row is marked as not enterable. Dutch-language sidebar shows Wedstrijddata, Import, and Seizoenen sections.
/admin/bulk-ronde-invoer is the form the club secretary uses for weekly result entry. Scores and status only; structure is immutable after publish.

Mechanism Behaviour
Import pipeline Five named steps guide the process: proof, stage, review, approve, and publish. Each step has an explicit outcome. Validation runs before any database write. Missing team references are hard blocking errors. Publication is transactional, so partial publishes are not possible.
Overwrite protection Before any publication, every staged row is classified across eight categories. Rows that would replace an existing score are flagged as overwrite risk. Publication stays blocked until the operator explicitly acknowledges the risk, so there is no silent result overwrite.
Audit trail Every import publication and every in-season edit is logged with full before/after JSON snapshots. Each entry records the event type, timestamp, and the acting account. Any data point traces to a file upload, an approval action, and a named user.
Edit scope restriction Post-publication, only scores and status are editable. Teams, dates, and round assignments are immutable. The service layer enforces this, and there is no UI path to structural changes after publish. Fixture integrity is preserved without relying on user discipline.
Seasonal isolation Publishing the upcoming season's fixtures does not change what the public sees. Season activation is a separate explicit action. Next year's calendar can be loaded, reviewed, and approved while the current season remains live.

Item Detail
241 fixtures 182 league + 59 cup, validated through the full pipeline in the end-to-end test suite
Import lifecycle tests Tests assert publication counts, approval blocking behavior, and diff classification per row
Batch provenance Every canonical row carries import_batch_id, published_from_batch_id, and SHA-256 file hash of the source workbook
Deployed Live with the 2026/2027 season

record: projects/biljartv2 | status: live | type: operational_rebuild

Rebuilds, workflow systems, and the backend work that keeps running after launch.

LinkedIn GitHub © 2026 Gerben van der Velde