diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 33249e9..b644424 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,8 +4,9 @@ on: push: branches: - main - - fixup-docs - - ab/fy26-roadmap + pull_request: + branches: + - main jobs: build: diff --git a/.github/workflows/update-reports.yml b/.github/workflows/update-reports.yml new file mode 100644 index 0000000..c5e1fef --- /dev/null +++ b/.github/workflows/update-reports.yml @@ -0,0 +1,65 @@ +name: Update Reports + +on: + schedule: + # Run every Monday at 9 AM ET (14:00 UTC) + - cron: '0 14 * * 1' + push: + branches: + - main + paths: + - 'reports/*.py' + - 'reports/pyproject.toml' + workflow_dispatch: + +jobs: + update-reports: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.9.*" + enable-cache: true + + - name: Generate config data + working-directory: reports + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: uv run generate_config.py + + - name: Generate commit data + working-directory: reports + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: uv run main.py + + - name: Generate plot + working-directory: reports + run: uv run plot.py + + - name: Generate docs page + working-directory: reports + run: uv run generate_docs.py + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7.0.11 + with: + commit-message: "Update reports for ${{ github.run_id }}" + title: "Update reports" + body: | + Automated update of commit reports and visualization. + + Generated by GitHub Actions workflow. + branch: update-reports + add-paths: | + reports/output/ + docs/images/ + docs/objectives.md diff --git a/docs/images/pi-26.1.png b/docs/images/pi-26.1.png new file mode 100644 index 0000000..25f075a Binary files /dev/null and b/docs/images/pi-26.1.png differ diff --git a/docs/objectives.md b/docs/objectives.md new file mode 100644 index 0000000..267f206 --- /dev/null +++ b/docs/objectives.md @@ -0,0 +1,84 @@ +# Quarterly Objectives + +This page tracks quarterly objectives and their related repositories across Program Increments (PIs). + +## Current PI: 26.1 + +| # | Objective | Contributors | Repos | +|---|-----------|--------------|-------| +| [#244](https://github.com/NASA-IMPACT/veda-odd/issues/244) | 🗺️ Add dynamic tiling and timeseries support for Virtual Zar... | jbusecke, hrodmn | titiler, icechunk | +| [#245](https://github.com/NASA-IMPACT/veda-odd/issues/245) | 🌍 Add dynamic tiling and timeseries support for datasets in ... | abarciauskas-bgse, hrodmn | titiler, titiler-cmr, titiler-lambda-layer, titiler-md-demo, python_cmr | +| [#246](https://github.com/NASA-IMPACT/veda-odd/issues/246) | 🤖 Support virtualization of additional data products | sharkinsspatial, maxrjones, jbusecke | virtualizarr, obspec-utils, virtual-tiff, hrrr-parser, async-tiff, virtualizarr-data-pipelines, icechunk | +| [#247](https://github.com/NASA-IMPACT/veda-odd/issues/247) | 🛰 Explore scalable, cloud native approaches for search, disc... | sharkinsspatial, kylebarron | obstore, obspec, zarr-datafusion-search, geoarrow-rs, arrow-zarr | +| [#248](https://github.com/NASA-IMPACT/veda-odd/issues/248) | 🤗 Support community adoption of the technologies incubated b... | sharkinsspatial, chuckwondo, maxrjones, abarciauskas-bgse | geozarr-spec, zarr-python, multiscales, geo-proj, spatial, datacube-guide, geozarr-examples, warp-resample-profiling, pangeo.io, pangeo-docker-images, earthdata-cloud-cookbook | + +--- + +
+PI 25.4 (8 objectives, 5 closed) + +| # | Objective | State | Contributors | +|---|-----------|-------|--------------| +| [#121](https://github.com/NASA-IMPACT/veda-odd/issues/121) | Visualize Web-Optimized Zarr (WOZ) in VEDA (previe... | closed | maxrjones | +| [#122](https://github.com/NASA-IMPACT/veda-odd/issues/122) | Research, develop and document methods for Zarr an... | closed | maxrjones, kylebarron | +| [#197](https://github.com/NASA-IMPACT/veda-odd/issues/197) | 🎬 TiTiler-CMR is production ready | open | abarciauskas-bgse, hrodmn | +| [#198](https://github.com/NASA-IMPACT/veda-odd/issues/198) | 🚀 Dataset support for VEDA instances | closed | maxrjones, jbusecke | +| [#203](https://github.com/NASA-IMPACT/veda-odd/issues/203) | 🗺️Research, develop and document methods for Zarr ... | open | maxrjones | +| [#204](https://github.com/NASA-IMPACT/veda-odd/issues/204) | 🛠️ Zarr Development | open | d-v-b, maxrjones | +| [#205](https://github.com/NASA-IMPACT/veda-odd/issues/205) | 🤗 Community engagement | closed | sharkinsspatial, chuckwondo, maxrjones, abarciauskas-bgse | +| [#206](https://github.com/NASA-IMPACT/veda-odd/issues/206) | 📦 Obstore outreach | closed | chuckwondo, kylebarron | + +
+ +
+PI 25.3 (6 objectives, 4 closed) + +| # | Objective | State | Contributors | +|---|-----------|-------|--------------| +| [#118](https://github.com/NASA-IMPACT/veda-odd/issues/118) | Support CMR Modernization | open | sharkinsspatial, kylebarron | +| [#119](https://github.com/NASA-IMPACT/veda-odd/issues/119) | Continue to Build Out the VirtualiZarr Ecosystem | closed | sharkinsspatial, maxrjones | +| [#124](https://github.com/NASA-IMPACT/veda-odd/issues/124) | Publish Cloud-Optimized Datasets | open | chuckwondo, abarciauskas-bgse | +| [#126](https://github.com/NASA-IMPACT/veda-odd/issues/126) | Support TiTiler-CMR Adoption | closed | sharkinsspatial, hrodmn | +| [#127](https://github.com/NASA-IMPACT/veda-odd/issues/127) | Community Involvement | closed | maxrjones, abarciauskas-bgse, hrodmn | +| [#165](https://github.com/NASA-IMPACT/veda-odd/issues/165) | Foundational Zarr-Python and Xarray Contributions | closed | d-v-b, maxrjones | + +
+ +
+PI 25.2 (8 objectives, 8 closed) + +| # | Objective | State | Contributors | +|---|-----------|-------|--------------| +| [#31](https://github.com/NASA-IMPACT/veda-odd/issues/31) | Increase data format support in VirtualiZarr | closed | chuckwondo, maxrjones | +| [#34](https://github.com/NASA-IMPACT/veda-odd/issues/34) | Visualize OCO-3 Datasets in VEDA | closed | abarciauskas-bgse | +| [#35](https://github.com/NASA-IMPACT/veda-odd/issues/35) | Deliver Virtual Zarr Stores for NASA Datasets Usin... | closed | abarciauskas-bgse | +| [#36](https://github.com/NASA-IMPACT/veda-odd/issues/36) | Support for Modernizing VirtualiZarr to use zarr-p... | closed | sharkinsspatial, abarciauskas-bgse | +| [#37](https://github.com/NASA-IMPACT/veda-odd/issues/37) | Support CMR Modernization | closed | sharkinsspatial, kylebarron | +| [#40](https://github.com/NASA-IMPACT/veda-odd/issues/40) | Upgrade titiler and titiler-xarray to zarr-Python ... | closed | maxrjones | +| [#41](https://github.com/NASA-IMPACT/veda-odd/issues/41) | Draft Web-Optimized Zarr (WOZ) Standard | closed | maxrjones | +| [#76](https://github.com/NASA-IMPACT/veda-odd/issues/76) | Demonstrate how to tile HLS using titiler-cmr | closed | hrodmn | + +
+ +--- + +## Commits Per Repository + +The commits per repository chart uses color-coding to show which objective each repo contributes to. Repos that contribute to multiple objectives are shown with split bars. + +![PI-26.1 Commits per Repository](images/pi-26.1.png) + +--- + +## Configuration + +Objectives are configured in [`reports/config.py`](https://github.com/NASA-IMPACT/veda-odd/blob/main/reports/config.py). + +To regenerate this page from config: + +```bash +cd reports +uv run generate_docs.py +``` + +See [FY26 Roadmap](./fy26-roadmap.md) for the broader context of these objectives. diff --git a/mkdocs.yml b/mkdocs.yml index aff1143..b2322ba 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,6 +11,7 @@ extra: nav: - "index.md" - FY26 Roadmap: "fy26-roadmap.md" + - PI Objectives: "objectives.md" - ODD Products: "products.md" theme: diff --git a/reports/README.md b/reports/README.md index e730539..5357d6a 100644 --- a/reports/README.md +++ b/reports/README.md @@ -6,10 +6,40 @@ 2. Select public repositories 3. Add new token as the environment variable `GH_ODD_PAT` +## Configuration + +The `config.py` file contains: +- `TIME_RANGE`: Start and end dates for commit analysis +- `OBJECTIVES`: Quarterly objectives with repos and contributors per objective + +### Regenerating objectives from GitHub + +To fetch the latest objectives from GitHub issues: + +```bash +uv run generate_config.py +``` + +This generates `objectives_config.py` with objectives and contributors from issues labeled `pi-*-objective`. You'll need to manually add repos to each objective, then copy to `config.py`. + +### Regenerating docs/objectives.md + +To regenerate the objectives documentation page from config: + +```bash +uv run generate_docs.py +``` + ## Generating data -1. Update the dates in config.py -2. Add any new contributors to config.py -3. Add any new repositories to config.py -4. Run `uv run main.py` -5. Run `uv run plot.py` +1. Run `uv run main.py` (uses 10 parallel workers by default) +2. Run `uv run plot.py` + +`TIME_RANGE` is automatically set to the current fiscal quarter (Q1: Oct-Dec, Q2: Jan-Mar, Q3: Apr-Jun, Q4: Jul-Sep). + +The generated chart colors bars by PI objective (see [objectives page](https://nasa-impact.github.io/veda-odd/objectives) for details). + +## Performance + +- **generate_config.py**: Uses GitHub search API to fetch only objective issues (~2-3 seconds) +- **main.py**: Parallelizes API calls with ThreadPoolExecutor (10x faster than sequential) diff --git a/reports/config.py b/reports/config.py index a9dde74..3e84ac1 100644 --- a/reports/config.py +++ b/reports/config.py @@ -1,84 +1,398 @@ -TIME_RANGE = ("20251106", "20251215") - -# Name, username, start date, end date -USERS = [ - # ODD - ("Max Jones", "maxrjones", None, None), - ("Kyle Barron", "kylebarron", None, None), - ("Aimee Barciauskas", "abarciauskas-bgse", None, None), - ("Chuck Daniels", "chuckwondo", None, None), - ("Sean Harkins", "sharkinsspatial", None, None), - ("Henry Rodman", "hrodmn", None, None), - ("Julius Busecke", "jbusecke", "20250623", None), - # Science support - ("Alex Mandel", "wildintellect", None, None), - ("Julia Signell", "jsignell", None, None), - ("Sheyenne Kirkland", "smk0033", None, None), - ("Zac Deziel", "zacdezgeo", None, None), - ("Nathan Zimmermann", "moradology", None, None), - # JupyterHub - ("Sanjay Bhangar", "batpad", None, None), - ("Tarashish Mishra", "sunu", None, None), -] - - -REPOS = [ - ("apache", "arrow-rs"), - ("boettiger-lab", "earthdatalogin"), - ("conda-forge", "r-lasr-feedstock"), - ("cloudnativegeo", "cloud-optimized-geospatial-formats-guide"), - ("datafusion-contrib", "arrow-zarr"), - ("developmentseed", "async-tiff"), - ("developmentseed", "datacube-guide"), - ("developmentseed", "eoAPI"), - ("developmentseed", "eoAPI-cdk"), - ("developmentseed", "geozarr-examples"), - ("developmentseed", "lonboard"), - ("developmentseed", "rio-stac"), - ("developmentseed", "tilebench"), - ("developmentseed", "titiler"), - ("developmentseed", "titiler-cmr"), - ("developmentseed", "titiler-lambda-layer"), - ("developmentseed", "titiler-md-demo"), - ("developmentseed", "titiler-stacapi"), - ("developmentseed", "warp-resample-profiling"), - ("developmentseed", "obspec"), - ("developmentseed", "obstore"), - ("developmentseed", "virtualizarr-data-pipelines"), - ("developmentseed", "zarr-datafusion-search"), - ("earth-mover", "icechunk"), - ("flatgeobuf", "flatgeobuf"), - ("geoarrow", "deck.gl-layers"), - ("geoarrow", "geoarrow-rs"), - ("geopandas", "geopandas"), - ("georust", "geo-svg"), - ("holoviz", "hvplot"), - ("jupyterhub", "repo2docker"), - ("pangeo-data", "pangeo-docker-images"), - ("pangeo-data", "pangeo.io"), - ("maap-project", "gedi-geoparquet"), - ("nasa", "python_cmr"), - ("nasa-openscapes", "earthdata-cloud-cookbook"), - ("nsidc", "earthaccess"), - ("nsidc", "cloud-optimized-icesat2"), - ("numbagg", "numbagg"), - ("pydata", "xarray"), - ("radiantearth", "stac-spec"), - ("stac-extensions", "zarr"), - ("stac-utils", "stac-fastapi-pgstac"), - ("stac-utils", "rustac-py"), - ("stac-utils", "pgstac"), - ("stac-utils", "pystac"), - ("stac-utils", "pystac-client"), - ("stac-utils", "xpystac"), - ("virtual-zarr", "hrrr-parser"), - ("virtual-zarr", "virtual-tiff"), - ("virtual-zarr", "obspec-utils"), - ("zarr-conventions", "spatial"), - ("zarr-conventions", "geo-proj"), - ("zarr-conventions", "multiscales"), - ("zarr-developers", "zarr-python"), - ("zarr-developers", "geozarr-spec"), - ("zarr-developers", "VirtualiZarr"), - ("2i2c-org", "jupyterhub-fancy-profiles"), -] +from datetime import date + +# Manually maintained PI date ranges +# Update these when new PIs are planned +PI_DATES = { + "pi-25.2": ("20250119", "20250418"), + "pi-25.3": ("20250419", "20250718"), + "pi-25.4": ("20250719", "20251018"), + "pi-26.1": ("20251019", "20260117"), + "pi-26.2": ("20260118", "20260425"), +} + + +def get_current_pi(): + """Find the current PI based on today's date.""" + today = date.today().strftime("%Y%m%d") + for pi_name, (start, end) in PI_DATES.items(): + if start <= today <= end: + return pi_name + return None + + +def get_time_range(pi: str = None): + """Get date range for a PI, or current PI if not specified.""" + if pi: + return PI_DATES.get(pi) + current = get_current_pi() + if current: + return PI_DATES[current] + # Fallback to most recent PI if not in any range + return list(PI_DATES.values())[-1] + + +TIME_RANGE = get_time_range() + +# Quarterly objectives with repos and contributors per objective +# Run `uv run generate_config.py` to regenerate from GitHub issues +# - Objectives: Issues with pi-X.Y-objective labels +# - Contributors: Issue assignees +# - Repos: Labels matching repo:org/repo-name +OBJECTIVES = { + "pi-25.2": [ + { + "issue_number": 31, + "title": "ODD PI 25.2 Objective 7: Increase data format support in VirtualiZarr", + "state": "closed", + "contributors": [ + ("Chuck Daniels", "chuckwondo"), + ("Max Jones", "maxrjones"), + ], + "repos": [], + }, + { + "issue_number": 34, + "title": "ODD PI 25.2 Objective 3: Visualize OCO-3 Datasets in VEDA", + "state": "closed", + "contributors": [ + ("Aimee Barciauskas", "abarciauskas-bgse"), + ], + "repos": [], + }, + { + "issue_number": 35, + "title": "ODD PI 25.2 Objective 5: Deliver Virtual Zarr Stores for NASA Datasets Using Icechunk", + "state": "closed", + "contributors": [ + ("Aimee Barciauskas", "abarciauskas-bgse"), + ], + "repos": [], + }, + { + "issue_number": 36, + "title": "ODD PI 25.2 Objective 6: Support for Modernizing VirtualiZarr to use zarr-python 3.0", + "state": "closed", + "contributors": [ + ("Sean Harkins", "sharkinsspatial"), + ("Aimee Barciauskas", "abarciauskas-bgse"), + ], + "repos": [], + }, + { + "issue_number": 37, + "title": "ODD PI 25.2 Objective 8: Support CMR Modernization", + "state": "closed", + "contributors": [ + ("Sean Harkins", "sharkinsspatial"), + ("Kyle Barron", "kylebarron"), + ], + "repos": [], + }, + { + "issue_number": 40, + "title": "ODD PI 25.2 Objective 1: Upgrade titiler and titiler-xarray to zarr-Python 3.0 and deploy to staging", + "state": "closed", + "contributors": [ + ("Max Jones", "maxrjones"), + ], + "repos": [], + }, + { + "issue_number": 41, + "title": "ODD PI 25.2 Objective 4: Draft Web-Optimized Zarr (WOZ) Standard", + "state": "closed", + "contributors": [ + ("Max Jones", "maxrjones"), + ], + "repos": [], + }, + { + "issue_number": 76, + "title": "ODD PI 25.2 Objective 2: Demonstrate how to tile HLS using titiler-cmr", + "state": "closed", + "contributors": [ + ("Henry Rodman", "hrodmn"), + ], + "repos": [], + }, + ], + "pi-25.3": [ + { + "issue_number": 118, + "title": "ODD PI 25.3 Objective 1: Support CMR Modernization", + "state": "open", + "contributors": [ + ("Sean Harkins", "sharkinsspatial"), + ("Kyle Barron", "kylebarron"), + ], + "repos": [], + }, + { + "issue_number": 119, + "title": "ODD PI 25.3 Objective 2: Continue to Build Out the VirtualiZarr Ecosystem", + "state": "closed", + "contributors": [ + ("Sean Harkins", "sharkinsspatial"), + ("Max Jones", "maxrjones"), + ], + "repos": [], + }, + { + "issue_number": 124, + "title": "ODD PI 25.3 Objective 3: Publish Cloud-Optimized Datasets", + "state": "open", + "contributors": [ + ("Chuck Daniels", "chuckwondo"), + ("Aimee Barciauskas", "abarciauskas-bgse"), + ], + "repos": [], + }, + { + "issue_number": 126, + "title": "ODD PI 25.3 Objective 4: Support TiTiler-CMR Adoption", + "state": "closed", + "contributors": [ + ("Sean Harkins", "sharkinsspatial"), + ("Henry Rodman", "hrodmn"), + ], + "repos": [], + }, + { + "issue_number": 127, + "title": "ODD PI 25.3 Objective 6: Community Involvement", + "state": "closed", + "contributors": [ + ("Max Jones", "maxrjones"), + ("Aimee Barciauskas", "abarciauskas-bgse"), + ("Henry Rodman", "hrodmn"), + ], + "repos": [], + }, + { + "issue_number": 165, + "title": "ODD PI 25.3 Objective 5: Foundational Zarr-Python and Xarray Contributions", + "state": "closed", + "contributors": [ + ("Davis Bennett", "d-v-b"), + ("Max Jones", "maxrjones"), + ], + "repos": [], + }, + ], + "pi-25.4": [ + { + "issue_number": 121, + "title": "Potential ODD PI 25.4 Objective: Visualize Web-Optimized Zarr (WOZ) in VEDA (preview)", + "state": "closed", + "contributors": [ + ("Max Jones", "maxrjones"), + ], + "repos": [], + }, + { + "issue_number": 122, + "title": "Potential 25.4 ODD Objective: Research, develop and document methods for Zarr and VirtualiZarr visualization", + "state": "closed", + "contributors": [ + ("Max Jones", "maxrjones"), + ("Kyle Barron", "kylebarron"), + ], + "repos": [], + }, + { + "issue_number": 197, + "title": "ODD PI 25.4 Objective 1: 🎬 TiTiler-CMR is production ready", + "state": "open", + "contributors": [ + ("Aimee Barciauskas", "abarciauskas-bgse"), + ("Henry Rodman", "hrodmn"), + ], + "repos": [], + }, + { + "issue_number": 198, + "title": "ODD PI 25.4 Objective 2: 🚀 Dataset support for VEDA instances", + "state": "closed", + "contributors": [ + ("Max Jones", "maxrjones"), + ("Julius Busecke", "jbusecke"), + ], + "repos": [], + }, + { + "issue_number": 203, + "title": "ODD PI 25.4 Objective 3: 🗺️Research, develop and document methods for Zarr and VirtualiZarr data services", + "state": "open", + "contributors": [ + ("Max Jones", "maxrjones"), + ], + "repos": [], + }, + { + "issue_number": 204, + "title": "ODD PI 25.4 Objective 4: 🛠️ Zarr Development", + "state": "open", + "contributors": [ + ("Davis Bennett", "d-v-b"), + ("Max Jones", "maxrjones"), + ], + "repos": [], + }, + { + "issue_number": 205, + "title": "ODD PI 25.4 Objective 5: 🤗 Community engagement", + "state": "closed", + "contributors": [ + ("Sean Harkins", "sharkinsspatial"), + ("Chuck Daniels", "chuckwondo"), + ("Max Jones", "maxrjones"), + ("Aimee Barciauskas", "abarciauskas-bgse"), + ], + "repos": [], + }, + { + "issue_number": 206, + "title": "ODD PI 25.4 Objective 6: 📦 Obstore outreach", + "state": "closed", + "contributors": [ + ("Chuck Daniels", "chuckwondo"), + ("Kyle Barron", "kylebarron"), + ], + "repos": [], + }, + ], + "pi-26.1": [ + { + "issue_number": 244, + "title": "ODD PI 26.1 Objective 1: 🗺️ Add dynamic tiling and timeseries support for Virtual Zarr Stores", + "state": "open", + "contributors": [ + ("Julius Busecke", "jbusecke"), + ("Henry Rodman", "hrodmn"), + ], + "repos": [ + ("developmentseed", "titiler"), + ("earth-mover", "icechunk"), + ], + }, + { + "issue_number": 245, + "title": "ODD PI 26.1 Objective 2: 🌍 Add dynamic tiling and timeseries support for datasets in CMR", + "state": "open", + "contributors": [ + ("Aimee Barciauskas", "abarciauskas-bgse"), + ("Henry Rodman", "hrodmn"), + ], + "repos": [ + ("developmentseed", "titiler"), + ("developmentseed", "titiler-cmr"), + ("developmentseed", "titiler-lambda-layer"), + ("developmentseed", "titiler-md-demo"), + ("nasa", "python_cmr"), + ], + }, + { + "issue_number": 246, + "title": "ODD PI 26.1 Objective 3: 🤖 Support virtualization of additional data products", + "state": "open", + "contributors": [ + ("Sean Harkins", "sharkinsspatial"), + ("Max Jones", "maxrjones"), + ("Julius Busecke", "jbusecke"), + ], + "repos": [ + ("zarr-developers", "virtualizarr"), + ("virtual-zarr", "obspec-utils"), + ("virtual-zarr", "virtual-tiff"), + ("virtual-zarr", "hrrr-parser"), + ("developmentseed", "async-tiff"), + ("developmentseed", "virtualizarr-data-pipelines"), + ("earth-mover", "icechunk"), + ], + }, + { + "issue_number": 247, + "title": "ODD PI 26.1 Objective 4: 🛰 Explore scalable, cloud native approaches for search, discovery and access of non-gridded data", + "state": "closed", + "contributors": [ + ("Sean Harkins", "sharkinsspatial"), + ("Kyle Barron", "kylebarron"), + ], + "repos": [ + ("developmentseed", "obstore"), + ("developmentseed", "obspec"), + ("developmentseed", "zarr-datafusion-search"), + ("geoarrow", "geoarrow-rs"), + ("datafusion-contrib", "arrow-zarr"), + ], + }, + { + "issue_number": 248, + "title": "ODD PI 26.1 Objective 5: 🤗 Support community adoption of the technologies incubated by EODC and VEDA", + "state": "open", + "contributors": [ + ("Sean Harkins", "sharkinsspatial"), + ("Chuck Daniels", "chuckwondo"), + ("Max Jones", "maxrjones"), + ("Aimee Barciauskas", "abarciauskas-bgse"), + ], + "repos": [ + ("zarr-developers", "geozarr-spec"), + ("zarr-developers", "zarr-python"), + ("zarr-conventions", "multiscales"), + ("zarr-conventions", "geo-proj"), + ("zarr-conventions", "spatial"), + ("developmentseed", "datacube-guide"), + ("developmentseed", "geozarr-examples"), + ("developmentseed", "warp-resample-profiling"), + ("pangeo-data", "pangeo.io"), + ("pangeo-data", "pangeo-docker-images"), + ("nasa-openscapes", "earthdata-cloud-cookbook"), + ], + }, + ], +} + + +def get_all_repos(): + """Derive unique repos from all objectives.""" + repos = set() + for pi_objectives in OBJECTIVES.values(): + for obj in pi_objectives: + for repo in obj["repos"]: + repos.add(repo) + return sorted(repos) + + +def get_all_contributors(): + """Derive unique contributors from all objectives.""" + contributors = {} + for pi_objectives in OBJECTIVES.values(): + for obj in pi_objectives: + for name, username in obj["contributors"]: + contributors[username] = name + return [ + (name, username) + for username, name in sorted(contributors.items(), key=lambda x: x[1]) + ] + + +def get_repos_for_pi(pi: str): + """Get all repos for a specific PI.""" + repos = set() + for obj in OBJECTIVES.get(pi, []): + for repo in obj["repos"]: + repos.add(repo) + return sorted(repos) + + +def get_contributors_for_pi(pi: str): + """Get all contributors for a specific PI.""" + contributors = {} + for obj in OBJECTIVES.get(pi, []): + for name, username in obj["contributors"]: + contributors[username] = name + return [ + (name, username) + for username, name in sorted(contributors.items(), key=lambda x: x[1]) + ] diff --git a/reports/generate_config.py b/reports/generate_config.py new file mode 100644 index 0000000..3bd6135 --- /dev/null +++ b/reports/generate_config.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +""" +Generate OBJECTIVES config from GitHub issues with pi-*-objective labels. + +Data sources: +- Objectives: Issues with `pi-X.Y-objective` labels +- Contributors: Issue assignees +- Repos: Labels matching `repo:org/repo-name` pattern + +Usage: + uv run generate_config.py +""" + +import os +import re +from github import Github, Auth + + +def get_objective_issues(g: Github, repo_name: str = "NASA-IMPACT/veda-odd"): + """Fetch all issues with pi-*-objective labels using search API.""" + objectives_by_pi = {} + + # Use search API - much faster than iterating all issues + # Search for issues with any pi-*-objective label + query = f"repo:{repo_name} is:issue label:pi-25.2-objective,pi-25.3-objective,pi-25.4-objective,pi-26.1-objective,pi-26.2-objective,pi-26.3-objective,pi-26.4-objective" + issues = g.search_issues(query) + + for issue in issues: + pi = None + repos = [] + + for label in issue.labels: + # Check for PI objective label + match = re.match(r"pi-(\d+\.\d+)-objective", label.name) + if match: + pi = f"pi-{match.group(1)}" + + # Check for repo label (format: repo:org/repo-name) + if label.name.startswith("repo:"): + repo_str = label.name[5:] # Remove "repo:" prefix + if "/" in repo_str: + org, repo_name_part = repo_str.split("/", 1) + repos.append((org, repo_name_part)) + + if pi: + if pi not in objectives_by_pi: + objectives_by_pi[pi] = [] + + # Get assignees + contributors = [ + (assignee.name or assignee.login, assignee.login) + for assignee in issue.assignees + ] + + objectives_by_pi[pi].append( + { + "issue_number": issue.number, + "title": issue.title, + "contributors": contributors, + "state": issue.state, + "repos": repos, + } + ) + + return objectives_by_pi + + +def generate_config(objectives_by_pi: dict) -> str: + """Generate Python config code from objectives data.""" + lines = [ + "from datetime import date", + "", + "# Manually maintained PI date ranges", + "# Update these when new PIs are planned", + "PI_DATES = {", + ' "pi-25.2": ("20250119", "20250418"),', + ' "pi-25.3": ("20250419", "20250718"),', + ' "pi-25.4": ("20250719", "20251018"),', + ' "pi-26.1": ("20251019", "20260117"),', + ' "pi-26.2": ("20260118", "20260425"),', + "}", + "", + "", + "def get_current_pi():", + ' """Find the current PI based on today\'s date."""', + ' today = date.today().strftime("%Y%m%d")', + " for pi_name, (start, end) in PI_DATES.items():", + " if start <= today <= end:", + " return pi_name", + " return None", + "", + "", + "def get_time_range(pi: str = None):", + ' """Get date range for a PI, or current PI if not specified."""', + " if pi:", + " return PI_DATES.get(pi)", + " current = get_current_pi()", + " if current:", + " return PI_DATES[current]", + " # Fallback to most recent PI if not in any range", + " return list(PI_DATES.values())[-1]", + "", + "", + "TIME_RANGE = get_time_range()", + "", + "# Quarterly objectives with repos and contributors per objective", + "# Run `uv run generate_config.py` to regenerate from GitHub issues", + "# - Objectives: Issues with pi-X.Y-objective labels", + "# - Contributors: Issue assignees", + "# - Repos: Labels matching repo:org/repo-name", + "OBJECTIVES = {", + ] + + # Sort PIs chronologically + sorted_pis = sorted(objectives_by_pi.keys(), key=lambda x: float(x.split("-")[1])) + + for pi in sorted_pis: + objectives = objectives_by_pi[pi] + lines.append(f' "{pi}": [') + + # Sort objectives by issue number + for obj in sorted(objectives, key=lambda x: x["issue_number"]): + lines.append(" {") + lines.append(f' "issue_number": {obj["issue_number"]},') + title = obj["title"].replace('"', '\\"') + lines.append(f' "title": "{title}",') + lines.append(f' "state": "{obj["state"]}",') + lines.append(' "contributors": [') + for name, username in obj["contributors"]: + name = (name or username).replace('"', '\\"') + lines.append(f' ("{name}", "{username}"),') + lines.append(" ],") + lines.append(' "repos": [') + for org, repo in obj.get("repos", []): + lines.append(f' ("{org}", "{repo}"),') + lines.append(" ],") + lines.append(" },") + + lines.append(" ],") + + lines.append("}") + lines.append("") + lines.append("") + lines.append("def get_all_repos():") + lines.append(' """Derive unique repos from all objectives."""') + lines.append(" repos = set()") + lines.append(" for pi_objectives in OBJECTIVES.values():") + lines.append(" for obj in pi_objectives:") + lines.append(' for repo in obj["repos"]:') + lines.append(" repos.add(repo)") + lines.append(" return sorted(repos)") + lines.append("") + lines.append("") + lines.append("def get_all_contributors():") + lines.append(' """Derive unique contributors from all objectives."""') + lines.append(" contributors = {}") + lines.append(" for pi_objectives in OBJECTIVES.values():") + lines.append(" for obj in pi_objectives:") + lines.append(' for name, username in obj["contributors"]:') + lines.append(" contributors[username] = name") + lines.append( + " return [(name, username) for username, name in sorted(contributors.items(), key=lambda x: x[1])]" + ) + lines.append("") + lines.append("") + lines.append("def get_repos_for_pi(pi: str):") + lines.append(' """Get all repos for a specific PI."""') + lines.append(" repos = set()") + lines.append(" for obj in OBJECTIVES.get(pi, []):") + lines.append(' for repo in obj["repos"]:') + lines.append(" repos.add(repo)") + lines.append(" return sorted(repos)") + lines.append("") + lines.append("") + lines.append("def get_contributors_for_pi(pi: str):") + lines.append(' """Get all contributors for a specific PI."""') + lines.append(" contributors = {}") + lines.append(" for obj in OBJECTIVES.get(pi, []):") + lines.append(' for name, username in obj["contributors"]:') + lines.append(" contributors[username] = name") + lines.append( + " return [(name, username) for username, name in sorted(contributors.items(), key=lambda x: x[1])]" + ) + + return "\n".join(lines) + + +def main(): + token = os.environ.get("GH_ODD_PAT") or os.environ.get("GITHUB_TOKEN") + if not token: + raise ValueError("Set GH_ODD_PAT or GITHUB_TOKEN environment variable") + + auth = Auth.Token(token) + g = Github(auth=auth) + + print("Fetching objective issues from GitHub (using search API)...") + objectives_by_pi = get_objective_issues(g) + + g.close() + + print(f"Found {len(objectives_by_pi)} PIs:") + for pi, objs in sorted(objectives_by_pi.items()): + repos_count = sum(len(o["repos"]) for o in objs) + print(f" {pi}: {len(objs)} objectives, {repos_count} repo mappings") + + config_code = generate_config(objectives_by_pi) + + output_file = "config.py" + with open(output_file, "w") as f: + f.write(config_code) + print(f"\nGenerated config written to {output_file}") + print("\nTo add repos to an objective, add labels like:") + print(" repo:zarr-developers/VirtualiZarr") + print(" repo:developmentseed/titiler-cmr") + + +if __name__ == "__main__": + main() diff --git a/reports/generate_docs.py b/reports/generate_docs.py new file mode 100644 index 0000000..98081c6 --- /dev/null +++ b/reports/generate_docs.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +""" +Generate docs/objectives.md from config.py OBJECTIVES. + +Usage: + uv run generate_docs.py +""" + +from config import OBJECTIVES + + +def generate_objectives_md() -> str: + """Generate markdown content for objectives page.""" + lines = [ + "# Quarterly Objectives", + "", + "This page tracks quarterly objectives and their related repositories across Program Increments (PIs).", + "", + ] + + # Sort PIs reverse chronologically (newest first) + sorted_pis = sorted( + OBJECTIVES.keys(), key=lambda x: float(x.split("-")[1]), reverse=True + ) + + for i, pi in enumerate(sorted_pis): + objectives = OBJECTIVES[pi] + pi_upper = pi.upper().replace("-", " ") + + if i == 0: + # Current PI - show full details + lines.append(f"## Current PI: {pi.split('-')[1]}") + lines.append("") + lines.append("| # | Objective | Contributors | Repos |") + lines.append("|---|-----------|--------------|-------|") + + for obj in sorted(objectives, key=lambda x: x["issue_number"]): + num = obj["issue_number"] + # Clean up title (remove PI prefix if present) + title = obj["title"] + if "Objective" in title and ":" in title: + title = title.split(":", 1)[1].strip() + title = title[:60] + "..." if len(title) > 60 else title + + contributors = ", ".join(u for _, u in obj["contributors"]) + repos = ", ".join(r for _, r in obj["repos"]) if obj["repos"] else "-" + + lines.append( + f"| [#{num}](https://github.com/NASA-IMPACT/veda-odd/issues/{num}) | {title} | {contributors} | {repos} |" + ) + + lines.append("") + lines.append("---") + lines.append("") + else: + # Historical PIs - collapsible + closed_count = sum(1 for o in objectives if o["state"] == "closed") + + lines.append("
") + lines.append( + f"{pi_upper} ({len(objectives)} objectives, {closed_count} closed)" + ) + lines.append("") + lines.append("| # | Objective | State | Contributors |") + lines.append("|---|-----------|-------|--------------|") + + for obj in sorted(objectives, key=lambda x: x["issue_number"]): + num = obj["issue_number"] + title = obj["title"] + if "Objective" in title and ":" in title: + title = title.split(":", 1)[1].strip() + title = title[:50] + "..." if len(title) > 50 else title + + state = obj["state"] + contributors = ", ".join(u for _, u in obj["contributors"]) + + lines.append( + f"| [#{num}](https://github.com/NASA-IMPACT/veda-odd/issues/{num}) | {title} | {state} | {contributors} |" + ) + + lines.append("") + lines.append("
") + lines.append("") + + lines.append("---") + lines.append("") + lines.append("## Visualization") + lines.append("") + lines.append( + "The commits per repository chart uses color-coding to show which objective each repo contributes to. Repos that contribute to multiple objectives are shown with split bars." + ) + lines.append("") + # Add image for the current PI + current_pi = sorted_pis[0] + lines.append( + f"![{current_pi.upper()} Commits per Repository](images/{current_pi}.png)" + ) + lines.append("") + lines.append("---") + lines.append("") + lines.append("## Configuration") + lines.append("") + lines.append( + "Objectives are configured in [`reports/config.py`](https://github.com/NASA-IMPACT/veda-odd/blob/main/reports/config.py)." + ) + lines.append("") + lines.append("To regenerate this page from config:") + lines.append("") + lines.append("```bash") + lines.append("cd reports") + lines.append("uv run generate_docs.py") + lines.append("```") + lines.append("") + lines.append( + "See [FY26 Roadmap](./fy26-roadmap.md) for the broader context of these objectives." + ) + + return "\n".join(lines) + + +def main(): + content = generate_objectives_md() + + output_file = "../docs/objectives.md" + with open(output_file, "w") as f: + f.write(content) + + print(f"Generated {output_file}") + + # Print summary + total_objectives = sum(len(objs) for objs in OBJECTIVES.values()) + print(f" {len(OBJECTIVES)} PIs, {total_objectives} total objectives") + + +if __name__ == "__main__": + main() diff --git a/reports/main.py b/reports/main.py index 2fd033d..58dccd9 100644 --- a/reports/main.py +++ b/reports/main.py @@ -1,136 +1,157 @@ #!/usr/bin/env python3 """ -Query GitHub API for commits to a given repository +Query GitHub API for commits to repositories in parallel. """ from github import Github, Auth from datetime import datetime from typing import List +from concurrent.futures import ThreadPoolExecutor, as_completed import os import pandas as pd -from config import USERS, REPOS, TIME_RANGE - - -def get_commits_for_author( - repository, +from config import ( + get_time_range, + get_current_pi, + get_repos_for_pi, + get_contributors_for_pi, +) + + +def get_commits_for_repo_author( + g: Github, + owner: str, + repo: str, author: str, start_date: datetime, end_date: datetime, -) -> List: +) -> List[dict]: """ - Query GitHub API for commits by a specific author within a date range + Query GitHub API for commits by a specific author in a repo. - Args: - repository: GitHub repository object (already connected) - author: GitHub username/email to filter commits by - start_date: Start date for commit search (inclusive) - end_date: End date for commit search (inclusive) + Returns list of commit detail dicts (not commit objects) to avoid + thread safety issues with PyGithub objects. + """ + try: + repository = g.get_repo(f"{owner}/{repo}") + commits = repository.get_commits( + author=author, since=start_date, until=end_date + ) - Returns: - List of commit objects + # Group commits by PR + prs = [] + pr_commits = [] + standalone_commits = [] + + for commit in commits: + pulls = commit.get_pulls() + if pulls.totalCount == 1: + if (number := pulls[0].number) not in prs: + pr_commits.append(commit) + prs.append(number) + elif pulls.totalCount == 0: + standalone_commits.append(commit) + + # Extract details immediately (avoid returning PyGithub objects) + results = [] + for commit in pr_commits + standalone_commits: + results.append( + { + "sha": commit.sha, + "message": commit.commit.message.split("\n")[0], + "author": commit.commit.author.name, + "committer": commit.commit.committer.name, + "url": commit.html_url, + "total_changes": commit.stats.total if commit.stats else 0, + "organization": owner, + "repository": repo, + } + ) + return results + except Exception as e: + print(f" Error processing {owner}/{repo} for {author}: {e}") + return [] + + +def main(token: str = None, pi: str = None, max_workers: int = 10): """ - # Get commits with filters using the existing repository connection - commits = repository.get_commits(author=author, since=start_date, until=end_date) - # Group commits by PR - prs = [] - pr_commits = [] - standalone_commits = [] - - for commit in commits: - # Get PRs associated with this commit - pulls = commit.get_pulls() - - if pulls.totalCount == 1: - # Commit is part of one or more PRs - if (number := pulls[0].number) not in prs: - pr_commits.append(commit) - prs.append(number) - elif pulls.totalCount == 0: - # Commit is not part of any PR (direct to branch) - standalone_commits.append(commit) - else: - raise ValueError(f"Unexpected pulls.totalCount: {pulls.totalCount}") - # Convert PaginatedList to regular list - commit_list = pr_commits + standalone_commits - return commit_list - - -def get_commit_details(commit) -> dict: - """Extract detailed commit information""" - return { - "sha": commit.sha, - "message": commit.commit.message.split("\n")[0], - "author": commit.commit.author.name, - "committer": commit.commit.committer.name, - "url": commit.html_url, - "total_changes": commit.stats.total if commit.stats else 0, - } - - -def main(token: str = None): - time_start = datetime.strptime(TIME_RANGE[0], "%Y%m%d") - time_end = datetime.strptime(TIME_RANGE[1], "%Y%m%d") - all_commits = [] + Query GitHub for commits using parallel requests. + + Args: + token: GitHub personal access token + pi: Optional PI to filter repos/contributors (e.g., "pi-26.1"). + If None, uses current PI based on today's date. + max_workers: Number of parallel threads (default 10) + """ + # Default to current PI if not specified + if pi is None: + pi = get_current_pi() + + time_range = get_time_range(pi) + if not time_range: + raise ValueError(f"No date range found for PI: {pi}") + + time_start = datetime.strptime(time_range[0], "%Y%m%d") + time_end = datetime.strptime(time_range[1], "%Y%m%d") + + # Get repos and contributors for the PI + repos = get_repos_for_pi(pi) + contributors = get_contributors_for_pi(pi) + print( + f"PI: {pi} ({time_start.strftime('%Y-%m-%d')} to {time_end.strftime('%Y-%m-%d')})" + ) + print(f" {len(repos)} repos, {len(contributors)} contributors") - # Initialize GitHub client once - if token: - auth = Auth.Token(token) - g = Github(auth=auth) - else: - g = Github() # Unauthenticated (lower rate limits) + if len(contributors) < 1: + raise ValueError("No contributors found in config.") - if len(USERS) < 1: - raise ValueError( - "No users were included in the config. See README for instructions on populating the USER list." - ) + # Build list of (repo, contributor) tasks + tasks = [] + for owner, repo in repos: + for name, username in contributors: + tasks.append((owner, repo, username)) - # Iterate through repositories first - for owner, repo in REPOS: - print(f"Processing repository: {owner}/{repo}") + print( + f"Querying {len(tasks)} repo×contributor combinations with {max_workers} workers..." + ) - # Get repository object once per repository - repository = g.get_repo(f"{owner}/{repo}") + all_commits = [] - # Iterate through all users and their emails for this repository - for name, username, start_date_str, end_date_str in USERS: - # Parse dates for this user - start_date = ( - datetime.strptime(start_date_str, "%Y%m%d") - if start_date_str - else time_start - ) - end_date = ( - datetime.strptime(end_date_str, "%Y%m%d") if end_date_str else time_end + # Use thread pool for parallel API calls + # Each thread gets its own Github client to avoid rate limit issues + def process_task(task): + owner, repo, username = task + if token: + auth = Auth.Token(token) + g = Github(auth=auth) + else: + g = Github() + try: + return get_commits_for_repo_author( + g, owner, repo, username, time_start, time_end ) + finally: + g.close() - print(f" Processing user: {username}") - commits = get_commits_for_author( - repository=repository, - author=username, - start_date=start_date, - end_date=end_date, - ) - for commit in commits: - commit_details = get_commit_details(commit) - commit_details.update( - { - "organization": owner, - "repository": repo, - } - ) - all_commits.append(commit_details) - - g.close() + completed = 0 + with ThreadPoolExecutor(max_workers=max_workers) as executor: + futures = {executor.submit(process_task, task): task for task in tasks} + for future in as_completed(futures): + completed += 1 + if completed % 50 == 0: + print(f" Progress: {completed}/{len(tasks)}") + commits = future.result() + all_commits.extend(commits) + + print(f"Found {len(all_commits)} commits") df = pd.DataFrame(all_commits) - csv_filename = ( - f"output/{time_start.strftime('%Y-%m-%d')}-{time_end.strftime('%Y-%m-%d')}.csv" - ) + csv_filename = f"output/{pi}.csv" df.to_csv(csv_filename, index=False) + print(f"Saved to {csv_filename}") return df if __name__ == "__main__": - token = os.environ["GH_ODD_PAT"] + token = os.environ.get("GH_ODD_PAT") or os.environ.get("GITHUB_TOKEN") main(token=token) diff --git a/reports/output/pi-26.1.csv b/reports/output/pi-26.1.csv new file mode 100644 index 0000000..18f84fd --- /dev/null +++ b/reports/output/pi-26.1.csv @@ -0,0 +1,120 @@ +sha,message,author,committer,url,total_changes,organization,repository +2b155d610ec62b610a4edb66061cb03543ad126a,Fix reproducibility issues (#17),Max Jones,GitHub,https://github.com/developmentseed/datacube-guide/commit/2b155d610ec62b610a4edb66061cb03543ad126a,3712,developmentseed,datacube-guide +3397ad783bd6789f638177d1406e8e30e28ab7c8,Remove titiler cmr benchmarking (#16),Aimee Barciauskas,GitHub,https://github.com/developmentseed/datacube-guide/commit/3397ad783bd6789f638177d1406e8e30e28ab7c8,12547,developmentseed,datacube-guide +ef2381ebc5646ef27091c4f49d4d85138789f535,Remove broken symlink when building windows wheels (#120),Max Jones,GitHub,https://github.com/developmentseed/async-tiff/commit/ef2381ebc5646ef27091c4f49d4d85138789f535,65,developmentseed,async-tiff +b7f0e6137d4df2f63c215949b3688bb3464b64a5,Latitude and Longitude should be an option for dim names (#1268),Aimee Barciauskas,GitHub,https://github.com/developmentseed/titiler/commit/b7f0e6137d4df2f63c215949b3688bb3464b64a5,4,developmentseed,titiler +1e4bdd65e02b3e36261a10d0c5a4f96b18e76ac5,fix: add opener_options arg to titiler.xarray.io.Reader (#1248),Henry Rodman,GitHub,https://github.com/developmentseed/titiler/commit/1e4bdd65e02b3e36261a10d0c5a4f96b18e76ac5,70,developmentseed,titiler +899f104ddd40f3e8a1a36da487d3825e04da68bd,Bump minimum supported python version to 3.11 (#1254),Max Jones,GitHub,https://github.com/developmentseed/titiler/commit/899f104ddd40f3e8a1a36da487d3825e04da68bd,1073,developmentseed,titiler +e6389e6e9636e262b41b0e2b0d6db0fdd5e81e7e,Add a upstream workflow with a dispatch trigger; follow SPEC0 (#1245),Max Jones,GitHub,https://github.com/developmentseed/titiler/commit/e6389e6e9636e262b41b0e2b0d6db0fdd5e81e7e,46,developmentseed,titiler +24350b3d8baa47b41e95db44c7a842742f5ded3c,Remove deprecated FastAPI on_event decorator. (#107),Chuck Daniels,GitHub,https://github.com/developmentseed/titiler-cmr/commit/24350b3d8baa47b41e95db44c7a842742f5ded3c,43,developmentseed,titiler-cmr +66a9a806e5ac85bbc419ba6e90c6fbc0009efce3,Avoid recording EDL token during testing. (#105),Chuck Daniels,GitHub,https://github.com/developmentseed/titiler-cmr/commit/66a9a806e5ac85bbc419ba6e90c6fbc0009efce3,258,developmentseed,titiler-cmr +b9214801fd760a2a3af22bd26a423300f8577d74,chore: update changelog (#97),Henry Rodman,GitHub,https://github.com/developmentseed/titiler-cmr/commit/b9214801fd760a2a3af22bd26a423300f8577d74,13,developmentseed,titiler-cmr +e0e87b8cf3e205be282992402f10147fac68ac8c,feat: add variable stats to /compatibility output for xarray datasets (#82),Henry Rodman,GitHub,https://github.com/developmentseed/titiler-cmr/commit/e0e87b8cf3e205be282992402f10147fac68ac8c,396,developmentseed,titiler-cmr +b763a3f428a0161c635ad6005a6c54dcb58a0a7f,"feat: convert to container function, add OpenTelemetry + X-Ray tracing (#81)",Henry Rodman,GitHub,https://github.com/developmentseed/titiler-cmr/commit/b763a3f428a0161c635ad6005a6c54dcb58a0a7f,746,developmentseed,titiler-cmr +c2ed5fea20cb05f1909e9e4c8f28242c34a6bae1,chore(docs): fix mkdocs action,hrodmn,hrodmn,https://github.com/developmentseed/titiler-cmr/commit/c2ed5fea20cb05f1909e9e4c8f28242c34a6bae1,39,developmentseed,titiler-cmr +aec2ed560e7253449f5f09eb7025f01403218e69,feat: Allow S3 HTTP URLs without region (#590),Kyle Barron,GitHub,https://github.com/developmentseed/obstore/commit/aec2ed560e7253449f5f09eb7025f01403218e69,4,developmentseed,obstore +5819904cb4f2ccffd4ccd4718adc69162f70a5e2,chore: Bump pyo3-object_store to 0.7 (#586),Kyle Barron,GitHub,https://github.com/developmentseed/obstore/commit/5819904cb4f2ccffd4ccd4718adc69162f70a5e2,9,developmentseed,obstore +f1970d9508254f7cf9677e232ddd86fe6a6653cd,chore: Bump pyo3 to 0.27 (#584),Kyle Barron,GitHub,https://github.com/developmentseed/obstore/commit/f1970d9508254f7cf9677e232ddd86fe6a6653cd,680,developmentseed,obstore +956add147d24eb15ea934d50c68b47d64442770c,chore: Bump pyo3-bytes to pyo3 0.27 (#583),Kyle Barron,GitHub,https://github.com/developmentseed/obstore/commit/956add147d24eb15ea934d50c68b47d64442770c,21,developmentseed,obstore +69031c222f8817045914ef01fdc60d1a05c34d15,Add view of algorithms overview diagram (#30),Max Jones,GitHub,https://github.com/developmentseed/warp-resample-profiling/commit/69031c222f8817045914ef01fdc60d1a05c34d15,13,developmentseed,warp-resample-profiling +682d2bfb4b9ef9d1254011025000b8d747c0c440,Icechunk update for S3 requester pays support. (#27),Sean Harkins,GitHub,https://github.com/developmentseed/zarr-datafusion-search/commit/682d2bfb4b9ef9d1254011025000b8d747c0c440,1542,developmentseed,zarr-datafusion-search +126e60e52fe90cf192d729e490b2d0fc43227c91,Include missing construct scope parameters for event target.,sharkinsspatial,sharkinsspatial,https://github.com/developmentseed/virtualizarr-data-pipelines/commit/126e60e52fe90cf192d729e490b2d0fc43227c91,2,developmentseed,virtualizarr-data-pipelines +83d3b5f4e07e97d9590862ff9be7a59aa644fc4b,README updates.,sharkinsspatial,sharkinsspatial,https://github.com/developmentseed/virtualizarr-data-pipelines/commit/83d3b5f4e07e97d9590862ff9be7a59aa644fc4b,12,developmentseed,virtualizarr-data-pipelines +0931b183b0ed151cc4707130664d6f93ddbbbb0b,Make max_concurrency user configurable.,sharkinsspatial,sharkinsspatial,https://github.com/developmentseed/virtualizarr-data-pipelines/commit/0931b183b0ed151cc4707130664d6f93ddbbbb0b,4,developmentseed,virtualizarr-data-pipelines +c4e05ff790593f57b2edad686caad0b119bf18c9,Add AWS Batch infrastructure for managing Icechunk garbage collection.,sharkinsspatial,sharkinsspatial,https://github.com/developmentseed/virtualizarr-data-pipelines/commit/c4e05ff790593f57b2edad686caad0b119bf18c9,550,developmentseed,virtualizarr-data-pipelines +6bd5a3b4ab2e51fe191d1c209c75bbb384c271a4,Fix process file typo.,sharkinsspatial,sharkinsspatial,https://github.com/developmentseed/virtualizarr-data-pipelines/commit/6bd5a3b4ab2e51fe191d1c209c75bbb384c271a4,2,developmentseed,virtualizarr-data-pipelines +a866a2162db2581ee4d3965eaba1ef4be1aa1665,Change VirtualizarrProcessor protocol from append to process_file.,sharkinsspatial,sharkinsspatial,https://github.com/developmentseed/virtualizarr-data-pipelines/commit/a866a2162db2581ee4d3965eaba1ef4be1aa1665,44,developmentseed,virtualizarr-data-pipelines +688e880d5b90e72b517a677a3d861b8701d808ce,README updates.,sharkinsspatial,sharkinsspatial,https://github.com/developmentseed/virtualizarr-data-pipelines/commit/688e880d5b90e72b517a677a3d861b8701d808ce,4,developmentseed,virtualizarr-data-pipelines +90313ab88c53e88d088ecbb027d4480ba82b168e,README updates.,sharkinsspatial,sharkinsspatial,https://github.com/developmentseed/virtualizarr-data-pipelines/commit/90313ab88c53e88d088ecbb027d4480ba82b168e,22,developmentseed,virtualizarr-data-pipelines +30e865581777f1e7179af5e129e5f49b4e291605,Initial README.,sharkinsspatial,sharkinsspatial,https://github.com/developmentseed/virtualizarr-data-pipelines/commit/30e865581777f1e7179af5e129e5f49b4e291605,4914,developmentseed,virtualizarr-data-pipelines +0cd1feedc9d3c4e03b4421d81fa3fa9c2faae124,Add initial cdk infrastructure and update lambda Dockerfiles.,sharkinsspatial,sharkinsspatial,https://github.com/developmentseed/virtualizarr-data-pipelines/commit/0cd1feedc9d3c4e03b4421d81fa3fa9c2faae124,986,developmentseed,virtualizarr-data-pipelines +1ff163e2271f58c4f1bca18519480c31bf1626fe,Add example garbage collection implementation.,sharkinsspatial,sharkinsspatial,https://github.com/developmentseed/virtualizarr-data-pipelines/commit/1ff163e2271f58c4f1bca18519480c31bf1626fe,43,developmentseed,virtualizarr-data-pipelines +8ac6a1132fabddc0affbffd829883a6229bb1231,First,sharkinsspatial,sharkinsspatial,https://github.com/developmentseed/virtualizarr-data-pipelines/commit/8ac6a1132fabddc0affbffd829883a6229bb1231,1649,developmentseed,virtualizarr-data-pipelines +b987f25719722351c92ef43423b796995f5d6172,chore: Prepare 0.7.0 release (#1418),Kyle Barron,GitHub,https://github.com/geoarrow/geoarrow-rs/commit/b987f25719722351c92ef43423b796995f5d6172,947,geoarrow,geoarrow-rs +e17e9e48a6e1b357d5c71839dd33c7cb72d853c0,chore: Clippy updates (#1415),Kyle Barron,GitHub,https://github.com/geoarrow/geoarrow-rs/commit/e17e9e48a6e1b357d5c71839dd33c7cb72d853c0,7,geoarrow,geoarrow-rs +e98313a6cfe6549cec9f8cbcd0092a59ab40436e,"chore(js): Clean up JS bindings, deleting most unused code (#1406)",Kyle Barron,GitHub,https://github.com/geoarrow/geoarrow-rs/commit/e98313a6cfe6549cec9f8cbcd0092a59ab40436e,1690,geoarrow,geoarrow-rs +7abc908aad799542e1cc5ddef129842a72424190,chore(python): prepare 0.6.2 (#1405),Kyle Barron,GitHub,https://github.com/geoarrow/geoarrow-rs/commit/7abc908aad799542e1cc5ddef129842a72424190,6,geoarrow,geoarrow-rs +56e1f96a969e30ff56e0d345397d3990b277a19e,chore(python): Prepare 0.6.1 (#1404),Kyle Barron,GitHub,https://github.com/geoarrow/geoarrow-rs/commit/56e1f96a969e30ff56e0d345397d3990b277a19e,8,geoarrow,geoarrow-rs +aa89078b014ab734df5e399f4ff7af906d262c5c,feat(Python): Update macOS runners in GitHub workflows; build intel wheels (#1403),Kyle Barron,GitHub,https://github.com/geoarrow/geoarrow-rs/commit/aa89078b014ab734df5e399f4ff7af906d262c5c,11,geoarrow,geoarrow-rs +c3d151c9a63f956c5c8068acbf9ef493cb23d245,chore(python): Build wheels for python 3.14 (#1399),Kyle Barron,GitHub,https://github.com/geoarrow/geoarrow-rs/commit/c3d151c9a63f956c5c8068acbf9ef493cb23d245,20,geoarrow,geoarrow-rs +2e7dea71d105d368a9d94c1d5f5b8b1f65ba195f,fix(geoarrow-array): Fix validation of sliced geometry arrays (#1391),Kyle Barron,GitHub,https://github.com/geoarrow/geoarrow-rs/commit/2e7dea71d105d368a9d94c1d5f5b8b1f65ba195f,194,geoarrow,geoarrow-rs +44d9a1ea84c06eefbfd43b0f1117e53ad7ff9b33,Add EGU session (#116),Max Jones,GitHub,https://github.com/pangeo-data/pangeo.io/commit/44d9a1ea84c06eefbfd43b0f1117e53ad7ff9b33,23,pangeo-data,pangeo.io +d70f965d93555795c6ac16af8d7426cbf77d449d,Fix link (#115),Max Jones,GitHub,https://github.com/pangeo-data/pangeo.io/commit/d70f965d93555795c6ac16af8d7426cbf77d449d,2,pangeo-data,pangeo.io +de06ca0b584a687497abde6910c0aaa6360dc3f6,Add link to lightning talk (#114),Max Jones,GitHub,https://github.com/pangeo-data/pangeo.io/commit/de06ca0b584a687497abde6910c0aaa6360dc3f6,2,pangeo-data,pangeo.io +3f94b03b8a1e384c46dbe9b414310c1ce467817a,Remove unclaimed talk (#113),Max Jones,GitHub,https://github.com/pangeo-data/pangeo.io/commit/3f94b03b8a1e384c46dbe9b414310c1ce467817a,9,pangeo-data,pangeo.io +64dc75029b0395da6e59737290563ecc4d8b2013,Disable banner (#112),Max Jones,GitHub,https://github.com/pangeo-data/pangeo.io/commit/64dc75029b0395da6e59737290563ecc4d8b2013,2,pangeo-data,pangeo.io +38d28d1494212125b547ddc6a6b7f0478b004cbb,Add links to discourse for showcases (#111),Max Jones,GitHub,https://github.com/pangeo-data/pangeo.io/commit/38d28d1494212125b547ddc6a6b7f0478b004cbb,8,pangeo-data,pangeo.io +e73d9817cafb8cdec8592f346423f6f0dec6a507,Support using HTTPStore (#56),Max Jones,GitHub,https://github.com/virtual-zarr/virtual-tiff/commit/e73d9817cafb8cdec8592f346423f6f0dec6a507,22,virtual-zarr,virtual-tiff +fe517b5fbad0946db05ed75404b7e185fc6e90b2,Make layout of IFDs as Zarr groups configurable (#54),Max Jones,GitHub,https://github.com/virtual-zarr/virtual-tiff/commit/fe517b5fbad0946db05ed75404b7e185fc6e90b2,195,virtual-zarr,virtual-tiff +9143d3b346e762099ec3ee1731bbd071fb305223,Add PyPI install instructions and example to readme (#50),Max Jones,GitHub,https://github.com/virtual-zarr/virtual-tiff/commit/9143d3b346e762099ec3ee1731bbd071fb305223,189,virtual-zarr,virtual-tiff +4a2123e2d5e3b221ee3694ea4ca9dfb99da8b151,Add py.typed (#49),Max Jones,GitHub,https://github.com/virtual-zarr/virtual-tiff/commit/4a2123e2d5e3b221ee3694ea4ca9dfb99da8b151,10,virtual-zarr,virtual-tiff +0113bc491198ff99e0caa6e402878a9d3058d1fb,Separate coordinate transformation information into 'spatial' convention (#16),Max Jones,GitHub,https://github.com/zarr-conventions/geo-proj/commit/0113bc491198ff99e0caa6e402878a9d3058d1fb,379,zarr-conventions,geo-proj +bb75d72b558aa39f540a96d5e9c5a9265e5d5031,Update according to zarr conventions spec changes (#15),Max Jones,GitHub,https://github.com/zarr-conventions/geo-proj/commit/bb75d72b558aa39f540a96d5e9c5a9265e5d5031,475,zarr-conventions,geo-proj +29fdac68ccfbadf5e2a01e7fdb26505459cff224,Update schema.json (#12),Max Jones,GitHub,https://github.com/zarr-conventions/geo-proj/commit/29fdac68ccfbadf5e2a01e7fdb26505459cff224,7,zarr-conventions,geo-proj +1d23cf321f12961ff97421ba399fc7fd6865fc65,Use the spatial convention (#25),Max Jones,GitHub,https://github.com/zarr-conventions/multiscales/commit/1d23cf321f12961ff97421ba399fc7fd6865fc65,114,zarr-conventions,multiscales +1c20751adea63016ccd1fa07c65f8dc608091d55,Sync with updates to zarr conventions spec (#20),Max Jones,GitHub,https://github.com/zarr-conventions/multiscales/commit/1c20751adea63016ccd1fa07c65f8dc608091d55,377,zarr-conventions,multiscales +574a3f4d75d9d102bf6dbadac370a8ac11cd19b8,fix: dynamically load correct array types given inferred schema (#26),Kyle Barron,GitHub,https://github.com/developmentseed/zarr-datafusion-search/commit/574a3f4d75d9d102bf6dbadac370a8ac11cd19b8,175,developmentseed,zarr-datafusion-search +c04b2712553a4a28b9aedbc0fbf4191f72b128e8,Support for icechunk (#24),Kyle Barron,GitHub,https://github.com/developmentseed/zarr-datafusion-search/commit/c04b2712553a4a28b9aedbc0fbf4191f72b128e8,8844,developmentseed,zarr-datafusion-search +8990087e25f03f77586952490315ca230e83467c,feat: Add `ZarrTable.from_obstore` to python API to support object-store based sources (#22),Kyle Barron,GitHub,https://github.com/developmentseed/zarr-datafusion-search/commit/8990087e25f03f77586952490315ca230e83467c,843,developmentseed,zarr-datafusion-search +dd5b9439731e377d8d95898f69b6d211cdb5dbfa,feat: Use schema inference code path in Zarr table provider (#21),Kyle Barron,GitHub,https://github.com/developmentseed/zarr-datafusion-search/commit/dd5b9439731e377d8d95898f69b6d211cdb5dbfa,496,developmentseed,zarr-datafusion-search +972cd6f69e869f2bb2331225e419a1304ceae5f2,"feat: Set GeoArrow WKT extension type and WGS84 on Zarr arrays named ""`bbox`"" (#20)",Kyle Barron,GitHub,https://github.com/developmentseed/zarr-datafusion-search/commit/972cd6f69e869f2bb2331225e419a1304ceae5f2,118,developmentseed,zarr-datafusion-search +b1907b135175785659b40c942b640c7c776cfb77,feat: infer schema from Zarrs Group (#19),Kyle Barron,GitHub,https://github.com/developmentseed/zarr-datafusion-search/commit/b1907b135175785659b40c942b640c7c776cfb77,825,developmentseed,zarr-datafusion-search +e992e13e34800736e5319accadd8939fcc621903,fix: Fix failing python test (#18),Kyle Barron,GitHub,https://github.com/developmentseed/zarr-datafusion-search/commit/e992e13e34800736e5319accadd8939fcc621903,10,developmentseed,zarr-datafusion-search +69d6f61b5d0d797e1b2ad261da362d1241f71263,chore: Rename `zarr-datafusion-internal` to `zarr-datafusion-search` (#17),Kyle Barron,GitHub,https://github.com/developmentseed/zarr-datafusion-search/commit/69d6f61b5d0d797e1b2ad261da362d1241f71263,52,developmentseed,zarr-datafusion-search +5c91159dde2acbdd34eeeb92b653dc8b894d5768,ci: Add Python CI (#8),Kyle Barron,GitHub,https://github.com/developmentseed/zarr-datafusion-search/commit/5c91159dde2acbdd34eeeb92b653dc8b894d5768,411,developmentseed,zarr-datafusion-search +70508eab9068306ad335da62aef2e0ddf6de302b,chore: Bump dependencies (#7),Kyle Barron,GitHub,https://github.com/developmentseed/zarr-datafusion-search/commit/70508eab9068306ad335da62aef2e0ddf6de302b,237,developmentseed,zarr-datafusion-search +3f1fd795b0580cc0f02c3fd941d362abe3068d80,Add link to maintenance team in the contributing guide (#104),Max Jones,GitHub,https://github.com/zarr-developers/geozarr-spec/commit/3f1fd795b0580cc0f02c3fd941d362abe3068d80,2,zarr-developers,geozarr-spec +b920d336b6ac68a7aef760f3f7caf0020b836dd9,Add docs site (#7),Max Jones,GitHub,https://github.com/virtual-zarr/obspec-utils/commit/b920d336b6ac68a7aef760f3f7caf0020b836dd9,330,virtual-zarr,obspec-utils +7380ee5d3ab318178b53e2efa441eccf5ac3c8b7,Support py3.13/py3.14 (#6),Max Jones,GitHub,https://github.com/virtual-zarr/obspec-utils/commit/7380ee5d3ab318178b53e2efa441eccf5ac3c8b7,4,virtual-zarr,obspec-utils +c73974011963df04d388f1f4a94d3a24773e619c,feature(typing): add py.typed file to package root (#5),Max Jones,GitHub,https://github.com/virtual-zarr/obspec-utils/commit/c73974011963df04d388f1f4a94d3a24773e619c,0,virtual-zarr,obspec-utils +ced0b63ed34e4fdf6bd74a39f382685b6d0d7ee3,Add ObjectStoreRegistry (#4),Max Jones,GitHub,https://github.com/virtual-zarr/obspec-utils/commit/ced0b63ed34e4fdf6bd74a39f382685b6d0d7ee3,325,virtual-zarr,obspec-utils +fc8438ebf4c670a090c34315216ce1789bafe3c6,Expose buffer_size kwarg in open_reader (#3),Max Jones,GitHub,https://github.com/virtual-zarr/obspec-utils/commit/fc8438ebf4c670a090c34315216ce1789bafe3c6,8,virtual-zarr,obspec-utils +e3b20e1608920bb16d50ec0e49fac6ff9a1c9af1,Add ObstoreMemCacheReader (#2),Max Jones,GitHub,https://github.com/virtual-zarr/obspec-utils/commit/e3b20e1608920bb16d50ec0e49fac6ff9a1c9af1,87,virtual-zarr,obspec-utils +54bc68540742c624adbf295731441f4d11d350a7,Define reader usable by Xarray (#1),Max Jones,GitHub,https://github.com/virtual-zarr/obspec-utils/commit/54bc68540742c624adbf295731441f4d11d350a7,214,virtual-zarr,obspec-utils +3d09b61876e5aa88f6d667cd9914677a1e971f61,Merge pull request #98 from developmentseed/feat/titiler-cmr-compatibility-report,Aimee Barciauskas,GitHub,https://github.com/developmentseed/titiler-cmr/commit/3d09b61876e5aa88f6d667cd9914677a1e971f61,4272,developmentseed,titiler-cmr +8fb1d81a383d1ae0778855336b1f21de6b5a8506,Merge pull request #101 from developmentseed/fix/bidx-indexes-error-message,Aimee Barciauskas,GitHub,https://github.com/developmentseed/titiler-cmr/commit/8fb1d81a383d1ae0778855336b1f21de6b5a8506,8,developmentseed,titiler-cmr +81225a4e46f086d80dad3a0eabf8c559df9d080a,Merge pull request #93 from developmentseed/ab/dry-backend-functions,Aimee Barciauskas,GitHub,https://github.com/developmentseed/titiler-cmr/commit/81225a4e46f086d80dad3a0eabf8c559df9d080a,135,developmentseed,titiler-cmr +ef7c2171eff3128b253e31cd4507c76133d07bcf,Merge pull request #89 from developmentseed/ab/fix-bands-error,Aimee Barciauskas,GitHub,https://github.com/developmentseed/titiler-cmr/commit/ef7c2171eff3128b253e31cd4507c76133d07bcf,122,developmentseed,titiler-cmr +dd8f2d0f9068846c941cc331132db3ddd74df372,ci: Bump macos runner version in wheel build (#160),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/dd8f2d0f9068846c941cc331132db3ddd74df372,4,developmentseed,async-tiff +dc345abe5a92a97f7e63877d5e7e0f8fc1163309,chore: Prepare python 0.3 release (#159),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/dc345abe5a92a97f7e63877d5e7e0f8fc1163309,308,developmentseed,async-tiff +5086669ff02f0168b54cedb27f7c0bc9fb8111ea,refactor: Use `pyclass(get_all)` for cleaner code (#158),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/5086669ff02f0168b54cedb27f7c0bc9fb8111ea,97,developmentseed,async-tiff +5e6858c76609e4e1b17a66892079884aa08afd36,chore: move tiff tag structs to top level (#150),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/5e6858c76609e4e1b17a66892079884aa08afd36,1345,developmentseed,async-tiff +28d729a6e08d06a2829a5546f51c26aa4461bf37,feat: Include Endianness as property of TIFF struct (#149),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/28d729a6e08d06a2829a5546f51c26aa4461bf37,81,developmentseed,async-tiff +d803d0f6de8ce01a2320cc3868fae41ba9f3ab39,feat(python): implement Mapping protocol for IFD and GeoKeyDirectory (#148),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/d803d0f6de8ce01a2320cc3868fae41ba9f3ab39,452,developmentseed,async-tiff +b53ffbda027dd0427957b4eb3c1d386cef375482,feat: Use `async-trait` instead of boxed futures for simpler trait interface (#147),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/b53ffbda027dd0427957b4eb3c1d386cef375482,216,developmentseed,async-tiff +fa4b52c13ad52826c1e514b671aec059f0ba25e4,feat: Exponential read-ahead cache (#140),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/fa4b52c13ad52826c1e514b671aec059f0ba25e4,499,developmentseed,async-tiff +32131e6237c9ca1331e521923376b08c58a18087,fix: Skip unknown GeoTag keys (#134),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/32131e6237c9ca1331e521923376b08c58a18087,128,developmentseed,async-tiff +fac684fcd3cb866b64e0f22588283a593433fe57,"ci: Deprecate Python 3.9, add testing on Python 3.13 (#129)",Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/fac684fcd3cb866b64e0f22588283a593433fe57,1803,developmentseed,async-tiff +1ad420c13451439dfbda5215a9810e6d4918bad7,ci: Fix tag name to initiate rust release (#130),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/1ad420c13451439dfbda5215a9810e6d4918bad7,4,developmentseed,async-tiff +3c77377af7909f4740832f503c7e7b8eb576b843,ci: Set up Rust trusted publishing (#128),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/3c77377af7909f4740832f503c7e7b8eb576b843,19,developmentseed,async-tiff +270e370ea8ee7ba99415468fc4c6d8b64d37425c,ci: Fix wheel build by switching to pypy 3.11 (#127),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/270e370ea8ee7ba99415468fc4c6d8b64d37425c,12,developmentseed,async-tiff +374b72748650675c4f370e00402b2fd8b4216866,chore: Bump python binding version to 0.2.0 (#124),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/374b72748650675c4f370e00402b2fd8b4216866,25,developmentseed,async-tiff +9da0d7769bb435f0aa735401786317b5e53433ce,chore: Bump pyo3 to 0.27 (#126),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/9da0d7769bb435f0aa735401786317b5e53433ce,72,developmentseed,async-tiff +6397a9aca840bf883ca2ecaf60a08251d25b7dee,chore: Bump _obstore submodule (#125),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/6397a9aca840bf883ca2ecaf60a08251d25b7dee,3,developmentseed,async-tiff +512d86b28128fae0bca5997cea4dff2519862d1e,ci: Build abi3 wheels where possible (#123),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/512d86b28128fae0bca5997cea4dff2519862d1e,63,developmentseed,async-tiff +b9d802e2d93ea4e20d1953b573478483f5bac041,chore: Bump pyo3 to 0.26 (#121),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/b9d802e2d93ea4e20d1953b573478483f5bac041,984,developmentseed,async-tiff +e9b23766976306d2767ce72908e046075f6234ad,chore!: Bump minimum Python version to 3.10 (#122),Kyle Barron,GitHub,https://github.com/developmentseed/async-tiff/commit/e9b23766976306d2767ce72908e046075f6234ad,2,developmentseed,async-tiff +d5d404c8e967449a7b78df23596350a8a74146c3,Add property for denoting raster space/grid registration (#4),Max Jones,GitHub,https://github.com/zarr-conventions/spatial/commit/d5d404c8e967449a7b78df23596350a8a74146c3,146,zarr-conventions,spatial +32110cb24231b47c37bc30d98e1a84604e02d229,Add linters/formatters (#3),Max Jones,GitHub,https://github.com/zarr-conventions/spatial/commit/32110cb24231b47c37bc30d98e1a84604e02d229,179,zarr-conventions,spatial +c5e1d29a40f10a580e46a0cecd846dbfe34d1fc1,Fix bold formatting (#2),Max Jones,GitHub,https://github.com/zarr-conventions/spatial/commit/c5e1d29a40f10a580e46a0cecd846dbfe34d1fc1,2,zarr-conventions,spatial +5ca001b2903ecdde01e8058730e2f3406c8b18d9,Initial design for spatial convention (#1),Max Jones,GitHub,https://github.com/zarr-conventions/spatial/commit/5ca001b2903ecdde01e8058730e2f3406c8b18d9,806,zarr-conventions,spatial +e35be479a561d6c37b3735fa067f1f2d59357948,Initial commit,Max Jones,GitHub,https://github.com/zarr-conventions/spatial/commit/e35be479a561d6c37b3735fa067f1f2d59357948,639,zarr-conventions,spatial +acdd892234dbb62833c5d57e59b94d180cd1115d,Add instructions to contributing guide about executable docs (#3645),Max Jones,GitHub,https://github.com/zarr-developers/zarr-python/commit/acdd892234dbb62833c5d57e59b94d180cd1115d,34,zarr-developers,zarr-python +87d2041b2cd52639d7d64d4711b700e921403a63,Add experimental section to the API docs (#3642),Max Jones,GitHub,https://github.com/zarr-developers/zarr-python/commit/87d2041b2cd52639d7d64d4711b700e921403a63,27,zarr-developers,zarr-python +8eb244f3957f59210d2fb73a26cf9aeac12a7fa8,Reduce number of documentation redirects needed (#3584),Max Jones,GitHub,https://github.com/zarr-developers/zarr-python/commit/8eb244f3957f59210d2fb73a26cf9aeac12a7fa8,218,zarr-developers,zarr-python +c8d8e6430b67264eb4457dc337746856a46965cc,Improve compatibility between old sphinx and new mkdocs setup (#3544),Max Jones,GitHub,https://github.com/zarr-developers/zarr-python/commit/c8d8e6430b67264eb4457dc337746856a46965cc,109,zarr-developers,zarr-python +fc8e8ad1a143b1f39a2e08b470857a5e48a38bca,Fix 404 page (#3543),Max Jones,GitHub,https://github.com/zarr-developers/zarr-python/commit/fc8e8ad1a143b1f39a2e08b470857a5e48a38bca,2,zarr-developers,zarr-python +c67dcc1b89f69d4ae59c92276a4ead5963c944ac,Remove wrapper for kerchunk's tiff parsing (#849),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/c67dcc1b89f69d4ae59c92276a4ead5963c944ac,68,zarr-developers,virtualizarr +aa66445137b186028266f7cc14e6d6ec6cbadd98,Fix typing errors (#847),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/aa66445137b186028266f7cc14e6d6ec6cbadd98,15,zarr-developers,virtualizarr +4529e99851a9e8e5b3af46dd47884272774d8135,Fix broken cross-references (#846),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/4529e99851a9e8e5b3af46dd47884272774d8135,8,zarr-developers,virtualizarr +0a18a00a4e7947c3a3e1a8354baa85b25bde17fe,Polish release notes for v2.2.1 (#839),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/0a18a00a4e7947c3a3e1a8354baa85b25bde17fe,10,zarr-developers,virtualizarr +38ef649c531d8097695fe3ed42f8f062c24684f7,Improve ManifestStore.list_dir for arrays and nested groups (#837),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/38ef649c531d8097695fe3ed42f8f062c24684f7,87,zarr-developers,virtualizarr +c6de7b30dfc8c16c902e72059abbd00289cfb8ba,Allow storing scalar arrays under 'c' key (#836),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/c6de7b30dfc8c16c902e72059abbd00289cfb8ba,192,zarr-developers,virtualizarr +847dbdef590fd5ea46e55962e54ecdbc0f5446a7,Polish release notes for v2.2.0 (#832),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/847dbdef590fd5ea46e55962e54ecdbc0f5446a7,10,zarr-developers,virtualizarr +a8491761e8c3d95680fe9c476b0af26e86914075,Bump workflow actions versions (#830),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/a8491761e8c3d95680fe9c476b0af26e86914075,44,zarr-developers,virtualizarr +2a13ee379990816702c96cb74e8b0ef2e23c4daa,Raise informative error on Zarr V2 parsing with Zarr-Python<3.1.3 (#829),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/2a13ee379990816702c96cb74e8b0ef2e23c4daa,82,zarr-developers,virtualizarr +61ca10b81f27a8685af974d3baf5e6b0bb06ed2a,"Add back supports_partial_writes property, returning False (#828)",Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/61ca10b81f27a8685af974d3baf5e6b0bb06ed2a,5,zarr-developers,virtualizarr +565d40fa1553a20bcac18f1ddcade04ec247ed18,Return None for Zarr V2/consolidated metadata requests (#827),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/565d40fa1553a20bcac18f1ddcade04ec247ed18,22,zarr-developers,virtualizarr +cb2912e650ddea8ed700fe1d45375e407e2ba99c,Add virtual tiff as an optional dependency (#810),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/cb2912e650ddea8ed700fe1d45375e407e2ba99c,49,zarr-developers,virtualizarr +57c346a86e26fc20284aec6fde2e11047706ce0d,Fix typing in ManifestStore (#820),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/57c346a86e26fc20284aec6fde2e11047706ce0d,25,zarr-developers,virtualizarr +b01cd0aae4fde995235f1d324992c77087664b33,Add link to ESIP talk (#819),Max Jones,GitHub,https://github.com/zarr-developers/VirtualiZarr/commit/b01cd0aae4fde995235f1d324992c77087664b33,1,zarr-developers,virtualizarr diff --git a/reports/plot.py b/reports/plot.py index 6a00a5a..ff9448c 100644 --- a/reports/plot.py +++ b/reports/plot.py @@ -1,63 +1,201 @@ +import re +from pathlib import Path + import pandas as pd import matplotlib.pyplot as plt -from datetime import datetime +from matplotlib.patches import Patch +from matplotlib.ticker import MaxNLocator -from config import TIME_RANGE +from config import get_current_pi, OBJECTIVES -def main(): - time_start = datetime.strptime(TIME_RANGE[0], "%Y%m%d") - time_end = datetime.strptime(TIME_RANGE[1], "%Y%m%d") - csv_filename = ( - f"output/{time_start.strftime('%Y-%m-%d')}-{time_end.strftime('%Y-%m-%d')}.csv" - ) +# Color palette for objectives (cycles if more than 10 objectives) +COLORS = [ + "#e74c3c", # red + "#3498db", # blue + "#2ecc71", # green + "#9b59b6", # purple + "#f39c12", # orange + "#1abc9c", # teal + "#e91e63", # pink + "#00bcd4", # cyan + "#ff5722", # deep orange + "#607d8b", # blue grey +] + + +def get_repo_objectives(pi: str) -> dict: + """ + Build a mapping from repo to list of objectives it belongs to. + + Returns: + Dict mapping "org/repo" to list of (issue_number, title) tuples + """ + repo_to_objectives = {} + for obj in OBJECTIVES.get(pi, []): + for org, repo in obj["repos"]: + key = f"{org}/{repo}" + if key not in repo_to_objectives: + repo_to_objectives[key] = [] + repo_to_objectives[key].append((obj["issue_number"], obj["title"])) + return repo_to_objectives + + +def get_objective_colors(pi: str) -> dict: + """Generate color mapping for objectives in a PI.""" + objectives = OBJECTIVES.get(pi, []) + return { + obj["issue_number"]: COLORS[i % len(COLORS)] for i, obj in enumerate(objectives) + } + + +def get_objective_titles(pi: str) -> dict: + """Get short titles for objectives (strip PI prefix and emojis).""" + objectives = OBJECTIVES.get(pi, []) + titles = {} + length = 100 + for obj in objectives: + title = obj["title"] + # Strip "ODD PI X.Y Objective N: " prefix if present + if ": " in title: + title = title.split(": ", 1)[1] + # Strip emojis (unicode emoji ranges) + title = re.sub(r"[\U0001F300-\U0001F9FF]", "", title).strip() + # Truncate if too long + if len(title) > length: + title = title[: length - 3] + "..." + titles[obj["issue_number"]] = title + return titles + + +def main(pi: str = None, show_labels: bool = False): + # Default to current PI if not specified + if pi is None: + pi = get_current_pi() + + csv_filename = f"output/{pi}.csv" df = pd.read_csv(csv_filename) + # Build repo to objectives mapping and colors + repo_to_objectives = get_repo_objectives(pi) + objective_colors = get_objective_colors(pi) + + # Get commits per repo with full path + df["full_repo"] = df["organization"] + "/" + df["repository"] commits_per_repo = df["repository"].value_counts() + full_repo_map = df.groupby("repository")["full_repo"].first().to_dict() fig, ax = plt.subplots(1, 1, figsize=(16, 10)) - ax.barh( - commits_per_repo.index, - commits_per_repo.values, - color=["#9b59b6", "#f39c12", "#1abc9c"], - alpha=0.8, - edgecolor="black", - linewidth=1.2, - ) - ax.set_xlabel("Number of Commits", fontsize=16) - ax.tick_params(axis="y", labelsize=15) + + # Plot bars with objective-based coloring + for i, (repo, count) in enumerate(commits_per_repo.items()): + full_repo = full_repo_map.get(repo, repo) + objectives = repo_to_objectives.get(full_repo, []) + + if len(objectives) == 0: + # No objective mapping - gray + ax.barh( + i, count, color="#95a5a6", alpha=0.8, edgecolor="black", linewidth=1.2 + ) + elif len(objectives) == 1: + # Single objective - solid color + color = objective_colors.get(objectives[0][0], "#95a5a6") + ax.barh(i, count, color=color, alpha=0.8, edgecolor="black", linewidth=1.2) + else: + # Multiple objectives - split bar by color + width_per_obj = count / len(objectives) + current_x = 0 + for j, (issue_num, _) in enumerate(objectives): + color = objective_colors.get(issue_num, "#95a5a6") + ax.barh( + i, + width_per_obj, + left=current_x, + color=color, + alpha=0.8, + edgecolor="black", + linewidth=1.2, + ) + current_x += width_per_obj + + ax.set_yticks(range(len(commits_per_repo))) + ax.set_yticklabels(commits_per_repo.index) + ax.set_xlabel("Number of Commits", fontsize=16, loc="left") + ax.tick_params(axis="y", labelsize=13) + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) ax.grid(axis="x", alpha=0.3) - for i, v in enumerate(commits_per_repo.values): - ax.text( - v + 0.5, i, str(v), ha="left", va="center", fontweight="bold", fontsize=11 - ) + # Add value labels if requested + if show_labels: + for i, v in enumerate(commits_per_repo.values): + ax.text( + v + 0.5, + i, + str(v), + ha="left", + va="center", + fontweight="bold", + fontsize=11, + ) + plt.subplots_adjust(left=0.3) - # Bold title with date range - date_range = f"{time_start.strftime('%Y-%m-%d')} to {time_end.strftime('%Y-%m-%d')}" - fig.text( - 0.5, - 0.93, - f"Commits per Repository ({date_range})", + ax.set_title( + f"{pi.upper()} ODD's commits to the default branch", fontsize=24, fontweight="bold", - horizontalalignment="center", - transform=fig.transFigure, ) - # Regular subtitle - fig.text( - 0.5, - 0.90, - "Merged pull requests are counted as one commit", - fontsize=16, - style="normal", - horizontalalignment="center", - transform=fig.transFigure, + # Legend for objectives with titles + objective_titles = get_objective_titles(pi) + legend_elements = [ + Patch( + facecolor=color, + edgecolor="black", + label=objective_titles.get(num, f"#{num}"), + ) + for num, color in objective_colors.items() + ] + ax.legend( + handles=legend_elements, + loc="upper right", + fontsize=9, + title=f"{pi.upper()} Objectives", + title_fontsize=10, + ) + + # Caveats and link in bottom right of plot area + caveats = ( + "Caveats:\n" + "- Only community-governed open source repositories are tracked (not ODSI-specific repos)\n" + "- Merged PRs counted as one commit\n" + "- Individual changes may span multiple PRs\n" + "- Split bars indicate repos in multiple objectives\n" + "- Includes all open source work by VEDA/EODC ODD team members, not only ODSI-funded work\n\n" + "Objective details: nasa-impact.github.io/veda-odd/objectives" + ) + ax.text( + 1.0, + -0.06, + caveats, + fontsize=8, + style="italic", + horizontalalignment="right", + verticalalignment="top", + transform=ax.transAxes, + bbox=dict( + boxstyle="round,pad=0.3", facecolor="white", edgecolor="gray", alpha=0.8 + ), ) - plt.savefig(f"output/{TIME_RANGE[0]}-{TIME_RANGE[1]}_commits_per_repo_hist.png") + # Save to docs for website + docs_images = Path(__file__).parent.parent / "docs" / "images" + docs_images.mkdir(exist_ok=True) + plt.savefig( + docs_images / f"{pi}.png", + bbox_inches="tight", + dpi=150, + ) if __name__ == "__main__":