Skip to content

API Reference

Auto-generated code documentation.

civic_data_boundaries_us_mn_precincts

__main__

Entry point for civic_data_boundaries_us_mn_precincts CLI.

File: civic_data_boundaries_us_mn_precincts.main

build_layer

Build pipeline for Minnesota precincts.

Reads transformation configuration from the packaged data/us_mn_precincts.yaml resource.

Snapshot version, source date, and input path are supplied dynamically by the refresh workflow.

Outputs under

data-out/states/minnesota/precincts// mn-precincts-full.geojson mn-precincts-web.geojson metadata.json

BuildError

Bases: RuntimeError

Custom exception for build errors in the Minnesota precincts pipeline.

Source code in src/civic_data_boundaries_us_mn_precincts/build_layer.py
37
38
class BuildError(RuntimeError):
    """Custom exception for build errors in the Minnesota precincts pipeline."""

main

main(
    version: str | None = None,
    input_path: Path | None = None,
    snapshot_date: str | None = None,
) -> int

Build the Minnesota precinct layer.

Parameters:

Name Type Description Default
version str | None

Snapshot version used for output directory naming.

None
input_path Path | None

Optional explicit source GeoJSON path.

None
snapshot_date str | None

Source snapshot date in YYYY-MM-DD format.

None

Returns:

Type Description
int

0 when the build succeeds; otherwise 1.

Source code in src/civic_data_boundaries_us_mn_precincts/build_layer.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
def main(
    version: str | None = None,
    input_path: Path | None = None,
    snapshot_date: str | None = None,
) -> int:
    """Build the Minnesota precinct layer.

    Args:
        version: Snapshot version used for output directory naming.
        input_path: Optional explicit source GeoJSON path.
        snapshot_date: Source snapshot date in YYYY-MM-DD format.

    Returns:
        0 when the build succeeds; otherwise 1.
    """
    try:
        build_cfg = _load_build_cfg()

        if not version:
            raise BuildError("version is required")

        if input_path is not None:
            source_path = input_path
        else:
            source_path = (
                get_data_in_dir() / "states" / "minnesota" / f"precincts_{version}.json"
            )

        if not source_path.exists():
            raise BuildError(f"Input not found: {source_path}")

        out_dir = _out_dir(version)

        gdf: gpd.GeoDataFrame = gpd.read_file(source_path)

        gdf = _normalize_columns(
            gdf,
            to_lower=bool(build_cfg.get("fields_lowercase", True)),
            trim=bool(build_cfg.get("fields_trim", True)),
        )

        gdf = _rename_columns(
            gdf,
            mapping=build_cfg.get("fields_rename") or {},
        )

        gdf = _keep_columns(
            gdf,
            keep=build_cfg.get("fields_keep") or [],
        )

        add_fields: dict[str, Any] = {
            "snapshot_version": version,
        }

        if snapshot_date is not None:
            add_fields["snapshot_date"] = snapshot_date

        gdf = _add_constant_fields(
            gdf,
            add_fields=add_fields,
        )

        if bool(build_cfg.get("repair_geometries", True)):
            gdf = _repair_geometries(gdf)

        full_path = out_dir / "mn-precincts-full.geojson"

        gdf.to_file(
            full_path,
            driver="GeoJSON",
        )

        logger.info(f"Wrote full: {full_path}")

        web_geojson_name = "mn-precincts-web.geojson"
        web_geojson_path = out_dir / web_geojson_name

        shutil.copy2(
            full_path,
            web_geojson_path,
        )

        logger.info(f"Wrote web GeoJSON: {web_geojson_path}")

        topo_name: str | None = None

        if bool(build_cfg.get("write_topojson", False)):
            executable = _which_mapshaper()

            if executable:
                topo_path = out_dir / "mn-precincts-web.topojson"

                topo_output = _write_topojson(
                    executable,
                    web_geojson_path,
                    topo_path,
                    simplify_pct=_clamped_pct(build_cfg.get("simplify_pct", 0)),
                )

                topo_name = topo_output.name if topo_output else None
            else:
                logger.warning("mapshaper not found; skipping TopoJSON.")

        _write_metadata(
            full_path=full_path,
            web_name=web_geojson_name,
            topo_name=topo_name,
            out_dir=out_dir,
            version=version,
            snapshot_date=snapshot_date,
        )

        logger.info("Build completed.")
        return 0

    except BuildError as exc:
        logger.error(f"Build failed: {exc}")
        return 1

