A Drupal site for a Northwest Territories regulatory body, built around a public registry of land and water use authorizations synced from SharePoint — with an AI assistant prototype in development that can answer questions about both the site and the live registry.
View Site






The Mackenzie Valley Land and Water Board is a Northwest Territories regulatory body that oversees land and water use authorizations across the region. I've worked on MVLWB sites at Kellett Communications for many years — the relationship predates Drupal 10 by a long way. The current site is a single Drupal 10 install serving four boards simultaneously — Mackenzie Valley, Sahtu, Gwich'in, and Wek'èezhìi — each with its own domain, branding, and content via the Domain Access module. It was migrated from a Drupal 7 site where I did much of the original development, including the SharePoint registry sync that remains the centrepiece of the site. The D10 migration was led by other staff at Kellett; I've been the primary developer since taking it over from them.
The centrepiece is a public registry containing over 2,200 authorization records (permits, licences, and similar regulatory decisions) and more than 100,000 associated documents. It's the primary reference point for industry, government, and the public to track active authorizations across the region.
Authorization and document records live in a separate system and are exported as XML. A scheduled Drupal process fetches those exports from configured URLs, unpacks and parses them, and upserts records into custom RegistryAuthorization and RegistryDocument entity types. The sync logs runtimes and errors to Drupal's key-value store; an internal admin report surfaces that history, and a separate error notification system emails admins when something goes wrong.
The public-facing registry is built on those entities: custom Views for the authorization and document listings, faceted search powered by Drupal's Facets module, and a regional map navigator built with OpenLayers. There's a custom module — lwb_ajax_facets — wrapping the Facets integration with enhanced UX: AJAX loading indicators while facets filter, a "Clear Search" reset button injected into the exposed form, and per-facet reset links on active filters. The document tab has its own advanced search, separately filterable by document type, date range, and region.
Public users can subscribe to specific authorizations to receive email notifications when new documents are added. The subscription module handles the full lifecycle: a subscribe form, double opt-in confirmation emails, daily digest notifications via Mailgun, per-subscriber authorization lists used to filter which updates get included in each email, and delivery failure tracking with admin alerts when Mailgun reports bounces or failures.
The notifications run on cron — if the registry sync has produced new documents since the last notification run, subscribers watching those authorizations get an email with a summary of what was added and direct links to the new documents.
Keeping the site stable under automated traffic has become a significant part of the ongoing work. The registry's faceted search creates a large combinatorial space of URLs — bots and AI crawlers that follow links exhaustively find a near-infinite rabbit hole of filter combinations, and they've stopped respecting both nofollow HTML attributes and robots.txt. The result is sustained traffic spikes that hit the database hard. Mitigation has involved a combination of server-side rate limiting, caching strategies, and monitoring to catch crawl storms early. It's the kind of problem that doesn't get resolved once and stay resolved.
A chatbot is in active development on a feature branch, requested by the client. It uses the Drupal AI module and its agents framework, with two tools available to the agent:
sqlite-vec) during development, with Milvus planned for production. This handles general questions about the board and its processes.RegistrySearchTool AiFunctionCall plugin that queries live RegistryAuthorization and RegistryDocument entities directly. Deterministic entity queries rather than vector similarity search, so results can't hallucinate: the tool returns exactly what's in the database.The tool schema is populated dynamically before each Claude call: a normalize() method queries the database for the current distinct values of each enum field — authorization types, statuses, regions, document types — and injects them as constrained choices into the schema. Claude always sees valid, current filter values rather than stale placeholders. The agent runs up to 8 tool loops, using Claude (via the Anthropic API) for reasoning and Ollama locally for embeddings.
The Drupal AI work and Job Scout ended up being directly related. Building the chatbot in PHP — where Claude calls go out, tool use blocks come back, tool results get appended, and the whole thing loops — gave me a concrete understanding of how agentic flows are structured outside of a streaming runtime. That's what I drew on when designing Job Scout's background worker and polling approach: the same tool-loop pattern, but in bare PHP with a MariaDB status table as the message bus instead of a framework handling the state.