forcingkit 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 (83) hide show
  1. forcingkit-0.1.0/.agents/forcingkit.md +98 -0
  2. forcingkit-0.1.0/.claude/CLAUDE.md +10 -0
  3. forcingkit-0.1.0/.env.template +17 -0
  4. forcingkit-0.1.0/.github/copilot-instructions.md +16 -0
  5. forcingkit-0.1.0/.github/workflows/docs.yml +49 -0
  6. forcingkit-0.1.0/.github/workflows/publish.yml +51 -0
  7. forcingkit-0.1.0/.github/workflows/tests.yml +35 -0
  8. forcingkit-0.1.0/.gitignore +19 -0
  9. forcingkit-0.1.0/.markdownlint.yaml +5 -0
  10. forcingkit-0.1.0/.pre-commit-config.yaml +48 -0
  11. forcingkit-0.1.0/.python-version +1 -0
  12. forcingkit-0.1.0/AGENTS.md +71 -0
  13. forcingkit-0.1.0/CONTRIBUTING.md +30 -0
  14. forcingkit-0.1.0/Dockerfile +48 -0
  15. forcingkit-0.1.0/LICENSE +201 -0
  16. forcingkit-0.1.0/PKG-INFO +329 -0
  17. forcingkit-0.1.0/README.md +88 -0
  18. forcingkit-0.1.0/docker-compose.yml +10 -0
  19. forcingkit-0.1.0/docs/Makefile +20 -0
  20. forcingkit-0.1.0/docs/make.bat +35 -0
  21. forcingkit-0.1.0/docs/requirements-docs.txt +3 -0
  22. forcingkit-0.1.0/docs/source/_extra/CNAME +1 -0
  23. forcingkit-0.1.0/docs/source/_static/forcingkit-inventory-screenshot.png +0 -0
  24. forcingkit-0.1.0/docs/source/atmospheric_forcing.rst +196 -0
  25. forcingkit-0.1.0/docs/source/conf.py +30 -0
  26. forcingkit-0.1.0/docs/source/fetchers.rst +29 -0
  27. forcingkit-0.1.0/docs/source/index.rst +16 -0
  28. forcingkit-0.1.0/docs/source/nyofs.rst +188 -0
  29. forcingkit-0.1.0/docs/source/removed_endpoints.rst +44 -0
  30. forcingkit-0.1.0/main.py +6 -0
  31. forcingkit-0.1.0/pyproject.toml +89 -0
  32. forcingkit-0.1.0/service/__init__.py +0 -0
  33. forcingkit-0.1.0/service/forcingkit_serve/__init__.py +0 -0
  34. forcingkit-0.1.0/service/forcingkit_serve/main.py +544 -0
  35. forcingkit-0.1.0/service/forcingkit_serve/routers/bathymetry.py +241 -0
  36. forcingkit-0.1.0/service/forcingkit_serve/routers/plotly_api.py +150 -0
  37. forcingkit-0.1.0/service/forcingkit_serve/routers/removed.py +86 -0
  38. forcingkit-0.1.0/service/forcingkit_serve/routers/viewer.py +1199 -0
  39. forcingkit-0.1.0/service/run_server.py +36 -0
  40. forcingkit-0.1.0/src/forcingkit/__init__.py +0 -0
  41. forcingkit-0.1.0/src/forcingkit/dispatcher.py +448 -0
  42. forcingkit-0.1.0/src/forcingkit/fetchers/dbofs.py +442 -0
  43. forcingkit-0.1.0/src/forcingkit/fetchers/erddap.py +142 -0
  44. forcingkit-0.1.0/src/forcingkit/fetchers/hrrr.py +72 -0
  45. forcingkit-0.1.0/src/forcingkit/fetchers/hrrr_atmosphere.py +289 -0
  46. forcingkit-0.1.0/src/forcingkit/fetchers/hycom.py +159 -0
  47. forcingkit-0.1.0/src/forcingkit/fetchers/hydrography.py +117 -0
  48. forcingkit-0.1.0/src/forcingkit/fetchers/ndbc.py +231 -0
  49. forcingkit-0.1.0/src/forcingkit/fetchers/necofs.py +369 -0
  50. forcingkit-0.1.0/src/forcingkit/fetchers/noaa.py +87 -0
  51. forcingkit-0.1.0/src/forcingkit/fetchers/nyofs.py +458 -0
  52. forcingkit-0.1.0/src/forcingkit/settings.py +72 -0
  53. forcingkit-0.1.0/src/forcingkit/zarr_stream.py +146 -0
  54. forcingkit-0.1.0/static/app.js +590 -0
  55. forcingkit-0.1.0/static/favicon.ico +0 -0
  56. forcingkit-0.1.0/static/favicon.svg +258 -0
  57. forcingkit-0.1.0/static/index.html +142 -0
  58. forcingkit-0.1.0/static/logo.svg +29 -0
  59. forcingkit-0.1.0/static/preview3d.js +272 -0
  60. forcingkit-0.1.0/static/styles.css +701 -0
  61. forcingkit-0.1.0/tests/__init__.py +0 -0
  62. forcingkit-0.1.0/tests/integration/test_auth.py +24 -0
  63. forcingkit-0.1.0/tests/integration/test_erddap_fetch.py +16 -0
  64. forcingkit-0.1.0/tests/integration/test_nyofs_obc_fetch.py +36 -0
  65. forcingkit-0.1.0/tests/integration/test_obc_mab.py +47 -0
  66. forcingkit-0.1.0/tests/integration/test_s3_roms_fetchers.py +87 -0
  67. forcingkit-0.1.0/tests/scripts/inspect_dem.py +11 -0
  68. forcingkit-0.1.0/tests/scripts/inspect_grib.py +11 -0
  69. forcingkit-0.1.0/tests/scripts/inspect_zarr.py +6 -0
  70. forcingkit-0.1.0/tests/unit/test_dbofs.py +280 -0
  71. forcingkit-0.1.0/tests/unit/test_dispatcher.py +40 -0
  72. forcingkit-0.1.0/tests/unit/test_hrrr_atm.py +153 -0
  73. forcingkit-0.1.0/tests/unit/test_hrrr_idx.py +72 -0
  74. forcingkit-0.1.0/tests/unit/test_hycom.py +32 -0
  75. forcingkit-0.1.0/tests/unit/test_main.py +17 -0
  76. forcingkit-0.1.0/tests/unit/test_ndbc.py +97 -0
  77. forcingkit-0.1.0/tests/unit/test_necofs_parent.py +140 -0
  78. forcingkit-0.1.0/tests/unit/test_nyofs.py +294 -0
  79. forcingkit-0.1.0/tests/unit/test_obc_pipeline.py +249 -0
  80. forcingkit-0.1.0/tests/unit/test_removed_routes.py +32 -0
  81. forcingkit-0.1.0/tests/unit/test_settings.py +79 -0
  82. forcingkit-0.1.0/tests/unit/test_zarr_stream.py +170 -0
  83. forcingkit-0.1.0/uv.lock +3269 -0