cli

cli

CLI for MN precincts workflow (GeoJSON-in -> data-out).

Commands

civic-us-mn build --version 2025-04 civic-us-mn validate --version 2025-04 civic-us-mn index

cmd_build
cmd_build(
    version: str = typer.Option(
        ...,
        "--version",
        "-v",
        help="Snapshot tag like 2025-04",
    ),
) -> None

Build the MN precincts layer for a given snapshot version.

Parameters

version : str Snapshot tag like '2025-04'.

Source code in src/civic_data_boundaries_us_mn_precincts/cli/cli.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@app.command("build")
def cmd_build(
    version: str = typer.Option(
        ..., "--version", "-v", help="Snapshot tag like 2025-04"
    ),
) -> None:
    """Build the MN precincts layer for a given snapshot version.

    Parameters
    ----------
    version : str
        Snapshot tag like '2025-04'.
    """
    code = build_layer.main(version=version)
    raise typer.Exit(code)
cmd_index
cmd_index() -> None

Run the index command to build the MN precincts index.

Source code in src/civic_data_boundaries_us_mn_precincts/cli/cli.py
62
63
64
65
66
@app.command("index")
def cmd_index() -> None:
    """Run the index command to build the MN precincts index."""
    code = index_mod.main()
    raise typer.Exit(code)
cmd_refresh
cmd_refresh() -> None

Refresh from the current official Minnesota statewide precinct source.

Source code in src/civic_data_boundaries_us_mn_precincts/cli/cli.py
38
39
40
41
42
@app.command("refresh")
def cmd_refresh() -> None:
    """Refresh from the current official Minnesota statewide precinct source."""
    code = refresh.main()
    raise typer.Exit(code)
cmd_validate
cmd_validate(
    version: str = typer.Option(
        ...,
        "--version",
        "-v",
        help="Snapshot tag like 2025-04",
    ),
) -> None

Validate the MN precincts layer for a given snapshot version.

Parameters

version : str Snapshot tag like '2025-04'.

Source code in src/civic_data_boundaries_us_mn_precincts/cli/cli.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@app.command("validate")
def cmd_validate(
    version: str = typer.Option(
        ..., "--version", "-v", help="Snapshot tag like 2025-04"
    ),
) -> None:
    """Validate the MN precincts layer for a given snapshot version.

    Parameters
    ----------
    version : str
        Snapshot tag like '2025-04'.
    """
    code = validate.main(version=version)
    raise typer.Exit(code)
main
main() -> int

Entry point for the MN Precincts CLI.

Runs the Typer app and handles exceptions, returning an appropriate exit code.

Source code in src/civic_data_boundaries_us_mn_precincts/cli/cli.py
69
70
71
72
73
74
75
def main() -> int:
    """Entry point for the MN Precincts CLI.

    Runs the Typer app and handles exceptions, returning an appropriate exit code.
    """
    app()
    return 0

index

Generates index files and summary metadata in data-out/.

  • data-out/index.json: flat listing of all GeoJSONs with bbox and feature counts
  • data-out/states/minnesota/index.json: layer pointers to latest versions
  • data-out/states/minnesota/precincts//metadata.json: written by build
CLI

uv run python -m civic_data_boundaries_us_mn_precincts.index

IndexBuildError

Bases: Exception

Custom exception for errors encountered during index building.

Source code in src/civic_data_boundaries_us_mn_precincts/index.py
23
24
class IndexBuildError(Exception):
    """Custom exception for errors encountered during index building."""

build_index_main

build_index_main() -> int

Scan data-out for GeoJSON, compute metadata, generate index and manifest.

Workflow: 1. Scans the output directory for all GeoJSON files. 2. For each file, computes its bounding box and feature count. 3. Writes a flat index of all files to 'index.json'. 4. Writes a dataset-level manifest to 'manifest.json'. 5. Writes a state-level index to 'states/minnesota/index.json'.

Returns:

Name Type Description
int int

0 if the index build succeeds, 1 if an error occurs.

