Syed Kazmi
Colour theme

← Selected work

CEPA Environmental Registry search

Role
IT Specialist, Registry and Search team, ECCC
Period
May 2025 to August 2026
Outcome
Five bilingual search pages in production over a corpus of more than 100,000 records
Stack
  • Vue 3
  • TypeScript
  • Pinia
  • .NET 8
  • Azure AI Search
  • SQL Server
  • Drupal 10

Problem

The CEPA Environmental Registry is the public record of what Canada regulates under the Canadian Environmental Protection Act. Substances, instruments, documents, regulations, public consultations, permits, codes of practice: more than 100,000 records, all of which somebody has a legal or professional reason to find.

Finding them was the hard part. The records live in different shapes, come from different upstream systems, and mean different things. A substance is not a consultation and cannot be searched as if it were. Building five separate search experiences would have meant five sets of bugs and five things to keep in sync.

Constraints

  • Bilingual is not optional. Every page, every route, every filter label, and every result has to exist in English and French. That is a legal requirement for a Government of Canada service, not a feature request.
  • Two content systems. Authors work in a Drupal 10 site. The public reads a Vue 3 single page app. Neither one owns the data outright.
  • Search is a separate index. Azure AI Search sits in front of SQL Server, so nothing is truly live. Anything published has to make it through a pipeline before a member of the public can find it.
  • Accessibility. Government of Canada services are held to WCAG standards, and the GC Design System exists to enforce that.

What I built

Five public search pages for substances, instruments, documents, regulations, and public consultations. Rather than write five, I built one search template and specialised it. The shared pieces are the parts that are genuinely the same everywhere: result cards, filter panels, pagination, autocomplete, and bilingual routing. What differs per record type is the field mapping and the facets.

The front end is Vue 3 with TypeScript, Pinia for search state, Vue Router for the bilingual routes, vue-i18n for content, and Axios against the API.

The API layer is .NET 8 Azure Functions in C#: item retrieval, publishing workflows, relationship expansion so a substance can carry its related instruments, and the indexing path into Azure AI Search. EF Core over SQL Server, OpenAPI for the contract, Docker for local development against the Azure Search emulator.

The data path underneath. I worked on the CEPA migration and publishing pipelines: SQL Server schemas, stored procedures, and the item, revision, and publish-event tables, plus DataHub imported datasets that feed the index.

Where the bugs actually lived. A defect in a system like this rarely stays in one layer. The ones I spent the most time on crossed the Vue site, the Drupal WxT admin site, the Functions API, SQL Server, and the search emulator at once. Most of what I fixed was in search indexing, routing, and template rendering.

Release quality. xUnit integration tests, Azure DevOps CI, and SonarQube analysis, inside a normal Agile sprint cadence.

Decisions and tradeoffs

One search template instead of five pages. The five record types share roughly eighty percent of their search behaviour, so five independent implementations would have meant fixing pagination five times. The cost is that the template has to carry per type configuration, and a genuinely unusual record type would have to fight it. That tradeoff was worth taking with five types. It would not be at fifteen.

All projectsEmail me about this