@@ -0,0 +1,98 @@
1
+ # forcingkit Reference
2
+
3
+ ## Mission
4
+
5
+ Python microservice providing real-time and historical forcing (the parent ocean and
6
+ the HRRR atmosphere) and validation observations to the `coastal-sim` Julia physics
7
+ engine.
8
+
9
+ ## Environment
10
+
11
+ - **Python 3.11+** via `uv`. Always use `uv run` — never `pip install`.
12
+ - **FastAPI** microservice in `service/forcingkit_serve/`.
13
+ - Tests run with `uv run pytest`.
14
+
15
+ ## Architecture
16
+
17
+ ### Core Pipeline
18
+
19
+ ```text
20
+ HTTP Request → service/forcingkit_serve/main.py
21
+ → src/forcingkit/dispatcher.py
22
+ → fetchers/*.py
23
+ → Zarr (~/.cache/forcingkit/)
24
+ ```
25
+
26
+ - **Entry point**: `service/forcingkit_serve/main.py` — FastAPI application.
27
+ - **Dispatcher**: `src/forcingkit/dispatcher.py` — interprets
28
+ requests and routes to the appropriate fetcher via tiered fallback.
29
+ Uses `_rank_obc_candidates(bbox)` to select the best parent by
30
+ resolution and domain overlap.
31
+ - **Fetchers**: `src/forcingkit/fetchers/` — isolated modules per data source.
32
+
33
+ ## Fetcher Tier (Parent Ocean Priority Order)
34
+
35
+ | Fetcher | Resolution | Domain | Notes |
36
+ |---|---|---|---|
37
+ | `nyofs.py` | ~100 m | NY/NJ Harbor | POM curvilinear grid, C-grid stagger |
38
+ | `necofs.py` | ~200 m | New England | FVCOM unstructured |
39
+ | `hycom.py` | ~9 km | Global | Final fallback (Operational + Historical) |
40
+
41
+ *Note: Because the architecture is modular, a future roadmap item includes the integration of TPXO (via `tpxo.py`) given usage rights.*
42
+
43
+ Atmospheric forcing: HRRR from 2014-07-30 (`hrrr_atmosphere.py`, `/api/v1/atmosphere`).
44
+ Earlier runs use ERA5 through NumericalEarth in coastal-sim, not this service.
45
+
46
+ ## Testing
47
+
48
+ ```bash
49
+ # Unit tests (no network I/O, fast)
50
+ uv run pytest tests/unit/ -v
51
+
52
+ # Single test file
53
+ uv run pytest tests/unit/test_nyofs.py -v
54
+
55
+ # Integration tests (live APIs, slower)
56
+ uv run pytest tests/integration/ -v
57
+
58
+ # Pre-commit checks (ruff + mypy) on modified files
59
+ uv run pre-commit run --files <file1> <file2> ...
60
+ ```
61
+
62
+ ## Commands
63
+
64
+ ```bash
65
+ # Sync dependencies
66
+ uv sync
67
+
68
+ # Start data service
69
+ uv run python service/run_server.py
70
+
71
+ # Run one-off fetch
72
+ uv run python -c "from forcingkit.fetchers import nyofs; print(nyofs.get_metadata())"
73
+ ```
74
+
75
+ ## Gotchas
76
+
77
+ - **Telemetry performance**: Use `.csvp` endpoints for ERDDAP
78
+ tabular time-series to avoid heavy NetCDF binary parsing overhead.
79
+ - **OPeNDAP engine**: Always pass `engine="pydap"` to `xr.open_dataset` for THREDDS
80
+ endpoints. The default netcdf4 engine fails on remote DAP URLs.
81
+ - **NYOFS FMRC vs NCEI tiering**: FMRC aggregation covers rolling 7-day window
82
+ (< 31 days). Older data requires per-hour NCEI file enumeration with naming
83
+ conventions that changed on 2024-09-09.
84
+ - **C-grid interpolation**: NYOFS u/v are on Arakawa C-grid face
85
+ points. After `.where(mask, drop=True)`, the subset is already
86
+ zero-indexed — use the `_c_grid_to_rho()` helper that averages
87
+ adjacent cells on the subset array, not the old absolute-index
88
+ approach.
89
+ - **Mocking strategy**: Dispatcher unit tests check tuple/dict return
90
+ boundaries carefully. When modifying mocked fetchers, track
91
+ keyword-argument vs positional argument boundaries.
92
+ - **Grid normalization**: `coastal-sim` expects elevations positive
93
+ up (LMSL/NAVD88). Normalize any inverted datasets in the fetcher
94
+ tier before the dispatcher sees them.
95
+ - **Cache keys**: Cache Zarr keys are deterministic hashes of bbox
96
+ - time window. Changing fetcher output schema (variable names, dims)
97
+ will miss existing cache entries — purge
98
+ `~/.cache/forcingkit/` when making breaking schema changes.
@@ -0,0 +1,10 @@
1
+ # Agent Instructions
2
+
3
+ > **Redirect**: Please refer to [AGENTS.md](../AGENTS.md) for the
4
+ > canonical source of truth.
5
+
6
+ ## Expected Follow-on Context
7
+
8
+ - `/.agents/forcingkit.md`
9
+
10
+ Use this file as a compatibility entry point only.
@@ -0,0 +1,17 @@
1
+ # forcingkit environment variables
2
+ # Copy this file to .env and fill in your credentials.
3
+ # This file is loaded by coastal-sim/docker-compose.yml via env_file.
4
+
5
+ # No credentials are required at present: HRRR, NOAA OFS, NECOFS, HYCOM, NDBC and
6
+ # CO-OPS are all served without authentication.
7
+
8
+ # Cache root for every store (default ~/.cache/forcingkit). The old names
9
+ # ECODATA_CACHE_CACHE_DIR and COASTAL_SIM_DATA_CACHE_DIR are still read, with a
10
+ # warning, until the next release.
11
+ # FORCINGKIT_CACHE_DIR=~/.cache/forcingkit
12
+
13
+ # Threads for parallel OPeNDAP reads (default 4; was ECODATA_CACHE_MAX_WORKERS).
14
+ # FORCINGKIT_MAX_WORKERS=4
15
+
16
+ # The topobathykit elevation service (default http://localhost:9595; was TOPOBATHYSIM_URL).
17
+ # TOPOBATHYKIT_URL=http://localhost:9595
@@ -0,0 +1,16 @@
1
+ # forcingkit — Copilot Compatibility
2
+
3
+ This file exists for tools that specifically load `.github/copilot-instructions.md`.
4
+
5
+ ## Canonical Guidance
6
+
7
+ - Treat `/AGENTS.md` as the primary instruction entry point.
8
+ - Treat `/.agents/` as the vendor-neutral location for deeper project guidance.
9
+
10
+ ## Copilot-Specific Compatibility Notes
11
+
12
+ - Always use `uv run` for Python commands and `uv run pytest` for tests.
13
+ - Run `uv run pre-commit run --files <modified-files>` before staging any changes.
14
+ - Never commit without explicit user approval: present the change set and commit
15
+ message first, then wait for positive confirmation.
16
+ - Stage files explicitly (`git add <file>`). Never `git add .`.
@@ -0,0 +1,49 @@
1
+ name: Deploy Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "docs/**"
8
+ - "src/forcingkit/**"
9
+ - ".github/workflows/docs.yml"
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+ pages: write
15
+ id-token: write
16
+
17
+ concurrency:
18
+ group: pages
19
+ cancel-in-progress: false
20
+
21
+ jobs:
22
+ build:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.12"
30
+
31
+ - name: Install Sphinx dependencies
32
+ run: pip install -r docs/requirements-docs.txt
33
+
34
+ - name: Build HTML docs
35
+ run: sphinx-build -W --keep-going -b html docs/source docs/build/html
36
+
37
+ - uses: actions/upload-pages-artifact@v3
38
+ with:
39
+ path: docs/build/html
40
+
41
+ deploy:
42
+ needs: build
43
+ runs-on: ubuntu-latest
44
+ environment:
45
+ name: github-pages
46
+ url: ${{ steps.deployment.outputs.page_url }}
47
+ steps:
48
+ - id: deployment
49
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,51 @@
1
+ name: Publish to PyPI
2
+
3
+ # Pushing a tag vX.Y.Z builds the sdist and wheel and publishes them to PyPI through trusted
4
+ # publishing (OIDC): no API token is stored anywhere. PyPI must list this repository, this
5
+ # workflow file and the `pypi` environment as a trusted publisher for forcingkit.
6
+ on:
7
+ push:
8
+ tags: ["v*"]
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - uses: astral-sh/setup-uv@v6
17
+
18
+ - name: Check the tag matches the package version
19
+ run: |
20
+ version=$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
21
+ if [ "v$version" != "$GITHUB_REF_NAME" ]; then
22
+ echo "Tag $GITHUB_REF_NAME does not match pyproject.toml version $version" >&2
23
+ exit 1
24
+ fi
25
+
26
+ - name: Build
27
+ run: uv build
28
+
29
+ - name: Check the distributions
30
+ run: uvx twine check --strict dist/*
31
+
32
+ - uses: actions/upload-artifact@v4
33
+ with:
34
+ name: dist
35
+ path: dist/
36
+
37
+ publish:
38
+ needs: build
39
+ runs-on: ubuntu-latest
40
+ environment:
41
+ name: pypi
42
+ url: https://pypi.org/p/forcingkit
43
+ permissions:
44
+ id-token: write
45
+ steps:
46
+ - uses: actions/download-artifact@v4
47
+ with:
48
+ name: dist
49
+ path: dist/
50
+
51
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,35 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ unit:
11
+ name: Unit tests
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Install uv
17
+ uses: astral-sh/setup-uv@v5
18
+ with:
19
+ enable-cache: true
20
+
21
+ - name: Sync dependencies
22
+ run: uv sync --dev
23
+
24
+ # Gating. Runs the pinned hooks from .pre-commit-config.yaml rather than
25
+ # whatever ruff the venv resolves, so CI and a local commit apply the same
26
+ # version and the same rule selection. Grading against ambient-latest is
27
+ # what made the first run of this workflow fail on untouched code.
28
+ - name: Lint
29
+ run: uv run pre-commit run --all-files --show-diff-on-failure
30
+
31
+ # Integration tests are excluded: they call live NOAA, NECOFS and HYCOM
32
+ # endpoints and take several minutes.
33
+ # Run them locally or via workflow_dispatch.
34
+ - name: Unit tests
35
+ run: uv run python -m pytest tests/unit -m "not integration" --durations=10
@@ -0,0 +1,19 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+ .env
12
+
13
+ # Testing and Logs
14
+ *.log
15
+ .pytest_cache/
16
+ .coverage
17
+ htmlcov/
18
+ .vscode/
19
+ .mypy_cache/
@@ -0,0 +1,5 @@
1
+ default: true
2
+ MD013: false # line-length: Disable (too strict for documentation/URLs)
3
+ MD033: false # no-inline-html: Disable (needed for centering/resizing images)
4
+ MD003: # heading-style
5
+ style: atx # Enforce hash-style headers
@@ -0,0 +1,48 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-added-large-files
9
+ args: ["--maxkb=500000"]
10
+ - id: check-toml
11
+ - id: detect-private-key
12
+
13
+ # Pinned deliberately. This rev is the authoritative linter version for the
14
+ # repo; CI runs these same hooks so there is one standard everywhere. Bump it
15
+ # as its own reviewed change, never implicitly. The rule selection is pinned
16
+ # alongside it in pyproject.toml [tool.ruff.lint], because ruff's built-in
17
+ # defaults are not stable across versions.
18
+ - repo: https://github.com/astral-sh/ruff-pre-commit
19
+ rev: v0.16.9
20
+ hooks:
21
+ - id: ruff-check
22
+ args: [--fix]
23
+ - id: ruff-format
24
+
25
+ - repo: local
26
+ hooks:
27
+ - id: mypy
28
+ name: mypy
29
+ entry: mypy
30
+ language: system
31
+ types: [python]
32
+ require_serial: true
33
+ args: ["--ignore-missing-imports", "--explicit-package-bases"]
34
+
35
+ - repo: https://github.com/igorshubovych/markdownlint-cli
36
+ rev: v0.39.0
37
+ hooks:
38
+ - id: markdownlint
39
+ language_version: 20.10.0
40
+ args: ["--fix"]
41
+
42
+ - repo: local
43
+ hooks:
44
+ - id: block-cds-secrets
45
+ name: Block Copernicus Secrets
46
+ entry: 'url: https://cds.climate.copernicus.eu'
47
+ language: pygrep
48
+ files: \.jl$|\.py$|\.env$|\.cdsapirc$
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,71 @@
1
+ # forcingkit Agent Guide
2
+
3
+ This file is the canonical, vendor-neutral entry point for agent
4
+ guidance in this repository.
5
+
6
+ ## Scope
7
+
8
+ - Prefer standards and shared conventions over vendor-specific agent layouts.
9
+ - Treat `AGENTS.md` and `.agents/` as the primary instruction surface.
10
+ - Keep agent-specific files as thin compatibility shims when needed by a tool.
11
+
12
+ ## Tool Discovery Map
13
+
14
+ - Canonical source of truth: `AGENTS.md` and `/.agents/`.
15
+ - Claude Code:
16
+ - Compatibility shim: `/.claude/CLAUDE.md`.
17
+ - VS Code GitHub Copilot Agent:
18
+ - Compatibility shim: `/.github/copilot-instructions.md`.
19
+ - Rule for all tool-specific files: redirect to this file and
20
+ `/.agents/`, and only add minimal tool-local behavior.
21
+
22
+ ## Repository Focus
23
+
24
+ forcingkit is a Python microservice that fetches, harmonizes, regrids, and serves
25
+ real-time and historical forcing to the `coastal-sim` Julia physics engine: the parent
26
+ ocean (`/api/v1/obc`, schema z-v2), the HRRR atmosphere (`/api/v1/atmosphere`), tides,
27
+ and station telemetry and NDBC observations for validation.
28
+
29
+ ## Core Rules
30
+
31
+ - **Dependency management**: Always use `uv run` - never `pip install` directly.
32
+ - **Linting/formatting**: Run `uv run pre-commit run --files <files>` (ruff + mypy)
33
+ before staging any changes.
34
+ - **Testing**: Run tests with `uv run python -m pytest tests/unit/` (unit) or
35
+ `uv run python -m pytest tests/integration/` (live API, roughly 10 minutes).
36
+ Use `python -m pytest` rather than the bare `pytest` entry point: the latter can
37
+ resolve to a system interpreter outside the venv. `tests/conftest.py` puts
38
+ `src/` and `service/` on `sys.path`, so no `PYTHONPATH` export is needed.
39
+ - **Commits**: Conventional commits (`feat:`, `fix:`, `docs:`, `chore:`).
40
+ Author and committer are always the human contributor (`dfry-lhzn <dfry@lhzn.io>`,
41
+ from `git config`), so the log shows who was behind each change. No `Co-Authored-By:`
42
+ trailers or non-human identities: GitHub parses those into the Contributors list,
43
+ which is reserved for people. Noting the agent harness or model that collaborated
44
+ is welcome as plain text in the commit body. Verify with `git var GIT_AUTHOR_IDENT`
45
+ before the first commit in a shell.
46
+ - **Git staging**: Stage files explicitly (`git add <file>`). Never `git add .`.
47
+ - **Never commit without approval**: Present the proposed change set and commit message
48
+ to the user and wait for explicit confirmation before running any `git commit`.
49
+
50
+ ## Domain Constraints
51
+
52
+ - **Grid normalization**: Elevations and water levels are positive up. Normalize
53
+ external dataset quirks at the fetcher tier - never let them propagate into the
54
+ dispatcher.
55
+ - **OPeNDAP access**: Use the `pydap` engine (`engine="pydap"`) for all THREDDS/OPeNDAP
56
+ endpoints. Prefer `.csvp` endpoints for tabular time-series to avoid NetCDF overhead.
57
+ - **C-grid stagger**: NYOFS (POM) uses an Arakawa C-grid. Interpolation to rho-points
58
+ must be done in the fetcher before returning data to the dispatcher.
59
+ - **Precision**: Output arrays should default to `float32` (`<f4`) and use Little-Endian
60
+ endianness for compatibility with `Zarr.jl` and `Oceananigans.jl`.
61
+
62
+ ## Where To Read Next
63
+
64
+ - `.agents/forcingkit.md`: architecture pipeline, fetcher tier, testing strategy
65
+ and gotchas
66
+
67
+ ## Compatibility
68
+
69
+ If a tool only reads `.github/copilot-instructions.md`, `CLAUDE.md`, or another
70
+ vendor-specific file, that file redirects here and adds only the minimum compatibility
71
+ details required by that tool.
@@ -0,0 +1,30 @@
1
+ # Contributing to forcingkit
2
+
3
+ Thank you for considering contributing to `forcingkit`. This repository is a community resource for providing clean, standardized ocean and atmospheric forcing boundaries for high-fidelity coastal hydrodynamic models. Whether you're fixing a bug, improving documentation, or adding a new data node, contributions are welcome.
4
+
5
+ ## Development Workflow
6
+
7
+ ### 1. Environment Setup
8
+
9
+ We use `uv` for Python package management.
10
+
11
+ 1. Install `uv`.
12
+ 2. Sync the environment: `uv sync`
13
+ 3. We use `pre-commit` to ensure code format standardization. Run `pre-commit install` to set up your git hooks.
14
+
15
+ ### 2. Making Changes
16
+
17
+ - All code must pass `ruff` (for formatting and linting) and `mypy` (for typing).
18
+ - Ensure your changes are covered by tests where applicable (we use `pytest`). Tests are located in the `tests/` directory.
19
+ - `forcingkit.fetchers` logic handles API integrations. If adding a new telemetry source (e.g., a new regional IOOS node), follow the patterns established in `ndbc.py` (observations) or `dbofs.py` (a parent ocean).
20
+ - Keep the Sphinx documentation up to date.
21
+
22
+ ### 3. Pull Requests
23
+
24
+ 1. Create a descriptive branch name: `git checkout -b feature/ioos-integration`
25
+ 2. Make your commits clear and logical.
26
+ 3. Open a Pull Request.
27
+
28
+ ### 4. Code of Conduct
29
+
30
+ We encourage an inclusive, patient, and highly respectful environment for all collaborators.
@@ -0,0 +1,48 @@
1
+ # Tested on: NVIDIA Jetson AGX Orin (JetPack 6.x / l4t r36) and x86_64 (Ubuntu 24.04)
2
+ # Override BASE_IMAGE at build time for your target platform:
3
+ # Jetson Orin: --build-arg BASE_IMAGE=nvcr.io/nvidia/l4t-base:r36.2.0
4
+ # x86_64/WSL2: --build-arg BASE_IMAGE=ubuntu:24.04 (default)
5
+ ARG BASE_IMAGE=ubuntu:24.04
6
+ FROM ${BASE_IMAGE}
7
+
8
+ WORKDIR /app
9
+
10
+ # Install python and dev dependencies
11
+ RUN apt-get update && apt-get install -y \
12
+ python3 \
13
+ python3-pip \
14
+ python3-venv \
15
+ gcc \
16
+ curl \
17
+ libeccodes-dev \
18
+ && rm -rf /var/lib/apt/lists/*
19
+
20
+ # Alias python to python3
21
+ RUN ln -s /usr/bin/python3 /usr/bin/python
22
+
23
+ # Install UV
24
+ RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_UNMANAGED_INSTALL="/usr/local/bin" sh
25
+
26
+ # Configure environment
27
+ ENV UV_PROJECT_ENVIRONMENT="/app/.venv" \
28
+ PYTHONPATH="/app/src:/app/service"
29
+
30
+ COPY pyproject.toml uv.lock ./
31
+ COPY README.md ./
32
+ COPY src/ /app/src/
33
+ COPY service/ /app/service/
34
+ COPY static/ /app/static/
35
+
36
+ # Downgrade python was done in pyproject.toml.
37
+ # We use --frozen to respect uv.lock
38
+ RUN uv sync --frozen --no-dev --compile-bytecode
39
+
40
+ # Fix ownership so non-root users can write to .venv
41
+ ARG UID=1000
42
+ ARG GID=1000
43
+ RUN chown -R ${UID}:${GID} /app
44
+
45
+ # Default port 9598
46
+ EXPOSE 9598
47
+
48
+ CMD ["uv", "run", "python", "-m", "forcingkit_serve.main", "--host", "0.0.0.0", "--port", "9598"]