Source code in src/civic_data_boundaries_us_mn_precincts/index.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def build_index_main() -> int:
    """Scan data-out for GeoJSON, compute metadata, generate index and manifest.

    Workflow:
    1. Scans the output directory for all GeoJSON files.
    2. For each file, computes its bounding box and feature count.
    3. Writes a flat index of all files to 'index.json'.
    4. Writes a dataset-level manifest to 'manifest.json'.
    5. Writes a state-level index to 'states/minnesota/index.json'.

    Returns:
        int: 0 if the index build succeeds, 1 if an error occurs.
    """
    try:
        out_root = get_data_out_dir()
        flat_index: list[dict[str, Any]] = []

        logger.info(f"Scanning {out_root} for GeoJSONs...")
        for geojson in out_root.rglob("*.geojson"):
            bbox = _compute_bbox(geojson)
            nfeat = _compute_feature_count(geojson)
            flat_index.append(
                {
                    "path": geojson.relative_to(out_root).as_posix(),
                    "bbox": bbox,
                    "features": nfeat,
                }
            )

        # data-out/index.json
        index_file = out_root / "index.json"
        with index_file.open("w", encoding="utf-8") as f:
            json.dump(flat_index, f, indent=2)
        logger.info(f"Flat index written: {index_file} ({len(flat_index)} files)")

        # data-out/manifest.json (dataset-level)
        _write_manifest(out_root, flat_index)

        # data-out/states/minnesota/index.json (layer pointers)
        _write_state_index(out_root)

        return 0

    except Exception as e:  # noqa: BLE001
        logger.error(f"Index build failed: {e}")
        return 1

main

main() -> int

Execute the main entry point for building the index.

Returns:

Name Type Description
int int

0 if the index was built successfully, 1 if an error occurs.

Source code in src/civic_data_boundaries_us_mn_precincts/index.py
159
160
161
162
163
164
165
def main() -> int:
    """Execute the main entry point for building the index.

    Returns:
        int: 0 if the index was built successfully, 1 if an error occurs.
    """
    return build_index_main()

refresh

Refresh Minnesota precinct data from the official statewide source.

RefreshError

Bases: RuntimeError

Raised when the Minnesota precinct refresh cannot be completed.

Source code in src/civic_data_boundaries_us_mn_precincts/refresh.py
22
23
class RefreshError(RuntimeError):
    """Raised when the Minnesota precinct refresh cannot be completed."""

main

main() -> int

Download, build, validate, and index the current Minnesota precinct snapshot.

Source code in src/civic_data_boundaries_us_mn_precincts/refresh.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def main() -> int:
    """Download, build, validate, and index the current Minnesota precinct snapshot."""
    try:
        source_url = _load_source_url()

        logger.info(f"Fetching Minnesota precinct source: {source_url}")
        data = _download(source_url)

        source_date, version = _read_source_metadata(data)

        logger.info(f"Source date: {source_date}")
        logger.info(f"Snapshot version: {version}")

        if _already_current(version, source_date):
            logger.info("Snapshot is already current.")
            return 0

        input_path = (
            get_data_in_dir() / "states" / "minnesota" / f"precincts_{version}.json"
        )
        input_path.parent.mkdir(parents=True, exist_ok=True)
        input_path.write_bytes(data)

        if (
            build_layer.main(
                version=version,
                input_path=input_path,
                snapshot_date=source_date,
            )
            != 0
        ):
            raise RefreshError("Build failed")

        if validate.main(version=version) != 0:
            raise RefreshError("Validation failed")

        if index_mod.main() != 0:
            raise RefreshError("Index generation failed")

        logger.info(f"Refresh completed successfully for {version}.")
        return 0

    except (RefreshError, OSError, URLError, ValueError, yaml.YAMLError) as exc:
        logger.error(f"Refresh failed: {exc}")
        return 1

utils

get_paths

civic_data_boundaries_us_mn_precincts.utils.get_paths.

Utilities for resolving paths to various data directories in the civic_data_boundaries_us_mn_precincts package.

get_cd118_in_dir
get_cd118_in_dir() -> Path

Return the folder under data-in/ where raw CD118 shapefiles are extracted.

