gri-multitrack 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. gri_multitrack-0.1.0/.docs_other_projects.md +85 -0
  2. gri_multitrack-0.1.0/.githooks/pre-push +18 -0
  3. gri_multitrack-0.1.0/.gitignore +36 -0
  4. gri_multitrack-0.1.0/.gitlab-ci-deps.yml +18 -0
  5. gri_multitrack-0.1.0/.gitlab-ci.yml +99 -0
  6. gri_multitrack-0.1.0/.hatch_build.py +46 -0
  7. gri_multitrack-0.1.0/.pre-commit-config.yaml +47 -0
  8. gri_multitrack-0.1.0/.pre-commit-unskipped.py +38 -0
  9. gri_multitrack-0.1.0/.python-version +1 -0
  10. gri_multitrack-0.1.0/.ruff.toml +52 -0
  11. gri_multitrack-0.1.0/.vscode/settings.json +15 -0
  12. gri_multitrack-0.1.0/CONTRIBUTING.md +124 -0
  13. gri_multitrack-0.1.0/CONVOLVE.md +555 -0
  14. gri_multitrack-0.1.0/LICENSE +21 -0
  15. gri_multitrack-0.1.0/MULTITRACK.md +344 -0
  16. gri_multitrack-0.1.0/PKG-INFO +146 -0
  17. gri_multitrack-0.1.0/PLAN.md +282 -0
  18. gri_multitrack-0.1.0/README.md +113 -0
  19. gri_multitrack-0.1.0/examples/two_target_demo.py +83 -0
  20. gri_multitrack-0.1.0/gri_multitrack/__init__.py +100 -0
  21. gri_multitrack-0.1.0/gri_multitrack/association.py +196 -0
  22. gri_multitrack-0.1.0/gri_multitrack/cardinality.py +68 -0
  23. gri_multitrack-0.1.0/gri_multitrack/ingest.py +232 -0
  24. gri_multitrack-0.1.0/gri_multitrack/lifecycle.py +262 -0
  25. gri_multitrack-0.1.0/gri_multitrack/mfa.py +436 -0
  26. gri_multitrack-0.1.0/gri_multitrack/output.py +255 -0
  27. gri_multitrack-0.1.0/gri_multitrack/scoring.py +165 -0
  28. gri_multitrack-0.1.0/gri_multitrack/track.py +303 -0
  29. gri_multitrack-0.1.0/gri_multitrack/tracker.py +186 -0
  30. gri_multitrack-0.1.0/pyproject.toml +103 -0
  31. gri_multitrack-0.1.0/report.xml +1 -0
  32. gri_multitrack-0.1.0/test/__init__.py +0 -0
  33. gri_multitrack-0.1.0/test/_scene.py +113 -0
  34. gri_multitrack-0.1.0/test/test_association.py +59 -0
  35. gri_multitrack-0.1.0/test/test_cardinality.py +43 -0
  36. gri_multitrack-0.1.0/test/test_ingest.py +92 -0
  37. gri_multitrack-0.1.0/test/test_lifecycle.py +84 -0
  38. gri_multitrack-0.1.0/test/test_mfa.py +104 -0
  39. gri_multitrack-0.1.0/test/test_scoring.py +53 -0
  40. gri_multitrack-0.1.0/test/test_track.py +72 -0
  41. gri_multitrack-0.1.0/test/test_tracker.py +155 -0
  42. gri_multitrack-0.1.0/uv.lock +1786 -0
