YESAB is the Yukon Environmental and Socio-economic Assessment Board, which tracks assessment projects across Yukon. This repository exists to pull the published project map data and registry metadata into reproducible local artifacts so it is easier to inspect, rebuild, and share static map outputs without depending on the live services at runtime.
These tools are an independent consumer of YESAB information, it is not associated with YESAB.
- downloading the YESAB Project Map - shapefile archive
- fetching and caching the YESAB registry API in year buckets
- joining the shapefile archive and registry API data into a single GeoPackage, filtering approximate locations into their own class
- building static map outputs from the previous steps
- building a query-friendly SQLite database and Datasette metadata file for ad-hoc offline exploration
The workhorse or main value of the project is the combined shapefile and registry API data in a single geospatial package. It is one step of a larger internal ETL (extract, transform, load) pipeline. The maps are demos, toys made to quickly view the data and illustrate the concept of a self-contained dynamic map.
Example of refreshing and building the GeoPackage from scratch:
❯ uv run scripts/refresh_and_build_geopackage.py
Step 1/3: refresh YESAB project map archive
Checking YESAB all.zip...
Local dataset missing, forcing download: /var/home/matt/dev/yesab/data/yesab_all.zip
Download complete
State updated: {'last_modified': 'Mon, 11 May 2026 19:26:06 GMT', 'content_length': '2333858'}
Step 2/3: refresh YESAB API cache
Reusing cached 2026-2026: /var/home/matt/dev/yesab/data/api/buckets/projects_2026-2026.json.zst
State file : /var/home/matt/dev/yesab/data/api/state.json
Merged cache : 4617 projects from 7 bucket(s)
Merged dataset: /var/home/matt/dev/yesab/data/api/projects_merged.json.zst
Bucket changes:
- 2026-2026: not checked for remote changes (reused local cache; run with --force to fetch and compare)
- 2005-2005 : /var/home/matt/dev/yesab/data/api/buckets/projects_2005-2005.json.zst
- 2006-2010 : /var/home/matt/dev/yesab/data/api/buckets/projects_2006-2010.json.zst
- 2011-2015 : /var/home/matt/dev/yesab/data/api/buckets/projects_2011-2015.json.zst
- 2016-2020 : /var/home/matt/dev/yesab/data/api/buckets/projects_2016-2020.json.zst
- 2021-2025 : /var/home/matt/dev/yesab/data/api/buckets/projects_2021-2025.json.zst
- 2025-2025 : /var/home/matt/dev/yesab/data/api/buckets/projects_2025-2025.json.zst
- 2026-2026 : /var/home/matt/dev/yesab/data/api/buckets/projects_2026-2026.json.zst
Step 3/3: build GeoPackage
Wrote /var/home/matt/dev/yesab/out/yesab-projects.gpkg
Projects_Linear: 310 features
Projects_Placer: 1133 features
Projects_Points: 1093 features
Projects_Polygons: 787 features
Projects_Quartz: 462 features
API_Approximate_Points: 1263 features
Example of building a single self-contained map:
❯ uv run scripts/build_static_map_single.py
Wrote out/yesab-map-in-one.html with 6 layers and 5048 features.
Output size: 19.6 MB
YESAB shapefile date: 2026-05-11 12:26 YST
Latest registry change: 2026-04-21 13:53 YST (2025-0172, Adequacy Review Response ended)
Wrote QA artifacts: yesab-map-in-one.qa.html, yesab-map-in-one.qa.json
Build the optional browser-native compressed wrapper when you need a smaller single-file artifact for sharing:
❯ uv run scripts/build_static_map_single.py --compressed
Wrote out/yesab-map-in-one.html with 6 layers and 5048 features.
Output size: 19.6 MB
Wrote compressed wrapper: out/yesab-map-in-one.compressed.html (5.4 MB)
...
The compressed wrapper embeds a gzip copy of the generated
yesab-map-in-one.html bytes and reconstructs the original document in browsers
that support DecompressionStream("gzip"). Keep yesab-map-in-one.html as the
canonical compatibility artifact.
Example of building and opening the optional Datasette explorer:
uv run --python 3.14 .\scripts\build_datasette_explorer.py
yesab-explorerThe explorer output is meant for questions that are heavier than the static web map but too small to justify a custom script. It creates normalized project, location, sector, district, map-feature, and downloaded-project-bundle tables, adds full-text search on projects, and writes Datasette facets plus canned queries for active projects, counts by sector/district, map join summaries, unmapped projects, and downloaded bundle documents. Its cluster maps use a local Datasette plugin to redirect Leaflet XYZ tile requests to Government of Yukon ArcGIS export URLs for the same topographic and shaded-relief basemap services used by the static map.
The yesab-explorer launcher starts Datasette with the generated default
database, metadata, local plugins, datasette-cluster-map, and bundle static
file mount. It also builds missing default explorer artifacts before starting
Datasette. Use wrapper options such as --db, --metadata, --plugins-dir,
--bundle-root, --rebuild, or any Datasette option such as --port 8011 for
tuning.
The launcher serves downloaded bundle
attachments below /bundles/. The explorer stores both the local filesystem
path and a URL-encoded Datasette path such as
/bundles/2025-0069/attachments/proposal.pdf; PDFs and images open in the
browser when supported, while other file types remain available for download.
Summary: Project points and areas from Yukon Environmental and Socio-economic Assessment Board, with YESAB registry metadata when a project-number match is available. Uses approximate point locations when no shapefile geometry exists.
Description: {todo: insert data sources}
scripts/download_project_map_archive.py- Downloadsall.ziponly when the remote file changed.scripts/refresh_api_cache.py- Caches YESAB API project records into local year-bucket Zstandard-compressed JSON files and writes a merged dataset.scripts/download_project_bundle.py- Mirrors the public Registry project page APIs into one local JSON-plus-attachments bundle for a project URL, ID, or number, with manifest-based attachment reuse,.partwrites, pacing, retries, and--force. Seedocs/project-bundle-downloads.mdfor attachment heuristics.scripts/build_geopackage.py- Builds an enriched GeoPackage shapefile/API joins and approximate API-only points.scripts/refresh_and_build_geopackage.py- wrapper for downloading the latest map archive, refreshing the API cache, and building the GeoPackage in one command.scripts/deploy_to_production.py- Mirrors the deployable code subset to the production ETL workspace.scripts/build_static_map_single.py- Builds a single self-contained HTML file, with an optional compressed wrapper.scripts/build_static_map_split.py- Builds a multi-file static site with separate HTML, CSS, JS, and layer data files.scripts/build_datasette_explorer.py- Buildsout/yesab-explorer.dband Datasette metadata for searchable offline project exploration.
Run commands from the repository root. If you omit output arguments, the builders write safely into ./out without clobbering each other.
Output arguments differ by builder:
scripts/build_static_map_single.pyaccepts either an.htmlfile path or a directory. Directory output writesyesab-map-in-one.htmlinside that directory.scripts/build_static_map_single.py --compressedalso writesyesab-map-in-one.compressed.htmlnext to the single-file output. Use--compressed-outputto choose a different compressed.htmlpath or output directory.scripts/build_static_map_split.pyaccepts an output directory and recreates that directory before writing.scripts/build_geopackage.pyaccepts a.gpkgfile path.
Use uv with Python 3.14+.
uv run .\scripts\download_project_map_archive.py
uv run .\scripts\download_project_bundle.py https://yesabregistry.ca/projects/00ba642c-2cef-4a75-8412-6afa6ab76487/
uv run .\scripts\download_project_bundle.py 2025-0069 --zip
uv run .\scripts\download_project_bundle.py 2025-0069 --force --retry-count 4
uv run .\scripts\refresh_api_cache.py
uv run .\scripts\refresh_api_cache.py --force
uv run .\scripts\refresh_api_cache.py --start-year 2024 --end-year 2025 --force
uv run .\scripts\refresh_api_cache.py --years 2022 2023 2024 --force
uv run .\scripts\build_static_map_single.py
uv run .\scripts\build_static_map_single.py --compressed
uv run .\scripts\build_static_map_single.py .\some-output-dir
uv run .\scripts\build_static_map_single.py .\some-output-dir --compressed-output .\some-output-dir\shared-map.html
uv run .\scripts\build_static_map_split.py
uv run .\scripts\build_static_map_split.py .\some-output-dir
uv run .\scripts\build_datasette_explorer.py
uv run .\scripts\build_datasette_explorer.py .\out\custom-explorer.db --no-map-features
yesab-explorer
yesab-explorer --port 8011
yesab-explorer --db .\out\custom-explorer.db --metadata .\out\custom-explorer.metadata.json
uv run .\scripts\build_geopackage.py
uv run .\scripts\build_geopackage.py .\some-output.gpkg
uv run .\scripts\refresh_and_build_geopackage.py
uv run .\scripts\refresh_and_build_geopackage.py .\some-output.gpkg
uv run .\scripts\refresh_and_build_geopackage.py --force
uv run .\scripts\refresh_and_build_geopackage.py --years 2024 2025 --force .\some-output.gpkgThe production ETL code workspace is \\envgeoserver\dev\YESAB\yesab_map.
Preview the deploy plan without copying files:
uv run .\scripts\deploy_to_production.pyDeploy the current clean checkout:
uv run .\scripts\deploy_to_production.py --goThe deploy tool defaults to dry-run mode. Dry-run reports the selected files, mirror deletion behavior, and, when the checkout is dirty, separate scenarios for running with and without --allow-dirty. With --go, it runs tests, stages an allowlisted source subset, mirrors that subset into the dedicated production code directory, writes deploy_manifest.json, and runs a --help smoke check from the deployed copy. Mirror mode removes destination-only files under yesab_map-toy-maker. It intentionally excludes generated outputs, metrics, git metadata, and API cache state.
Non-default destinations are rejected unless --allow-any-dest is passed.
For in-progress handoff from a dirty checkout, use:
uv run .\scripts\deploy_to_production.py --go --allow-dirtyRun the regression tests with the same Python version the builders require:
uv run --python 3.14 python -m unittest discover -s testsFor new join, QA, API fallback, or details-panel behavior, add a failing fixture-style test first, then make both builders pass through the shared helper path.
Typical workflow:
- Refresh the shapefile archive when needed.
- Refresh the API cache.
- Rebuild one or both map outputs.
Default output locations:
scripts/download_project_map_archive.pywrites:data/yesab_all.zipdata/yesab_all_zip.state.json
scripts/refresh_api_cache.pywrites:data/api/buckets/projects_<start>-<end>.json.zstdata/api/projects_merged.json.zstdata/api/state.json
scripts/download_project_bundle.pywrites:out/project-bundles/<project-id-or-number>/manifest.jsonout/project-bundles/<project-id-or-number>/json/out/project-bundles/<project-id-or-number>/attachments/out/project-bundles/<project-id-or-number>.zipwhen--zipis used
scripts/build_static_map_single.pywrites:out/yesab-map-in-one.htmlout/yesab-map-in-one.compressed.htmlwhen--compressedor--compressed-outputis usedout/yesab-map-in-one.qa.htmlout/yesab-map-in-one.qa.json
scripts/build_static_map_split.pywrites:out/yesab-map/index.htmlout/yesab-map/app.cssout/yesab-map/app.jsout/yesab-map/data/out/yesab-map/qa_report.htmlout/yesab-map/qa_report.json
scripts/build_geopackage.pywrites:out/yesab-projects.gpkg
scripts/build_datasette_explorer.pywrites:out/yesab-explorer.dbout/yesab-explorer.metadata.json
scripts/refresh_and_build_geopackage.pywrites the same download, API cache, and GeoPackage outputs as the three scripts it chains.
The split builder removes and recreates only its own target directory before writing files.
scripts/refresh_api_cache.py defaults to refreshing the current year bucket only.
Older cache buckets stay on disk until you explicitly refresh them with --force.
This keeps the sync logic simple while still updating the projects most likely to change.
Refresh API cache buckets sequentially. The script uses a shared data/api/state.json file and is not designed for concurrent writers.
Each run prints a bucket change summary for requested buckets. Fetched buckets are compared with the previous local bucket snapshot and report new, changed, and removed project records; reused buckets are marked as not checked because no remote request was made.
As of June 8, 2026, scripts/refresh_api_cache.py uses the versioned
/api/v1/integration/projects endpoint. The cache normalizes the v1 project
field names back to the legacy shape consumed by the map builders.
- Python
3.14+is required for stdlibcompression.zstdsupport. - If
data/api/projects_merged.json.zstexists, both map builders will enrich matching features with YESAB registry metadata. - QA reports are generated with both builders so you can inspect map/API coverage and unmatched records. The map About panel includes the compact QA coverage summary; the separate QA HTML/JSON artifacts contain the detailed project lists.
- Investigate whether the GeoPackage build should consume a shared data-preparation layer directly instead of depending on
scripts/build_static_map_single.py. The current dependency works and keeps behavior aligned, but the pipeline order is surprising: static-map assembly now acts as the input path for the GIS artifact.
This repo uses lightweight agent-session, command-run, and decision metrics under metrics/.
uv run .\scripts\run_timed.py --task-id tests -- uv run --python 3.14 python -m unittest discover -s tests
uv run .\scripts\summarize_metrics.pyBuilt by Matt Wilkie at Yukon department of Environment. The code is open-source and available under the MIT and Yukon Open Government licenses. Provided as-is, without warranty or support.