Source code in src/civic_data_boundaries_us_mn_precincts/utils/get_paths.py
39
40
41
def get_cd118_in_dir() -> Path:
    """Return the folder under data-in/ where raw CD118 shapefiles are extracted."""
    return get_tiger_in_dir() / "tl_2022_us_cd118"
get_cd118_out_dir
get_cd118_out_dir() -> Path

Return the directory under data-out/national/ where CD118 geojsons are stored.

Source code in src/civic_data_boundaries_us_mn_precincts/utils/get_paths.py
65
66
67
def get_cd118_out_dir() -> Path:
    """Return the directory under data-out/national/ where CD118 geojsons are stored."""
    return get_national_out_dir()
get_data_in_dir
get_data_in_dir() -> Path

Return the root data-in directory for raw input data (downloads, archives).

Source code in src/civic_data_boundaries_us_mn_precincts/utils/get_paths.py
29
30
31
def get_data_in_dir() -> Path:
    """Return the root data-in directory for raw input data (downloads, archives)."""
    return get_repo_root() / "data-in"
get_data_out_dir
get_data_out_dir() -> Path

Return the root data-out directory for processed GeoJSON and chunked outputs.

Source code in src/civic_data_boundaries_us_mn_precincts/utils/get_paths.py
47
48
49
def get_data_out_dir() -> Path:
    """Return the root data-out directory for processed GeoJSON and chunked outputs."""
    return get_repo_root() / "data-out"
get_national_out_dir
get_national_out_dir() -> Path

Return the directory under data-out/ where national-level files are written.

Includes layers like national states, counties, or CD118 merged geojsons.

Source code in src/civic_data_boundaries_us_mn_precincts/utils/get_paths.py
57
58
59
60
61
62
def get_national_out_dir() -> Path:
    """Return the directory under data-out/ where national-level files are written.

    Includes layers like national states, counties, or CD118 merged geojsons.
    """
    return get_data_out_dir() / "national"
get_repo_root
get_repo_root() -> Path

Return the working directory used for data input and output.

Source code in src/civic_data_boundaries_us_mn_precincts/utils/get_paths.py
21
22
23
def get_repo_root() -> Path:
    """Return the working directory used for data input and output."""
    return Path.cwd()
get_states_out_dir
get_states_out_dir() -> Path

Return the directory under data-out/ where per-state folders are written.

Source code in src/civic_data_boundaries_us_mn_precincts/utils/get_paths.py
52
53
54
def get_states_out_dir() -> Path:
    """Return the directory under data-out/ where per-state folders are written."""
    return get_data_out_dir() / "states"
get_tiger_in_dir
get_tiger_in_dir() -> Path

Return the folder under data-in/ where TIGER shapefiles are stored after download and extraction.

Source code in src/civic_data_boundaries_us_mn_precincts/utils/get_paths.py
34
35
36
def get_tiger_in_dir() -> Path:
    """Return the folder under data-in/ where TIGER shapefiles are stored after download and extraction."""
    return get_data_in_dir() / "tiger"

validate

Validate MN precincts outputs in data-out/.

Checks: - required files exist for a given snapshot version - GeoJSON loads; geometry not empty - CRS is EPSG:4326 (WGS84 lon/lat) - required columns present after build - optional: precinct_id uniqueness if present

Usage

uv run python -m civic_data_boundaries_us_mn_precincts.validate --version 2025-04

ValidateError

Bases: RuntimeError

Custom error class for validation errors.

Source code in src/civic_data_boundaries_us_mn_precincts/validate.py
43
44
class ValidateError(RuntimeError):
    """Custom error class for validation errors."""

main

main(version: str) -> int

Validate MN precincts outputs for the specified snapshot version.

Source code in src/civic_data_boundaries_us_mn_precincts/validate.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def main(version: str) -> int:
    """Validate MN precincts outputs for the specified snapshot version."""
    try:
        out_dir = _out_dir(version)
        _require_files(out_dir, REQUIRED_FILES)

        full_path = out_dir / "mn-precincts-full.geojson"
        gdf = _load_gdf(full_path)

        _require_columns(gdf, REQUIRED_COLUMNS)
        _check_precinct_id_consistency(gdf, "precinct_id")

        logger.info("Validation passed.")
        return 0
    except ValidateError as exc:
        logger.error(f"Validation failed: {exc}")
        return 1