@@ -0,0 +1,85 @@
1
+ [![GeoSol Research Logo](https://geosolresearch.com/logos/foss_logo.png "GeoSol Research")](https://geosolresearch.com)
2
+
3
+ # GeoSol FOSS Projects
4
+
5
+ [Back to main README](README.md)
6
+
7
+ GeoSol Research is working to create, maintain, and improve several FOSS projects in the geolocation space. Here is what we are currently offering!
8
+
9
+ ## Layer 1: Foundation
10
+
11
+ ### [GRI Fitter](https://gitlab.com/geosol-foss/python/gri-fitter)
12
+
13
+ Equation fitting wrapper around scipy.optimize.curve\_fit. Simplifies curve fitting workflows with a clean API.
14
+
15
+ ### [GRI Memoize](https://gitlab.com/geosol-foss/python/gri-memoize)
16
+
17
+ Class-based memoization with full type hints and IDE integration. Provides caching decorators for performance optimization.
18
+
19
+ ### [GRI Signal](https://gitlab.com/geosol-foss/python/gri-signal)
20
+
21
+ Functional signal processing library with pure functions operating on numpy arrays. Includes signal generation (tones, PRN, pulsed), channel simulation (delay, AWGN, Doppler, multipath), modulation (AM/FM/PM, BPSK/QPSK/QAM), sampling (ADC), filtering (lowpass, highpass, bandpass, notch, analytic), spectral analysis (PSD, SNR, THD, time-bandwidth product), cross-correlation, and beamforming.
22
+
23
+ ### [GRI Utils](https://gitlab.com/geosol-foss/python/gri-utils)
24
+
25
+ The math back end to many other projects. This includes constants, coordinate conversion and transformation, orbit propagation, observable calculations, and geolocation algorithms. Most are based around numpy arrays.
26
+
27
+ ## Layer 2: Mid-Level
28
+
29
+ ### [GRI Ell](https://gitlab.com/geosol-foss/python/gri-ell)
30
+
31
+ An object to handle statistical locations like ellipse and ellipsoid, centered around a GRI-Position object. Transformations between statistical spaces, conversions between sigma and percentages in 2D and 3D, statistical distances, and attributes.
32
+
33
+ ### [GRI Iono](https://gitlab.com/geosol-foss/python/gri-iono)
34
+
35
+ Ionospheric delay corrections using Bent, IRI, and NeQuick models.
36
+
37
+ ### [GRI Iono Data](https://gitlab.com/geosol-foss/python/gri-iono-data)
38
+
39
+ Solar flux and NeQuickG coefficient data pipeline for ionospheric modeling.
40
+
41
+ ### [GRI NSEpoch](https://gitlab.com/geosol-foss/python/gri-nsepoch)
42
+
43
+ A lot of the work we do is down to nanosecond or tens of nanosecond accuracy. We needed an object that could both handle that level of precision as well as do a lot of the string <=> time conversions for the many semi-standards formats we run into in this field.
44
+
45
+ ### [GRI Obs](https://gitlab.com/geosol-foss/python/gri-obs)
46
+
47
+ Typed observation objects (TDOA, FDOA, AOA, Range, PDOA) for geolocation measurements. Each observation carries measurement data, noise characterization, sensor geometry, and measurement math (predicted values, Jacobians, noise covariance) for EKF integration with Kalman filters.
48
+
49
+ ### [GRI Plot](https://gitlab.com/geosol-foss/python/gri-plot)
50
+
51
+ Plotting utilities for Plotly and Matplotlib. Provides consistent styling and common plot types for geospatial visualization.
52
+
53
+ ### [GRI Pos](https://gitlab.com/geosol-foss/python/gri-pos)
54
+
55
+ A position object that greatly simplifies handling coordinates, distances, and memory, mostly on the WGS-84 ellipsoid.
56
+
57
+ ### [GRI Terrain](https://gitlab.com/geosol-foss/python/gri-terrain)
58
+
59
+ Unified terrain elevation data access with fallback chain across multiple sources including DTED, Cloud-Optimized GeoTIFF, and Copernicus DEM.
60
+
61
+ ### [GRI Tropo](https://gitlab.com/geosol-foss/python/gri-tropo)
62
+
63
+ Tropospheric delay corrections using ITU-R 2019 models and ERA5 refractivity data.
64
+
65
+ ### [GRI Tropo Data](https://gitlab.com/geosol-foss/python/gri-tropo-data)
66
+
67
+ ERA5 data pipeline for downloading and processing tropospheric refractivity data used by gri-tropo.
68
+
69
+ ## Layer 3: Applications
70
+
71
+ ### [GRI Convolve](https://gitlab.com/geosol-foss/python/gri-convolve)
72
+
73
+ Ellipsoid convolution with outlier detection and clustering, plus Kalman tracking via Interacting Multiple Model (IMM) filter. Combines multiple statistical position estimates into refined solutions. The Kalman tracker supports both ellipsoid-based and raw observable (EKF) updates, bidirectional outlier detection, and online track segmentation.
74
+
75
+ ### [GRI GeoSim](https://gitlab.com/geosol-foss/python/gri-geosim)
76
+
77
+ Geometric/geolocation simulation library for platforms, sensors, emitters, and observables. Simulate satellite and ground-based geolocation scenarios with AOA/TDOA/FDOA measurements.
78
+
79
+ ### [GRI Sandbox](https://gitlab.com/geosol-foss/python/gri-sandbox)
80
+
81
+ Some neat (small) examples and projects we've put together over time using the above libraries!
82
+
83
+ ### [GRI SigSim](https://gitlab.com/geosol-foss/python/gri-sigsim)
84
+
85
+ Object-based RF signal simulation library. Wraps gri-signal functions with chainable object-oriented classes for waveforms (pulsed, PRN, tone), channel modeling (delay, Doppler, multipath), receiver simulation, and TOA estimation.
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ # Tracked pre-push hook. Wired up automatically by the hatch build hook on
3
+ # `uv sync` (it sets core.hooksPath to this directory). Delegates to pre-commit
4
+ # in the project's .venv. Unlike a `pre-commit install` generated hook, the
5
+ # python path is resolved at run time, so this file is portable across clones.
6
+ ARGS=(hook-impl --config=.pre-commit-config.yaml --hook-type=pre-push)
7
+ HERE="$(cd "$(dirname "$0")" && pwd)"
8
+ ARGS+=(--hook-dir "$HERE" -- "$@")
9
+
10
+ INSTALL_PYTHON="$(git rev-parse --show-toplevel)/.venv/bin/python"
11
+ if [ -x "$INSTALL_PYTHON" ]; then
12
+ exec "$INSTALL_PYTHON" -mpre_commit "${ARGS[@]}"
13
+ elif command -v pre-commit > /dev/null; then
14
+ exec pre-commit "${ARGS[@]}"
15
+ else
16
+ echo '`pre-commit` not found. Run `uv sync` to set up the environment.' 1>&2
17
+ exit 1
18
+ fi
@@ -0,0 +1,36 @@
1
+ ### Python ###
2
+ *.py[cod]
3
+ __pycache__/
4
+ build/
5
+ dist/
6
+ wheels/
7
+ lib/
8
+ lib64/
9
+ *.egg-info/
10
+ *.egg
11
+ .venv
12
+
13
+ ### Temp files ###
14
+ *~
15
+ *.swp
16
+ .version
17
+ .pytest_cache/
18
+ .mypy_cache/
19
+ testreports/
20
+ .VSCodeCounter/
21
+ .coverage
22
+ coverage.xml
23
+ .cov_html/
24
+ *.pstats
25
+ *.stats
26
+ .claude/
27
+ CLAUDE.md
28
+ AGENTS.md
29
+
30
+ ### Credentials ###
31
+ .env*
32
+ *.key
33
+ *.pem
34
+ *.p12
35
+ .cdsapirc
36
+ .ecmwfdatastoresrc
@@ -0,0 +1,18 @@
1
+ # Project-specific dependencies to clone
2
+ # Customize this file per project to clone different repositories
3
+ # TODO: This is only a stopgap until the projects are on pypi. For projects with no
4
+ # dependencies: "before_script: []"
5
+ .clone_dependencies:
6
+ before_script:
7
+ - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-utils.git ../gri-utils
8
+ - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-memoize.git ../gri-memoize
9
+ - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-pos.git ../gri-pos
10
+ - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-ell.git ../gri-ell
11
+ - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-nsepoch.git ../gri-nsepoch
12
+ - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-tropo.git ../gri-tropo
13
+ - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-iono.git ../gri-iono
14
+ - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-obs.git ../gri-obs
15
+ - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-convolve.git ../gri-convolve
16
+ - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-kalman.git ../gri-kalman
17
+ - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-trajectory.git ../gri-trajectory
18
+ - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/geosol-foss/python/gri-geosim.git ../gri-geosim
@@ -0,0 +1,99 @@
1
+ include:
2
+ - local: .gitlab-ci-deps.yml
3
+
4
+ stages:
5
+ - test
6
+ - lint
7
+ - deploy
8
+
9
+ variables:
10
+ UV_CACHE_DIR: "$CI_PROJECT_DIR/.uv-cache"
11
+ COVERAGE_THRESHOLD: "70"
12
+
13
+ cache:
14
+ key: "${CI_COMMIT_REF_SLUG}"
15
+ paths:
16
+ - .uv-cache/
17
+ - .venv/
18
+
19
+ .python_base:
20
+ image: python:3.12
21
+ before_script:
22
+ # TODO: This reference and include is a stopgap until we have the projects on pypi
23
+ - !reference [.clone_dependencies, before_script]
24
+ - pip install uv
25
+ - uv sync
26
+ - source .venv/bin/activate
27
+
28
+ test:
29
+ extends: .python_base
30
+ stage: test
31
+ script:
32
+ - pytest --cov --cov-report=term --cov-report=html --cov-report=xml --junitxml=report.xml -v
33
+ coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
34
+ artifacts:
35
+ when: always
36
+ reports:
37
+ junit: report.xml
38
+ coverage_report:
39
+ coverage_format: cobertura
40
+ path: coverage.xml
41
+ paths:
42
+ - report.xml
43
+ - .cov_html/
44
+ - coverage.xml
45
+ expire_in: 30 days
46
+ only:
47
+ - branches
48
+ - merge_requests
49
+ - tags
50
+
51
+ lint:ruff:
52
+ extends: .python_base
53
+ stage: lint
54
+ script:
55
+ - ruff check . --config .ruff.toml
56
+ - ruff format --check . --config .ruff.toml
57
+ allow_failure: false
58
+ only:
59
+ - branches
60
+ - merge_requests
61
+ - tags
62
+
63
+ lint:ty:
64
+ extends: .python_base
65
+ stage: lint
66
+ script:
67
+ - ty check --output-format gitlab --exit-zero > ty-report.json
68
+ - ty check
69
+ allow_failure: false
70
+ artifacts:
71
+ when: always
72
+ reports:
73
+ codequality: ty-report.json
74
+ expire_in: 30 days
75
+ only:
76
+ - branches
77
+ - merge_requests
78
+ - tags
79
+
80
+ deploy:pypi:
81
+ stage: deploy
82
+ image: python:3.12
83
+ id_tokens:
84
+ PYPI_ID_TOKEN:
85
+ aud: pypi
86
+ variables:
87
+ UV_CACHE_DIR: /tmp/uv-cache
88
+ cache: []
89
+ before_script:
90
+ - pip install uv
91
+ script:
92
+ - uv build
93
+ - uv publish
94
+ artifacts:
95
+ paths:
96
+ - dist/
97
+ expire_in: 1 week
98
+ only:
99
+ - tags
@@ -0,0 +1,46 @@
1
+ """Custom hatchling build hook that wires up the project's git hooks.
2
+
3
+ `uv sync` installs this project in editable mode, which runs the hatchling
4
+ build backend and therefore this hook. The hook points git's ``core.hooksPath``
5
+ at the tracked ``.githooks`` directory, so a fresh clone gets working git hooks
6
+ on its first sync without a separate install step.
7
+
8
+ This only adjusts local git config and is a no-op outside a git work tree (for
9
+ example, in an isolated sdist/wheel build), so it is safe in CI release builds.
10
+ """
11
+
12
+ import shutil
13
+ import subprocess
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
18
+
19
+
20
+ class GitHooksBuildHook(BuildHookInterface):
21
+ """Set ``core.hooksPath`` to the tracked ``.githooks`` directory."""
22
+
23
+ PLUGIN_NAME = "custom"
24
+
25
+ def initialize(self, version: str, build_data: dict[str, Any]) -> None: # noqa: ARG002
26
+ """Point git at ``.githooks`` if this build runs inside a work tree."""
27
+ root = Path(self.root)
28
+ git = shutil.which("git")
29
+ if git is None or not (root / ".githooks").is_dir():
30
+ return
31
+ # Only act inside a real git work tree; check=False keeps release
32
+ # builds (sdist/wheel in a non-git temp dir) from failing.
33
+ inside = subprocess.run( # noqa: S603
34
+ [git, "rev-parse", "--is-inside-work-tree"],
35
+ cwd=root,
36
+ capture_output=True,
37
+ text=True,
38
+ check=False,
39
+ )
40
+ if inside.stdout.strip() != "true":
41
+ return
42
+ subprocess.run( # noqa: S603
43
+ [git, "config", "core.hooksPath", ".githooks"],
44
+ cwd=root,
45
+ check=False,
46
+ )
@@ -0,0 +1,47 @@
1
+ # See https://pre-commit.com for more information
2
+ # See https://pre-commit.com/hooks.html for more hooks
3
+ # Checks run at pre-push (not pre-commit): commits stay fast, the suite runs
4
+ # once before code is shared. Hooks are wired up automatically on `uv sync`
5
+ # (see .hatch_build.py), so no separate `pre-commit install` step is needed.
6
+ default_stages: [pre-push]
7
+ repos:
8
+ - repo: https://github.com/gitleaks/gitleaks
9
+ rev: v8.24.3
10
+ hooks:
11
+ - id: gitleaks
12
+ - repo: https://github.com/pre-commit/pre-commit-hooks
13
+ # Base pre-commit-provided hooks
14
+ rev: v6.0.0
15
+ hooks:
16
+ - id: trailing-whitespace
17
+ args: [ --markdown-linebreak-ext=md ]
18
+ exclude: '\.dt[012]$'
19
+ - id: end-of-file-fixer
20
+ exclude: '\.dt[012]$'
21
+ - id: check-added-large-files
22
+ - repo: local
23
+ hooks:
24
+ - id: ruff-check
25
+ name: ruff check
26
+ entry: uv run --no-sync ruff check --fix --config .ruff.toml
27
+ language: system
28
+ types: [python]
29
+ - id: ruff-format
30
+ name: ruff format
31
+ entry: uv run --no-sync ruff format
32
+ language: system
33
+ types: [python]
34
+ - id: ty
35
+ name: ty
36
+ entry: uv run --no-sync ty check
37
+ language: system
38
+ types: [python]
39
+ # ty's [tool.ty.src] include scoping is overridden by explicit file
40
+ # args; run without filenames so it checks the configured source set
41
+ # (matching CI) instead of, say, test files passed in by pre-commit.
42
+ pass_filenames: false
43
+ - id: unskipped
44
+ name: Check for pytest skipped tests commented back in
45
+ entry: uv run --no-sync python .pre-commit-unskipped.py
46
+ language: system
47
+ files: '\.py$'
@@ -0,0 +1,38 @@
1
+ # ruff: noqa: T201
2
+ """Find all the unit tests and make sure none of the skips are commented in!
3
+
4
+ You can run this manually with:
5
+ > python .pre-commit-noskips.py
6
+ """
7
+
8
+ import sys
9
+ from pathlib import Path
10
+
11
+
12
+ def _main() -> None:
13
+ # Get the test directory, which should be adjascent to this script
14
+ script_dir = Path(__file__)
15
+ test_dir = script_dir.parent / "test"
16
+ if not test_dir.exists():
17
+ print("WARNING::: Didn't find a test directory!")
18
+ sys.exit(0)
19
+ tests = test_dir.rglob("test*.py")
20
+ found = False
21
+ for testfile in tests:
22
+ with testfile.open("r") as fp:
23
+ for idx, line in enumerate(fp, 1):
24
+ if line.strip().startswith("#") and (
25
+ "pytest.skip" in line or "pytest.mark.skip" in line
26
+ ):
27
+ print(
28
+ f"ERROR::: Line {idx} of {testfile.relative_to(test_dir)} "
29
+ "has a skip commented out:",
30
+ )
31
+ print(f" {idx}: {line}")
32
+ found = True
33
+ if found:
34
+ sys.exit(1) # Error4 aborts the commit
35
+
36
+
37
+ if __name__ == "__main__":
38
+ _main()
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,52 @@
1
+ extend = "pyproject.toml"
2
+
3
+ line-length = 88
4
+
5
+ [lint]
6
+ select = ["ALL"]
7
+
8
+ ignore = [
9
+ "COM812", # conflicts with the ruff formatter (trailing comma)
10
+ "ISC001", # conflicts with the ruff formatter (implicit str concat)
11
+ "D105", # Do not require magic method documentation
12
+ "ANN002", # Allow non-typed *args
13
+ "ANN003", # Allow non-typed **kwargs
14
+ "ANN204", # Do not require magic method documentation
15
+ "CPY", # Copyright notices
16
+ "EM101", # raw string in exception
17
+ "EM102", # fstrings in exceptions
18
+ "ERA001", # allow commented out code
19
+ "PERF203",# allow try-except in loops
20
+ "TRY003", # raise vanilla
21
+ "TD002", # TODO author
22
+ "TD003", # TODO link
23
+ "TD004", # TODO colon
24
+ "FIX001", # Fixme
25
+ "FIX002", # TODO
26
+ ]
27
+
28
+ [lint.extend-per-file-ignores]
29
+ "test/**/*.py" = [
30
+ "D100", # undocumented public module
31
+ "D103", # undocumented public function
32
+ "D104", # undocumented public package
33
+ "ANN001", # no type annotations for function arguments
34
+ "ANN201", # no return type for public function
35
+ "ANN202", # no return type for private test helpers
36
+ "S101", # asserts allowed
37
+ "SLF001", # Allow private member access in unit tests
38
+ "PLR2004", # magic values allowed in comparisons
39
+ "PT011", # pytest.raises can be broad
40
+ "PT013", # allow sub-imports of pytest
41
+ "PLR0913", # allow many arguments in parameterized tests
42
+ ]
43
+ "examples/**/*.py" = [
44
+ "D100",
45
+ "INP001", # examples are not a package
46
+ "T201", # allow print in examples
47
+ ]
48
+
49
+ [lint.pydocstyle]
50
+ convention = "google"
51
+ ignore-var-parameters = true
52
+ ignore-decorators = ["typing.overload"]
@@ -0,0 +1,15 @@
1
+ {
2
+ "python.testing.pytestArgs": ["test"],
3
+ "python.testing.unittestEnabled": false,
4
+ "python.testing.pytestEnabled": true,
5
+ "editor.rulers": [88],
6
+ "[python]": {
7
+ "editor.formatOnSave": true,
8
+ "editor.defaultFormatter": "charliermarsh.ruff",
9
+ "editor.codeActionsOnSave": {
10
+ "source.fixAll": "explicit",
11
+ "source.organizeImports.ruff": "explicit"
12
+ }
13
+ },
14
+ "ruff.interpreter": ["${workspaceFolder}/.venv/bin/python"]
15
+ }
@@ -0,0 +1,124 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in contributing! This guide covers everything you need
4
+ to get started.
5
+
6
+ ## Setup
7
+
8
+ Requires **Python 3.12+** and **bash**.
9
+
10
+ ```bash
11
+ git clone <repo-url>
12
+ cd <repo>
13
+ uv sync
14
+ ```
15
+
16
+ `uv sync` creates a virtual environment with [uv](https://docs.astral.sh/uv/),
17
+ installs all dependencies (including any editable local siblings), and wires up
18
+ the git hooks automatically: a [hatchling](https://hatch.pypa.io/) build hook
19
+ (`.hatch_build.py`) runs during the editable install and points
20
+ `core.hooksPath` at the tracked `.githooks/` directory. No separate
21
+ `pre-commit install` step is needed. To run commands in the environment, prefix
22
+ them with `uv run` (for example `uv run pytest`), or activate it with
23
+ `source .venv/bin/activate`.
24
+
25
+ To start fresh, delete `.venv/` and run `uv sync` again.
26
+
27
+ > **Note (editable local siblings):** some `gri-*` projects depend on sibling
28
+ > packages as editable path sources. uv requires `[tool.uv]`
29
+ > `config-settings = { editable_mode = "compat" }` in `pyproject.toml` for those
30
+ > to import correctly. If a project declares editable path dependencies and
31
+ > imports fail, verify that setting is present.
32
+
33
+ ## Running Tests
34
+
35
+ [pytest](https://docs.pytest.org/) with
36
+ [pytest-cov](https://pypi.org/project/pytest-cov/) for coverage:
37
+
38
+ ```bash
39
+ pytest --cov # terminal summary
40
+ pytest --cov --cov-report html # detailed HTML report in .cov_html/
41
+ ```
42
+
43
+ CI requires **70% minimum** coverage. Open the HTML report to see exactly which
44
+ lines are covered:
45
+
46
+ ```bash
47
+ xdg-open .cov_html/index.html # or: google-chrome, firefox
48
+ ```
49
+
50
+ ## Linting and Type Checking
51
+
52
+ ### Ruff
53
+
54
+ [Ruff](https://docs.astral.sh/ruff/) handles both linting and formatting. The
55
+ configuration selects **all rules** (`select = ["ALL"]`) with a small ignore
56
+ list in `.ruff.toml`.
57
+
58
+ ```bash
59
+ ruff check # lint
60
+ ruff format # format
61
+ ```
62
+
63
+ ### ty
64
+
65
+ [ty](https://docs.astral.sh/ty/) for static type checking:
66
+
67
+ ```bash
68
+ ty check
69
+ ```
70
+
71
+ ### Pre-push hooks
72
+
73
+ All of the above run automatically on `git push` via
74
+ [pre-commit](https://pre-commit.com/) (configured for the `pre-push` stage, so
75
+ commits stay fast). If the push fails, fix the issues, commit the fix, and push
76
+ again.
77
+
78
+ ```bash
79
+ uv run pre-commit run --all-files --hook-stage pre-push # run manually
80
+ ```
81
+
82
+ > **Tip:** To force a push past the hooks (e.g. a work-in-progress branch), use
83
+ > `git push --no-verify`. CI will still enforce the same checks.
84
+
85
+ ## Code Style
86
+
87
+ - **Docstrings**: [Google convention](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
88
+ - **Type hints**: Required on all public function signatures
89
+ - **Line length**: 88 characters max
90
+ - **Formatting**: Let `ruff format` handle it
91
+ - **Imports**: All at the top of the file, organized by ruff's isort rules
92
+
93
+ ## Submitting Changes
94
+
95
+ 1. Create a branch from `main`
96
+ 2. Make your changes with tests
97
+ 3. Ensure `pytest --cov` passes with >= 70% coverage
98
+ 4. Ensure `uv run pre-commit run --all-files --hook-stage pre-push` passes
99
+ 5. Open a merge request against `main`
100
+
101
+ ## IDE Setup (VS Code)
102
+
103
+ Install the [Ruff extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff)
104
+ by Astral and add to your `settings.json`:
105
+
106
+ ```json
107
+ {
108
+ "[python]": {
109
+ "editor.formatOnSave": true,
110
+ "editor.defaultFormatter": "charliermarsh.ruff",
111
+ "editor.codeActionsOnSave": {
112
+ "source.fixAll": "explicit",
113
+ "source.organizeImports.ruff": "explicit"
114
+ }
115
+ },
116
+ "ruff.interpreter": ["${workspaceFolder}/.venv/bin/python"]
117
+ }
118
+ ```
119
+
120
+ For type checking in VS Code, install the
121
+ [ty extension](https://marketplace.visualstudio.com/items?itemName=astral-sh.ty)
122
+ by Astral. To avoid conflicts with Pylance, either let ty disable the Python
123
+ language server (the default) or keep Pylance and set
124
+ `"ty.disableLanguageServices": true` to use ty for diagnostics only.