diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..4abf435 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,83 @@ +# ------------------------------------------------------------------------------ +# BIOMASS BPS Code Owners +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) - ACRI-ST +# SPDX-License-Identifier: Apache-2.0 +# +# Rules: +# - Last matching line wins. +# - Only users with WRITE access can be code owners. +# - Personal repos accept @usernames only (no @org/team syntax). +# - Replace @handles below with real GitHub usernames before enforcing. +# +# ESA repository gate (documented in docs/governance/repository-stewards.md): +# - Klaus Scipal ESA final authority (disputes, release gate) +# - Clement Albinet ESA repository representative +# ------------------------------------------------------------------------------ + +# ---- Catch-all: Core Maintainer reviews anything not matched below ----------- +* @matteoaletti + +# ---- CI / CD / repo governance ----------------------------------------------- +/.github/ @yreyricord +/.pre-commit-config.yaml @matteoaletti +/REUSE.toml @yreyricord +/LICENSES/ @yreyricord +/pyproject.toml @matteoaletti +/pytest.ini @matteoaletti +/ruff.toml @matteoaletti +/noxfile.py @matteoaletti +/.gitignore @matteoaletti +/.secrets.baseline @matteoaletti + +# ---- Shared libraries (technical: R. Piantanida) ---------------------------- +/bps-common/ @matteoaletti +/bps-task-tables/ @matteoaletti +/bps-transcoder/ @matteoaletti + +# ---- L1 processors (scientific: S. Tebaldini) ------------------------------- +/bps-l1_binaries/ @matteoaletti +/bps-l1_core_processor/ @matteoaletti +/bps-l1_framing_processor/ @matteoaletti +/bps-l1_pre_processor/ @matteoaletti +/bps-l1_processor/ @matteoaletti + +# ---- L2 processors ----------------------------------------------------------- +/bps-l2a_processor/ @matteoaletti +/bps-l2b_agb_processor/ @matteoaletti +/bps-l2b_fd_processor/ @matteoaletti +/bps-l2b_fh_processor/ @matteoaletti + +# ---- Stack processors -------------------------------------------------------- +/bps-stack_binaries/ @matteoaletti +/bps-stack_cal_processor/ @matteoaletti +/bps-stack_coreg_processor/ @matteoaletti +/bps-stack_pre_processor/ @matteoaletti +/bps-stack_processor/ @matteoaletti + +# ---- Container / packaging --------------------------------------------------- +/bps-dockerfiles/ @matteoaletti + +# ---- Tests ------------------------------------------------------------------- +/test/ @matteoaletti +**/tests/unit/ @matteoaletti +**/tests/integration/ @matteoaletti +**/tests/fixtures/ @matteoaletti +**/tests/baseline/ @matteoaletti +**/tests/smoke/ @matteoaletti +/tests/extended/ @matteoaletti +/tests/heavy/ @matteoaletti + +# ---- Documentation ----------------------------------------------------------- +/README.md @matteoaletti +/CREDITS.md @matteoaletti +/LICENSE.md @yreyricord +/docs/ @yreyricord + +# ---- Scripts & schemas ------------------------------------------------------- +/scripts/ @matteoaletti +/xsd/ @matteoaletti + +# ---- Release gate (ESA approval mandatory) ---------------------------------- +# Replace @handles with Klaus Scipal (final authority) and Clement Albinet GitHub usernames. +/VERSION @matteoaletti +/CHANGELOG.md @matteoaletti diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yml b/.github/ISSUE_TEMPLATE/01_bug_report.yml new file mode 100644 index 0000000..0df5fe5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01_bug_report.yml @@ -0,0 +1,137 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) - ACRI-ST +# SPDX-License-Identifier: Apache-2.0 + +name: 01 · Bug report +description: Report a defect in a processor, the CI/CD pipeline, or the documentation. +title: "[bug] " +labels: ["type:bug", "needs-triage"] +body: + - type: markdown + attributes: + value: | + Please complete this form to report a defect in the BIOMASS BPS software, + the CI/CD pipeline, or the documentation. The more reproducible the report, + the faster the issue can be diagnosed and resolved. + + - type: dropdown + id: component + attributes: + label: Affected component + description: Select the part of BIOMASS BPS impacted by this defect. + options: + - bps-common + - bps-dockerfiles + - bps-l1_binaries + - bps-l1_core_processor + - bps-l1_framing_processor + - bps-l1_pre_processor + - bps-l1_processor + - bps-l2a_processor + - bps-l2b_agb_processor + - bps-l2b_fd_processor + - bps-l2b_fh_processor + - bps-stack_binaries + - bps-stack_cal_processor + - bps-stack_coreg_processor + - bps-stack_pre_processor + - bps-stack_processor + - bps-task-tables + - bps-transcoder + - CI / GitHub Actions + - Documentation + - Other (please specify in the description) + validations: + required: true + + - type: dropdown + id: severity + attributes: + label: Severity + description: Operational impact of the defect. + options: + - Critical - blocks the processing chain + - Major - incorrect scientific output + - Minor - degraded behaviour with workaround + - Cosmetic - documentation or display only + validations: + required: true + + - type: input + id: version + attributes: + label: Version or commit reference + description: Output of `git describe --tags --always`, or the release tag, or the commit SHA. + placeholder: "v4.4.4 or a1b2c3d" + validations: + required: true + + - type: dropdown + id: environment + attributes: + label: Environment + options: + - Local developer environment + - GitHub Actions (CI) + - MAAP + - Operational deployment + - Jupyter Notebook + - Other + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: Observed behaviour + description: A clear and concise description of the defect. + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behaviour + description: What was expected to happen instead. + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Steps to reproduce + description: Minimal, deterministic sequence of commands or actions. + value: | + 1. + 2. + 3. + render: markdown + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Logs and traceback + description: Relevant log output or Python traceback. Will be rendered as a code block. + render: shell + + - type: textarea + id: additional_context + attributes: + label: Additional context + description: | + Optional. Suspected root cause, relevant file paths, links to related issues + or pull requests, screenshots, references, or any other context that may + help triage. + + - type: checkboxes + id: checks + attributes: + label: Pre-submission checklist + options: + - label: I have searched existing issues and confirmed this is not a duplicate. + required: true + - label: I am running a supported version (a tagged release or the current `develop` branch). + required: true + - label: I have included enough information for the defect to be reproduced. + required: true diff --git a/.github/ISSUE_TEMPLATE/02_feature_request.yml b/.github/ISSUE_TEMPLATE/02_feature_request.yml new file mode 100644 index 0000000..378e4b7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02_feature_request.yml @@ -0,0 +1,112 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) - ACRI-ST +# SPDX-License-Identifier: Apache-2.0 + +name: 02 · Feature or enhancement request +description: Propose a non-scientific feature, an enhancement, or a tooling improvement. +title: "[feature] " +labels: ["type:feature", "needs-triage"] +body: + - type: markdown + attributes: + value: | + Use this template to propose a software feature, an enhancement to an existing + component, or a tooling improvement. + + For the proposal of a new scientific algorithm or a methodological change, + please use the **Algorithm proposal** template instead. + + - type: dropdown + id: scope + attributes: + label: Scope of the proposal + options: + - New software feature + - Enhancement to an existing component + - Performance or scalability improvement + - CI/CD or tooling improvement + - Governance or process improvement + - Other + validations: + required: true + + - type: dropdown + id: component + attributes: + label: Most impacted component + options: + - bps-common + - bps-dockerfiles + - bps-l1_binaries + - bps-l1_core_processor + - bps-l1_framing_processor + - bps-l1_pre_processor + - bps-l1_processor + - bps-l2a_processor + - bps-l2b_agb_processor + - bps-l2b_fd_processor + - bps-l2b_fh_processor + - bps-stack_binaries + - bps-stack_cal_processor + - bps-stack_coreg_processor + - bps-stack_pre_processor + - bps-stack_processor + - bps-task-tables + - bps-transcoder + - CI / GitHub Actions + - Documentation + - Not specific to one component + validations: + required: true + + - type: textarea + id: problem + attributes: + label: Problem statement and motivation + description: What problem does this address? Who is affected and why does it matter? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: Describe the proposed change in concrete terms. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Other approaches that were considered and reasons for rejection. + + - type: textarea + id: impact + attributes: + label: Expected impact + description: | + Discuss backward compatibility, interfaces, performance, dependencies, + documentation and validation impact. + validations: + required: true + + - type: textarea + id: additional_context + attributes: + label: Additional context + description: | + Optional. Relevant file paths, links to related issues or pull requests, + screenshots, prior art in other projects, references, or any other context + that may help triage. + + - type: checkboxes + id: checks + attributes: + label: Pre-submission checklist + options: + - label: I have searched existing issues and discussions for a similar proposal. + required: true + - label: I have read the contributing guidelines and the project governance. + required: true + - label: I am available to discuss the design before any implementation starts. + required: true diff --git a/.github/ISSUE_TEMPLATE/03_algorithm_proposal.yml b/.github/ISSUE_TEMPLATE/03_algorithm_proposal.yml new file mode 100644 index 0000000..3ff0e5b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03_algorithm_proposal.yml @@ -0,0 +1,172 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) - ACRI-ST +# SPDX-License-Identifier: Apache-2.0 + +name: 03 · Algorithm proposal +description: Propose a new scientific algorithm, methodological change, or processing chain modification. Scientific justification is required. +title: "[algorithm] " +labels: ["type:algorithm", "needs-sme"] +body: + - type: markdown + attributes: + value: | + Use this template to propose a new scientific algorithm, a change to an + existing retrieval method, or any modification with a scientific impact + on the BIOMASS processing chain. + + Proposals submitted through this template are routed to the relevant + Scientific Expert and to ESA. A peer-reviewed + reference or a written scientific justification is required. + + - type: dropdown + id: processor + attributes: + label: Target processor or processing stage + options: + - bps-l1_pre_processor + - bps-l1_framing_processor + - bps-l1_core_processor + - bps-l1_processor + - bps-l2a_processor + - bps-l2b_agb_processor (Above-Ground Biomass) + - bps-l2b_fh_processor (Forest Height) + - bps-l2b_fd_processor (Forest Disturbance) + - bps-stack_pre_processor + - bps-stack_cal_processor + - bps-stack_coreg_processor + - bps-stack_processor + - bps-transcoder + - Cross-cutting (multiple processors) + - General methodology + validations: + required: true + + - type: dropdown + id: proposal_type + attributes: + label: Type of proposal + options: + - New algorithm + - Modification of an existing algorithm + - Change in calibration or auxiliary data/LUTs (lookup tables) + - New LUT or Auxiliary / Files (lookup tables) + - Change in validation methodology or acceptance criteria + validations: + required: true + + - type: textarea + id: scientific_context + attributes: + label: Scientific context and motivation + description: Describe the scientific gap, limitation or improvement opportunity that motivates this proposal. + placeholder: | + Example: + - Scientific gap, limitation or improvement opportunity, attached below. + - Scientific justification, attached below. + - Validation plan, attached below. + - Test datasets, attached below. + - Reference products, attached below. + - Expected accuracy metrics, attached below. + - Acceptance criteria, attached below. + validations: + required: true + + - type: textarea + id: proposed_algorithm + attributes: + label: Proposed algorithm or method + description: | + Provide a concise but complete description of the proposed algorithm, + including inputs, outputs, key equations, assumptions and limitations. + placeholder: | + Example: + - Algorithm description, attached below. + - Key equations, attached below. + - Assumptions, attached below. + - Limitations, attached below. + validations: + required: true + + - type: textarea + id: justification + attributes: + label: Scientific justification + description: | + Provide a peer-reviewed reference (DOI, arXiv ID or full citation) or + a written justification supported by experimental evidence. + placeholder: | + Example: + - K. Scipal et al., "BIOMASS: ESA’s P-Band SAR Mission," in Proceedings of the IEEE, doi: 10.1109/JPROC.2026.3687416 + - Internal technical note, attached below. + - External technical note, attached below. + - Experimental evidence, attached below. + validations: + required: true + + - type: textarea + id: validation_plan + attributes: + label: Validation plan + description: | + Describe how the proposed algorithm will be validated: test datasets, + reference products, expected accuracy metrics and acceptance criteria. + placeholder: | + Example: + - Validation plan, attached below. + - Test datasets, attached below. + - Reference products, attached below. + - Expected accuracy metrics, attached below. + - Acceptance criteria, attached below. + validations: + required: true + + - type: textarea + id: impact + attributes: + label: Expected impact on the processing chain + description: | + Backward compatibility, interface or product format changes, dependency + on upstream products, expected change in performance or accuracy. + placeholder: | + Example: + - Backward compatibility, interface or product format changes, dependency + on upstream products, expected change in performance or accuracy. + - Expected change in performance or accuracy. + - Expected change in accuracy metrics. + - Expected change in acceptance criteria. + - Expected change in validation methodology. + - Expected change in validation plan. + - Expected change in test datasets. + - Expected change in reference products. + - Expected change in expected accuracy metrics. + validations: + required: true + + - type: textarea + id: references + attributes: + label: Additional references and attachments + description: | + Drag and drop any technical note, ATBD draft, validation report or plot + that supports the proposal. Do not attach data covered by NDA or export + restrictions. + placeholder: | + Example: + - Validation plan, attached below. + - Test datasets, attached below. + - Reference products, attached below. + - Expected accuracy metrics, attached below. + - Acceptance criteria, attached below. + + - type: checkboxes + id: checks + attributes: + label: Pre-submission checklist + options: + - label: A peer-reviewed reference or a written scientific justification has been provided. + required: true + - label: I have consulted the relevant Science Guide and ATBD sections. + required: true + - label: I understand that this proposal will be reviewed by the SME and the ESA scientific board before any implementation. + required: true + - label: I am not attaching any data covered by an NDA or export restrictions. + required: true diff --git a/.github/ISSUE_TEMPLATE/04_documentation.yml b/.github/ISSUE_TEMPLATE/04_documentation.yml new file mode 100644 index 0000000..e25e997 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/04_documentation.yml @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 + +name: 04 · Documentation issue +description: Report an error, gap or improvement opportunity in the BIOMASS BPS documentation. +title: "[docs] " +labels: ["type:docs", "needs-triage"] +body: + - type: markdown + attributes: + value: | + Use this template to report an issue in the documentation: an error, + an omission, an outdated section, a broken link, or a request for clarification. + + - type: dropdown + id: section + attributes: + label: Documentation section + description: Select the section of the BIOMASS BPS documentation site impacted. + options: + - About + - Getting started + - User guide + - Tutorials + - Science guide + - Developer guide + - Contributing + - Governance + - Index / landing page + - README or top-level repository files + - Templates + - Other (please specify in the description) + validations: + required: true + + - type: dropdown + id: issue_type + attributes: + label: Type of documentation issue + options: + - Technical error or incorrect information + - Outdated content + - Missing content or coverage gap + - Unclear or ambiguous wording + - Broken link or rendering issue + - Typographical or formatting issue + - Translation or terminology issue + - New content proposal + - Other (please specify in the description) + validations: + required: true + + - type: input + id: page + attributes: + label: Page URL or file path + description: Link to the affected page on the documentation site, or path in the repository. + placeholder: "https://biopal.github.io/BPS/... or docs/source/user_guide/...md" + validations: + required: true + + - type: textarea + id: description + attributes: + label: Description of the issue + description: Describe what is wrong, unclear or missing. + placeholder: | + Example: + - What is wrong, unclear or missing, attached below. + - New content proposal, attached below. + validations: + required: true + + - type: textarea + id: suggestion + attributes: + label: Suggested change + description: If possible, propose the corrected wording or the missing content. + placeholder: | + Example: + - Corrected wording, attached below. + - Missing content, attached below. + - New content proposal, attached below. + - type: checkboxes + id: checks + attributes: + label: Pre-submission checklist + options: + - label: I have verified that the issue is present in the latest published version of the documentation. + required: true + - label: I have searched existing issues to confirm this is not a duplicate. + required: true diff --git a/.github/ISSUE_TEMPLATE/05_security_report.yml b/.github/ISSUE_TEMPLATE/05_security_report.yml new file mode 100644 index 0000000..1d36a28 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/05_security_report.yml @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 + +name: 05 · Security report +description: Report a non-sensitive security concern, hardening recommendation, or supply-chain issue. +title: "[security] " +labels: ["type:security", "needs-triage", "priority:high"] +body: + - type: markdown + attributes: + value: | + Use this template to report a security concern that is **safe to disclose publicly**: + a hardening recommendation, a known CVE in a dependency, a supply-chain concern, + or a configuration weakness. + + **Sensitive vulnerabilities that could be exploited must NOT be disclosed in + a public issue.** They must be reported through a private GitHub Security Advisory: + https://github.com/BioPAL/BPS/security/advisories/new + + - type: dropdown + id: category + attributes: + label: Category + options: + - Vulnerable dependency (known CVE) + - Supply-chain concern (provenance, signing, REUSE) + - CI/CD configuration weakness + - Repository or branch protection hardening + - Credential or secret exposure risk + - License or third-party code concern + - Other + validations: + required: true + + - type: dropdown + id: severity + attributes: + label: Estimated severity + description: Use the CVSS qualitative scale when applicable. + options: + - Critical + - High + - Medium + - Low + - Informational + validations: + required: true + + - type: input + id: identifiers + attributes: + label: CVE or advisory identifier (if applicable) + placeholder: "CVE-2025-12345 / GHSA-xxxx-xxxx-xxxx" + + - type: textarea + id: description + attributes: + label: Description + description: | + Describe the concern. Do not include exploit code, working proof of concept, + or any information that could enable an attack before a fix is available. + placeholder: | + Example: + - Concern, attached below. + - Exploit code, attached below. + - Working proof of concept, attached below. + - Information that could enable an attack before a fix is available, attached below. + validations: + required: true + + - type: textarea + id: affected + attributes: + label: Affected component or configuration + description: Component, file, workflow, dependency or configuration impacted. + placeholder: | + Example: + - Component, attached below. + - File, attached below. + - Workflow, attached below. + - Dependency, attached below. + - Configuration, attached below. + validations: + required: true + + - type: textarea + id: mitigation + attributes: + label: Proposed mitigation or remediation + description: Suggested patch, configuration change, dependency upgrade, or other mitigation. + placeholder: | + Example: + - Patch, attached below. + - Configuration change, attached below. + - Dependency upgrade, attached below. + - Other mitigation, attached below. + - type: checkboxes + id: disclosure + attributes: + label: Disclosure acknowledgement + options: + - label: I confirm that the information disclosed in this issue is safe for public disclosure. + required: true + - label: I understand that sensitive or exploitable vulnerabilities must be reported through a private GitHub Security Advisory rather than a public issue. + required: true + - label: I have not included exploit code or any information that could enable an attack before a fix is available. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0c9a9a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 +# +# Issue template chooser configuration for the BIOMASS BPS repository. +# Blank issues are disabled to ensure every report follows a structured template, +# which is required for automated triage and tier classification by the CI. + +blank_issues_enabled: false + +contact_links: + - name: Documentation site + url: https://biomass-disc.info/docs + about: User Guide, Tutorials, Science Guide and Governance. Please consult the documentation before opening an issue. + + - name: GitHub Discussions + url: https://github.com/BioPAL/BPS/discussions + about: For open-ended questions, design discussions or community conversation that are not a concrete defect, request, or proposal. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c6e86c5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,96 @@ + + +## Linked issue + + + +Closes # + +- [ ] The linked issue is labelled `status:approved`, `good-first-issue` or `help-wanted`. +Being open in the backlog is not enough on its own: only these three labels mean the scope has been triaged and approved for implementation. + +## What this PR changes + + + +* +* +* + +## Notes for reviewers + + + +## User-facing change + +- [ ] This PR introduces a user-facing change (API, CLI, output format, performance, documentation). + +If yes, write one short release-note sentence here (it will be picked up in `CHANGELOG.md`): + + +## Documentation + +- [ ] This PR modifies the documentation (Sphinx site, README, wiki, ATBD, Science Guide). +- [ ] This PR does not modify the documentation, and no documentation update is needed. +- [ ] This PR does not modify the documentation, but a documentation update is needed and tracked in issue #_____. + +## Testing + +- [ ] Unit tests cover new or changed logic +- [ ] Integration or workflow tests added where relevant +- [ ] Scientific regression handled (`baseline` / `extended` / `heavy` updated if applicable) +- [ ] Coverage ≥ 60% on touched code; all relevant tests pass locally +- [ ] Tests are independent; test data is included or documented + +## Tier rationale + +The CI computes the tier automatically from the diff against the base branch. You do not assign it, but stating your expectation helps reviewers spot a mismatch quickly. + +| Tier | Triggers | Checks that run | +|---|---|---| +| **0** | Routine changes, no sensitive path touched | Baseline only | +| **1** | Locked paths, SME-owned paths, marker fail, Dependabot major | Baseline + Extended | +| **2** | `VERSION` promoted to `main`, designated heavy paths, manual `run_heavy` | Baseline + Extended + Heavy | + +Full rules: [`.github/tier-policy.yml`](.github/tier-policy.yml). Background: [Contribution tiers in the contributor guide](../../wiki/CONTRIBUTING_PART1#contribution-tiers). + +* Expected tier: +* Why: + +## AI assistance disclosure + + + +- [ ] No AI tools were used to prepare this PR. +- [ ] AI tools were used. Tool(s):   +
What was generated (code, tests, documentation, commit messages): +
I have reviewed the generated content and take responsibility for it. + +## Checklist + +- [ ] This PR closes exactly one tracking issue, linked above. +- [ ] The scope of the diff matches the approved scope in the linked issue. No drift, no extras. +- [ ] A breaking change is explicitly flagged in the release note sentence above (if applicable). +- [ ] The reviewer assigned has the relevant domain knowledge for this change. diff --git a/.github/good_first_issue_comment.md b/.github/good_first_issue_comment.md new file mode 100644 index 0000000..49d1ee7 --- /dev/null +++ b/.github/good_first_issue_comment.md @@ -0,0 +1,37 @@ + + +Hello, and thank you for your interest in contributing to **BIOMASS BPS**. + +This issue has been labelled `good-first-issue` because it is well-scoped and a +suitable entry point into the codebase. The maintainers will be happy to review +and answer questions on the pull request. + +### Expected effort +TODO: estimate, for example `1–2 hours, including the unit test`. + +### Suggested approach +TODO: list the concrete steps, for example: +1. Reproduce the issue locally using the steps above. +2. Add a unit test in `/tests/_test.py` that fails before the fix. +3. Implement the fix in `/.py`. +4. Open a pull request that links back to this issue. + +### Relevant files +TODO: list the files a newcomer should read first, for example: +- `bps-common/bps_common/.py` +- `bps-/bps_/.py` + +### Before you start +- Read the [Contributing guide](https://biomass-disc.info/docs/contributing/). +- Make sure you can build and run the test suite locally — see the [Getting started](https://biomass-disc.info/docs/getting-started/) page. +- Every commit must be signed off (`git commit -s`) to satisfy the Developer Certificate of Origin. + +### If you have questions +Comment on this issue or open a draft pull request early. The maintainers prefer +early feedback over a polished pull request that has gone in the wrong direction. diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..d12b51f --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,188 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 +# +# Build the Sphinx site and publish to GitHub Pages on every push to develop. +# Preview builds run on pull requests (no deploy). +# biomass-disc rebuild also runs on push to docs/sphinx-site-migration (temporary). + +name: Documentation + +on: + push: + branches: + - develop + - docs/sphinx-site-migration + paths: + - "docs/**" + - "docs/presentations/**" + - ".github/workflows/docs.yml" + pull_request: + paths: + - "docs/**" + - "docs/presentations/**" + - ".github/workflows/docs.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: docs-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-tags: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: docs/requirements.txt + + - name: Install dependencies + run: pip install -r docs/requirements.txt + + - name: Install presentation build deps + run: pip install -r docs/presentations/2026-06-30-first-dev-meeting/requirements.txt + + - name: Install LaTeX + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + latexmk \ + texlive-latex-recommended \ + texlive-latex-extra \ + texlive-fonts-recommended \ + texlive-fonts-extra \ + texlive-xetex \ + fonts-dejavu + + - name: Build ATBD web export PDF + continue-on-error: true + run: make -C docs atbd-pdf + + - name: Build HTML + env: + DOCS_BASEURL: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && 'https://biopal.github.io/BPS/' || '' }} + DOCS_GITHUB_VERSION: develop + run: make -C docs html + + - name: Upload Pages artifact + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + uses: actions/upload-pages-artifact@v3 + with: + path: docs/_build/html + + deploy: + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + concurrency: + group: pages + cancel-in-progress: false + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + + # Trigger the biomass-disc frontend rebuild on the ACRI GitLab pipeline. + # The trigger token is write-only (cannot read pipeline status from here); + # the GitLab web URL is logged as a GitHub Actions notice so a human can + # follow the rebuild manually. + # + # Routes: + # push -> develop : auto-trigger DEV cluster (deploy) + # push -> docs/sphinx-site-migration : auto-trigger PROD publish (no deploy) + # workflow_dispatch (dev) : manual trigger DEV cluster + # workflow_dispatch (prod) : manual trigger PROD cluster + trigger-biomass-disc: + needs: build + if: | + (github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/docs/sphinx-site-migration')) || + (github.event_name == 'workflow_dispatch' && inputs.deploy_target != 'none') + runs-on: ubuntu-latest + environment: + name: ${{ ((github.event_name == 'workflow_dispatch' && inputs.deploy_target == 'prod') || (github.event_name == 'push' && github.ref == 'refs/heads/docs/sphinx-site-migration')) && 'biomass-disc-prod' || 'biomass-disc-dev' }} + steps: + - name: Resolve deploy overlay + id: target + env: + EVENT_NAME: ${{ github.event_name }} + DISPATCH_INPUT: ${{ inputs.deploy_target }} + GIT_REF: ${{ github.ref }} + run: | + if [ "${EVENT_NAME}" = "workflow_dispatch" ]; then + case "${DISPATCH_INPUT}" in + dev|prod) overlay="${DISPATCH_INPUT}" ;; + *) echo "::error::Invalid deploy_target: ${DISPATCH_INPUT}"; exit 1 ;; + esac + do_deploy="true" + elif [ "${GIT_REF}" = "refs/heads/docs/sphinx-site-migration" ]; then + overlay="prod" + do_deploy="false" + else + overlay="dev" + do_deploy="true" + fi + echo "overlay=${overlay}" >> "$GITHUB_OUTPUT" + echo "do_deploy=${do_deploy}" >> "$GITHUB_OUTPUT" + echo "Resolved overlay: ${overlay}, DO_DEPLOY=${do_deploy}" + + - name: Trigger biomass-disc GitLab pipeline + env: + GITLAB_TRIGGER_TOKEN: ${{ secrets.BIOMASS_DISC_TRIGGER_TOKEN }} + DEPLOY_OVERLAY: ${{ steps.target.outputs.overlay }} + DO_DEPLOY: ${{ steps.target.outputs.do_deploy }} + run: | + if [ -z "${GITLAB_TRIGGER_TOKEN}" ]; then + echo "::error::Secret BIOMASS_DISC_TRIGGER_TOKEN is missing. Add it under Settings -> Secrets and variables -> Actions." + exit 1 + fi + + response=$(curl -s --fail-with-body --request POST \ + --form "token=${GITLAB_TRIGGER_TOKEN}" \ + --form "ref=development" \ + --form "variables[REBUILD_FRONTEND]=true" \ + --form "variables[DO_BUILD]=true" \ + --form "variables[DO_PUBLISH]=true" \ + --form "variables[DO_DEPLOY]=${DO_DEPLOY}" \ + --form "variables[DO_TAG]=false" \ + --form "variables[SKIP_BUILD_BACKEND]=true" \ + --form "variables[SKIP_BUILD_DB]=true" \ + --form "variables[SKIP_TEST]=true" \ + --form "variables[DEPLOY_OVERLAY]=${DEPLOY_OVERLAY}" \ + "https://gitlab.shared.acrist-services.com/api/v4/projects/1478/trigger/pipeline") + + echo "Raw response:" + echo "${response}" + + pipeline_url=$(echo "${response}" | python3 -c "import sys, json; print(json.load(sys.stdin).get('web_url', ''))") + pipeline_id=$(echo "${response}" | python3 -c "import sys, json; print(json.load(sys.stdin).get('id', ''))") + + if [ -z "${pipeline_url}" ]; then + echo "::error::Trigger call did not return a pipeline URL. Check the raw response above." + exit 1 + fi + + echo "::notice title=biomass-disc ${DEPLOY_OVERLAY} pipeline started::${pipeline_url}" + { + echo "## biomass-disc rebuild triggered" + echo "" + echo "- **Cluster:** ${DEPLOY_OVERLAY}" + echo "- **Pipeline:** [${pipeline_id}](${pipeline_url})" + echo "" + echo "The trigger token is write-only, so the GitHub run cannot follow the GitLab pipeline status. Open the link above to verify the rebuild and deploy." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.gitignore b/.gitignore index 45daded..84aa3d1 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,26 @@ bps-stack_binaries/lib */dist # local test config -test/integration/config/local.toml \ No newline at end of file +test/integration/config/local.toml + +# macOS +.DS_Store + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini + +# Linux +.directory +.DS_Store +.cache +.config +.local + +# Private ATBD conversion workflow (local only; not published) +private/ + +# Generated HTML decks (built via `make html` / `make presentation`) +docs/_extra_static/presentations/ +docs/presentations/*/dist/ \ No newline at end of file diff --git a/README.md b/README.md index 8048f0d..c1be45b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ The BIOMASS Product Algorithm Laboratory hosts official tools for processing and analysing ESA\'s BIOMASS mission data. - Website: [www.biomass-disc.info](https://www.biomass-disc.info/) -- Documentation: [www.biomass-disc.info/release_note](https://www.biomass-disc.info/release_note) +- Documentation: [https://www.biomass-disc.info/docs/](https://www.biomass-disc.info/docs/) +- Release Note: [www.biomass-disc.info/release_note](https://www.biomass-disc.info/release_note) - Repository: [github.com/BioPAL/BPS](https://github.com/BioPAL/BPS) - Bug reports: [github.com/BioPAL/BPS/issues](https://github.com/BioPAL/BPS/issues) diff --git a/bps-l2a_processor/bps/l2a_processor/commands.py b/bps-l2a_processor/bps/l2a_processor/commands.py index e9111c8..dfccbae 100644 --- a/bps-l2a_processor/bps/l2a_processor/commands.py +++ b/bps-l2a_processor/bps/l2a_processor/commands.py @@ -175,12 +175,17 @@ def run_l2a_processing( stack_products_list.append(stack_product_obj.read()) acquisition_paths_selected.append(acquisition_path) - decoded_quality = StackQualityIndex.decode(stack_product_obj.stack_quality.overall_product_quality_index) - decoded_quality_string = [k.replace("_", " ") for k, v in decoded_quality.__dict__.items() if v is True] - if len(decoded_quality_string): - bps_logger.warning( - f"Input L1c acquisition staQuality overallProductQualityIndex={stack_product_obj.stack_quality.overall_product_quality_index}: {decoded_quality_string[0]}" - ) + sta_decoded_quality = StackQualityIndex.decode( + stack_product_obj.stack_quality.overall_product_quality_index + ) + sta_decoded_quality_string = [ + k.replace("_", " ") for k, v in vars(sta_decoded_quality).items() if v is True + ] + if len(sta_decoded_quality_string): + sta_quality_messages = ", ".join(sta_decoded_quality_string) + sta_message = f"Input L1c acquisition staQuality overall product quality index={stack_product_obj.stack_quality.overall_product_quality_index}: {sta_quality_messages}" + bps_logger.warning(f"{sta_message}") + if stack_product_obj.stack_processing_parameters.skp_phase_correction_flag: bps_logger.debug(f"SKP phase correction has been applied to input L1c acquisition {acquisition_path}") if counter == 0 and aux_pp2_2a.general.apply_calibration_screen != CalibrationScreenType.NONE: diff --git a/bps-l2b_fd_processor/bps/l2b_fd_processor/fd/fd_commands.py b/bps-l2b_fd_processor/bps/l2b_fd_processor/fd/fd_commands.py index 154bf02..0f72c5a 100644 --- a/bps-l2b_fd_processor/bps/l2b_fd_processor/fd/fd_commands.py +++ b/bps-l2b_fd_processor/bps/l2b_fd_processor/fd/fd_commands.py @@ -84,48 +84,53 @@ def _get_l2a_inputs_information(self): global_coverage_id = None l2a_inputs_list = [] basin_id_list = [] + counter = 0 for idx, l2a_product in enumerate(self.l2a_fd_products_list): - basin_id_list = basin_id_list + l2a_product.main_ads_product.basin_id_list + if self.job_order.processing_parameters.tile_id in l2a_product.main_ads_product.tile_id_list: + # Only the used L2A products (see dgg_tiling) - footprint = main_annotation_models_l2b_fd.FloatArrayWithUnits( - value=l2a_product.main_ads_input_information.footprint, - count=8, - units=common_types.UomType.DEG, - ) + basin_id_list = basin_id_list + l2a_product.main_ads_product.basin_id_list - l1_inputs = common_annotation_models_l2.InputInformationL2AType( - product_type=common_annotation_models_l2.ProductType( - l2a_product.main_ads_input_information.product_type - ), - overall_products_quality_index=l2a_product.main_ads_input_information.overall_products_quality_index, - nominal_stack=str(l2a_product.main_ads_input_information.nominal_stack).lower(), - polarisation_list=l2a_product.main_ads_input_information.polarisation_list, - projection=common_annotation_models_l2.ProjectionType( - l2a_product.main_ads_input_information.projection - ), - footprint=footprint, - vertical_wavenumbers=l2a_product.main_ads_input_information.vertical_wavenumbers, - height_of_ambiguity=l2a_product.main_ads_input_information.height_of_ambiguity, - acquisition_list=l2a_product.main_ads_input_information.acquisition_list, - ) + footprint = main_annotation_models_l2b_fd.FloatArrayWithUnits( + value=l2a_product.main_ads_input_information.footprint, + count=8, + units=common_types.UomType.DEG, + ) - l2a_inputs_list.append( - common_annotation_models_l2.InputInformationL2BL3ListType.L2AInputs( - l2a_product_folder_name=str(self.job_order.input_l2a_products[idx]), - l2a_product_date=l2a_product.main_ads_product.start_time.isoformat(timespec="microseconds"), - l1_inputs=l1_inputs, - significance_level=l2a_product.main_ads_processing_parameters.significance_level, + l1_inputs = common_annotation_models_l2.InputInformationL2AType( + product_type=common_annotation_models_l2.ProductType( + l2a_product.main_ads_input_information.product_type + ), + overall_products_quality_index=l2a_product.main_ads_input_information.overall_products_quality_index, + nominal_stack=str(l2a_product.main_ads_input_information.nominal_stack).lower(), + polarisation_list=l2a_product.main_ads_input_information.polarisation_list, + projection=common_annotation_models_l2.ProjectionType( + l2a_product.main_ads_input_information.projection + ), + footprint=footprint, + vertical_wavenumbers=l2a_product.main_ads_input_information.vertical_wavenumbers, + height_of_ambiguity=l2a_product.main_ads_input_information.height_of_ambiguity, + acquisition_list=l2a_product.main_ads_input_information.acquisition_list, ) - ) - if idx == 0: - mission_phase_id = l2a_product.main_ads_product.mission_phase_id - global_coverage_id = l2a_product.main_ads_product.global_coverage_id - else: - if not mission_phase_id == l2a_product.main_ads_product.mission_phase_id: - raise ValueError( - "Input L2a products should be of the same phase: found some INT and some TOM phase L2a products." + l2a_inputs_list.append( + common_annotation_models_l2.InputInformationL2BL3ListType.L2AInputs( + l2a_product_folder_name=str(self.job_order.input_l2a_products[idx]), + l2a_product_date=l2a_product.main_ads_product.start_time.isoformat(timespec="microseconds"), + l1_inputs=l1_inputs, + significance_level=l2a_product.main_ads_processing_parameters.significance_level, ) + ) + + if counter == 0: + mission_phase_id = l2a_product.main_ads_product.mission_phase_id + global_coverage_id = l2a_product.main_ads_product.global_coverage_id + else: + if not mission_phase_id == l2a_product.main_ads_product.mission_phase_id: + raise ValueError( + "Input L2a products should be of the same phase: found some INT and some TOM phase L2a products." + ) + counter += 1 return ( l2a_inputs_list, @@ -231,29 +236,32 @@ def _core_processing(self): main_ads_input_information_l2b = None bps_logger.info("L2b FD processor: nothing to compute, exiting.") - start_time_l2a = self.l2a_fd_products_list[0].main_ads_product.start_time - stop_time_l2a = self.l2a_fd_products_list[0].main_ads_product.stop_time + start_time_l2a = None + stop_time_l2a = None for l2a_product in self.l2a_fd_products_list: - start_time_l2a = min(start_time_l2a, l2a_product.main_ads_product.start_time) - stop_time_l2a = max(stop_time_l2a, l2a_product.main_ads_product.stop_time) + if self.job_order.processing_parameters.tile_id in l2a_product.main_ads_product.tile_id_list: + # Only the used L2A products (see dgg_tiling) + if start_time_l2a is None: + start_time_l2a = l2a_product.main_ads_product.start_time + stop_time_l2a = l2a_product.main_ads_product.stop_time + else: + start_time_l2a = min(start_time_l2a, l2a_product.main_ads_product.start_time) + stop_time_l2a = max(stop_time_l2a, l2a_product.main_ads_product.stop_time) # type: ignore # Footprint mask for quick looks transparency footprint_masks_for_quicklooks = {} + fd_for_footprint = data_3d_mat_dict["fd"] if AVERAGING_FACTOR_QUICKLOOKS > 1: - fd_for_footprint = data_3d_mat_dict["fd"].squeeze()[ - ::DECIMATION_FACTOR_QUICKLOOKS, ::DECIMATION_FACTOR_QUICKLOOKS - ] - else: - fd_for_footprint = data_3d_mat_dict["dh"].squeeze() - footprint_masks_for_quicklooks["data_mask"] = np.logical_not(np.isnan(fd_for_footprint)) + fd_for_footprint = data_3d_mat_dict["fd"][::DECIMATION_FACTOR_QUICKLOOKS, ::DECIMATION_FACTOR_QUICKLOOKS] + footprint_masks_for_quicklooks["data_mask"] = np.logical_not(np.isnan(fd_for_footprint)).any(axis=-1) # Footprint mask for quick looks transparency - cfm_no_data_value_mask = data_3d_mat_dict["cfm"].squeeze() == INT_NODATA_VALUE - cfm_for_footprint = data_3d_mat_dict["cfm"].squeeze().astype(np.float32) + cfm_no_data_value_mask = data_3d_mat_dict["cfm"] == INT_NODATA_VALUE + cfm_for_footprint = data_3d_mat_dict["cfm"].astype(np.float32) cfm_for_footprint[cfm_no_data_value_mask] = np.nan if AVERAGING_FACTOR_QUICKLOOKS > 1: cfm_for_footprint = cfm_for_footprint[::DECIMATION_FACTOR_QUICKLOOKS, ::DECIMATION_FACTOR_QUICKLOOKS] - footprint_masks_for_quicklooks["cfm_mask"] = np.logical_not(np.isnan(cfm_for_footprint)) + footprint_masks_for_quicklooks["cfm_mask"] = np.logical_not(np.isnan(cfm_for_footprint)).any(axis=-1) return ( l2b_data_final_d, diff --git a/bps-l2b_fh_processor/bps/l2b_fh_processor/fh/fh_commands.py b/bps-l2b_fh_processor/bps/l2b_fh_processor/fh/fh_commands.py index abf70a9..07ad32c 100644 --- a/bps-l2b_fh_processor/bps/l2b_fh_processor/fh/fh_commands.py +++ b/bps-l2b_fh_processor/bps/l2b_fh_processor/fh/fh_commands.py @@ -93,47 +93,52 @@ def _get_l2a_inputs_information(self): global_coverage_id = None l2a_inputs_list = [] basin_id_list = [] + counter = 0 for idx, l2a_product in enumerate(self.l2a_fh_products_list): - basin_id_list = basin_id_list + l2a_product.main_ads_product.basin_id_list + if self.job_order.processing_parameters.tile_id in l2a_product.main_ads_product.tile_id_list: + # Only the used L2A products (see dgg_tiling) - footprint = main_annotation_models_l2b_fh.FloatArrayWithUnits( - value=l2a_product.main_ads_input_information.footprint, - count=8, - units=common_types.UomType.DEG, - ) + basin_id_list = basin_id_list + l2a_product.main_ads_product.basin_id_list - l1_inputs = common_annotation_models_l2.InputInformationL2AType( - product_type=common_annotation_models_l2.ProductType( - l2a_product.main_ads_input_information.product_type - ), - overall_products_quality_index=l2a_product.main_ads_input_information.overall_products_quality_index, - nominal_stack=str(l2a_product.main_ads_input_information.nominal_stack).lower(), - polarisation_list=l2a_product.main_ads_input_information.polarisation_list, - projection=common_annotation_models_l2.ProjectionType( - l2a_product.main_ads_input_information.projection - ), - footprint=footprint, - vertical_wavenumbers=l2a_product.main_ads_input_information.vertical_wavenumbers, - height_of_ambiguity=l2a_product.main_ads_input_information.height_of_ambiguity, - acquisition_list=l2a_product.main_ads_input_information.acquisition_list, - ) + footprint = main_annotation_models_l2b_fh.FloatArrayWithUnits( + value=l2a_product.main_ads_input_information.footprint, + count=8, + units=common_types.UomType.DEG, + ) - l2a_inputs_list.append( - common_annotation_models_l2.InputInformationL2BL3ListType.L2AInputs( - l2a_product_folder_name=str(self.job_order.input_l2a_products[idx]), - l2a_product_date=l2a_product.main_ads_product.start_time.isoformat(timespec="microseconds"), - l1_inputs=l1_inputs, + l1_inputs = common_annotation_models_l2.InputInformationL2AType( + product_type=common_annotation_models_l2.ProductType( + l2a_product.main_ads_input_information.product_type + ), + overall_products_quality_index=l2a_product.main_ads_input_information.overall_products_quality_index, + nominal_stack=str(l2a_product.main_ads_input_information.nominal_stack).lower(), + polarisation_list=l2a_product.main_ads_input_information.polarisation_list, + projection=common_annotation_models_l2.ProjectionType( + l2a_product.main_ads_input_information.projection + ), + footprint=footprint, + vertical_wavenumbers=l2a_product.main_ads_input_information.vertical_wavenumbers, + height_of_ambiguity=l2a_product.main_ads_input_information.height_of_ambiguity, + acquisition_list=l2a_product.main_ads_input_information.acquisition_list, ) - ) - if idx == 0: - mission_phase_id = l2a_product.main_ads_product.mission_phase_id - global_coverage_id = l2a_product.main_ads_product.global_coverage_id - else: - if not mission_phase_id == l2a_product.main_ads_product.mission_phase_id: - raise ValueError( - "Input L2a products should be of the same phase: found some INT and some TOM phase L2a products." + l2a_inputs_list.append( + common_annotation_models_l2.InputInformationL2BL3ListType.L2AInputs( + l2a_product_folder_name=str(self.job_order.input_l2a_products[idx]), + l2a_product_date=l2a_product.main_ads_product.start_time.isoformat(timespec="microseconds"), + l1_inputs=l1_inputs, ) + ) + + if counter == 0: + mission_phase_id = l2a_product.main_ads_product.mission_phase_id + global_coverage_id = l2a_product.main_ads_product.global_coverage_id + else: + if not mission_phase_id == l2a_product.main_ads_product.mission_phase_id: + raise ValueError( + "Input L2a products should be of the same phase: found some INT and some TOM phase L2a products." + ) + counter += 1 return ( l2a_inputs_list, @@ -199,7 +204,9 @@ def _core_processing(self): data_footprint_list = [] for l2a_product in self.l2a_fh_products_list: - data_footprint_list.append(np.array(l2a_product.main_ads_input_information.footprint)) + if self.job_order.processing_parameters.tile_id in l2a_product.main_ads_product.tile_id_list: + # Only the used L2A products (see dgg_tiling) + data_footprint_list.append(np.array(l2a_product.main_ads_input_information.footprint)) # Average ( @@ -259,13 +266,10 @@ def _core_processing(self): stop_time_l2a = max(stop_time_l2a, l2a_product.main_ads_product.stop_time) # Footprint mask for quick looks transparency + fh_for_footprint = data_3d_mat_dict["fh"] if AVERAGING_FACTOR_QUICKLOOKS > 1: - fh_for_footprint = data_3d_mat_dict["fh"].squeeze()[ - ::DECIMATION_FACTOR_QUICKLOOKS, ::DECIMATION_FACTOR_QUICKLOOKS - ] - else: - fh_for_footprint = data_3d_mat_dict["fh"].squeeze() - footprint_mask_for_quicklooks = np.logical_not(np.isnan(fh_for_footprint)) + fh_for_footprint = data_3d_mat_dict["fh"][::DECIMATION_FACTOR_QUICKLOOKS, ::DECIMATION_FACTOR_QUICKLOOKS] + footprint_mask_for_quicklooks = np.logical_not(np.isnan(fh_for_footprint)).any(axis=-1) return ( l2b_data_final_d, diff --git a/bps-stack_cal_processor/bps/stack_cal_processor/core/skp/skpcalibration.py b/bps-stack_cal_processor/bps/stack_cal_processor/core/skp/skpcalibration.py index 99a2a84..1ddc62d 100644 --- a/bps-stack_cal_processor/bps/stack_cal_processor/core/skp/skpcalibration.py +++ b/bps-stack_cal_processor/bps/stack_cal_processor/core/skp/skpcalibration.py @@ -338,8 +338,8 @@ def skp_calibration( skp_calibration_phases=subsampled_calibration_phases, azimuth_axis=azimuth_axis, range_axis=range_axis, - azimuth_estimation_axis=azimuth_axis[azimuth_estimation_indices], - range_estimation_axis=range_axis[range_estimation_indices], + azimuth_estimation_axis=azimuth_axis[azimuth_estimation_indices].squeeze(), + range_estimation_axis=range_axis[range_estimation_indices].squeeze(), output_azimuth_subsampling_step=conf.output_azimuth_subsampling_step, output_range_subsampling_step=conf.output_range_subsampling_step, dtypes=estimation_dtypes, @@ -354,8 +354,8 @@ def skp_calibration( bps_logger.info("Extracting qualities from FNF mask") skp_calibration_quality = compute_skp_fnf_quality( skp_fnf_mask=skp_fnf_mask, - skp_azimuth_axis=azimuth_axis[azimuth_output_indices], - skp_range_axis=range_axis[range_output_indices], + skp_azimuth_axis=azimuth_axis[azimuth_output_indices].squeeze(), + skp_range_axis=range_axis[range_output_indices].squeeze(), ) # Possibly, postprocess the SKP calibration phase with a filter. diff --git a/bps-stack_cal_processor/bps/stack_cal_processor/core/skp/skpcorrection.py b/bps-stack_cal_processor/bps/stack_cal_processor/core/skp/skpcorrection.py index 1fe0375..77bf6e4 100644 --- a/bps-stack_cal_processor/bps/stack_cal_processor/core/skp/skpcorrection.py +++ b/bps-stack_cal_processor/bps/stack_cal_processor/core/skp/skpcorrection.py @@ -310,8 +310,8 @@ def upsample_skp_phases_multithreaded( # Apply the flattening phases to the SKP calibration phases. with ThreadPoolExecutor(max_workers=num_worker_threads) as executor: # The output axes. - azimuth_output_axis = azimuth_axis[azimuth_output_indices] - range_output_axis = range_axis[range_output_indices] + azimuth_output_axis = azimuth_axis[azimuth_output_indices].squeeze() + range_output_axis = range_axis[range_output_indices].squeeze() # The core routine. def compute_skp_calibration_phases_fn(phi_cal): diff --git a/bps-transcoder/bps/transcoder/sarproduct/biomass_l2bfdproduct_writer.py b/bps-transcoder/bps/transcoder/sarproduct/biomass_l2bfdproduct_writer.py index b6b6d6b..bbd104e 100644 --- a/bps-transcoder/bps/transcoder/sarproduct/biomass_l2bfdproduct_writer.py +++ b/bps-transcoder/bps/transcoder/sarproduct/biomass_l2bfdproduct_writer.py @@ -1008,106 +1008,106 @@ def _write_quicklook_files(self): _type_ _description_ """ + with np.errstate(divide="ignore", invalid="ignore"): # FD may have all NaNs at first cycle + dirname = Path(self.product_structure.quicklook_files[0]).parent + if not dirname.exists(): + dirname.mkdir(parents=True, exist_ok=True) + + for key in self.product.measurement.data_dict.keys(): + if key not in [ + "fd", + "cfm", + "probability_ofchange", + "heat_map", + ]: + continue + + imm_to_save = self.product.measurement.data_dict[key] + + # set no data values to zeros, for the quick look + name_to_search = "_" + key + "_ql" + if key in ["fd", "cfm"]: + imm_to_save[imm_to_save == INT_NODATA_VALUE] = 0 + if key == "heat_map": + imm_to_save1 = self.product.measurement.data_dict[key]["heat_map_contributing"] + imm_to_save2 = self.product.measurement.data_dict[key]["heat_map_agreeing"] + imm_to_save1[imm_to_save1 == INT_NODATA_VALUE] = 0 + imm_to_save2[imm_to_save2 == INT_NODATA_VALUE] = 0 + if key == "probability_ofchange": + imm_to_save[imm_to_save == FLOAT_NODATA_VALUE] = 0.0 + name_to_search = "_probability_ql" + + # If required, multilook input data + if AVERAGING_FACTOR_QUICKLOOKS > 1: + boxcar = np.ones((AVERAGING_FACTOR_QUICKLOOKS, AVERAGING_FACTOR_QUICKLOOKS)) + + if key == "heat_map": + imm_to_save1 = np.sqrt( + convolve2d(imm_to_save1**2, boxcar, "same") + / (AVERAGING_FACTOR_QUICKLOOKS * AVERAGING_FACTOR_QUICKLOOKS) + )[::DECIMATION_FACTOR_QUICKLOOKS, ::DECIMATION_FACTOR_QUICKLOOKS] + + imm_to_save2 = np.sqrt( + convolve2d(imm_to_save2**2, boxcar, "same") + / (AVERAGING_FACTOR_QUICKLOOKS * AVERAGING_FACTOR_QUICKLOOKS) + )[::DECIMATION_FACTOR_QUICKLOOKS, ::DECIMATION_FACTOR_QUICKLOOKS] + else: + imm_to_save = np.sqrt( + convolve2d(imm_to_save**2, boxcar, "same") + / (AVERAGING_FACTOR_QUICKLOOKS * AVERAGING_FACTOR_QUICKLOOKS) + )[::DECIMATION_FACTOR_QUICKLOOKS, ::DECIMATION_FACTOR_QUICKLOOKS] + + fname = None + for fname in self.product_structure.quicklook_files: + if name_to_search in fname: + break + assert fname is not None + + # Write quicklook file + if key == "heat_map": + full_fname = Path(fname) + file_name = full_fname.name + file_name1 = file_name[0:35] + "hmcontrib_ql.png" + full_fname1 = str(full_fname.parent.joinpath(file_name1)) + file_name2 = file_name[0:35] + "hmagreeing_ql.png" + full_fname2 = str(full_fname.parent.joinpath(file_name2)) + + # Scale float values to be integers in the [0:255] range + imm_to_save1 = (255.0 * imm_to_save1 / np.max(imm_to_save1)).astype(np.uint8) + imm_to_save2 = (255.0 * imm_to_save2 / np.max(imm_to_save2)).astype(np.uint8) + + # Convert from 2D GRAY image a 4D BGRA (replica of image in three RGB channels) + imm_bgra1 = cv2.cvtColor(imm_to_save1, cv2.COLOR_GRAY2BGRA) + # make transparent (alpha = 0) out of the footprint + alpha = np.where(self.footprint_masks_for_quicklooks["data_mask"], 255, 0).astype(np.uint8) + # Insert Alpha channel to the image BRGA + imm_bgra1[:, :, 3] = alpha + cv2.imwrite(fname, imm_bgra1) - dirname = Path(self.product_structure.quicklook_files[0]).parent - if not dirname.exists(): - dirname.mkdir(parents=True, exist_ok=True) - - for key in self.product.measurement.data_dict.keys(): - if key not in [ - "fd", - "cfm", - "probability_ofchange", - "heat_map", - ]: - continue - - imm_to_save = self.product.measurement.data_dict[key] - - # set no data values to zeros, for the quick look - name_to_search = "_" + key + "_ql" - if key in ["fd", "cfm"]: - imm_to_save[imm_to_save == INT_NODATA_VALUE] = 0 - if key == "heat_map": - imm_to_save1 = self.product.measurement.data_dict[key]["heat_map_contributing"] - imm_to_save2 = self.product.measurement.data_dict[key]["heat_map_agreeing"] - imm_to_save1[imm_to_save1 == INT_NODATA_VALUE] = 0 - imm_to_save2[imm_to_save2 == INT_NODATA_VALUE] = 0 - if key == "probability_ofchange": - imm_to_save[imm_to_save == FLOAT_NODATA_VALUE] = 0.0 - name_to_search = "_probability_ql" - - # If required, multilook input data - if AVERAGING_FACTOR_QUICKLOOKS > 1: - boxcar = np.ones((AVERAGING_FACTOR_QUICKLOOKS, AVERAGING_FACTOR_QUICKLOOKS)) + # Convert from 2D GRAY image a 4D BGRA (replica of image in three RGB channels) + imm_bgra2 = cv2.cvtColor(imm_to_save2, cv2.COLOR_GRAY2BGRA) + # make transparent (alpha = 0) out of the footprint + alpha = np.where(self.footprint_masks_for_quicklooks["data_mask"], 255, 0).astype(np.uint8) + # Insert Alpha channel to the image BRGA + imm_bgra2[:, :, 3] = alpha - if key == "heat_map": - imm_to_save1 = np.sqrt( - convolve2d(imm_to_save1**2, boxcar, "same") - / (AVERAGING_FACTOR_QUICKLOOKS * AVERAGING_FACTOR_QUICKLOOKS) - )[::DECIMATION_FACTOR_QUICKLOOKS, ::DECIMATION_FACTOR_QUICKLOOKS] - - imm_to_save2 = np.sqrt( - convolve2d(imm_to_save2**2, boxcar, "same") - / (AVERAGING_FACTOR_QUICKLOOKS * AVERAGING_FACTOR_QUICKLOOKS) - )[::DECIMATION_FACTOR_QUICKLOOKS, ::DECIMATION_FACTOR_QUICKLOOKS] - else: - imm_to_save = np.sqrt( - convolve2d(imm_to_save**2, boxcar, "same") - / (AVERAGING_FACTOR_QUICKLOOKS * AVERAGING_FACTOR_QUICKLOOKS) - )[::DECIMATION_FACTOR_QUICKLOOKS, ::DECIMATION_FACTOR_QUICKLOOKS] - - fname = None - for fname in self.product_structure.quicklook_files: - if name_to_search in fname: - break - assert fname is not None - - # Write quicklook file - if key == "heat_map": - full_fname = Path(fname) - file_name = full_fname.name - file_name1 = file_name[0:35] + "hmcontrib_ql.png" - full_fname1 = str(full_fname.parent.joinpath(file_name1)) - file_name2 = file_name[0:35] + "hmagreeing_ql.png" - full_fname2 = str(full_fname.parent.joinpath(file_name2)) - - # Scale float values to be integers in the [0:255] range - imm_to_save1 = (255.0 * imm_to_save1 / np.max(imm_to_save1)).astype(np.uint8) - imm_to_save2 = (255.0 * imm_to_save2 / np.max(imm_to_save2)).astype(np.uint8) - - # Convert from 2D GRAY image a 4D BGRA (replica of image in three RGB channels) - imm_bgra1 = cv2.cvtColor(imm_to_save1, cv2.COLOR_GRAY2BGRA) - # make transparent (alpha = 0) out of the footprint - alpha = np.where(self.footprint_masks_for_quicklooks["data_mask"], 255, 0).astype(np.uint8) - # Insert Alpha channel to the image BRGA - imm_bgra1[:, :, 3] = alpha - cv2.imwrite(fname, imm_bgra1) - - # Convert from 2D GRAY image a 4D BGRA (replica of image in three RGB channels) - imm_bgra2 = cv2.cvtColor(imm_to_save2, cv2.COLOR_GRAY2BGRA) - # make transparent (alpha = 0) out of the footprint - alpha = np.where(self.footprint_masks_for_quicklooks["data_mask"], 255, 0).astype(np.uint8) - # Insert Alpha channel to the image BRGA - imm_bgra2[:, :, 3] = alpha - - cv2.imwrite(full_fname1, imm_bgra1) - cv2.imwrite(full_fname2, imm_bgra1) + cv2.imwrite(full_fname1, imm_bgra1) + cv2.imwrite(full_fname2, imm_bgra1) - else: - # Scale float values to be integers in the [0:255] range - imm_to_save = (255.0 * imm_to_save / np.max(imm_to_save)).astype(np.uint8) - - # Convert from 2D GRAY image a 4D BGRA (replica of image in three RGB channels) - imm_bgra = cv2.cvtColor(imm_to_save, cv2.COLOR_GRAY2BGRA) - # make transparent (alpha = 0) out of the footprint - if key == "cfm": - alpha = np.where(self.footprint_masks_for_quicklooks["cfm_mask"], 255, 0).astype(np.uint8) else: - alpha = np.where(self.footprint_masks_for_quicklooks["data_mask"], 255, 0).astype(np.uint8) - # Insert Alpha channel to the image BRGA - imm_bgra[:, :, 3] = alpha - cv2.imwrite(fname, imm_bgra) + # Scale float values to be integers in the [0:255] range + imm_to_save = (255.0 * imm_to_save / np.max(imm_to_save)).astype(np.uint8) + + # Convert from 2D GRAY image a 4D BGRA (replica of image in three RGB channels) + imm_bgra = cv2.cvtColor(imm_to_save, cv2.COLOR_GRAY2BGRA) + # make transparent (alpha = 0) out of the footprint + if key == "cfm": + alpha = np.where(self.footprint_masks_for_quicklooks["cfm_mask"], 255, 0).astype(np.uint8) + else: + alpha = np.where(self.footprint_masks_for_quicklooks["data_mask"], 255, 0).astype(np.uint8) + # Insert Alpha channel to the image BRGA + imm_bgra[:, :, 3] = alpha + cv2.imwrite(fname, imm_bgra) def _write_overlay_files(self): # NE, SE, SW, NW diff --git a/bps-transcoder/bps/transcoder/utils/dgg_utils.py b/bps-transcoder/bps/transcoder/utils/dgg_utils.py index e213bb7..4c533b9 100644 --- a/bps-transcoder/bps/transcoder/utils/dgg_utils.py +++ b/bps-transcoder/bps/transcoder/utils/dgg_utils.py @@ -46,7 +46,7 @@ def dgg_search_tiles(latlon_coverage: list[float], create_tiles_dict: bool | Non tile_extent_lonlat_dict = {} # list of tile degrees extension in lat / lon for each tile [tile extent lon deg, tile extent lat deg] # whole Copernicus latitude vector - latitude_vector = np.arange(-70, 70, tile_extent_lat) + pixel_spacing_lat / 2 + latitude_vector = np.arange(-90, 90, tile_extent_lat) + pixel_spacing_lat / 2 # Copernicus longitude pixel spacing varies depending on latitude sector pixel_spacing_lon_list = np.zeros(latitude_vector.shape).astype(np.float64) @@ -55,8 +55,10 @@ def dgg_search_tiles(latlon_coverage: list[float], create_tiles_dict: bool | Non pixel_spacing_lon_list[np.logical_and(latitude_vector >= -50, latitude_vector < 50)] = 3 / 3600 pixel_spacing_lon_list[np.logical_and(latitude_vector >= 50, latitude_vector < 60)] = 4.5 / 3600 pixel_spacing_lon_list[np.logical_and(latitude_vector >= 60, latitude_vector < 70)] = 6 / 3600 + pixel_spacing_lon_list[latitude_vector >= 70] = 6 / 3600 + pixel_spacing_lon_list[latitude_vector <= -70] = 6 / 3600 - lat_tiles_name = ["S{:02d}".format(70 - lat) for lat in range(0, 70, tile_extent_lat)] + [ + lat_tiles_name = ["S{:02d}".format(90 - lat) for lat in range(0, 90, tile_extent_lat)] + [ "N{:02d}".format(lat) for lat in range(0, 89, tile_extent_lat) ] diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..d2afba1 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 + +# Sphinx build artefacts +_build/ + +# Local virtual environment used to build the docs +.venv/ + +# Compiled python files inside the docs tree +__pycache__/ +*.py[cod] diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..c02434c --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,125 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 +# +# Minimal Makefile for the BIOMASS BPS Sphinx documentation. +# +# First-time setup (from this directory): +# make install +# make html +# make livehtml + +# Prefer docs/.venv when present (avoids missing extensions on system Python). +VENV := .venv +ifeq ($(wildcard $(VENV)/bin/sphinx-build),) + SPHINXBUILD ?= sphinx-build + AUTOBUILD ?= sphinx-autobuild + PIP ?= pip +else + SPHINXBUILD ?= $(VENV)/bin/sphinx-build + AUTOBUILD ?= $(VENV)/bin/sphinx-autobuild + PIP ?= $(VENV)/bin/pip +endif + +SPHINXOPTS ?= +SOURCEDIR = . +BUILDDIR = _build + +.PHONY: help install html html-all clean livehtml linkcheck diagrams sync-bib atbd-pdf presentation presentation-shared + +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) + @echo + @echo "Extra targets:" + @echo " make presentation Build dated deck slides into _extra_static/" + @echo " make presentation-shared Sync shared logos only (after editing _shared/logos/)" + @echo " make atbd-pdf Build web-export PDF for the L2 AGB ATBD (requires LaTeX)" + @echo " make html-all atbd-pdf + html" + @echo " make sync-bib Copy references.bib from the private conversion pipeline" + +install: + @test -d "$(VENV)" || python3 -m venv "$(VENV)" + $(PIP) install -r requirements.txt sphinx-autobuild + @echo "Docs venv ready. Run: make html (or: make livehtml)" + +presentation: + @$(MAKE) -C presentations/2026-06-30-first-dev-meeting publish + +presentation-shared: + @python3 presentations/2026-06-30-first-dev-meeting/scripts/publish_docs.py --shared-only + +# Standard HTML build (includes presentation static files when built) +html: presentation-shared presentation + @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) + @echo + @echo "Build finished. Open $(BUILDDIR)/html/index.html in your browser." + +# HTML plus ATBD web-export PDF (requires LaTeX for atbd-pdf) +html-all: atbd-pdf html + +# Clean the build directory +clean: + @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) + @echo "Build directory cleaned." + +# Live-rebuild server (install via make install) +livehtml: + $(AUTOBUILD) "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) + +# Check external links +linkcheck: + @$(SPHINXBUILD) -M linkcheck "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) + +# Re-render every D2 source into SVG. Requires the d2 CLI (brew install d2). +diagrams: + @for f in _static/diagrams/*.d2; do \ + out="$${f%.d2}.svg"; \ + echo " d2 $$f -> $$out"; \ + d2 --layout=elk --theme=0 --pad=20 "$$f" "$$out"; \ + done + +# Copy shared bibliography from the local private conversion pipeline. +PRIVATE_BIB ?= ../private/atbd-conversion/pipeline/references.bib + +sync-bib: + @test -f "$(PRIVATE_BIB)" || (echo "Missing $(PRIVATE_BIB) — private pipeline is local-only."; exit 1) + cp "$(PRIVATE_BIB)" references.bib + @echo "Synced references.bib from private/atbd-conversion/pipeline/" + +# Build a standalone PDF for one multi-page ATBD (requires LaTeX: texlive or MacTeX). +# Includes every chapter listed in the ATBD index toctree (through References). +# Usage: make atbd-pdf +# make atbd-pdf ATBD=atbd-l2-agb +# Optional: install mermaid-cli (`npm i -g @mermaid-js/mermaid-cli`) so flowcharts +# render in the PDF; otherwise PNG figures from the PDF conversion are included. +ATBD ?= atbd-l2-agb +ATBD_INDEX = $(ATBD)/index +ATBD_PDF = _static/pdf/$(ATBD).pdf +ATBD_LATEX_DIR = $(BUILDDIR)/atbd-pdf/latex + +atbd-pdf: + @echo "Building web-export PDF for science-guide/$(ATBD)/ …" + @SPHINX_ATBD_PDF=$(ATBD) $(SPHINXBUILD) -M latex "$(SOURCEDIR)" "$(BUILDDIR)/atbd-pdf" $(SPHINXOPTS) + @cd "$(ATBD_LATEX_DIR)" && \ + if command -v latexmk >/dev/null 2>&1; then \ + latexmk -xelatex -interaction=nonstopmode "$(ATBD).tex"; \ + elif command -v xelatex >/dev/null 2>&1; then \ + xelatex -interaction=nonstopmode "$(ATBD).tex"; \ + xelatex -interaction=nonstopmode "$(ATBD).tex"; \ + elif command -v pdflatex >/dev/null 2>&1; then \ + pdflatex -interaction=nonstopmode "$(ATBD).tex"; \ + pdflatex -interaction=nonstopmode "$(ATBD).tex"; \ + else \ + echo "No LaTeX engine found. Install MacTeX or TeX Live (pdflatex or latexmk)."; \ + echo "LaTeX sources are in $(ATBD_LATEX_DIR)/"; exit 1; \ + fi + @if [ -f "$(ATBD_LATEX_DIR)/$(ATBD).pdf" ]; then \ + mkdir -p _static/pdf; \ + cp "$(ATBD_LATEX_DIR)/$(ATBD).pdf" "$(ATBD_PDF)"; \ + echo "PDF written to $(ATBD_PDF) (linked from the ATBD sidebar and References page)"; \ + else \ + echo "PDF build failed (no output file). Check $(ATBD_LATEX_DIR)/$(ATBD).log"; exit 1; \ + fi + +# Catch-all: route any unknown target to sphinx-build via the M shortcut +%: + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) diff --git a/docs/_ext/atbd_doc_refs.py b/docs/_ext/atbd_doc_refs.py new file mode 100644 index 0000000..82691fb --- /dev/null +++ b/docs/_ext/atbd_doc_refs.py @@ -0,0 +1,259 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 +"""Link [ADn] / [RDn] codes, Fig.N, sec. X.Y.Z, and eq. X.Y references in the ATBD.""" + +from __future__ import annotations + +import os +from pathlib import PurePosixPath +from typing import Any + +from docutils import nodes +from sphinx.application import Sphinx +from sphinx.util.docutils import SphinxRole + +INTRO_DOC = "science-guide/atbd-l2-agb/01-introduction" + +# PDF figure number -> (source document, HTML anchor id on the figure wrapper). +FIGURE_TARGETS: dict[str, tuple[str, str]] = { + "1": ("science-guide/atbd-l2-agb/02-bps-agb-overview", "fig-atbd-agb-bps-scheme-tom"), + "2": ("science-guide/atbd-l2-agb/02-bps-agb-overview", "fig-atbd-agb-bps-scheme-int"), + "3": ("science-guide/atbd-l2-agb/02-bps-agb-overview", "fig-atbd-agb-workflow"), + "4": ("science-guide/atbd-l2-agb/03-ground-cancellation", "fig-atbd-agb-int-subset-7"), + "5": ("science-guide/atbd-l2-agb/03-ground-cancellation", "fig-atbd-agb-int-subset-8"), + "6": ("science-guide/atbd-l2-agb/03-ground-cancellation", "fig-atbd-agb-calibration"), + "7": ("science-guide/atbd-l2-agb/03-ground-cancellation", "fig-atbd-agb-gc-two"), + "8": ("science-guide/atbd-l2-agb/03-ground-cancellation", "fig-atbd-agb-gc-multi"), + "9": ("science-guide/atbd-l2-agb/03-ground-cancellation", "fig-atbd-agb-sigma-naught-normalisation"), + "10": ("science-guide/atbd-l2-agb/03-ground-cancellation", "fig-atbd-agb-geocoding"), + "11": ("science-guide/atbd-l2-agb/03-ground-cancellation", "fig-atbd-agb-geocoding-forest-point"), + "12": ("science-guide/atbd-l2-agb/04-agb-estimation", "fig-atbd-agb-processing-blocks"), + "13": ("science-guide/atbd-l2-agb/04-agb-estimation", "fig-atbd-agb-l2b-workflow"), + "14": ("science-guide/atbd-l2-agb/04-agb-estimation", "fig-atbd-agb-model-parameter-estimation"), + "15": ("science-guide/atbd-l2-agb/04-agb-estimation", "fig-atbd-agb-agb-mean-std-estimation"), +} + +# PDF section number -> (source document, HTML anchor id). +SECTION_TARGETS: dict[str, tuple[str, str]] = { + "3.3.4": ("science-guide/atbd-l2-agb/03-ground-cancellation", "sec-calibration-math"), + "3.4.3": ("science-guide/atbd-l2-agb/03-ground-cancellation", "sec-ground-cancellation-outputs"), + "3.4.4.1": ("science-guide/atbd-l2-agb/03-ground-cancellation", "sec-ground-cancellation-two-images"), + "3.4.4.2": ( + "science-guide/atbd-l2-agb/03-ground-cancellation", + "interpolated-ground-cancellation-single-reference", + ), + "3.4.4.2.1": ( + "science-guide/atbd-l2-agb/03-ground-cancellation", + "reference-acquisition-selection", + ), + "3.4.4.2.2": ( + "science-guide/atbd-l2-agb/03-ground-cancellation", + "multi-reference-selection", + ), + "4.1.4.2": ("science-guide/atbd-l2-agb/04-agb-estimation", "sec-training-data-consolidation"), + "4.1.4.3": ( + "science-guide/atbd-l2-agb/04-agb-estimation", + "regression-based-parameter-estimation", + ), +} + +# PDF equation number (\\tag{X.Y}) -> (source document, HTML anchor id on the equation block). +EQUATION_TARGETS: dict[str, tuple[str, str]] = { + "3.1": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-forest-coverage-check-1"), + "3.2": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-calibration-1"), + "3.3": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-calibration-2"), + "3.4": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-ground-cancellation-1"), + "3.5": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-ground-cancellation-2"), + "3.6": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-ground-cancellation-3"), + "3.7": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-ground-cancellation-4"), + "3.8": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-ground-cancellation-5"), + "3.9": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-ground-cancellation-6"), + "3.10": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-ground-cancellation-7"), + "3.11": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-ground-cancellation-8"), + "3.12": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-ground-cancellation-9"), + "3.13": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-sigma-naught-normalisation-1"), + "3.14": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-sigma-naught-normalisation-2"), + "3.15": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-sigma-naught-normalisation-3"), + "3.16": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-geocoding-1"), + "3.17": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-geocoding-2"), + "3.18": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-geocoding-3"), + "3.19": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-fnf-annotation-1"), + "3.20": ("science-guide/atbd-l2-agb/03-ground-cancellation", "equation-eq-fnf-annotation-2"), + "4.1": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-1"), + "4.2": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-2"), + "4.3": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-3"), + "4.4": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-4"), + "4.5": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-5"), + "4.6": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-6"), + "4.7": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-7"), + "4.8": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-8"), + "4.9": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-9"), + "4.10": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-10"), + "4.11": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-11"), + "4.12": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-12"), + "4.13": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-13"), + "4.14": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-14"), + "4.15": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-15"), + "4.16": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-16"), + "4.17": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-17"), + "4.18": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-18"), + "4.19": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-19"), + "4.20": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-20"), + "4.21": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-21"), + "4.22": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-22"), + "4.23": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-23"), + "4.24": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-24"), + "4.25": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-25"), + "4.26": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-26"), + "4.27": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-27"), + "4.28": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-28"), + "4.29": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-29"), + "4.30": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-30"), + "4.31": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-31"), + "4.32": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-32"), + "4.33": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-33"), + "4.34": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-34"), + "4.35": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-35"), + "4.36": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-36"), + "4.37": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-37"), + "4.38": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-38"), + "4.39": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-39"), + "4.40": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-40"), + "4.41": ("science-guide/atbd-l2-agb/04-agb-estimation", "equation-eq-model-parameter-estimation-41"), + "5.1": ("science-guide/atbd-l2-agb/05-appendix", "equation-eq-appendix-gn-1"), +} + + +def _doc_link_uri(from_docname: str, target_docname: str, anchor: str) -> str: + if from_docname == target_docname: + return f"#{anchor}" + rel = PurePosixPath( + os.path.relpath( + PurePosixPath(target_docname).with_suffix(".html"), + start=PurePosixPath(from_docname).parent, + ) + ) + return f"{rel.as_posix()}#{anchor}" + + +def _intro_link_uri(from_docname: str, anchor: str) -> str: + return _doc_link_uri(from_docname, INTRO_DOC, anchor) + + +class _DocCodeRole(SphinxRole): + prefix: str + + def run(self) -> tuple[list[nodes.Node], list[nodes.system_message]]: + num = self.text.strip() + if not num.isdigit(): + msg = self.inliner.reporter.error( + f"Invalid {self.prefix.upper()} number: {self.text!r}", + line=self.lineno, + ) + return [], [msg] + label = f"[{self.prefix.upper()}{num}]" + anchor = f"{self.prefix}{num}" + refuri = _intro_link_uri(self.env.docname, anchor) + node = nodes.reference(self.rawtext, label, refuri=refuri, internal=True) + return [node], [] + + +class ADRole(_DocCodeRole): + prefix = "ad" + + +class RDRole(_DocCodeRole): + prefix = "rd" + + +class FigRole(SphinxRole): + def run(self) -> tuple[list[nodes.Node], list[nodes.system_message]]: + num = self.text.strip() + if num not in FIGURE_TARGETS: + msg = self.inliner.reporter.error( + f"Unknown figure number: {self.text!r}", + line=self.lineno, + ) + return [], [msg] + target_doc, anchor = FIGURE_TARGETS[num] + label = f"Fig.{num}" + refuri = _doc_link_uri(self.env.docname, target_doc, anchor) + node = nodes.reference(self.rawtext, label, refuri=refuri, internal=True) + return [node], [] + + +class SecRole(SphinxRole): + """Link PDF-style section references, e.g. {sec}`3.4.4.1` or {sec}`Section|3.3.4`.""" + + def run(self) -> tuple[list[nodes.Node], list[nodes.system_message]]: + raw = self.text.strip() + if "|" in raw: + prefix, num = (part.strip() for part in raw.split("|", 1)) + else: + prefix, num = "sec.", raw + + prefix_lower = prefix.lower().rstrip(".") + if prefix_lower == "section": + label = f"Section {num}" + elif prefix_lower == "sec": + label = f"sec. {num}" + elif prefix.endswith("."): + label = f"{prefix} {num}" + else: + label = f"{prefix} {num}" + + if num not in SECTION_TARGETS: + msg = self.inliner.reporter.error( + f"Unknown section number: {num!r}", + line=self.lineno, + ) + return [], [msg] + + target_doc, anchor = SECTION_TARGETS[num] + refuri = _doc_link_uri(self.env.docname, target_doc, anchor) + node = nodes.reference(self.rawtext, label, refuri=refuri, internal=True) + return [node], [] + + +class EqRole(SphinxRole): + """Link PDF-style equation references, e.g. {eq}`3.17` or {eq}`eq|3.17` (no dot).""" + + def run(self) -> tuple[list[nodes.Node], list[nodes.system_message]]: + raw = self.text.strip() + if "|" in raw: + prefix, num = (part.strip() for part in raw.split("|", 1)) + else: + prefix, num = "eq.", raw + + prefix_lower = prefix.lower().rstrip(".") + if prefix_lower == "eq": + label = f"eq {num}" if prefix == "eq" else f"eq. {num}" + elif prefix.endswith("."): + label = f"{prefix} {num}" + else: + label = f"{prefix} {num}" + + if num not in EQUATION_TARGETS: + msg = self.inliner.reporter.error( + f"Unknown equation number: {num!r}", + line=self.lineno, + ) + return [], [msg] + + target_doc, anchor = EQUATION_TARGETS[num] + refuri = _doc_link_uri(self.env.docname, target_doc, anchor) + node = nodes.reference(self.rawtext, label, refuri=refuri, internal=True) + return [node], [] + + +def setup(app: Sphinx) -> dict[str, Any]: + app.add_role("ad", ADRole()) + app.add_role("rd", RDRole()) + app.add_role("fig", FigRole()) + app.add_role("sec", SecRole()) + app.add_role("eq", EqRole(), override=True) + return { + "version": "1.0", + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/docs/_ext/atbd_sidebar_toc.py b/docs/_ext/atbd_sidebar_toc.py new file mode 100644 index 0000000..bda9322 --- /dev/null +++ b/docs/_ext/atbd_sidebar_toc.py @@ -0,0 +1,192 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 +"""Collapsible PDF-style sidebar TOC and web-export PDF helpers for ATBD drafts.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +from docutils import nodes +from sphinx.util.docutils import SphinxDirective + +ATBD_ROOT = "science-guide/atbd-l2-agb" +# Web export built by `make atbd-pdf` → docs/_static/pdf/.pdf +ATBD_PDF_FILENAME = "atbd-l2-agb.pdf" +ATBD_PDF_BUILD_CMD = "make atbd-pdf" +ATBD_CHAPTERS = ( + f"{ATBD_ROOT}/01-introduction", + f"{ATBD_ROOT}/02-bps-agb-overview", + f"{ATBD_ROOT}/03-ground-cancellation", + f"{ATBD_ROOT}/04-agb-estimation", + f"{ATBD_ROOT}/05-appendix", + f"{ATBD_ROOT}/references", +) + + +def _section_tree(section: nodes.section, *, max_depth: int = 1) -> list[dict[str, Any]]: + """Build section entries up to max_depth (1 = x.x only, no x.x.x).""" + items: list[dict[str, Any]] = [] + for child in section.children: + if not isinstance(child, nodes.section): + continue + title_node = child.next_node(nodes.title) + title = title_node.astext() if title_node else "" + anchor = child["ids"][0] if child.get("ids") else "" + children: list[dict[str, Any]] = [] + if max_depth > 1: + children = _section_tree(child, max_depth=max_depth - 1) + items.append( + { + "title": title, + "anchor": anchor, + "children": children, + } + ) + return items + + +def _chapter_sections(doctree: nodes.document) -> list[dict[str, Any]]: + """Return top-level subsections (1.1, 2.1, 3.1, …) for a chapter doctree.""" + for child in doctree.children: + if not isinstance(child, nodes.section): + continue + return _section_tree(child) + return [] + + +def _build_atbd_sidebar_toc(env: Any) -> list[dict[str, Any]]: + toc: list[dict[str, Any]] = [] + for docname in ATBD_CHAPTERS: + if docname not in env.titles: + continue + doctree = env.get_doctree(docname) + toc.append( + { + "docname": docname, + "title": env.titles[docname].astext(), + "sections": _chapter_sections(doctree), + } + ) + return toc + + +def _atbd_pdf_path(app: Any) -> Path: + return Path(app.srcdir) / "_static" / "pdf" / ATBD_PDF_FILENAME + + +class atbd_pdf_export(nodes.General, nodes.Element): + """Placeholder for the HTML-only PDF download block on the References page.""" + + +class AtbdPdfExportDirective(SphinxDirective): + """Render a download link when the web-export PDF has been built.""" + + has_content = False + + def run(self) -> list[nodes.Node]: + return [atbd_pdf_export()] + + +def _visit_atbd_pdf_export_html(self: Any, node: atbd_pdf_export) -> None: + pdf_path = _atbd_pdf_path(self.builder.app) + if pdf_path.is_file(): + url = self.builder.get_relative_uri( + self.builder.current_docname, f"_static/pdf/{ATBD_PDF_FILENAME}" + ) + self.body.append( + '
' + '

Download web export

' + f'

' + f' ' + "Download this ATBD as PDF " + "(generated from the same MyST source as this website).

" + "

The " + '' + "official archival PDF on biomass-disc.info remains authoritative " + "until Aresys and ESA approve this web conversion.

" + "
" + ) + else: + self.body.append( + '
' + '

PDF export

' + f"

Build the web-export PDF from the docs/ directory with " + f"{ATBD_PDF_BUILD_CMD} (requires TeX Live or MacTeX). " + f"The file is written to _static/pdf/{ATBD_PDF_FILENAME} " + "and linked here once generated.

" + "
" + ) + raise nodes.SkipNode + + +def _visit_atbd_pdf_export_latex(_self: Any, _node: atbd_pdf_export) -> None: + raise nodes.SkipNode + + +def _inject_atbd_sidebar_toc( + app: Any, + pagename: str, + templatename: str, + context: dict[str, Any], + doctree: nodes.document, +) -> None: + if not pagename.startswith(ATBD_ROOT): + return + cache_key = "_atbd_sidebar_toc_cache" + if not hasattr(app.env, cache_key): + setattr(app.env, cache_key, _build_atbd_sidebar_toc(app.env)) + context["atbd_sidebar_toc"] = getattr(app.env, cache_key) + context["atbd_sidebar_current"] = pagename + index_doc = f"{ATBD_ROOT}/index" + context["atbd_index_docname"] = index_doc + context["atbd_index_title"] = ( + app.env.titles[index_doc].astext() + if index_doc in app.env.titles + else "Above-Ground Biomass Product ATBD" + ) + pdf_path = _atbd_pdf_path(app) + if pdf_path.is_file(): + context["atbd_pdf_url"] = app.builder.get_relative_uri( + pagename, f"_static/pdf/{ATBD_PDF_FILENAME}" + ) + else: + context["atbd_pdf_url"] = None + + +def _silence_sidebar_wildcard_warnings(_app: Any, _config: Any) -> None: + """ATBD pages intentionally match both ** and science-guide/atbd-l2-agb/*.""" + import logging + + class _Filter(logging.Filter): + def filter(self, record: logging.LogRecord) -> bool: + try: + msg = record.getMessage() + except TypeError: + # Some Sphinx warnings use literal % in the message without args. + msg = str(record.msg) + return not ( + "matches two" in msg + and ("html_sidebars" in msg or "secondary_sidebar_items" in msg) + ) + + logging.getLogger("sphinx").addFilter(_Filter()) + + +def setup(app: Any) -> dict[str, Any]: + app.add_node( + atbd_pdf_export, + html=(_visit_atbd_pdf_export_html, None), + latex=(_visit_atbd_pdf_export_latex, None), + text=(_visit_atbd_pdf_export_latex, None), + override=True, + ) + app.add_directive("atbd-pdf-export", AtbdPdfExportDirective) + app.connect("config-inited", _silence_sidebar_wildcard_warnings) + app.connect("html-page-context", _inject_atbd_sidebar_toc) + return { + "version": "1.0", + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/docs/_ext/bps_version.py b/docs/_ext/bps_version.py new file mode 100644 index 0000000..0fd3175 --- /dev/null +++ b/docs/_ext/bps_version.py @@ -0,0 +1,149 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 +"""Resolve BPS release version from git tags or GitHub.""" + +from __future__ import annotations + +import json +import os +import re +import subprocess +import urllib.error +import urllib.request +from pathlib import Path +from typing import Any + +_BPS_TAG_RE = re.compile(r"^bps-v(?P.+)$", re.IGNORECASE) +_GITHUB_TAGS_API = "https://api.github.com/repos/BioPAL/BPS/tags?per_page=100" + + +def _parse_bps_tag(tag: str) -> tuple[str, str] | None: + match = _BPS_TAG_RE.match(tag.strip()) + if not match: + return None + version = match.group("version") + return version, f"bps-v{version}" + + +def _from_env() -> tuple[str, str] | None: + raw = os.environ.get("BPS_RELEASE_VERSION") or os.environ.get("DOCS_BPS_VERSION") + if not raw: + return None + raw = raw.strip() + if raw.lower().startswith("bps-v"): + parsed = _parse_bps_tag(raw) + return parsed + version = raw.lstrip("v") + return version, f"bps-v{version}" + + +def _from_git(repo_root: Path) -> tuple[str, str] | None: + try: + proc = subprocess.run( + ["git", "tag", "-l", "bps-v*", "--sort=-v:refname"], + check=True, + capture_output=True, + text=True, + timeout=10, + cwd=repo_root, + ) + except (OSError, subprocess.SubprocessError): + return None + + for line in proc.stdout.splitlines(): + parsed = _parse_bps_tag(line) + if parsed: + return parsed + return None + + +def _from_github_api() -> tuple[str, str] | None: + try: + request = urllib.request.Request( + _GITHUB_TAGS_API, + headers={ + "Accept": "application/vnd.github+json", + "User-Agent": "BioPAL-Sphinx-Docs", + }, + ) + with urllib.request.urlopen(request, timeout=8) as response: + payload = json.load(response) + except (OSError, urllib.error.URLError, json.JSONDecodeError, ValueError): + return None + + candidates: list[tuple[tuple[int, ...], str, str]] = [] + for item in payload: + if not isinstance(item, dict): + continue + parsed = _parse_bps_tag(str(item.get("name", ""))) + if not parsed: + continue + version, tag = parsed + parts = tuple(int(part) for part in version.split(".")) + candidates.append((parts, version, tag)) + + if not candidates: + return None + + _, version, tag = max(candidates, key=lambda item: item[0]) + return version, tag + + +def resolve_bps_release( + *, + docs_dir: Path, + fallback_version: str = "unknown", +) -> tuple[str, str]: + """Return (display_version, tag_name) e.g. ('4.4.5', 'bps-v4.4.5').""" + env = _from_env() + if env: + return env + + repo_root = docs_dir.parent + git = _from_git(repo_root) + if git: + return git + + remote = _from_github_api() + if remote: + return remote + + return fallback_version, f"bps-v{fallback_version}" + + +def _hero_meta_html(config: Any) -> str: + version = config.bps_version + url = config.bps_releases_url + return ( + f'

' + f'' + f"BPS v{version}" + f'Processing Suite release' + f"

" + ) + + +_BADGE_PLACEHOLDER = "" + + +def _inject_bps_version_badge(app: Any, docname: str, source: list[str]) -> None: + if docname != "index" or _BADGE_PLACEHOLDER not in source[0]: + return + source[0] = source[0].replace(_BADGE_PLACEHOLDER, _hero_meta_html(app.config)) + + +def setup(app: Any) -> dict[str, Any]: + docs_dir = Path(app.confdir) + version, tag = resolve_bps_release(docs_dir=docs_dir) + releases_url = f"https://github.com/BioPAL/BPS/releases/tag/{tag}" + + app.add_config_value("bps_version", version, "html") + app.add_config_value("bps_tag", tag, "html") + app.add_config_value("bps_releases_url", releases_url, "html") + app.connect("source-read", _inject_bps_version_badge) + + return { + "version": "1.0", + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/docs/_ext/meeting_status.py b/docs/_ext/meeting_status.py new file mode 100644 index 0000000..32ae6b5 --- /dev/null +++ b/docs/_ext/meeting_status.py @@ -0,0 +1,105 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 +"""Render meeting status badges from presentations//meeting.yaml.""" + +from __future__ import annotations + +import re +from pathlib import Path +from typing import Any + +import yaml +from docutils import nodes +from docutils.parsers.rst import directives +from sphinx.util.docutils import SphinxDirective + +_PLACEHOLDER_RE = re.compile( + r"", + re.IGNORECASE, +) + +STATUSES: dict[str, dict[str, str]] = { + "planned": { + "label": "Planned", + "modifier": "planned", + "icon": "fa-regular fa-calendar-check", + }, + "held": { + "label": "Held", + "modifier": "held", + "icon": "fa-solid fa-circle-check", + }, + "cancelled": { + "label": "Cancelled", + "modifier": "cancelled", + "icon": "fa-solid fa-circle-xmark", + }, +} + + +def _load_status(confdir: Path, deck_id: str) -> str: + path = confdir / "presentations" / deck_id / "meeting.yaml" + if not path.is_file(): + raise FileNotFoundError(f"Missing meeting config: {path}") + data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + status = str(data.get("status", "")).strip().lower() + if status not in STATUSES: + allowed = ", ".join(sorted(STATUSES)) + raise ValueError(f"Invalid status {status!r} in {path.name} — use one of: {allowed}") + return status + + +def render_badge(confdir: Path, deck_id: str, *, inline: bool = False) -> str: + status = _load_status(confdir, deck_id) + meta = STATUSES[status] + modifier = meta["modifier"] + size_class = " bps-meeting-status--inline" if inline else "" + return ( + f'' + f'' + f'{meta["label"]}' + f"" + ) + + +class MeetingStatusDirective(SphinxDirective): + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + has_content = False + option_spec = {"inline": directives.flag} + + def run(self) -> list[nodes.Node]: + deck_id = self.arguments[0].strip() + inline = "inline" in self.options + try: + html = render_badge(Path(self.env.srcdir), deck_id, inline=inline) + except (FileNotFoundError, ValueError) as exc: + raise self.error(str(exc)) from exc + raw = nodes.raw("", html, format="html") + return [raw] + + +def _inject_meeting_status_placeholders(app: Any, docname: str, source: list[str]) -> None: + confdir = Path(app.srcdir) + + def repl(match: re.Match[str]) -> str: + deck_id = match.group(1) + inline = match.group(2) is not None + try: + return render_badge(confdir, deck_id, inline=inline) + except (FileNotFoundError, ValueError) as exc: + app.warn(str(exc)) + return match.group(0) + + source[0] = _PLACEHOLDER_RE.sub(repl, source[0]) + + +def setup(app: Any) -> dict[str, Any]: + app.add_directive("meeting-status", MeetingStatusDirective) + app.connect("source-read", _inject_meeting_status_placeholders) + return { + "version": "1.0", + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/docs/_ext/science_guide_nav.py b/docs/_ext/science_guide_nav.py new file mode 100644 index 0000000..0418d59 --- /dev/null +++ b/docs/_ext/science_guide_nav.py @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 +"""Science Guide hub sidebar: list of web ATBDs (not chapter outlines).""" + +from __future__ import annotations + +from typing import Any + +from docutils import nodes +from sphinx.util.docutils import SphinxDirective + +SCIENCE_GUIDE_HUB = "science-guide/index" + +# Top-level web ATBD conversions shown on the Science Guide hub sidebar. +SCIENCE_GUIDE_ATBDS: tuple[dict[str, str], ...] = ( + { + "title": "L2 AGB ATBD", + "docname": "science-guide/atbd-l2-agb/index", + "reference": "BIO-BPS-AGB-ATBD-ARE-024912", + }, +) + + +class atbd_logo_banner(nodes.General, nodes.Element): + """Placeholder for the ESA / Aresys partner logo banner.""" + + +class AtbdLogoBannerDirective(SphinxDirective): + """ESA / Aresys partner logo banner with paths resolved per page.""" + + has_content = False + + def run(self) -> list[nodes.Node]: + return [atbd_logo_banner()] + + +def _static_uri(docname: str, asset: str) -> str: + """Relative path from a built HTML page to a file under _static/.""" + depth = len(docname.split("/")) - 1 + return f"{'../' * depth}{asset}" + + +def _visit_atbd_logo_banner_html(self: Any, node: atbd_logo_banner) -> None: + docname = self.builder.current_docname + + def static(path: str) -> str: + return _static_uri(docname, path) + + self.body.append( + '
' + '" + "
" + ) + raise nodes.SkipNode + + +def _skip_atbd_logo_banner(_self: Any, _node: atbd_logo_banner) -> None: + raise nodes.SkipNode + + +def _inject_science_guide_nav( + app: Any, + pagename: str, + templatename: str, + context: dict[str, Any], + doctree: Any, +) -> None: + if pagename != SCIENCE_GUIDE_HUB: + return + context["science_guide_atbds"] = SCIENCE_GUIDE_ATBDS + + +def setup(app: Any) -> dict[str, Any]: + app.add_node( + atbd_logo_banner, + html=(_visit_atbd_logo_banner_html, None), + latex=(_skip_atbd_logo_banner, None), + text=(_skip_atbd_logo_banner, None), + override=True, + ) + app.add_directive("atbd-logo-banner", AtbdLogoBannerDirective) + app.connect("html-page-context", _inject_science_guide_nav) + return { + "version": "1.0", + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/docs/_extra_static/images/CI-CD-Annexe_Light.drawio.svg b/docs/_extra_static/images/CI-CD-Annexe_Light.drawio.svg new file mode 100644 index 0000000..f1befe7 --- /dev/null +++ b/docs/_extra_static/images/CI-CD-Annexe_Light.drawio.svg @@ -0,0 +1,4 @@ + + + +
Yes
Any file matches 
locked paths 
or 
SME paths?
Load .github/tier-policy.yml 
from the PR's base branch SHA (main / develop / release never PR head)
List changes files
No
Yes
Baseline Test Failed ?
(diff vs nominal ref)
Baseline reference check 
pytest 
Yes
Dependabot major 
bump detected?
No
Tier 0
baseline only 
merge allowed
Yes
No
Run Heavy CI/CD
Manually set to true 
Tier 2
Heavy required 
 explicit manual upclass
Tier 1
Extended required merge blocked until green
Automatic Tier Triage
1 · Dependabot
Schedules and opens automated dependency-update PRs
5 · CI
Runs the baseline
extended/heavy pipeline on every PR
4 · Pytest.ini
Declares the test markers used by CI (baseline · unit · extended · heavy)
2 · CODEOWNERS
Maps repo paths to required reviewers (per-processor SME ownership)
3 · Tier Policy
Defines locked paths, SME-owned paths, and Dependabot rule for the tier decision
0 · PR Template
Author checklist shown when opening a PR (DCO, scope, tier guidance)
7 · pyproject
Package metadata + build config
11 · LICENCES
Full texts of referenced licenses
10 · REUSE
License compliance config (SPDX)
8 · tests
Pytest suites by marker (baseline / extended / heavy)
9 · pre commit
Code-quality hooks (Ruff, mypy)
6 · VERSION
Project version (locked path → tier 1)
Input Files
🔒 Security principle
Judge from base
The PR cannot modify its own judge. Policy, baseline references,
and harness are always read from the base branch SHA.
1
5
3
13 · baseline-build
Built Python package · sdist (.tar.gz) + wheel (.whl) to validate installability
17 · val-report
extended HTML validation report · reference artifact for SME review
16 · extended-cov
Code coverage XML report produced during the Extended stage
14 · baseline-docs
Sphinx documentation rendered as static HTML (docs/_build/html/)
15 · extended-junit
Extended test results in JUnit XML format for automated parsing
12 · bandit-report
JSON report from the Bandit static security analysis 
19 · heavy-cov
Browsable HTML coverage report produced during the Heavy stage
20 · val-report-2
Heavy scientific non-regression results · XML + coverage
18 · heavy-junit
Heavy test results in JUnit XML format
Generated Files
\ No newline at end of file diff --git a/docs/_extra_static/images/CI-CD-Annexe_Shadow.drawio.svg b/docs/_extra_static/images/CI-CD-Annexe_Shadow.drawio.svg new file mode 100644 index 0000000..598cd72 --- /dev/null +++ b/docs/_extra_static/images/CI-CD-Annexe_Shadow.drawio.svg @@ -0,0 +1,4 @@ + + + +
Yes
Any file matches 
locked paths 
or 
SME paths?
Load .github/tier-policy.yml 
from the PR's base branch SHA (main / develop / release never PR head)
List changes files
No
Yes
Baseline Test Failed ?
(diff vs nominal ref)
Baseline reference check 
pytest 
Yes
Dependabot major 
bump detected?
No
Tier 0
baseline only 
merge allowed
Yes
No
Run Heavy CI/CD
Manually set to true 
Tier 2
Heavy required 
 explicit manual upclass
Tier 1
Extended required merge blocked until green
Automatic Tier Triage
1 · Dependabot
Schedules and opens automated dependency-update PRs
5 · CI
Runs the baseline
extended/heavy pipeline on every PR
4 · Pytest.ini
Declares the test markers used by CI (baseline · unit · extended · heavy)
2 · CODEOWNERS
Maps repo paths to required reviewers (per-processor SME ownership)
3 · Tier Policy
Defines locked paths, SME-owned paths, and Dependabot rule for the tier decision
0 · PR Template
Author checklist shown when opening a PR (DCO, scope, tier guidance)
7 · pyproject
Package metadata + build config
11 · LICENCES
Full texts of referenced licenses
10 · REUSE
License compliance config (SPDX)
8 · tests
Pytest suites by marker (baseline / extended / heavy)
9 · pre commit
Code-quality hooks (Ruff, mypy)
6 · VERSION
Project version (locked path → tier 1)
Input Files
🔒 Security principle
Judge from base
The PR cannot modify its own judge. Policy, baseline references,
and harness are always read from the base branch SHA.
1
5
3
13 · baseline-build
Built Python package · sdist (.tar.gz) + wheel (.whl) to validate installability
17 · val-report
extended HTML validation report · reference artifact for SME review
16 · extended-cov
Code coverage XML report produced during the Extended stage
14 · baseline-docs
Sphinx documentation rendered as static HTML (docs/_build/html/)
15 · extended-junit
Extended test results in JUnit XML format for automated parsing
12 · bandit-report
JSON report from the Bandit static security analysis 
19 · heavy-cov
Browsable HTML coverage report produced during the Heavy stage
20 · val-report-2
Heavy scientific non-regression results · XML + coverage
18 · heavy-junit
Heavy test results in JUnit XML format
Generated Files
\ No newline at end of file diff --git a/docs/_extra_static/images/CI-CD-Contribution_Light.drawio.svg b/docs/_extra_static/images/CI-CD-Contribution_Light.drawio.svg new file mode 100644 index 0000000..2f702d5 --- /dev/null +++ b/docs/_extra_static/images/CI-CD-Contribution_Light.drawio.svg @@ -0,0 +1,4 @@ + + + +
1.0.0
Main
2.1.0
Hotfix
Develop
2.0.1
2.0.1
2.0.0
Release
processor/feature
processor/feature
Local Dev
Release Fixes
Fork
Fork
Pull Request
PR
Fork
Fork
PR
PR
GitHub Contribution Process
ESA

External contributors only PR into develop

Promotion to release and main is reserved to maintainers.

\ No newline at end of file diff --git a/docs/_extra_static/images/CI-CD-Contribution_Shadow.drawio.svg b/docs/_extra_static/images/CI-CD-Contribution_Shadow.drawio.svg new file mode 100644 index 0000000..44d3a59 --- /dev/null +++ b/docs/_extra_static/images/CI-CD-Contribution_Shadow.drawio.svg @@ -0,0 +1,4 @@ + + + +
1.0.0
Main
2.1.0
Hotfix
Develop
2.0.1
2.0.1
2.0.0
Release
processor/feature
processor/feature
Local Dev
Release Fixes
Fork
Fork
Pull Request
PR
Fork
Fork
PR
PR
GitHub Contribution Process
ESA

External contributors only PR into develop

Promotion to release and main is reserved to maintainers.

\ No newline at end of file diff --git a/docs/_extra_static/images/CI-CD-Workflow.drawio.svg b/docs/_extra_static/images/CI-CD-Workflow.drawio.svg new file mode 100644 index 0000000..d0971f3 --- /dev/null +++ b/docs/_extra_static/images/CI-CD-Workflow.drawio.svg @@ -0,0 +1,4 @@ + + + +
Branch protection (main · release · develop)
Direct pushes are blocked on protected branches
Every change reaches CI through a pull request
Entry Point
External contributor standard path
Fork the repo → feature branch on the fork
Open a PR from the fork into upstream develop
pull_request event
Fires automatically on every PR to 
main · release · develop 
Baseline Checks
Success
Failure
Failure
Remediation Fix failing job · repush

Automatic
Tier Triage
Tier 0
Tier 1
Tier 2








Timeout 30 min
pytest -m extended on tests/extended/








Timeout 60 min
pytest -m heavy on tests/heavy/
Success
Failure
Failure
Success
Final required status check (branch protection)

Tier 0 → baseline must pass
Tier 1 → baseline + extended must pass
Tier 2 → baseline + extended + heavy must pass





Promotion PR develop -> Main
Merge to develop / ci-gate green
Review Squash merge

Tier 0 :        Maintener Approval
Tier 1 :       Maintener +      SME Approval
Tier 2 :       Maintener +       SME +      ESA Approval
Merge to main / ci-gate green 

Tier 2 :       Maintener +       SME +      ESA Approval
Manual Workflow Triggering 
Heavy CI/CD set to true/false
pr-guidance-bot : Posts a sticky comment on the PR (always runs)
Tells the author: which stages passed, which tier was decided,
why this tier, and what to do next
pytest -m extended on tests/extended/
Extended CI/CD
Extended + Heavy CI/CD
CI/CD Gate [BLOCKING]
Merge
Aggregates the 10 baseline jobs into one verdict
Passes only when every required job is green
Signals "baseline OK" to tier-decision downstream
Baseline Gate [BLOCKING]
5
HTML Validation Report uploaded
HTML Validation Report 2 uploaded
Context
Resolves pr_number · head_sha · base_sha · base_ref · run_heavy
Feeds every downstream job
0
4
9
10
11
5
8
7
1
5
4
8
5
4
8
5
5
2
License & copyright check (REUSE, SPDX headers)
Baseline reference check pytest 
Code quality checks(Ruff, mypy, pre-commit hooks)
Security static analysis(Bandit)
Package build validation(sdist + wheel)
Sensitive file policy check(blocked names + large files)
Unit test suite(currently non-blocking during migration)
Documentation build(Sphinx HTML, currently non-blocking)
Dependabot governance check(config exists + major update signal)
DCO Check 
(signed commit)
\ No newline at end of file diff --git a/docs/_extra_static/images/CI-CD-Workflow_Shadow.drawio.svg b/docs/_extra_static/images/CI-CD-Workflow_Shadow.drawio.svg new file mode 100644 index 0000000..50175bb --- /dev/null +++ b/docs/_extra_static/images/CI-CD-Workflow_Shadow.drawio.svg @@ -0,0 +1,4 @@ + + + +
Branch protection (main · release · develop)
Direct pushes are blocked on protected branches
Every change reaches CI through a pull request
Entry Point
External contributor standard path
Fork the repo → feature branch on the fork
Open a PR from the fork into upstream develop
pull_request event
Fires automatically on every PR to 
main · release · develop 
Baseline Checks
Success
Failure
Failure
Remediation Fix failing job · repush

Automatic
Tier Triage
Tier 0
Tier 1
Tier 2








Timeout 30 min
pytest -m extended on tests/extended/








Timeout 60 min
pytest -m heavy on tests/heavy/
Success
Failure
Failure
Success
Final required status check (branch protection)

Tier 0 → baseline must pass
Tier 1 → baseline + extended must pass
Tier 2 → baseline + extended + heavy must pass





Promotion PR develop -> Main
Merge to develop / ci-gate green
Review Squash merge

Tier 0 :        Maintener Approval
Tier 1 :       Maintener +      SME Approval
Tier 2 :       Maintener +       SME +      ESA Approval
Merge to main / ci-gate green 

Tier 2 :       Maintener +       SME +      ESA Approval
Manual Workflow Triggering 
Heavy CI/CD set to true/false
pr-guidance-bot : Posts a sticky comment on the PR (always runs)
Tells the author: which stages passed, which tier was decided,
why this tier, and what to do next
pytest -m extended on tests/extended/
Extended CI/CD
Extended + Heavy CI/CD
CI/CD Gate [BLOCKING]
Merge
Aggregates the 10 baseline jobs into one verdict
Passes only when every required job is green
Signals "baseline OK" to tier-decision downstream
Baseline Gate [BLOCKING]
5
HTML Validation Report uploaded
HTML Validation Report 2 uploaded
Context
Resolves pr_number · head_sha · base_sha · base_ref · run_heavy
Feeds every downstream job
0
4
9
10
11
5
8
7
1
5
4
8
5
4
8
5
5
2
License & copyright check (REUSE, SPDX headers)
Baseline reference check pytest 
Code quality checks(Ruff, mypy, pre-commit hooks)
Security static analysis(Bandit)
Package build validation(sdist + wheel)
Sensitive file policy check(blocked names + large files)
Unit test suite(currently non-blocking during migration)
Documentation build(Sphinx HTML, currently non-blocking)
Dependabot governance check(config exists + major update signal)
DCO Check 
(signed commit)
\ No newline at end of file diff --git a/docs/_extra_static/images/CI-CD_light.svg b/docs/_extra_static/images/CI-CD_light.svg new file mode 100644 index 0000000..9be3ae4 --- /dev/null +++ b/docs/_extra_static/images/CI-CD_light.svg @@ -0,0 +1,4 @@ + + + +
Branch protection (main · release · develop)
Direct pushes are blocked on protected branches
Every change reaches CI through a pull request
Entry Point
External contributor standard path
Fork the repo → feature branch on the fork
Open a PR from the fork into upstream develop
pull_request event
Fires automatically on every PR to 
main · release · develop 
Baseline Checks
Success
Failure
Failure
Remediation Fix failing job · repush

Automatic
Tier Triage
Yes
Any file matches 
locked paths 
or 
SME paths?
Load .github/tier-policy.yml 
from the PR's base branch SHA (main / develop / release never PR head)
List changes files
No
Yes
Baseline Test Failed ?
(diff vs nominal ref)
Baseline reference check 
pytest 
Yes
Dependabot major 
bump detected?
No
Tier 0
baseline only 
merge allowed
Yes
No
Run Heavy CI/CD
Manually set to true 
Tier 2
Heavy required 
 explicit manual upclass
Tier 1
Extended required merge blocked until green
Tier 0
Tier 1
Tier 2








Timeout 30 min
pytest -m extended on tests/extended/








Timeout 60 min
pytest -m heavy on tests/heavy/
Success
Failure
Failure
Success
Final required status check (branch protection)

Tier 0 → baseline must pass
Tier 1 → baseline + extended must pass
Tier 2 → baseline + extended + heavy must pass





Promotion PR develop -> Main
Merge to develop / ci-gate green
Review Squash merge

Tier 0 :        Maintener Approval
Tier 1 :       Maintener +      SME Approval
Tier 2 :       Maintener +       SME +      ESA Approval
Merge to main / ci-gate green 

Tier 2 :       Maintener +       SME +      ESA Approval
Manual Workflow Triggering 
Heavy CI/CD set to true/false
pr-guidance-bot : Posts a sticky comment on the PR (always runs)
Tells the author: which stages passed, which tier was decided,
why this tier, and what to do next
pytest -m extended on tests/extended/
Extended CI/CD
Extended + Heavy CI/CD
CI/CD Gate [BLOCKING]
Merge
Aggregates the 10 baseline jobs into one verdict
Passes only when every required job is green
Signals "baseline OK" to tier-decision downstream
Baseline Gate [BLOCKING]
5
Automatic Tier Triage
1 · Dependabot
Schedules and opens automated dependency-update PRs
HTML Validation Report uploaded
HTML Validation Report 2 uploaded
5 · CI
Runs the baseline
extended/heavy pipeline on every PR
4 · Pytest.ini
Declares the test markers used by CI (baseline · unit · extended · heavy)
2 · CODEOWNERS
Maps repo paths to required reviewers (per-processor SME ownership)
3 · Tier Policy
Defines locked paths, SME-owned paths, and Dependabot rule for the tier decision
0 · PR Template
Author checklist shown when opening a PR (DCO, scope, tier guidance)
7 · pyproject
Package metadata + build config
11 · LICENCES
Full texts of referenced licenses
10 · REUSE
License compliance config (SPDX)
8 · tests
Pytest suites by marker (baseline / extended / heavy)
9 · pre commit
Code-quality hooks (Ruff, mypy)
6 · VERSION
Project version (locked path → tier 1)
Input Files
Context
Resolves pr_number · head_sha · base_sha · base_ref · run_heavy
Feeds every downstream job
🔒 Security principle
Judge from base
The PR cannot modify its own judge. Policy, baseline references,
and harness are always read from the base branch SHA.
1
0
5
3
4
9
10
11
5
8
7
1
5
4
8
5
4
8
5
5
2
13 · baseline-build
Built Python package · sdist (.tar.gz) + wheel (.whl) to validate installability
17 · val-report
extended HTML validation report · reference artifact for SME review
16 · extended-cov
Code coverage XML report produced during the Extended stage
14 · baseline-docs
Sphinx documentation rendered as static HTML (docs/_build/html/)
15 · extended-junit
Extended test results in JUnit XML format for automated parsing
12 · bandit-report
JSON report from the Bandit static security analysis 
19 · heavy-cov
Browsable HTML coverage report produced during the Heavy stage
20 · val-report-2
Heavy scientific non-regression results · XML + coverage
18 · heavy-junit
Heavy test results in JUnit XML format
Generated Files
License & copyright check (REUSE, SPDX headers)
Baseline reference check pytest 
Code quality checks(Ruff, mypy, pre-commit hooks)
Security static analysis(Bandit)
Package build validation(sdist + wheel)
Sensitive file policy check(blocked names + large files)
Unit test suite(currently non-blocking during migration)
Documentation build(Sphinx HTML, currently non-blocking)
Dependabot governance check(config exists + major update signal)
DCO Check 
(signed commit)
1.0.0
Main
2.1.0
Hotfix
Develop
2.0.1
2.0.1
2.0.0
Release
processor/feature
processor/feature
Local Dev
Release Fixes
Fork
Fork
Pull Request
PR
Fork
Fork
PR
PR
GitHub Contribution Process
ESA

External contributors only PR into develop

Promotion to release and main is reserved to maintainers.

\ No newline at end of file diff --git a/docs/_extra_static/images/CI-CD_shadow.svg b/docs/_extra_static/images/CI-CD_shadow.svg new file mode 100644 index 0000000..ebf3095 --- /dev/null +++ b/docs/_extra_static/images/CI-CD_shadow.svg @@ -0,0 +1,4 @@ + + + +
Branch protection (main · release · develop)
Direct pushes are blocked on protected branches
Every change reaches CI through a pull request
Entry Point
External contributor standard path
Fork the repo → feature branch on the fork
Open a PR from the fork into upstream develop
pull_request event
Fires automatically on every PR to 
main · release · develop 
Baseline Checks
Success
Failure
Failure
Remediation Fix failing job · repush

Automatic
Tier Triage
Yes
Any file matches 
locked paths 
or 
SME paths?
Load .github/tier-policy.yml 
from the PR's base branch SHA (main / develop / release never PR head)
List changes files
No
Yes
Baseline Test Failed ?
(diff vs nominal ref)
Baseline reference check 
pytest 
Yes
Dependabot major 
bump detected?
No
Tier 0
baseline only 
merge allowed
Yes
No
Run Heavy CI/CD
Manually set to true 
Tier 2
Heavy required 
 explicit manual upclass
Tier 1
Extended required merge blocked until green
Tier 0
Tier 1
Tier 2








Timeout 30 min
pytest -m extended on tests/extended/








Timeout 60 min
pytest -m heavy on tests/heavy/
Success
Failure
Failure
Success
Final required status check (branch protection)

Tier 0 → baseline must pass
Tier 1 → baseline + extended must pass
Tier 2 → baseline + extended + heavy must pass





Promotion PR develop -> Main
Merge to develop / ci-gate green
Review Squash merge

Tier 0 :        Maintener Approval
Tier 1 :       Maintener +      SME Approval
Tier 2 :       Maintener +       SME +      ESA Approval
Merge to main / ci-gate green 

Tier 2 :       Maintener +       SME +      ESA Approval
Manual Workflow Triggering 
Heavy CI/CD set to true/false
pr-guidance-bot : Posts a sticky comment on the PR (always runs)
Tells the author: which stages passed, which tier was decided,
why this tier, and what to do next
pytest -m extended on tests/extended/
Extended CI/CD
Extended + Heavy CI/CD
CI/CD Gate [BLOCKING]
Merge
Aggregates the 10 baseline jobs into one verdict
Passes only when every required job is green
Signals "baseline OK" to tier-decision downstream
Baseline Gate [BLOCKING]
5
Automatic Tier Triage
1 · Dependabot
Schedules and opens automated dependency-update PRs
HTML Validation Report uploaded
HTML Validation Report 2 uploaded
5 · CI
Runs the baseline
extended/heavy pipeline on every PR
4 · Pytest.ini
Declares the test markers used by CI (baseline · unit · extended · heavy)
2 · CODEOWNERS
Maps repo paths to required reviewers (per-processor SME ownership)
3 · Tier Policy
Defines locked paths, SME-owned paths, and Dependabot rule for the tier decision
0 · PR Template
Author checklist shown when opening a PR (DCO, scope, tier guidance)
7 · pyproject
Package metadata + build config
11 · LICENCES
Full texts of referenced licenses
10 · REUSE
License compliance config (SPDX)
8 · tests
Pytest suites by marker (baseline / extended / heavy)
9 · pre commit
Code-quality hooks (Ruff, mypy)
6 · VERSION
Project version (locked path → tier 1)
Input Files
Context
Resolves pr_number · head_sha · base_sha · base_ref · run_heavy
Feeds every downstream job
🔒 Security principle
Judge from base
The PR cannot modify its own judge. Policy, baseline references,
and harness are always read from the base branch SHA.
1
0
5
3
4
9
10
11
5
8
7
1
5
4
8
5
4
8
5
5
2
13 · baseline-build
Built Python package · sdist (.tar.gz) + wheel (.whl) to validate installability
17 · val-report
extended HTML validation report · reference artifact for SME review
16 · extended-cov
Code coverage XML report produced during the Extended stage
14 · baseline-docs
Sphinx documentation rendered as static HTML (docs/_build/html/)
15 · extended-junit
Extended test results in JUnit XML format for automated parsing
12 · bandit-report
JSON report from the Bandit static security analysis 
19 · heavy-cov
Browsable HTML coverage report produced during the Heavy stage
20 · val-report-2
Heavy scientific non-regression results · XML + coverage
18 · heavy-junit
Heavy test results in JUnit XML format
Generated Files
License & copyright check (REUSE, SPDX headers)
Baseline reference check pytest 
Code quality checks(Ruff, mypy, pre-commit hooks)
Security static analysis(Bandit)
Package build validation(sdist + wheel)
Sensitive file policy check(blocked names + large files)
Unit test suite(currently non-blocking during migration)
Documentation build(Sphinx HTML, currently non-blocking)
Dependabot governance check(config exists + major update signal)
DCO Check 
(signed commit)
1.0.0
Main
2.1.0
Hotfix
Develop
2.0.1
2.0.1
2.0.0
Release
processor/feature
processor/feature
Local Dev
Release Fixes
Fork
Fork
Pull Request
PR
Fork
Fork
PR
PR
GitHub Contribution Process
ESA

External contributors only PR into develop

Promotion to release and main is reserved to maintainers.

\ No newline at end of file diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..929c95f --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,1534 @@ +/* + * SPDX-FileCopyrightText: 2026 European Space Agency (ESA) + * SPDX-License-Identifier: Apache-2.0 + * + * Custom overrides for the PyData Sphinx Theme on the BIOMASS BPS docs. + */ + +/* Hide the RTD ethical-ads placeholder if the theme still injects the slot. + Without a publisher ID it renders as a large violet block in the sidebar. */ +#ethical-ad-placement { + display: none !important; +} + +/* ----- Top banner: return link + announcement -------------------------------- */ +.bps-top-banner { + width: 100%; + background: var(--bps-announcement-gradient); + color: var(--bps-announcement-text); + padding: 0.55rem 1rem; + border-bottom: 1px solid var(--bps-announcement-border); + /* Prevent PyData/Bootstrap link tokens from tinting the return pill on hover */ + --pst-color-link: var(--bps-announcement-text); + --pst-color-link-hover: var(--bps-announcement-text); + --pst-color-link-higher-contrast: var(--bps-announcement-text); +} + +.bps-top-banner__inner { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 0.65rem 1rem; + max-width: 100%; +} + +.bps-top-banner a.bps-return-link, +.bps-top-banner a.bps-return-link:link, +.bps-top-banner a.bps-return-link:visited { + display: inline-flex; + align-items: center; + gap: 0.4rem; + box-sizing: border-box; + min-height: 2rem; + padding: 0.35rem 0.9rem; + font-size: 0.8125rem; + font-weight: 600; + line-height: 1; + color: var(--bps-announcement-text) !important; + text-decoration: none !important; + text-decoration-thickness: 0 !important; + background-color: rgba(255, 255, 255, 0.14); + border: 1px solid rgba(255, 255, 255, 0.35); + border-radius: 999px; + white-space: nowrap; + box-shadow: none; +} + +.bps-top-banner a.bps-return-link i { + font-size: 0.7rem; + color: inherit; +} + +.bps-top-banner a.bps-return-link:hover, +.bps-top-banner a.bps-return-link:focus, +.bps-top-banner a.bps-return-link:focus-visible, +.bps-top-banner a.bps-return-link:active { + color: var(--bps-announcement-text) !important; + text-decoration: none !important; + text-decoration-thickness: 0 !important; + background-color: rgba(255, 255, 255, 0.24) !important; + border: 1px solid rgba(255, 255, 255, 0.5) !important; + border-radius: 999px !important; + box-shadow: none !important; + outline: none !important; +} + +.bps-top-banner__sep { + display: inline-block; + width: 1px; + height: 1.1em; + background-color: rgba(255, 255, 255, 0.45); +} + +.bps-top-banner__message { + font-weight: 500; + font-size: 0.9rem; + opacity: 0.98; + text-shadow: 0 1px 1px rgba(47, 33, 78, 0.25); +} + +@media (max-width: 576px) { + .bps-top-banner__inner { + flex-direction: column; + gap: 0.45rem; + } + + .bps-top-banner__sep { + display: none; + } +} + +/* ----- BioPAL brand tokens + PyData theme mapping ----------------------- */ +:root { + /* BioPAL brand palette */ + --theme-color-a: #2f214e; + --theme-color-b: #a3d8b0; + --auxiliary-color-a: #b182f9; + --main-color-light: #5234c9; + --acri-color-blue: #347891; + --acri-color-blue-bright: #00b6f0; + + /* Violet default · ACRI blue hover (light) */ + --brand-accent-color: var(--main-color-light); + --brand-accent-hover: var(--acri-color-blue); + --doc-icon-color: var(--brand-accent-color); + --doc-icon-color-hover: var(--brand-accent-hover); + --doc-icon-opacity: 0.82; + --brand-link-color: var(--brand-accent-color); + --brand-link-hover: var(--brand-accent-hover); + --brand-nav-active-bg: color-mix(in srgb, var(--main-color-light) 10%, transparent); + + /* PyData theme: align links and accents with BioPAL */ + --pst-color-primary: var(--main-color-light); + --pst-color-secondary: var(--theme-color-b); + --pst-color-link: var(--brand-link-color); + --pst-color-link-hover: var(--brand-link-hover); + + /* Announcement bar: BioPAL violet → ACRI blue → green */ + --bps-announcement-gradient: linear-gradient( + 90deg, + var(--theme-color-a) 0%, + var(--main-color-light) 32%, + var(--acri-color-blue) 68%, + #6fa882 100% + ); + --bps-announcement-text: #ffffff; + --bps-announcement-border: rgba(255, 255, 255, 0.12); +} + +html[data-theme="dark"] { + /* Violet default · green hover (dark) */ + --brand-accent-color: var(--auxiliary-color-a); + --brand-accent-hover: var(--theme-color-b); + --doc-icon-color: var(--brand-accent-color); + --doc-icon-color-hover: var(--brand-accent-hover); + --doc-icon-opacity: 0.9; + --brand-link-color: var(--brand-accent-color); + --brand-link-hover: var(--brand-accent-hover); + --brand-nav-active-bg: color-mix(in srgb, var(--auxiliary-color-a) 14%, transparent); + + --pst-color-primary: var(--auxiliary-color-a); + --pst-color-secondary: var(--theme-color-b); + --pst-color-link: var(--brand-link-color); + --pst-color-link-hover: var(--brand-link-hover); + + /* Announcement bar: deeper stops for dark mode readability */ + --bps-announcement-gradient: linear-gradient( + 90deg, + #1a1230 0%, + #3d2a6b 30%, + #1f4a59 62%, + #4a7356 100% + ); + --bps-announcement-text: #f5fbe6; + --bps-announcement-border: rgba(255, 255, 255, 0.1); +} + +/* ----- Tables: wider and zebra-striped ---------------------------------- */ +.bd-content table { + width: 100%; +} + +.bd-content table tbody tr:nth-child(even) { + background-color: var(--pst-color-surface); +} + +/* ----- Inline code: slightly more contrast ------------------------------ */ +.bd-content code.literal { + padding: 0.15em 0.35em; + border-radius: 3px; + font-size: 0.9em; +} + +/* ----- Homepage hero ----------------------------------------------------- */ +.index-hero { + margin: 0 0 2.25rem; +} + +.index-hero__inner { + display: flex; + align-items: center; + justify-content: center; + gap: clamp(1.25rem, 3.5vw, 2.75rem); + flex-wrap: wrap; + padding: clamp(1.25rem, 3vw, 2rem) clamp(1.25rem, 4vw, 2.5rem); + border: 1px solid var(--pst-color-border); + border-radius: 1rem; + background: linear-gradient( + 135deg, + color-mix(in srgb, var(--brand-accent-color) 7%, var(--pst-color-surface)) 0%, + var(--pst-color-background) 52%, + color-mix(in srgb, var(--theme-color-b) 10%, var(--pst-color-background)) 100% + ); + box-shadow: 0 1px 2px color-mix(in srgb, var(--pst-color-text-base) 6%, transparent); +} + +.index-hero__logo-stage { + position: relative; + flex: 0 0 auto; + width: clamp(5.25rem, 13vw, 7rem); + height: clamp(5.25rem, 13vw, 7rem); + display: grid; + place-items: center; + filter: drop-shadow(0 12px 24px color-mix(in srgb, var(--brand-accent-color) 20%, transparent)); +} + +.index-hero__halo { + position: absolute; + inset: -10%; + border-radius: 50%; + background: radial-gradient( + circle, + color-mix(in srgb, var(--brand-accent-color) 28%, transparent) 0%, + transparent 72% + ); + animation: index-hero-halo-breathe 10s ease-in-out infinite; + pointer-events: none; + will-change: opacity; +} + +.index-hero__logo-motion { + position: relative; + z-index: 1; + width: 100%; + height: 100%; + animation: index-hero-logo-float 9s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite; + will-change: transform; +} + +.index-hero__logo { + display: block; + width: 100%; + height: 100%; + object-fit: contain; + background: transparent !important; + border-radius: 0 !important; + padding: 0 !important; +} + +.index-hero__copy-col { + flex: 1 1 15rem; + max-width: 38rem; + min-width: 0; + text-align: left; +} + +.index-hero__copy { + min-width: 0; +} + +.index-hero__title { + margin: 0 0 0.55rem; + font-size: clamp(1.85rem, 4vw, 2.35rem); + line-height: 1.15; + letter-spacing: -0.02em; +} + +.index-hero__eyebrow { + margin: 0 0 0.4rem; + font-size: 0.76rem; + font-weight: 600; + letter-spacing: 0.07em; + text-transform: uppercase; + color: var(--brand-accent-color); +} + +.index-hero__tagline { + margin: 0; + font-size: 1.05rem; + line-height: 1.55; + color: var(--pst-color-text-muted); +} + +.index-hero__meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.45rem 0.65rem; + margin: 0.9rem 0 0; +} + +.index-version-badge { + display: inline-flex; + align-items: center; + padding: 0.22rem 0.7rem; + border-radius: 999px; + font-size: 0.8125rem; + font-weight: 600; + letter-spacing: 0.03em; + font-family: var(--pst-font-family-monospace, ui-monospace, SFMono-Regular, Menlo, monospace); + color: var(--brand-accent-color); + background: color-mix(in srgb, var(--brand-accent-color) 11%, var(--pst-color-surface)); + border: 1px solid color-mix(in srgb, var(--brand-accent-color) 26%, var(--pst-color-border)); + text-decoration: none; + transition: color 160ms ease, border-color 160ms ease, background-color 160ms ease; +} + +.index-version-badge:hover, +.index-version-badge:focus-visible { + color: var(--brand-accent-hover); + border-color: color-mix(in srgb, var(--brand-accent-hover) 40%, var(--pst-color-border)); + background: color-mix(in srgb, var(--brand-accent-hover) 14%, var(--pst-color-surface)); + text-decoration: none; +} + +.index-version-note { + font-size: 0.8125rem; + color: var(--pst-color-text-muted); +} + +/* PyData dark theme adds background-color:#fff on .bd-content img. */ +html[data-theme="dark"] .index-hero__logo, +html[data-mode="dark"] .index-hero__logo { + background-color: transparent !important; + border-radius: 0 !important; + padding: 0 !important; +} + +@keyframes index-hero-logo-float { + 0%, + 100% { + transform: translate3d(0, 0, 0); + } + + 35% { + transform: translate3d(0, -3px, 0); + } + + 65% { + transform: translate3d(0, -5px, 0); + } +} + +@keyframes index-hero-halo-breathe { + 0%, + 100% { + opacity: 0.62; + } + + 50% { + opacity: 0.92; + } +} + +@media (prefers-reduced-motion: reduce) { + .index-hero__logo-motion, + .index-hero__halo { + animation: none; + } +} + +@media (max-width: 640px) { + .index-hero__inner { + flex-direction: column; + text-align: center; + } + + .index-hero__copy-col { + text-align: center; + } +} + +/* ----- BioPAL scope notice (homepage) ----------------------------------- */ +.biopal-scope-banner { + margin: 0 0 1.5rem; + padding: 0.85rem 1rem; + border-left: 3px solid var(--pst-color-border); + background: var(--pst-color-surface); + font-size: 0.92rem; + line-height: 1.55; + color: var(--pst-color-text-muted); +} + +.biopal-scope-banner strong { + color: var(--pst-color-text-base); + font-weight: 600; +} + +/* ----- Brand links and doc icons (violet → ACRI blue / green) ------------ */ +.brand-link-bar a, +.bd-main .bd-content a.reference:not(.headerlink):not(.sd-stretched-link):not(.sd-hide-link-text), +.bd-main .bd-content a.reference.external:not(.headerlink) { + color: var(--brand-link-color); + font-weight: 500; + text-decoration: none; + transition: color 160ms ease; +} + +.brand-link-bar a:hover, +.brand-link-bar a:focus-visible, +.bd-main .bd-content a.reference:not(.headerlink):not(.sd-stretched-link):not(.sd-hide-link-text):hover, +.bd-main .bd-content a.reference:not(.headerlink):not(.sd-stretched-link):not(.sd-hide-link-text):focus-visible, +.bd-main .bd-content a.reference.external:not(.headerlink):hover, +.bd-main .bd-content a.reference.external:not(.headerlink):focus-visible, +.bd-content table tbody tr:hover a.reference { + color: var(--brand-link-hover); + text-decoration: underline; + text-underline-offset: 0.15em; +} + +.brand-link-bar { + font-size: 0.95rem; + line-height: 1.6; +} + +.brand-link-bar strong { + color: var(--pst-color-text-base); + font-weight: 600; + margin-right: 0.25rem; +} + +.doc-icon { + display: inline-block; + margin-right: 0.4rem; + font-size: 0.88em; + line-height: 1; + color: var(--doc-icon-color); + opacity: var(--doc-icon-opacity); + vertical-align: 0.08em; + transition: color 160ms ease, opacity 160ms ease; +} + +.intro-grid .sd-card-header .doc-icon, +.intro-grid .sd-card-body .doc-icon { + font-size: 0.95em; + margin-right: 0.35rem; +} + +.intro-grid .sd-card:hover .doc-icon, +.intro-grid .sd-card:focus-within .doc-icon, +.intro-grid .sd-card.sd-card-hover:hover .doc-icon, +.bd-content table tbody tr:hover .doc-icon { + color: var(--doc-icon-color-hover); + opacity: 1; +} + +/* Keep card titles/body readable; only icons pick up brand accent */ +.intro-grid .sd-card .sd-card-text, +.intro-grid .sd-card .sd-card-text strong, +.intro-grid .sd-card.sd-card-hover:hover .sd-card-text, +.intro-grid .sd-card.sd-card-hover:hover .sd-card-text strong { + color: var(--pst-color-text-base); +} + +h2 .doc-icon, +h3 .doc-icon { + font-size: 0.82em; +} + +.bd-content table .doc-icon { + font-size: 0.85em; + margin-right: 0.25rem; +} + +.bd-content table tbody tr:hover .doc-icon { + color: var(--doc-icon-color-hover); + opacity: 1; +} + +/* Heading anchor (#): violet, hover accent — not default blue */ +.bd-content a.headerlink, +.bd-content a.headerlink:visited { + color: var(--brand-link-color); + opacity: 0.42; + text-decoration: none; + transition: color 160ms ease, opacity 160ms ease; +} + +.bd-content a.headerlink:hover, +.bd-content a.headerlink:focus-visible { + color: var(--brand-link-hover); + opacity: 1; + text-decoration: none; +} + +.intro-grid .sd-card-header .sd-card-title { + font-weight: 600; +} + +/* Font Awesome icons in sphinx-design dropdown titles */ +.sd-summary-text .doc-icon { + margin-right: 0.35rem; + color: var(--brand-accent-color); +} + +/* ----- Horizontal workflow step cards ----------------------------------- */ +.workflow-steps-list .workflow-step-card .sd-card-body { + padding: 0; +} + +.workflow-step-card__inner { + display: flex; + align-items: flex-start; + gap: 1.25rem; + padding: 1rem 1.25rem; +} + +.workflow-step-card__num { + flex: 0 0 2.5rem; + font-size: 1.25rem; + font-weight: 700; + line-height: 1.2; + color: var(--brand-link-color); +} + +.workflow-step-card__content { + flex: 1; + min-width: 0; + font-size: 0.95rem; + line-height: 1.55; +} + +.workflow-step-card__content > p:first-child { + margin-top: 0; +} + +.workflow-step-card__content > p:last-child { + margin-bottom: 0; +} + +.workflow-step-card__content strong { + display: block; + margin-bottom: 0.35rem; + font-size: 1rem; + color: var(--pst-color-text-base); +} + +/* ----- Horizontal stat cards -------------------------------------------- */ +.stat-cards-list .stat-card .sd-card-body { + padding: 0; +} + +.stat-card__inner { + display: flex; + align-items: flex-start; + gap: 1.25rem; + padding: 1rem 1.25rem; +} + +.stat-card__value { + flex: 0 0 4.5rem; + font-size: 1.65rem; + font-weight: 700; + line-height: 1.1; + color: var(--brand-link-color); + text-align: right; +} + +.stat-card__content { + flex: 1; + min-width: 0; + font-size: 0.95rem; + line-height: 1.55; +} + +.stat-card__content > p:first-child { + margin-top: 0; +} + +.stat-card__content > p:last-child { + margin-bottom: 0; +} + +.stat-card__content strong { + display: block; + margin-bottom: 0.25rem; + color: var(--pst-color-text-base); +} + +.stat-card__content code { + font-size: 0.88em; +} + +/* ----- Landing-page cards (sphinx-design) ------------------------------- */ +.sd-card { + transition: transform 120ms ease, box-shadow 120ms ease; +} + +.sd-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 16px rgba(0, 50, 71, 0.15); +} + +/* ----- Quote blocks ------------------------------------------------------ */ +.bd-content blockquote { + border-left: 4px solid var(--brand-link-color); + padding-left: 1em; + font-style: italic; + color: var(--pst-color-text-muted); +} + +/* ATBD draft: PDF-style collapsible table of contents (left sidebar) */ +.atbd-sidebar-toc { + margin-bottom: 1.25rem; +} + +.atbd-sidebar-toc__home { + display: block; + margin: 0 0 0.75rem; + padding: 0.2rem 0; + font-size: 0.84rem; + font-weight: 600; + line-height: 1.35; + color: var(--pst-color-heading); + text-decoration: none; + transition: color 0.15s ease; +} + +.atbd-sidebar-toc__home:hover { + color: var(--pst-color-link); + text-decoration: none; +} + +.atbd-sidebar-toc__home--current { + color: var(--pst-color-link); +} + +.atbd-sidebar-toc__tree { + font-size: 0.8rem; + line-height: 1.4; +} + +.atbd-sidebar-toc__chapter { + margin: 0 0 0.3rem; + border: none; + background: transparent; +} + +.atbd-sidebar-toc__chapter-summary { + cursor: pointer; + list-style: none; + padding: 0.25rem 0; +} + +.atbd-sidebar-toc__chapter-summary::-webkit-details-marker { + display: none; +} + +.atbd-sidebar-toc__chapter-summary::before { + content: "▸"; + display: inline-block; + width: 0.85rem; + margin-right: 0.15rem; + color: var(--brand-link-color); + font-size: 0.72rem; + transition: transform 0.15s ease; +} + +.atbd-sidebar-toc__chapter[open] > .atbd-sidebar-toc__chapter-summary::before { + transform: rotate(90deg); +} + +.atbd-sidebar-toc__chapter-link { + color: var(--pst-color-text-base); + font-weight: 600; + text-decoration: none; +} + +.atbd-sidebar-toc__chapter-link:hover { + color: var(--pst-color-link); + text-decoration: none; +} + +.atbd-sidebar-toc__chapter--current > .atbd-sidebar-toc__chapter-summary .atbd-sidebar-toc__chapter-link { + color: var(--pst-color-link); +} + +.atbd-sidebar-toc__sections { + list-style: none; + margin: 0 0 0.35rem; + padding: 0.1rem 0 0.25rem 1.35rem; +} + +.atbd-sidebar-toc__section { + margin: 0; +} + +.atbd-sidebar-toc__section-link { + display: block; + padding: 0.15rem 0; + margin: 0.05rem 0; + color: var(--pst-color-text-muted); + text-decoration: none; + transition: color 0.15s ease; +} + +.atbd-sidebar-toc__section-link:hover { + color: var(--pst-color-link); + text-decoration: none; +} + +.atbd-sidebar-toc__section-link--active { + color: var(--pst-color-link); + font-weight: 600; +} + +.atbd-download-pdf a { + display: inline-flex; + align-items: center; + gap: 0.35rem; +} + +/* ATBD partner logo banner (ESA, Aresys) at top of each chapter */ +.atbd-logo-banner { + margin: 0 0 1.5rem; + padding: 0.85rem 0.25rem; + border: none; + border-bottom: 1px solid var(--pst-color-border); + border-radius: 0; + background: transparent; +} + +.atbd-logo-banner__inner { + display: flex; + flex-wrap: nowrap; + align-items: center; + justify-content: space-between; + gap: 1rem; + width: 100%; +} + +.atbd-logo-banner__link { + display: inline-flex; + align-items: center; + flex: 0 1 auto; + line-height: 0; + background: transparent; +} + +.atbd-logo-banner__link--left { + margin-right: auto; +} + +.atbd-logo-banner__link--right { + margin-left: auto; +} + +.atbd-logo-banner__img { + display: block; + height: 4rem; + width: auto; + max-width: 15rem; + object-fit: contain; + background: transparent; +} + +/* PyData Sphinx Theme (html[data-theme=dark] .bd-content img) forces a white mat + behind every image so dark-on-transparent figures stay visible. ATBD partner + logos are white-on-transparent and must stay background-free. only-dark + opts out; this block is a safety net for data-mode / load-order edge cases. */ +html[data-theme="dark"] .atbd-logo-banner .atbd-logo-banner__img, +html[data-mode="dark"] .atbd-logo-banner .atbd-logo-banner__img { + background-color: transparent !important; + border-radius: 0 !important; + filter: none !important; +} + +/* Default to dark-theme assets (safe when data-mode="dark" but data-theme="auto"). */ +.atbd-logo-banner__img--light { + display: none !important; +} + +.atbd-logo-banner__img--dark { + display: block !important; +} + +/* Explicit light theme (PyData sets data-theme and/or data-mode). */ +html[data-theme="light"] .atbd-logo-banner__img--light, +html[data-mode="light"] .atbd-logo-banner__img--light { + display: block !important; + mix-blend-mode: multiply; +} + +html[data-theme="light"] .atbd-logo-banner__img--dark, +html[data-mode="light"] .atbd-logo-banner__img--dark { + display: none !important; +} + +/* System auto: follow OS preference when user has not forced light/dark. */ +@media (prefers-color-scheme: light) { + html[data-theme="auto"][data-mode="auto"] .atbd-logo-banner__img--light { + display: block !important; + mix-blend-mode: multiply; + } + + html[data-theme="auto"][data-mode="auto"] .atbd-logo-banner__img--dark { + display: none !important; + } +} + +@media print { + .atbd-logo-banner { + border: none; + padding: 0 0 0.75rem; + } + + .atbd-logo-banner__img { + height: 3.25rem; + } + + html[data-theme="light"] .atbd-logo-banner__img--light, + html[data-mode="light"] .atbd-logo-banner__img--light { + mix-blend-mode: normal; + } +} + +/* ATBD figures: PNG, Mermaid, and tables in one contained block (PDF Fig. N caption) */ +.atbd-mermaid-figure { + display: block; + margin: 1.25rem auto 2rem; + max-width: 52rem; + padding: 1rem 1rem 0.85rem; + border: 1px solid var(--pst-color-border); + border-radius: 0.25rem; + background: var(--pst-color-background); + text-align: center; + box-sizing: border-box; +} + +.atbd-mermaid-figure .atbd-figure-content { + width: 100%; + max-width: 100%; + overflow-x: auto; + overflow-y: hidden; + box-sizing: border-box; +} + +/* Diagram / image area stays inside the figure frame */ +.atbd-mermaid-figure .atbd-figure-content > img, +.atbd-mermaid-figure .atbd-figure-content img { + display: block; + max-width: 100%; + height: auto; + margin: 0 auto; +} + +.atbd-mermaid-figure .atbd-figure-content > table { + width: 100%; + margin: 0; + text-align: left; +} + +.atbd-mermaid-figure .mermaid-container, +.atbd-mermaid-figure pre.mermaid, +.atbd-mermaid-figure div.mermaid { + margin: 0 auto !important; + max-width: 100%; + width: 100%; + box-sizing: border-box; +} + +.atbd-mermaid-figure .mermaid-container { + position: relative; + display: block; +} + +.atbd-mermaid-figure .mermaid-fullscreen-btn { + right: 0.35rem; + top: 0.35rem; +} + +.atbd-mermaid-figure svg { + display: block; + max-width: 100% !important; + height: auto !important; + margin: 0 auto; +} + +/* Hide layout-only edges between nodes inside a Mermaid subgraph */ +.atbd-mermaid-figure .cluster .edgePath .path, +.atbd-mermaid-figure .cluster .edgePath marker path { + stroke: transparent !important; + fill: transparent !important; +} + +.atbd-mermaid-figure figcaption.atbd-figure-caption { + margin: 0.85rem 0 0; + padding-top: 0.75rem; + border-top: 1px solid var(--pst-color-border); + width: 100%; + text-align: center; + font-size: 0.9rem; + color: var(--pst-color-text-muted); + line-height: 1.45; +} + +.atbd-mermaid-figure .atbd-figure-caption .caption-number { + font-weight: 600; + color: var(--pst-color-text-base); +} + +/* Standalone mermaid outside ATBD figures (contributing pages, etc.) */ +div.mermaid:not(.atbd-mermaid-figure div.mermaid), +pre.mermaid:not(.atbd-mermaid-figure pre.mermaid) { + text-align: center; + margin: 1.5em auto; + max-width: 52rem; +} + +div.mermaid .cluster-label .nodeLabel, +div.mermaid .cluster-label span { + font-weight: 600; + font-size: 0.95em; +} + +div.mermaid .node.default .label { + font-size: 0.9em; +} + +div.mermaid svg { + max-width: 100% !important; + height: auto !important; +} + +/* Keep section navigation visible (mermaid pages load extra JS/CSS) */ +.bd-sidebar-primary .sidebar-primary-items__start { + display: block !important; +} + +.bd-sidebar-primary .bd-docs-nav { + display: block !important; +} + +/* ----- Section navigation (left sidebar + top navbar) ------------------- */ + +/* Section captions */ +.bd-sidebar-primary .caption-text { + font-size: 0.85rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--brand-link-color); + margin-top: 1.2em; + margin-bottom: 0.4em; +} + +/* Left sidebar links */ +.bd-sidebar-primary .nav-link, +.bd-sidebar-primary .nav-link:visited { + padding: 0.35rem 0.5rem; + font-size: 0.9rem; + color: var(--pst-color-text-base); + transition: color 160ms ease, background-color 160ms ease, border-color 160ms ease; +} + +.bd-sidebar-primary .nav-link:hover, +.bd-sidebar-primary .nav-link:focus-visible { + color: var(--brand-link-hover); +} + +.bd-sidebar-primary .nav-link.active, +.bd-sidebar-primary .current > .nav-link { + color: var(--brand-link-color); + font-weight: 600; + background-color: var(--brand-nav-active-bg); + border-left: 3px solid var(--brand-link-hover); + padding-left: calc(0.5rem - 3px); +} + +/* Top navbar section links */ +.bd-header .navbar-nav .nav-link, +.bd-header .navbar-nav .nav-link:visited { + color: var(--pst-color-text-base); + transition: color 160ms ease; +} + +.bd-header .navbar-nav .nav-link:hover, +.bd-header .navbar-nav .nav-link:focus-visible, +.bd-header .navbar-nav .nav-link.active { + color: var(--brand-link-hover); +} + +.bd-header .navbar-nav .nav-link.active { + color: var(--brand-link-color); + font-weight: 600; +} + +/* Right sidebar: on-this-page + edit button */ +.bd-sidebar-secondary .page-toc .nav-link { + font-size: 0.85rem; + padding: 0.2rem 0.5rem; + color: var(--pst-color-text-muted); + transition: color 160ms ease; +} + +.bd-sidebar-secondary .page-toc .nav-link:hover, +.bd-sidebar-secondary .page-toc .nav-link:focus-visible, +.bd-sidebar-secondary .page-toc .nav-link.active { + color: var(--brand-link-hover); +} + +.bd-sidebar-secondary .page-toc .nav-link.active { + color: var(--brand-link-color); + font-weight: 600; +} + +.bd-sidebar-secondary .editthispage a { + display: inline-flex; + align-items: center; + gap: 0.4em; + padding: 0.4em 0.7em; + border-radius: 4px; + border: 1px solid var(--pst-color-border); + font-size: 0.85rem; + color: var(--pst-color-text-muted); + text-decoration: none; + transition: all 120ms ease; +} + +.bd-sidebar-secondary .editthispage a:hover { + background-color: var(--brand-link-color); + color: #ffffff; + border-color: var(--brand-link-color); +} + +html[data-theme="dark"] .bd-sidebar-secondary .editthispage a:hover { + color: var(--theme-color-a); +} + +/* ----- Prev / Next page navigation -------------------------------------- */ +.prev-next-area a.left-prev, +.prev-next-area a.right-next, +.prev-next-area a.left-prev:visited, +.prev-next-area a.right-next:visited { + color: var(--brand-link-color); + text-decoration: none; + transition: color 160ms ease; +} + +.prev-next-area a.left-prev:hover, +.prev-next-area a.right-next:hover, +.prev-next-area a.left-prev:focus-visible, +.prev-next-area a.right-next:focus-visible { + color: var(--brand-link-hover); +} + +.prev-next-area a.left-prev i, +.prev-next-area a.right-next i { + color: var(--brand-link-color); + transition: color 160ms ease; +} + +.prev-next-area a.left-prev:hover i, +.prev-next-area a.right-next:hover i, +.prev-next-area a.left-prev:focus-visible i, +.prev-next-area a.right-next:focus-visible i { + color: var(--brand-link-hover); +} + +.prev-next-subtitle { + color: var(--pst-color-text-muted); +} + +.prev-next-title { + color: inherit; + font-weight: 600; +} + +/* ----- Site footer (Sphinx / PyData credits) ---------------------------- */ +.bd-footer .sphinx-version, +.bd-footer .theme-version, +.bd-footer .copyright { + color: var(--pst-color-text-muted); + font-size: 0.85rem; +} + +.bd-footer .sphinx-version a, +.bd-footer .theme-version a, +.bd-footer .copyright a, +.bd-footer .sphinx-version a:visited, +.bd-footer .theme-version a:visited, +.bd-footer .copyright a:visited { + color: var(--brand-link-color); + font-weight: 500; + text-decoration: none; + transition: color 160ms ease; +} + +.bd-footer .sphinx-version a:hover, +.bd-footer .theme-version a:hover, +.bd-footer .copyright a:hover, +.bd-footer .sphinx-version a:focus-visible, +.bd-footer .theme-version a:focus-visible, +.bd-footer .copyright a:focus-visible { + color: var(--brand-link-hover); + text-decoration: underline; + text-underline-offset: 0.15em; +} + +/* ----- ATBD equations (PDF-style \tag{}) --------------------------------- */ +/* Sphinx still emits (1), (2) in .eqno when labels are present; hide them + so only MathJax \tag{3.1} numbers from the source PDF are visible. */ +div.math .eqno { + display: none; +} + +/* MathJax limit bars (\rule workaround for under/overline limits). + CHTML renders \rule as mjx-mspace with background-color: black, which + disappears on some theme/background combinations. Follow text color instead. */ +.bd-content mjx-mspace[style*="background-color: black"], +.bd-content mjx-mspace[style*="background-color:black"] { + background-color: currentColor !important; +} + +/* PyData sets overflow:hidden on math containers; clip overset/underset bars. */ +.bd-content span.math, +.bd-content div.math, +.bd-content span.math mjx-container, +.bd-content div.math mjx-container { + overflow: visible !important; +} + +/* MAAP eligibility callouts — semantic accent colours */ +.maap-eligibility-notice.admonition, +div.maap-eligibility-notice { + padding: 0.85rem 1rem 0.95rem; + border-style: solid; + border-width: 1px; + border-left-width: 0.45rem; + border-radius: 0.5rem; + margin: 1.25rem 0 1.75rem; +} + +.maap-eligibility-notice .admonition-title, +.maap-eligibility-notice__title { + display: flex; + align-items: center; + gap: 0.45rem; + margin: 0 0 0.45rem; + font-size: 1.02rem; + font-weight: 700; +} + +.maap-eligibility-notice .admonition-title::before, +.maap-eligibility-notice__title::before { + font-family: var(--fa-style-family-classic, "Font Awesome 6 Free"); + font-weight: 900; + font-size: 1.05rem; + line-height: 1; +} + +.maap-eligibility-notice .admonition-content > p:last-child, +.maap-eligibility-notice > .section > p:last-child, +.maap-eligibility-notice > p:last-child { + margin-bottom: 0; + font-size: 0.98rem; + line-height: 1.55; +} + +.maap-eligibility-notice.admonition, +div.maap-eligibility-notice { + border-color: color-mix(in srgb, var(--pst-color-warning) 55%, var(--pst-color-border)); + background: color-mix(in srgb, var(--pst-color-warning) 12%, var(--pst-color-surface)); + box-shadow: 0 1px 0 color-mix(in srgb, var(--pst-color-warning) 18%, transparent); +} + +.maap-eligibility-notice .admonition-title, +.maap-eligibility-notice__title { + color: var(--pst-color-warning); +} + +.maap-eligibility-notice .admonition-title::before, +.maap-eligibility-notice__title::before { + content: "\f071"; +} + +.maap-eligibility-notice--compact { + margin: 0.75rem 0 1rem; +} + +/* ----- Meetings & events — featured past session + deck preview ---------- */ +.bps-last-event { + margin: 0 0 2.5rem; +} + +.bps-last-event__card { + display: grid; + grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); + gap: 0; + overflow: hidden; + border-radius: 16px; + border: 1px solid color-mix(in srgb, var(--acri-color-blue) 28%, var(--pst-color-border)); + background: linear-gradient( + 135deg, + color-mix(in srgb, var(--acri-color-blue) 14%, var(--pst-color-surface)) 0%, + var(--pst-color-surface) 52%, + color-mix(in srgb, var(--theme-color-b) 12%, var(--pst-color-surface)) 100% + ); + box-shadow: + 0 1px 0 color-mix(in srgb, var(--acri-color-blue-bright) 20%, transparent), + 0 18px 42px color-mix(in srgb, var(--theme-color-a) 10%, transparent); +} + +.bps-last-event__copy { + padding: 1.75rem 1.85rem 1.65rem; + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.85rem; +} + +.bps-last-event__eyebrow { + margin: 0; + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--acri-color-blue); +} + +.bps-last-event__title { + margin: 0; + font-size: clamp(1.45rem, 2.4vw, 2rem); + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.02em; + color: var(--pst-color-text-base); +} + +.bps-last-event__meta { + display: flex; + flex-wrap: wrap; + gap: 0.55rem 1rem; + margin: 0; + padding: 0; + list-style: none; + font-size: 0.92rem; + color: var(--pst-color-text-muted); +} + +.bps-last-event__meta li { + display: inline-flex; + align-items: center; + gap: 0.4rem; +} + +.bps-last-event__meta i { + color: var(--acri-color-blue); + opacity: 0.9; +} + +.bps-last-event__actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-top: 0.35rem; +} + +.bps-last-event__btn { + display: inline-flex; + align-items: center; + gap: 0.45rem; + padding: 0.62rem 1.1rem; + border-radius: 999px; + font-size: 0.92rem; + font-weight: 600; + text-decoration: none !important; + transition: + background-color 160ms ease, + border-color 160ms ease, + color 160ms ease, + transform 160ms ease; +} + +.bps-last-event__btn--primary { + color: #fff !important; + background: linear-gradient(135deg, var(--acri-color-blue) 0%, var(--acri-color-blue-bright) 100%); + border: 1px solid color-mix(in srgb, var(--acri-color-blue-bright) 70%, transparent); +} + +.bps-last-event__btn--primary:hover, +.bps-last-event__btn--primary:focus-visible { + color: #fff !important; + transform: translateY(-1px); + background: linear-gradient(135deg, var(--acri-color-blue-bright) 0%, var(--acri-color-blue) 100%); +} + +.bps-last-event__btn--ghost { + color: var(--brand-link-color) !important; + background: color-mix(in srgb, var(--pst-color-surface) 80%, transparent); + border: 1px solid var(--pst-color-border); +} + +.bps-last-event__btn--ghost:hover, +.bps-last-event__btn--ghost:focus-visible { + color: var(--brand-link-hover) !important; + border-color: color-mix(in srgb, var(--acri-color-blue) 35%, var(--pst-color-border)); +} + +.bps-last-event__visual { + position: relative; + min-height: 260px; + background: #0f1923; + border-left: 1px solid rgba(255, 255, 255, 0.08); +} + +.bps-last-event__visual iframe { + position: absolute; + inset: 0; + width: 1920px; + height: 1080px; + border: 0; + pointer-events: none; + transform-origin: top left; + transform: scale(calc(100cqw / 1920)); +} + +.bps-last-event__visual-link { + position: absolute; + inset: 0; + z-index: 2; + display: flex; + align-items: flex-end; + justify-content: center; + padding: 1rem; + text-decoration: none !important; + background: linear-gradient(180deg, transparent 55%, rgba(15, 25, 35, 0.92) 100%); +} + +.bps-last-event__visual-link span { + display: inline-flex; + align-items: center; + gap: 0.45rem; + padding: 0.5rem 0.95rem; + border-radius: 999px; + font-size: 0.82rem; + font-weight: 600; + color: #fff; + background: rgba(0, 122, 148, 0.85); + border: 1px solid rgba(0, 182, 240, 0.35); + opacity: 0; + transform: translateY(6px); + transition: + opacity 180ms ease, + transform 180ms ease; +} + +.bps-last-event__visual-link:hover span, +.bps-last-event__visual-link:focus-visible span { + opacity: 1; + transform: translateY(0); +} + +.bps-last-event__visual { + container-type: inline-size; +} + +/* Deck page — large live preview + slide strip */ +.bps-deck-hero { + margin: 0 0 1.75rem; +} + +.bps-deck-preview { + position: relative; + width: 100%; + border-radius: 16px; + overflow: hidden; + border: 1px solid color-mix(in srgb, var(--acri-color-blue) 30%, var(--pst-color-border)); + background: #0f1923; + box-shadow: + 0 1px 0 rgba(0, 182, 240, 0.15), + 0 20px 48px color-mix(in srgb, var(--theme-color-a) 12%, transparent); + container-type: inline-size; +} + +.bps-deck-preview iframe { + display: block; + width: 1920px; + height: 1080px; + border: 0; + transform-origin: top left; + transform: scale(calc(100cqw / 1920)); + pointer-events: none; +} + +.bps-deck-preview__overlay { + position: absolute; + inset: 0; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; + text-decoration: none !important; + background: rgba(15, 25, 35, 0.08); + transition: background 180ms ease; +} + +.bps-deck-preview__overlay:hover, +.bps-deck-preview__overlay:focus-visible { + background: rgba(15, 25, 35, 0.35); +} + +.bps-deck-preview__play { + display: inline-flex; + align-items: center; + gap: 0.65rem; + padding: 0.85rem 1.35rem; + border-radius: 999px; + font-size: 1.05rem; + font-weight: 700; + color: #fff !important; + background: linear-gradient(135deg, var(--acri-color-blue) 0%, var(--acri-color-blue-bright) 100%); + border: 1px solid rgba(0, 182, 240, 0.45); + box-shadow: 0 10px 28px rgba(0, 0, 0, 0.35); + transition: transform 180ms ease, box-shadow 180ms ease; +} + +.bps-deck-preview__overlay:hover .bps-deck-preview__play, +.bps-deck-preview__overlay:focus-visible .bps-deck-preview__play { + transform: scale(1.04); + box-shadow: 0 14px 34px rgba(0, 0, 0, 0.42); +} + +.bps-deck-preview__play i { + font-size: 1.15em; +} + +.bps-deck-preview::after { + content: ""; + display: block; + padding-bottom: 56.25%; +} + +.bps-deck-preview iframe { + position: absolute; + top: 0; + left: 0; +} + +.bps-slide-strip { + display: flex; + gap: 0.85rem; + margin: 1.25rem 0 0; + padding: 0.35rem 0.15rem 0.75rem; + overflow-x: auto; + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; +} + +.bps-slide-strip__item { + flex: 0 0 clamp(180px, 22vw, 260px); + scroll-snap-align: start; + border-radius: 10px; + overflow: hidden; + border: 1px solid var(--pst-color-border); + background: #0f1923; + box-shadow: 0 8px 22px color-mix(in srgb, var(--theme-color-a) 8%, transparent); + transition: + transform 180ms ease, + border-color 180ms ease, + box-shadow 180ms ease; +} + +.bps-slide-strip__item:hover, +.bps-slide-strip__item:focus-visible { + transform: translateY(-3px); + border-color: color-mix(in srgb, var(--acri-color-blue-bright) 45%, var(--pst-color-border)); + box-shadow: 0 14px 28px color-mix(in srgb, var(--acri-color-blue) 16%, transparent); +} + +.bps-slide-strip__item img { + display: block; + width: 100%; + height: auto; + aspect-ratio: 16 / 9; + object-fit: cover; + object-position: top center; +} + +@media (max-width: 900px) { + .bps-last-event__card { + grid-template-columns: 1fr; + } + + .bps-last-event__visual { + min-height: 220px; + border-left: 0; + border-top: 1px solid rgba(255, 255, 255, 0.08); + } +} + +/* Meeting session status (from presentations//meeting.yaml) */ +.bps-meeting-status { + display: inline-flex; + align-items: center; + gap: 0.45rem; + padding: 0.35rem 0.85rem; + border-radius: 999px; + font-size: 0.875rem; + font-weight: 600; + letter-spacing: 0.02em; + line-height: 1.2; + border: 1px solid transparent; + margin: 0.35rem 0 0.75rem; +} + +.bps-meeting-status--inline { + margin: 0; + padding: 0.2rem 0.65rem; + font-size: 0.8125rem; +} + +.bps-meeting-status i { + font-size: 0.95em; + opacity: 0.92; +} + +.bps-meeting-status--planned { + color: color-mix(in srgb, var(--pst-color-warning) 88%, var(--pst-color-text)); + background: color-mix(in srgb, var(--pst-color-warning) 14%, var(--pst-color-surface)); + border-color: color-mix(in srgb, var(--pst-color-warning) 38%, var(--pst-color-border)); +} + +.bps-meeting-status--held { + color: color-mix(in srgb, #2d7a4a 92%, var(--pst-color-text)); + background: color-mix(in srgb, #6fa882 18%, var(--pst-color-surface)); + border-color: color-mix(in srgb, #6fa882 42%, var(--pst-color-border)); +} + +.bps-meeting-status--cancelled { + color: color-mix(in srgb, var(--pst-color-danger) 88%, var(--pst-color-text)); + background: color-mix(in srgb, var(--pst-color-danger) 12%, var(--pst-color-surface)); + border-color: color-mix(in srgb, var(--pst-color-danger) 35%, var(--pst-color-border)); +} diff --git a/docs/_static/figures/agb/figure_11_section_3.6.4.1.png b/docs/_static/figures/agb/figure_11_section_3.6.4.1.png new file mode 100644 index 0000000..b26134b Binary files /dev/null and b/docs/_static/figures/agb/figure_11_section_3.6.4.1.png differ diff --git a/docs/_static/figures/agb/figure_12_section_4.png b/docs/_static/figures/agb/figure_12_section_4.png new file mode 100644 index 0000000..b66f6a5 Binary files /dev/null and b/docs/_static/figures/agb/figure_12_section_4.png differ diff --git a/docs/_static/figures/agb/figure_14_section_4.1.png b/docs/_static/figures/agb/figure_14_section_4.1.png new file mode 100644 index 0000000..d35c015 Binary files /dev/null and b/docs/_static/figures/agb/figure_14_section_4.1.png differ diff --git a/docs/_static/figures/agb/figure_1_section_2.1.png b/docs/_static/figures/agb/figure_1_section_2.1.png new file mode 100644 index 0000000..a5c1c13 Binary files /dev/null and b/docs/_static/figures/agb/figure_1_section_2.1.png differ diff --git a/docs/_static/figures/agb/figure_2_section_2.1.png b/docs/_static/figures/agb/figure_2_section_2.1.png new file mode 100644 index 0000000..977f6cc Binary files /dev/null and b/docs/_static/figures/agb/figure_2_section_2.1.png differ diff --git a/docs/_static/figures/agb/figure_3_section_2.3.png b/docs/_static/figures/agb/figure_3_section_2.3.png new file mode 100644 index 0000000..93e45c3 Binary files /dev/null and b/docs/_static/figures/agb/figure_3_section_2.3.png differ diff --git a/docs/_static/logo.png b/docs/_static/logo.png new file mode 100644 index 0000000..0b14b23 Binary files /dev/null and b/docs/_static/logo.png differ diff --git a/docs/_static/logos/Aresys_dark.svg b/docs/_static/logos/Aresys_dark.svg new file mode 100644 index 0000000..90fa043 --- /dev/null +++ b/docs/_static/logos/Aresys_dark.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/docs/_static/logos/Aresys_light.png b/docs/_static/logos/Aresys_light.png new file mode 100644 index 0000000..a49c077 Binary files /dev/null and b/docs/_static/logos/Aresys_light.png differ diff --git a/docs/_static/logos/BioPAL.png b/docs/_static/logos/BioPAL.png new file mode 100644 index 0000000..8ac6b2c Binary files /dev/null and b/docs/_static/logos/BioPAL.png differ diff --git a/docs/_static/logos/BioPAL_textonbottom.png b/docs/_static/logos/BioPAL_textonbottom.png new file mode 100644 index 0000000..ec1c865 Binary files /dev/null and b/docs/_static/logos/BioPAL_textonbottom.png differ diff --git a/docs/_static/logos/BioPAL_textonright.png b/docs/_static/logos/BioPAL_textonright.png new file mode 100644 index 0000000..93c3bb2 Binary files /dev/null and b/docs/_static/logos/BioPAL_textonright.png differ diff --git a/docs/_static/logos/ESA_dark.png b/docs/_static/logos/ESA_dark.png new file mode 100644 index 0000000..08e7c6f Binary files /dev/null and b/docs/_static/logos/ESA_dark.png differ diff --git a/docs/_static/logos/ESA_light.png b/docs/_static/logos/ESA_light.png new file mode 100644 index 0000000..509e62b Binary files /dev/null and b/docs/_static/logos/ESA_light.png differ diff --git a/docs/_static/pdf/.gitignore b/docs/_static/pdf/.gitignore new file mode 100644 index 0000000..17d5f31 --- /dev/null +++ b/docs/_static/pdf/.gitignore @@ -0,0 +1,2 @@ +# Generated by `make atbd-pdf` (web export of the ATBD, all chapters). +*.pdf diff --git a/docs/_static/pdf/.gitkeep b/docs/_static/pdf/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/_templates/atbd-download-pdf.html b/docs/_templates/atbd-download-pdf.html new file mode 100644 index 0000000..0d73cf7 --- /dev/null +++ b/docs/_templates/atbd-download-pdf.html @@ -0,0 +1,10 @@ +{# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) #} +{# SPDX-License-Identifier: Apache-2.0 #} +{% if atbd_pdf_url %} + +{% endif %} diff --git a/docs/_templates/atbd-sidebar-toc.html b/docs/_templates/atbd-sidebar-toc.html new file mode 100644 index 0000000..3f2cc77 --- /dev/null +++ b/docs/_templates/atbd-sidebar-toc.html @@ -0,0 +1,89 @@ +{# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) #} +{# SPDX-License-Identifier: Apache-2.0 #} +{% if atbd_sidebar_toc %} + + +{% endif %} diff --git a/docs/_templates/science-guide-atbd-nav.html b/docs/_templates/science-guide-atbd-nav.html new file mode 100644 index 0000000..865eb9f --- /dev/null +++ b/docs/_templates/science-guide-atbd-nav.html @@ -0,0 +1,19 @@ +{# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) #} +{# SPDX-License-Identifier: Apache-2.0 #} +{% if science_guide_atbds %} + +{% endif %} diff --git a/docs/_templates/sections/announcement.html b/docs/_templates/sections/announcement.html new file mode 100644 index 0000000..d839a05 --- /dev/null +++ b/docs/_templates/sections/announcement.html @@ -0,0 +1,13 @@ +{#- Custom top banner: return link + announcement. -#} +{%- if theme_announcement %} + +{%- endif %} diff --git a/docs/_templates/site-return-link.html b/docs/_templates/site-return-link.html new file mode 100644 index 0000000..367ab07 --- /dev/null +++ b/docs/_templates/site-return-link.html @@ -0,0 +1,7 @@ +{# Alternative layout: slim text link in navbar end (before theme switcher). #} +{# Enable with: "navbar_end": ["site-return-link", "theme-switcher", "navbar-icon-links"] #} +{# Note: dedicated styles were removed during the CSS audit; links inherit nav-link styling. #} + + + Biomass DISC + diff --git a/docs/about/applicable-documents.md b/docs/about/applicable-documents.md new file mode 100644 index 0000000..8fd1ee5 --- /dev/null +++ b/docs/about/applicable-documents.md @@ -0,0 +1,73 @@ + + +# Applicable documents + +The table below lists every official document applicable to **BIOMASS BPS v4.4.4**. +Each entry links to the authoritative PDF hosted on the ESA dissemination portal +[biomass-disc.info](https://www.biomass-disc.info/release_note). + +When a new BPS version is released, this page is updated as the single source of +truth. Per-processor pages in the [Science Guide](../science-guide/index.md) +and the [User Guide](../user-guide/index.md) link back here. + +## Project-level + +| Document | Reference | Version | Date | Download | +|---|---|---|---|---| +| BIOMASS Processing Suite Release Note | `BIO-BPS-RN-ARE-010556` | 4.4.4 | 2026-05-15 | [PDF](https://www.biomass-disc.info/api/user-manager/v1/files/media/share/docs_BPS_v4_4_4/BPS_RN_v4_4_4.pdf) | +| BIOMASS Processing Suite Software User Manual (SUM) | `BIO-BPS-SUM-ARE-010479` | 4.4.1 | 2025-03-13 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_SUM_v4_4_1.pdf) | + +## Level 1: SAR and Stack + +| Document | Reference | Version | Date | Download | +|---|---|---|---|---| +| L1 a/b/c Products Format Specification | `BIO-BPS-L1-PFD-ARE-010076` | 1.6.1 | 2026-04-02 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_L1_PFD_v1_6_1.pdf) | +| L1 SAR Product ATBD | `BIO-BPS-L1-SAR-ATBD-ARE-010165` | 1.2.4 | 2026-03-27 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_L1_SAR_ATBD_v1_2_4.pdf) | +| L1c Stack Product ATBD | `BIO-BPS-L1-STACK-ATBD-ARE-010166` | 1.4.0 | 2026-04-02 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_L1_STACK_ATBD_v1_4_0.pdf) | + +## Level 2b: Above-Ground Biomass (AGB) + +| Document | Reference | Version | Date | PDF | Web | +|---|---|---|---|---|---| +| AGB Products Format Specification | `BIO-BPS-AGB-PFD-ARE-010257` | 3.4.0 | 2026-03-13 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_AGB_PFD_v3_4_0.pdf) | — | +| AGB Product ATBD | `BIO-BPS-AGB-ATBD-ARE-024912` | 3.1.4 | 2026-04-02 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_AGB_ATBD_v3_1_4.pdf) | [Draft](../science-guide/atbd-l2-agb/index.md) | + +## Level 2b: Forest Height (FH) + +| Document | Reference | Version | Date | Download | +|---|---|---|---|---| +| FH Products Format Specification | `BIO-BPS-FH-PFD-ARE-010256` | 3.4.0 | 2026-03-13 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_FH_PFD_v3_4_0.pdf) | +| FH Product ATBD | `BIO-BPS-FH-ATBD-ARE-10343` | 2.2.0 | 2026-03-13 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_FH_ATBD_v2_2_0.pdf) | + +## Level 2b: Forest Disturbance (FD) + +| Document | Reference | Version | Date | Download | +|---|---|---|---|---| +| FD Products Format Specification | `BIO-BPS-FD-PFD-ARE-010258` | 3.4.0 | 2026-03-13 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_FD_PFD_v3_4_0.pdf) | +| FD Product ATBD | `BIO-BPS-FD-ATBD-ARE-10344` | 2.1.8 | 2025-04-30 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_FD_ATBD_v2.1.8.pdf) | + +## Interfaces and auxiliary + +| Document | Reference | Version | Date | Download | +|---|---|---|---|---| +| Processing Interface Control Document (ICD) | `BIO-BPS-ICD-ARE-010113` | 3.2.3 | 2025-09-29 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_ICD_v3_2_3.pdf) | +| Processing Input & Output Data Definition (IODD) | `BIO-BPS-IODD-ARE-010112` | 3.1.2 | 2025-09-29 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_IODD_v3_1_2.pdf) | +| BPS Auxiliary Products Format | `BIO-BPS-AUX-FMT-ARE-010163` | 3.6.1 | 2026-04-02 | [PDF](https://www.biomass-disc.info/assets/documents/BPS_v4.4.2/BPS_AUX_FMT_v3_6_1.pdf) | + +--- + +## Notes + +- The **Release Note v4.4.4** lives on a dedicated path (`docs_BPS_v4_4_4/`). + The 13 other documents are hosted under `/assets/documents/BPS_v4.4.2/`, + the directory used as the active set during the 4.4.2 → 4.4.4 transition. + They will be relocated to a `BPS_v4_4_4/` directory in a future portal + refresh; this table will be updated accordingly. +- **Markdown conversion** of the ATBDs and SUM is tracked progressively in + separate `[docs]` issues on the [issue tracker](https://github.com/BioPAL/BPS/issues). + Until each conversion is merged and SME-approved, the PDF linked here remains + the authoritative reference. Draft web pages (when available) are linked in the + **Web** column and labelled accordingly in the [Science Guide](../science-guide/index.md). diff --git a/docs/about/index.md b/docs/about/index.md new file mode 100644 index 0000000..e3fb7e8 --- /dev/null +++ b/docs/about/index.md @@ -0,0 +1,83 @@ + + +# About + +## What is BIOMASS BPS? + +The **BIOMASS Processing Suite (BPS)** is the open-source software developed to +process Level 1, Level 2A, and Level 2B data from the **ESA BIOMASS Earth Explorer +mission**. + +It is being developed by **Aresys** and **ACRI-ST** under ESA contract, and is +published as open source in alignment with ESA's **Open Science policy** and the +**FAIR principles**. + +## Project facts + +| | | +|---|---| +| **Mission** | [ESA BIOMASS Earth Explorer](https://www.esa.int/Applications/Observing_the_Earth/FutureEO/Biomass) | +| **Mission type** | P-band Synthetic Aperture Radar (SAR) | +| **Goal** | Mapping global forest biomass and its annual evolution | +| **Expected use** | 5+ years of operations, 20+ years of public data exploitation | +| **License** | Apache License 2.0 | +| **Source language** | Python 3.12 | +| **Repository** | [github.com/BioPAL/BPS](https://github.com/BioPAL/BPS) | + +## In this section + +::::{grid} 1 2 2 2 +:gutter: 3 +:class-container: intro-grid + +:::{grid-item-card} +:link: applicable-documents +:link-type: doc +:class-card: intro-card + + **Applicable documents** +^^^ +The full list of ATBDs, SUM, ICD, PFD and auxiliary specifications applicable to the current BPS release, with PDF downloads. +::: + +:::{grid-item-card} +:link: licensing/index +:link-type: doc +:class-card: intro-card + + **Licensing** +^^^ +Project license, contributions, dependencies, and REUSE compliance. +::: + +:::: + +## Citation + +If you use BIOMASS BPS in your research, please cite the project. A citation file +(`CITATION.cff`) will be published alongside the first stable release. + +## Contact + +- **Bug reports / feature requests**: open an issue on [GitHub](https://github.com/BioPAL/BPS/issues). +- **General questions**: see the [Communication](../communication/index.md) page for channels and meetings. +- **Security concerns**: see `SECURITY.md` in the repository (to be added). + +## Acknowledgements + +- **ESA** for funding the mission and the open-source initiative. +- **Aresys** for the scientific algorithms and processor development. +- **ACRI-ST** for the open-source governance, CI/CD design, and best-practices framework. +- **The scientific community** for contributing reviews, ideas, and validations. + +```{toctree} +:caption: About +:maxdepth: 1 +:hidden: + +applicable-documents +Licensing +``` diff --git a/docs/about/licensing/contributions.md b/docs/about/licensing/contributions.md new file mode 100644 index 0000000..852d649 --- /dev/null +++ b/docs/about/licensing/contributions.md @@ -0,0 +1,82 @@ +# Contributions + +## Contributor license requirements + +### All contributions must be Apache 2.0 compatible + +**By contributing to BIOMASS BPS, you agree that your contributions will be licensed under the Apache License 2.0.** + +This means: + +- You retain copyright ownership of your contributions +- You grant the BIOMASS BPS project (and all users) a perpetual, worldwide, non-exclusive, royalty-free license to use, modify, and distribute your contributions under Apache 2.0 +- You have the right to submit the contribution +- Your contribution does not violate any third-party rights + +### Copyright assignment + +You do **not** need to assign copyright to the BIOMASS BPS project. You retain ownership of your contributions, but you grant the necessary licenses for the project to use them under Apache 2.0. + +## License headers in source files + +### Required header format + +All source code files should include a license header. The preferred format is the **SPDX short-form** (required for [REUSE compliance](reuse-compliance.md)): + +```python +# SPDX-FileCopyrightText: 2026 BIOMASS BPS Contributors +# +# SPDX-License-Identifier: Apache-2.0 +``` + +The long-form Apache header is also accepted for backwards compatibility: + +```python +# Copyright [Year] BIOMASS BPS Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +``` + +### Language-specific headers + +- **Python**: Use `#` comments +- **JavaScript/TypeScript**: Use `//` or `/* */` comments +- **C/C++**: Use `//` or `/* */` comments +- **Rust**: Use `//` comments +- **Other languages**: Follow language-specific comment conventions + +### Files that do not need headers + +The following files typically don't need license headers: + +- Configuration files (YAML, JSON, TOML) +- Data files +- Generated files +- Very short utility scripts (use your judgment) + +## License compliance checklist for contributors + +Before submitting a Pull Request, verify: + +- [ ] All new code is your original work or properly attributed +- [ ] All external dependencies are Apache 2.0 compatible +- [ ] License information is documented in the PR description +- [ ] Source files include SPDX headers (`SPDX-FileCopyrightText` + `SPDX-License-Identifier: Apache-2.0`) +- [ ] `reuse lint` passes locally (or pre-commit reuse hook passes) +- [ ] NOTICE file is updated (if required) +- [ ] No GPL or other incompatible code is included +- [ ] Third-party code snippets are properly attributed + +--- + +**Previous:** [Project license](project-license.md) | **Next:** [Dependencies](dependencies.md) diff --git a/docs/about/licensing/dependencies.md b/docs/about/licensing/dependencies.md new file mode 100644 index 0000000..7cd887e --- /dev/null +++ b/docs/about/licensing/dependencies.md @@ -0,0 +1,107 @@ +# Dependencies + +## License compatibility + +When adding external libraries or dependencies to BIOMASS BPS, you **must** ensure they are compatible with Apache 2.0. This is critical for maintaining legal clarity and avoiding license conflicts. + +### Compatible licenses + +The following licenses are generally compatible with Apache 2.0: + +- **MIT License** - Fully compatible +- **BSD 2-Clause / 3-Clause** - Fully compatible +- **Apache 2.0** - Fully compatible +- **ISC License** - Fully compatible +- **Public Domain** - Compatible + +### Licenses requiring review + +The following licenses may be compatible but require careful review: + +- **LGPL 2.1 / 3.0** - Compatible if used as a dynamically linked library (not statically linked) +- **MPL 2.0** - Generally compatible, but review required +- **EPL 1.0 / 2.0** - Generally compatible, but review required + +### Incompatible licenses + +The following licenses are **NOT compatible** with Apache 2.0 and **cannot** be used: + +- **GPL 2.0 / 3.0** - Incompatible (copyleft requirements conflict) +- **AGPL** - Incompatible +- **Proprietary licenses** - Incompatible +- **Any license that requires derivative works to use the same license** - Incompatible + +## Requirements for pull requests + +When submitting a Pull Request (PR) that includes external libraries or dependencies, you **must**: + +1. **Document the license** of each new dependency in the PR description +2. **Verify compatibility** with Apache 2.0 before submission +3. **Include license information** in dependency management files (e.g., `requirements.txt`, `package.json`, `Cargo.toml`) +4. **Update the NOTICE file** (if applicable) with attribution requirements +5. **Provide justification** for why the dependency is necessary + +### Example PR description template + +```markdown +## Dependencies Added + +- **library-name** (v1.2.3) + - License: MIT + - Purpose: [Brief description] + - Compatibility: Compatible with Apache 2.0 + - License URL: [Link to license] +``` + +## License verification process + +Before merging any PR with new dependencies: + +1. **Automated checks** will verify license compatibility +2. **Maintainers** will review license information +3. **Legal review** may be required for complex cases +4. **Documentation** must be updated with license information + +## Third-party code and code snippets + +### Using code from other sources + +If you want to include code from other sources (Stack Overflow, blogs, other projects, etc.), you must: + +1. **Verify the license** of the source code +2. **Ensure compatibility** with Apache 2.0 +3. **Provide proper attribution** in code comments +4. **Document the source** in your PR description + +### Attribution format + +```python +# This function is based on code from: +# Source: [URL or project name] +# License: [License name] +# Author: [Author name, if known] +``` + +### Code from public domain or permissive licenses + +Code from sources with permissive licenses (MIT, BSD, Apache 2.0) can generally be included with proper attribution. Always verify the specific license terms. + +### Code from GPL or other incompatible licenses + +**Do not include code from GPL-licensed projects or other incompatible licenses.** Even small snippets can create legal issues. If you need similar functionality, implement it from scratch or find an Apache 2.0 compatible alternative. + +## NOTICE file + +The NOTICE file contains important attribution and legal information. If you add dependencies that require attribution, you may need to update the NOTICE file. + +### When to update NOTICE + +Update the NOTICE file when: + +- Adding dependencies with attribution requirements +- Including code from other projects that requires attribution +- Adding third-party components with specific notice requirements + +--- + +**Previous:** [Contributions](contributions.md) | **Next:** [REUSE compliance](reuse-compliance.md) diff --git a/docs/about/licensing/index.md b/docs/about/licensing/index.md new file mode 100644 index 0000000..9c5767c --- /dev/null +++ b/docs/about/licensing/index.md @@ -0,0 +1,90 @@ + + +# Licensing + +BIOMASS BPS is an open-source project committed to transparency, collaboration, +and legal compliance. This section outlines licensing requirements, legal +obligations, and best practices for contributors and users of the codebase. + +::::{grid} 1 2 2 2 +:gutter: 3 +:class-container: intro-grid + +:::{grid-item-card} +:link: project-license +:link-type: doc +:class-card: intro-card + + **Project license** +^^^ +Apache 2.0 terms, patent grant, trademark usage, and obligations for downstream users. +::: + +:::{grid-item-card} +:link: contributions +:link-type: doc +:class-card: intro-card + + **Contributions** +^^^ +Contributor license terms, SPDX headers in source files, and the contributor checklist. +::: + +:::{grid-item-card} +:link: dependencies +:link-type: doc +:class-card: intro-card + + **Dependencies** +^^^ +License compatibility for libraries, third-party code, NOTICE file, and PR requirements. +::: + +:::{grid-item-card} +:link: reuse-compliance +:link-type: doc +:class-card: intro-card + + **REUSE compliance** +^^^ +`LICENSES/` directory, REUSE / SPDX standard, and the blocking CI gate. +::: + +:::: + +## Summary + +- **BIOMASS BPS uses Apache License 2.0** +- **All contributions must be Apache 2.0 compatible** +- **External dependencies must be license-compatible** +- **Proper attribution is required for third-party code** +- **Source files must include SPDX headers** (REUSE compliance: blocking CI gate) +- **`LICENSES/` directory must contain all license texts** +- **When in doubt, ask maintainers before submitting** + +## Questions + +If you have questions about licensing or legal requirements: + +1. Review the pages linked above +2. Check existing issues on GitHub for similar questions +3. Open a new issue with the `licensing` label +4. Contact maintainers for urgent legal questions + +If you believe you have found a license violation, do **not** create a public issue +immediately. Contact the project maintainers privately, provide specific details, +and allow time for investigation and resolution. + +```{toctree} +:caption: Licensing +:maxdepth: 2 +:hidden: + +Project license +Contributions +Dependencies +REUSE compliance +``` diff --git a/docs/about/licensing/project-license.md b/docs/about/licensing/project-license.md new file mode 100644 index 0000000..68f5e56 --- /dev/null +++ b/docs/about/licensing/project-license.md @@ -0,0 +1,64 @@ +# Project license + +BIOMASS BPS is licensed under the **Apache License 2.0** (Apache-2.0). This permissive open-source license allows you to: + +- **Use** the software for any purpose, including commercial use +- **Modify** the code to suit your needs +- **Distribute** the original or modified versions +- **Sublicense** and distribute under different terms (with proper attribution) + +## Key requirements of Apache 2.0 + +When using or distributing BIOMASS BPS, you must: + +1. **Include the original license and copyright notices** +2. **State any significant changes made to the code** +3. **Include a copy of the Apache 2.0 license** +4. **Include the NOTICE file** (if applicable) + +For the full text of the Apache License 2.0, see: [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +## Patent rights + +The Apache License 2.0 includes a patent grant clause. This means: + +- Contributors grant patent licenses for their contributions +- Users receive patent licenses for using the software +- Patent rights are terminated if you file a patent lawsuit against the project + +This provides additional protection for both contributors and users. + +## Trademark usage + +### BIOMASS BPS trademark + +The "BIOMASS BPS" name and logo are trademarks. When using BIOMASS BPS: + +- **You may use** the name to refer to the software +- **You may not use** the name or logo to imply endorsement without permission +- **You may not use** the name in a way that could cause confusion + +### ESA and BIOMASS mission + +BIOMASS BPS is affiliated with the European Space Agency (ESA) and the BIOMASS mission. Respect ESA's trademark and branding guidelines when referencing these entities. + +## License compliance checklist for users + +When using BIOMASS BPS: + +- [ ] Include the Apache 2.0 license file +- [ ] Include copyright notices +- [ ] Include the NOTICE file (if present) +- [ ] State any modifications made +- [ ] Comply with any additional requirements from dependencies + +## Resources + +- [Apache License 2.0 full text](https://www.apache.org/licenses/LICENSE-2.0) +- [Apache License 2.0 FAQ](https://www.apache.org/foundation/license-faq.html) +- [Open Source License Compatibility](https://opensource.org/licenses) +- [SPDX License List](https://spdx.org/licenses/) + +--- + +**Previous:** [Licensing](index.md) | **Next:** [Contributions](contributions.md) diff --git a/docs/about/licensing/reuse-compliance.md b/docs/about/licensing/reuse-compliance.md new file mode 100644 index 0000000..e24fc3f --- /dev/null +++ b/docs/about/licensing/reuse-compliance.md @@ -0,0 +1,49 @@ +# REUSE compliance + +BIOMASS BPS follows the **FSFE REUSE** standard for license compliance. This standard is automatically verified by `fsfe/reuse-action` in the CI pipeline (`job: baseline-reuse`): it is a **blocking gate** — a PR with non-compliant files will be rejected. + +For SPDX header format, see [Contributions](contributions.md#required-header-format). + +## LICENSES/ directory + +The `LICENSES/` directory at the root of the repository must contain the full text of every license used in the project. This is required by the REUSE standard. + +Currently required files: + +- `LICENSES/Apache-2.0.txt`: primary license of the project +- `LICENSES/MIT.txt`: license of certain dependencies or contributions + +If you add a dependency with a new compatible license, add its full text to this directory and use the correct SPDX identifier in the file headers. + +## What the REUSE standard requires + +1. **SPDX headers in every source file** (see [Contributions](contributions.md#required-header-format)) +2. **`LICENSES/` directory** at the root of the project containing the full license texts +3. **Valid SPDX identifiers** in headers (`Apache-2.0`, `MIT`, etc.) + +## Local verification + +```bash +# Via pre-commit (reuse hook) +pre-commit run reuse + +# Or directly +pip install reuse +reuse lint +``` + +## CI context + +REUSE verification runs as part of the baseline CI tier. See +[CI automation and contribution tiers](../../contributing/ci-automation-and-contribution-tiers.md) +for how baseline checks gate pull requests. + +## Resources + +- REUSE standard: https://reuse.software/ +- SPDX license list: https://spdx.org/licenses/ +- GitHub Action: https://github.com/fsfe/reuse-action + +--- + +**Previous:** [Dependencies](dependencies.md) | **Next:** [Licensing](index.md) diff --git a/docs/communication/channels.md b/docs/communication/channels.md new file mode 100644 index 0000000..a6cfd5e --- /dev/null +++ b/docs/communication/channels.md @@ -0,0 +1,131 @@ +# Communication channels + +We use multiple channels to ensure everyone can participate in the way that works best for them. Here's when to use each one: + +The following flowchart helps you choose the right communication channel: + +```{mermaid} +flowchart TD + Start([I need to communicate]) --> Type{Type of communication?} + + Type -->|General question| General{Urgent?} + Type -->|Bug to report| Bug[GitHub Issues] + Type -->|Feature idea| Feature[GitHub Issues] + Type -->|Discussion| Discussion[GitHub Discussions] + Type -->|Conflict| Conflict[Conflict Resolution Process] + Type -->|Security| Security[Security Email] + + General -->|Yes| OfficeHours[Office Hours
Weekly] + General -->|No| Discussion + + Bug --> IssueForm[Fill Issue template] + Feature --> IssueForm + IssueForm --> Submit[Submit Issue] + + Discussion --> Search{Search existing
discussions?} + Search -->|Found| Reply[Reply to discussion] + Search -->|Not found| New[Create new discussion] + + OfficeHours --> Join[Join session] + + Conflict --> ConflictProcess[See Conflict
Resolution section] + + Security --> SecurityEmail[Security email] + + classDef defaultStyle fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px,color:#333 + class Start,Type,General,Bug,Feature,Discussion,Conflict,Security,OfficeHours,IssueForm,Submit,Search,Reply,New,Join,ConflictProcess,SecurityEmail defaultStyle + + style Start fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px + style Bug fill:#ef9a9a,stroke:#e57373,stroke-width:2px + style Feature fill:#e1bee7,stroke:#e1bee7,stroke-width:2px + style Discussion fill:#a3d8b0,stroke:#a3d8b0,stroke-width:2px + style OfficeHours fill:#e1bee7,stroke:#e1bee7,stroke-width:2px + style Security fill:#ef9a9a,stroke:#e57373,stroke-width:2px +``` + +## GitHub Issues + +**Best for:** Actionable items that need triage, tracking, and implementation + +**[Open an Issue](https://github.com/BioPAL/BPS/issues/new/choose)** + +Use GitHub Issues when you want to: +- Report a bug or unexpected behavior +- Request a new feature or enhancement +- Propose a scientific algorithm or methodological change +- Report a documentation gap or security concern + +Open issues are reserved for actionable work. Questions, brainstorming, +and open-ended conversations belong on GitHub Discussions. + +**Tips:** +- Pick the matching issue template +- Use clear, descriptive titles +- Reference related issues or pull requests +- Wait for an approval label before starting implementation + +## GitHub Discussions + +**Best for:** General questions, ideas, scientific debate, and community conversation + +**[Open a Discussion](https://github.com/BioPAL/BPS/discussions)** + +Use GitHub Discussions when you want to: +- Ask questions about usage, installation, or the processing chain +- Brainstorm an idea before opening a tracking issue +- Discuss algorithms, validation, or methodology +- Share usage stories, papers, or conference talks + +Six categories help route the conversation: + +| Category | When to use | +|---|---| +| [Announcements](https://github.com/BioPAL/BPS/discussions/categories/announcements) | Project announcements, releases, governance decisions. Posts restricted to maintainers. | +| [Q&A](https://github.com/BioPAL/BPS/discussions/categories/q-a) | Usage, installation, API, processing chain, data formats. Mark the helpful reply as the answer. | +| [Ideas](https://github.com/BioPAL/BPS/discussions/categories/ideas) | Brainstorm a feature or a change before opening a tracking issue. | +| [Scientific discussions](https://github.com/BioPAL/BPS/discussions/categories/scientific-discussions) | Algorithms, validation, methodology, ATBD interpretations, references. | +| [Governance](https://github.com/BioPAL/BPS/discussions/categories/governance) | Project governance, maintainer paths, policy questions. | +| [Show and tell](https://github.com/BioPAL/BPS/discussions/categories/show-and-tell) | Introductions, usage stories, papers, conference talks. | + +**Tips:** +- Search existing discussions before posting +- Pick the category that best matches your topic +- Mark a helpful reply as the answer in Q&A +- Open a tracking issue once the conversation becomes actionable + +## Office Hours + +```{warning} +**Office Hours are not scheduled yet.** The format below describes what we plan to offer in the future. Until then, use [Q&A on GitHub Discussions](https://github.com/BioPAL/BPS/discussions/categories/q-a) for quick questions. +``` + +**Best for:** Quick clarifications, one-on-one support, and getting unstuck + +**Schedule:** Weekly, 1 hour sessions (planned) + +Office Hours are open sessions where maintainers and experienced contributors are available to help. This is perfect for: +- Quick questions that don't need a formal issue +- Getting help with setup or configuration +- Discussing contribution ideas before opening a PR +- One-on-one support for new contributors + +**How to join:** +- Check [Announcements on GitHub Discussions](https://github.com/BioPAL/BPS/discussions/categories/announcements) for the weekly schedule +- Join the video call or chat room +- No appointment needed - just drop in! + +## Email / Mailing List + +**Best for:** Important announcements, in-depth technical discussions, security issues, and governance matters + +Use email for: +- Important project announcements +- Security-related concerns (use the security email if available) +- In-depth technical discussions that benefit from longer-form communication +- Governance and policy matters + +**Note:** Email addresses are listed on the [Getting help](getting-help.md) page. + +--- + +**Previous:** [Communication](index.md) | **Next:** [Meetings and events](meetings.md) diff --git a/docs/communication/conflict-resolution.md b/docs/communication/conflict-resolution.md new file mode 100644 index 0000000..7e494f3 --- /dev/null +++ b/docs/communication/conflict-resolution.md @@ -0,0 +1,138 @@ +# Conflict resolution + +We recognize that conflicts can arise in any collaborative environment. Our goal is to resolve conflicts fairly, respectfully, and efficiently while maintaining a positive community atmosphere. + +## Our Approach + +We believe in addressing conflicts early and directly. Our process is designed to be: + +- **Fair:** All parties have a chance to be heard +- **Transparent:** Process and outcomes are documented (while respecting privacy) +- **Respectful:** We focus on issues, not personalities +- **Efficient:** We aim to resolve conflicts quickly without unnecessary bureaucracy + +## Resolution Process + +The following flowchart illustrates the conflict resolution process: + +```{mermaid} +flowchart TD + Conflict([Conflict identified]) --> Step1[Step 1: Direct Communication] + + Step1 --> Try1{Resolution
attempt?} + Try1 -->|Success| Resolved([Conflict resolved]) + Try1 -->|Failure| Step2[Step 2: Mediation] + + Step2 --> Mediator{Conflict type?} + Mediator -->|Contributor ↔ Contributor| CM1[Core Maintainer
as mediator] + Mediator -->|Contributor ↔ Maintainer| OSL1[Open Science Lead
as mediator] + Mediator -->|Maintainer ↔ Maintainer| OSL2[Open Science Lead
as mediator] + + CM1 --> Try2{Resolution?} + OSL1 --> Try2 + OSL2 --> Try2 + + Try2 -->|Success| Resolved + Try2 -->|Failure| Step3[Step 3: Escalation] + + Step3 --> Escalate[Escalate to
Open Science Lead or ESA] + Escalate --> Governance[Review by
Governance Group] + Governance --> Decision[Final decision] + Decision --> Resolved + + Resolved --> Document[Document conflict
and resolution] + + classDef defaultStyle fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px,color:#333 + class Conflict,Step1,Step2,Step3,Resolved,Try1,Mediator,CM1,OSL1,OSL2,Try2,Escalate,Governance,Decision,Document defaultStyle + + style Conflict fill:#ef9a9a,stroke:#e57373,stroke-width:2px + style Step1 fill:#e1bee7,stroke:#e1bee7,stroke-width:2px + style Step2 fill:#a3d8b0,stroke:#a3d8b0,stroke-width:2px + style Step3 fill:#e1bee7,stroke:#e1bee7,stroke-width:2px + style Resolved fill:#a3d8b0,stroke:#a3d8b0,stroke-width:3px + style Try1 fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px + style Try2 fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px +``` + +**Step 1: Direct Communication** + +Start by trying to resolve the conflict directly with the involved parties. Often, a respectful conversation can clear up misunderstandings. + +- Use private channels (direct messages, email) for sensitive matters +- Focus on the specific issue, not personal attributes +- Listen actively and try to understand different perspectives +- Look for common ground and mutually acceptable solutions + +**Step 2: Mediation** + +If direct communication doesn't resolve the issue, involve a neutral party: + +- For contributor-to-contributor conflicts: Contact a core maintainer +- For contributor-to-maintainer conflicts: Contact the Open Science Lead +- For maintainer-to-maintainer conflicts: Contact the Open Science Lead or ESA representative + +The mediator will: +- Listen to all parties +- Help clarify the issues +- Facilitate a constructive discussion +- Work toward a mutually acceptable solution + +**Step 3: Escalation** + +For serious conflicts or when mediation doesn't resolve the issue: + +- Escalate to the Open Science Lead or ESA representative +- The governance group will review the situation +- A formal decision will be made and communicated to all parties +- The resolution will be documented (respecting privacy) + +**Step 4: Documentation** + +All conflicts and their resolutions are documented (with appropriate privacy considerations) to: +- Ensure consistency in how conflicts are handled +- Learn from past experiences +- Maintain transparency in the process + +## Principles + +When resolving conflicts, we follow these principles: + +- **Focus on Issues:** Address the problem, not the person. Critique ideas, not individuals. +- **Seek Understanding:** Try to understand all perspectives before making judgments. +- **Find Common Ground:** Look for solutions that work for everyone involved. +- **Respect Decisions:** Once a decision is made through the proper process, respect it even if you disagree. +- **Maintain Privacy:** Keep sensitive matters private while being transparent about the process. + +## Escalation Path + +Here's the typical escalation path for different types of conflicts: + +1. **Contributor ↔ Contributor** + - Start with direct discussion + - If needed, involve a core maintainer as mediator + +2. **Contributor ↔ Maintainer** + - Start with direct discussion + - If needed, involve another core maintainer or Open Science Lead as mediator + +3. **Maintainer ↔ Maintainer** + - Start with direct discussion + - If needed, involve the Open Science Lead as mediator + - Escalate to ESA if necessary + +4. **Governance Issues** + - Discuss in Governance Meetings + - Final decision by ESA in consultation with governance group + +## Getting Help + +If you're unsure how to handle a conflict or need guidance: + +- Review the [Code of Conduct](../contributing/code-of-conduct.md) for behavioral guidelines +- Contact a core maintainer or the Open Science Lead +- Open a private issue or discussion if you prefer anonymity +- Remember: asking for help is always okay + +--- + +**Previous:** [First Developer Meeting](developer-meeting.md) | **Next:** [Getting help](getting-help.md) diff --git a/docs/communication/developer-meeting.md b/docs/communication/developer-meeting.md new file mode 100644 index 0000000..5bf9d0c --- /dev/null +++ b/docs/communication/developer-meeting.md @@ -0,0 +1,30 @@ + + +# First Developer Meeting + +```{meeting-status} 2026-06-30-first-dev-meeting +``` + +**30 June 2026** · Open Source & Open Science · 37 slides + +This session introduces the BioMASS Processing Suite as an open project: why ESA is opening the processor suite, what is already available on the documentation portal and GitHub, and how to contribute through issues and pull requests. The deck is organised in four parts—intent and principles, the live resources today, the end-to-end GitHub workflow (from issue templates to CI/CD and review), and how the community stays connected through channels and meetings. + +:::{div} bps-deck-hero + + + +::: + +Keyboard in the deck: **← / →** or **Space** · **F** present mode · **R** reset + +--- + +**Previous:** [Meetings and events](meetings.md) | **Next:** [Conflict resolution](conflict-resolution.md) diff --git a/docs/communication/getting-help.md b/docs/communication/getting-help.md new file mode 100644 index 0000000..2f9de9f --- /dev/null +++ b/docs/communication/getting-help.md @@ -0,0 +1,49 @@ +# Getting help + +## Code of Conduct + +All community members are expected to follow our [Code of Conduct](../contributing/code-of-conduct.md). The Code of Conduct ensures a welcoming, inclusive, and respectful environment for everyone. + +**Key points:** + +- Be respectful and inclusive +- Welcome newcomers and help them learn +- Focus on constructive feedback +- Respect different viewpoints and experiences + +**Reporting violations:** +If you experience or witness behavior that violates the Code of Conduct, please refer to our [Code of Conduct Reporting Guide](../contributing/code-of-conduct.md#reporting-guide). We take all reports seriously and handle them confidentially. + +## Still not sure where to go? + +- **General questions:** [Q&A on GitHub Discussions](https://github.com/BioPAL/BPS/discussions/categories/q-a) +- **Actionable work:** [GitHub Issues](https://github.com/BioPAL/BPS/issues/new/choose) +- **Quick help:** [Q&A on GitHub Discussions](https://github.com/BioPAL/BPS/discussions/categories/q-a) (Office Hours are planned — see [Channels](channels.md)) +- **Communication questions:** Ask in the [Governance category on GitHub Discussions](https://github.com/BioPAL/BPS/discussions/categories/governance) +- **Code of Conduct concerns:** See the [Reporting Guide](../contributing/code-of-conduct.md#reporting-guide) + +## Contact information + +**General and institutional enquiries:** [info@biomass.org](mailto:info@biomass.org) + +Use this address for partnership, press, or other matters that are not appropriate for a public GitHub thread. For technical questions, contributions, and community interaction, prefer the channels listed above. + +Named contacts for governance and processor modules are in [Repository stewards](../governance/repository-stewards.md) and [Processor leads](../governance/processor-leads.md). + +## Contributing to this guide + +This communication guide is a living document. If you have suggestions for improvement, please: + +1. Open an issue or discussion with your ideas +2. Propose changes via a pull request +3. Share feedback during Community Meetings + +We're always looking to improve how we communicate and collaborate! + +**Last updated:** 2026 + +--- + +**Previous:** [Conflict resolution](conflict-resolution.md) | **Next:** [Communication](index.md) + +**See also:** [Get started](../getting-started/index.md) | [Contributing](../contributing/index.md) | [Governance](../governance/index.md) diff --git a/docs/communication/index.md b/docs/communication/index.md new file mode 100644 index 0000000..e88e9a2 --- /dev/null +++ b/docs/communication/index.md @@ -0,0 +1,83 @@ + + +# Communication + +Welcome to the BioPAL community. This guide explains how we communicate, +collaborate, and work together whether you are a new contributor or a +long-time maintainer. + +::::{grid} 1 2 2 2 +:gutter: 3 +:class-container: intro-grid + +:::{grid-item-card} +:link: channels +:link-type: doc +:class-card: intro-card + + **Channels** +^^^ +GitHub Issues, Discussions, Office Hours, and email when to use each. +::: + +:::{grid-item-card} +:link: meetings +:link-type: doc +:class-card: intro-card + + **Meetings and events** +^^^ +Past session slides, upcoming technical and community meetings, workshops. +::: + +:::{grid-item-card} +:link: conflict-resolution +:link-type: doc +:class-card: intro-card + + **Conflict resolution** +^^^ +How we handle disagreements fairly and respectfully. +::: + +:::{grid-item-card} +:link: getting-help +:link-type: doc +:class-card: intro-card + + **Getting help** +^^^ +Code of Conduct, contacts, and how to improve this guide. +::: + +:::: + +## Where to start + +**I have a question or need help:** +- General questions → [GitHub Discussions](https://github.com/BioPAL/BPS/discussions) +- Actionable bug, feature, or proposal → [GitHub Issues](https://github.com/BioPAL/BPS/issues/new/choose) +- Quick help → [Q&A on GitHub Discussions](https://github.com/BioPAL/BPS/discussions/categories/q-a) (Office Hours are planned — see [Channels](channels.md)) + +**I want to contribute:** +- Start with the [Contributing guide](../contributing/index.md) +- Browse [Meetings and events](meetings.md) for session slides (including the [First Developer Meeting](developer-meeting.md)) + +**I have a conflict or concern:** +- Review [Conflict resolution](conflict-resolution.md) +- Check the [Code of Conduct](../contributing/code-of-conduct.md) + +```{toctree} +:caption: Communication +:maxdepth: 1 +:hidden: + +Channels +Meetings and events +First Developer Meeting +Conflict resolution +Getting help +``` diff --git a/docs/communication/meetings.md b/docs/communication/meetings.md new file mode 100644 index 0000000..30e9f2e --- /dev/null +++ b/docs/communication/meetings.md @@ -0,0 +1,103 @@ + + +# Meetings and events + +We organize meetings and events to keep the BioPAL community connected. Session slides and recordings are published here as they become available. + +## Recent meetings and events + +:::{div} bps-last-event + +
+
+

Latest session

+

First Developer Meeting
Open Source & Open Science

+
    +
  • 30 June 2026
  • +
  • +
  • 37 slides
  • +
  • Yoann Rey-Ricord · ACRI-ST
  • +
+ +
+ +
+ +::: + +```{note} +More meeting decks will appear in this section as we hold technical sessions, community calls, and workshops. +``` + +## Upcoming formats + +```{warning} +**Recurring meetings are not scheduled yet.** The formats below describe what we plan to organise. Invitations will be posted in [Announcements on GitHub Discussions](https://github.com/BioPAL/BPS/discussions/categories/announcements) when dates are set. +``` + +### Technical meetings + +**Who:** Maintainers and active contributors +**When:** Twice per month (bi-monthly) +**Duration:** 1 hour +**Format:** Video call with public meeting notes + +**What we discuss:** +- Review of Tier 0–1 pull requests +- Technical discussions and architecture decisions +- Short-term planning and priorities +- Code quality improvements +- Quick wins and blockers + +### Governance meetings + +**Who:** Steering Committee (ESA, Open Science Lead, Core Maintainers, Scientific Experts) +**When:** Once per quarter +**Duration:** 1.5 hours +**Format:** Video call with public meeting notes and decisions + +**What we discuss:** +- Review of Tier 2 pull requests +- Strategic decisions and roadmap +- Policy updates and governance changes +- Resource allocation and priorities +- Long-term vision and planning + +### Community meetings + +**Who:** All contributors and users +**When:** Once per quarter +**Duration:** 1 hour +**Format:** Video call with public meeting notes and recordings + +**What we discuss:** +- Feature presentations and demos +- Q&A sessions with maintainers +- Feedback collection from users +- Project announcements and updates +- Community highlights and recognition + +### Workshops and training + +**Onboarding workshops** (monthly, 2 hours) · **Technical training** (quarterly) · **Hackathons** (annual) · **Intercomparison exercises** (regular) + +Watch [Announcements on GitHub Discussions](https://github.com/BioPAL/BPS/discussions/categories/announcements) for dates. + +--- + +**Previous:** [Channels](channels.md) | **Next:** [First Developer Meeting](developer-meeting.md) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..c20da0a --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,279 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 +# +# Sphinx configuration for the BioPAL documentation site (BPS content). +# Theme: PyData Sphinx Theme (same family as docs.xarray.dev). +# Source: Markdown via MyST parser. + +import os +import sys +from datetime import datetime + +sys.path.insert(0, os.path.abspath("_ext")) + +# ----------------------------------------------------------------------------- +# Project information +# ----------------------------------------------------------------------------- +project = "BioPAL" +author = "ACRI-ST / ESA / Aresys" +copyright = f"{datetime.now().year}, {author}" +release = "0.2.0" + +# ----------------------------------------------------------------------------- +# General configuration +# ----------------------------------------------------------------------------- +extensions = [ + "myst_parser", # Markdown support (CommonMark + extensions) + "sphinx_copybutton", # Copy button on code blocks + "sphinx_design", # Cards, grids, tabs, etc. + "sphinxcontrib.mermaid", # Mermaid diagrams (used in CONTRIBUTING_PART1) + "sphinxcontrib.bibtex", # BibTeX bibliography (ATBD/PFD conversions) + "atbd_sidebar_toc", # PDF-style collapsible TOC in ATBD left sidebar + "atbd_doc_refs", # [ADn] / [RDn] / Fig.N / sec. / eq. links in the ATBD + "science_guide_nav", # Science Guide hub: ATBD list sidebar (not chapter tree) + "bps_version", # Latest bps-v* tag for homepage version badge + "meeting_status", # Status badge from presentations//meeting.yaml +] + +# Tell Sphinx that .md files exist alongside .rst +source_suffix = { + ".md": "markdown", + ".rst": "restructuredtext", +} + +# Master document (the landing page) +master_doc = "index" + +# Patterns to ignore +exclude_patterns = [ + "_build", + ".venv", + "**/.venv/**", + "_ext", + "Thumbs.db", + ".DS_Store", + "README.md", + "**/README.md", + "science-guide/_includes/*", + "science-guide/draft-pfd-l2-fh.md", + # Tutorial bundle assets: shipped with the docs but not Sphinx-rendered. + "tutorials/run-bps-locally/scripts/*", + "tutorials/run-bps-locally/notebooks/*", + "tutorials/run-bps-locally/CONFIGURATION_FILE/**", +] + +# Suppress noisy warnings. +# - "image.not_readable": raw tags reference SVGs in `_extra_static/`, +# which Sphinx doesn't track as source images. They render correctly in the +# browser, so the warning is misleading. +suppress_warnings = [ + "image.not_readable", + "toc.not_included", + "myst.header", +] + +# Language +language = "en" + +# ----------------------------------------------------------------------------- +# MyST parser configuration +# ----------------------------------------------------------------------------- +# Enable common MyST extensions for richer Markdown. +myst_enable_extensions = [ + "amsmath", # AMS math environments in MyST + "colon_fence", # ::: fenced directives + "deflist", # definition lists + "dollarmath", # $...$ and $$...$$ math (ATBD equations) + "html_admonition", # raw HTML admonitions + "html_image", # raw HTML tags + "linkify", # auto-link bare URLs + "replacements", # textual replacements (e.g. (c) -> ©) + "smartquotes", # smart quotes + "substitution", # variable substitution + "tasklist", # GitHub-style task lists +] + +# Auto-generate anchors for headings up to level 6 (ATBD §3.4.4.2.1, etc.) +myst_heading_anchors = 6 + +# Numbered figures and tables ({numref}`fig:...` in ATBD pages). +# Equation numbers use PDF-style \tag{} in ATBD sources (not auto (1), (2), …). +numfig = True +math_numfig = False + +# ----------------------------------------------------------------------------- +# BibTeX (sphinxcontrib-bibtex). Edit docs/references.bib for the site build. +# To refresh from your local private pipeline: make sync-bib (requires private/atbd-conversion/). +bibtex_bibfiles = ["references.bib"] +bibtex_default_style = "plain" +bibtex_reference_style = "author_year" + +# ----------------------------------------------------------------------------- +# HTML output configuration +# ----------------------------------------------------------------------------- +html_theme = "pydata_sphinx_theme" + +html_title = "BioPAL Documentation" +html_short_title = "BioPAL" + +# Base URL when docs are served under a subpath (e.g. biomass-disc.info/docs/). +html_baseurl = os.environ.get("SPHINX_HTML_BASEURL", "/docs/") + +# Main website URL for the "return to site" navbar link (site root when embedded). +site_home_url = os.environ.get("SPHINX_SITE_HOME_URL", "/") + +# Static assets (custom CSS, logos, favicons) +html_static_path = ["_static"] + +# Extra paths copied as-is to the build output (raw files, not processed by Sphinx). +# The CONTENTS of these folders are copied to the build root, so we wrap our +# `images/` folder inside `_extra_static/images/` to get the path right. +# We use this for SVG diagrams referenced via raw HTML / tags, +# which Sphinx does not track automatically. +html_extra_path = ["_extra_static"] + +# Logo and favicon. The PyData theme auto-scales the logo in the navbar header. +html_logo = "_static/logos/BioPAL_textonright.png" +# html_favicon = "_static/favicon.ico" + +# Right sidebar: every page gets "On this page" + Edit on GitHub. +# Homepage hides the secondary sidebar entirely via its own frontmatter. +# ATBD chapters add the PDF download button in addition. +_secondary_sidebar_items = { + "index": [], + "**": ["page-toc", "edit-this-page"], + "science-guide/atbd-l2-agb/*": [ + "page-toc", + "atbd-download-pdf.html", + "edit-this-page", + ], +} + +# Theme-specific options (PyData Sphinx Theme). +# See https://pydata-sphinx-theme.readthedocs.io/en/stable/user_guide/index.html +html_theme_options = { + # Top navbar xarray-style: section names in the header, search on the right + "navbar_start": ["navbar-logo"], + "navbar_center": ["navbar-nav"], + "navbar_end": ["theme-switcher", "navbar-icon-links"], + "navbar_persistent": ["search-button"], + # How many top-nav links to show before collapsing into "More" dropdown + "header_links_before_dropdown": 6, + # Icon links (top right) + "icon_links": [ + { + "name": "GitHub", + "url": "https://github.com/BioPAL/BPS", + "icon": "fa-brands fa-github", + "type": "fontawesome", + }, + ], + # Left sidebar (section navigation) + # show_nav_level = 1 → only show top-level section name expanded, + # sub-pages appear when the user is inside that section. This matches xarray. + "show_nav_level": 1, + "navigation_depth": 4, + "collapse_navigation": False, + # Right sidebar (page table of contents) + "show_toc_level": 2, + # Footer + "footer_start": ["copyright"], + "footer_end": ["sphinx-version", "theme-version"], + # Misc + "use_edit_page_button": True, + "show_prev_next": True, + "announcement": "BIOMASS BPS is an Open Science project — every contribution is welcome.", + # Secondary sidebar (right): in-page TOC on content pages; section hubs + # and the homepage omit "On this page" (see _SECTION_INDEX_PAGES). + "secondary_sidebar_items": _secondary_sidebar_items, + # Primary sidebar (left): always start collapsed at depth 1, then expand + # the current section. This gives the same UX as xarray. + # Ethical ads removed: local builds showed a violet RTD placeholder and + # this site is not served from Read the Docs. + "primary_sidebar_end": [], +} + +# Context used by the "Edit on GitHub" button. +# These values feed the URL pattern: github.com///edit///.rst +html_context = { + "github_user": "BioPAL", + "github_repo": "BPS", + "github_version": os.environ.get( + "SPHINX_GITHUB_VERSION", "docs/sphinx-site-migration" + ), + "doc_path": "docs", + "site_home_url": site_home_url, + # default_mode: "auto" lets the user's choice (stored in localStorage) + # take precedence on subsequent page loads. "auto" also matches the OS + # preference on first visit. + "default_mode": "auto", +} + +# Left sidebar: section navigation on all pages except those listed below. +# - Homepage: full-width, no sidebars. +# - Hubs without sibling chapters (user-guide, getting-started): nothing +# meaningful to show, so the sidebar is hidden rather than rendered empty. +# - developer-guide/index: orphan redirect page. +# - Science Guide and ATBD chapters use custom collapsible templates. +html_sidebars = { + "index": [], + "user-guide/index": [], + "getting-started/index": [], + "developer-guide/index": [], + "science-guide/index": [ + "sidebar-collapse", + "science-guide-atbd-nav.html", + ], + "science-guide/atbd-l2-agb/*": [ + "sidebar-collapse", + "atbd-sidebar-toc.html", + ], + "**": ["sidebar-nav-bs"], +} + +# Custom CSS to override / extend theme defaults +html_css_files = [ + "custom.css", +] + +templates_path = ["_templates"] + +# Single-ATBD PDF export (make atbd-pdf ATBD=atbd-l2-agb). +_sphinx_atbd_pdf = os.environ.get("SPHINX_ATBD_PDF") +if _sphinx_atbd_pdf: + _atbd_doc = _sphinx_atbd_pdf.removesuffix("/index").removesuffix("/") + if _atbd_doc.startswith("science-guide/"): + _atbd_doc = _atbd_doc.removeprefix("science-guide/") + _atbd_latex_stem = _atbd_doc.split("/")[-1] + _atbd_latex_titles = { + "atbd-l2-agb": "Above-Ground Biomass Product ATBD (BioPAL web export)", + } + _atbd_latex_title = _atbd_latex_titles.get(_atbd_latex_stem, "BioPAL ATBD") + latex_documents = [ + ( + f"science-guide/{_atbd_doc}/index", + f"{_atbd_latex_stem}.tex", + _atbd_latex_title, + author, + "manual", + ), + ] + latex_engine = "xelatex" + latex_elements = { + "papersize": "a4paper", + "pointsize": "11pt", + # XeLaTeX + DejaVu handles Unicode math symbols in ATBD body text. + "fontpkg": "", + "preamble": r""" +\usepackage{fontspec} +\setmainfont{DejaVu Serif}[Scale=0.95] +\setmonofont{DejaVu Sans Mono}[Scale=0.85] +\usepackage{amsmath,amssymb} +\usepackage{graphicx} +\usepackage{hyperref} +""", + } + # Mermaid diagrams need mermaid-cli (mmdc) and a headless browser; they are + # skipped with a warning when unavailable. Re-run `make atbd-pdf` after + # installing mmdc for complete figure coverage. + mermaid_output_format = "png" diff --git a/docs/contributing/architecture.md b/docs/contributing/architecture.md new file mode 100644 index 0000000..edc142e --- /dev/null +++ b/docs/contributing/architecture.md @@ -0,0 +1,775 @@ +# Architecture & Development Guide + +Complete guide to code structure, module interfaces, development patterns, and development practices for the BIOMASS Processing Suite (BPS). + +--- + +## Table of Contents + +1. [Repository Architecture](#repository-architecture): layout, modules, root config, CI test harness +2. [Code Structure](#code-structure): data flow between processors +3. [Module Interfaces](#module-interfaces) +4. [Development Patterns](#development-patterns) +5. [Development Guide](#development-guide) +6. [Best Practices](#best-practices) +7. [Automation Tools](#automation-tools): nox, xsdata + +--- + +## Repository Architecture + +### High-Level Repository Layout + +The BIOMASS Processing Suite (BPS) is an industrial monorepo developed by **Aresys** and **ACRI-ST** under ESA contract. It hosts all processors for the ESA BIOMASS mission. Each `bps-*` subdirectory is an independent Python package with its own `pyproject.toml`, tests, and changelog. + +``` +biomass-bps/ +├── bps-common/ # Shared types, utilities and interfaces +├── bps-task-tables/ # Processing task table definitions +├── bps-transcoder/ # BIOMASS data format transcoder +│ +├── bps-l1_pre_processor/ # L1 pre-processing +├── bps-l1_core_processor/ # L1 core processing +├── bps-l1_framing_processor/ # L1 framing +├── bps-l1_binaries/ # L1 compiled binaries +├── bps-l1_processor/ # L1 main processor +│ +├── bps-l2a_processor/ # L2A processor +├── bps-l2b_agb_processor/ # L2B Above-Ground Biomass (AGB) +├── bps-l2b_fd_processor/ # L2B Forest Disturbance (FD) +├── bps-l2b_fh_processor/ # L2B Forest Height (FH) +│ +├── bps-stack_pre_processor/ # SAR stack pre-processing +├── bps-stack_coreg_processor/ # SAR stack co-registration +├── bps-stack_cal_processor/ # SAR stack calibration +├── bps-stack_binaries/ # Stack compiled binaries +├── bps-stack_processor/ # SAR stack main processor +│ +├── bps-dockerfiles/ # Container definitions +│ +├── tests/ # Repository-level CI harness (see below) +│ ├── baseline/ +│ ├── extended/ +│ └── heavy/ +│ +├── VERSION # Global suite version (format MM.PP) +├── pyproject.toml # ruff, mypy, black configuration +├── ruff.toml # Linting configuration +├── pytest.ini # Test markers definition +├── noxfile.py # Automation sessions (XSD, versioning) +├── .pre-commit-config.yaml # Local validation hooks +├── .github/ +│ ├── tier-policy.yml # Automatic tier classification policy +│ ├── CODEOWNERS # Review routing by module +│ └── workflows/ +│ └── ci.yml # Unified CI/CD pipeline +└── LICENSES/ + ├── Apache-2.0.txt + └── MIT.txt +``` + +### Modules by Family + +#### Common libraries + +| Module | Role | +| ------------------ | -------------------------------------------------------------- | +| `bps-common/` | Shared types, utilities, and interfaces used by all processors | +| `bps-task-tables/` | Processing task table definitions | +| `bps-transcoder/` | BIOMASS data format transcoder | + +#### L1 processors + +| Module | Role | +| --------------------------- | ------------------ | +| `bps-l1_pre_processor/` | L1 pre-processing | +| `bps-l1_core_processor/` | L1 core processing | +| `bps-l1_framing_processor/` | L1 framing | +| `bps-l1_binaries/` | L1 binary tools | +| `bps-l1_processor/` | L1 main processor | + +#### L2 processors + +| Module | Role | +| ------------------------ | ------------------------------- | +| `bps-l2a_processor/` | L2A processor | +| `bps-l2b_agb_processor/` | L2B: Above-Ground Biomass (AGB) | +| `bps-l2b_fd_processor/` | L2B: Forest Disturbance (FD) | +| `bps-l2b_fh_processor/` | L2B: Forest Height (FH) | + +#### SAR stack processors + +| Module | Role | +| ---------------------------- | ------------------------- | +| `bps-stack_pre_processor/` | SAR stack pre-processing | +| `bps-stack_coreg_processor/` | SAR stack co-registration | +| `bps-stack_cal_processor/` | SAR stack calibration | +| `bps-stack_binaries/` | Stack binary tools | +| `bps-stack_processor/` | SAR stack main processor | + +#### Infrastructure + +| Module | Role | +| ------------------ | ---------------------------------------------- | +| `bps-dockerfiles/` | Docker files for containerising the processors | + +### Root Configuration Files + +These files apply to the entire monorepo: + +| File | Purpose | +| -------------------------- | ------------------------------------------------------------------------------------------ | +| `VERSION` | Global BPS version (format `MM.PP`, e.g. `5.0`) | +| `pyproject.toml` | Monorepo-level tool configuration (black, ruff, mypy) | +| `ruff.toml` | Ruff linting configuration (`line-length = 120`) | +| `pytest.ini` | Pytest markers (`unit`, `baseline`, `extended`, `heavy`, `smoke`, `integration`, `public`) | +| `noxfile.py` | Automation sessions (see [Automation Tools](#automation-tools)) | +| `.pre-commit-config.yaml` | Pre-commit hooks for local validation | +| `.github/tier-policy.yml` | Automatic CI tier classification policy | +| `.github/CODEOWNERS` | Required reviewers per path (ESA gate on `VERSION` and `CHANGELOG.md`) | +| `.github/workflows/ci.yml` | Unified CI/CD pipeline | + +### Structure of a Typical Module + +Every `bps-*` module follows this layout: + +``` +bps-/ +├── src/ # Python source code +│ └── bps_/ +├── tests/ +│ ├── unit/ # Unit tests (pytest -m unit) +│ ├── baseline/ # Baseline regression tests (pytest -m baseline) +│ ├── extended/ # Extended tests: TDS required (pytest -m extended) +│ └── heavy/ # Heavy tests: TDS required (pytest -m heavy) +├── pyproject.toml # Package configuration (build, ruff, mypy, black) +└── CHANGELOG.md # Module-level changelog +``` + +Install a module for development: + +```bash +cd bps- +pip install -e ".[dev]" +``` + +Install pre-commit hooks once at the repository root (applies to the whole monorepo): + +```bash +pre-commit install --hook-type commit-msg +pre-commit install +``` + +### Repository-Level CI Test Harness + +In addition to per-module `tests/`, the monorepo root provides shared suites used by [`.github/workflows/ci.yml`](https://github.com/BioPAL/BPS/blob/main/.github/workflows/ci.yml): + +| Directory | Pytest marker | When CI runs it | +| ---------------- | ------------- | --------------------------------- | +| `test/baseline/` | `baseline` | Every PR (baseline marker signal) | +| `test/extended/` | `extended` | Tier 1+ (Extended job) | +| `test/heavy/` | `heavy` | Tier 2 (Heavy job) | + +Changes to these paths (and to `pytest.ini`) are **`locked_paths`** in [`.github/tier-policy.yml`](https://github.com/BioPAL/BPS/blob/main/.github/tier-policy.yml): they escalate CI to at least **Tier 1 (Extended)**. Per-module paths matching `**/test/baseline/**`, `test/extended/**`, or `test/heavy/**` are **`sme_owned_paths`** (also Tier 1+). **Tier 2 (Heavy)** applies when policy rules require it (e.g. `VERSION` change on a PR targeting `main`, `tier_2_paths`, or manual `run_heavy` on dispatch). + +### Architecture Principles + +**Single Repository:** + +- One ESA external Git repository for all BIOMASS L2 code +- Hosts operational code, scientific developments, validation tools, and documentation +- Avoids divergence between operational and research versions +- Ensures validation workflows apply to production code + +**Stability and Clarity:** + +- Stable paths to key components +- Consistent structure for documentation and tools +- Easy navigation for new contributors +- Support for long-term evolution + +**Separation of Concerns:** + +- Core L2 algorithms separate from I/O +- Validation tools separate from processing code +- Configuration separate from implementation +- Documentation organized by audience + +--- + +## Code Structure + +### Component Interactions + +```{mermaid} +flowchart LR + L1[L1 Products] + AUX[Auxiliary data / XSD schemas] + + COMMON[bps-common] + TRANSCODER[bps-transcoder] + L1PROC[bps-l1_processor] + STACK[bps-stack_processor] + L2A[bps-l2a_processor] + L2B_AGB[bps-l2b_agb_processor] + L2B_FH[bps-l2b_fh_processor] + L2B_FD[bps-l2b_fd_processor] + + L1 --> TRANSCODER + AUX --> COMMON + COMMON --> L1PROC + COMMON --> STACK + TRANSCODER --> L1PROC + L1PROC --> STACK + STACK --> L2A + L2A --> L2B_AGB + L2A --> L2B_FH + L2A --> L2B_FD + L2B_AGB --> L2[L2 Products] + L2B_FH --> L2 + L2B_FD --> L2 + + classDef defaultStyle fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px,color:#333 + class L1,AUX,COMMON,TRANSCODER,L1PROC,STACK,L2A,L2B_AGB,L2B_FH,L2B_FD,L2 defaultStyle +``` + +**Data Flow:** + +1. **Input**: L1 products ingested and transcoded to internal format +2. **Stack processing**: SAR stack co-registration and calibration +3. **L2A**: Intermediate L2 products (polarimetric decomposition) +4. **L2B**: Final L2 products: AGB, Forest Height, Forest Disturbance +5. **Output**: L2 products in ESA-defined formats + +--- + +## Module Interfaces + +### Interface Design Principles + +**Explicit Parameters:** + +- Functions take explicit inputs (no hidden dependencies) +- Configuration passed as parameters or config objects +- No global state modification + +**Clear Return Values:** + +- Structured return types (xarray Datasets, named tuples, or typed dictionaries) +- Consistent return formats across modules +- Error conditions handled via exceptions + +**Type Hints:** + +- All public functions have type hints +- Use `typing` module for complex types +- Enable static type checking with `mypy` + +**Documentation:** + +- NumPy-style docstrings for all public functions +- Parameter descriptions with types and constraints +- Return value descriptions +- Examples where helpful + +### Example Interface + +```python +from typing import Dict, Optional +import numpy as np +import xarray as xr + +def calculate_biomass( + sar_data: xr.Dataset, + incidence_angle: xr.DataArray, + config: Dict[str, float], + aux_data: Optional[xr.Dataset] = None +) -> xr.Dataset: + """ + Calculate above-ground biomass from SAR data. + + Parameters + ---------- + sar_data : xr.Dataset + Input SAR backscatter data with variables: + - sigma0: backscatter coefficients + - coherence: interferometric coherence (if available) + incidence_angle : xr.DataArray + Incidence angle in degrees, shape matching sar_data + config : Dict[str, float] + Configuration parameters: + - threshold: detection threshold + - max_iterations: maximum iterations + - biomass_range: tuple (min, max) in Mg/ha + aux_data : xr.Dataset, optional + Auxiliary data (DEM, land cover, etc.) + + Returns + ------- + xr.Dataset + Biomass product with variables: + - agb: above-ground biomass in Mg/ha + - agb_uncertainty: uncertainty estimates + - quality_flag: quality indicators + + Raises + ------ + ValueError + If sar_data contains invalid values + RuntimeError + If convergence not achieved + + Examples + -------- + >>> sar_data = xr.Dataset({ + ... 'sigma0': (['y', 'x'], np.array([[0.1, 0.2], [0.3, 0.4]])) + ... }) + >>> angle = xr.DataArray([[30.0, 31.0], [32.0, 33.0]], dims=['y', 'x']) + >>> config = {'threshold': 0.5, 'max_iterations': 100} + >>> result = calculate_biomass(sar_data, angle, config) + """ + pass +``` + +### Module Dependencies + +**Dependency Rules:** + +- `bps-common` has no dependencies on other BPS modules +- `bps-transcoder` depends on `bps-common` +- L1/L2/stack processors depend on `bps-common` and may depend on `bps-transcoder` +- No circular dependencies allowed between modules + +**External Dependencies:** + +- Scientific: numpy, scipy, xarray, numba, netcdf4 +- Data models: xsdata (generated from XSD schemas) +- Testing: pytest, pytest-cov +- Development: black, ruff, mypy, pre-commit, nox + +--- + +## Development Patterns + +### Design Patterns + +**Pure Functions:** + +- Prefer functions that take explicit inputs and return outputs +- Avoid modifying global state +- Make functions testable and predictable + +```python +# Good: Pure function +def calculate_agb(sar_data: np.ndarray, config: dict) -> np.ndarray: + """Calculate AGB from SAR data.""" + # Process data + result = process(sar_data, config) + return result + +# Avoid: Function with side effects +def calculate_agb(sar_data: np.ndarray): + """Calculate AGB (modifies global state).""" + global global_config + # Uses global config - hard to test + pass +``` + +**Separation of Concerns:** + +- Keep I/O separate from processing +- Keep configuration separate from algorithms +- Keep validation separate from core code + +**Error Handling:** + +- Use structured error handling +- Provide informative error messages +- Allow errors to propagate with context + +```python +try: + result = process_data(input_file) +except FileNotFoundError: + logger.error(f"Input file not found: {input_file}") + raise +except ValueError as e: + logger.error(f"Invalid input data: {e}") + raise +``` + +**Configuration Management:** + +- Use configuration files (YAML/JSON) or dictionaries +- Support configuration inheritance +- Validate configuration at startup +- Document all configuration options + +```python +# Load and validate configuration +config = load_config("config.yaml") +validate_config(config) +result = process_with_config(data, config) +``` + +### Data Structures + +**Use xarray for Geophysical Data:** + +- Labeled multi-dimensional arrays +- Coordinate system support +- Metadata preservation +- CF conventions compliance + +```python +import xarray as xr + +# Create dataset with coordinates +data = xr.Dataset( + { + 'agb': (['latitude', 'longitude'], biomass_array) + }, + coords={ + 'latitude': lat_coords, + 'longitude': lon_coords, + 'time': time_coords + }, + attrs={ + 'title': 'Above Ground Biomass', + 'units': 'Mg/ha' + } +) +``` + +**Use NumPy for Arrays:** + +- Efficient numerical operations +- Well-tested and optimized +- Standard in scientific Python + +**Use Dictionaries for Configuration:** + +- Flexible and readable +- Easy to serialize (YAML/JSON) +- Support nested structures + +--- + +## Development Guide + +### Setting Up Development Environment + +**Requirements:** Python 3.12 + +1. **Clone Repository:** + + ```bash + git clone + cd biomass-bps + ``` + +2. **Create Virtual Environment:** + + ```bash + python3.12 -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` + +3. **Install a Module (example: bps-l2b_fh_processor):** + + ```bash + cd bps-l2b_fh_processor + pip install -e ".[dev]" + ``` + +4. **Set Up Pre-commit Hooks:** + + ```bash + pip install pre-commit + pre-commit install + pre-commit install --hook-type commit-msg # Required for DCO check + ``` + +5. **Verify Setup:** + ```bash + pytest tests/unit/ + ``` + +### Development Workflow + +**External contributors** must fork the repository. **Internal contributors** (with write access) can branch directly. + +1. **Fork and Clone (external contributors):** + + ```bash + # Fork via GitHub UI, then: + git clone https://github.com//biomass-bps.git + cd biomass-bps + git remote add upstream + ``` + + **Internal contributors** skip the fork and clone directly: + + ```bash + git clone + cd biomass-bps + ``` + +2. **Create Feature Branch:** + + ```bash + git checkout develop + git pull upstream develop # or origin develop for internal contributors + git checkout -b feature/your-feature-name + ``` + +3. **Make Changes:** + - Write code following [Code standards](code-standards.md) + - Add tests for new functionality + - Update documentation + +4. **Run Local Checks:** + + ```bash + # Auto-format code + black src/ tests/ + + # Lint and auto-fix + ruff check --fix src/ tests/ + + # Type check + mypy src/ + + # Run tests + pytest tests/unit/ -m unit + pytest test/baseline/ -m baseline + ``` + +5. **Commit Changes (DCO required):** + + ```bash + git add . + git commit -s -m "feat: add new feature" + # The -s flag adds the required Signed-off-by trailer + ``` + +6. **Push and Open PR toward `develop`:** + ```bash + git push origin feature/your-feature-name + # Then open a PR from your fork/branch toward the upstream develop branch + ``` + +### Adding New Modules + +**Steps:** + +1. Create module in the appropriate `bps-*/` directory +2. Define clear interfaces (functions with type hints) +3. Write comprehensive docstrings +4. Add unit tests under `tests/unit/` +5. Add baseline tests under `test/baseline/` (run on every PR) +6. Update documentation +7. Add to `__init__.py` exports + +**Module Template:** + +```python +""" +Module description. + +This module provides [functionality description]. +""" + +from typing import Optional +import numpy as np +import xarray as xr + +def public_function( + input_data: xr.Dataset, + parameter: float, + optional_param: Optional[str] = None +) -> xr.Dataset: + """ + Function description. + + Parameters + ---------- + input_data : xr.Dataset + Description of input + parameter : float + Description of parameter + optional_param : str, optional + Description of optional parameter + + Returns + ------- + xr.Dataset + Description of output + + Raises + ------ + ValueError + When invalid input provided + """ + # Implementation + pass +``` + +### Testing New Code + +**Unit Tests** (`tests/unit/`, marker: `unit`): + +- Test individual functions +- Use fixtures for test data +- Test edge cases and error conditions + +**Baseline Tests** (`test/baseline/`, marker: `baseline`): + +- Run on every PR as part of the CI baseline pipeline (`baseline-marker-signal` job) +- Quick sanity checks that the core output hasn't regressed +- If a difference is detected, the CI elevates the PR to Tier 1 automatically. To accept the change, update the reference outputs in the same PR; SME approval flows through CODEOWNERS-required reviews (no label needed). + +**Extended Tests** (`test/extended/`, marker: `extended`): + +- Run on Tier 1+ PRs (code changes) +- Smoke and integration-level coverage + +**Heavy Tests** (`test/heavy/`, marker: `heavy`): + +- Run on Tier 2 PRs (scientific changes, or any PR targeting the `release` branch) +- Full scientific regression against reference data +- PRs to `release` additionally require `run_heavy=true` to be set via `workflow_dispatch` for the `CI gate` to pass + +> See [Repository-Level CI Test Harness](#repository-level-ci-test-harness) for how root and per-module test paths affect CI tier classification. + +--- + +## Best Practices + +### Code Organization + +**Module Size:** + +- Keep modules focused (single responsibility) +- Split large modules into smaller ones +- Aim for 200-500 lines per module + +**Function Length:** + +- Keep functions short and focused +- Extract complex logic into helper functions +- Aim for < 50 lines per function + +**Naming:** + +- Use descriptive names +- Follow Python naming conventions +- Be consistent across codebase + +### Performance + +**Optimization Guidelines:** + +- Profile before optimizing +- Use vectorized operations (NumPy/xarray) +- Avoid premature optimization +- Document performance considerations + +**Memory Management:** + +- Use chunked I/O for large datasets +- Release resources explicitly when needed +- Monitor memory usage in tests + +**Parallelization:** + +- Use appropriate parallelization strategies +- Consider data locality +- Balance overhead vs. benefit + +### Documentation + +**Code Documentation:** + +- Docstrings for all public functions +- Inline comments for complex logic +- Type hints for clarity +- Examples in docstrings + +**Architecture Documentation:** + +- Document design decisions +- Update architecture docs when structure changes +- Include diagrams where helpful + +### Error Handling + +**Error Types:** + +- Use appropriate exception types +- Create custom exceptions for domain-specific errors +- Provide informative error messages + +**Logging:** + +- Use appropriate log levels +- Include context in log messages +- Log important state changes + +--- + +## Resources + +### Documentation + +- [Getting Started](../getting-started/index.md) - Introduction and getting started guide +- [Licensing](../about/licensing/index.md) - Apache 2.0 license requirements and legal obligations +- [Code of Conduct](code-of-conduct.md) - Community standards and expectations +- [Contributing overview](index.md) - Contribution process and workflows +- [CI automation and contribution tiers](ci-automation-and-contribution-tiers.md) - Pipeline reference, tier detection, branch protection +- [Release process](release-process.md) - How releases are prepared and published +- [Governance](../governance/index.md) - Roles, responsibilities, and decision-making +- [Processor leads](../governance/processor-leads.md) - Module review routing +- [Code standards](code-standards.md) - Coding conventions and best practices +- [Documentation standards](documentation-standards.md) - Documentation writing standards and best practices +- [Communication](../communication/index.md) - Communication channels and meeting schedules + +### External Resources + +- [xarray Documentation](https://docs.xarray.dev/) +- [NumPy Documentation](https://numpy.org/doc/) +- [Python Best Practices](https://docs.python-guide.org/) + +--- + +**Questions?** Open an issue with the `architecture` label or contact core maintainers. + +## Automation Tools + +### nox + +`nox` at the repository root provides automation sessions: + +| Session | Command | Purpose | +| --------------------- | ---------------------------- | -------------------------------------------------------------- | +| `align_xsd` | `nox -s align_xsd` | Aligns XSD schemas across `bps-*` submodules | +| `generate_xsd_models` | `nox -s generate_xsd_models` | Generates Python models from BIOMASS XSD schemas via xsdata | +| `version_update` | `nox -s version_update` | Bumps the version across all `.py`, `.toml`, and `.yaml` files | + +### xsdata + +BIOMASS XSD schemas define the input/output data formats for the processors. Python models are generated automatically: + +```bash +nox -s generate_xsd_models +``` + +Generated models are stored in `aux_pp2_*_models/` directories. These are excluded from mypy type checking and from version control (`.gitignore`). Run `nox -s generate_xsd_models` after any XSD schema update. + +--- + +**Last Updated:** 2026 + +--- + +**Previous:** [Quality and validation](quality-and-validation.md) | **Next:** [Code standards](code-standards.md) diff --git a/docs/contributing/becoming-a-maintainer.md b/docs/contributing/becoming-a-maintainer.md new file mode 100644 index 0000000..e4f65d8 --- /dev/null +++ b/docs/contributing/becoming-a-maintainer.md @@ -0,0 +1,238 @@ +# Becoming a maintainer + +We value long-term contributors who demonstrate commitment, technical expertise, and community leadership. Becoming a maintainer is a natural progression for contributors who have shown consistent dedication to the project. This section outlines the pathway from contributor to maintainer, inspired by successful open-source projects like Xarray. + +### What is a Maintainer? + +Maintainers are trusted community members who have demonstrated deep commitment to the project and have been granted elevated permissions on the repository. They play a crucial role in: + +- Reviewing and merging pull requests +- Managing releases and versioning +- Setting technical direction and architecture decisions +- Ensuring code quality and consistency +- Mentoring new contributors +- Representing the project in the community + +For more details about maintainer responsibilities, see the [Governance documentation](../governance/index.md). + +### The Path to Maintainership + +Becoming a maintainer is not about a specific number of contributions or a fixed timeline. Instead, it's about demonstrating consistent commitment, technical competence, and alignment with the project's values and goals. The path typically follows these stages: + +The following flowchart illustrates the journey from contributor to maintainer: + +```{mermaid} +flowchart TD + Start([New Contributor]) --> Stage1[Stage 1: Active Contributor
3-6 months] + + Stage1 --> Contrib1[Regular contributions] + Stage1 --> Standards[Follow standards] + Stage1 --> Community[Community engagement] + + Contrib1 --> Check1{Quality and
consistency?} + Standards --> Check1 + Community --> Check1 + + Check1 -->|Yes| Stage2[Stage 2: Trusted Contributor
6-12 months] + Check1 -->|No| Stage1 + + Stage2 --> Review[Code review] + Stage2 --> Mentor[Mentoring] + Stage2 --> Complex[Complex issues] + Stage2 --> Tier12[Tier 1-2 contributions] + + Review --> Check2{Leadership and
expertise?} + Mentor --> Check2 + Complex --> Check2 + Tier12 --> Check2 + + Check2 -->|Yes| Stage3[Stage 3: Maintainer Candidate] + Check2 -->|No| Stage2 + + Stage3 --> Express[Express interest] + Express --> Eval[Evaluation by maintainers] + Eval --> Discuss[Internal discussion] + Discuss --> Nominate{Nomination?} + + Nominate -->|Yes| Approve[Steering Council approval] + Nominate -->|No| Stage2 + + Approve --> Maintainer([Maintainer]) + + classDef defaultStyle fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px,color:#333 + class Start,Stage1,Stage2,Stage3,Maintainer,Contrib1,Standards,Community,Check1,Review,Mentor,Complex,Tier12,Check2,Express,Eval,Discuss,Nominate,Approve defaultStyle + + style Start fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px + style Stage1 fill:#e1bee7,stroke:#e1bee7,stroke-width:2px + style Stage2 fill:#a3d8b0,stroke:#a3d8b0,stroke-width:2px + style Stage3 fill:#e1bee7,stroke:#e1bee7,stroke-width:2px + style Maintainer fill:#a3d8b0,stroke:#a3d8b0,stroke-width:3px + style Check1 fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px + style Check2 fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px + style Nominate fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px +``` + +The following table compares the different stages: + +| Criterion | Stage 1: Active Contributor | Stage 2: Trusted Contributor | Stage 3: Maintainer Candidate | +|-----------|----------------------------|-----------------------------|-------------------------------| +| **Typical duration** | 3-6 months | 6-12 months | 2-4 weeks (process) | +| **Contributions** | Regular and quality | Complex and significant | Leadership and responsibilities | +| **Code review** | Submit PRs | Review others' PRs | Advanced review and mentoring | +| **Engagement** | Discussions and questions | Mentoring and triage | Project management | +| **Validation** | Mainly Tier 0 | Tier 1-2 with validation | All tiers | +| **Permissions** | Standard contributor | Trusted contributor | Maintainer candidate | +| **Next step** | Stage 2 | Stage 3 | Maintainer | + +#### Stage 1: Active Contributor + +**Focus:** Build a track record of quality contributions + +**What to do:** +- Make regular, meaningful contributions (code, documentation, tests, reviews) +- Follow all contribution guidelines and coding standards +- Respond promptly to review feedback +- Help improve documentation and examples +- Participate in discussions and answer questions + +**What we look for:** +- Consistent contributions over time (not just one large contribution) +- High-quality code that follows our standards +- Good understanding of the codebase and project goals +- Positive interactions with the community + +**Timeline:** This stage typically lasts 3-6 months of active contribution, but can vary based on the nature and quality of contributions. + +#### Stage 2: Trusted Contributor + +**Focus:** Demonstrate leadership and deeper engagement + +**What to do:** +- Take ownership of complex issues and features +- Help review pull requests from other contributors +- Mentor new contributors and answer questions +- Propose and implement significant improvements +- Participate actively in technical discussions +- Help triage issues and improve project organization +- Contribute to Tier 1-2 changes with scientific validation + +**What we look for:** +- Ability to review code effectively and provide constructive feedback +- Leadership in technical discussions and decision-making +- Mentoring and community-building skills +- Deep understanding of the project's scientific and technical aspects +- Reliability and consistency in contributions + +**Timeline:** This stage typically lasts 6-12 months, during which you build trust and demonstrate your commitment. + +#### Stage 3: Maintainer Candidate + +**Focus:** Express interest and demonstrate readiness + +**What to do:** +- Continue all activities from Stage 2 +- Express interest in becoming a maintainer to current maintainers or the Open Science Lead +- Take on additional responsibilities when asked +- Help with release management and project maintenance tasks +- Participate in maintainer discussions (as invited) + +**What we look for:** +- Proven track record of quality contributions and reviews +- Strong alignment with project values and goals +- Ability to work independently and make sound technical decisions +- Excellent communication and collaboration skills +- Commitment to the project's long-term success + +**Process:** +1. **Expression of Interest:** You can express interest directly to current maintainers, the Open Science Lead, or by opening a thread in the [Governance category on GitHub Discussions](https://github.com/BioPAL/BPS/discussions/categories/governance) +2. **Evaluation:** Current maintainers will review your contributions, community engagement, and technical expertise +3. **Discussion:** Maintainers will discuss your candidacy internally +4. **Nomination:** If there's consensus, a maintainer will nominate you +5. **Approval:** The nomination is reviewed by the repository stewards and ESA representatives +6. **Onboarding:** Once approved, you'll receive maintainer permissions and onboarding support + +**Timeline:** The evaluation and approval process typically takes 2-4 weeks after expression of interest. + +### Key Qualities We Value + +While there's no strict checklist, successful maintainers typically demonstrate: + +**Technical Excellence:** +- Deep understanding of the codebase and architecture +- Strong software engineering skills +- Knowledge of Earth observation and SAR processing (for scientific aspects) +- Ability to write clear, maintainable code +- Understanding of testing, validation, and quality assurance + +**Community Leadership:** +- Positive, respectful communication +- Ability to mentor and guide others +- Constructive feedback and code review skills +- Conflict resolution abilities +- Commitment to inclusivity and diversity + +**Project Commitment:** +- Long-term dedication to the project +- Availability for reviews and maintenance tasks +- Alignment with project goals and values +- Understanding of governance and decision-making processes +- Willingness to take on responsibilities + +**Scientific Rigor (for scientific aspects):** +- Understanding of validation requirements +- Ability to review scientific changes +- Knowledge of reference datasets and validation methods +- Commitment to reproducibility and transparency + +### Maintainer Responsibilities + +Once you become a maintainer, you'll take on additional responsibilities: + +**Code Review and Merging:** +- Review pull requests across all tiers +- Ensure code quality and standards compliance +- Approve and merge PRs after all requirements are met +- Help resolve conflicts and technical issues + +**Project Maintenance:** +- Manage releases and versioning +- Maintain code quality and architecture consistency +- Respond to critical issues and security vulnerabilities +- Manage protected branches and repository settings + +**Community Leadership:** +- Mentor new contributors +- Participate in technical and governance meetings +- Represent the project in the community +- Help set technical direction and priorities + +**Governance:** +- Participate in maintainer discussions and decisions +- Contribute to governance and policy discussions +- Help ensure compliance with ESA policies and Open Science principles + +### How to Get Started + +If you're interested in becoming a maintainer: + +1. **Start Contributing:** Focus on making quality contributions and building your track record +2. **Engage with the Community:** Participate in discussions, help others, and build relationships +3. **Take on Challenges:** Volunteer for complex issues and significant features +4. **Review Code:** Start reviewing other contributors' pull requests +5. **Express Interest:** When you feel ready, express your interest to current maintainers + +**Remember:** There's no rush. Focus on making quality contributions and building trust with the community. Maintainership will come naturally as you demonstrate your commitment and capabilities. + +### Questions? + +If you have questions about the maintainer path or want to discuss your journey: + +- Start a thread in the [Governance category on GitHub Discussions](https://github.com/BioPAL/BPS/discussions/categories/governance) +- Contact current maintainers directly +- Talk to the Open Science Lead +- Ask during Community Meetings or Office Hours + + +--- + +**Previous:** [Templates and checklists](templates-and-checklists.md) | **Next:** [Release process](release-process.md) diff --git a/docs/contributing/ci-automation-and-contribution-tiers.md b/docs/contributing/ci-automation-and-contribution-tiers.md new file mode 100644 index 0000000..4e5169f --- /dev/null +++ b/docs/contributing/ci-automation-and-contribution-tiers.md @@ -0,0 +1,426 @@ +# CI automation and contribution tiers + +Reference for how pull requests are classified, which CI stages run, +and which branch rules apply. For what to do once your PR is open, see +[Review and integration](review-and-integration.md). + +## Verify every change, automatically + +Every contribution is risk-scored before any human reviews it. The +classifier reads its policy from the base branch so the PR cannot +rewrite its own judge. + +::::{grid} 1 1 1 1 +:gutter: 2 +:class-container: stat-cards-list + +:::{grid-item-card} +:class-card: stat-card + +
+ +10 + +
+ +**Parallel baseline checks per PR.** + +`REUSE · DCO · Bandit · pre-commit · build · tests · docs · ...` + +
+ +
+::: + +:::{grid-item-card} +:class-card: stat-card + +
+ +3 + +
+ +**Automated tier levels.** + +`Tier 0 · Tier 1 · Tier 2`, computed from the diff. + +
+ +
+::: + +:::{grid-item-card} +:class-card: stat-card + +
+ +1 + +
+ +**Single source of truth.** + +`tier-policy.yml`, read from the base branch, never from the PR. + +
+ +
+::: + +:::{grid-item-card} +:class-card: stat-card + +
+ +100 % + +
+ +**Auto-classified changes.** + +No labels to set. The CI scores every PR before any human reviews it. + +
+ +
+::: + +:::: + +--- + +## Contribution tiers in one paragraph + +Every pull request is automatically classified into one of three tiers +based on the diff it introduces. The tier determines which CI stages +run and how many approvals are required to merge. + +- **Tiers 0, 1 and 2** are computed automatically by the CI from the + policy declared in `.github/tier-policy.yml`. The contributor does + not assign the tier; the CI does. +- **Tier 3** is a separate qualitative governance category for release + decisions and policy changes that go through an ESA process outside + the automated pipeline. + +--- + +## The three automated tiers + +::::{grid} 1 3 3 3 +:gutter: 3 + +:::{grid-item-card} +:class-card: intro-card + + **Tier 0 · Baseline** +^^^ +No risk-elevating change detected. + +**Triggers** +- No `locked_paths` modified. +- No SME-owned paths touched. +- Baseline marker unchanged. + +**Approvals**: 1 maintainer. +**CI**: Baseline gate only. + +*Examples*: bug fixes, doc updates, refactors without scientific impact, +typo corrections. +::: + +:::{grid-item-card} +:class-card: intro-card + + **Tier 1 · Extended** +^^^ +Locked or SME-owned paths modified. + +**Triggers** +- Touches `locked_paths`. +- Touches SME-owned paths. +- Baseline marker differs. +- Dependabot major version bump. + +**Approvals**: 2 (incl. SME). +**CI**: Baseline + Extended. + +*Examples*: small parameter updates, minor algorithm changes, +CI configuration changes. +::: + +:::{grid-item-card} +:class-card: intro-card + + **Tier 2 · Heavy** +^^^ +Release branch or explicit upclass. + +**Triggers** +- PR targets the `release` branch. +- `run_heavy=true` requested on dispatch. +- Manual upclass from Tier 1. + +**Approvals**: 3 (incl. SME and ESA). +**CI**: Baseline + Extended + Heavy. + +*Examples*: new retrieval approaches, significant changes to error +models, anything promoted to `release`. +::: + +:::: + +### Tier 3 · governance + +Tier 3 is a separate path for **release decisions and policy changes**. +It is not computed by the CI. These changes are approved through an +explicit ESA decision in consultation with the governance group, then +reflected in the repository through the standard branching mechanism +once the decision has been recorded. + +*Examples*: new processor version releases, governance changes, major +policy updates. + +### Judge from base, never from PR + +```{important} + **The tier policy is read from the base branch SHA, never from the PR +head.** The policy file, the baseline references, and the test harness +are all locked at the base commit. A pull request cannot rewrite its +own judge. +``` + +This is the core security guarantee of the CI: every PR is evaluated +against the rules in effect on the branch it targets, not against the +rules it tries to introduce. + +### Tier comparison + +| | Tier 0 · Baseline | Tier 1 · Extended | Tier 2 · Heavy | +|---|---|---|---| +| **CI stages** | Baseline | Baseline + Extended | Baseline + Extended + Heavy | +| **Approvals** | 1 maintainer | 2 (incl. SME) | 3 (incl. SME + ESA) | +| **Expected volume** | 60–70% of PRs | 20–25% of PRs | 5–10% of PRs | +| **Typical timeline** | under 3 days | 5 to 7 days | 10 to 14 days | +| **Review cadence** | Weekly | Monthly | Quarterly | + +--- + +## Pipeline overview + +When a pull request is opened, GitHub triggers `ci.yml`. The workflow runs +baseline checks in parallel, classifies the tier from the base-branch policy, +then runs Extended and Heavy stages when required. + +```{raw} html +CI/CD Workflow +CI/CD Workflow +``` + +### Diagram appendix + +```{raw} html +CI/CD Annexe +CI/CD Annexe +``` + +### Pull request sequence + +```{mermaid} +%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#f5f5f5','primaryTextColor':'#333','primaryBorderColor':'#9e9e9e','lineColor':'#666','secondaryColor':'#f5f5f5','tertiaryColor':'#f5f5f5'}}}%% +sequenceDiagram + participant Dev as Developer + participant Fork as Fork (external only) + participant GH as GitHub PR + participant CI as CI/CD (ci.yml) + participant Bot as PR Guidance Bot + participant Rev as Reviewer(s) + participant Develop as develop + + alt Internal contributor + Dev->>GH: Push feature branch · open PR → develop + else External contributor + Dev->>Fork: Push feature branch on fork + Fork->>GH: Open PR from fork → upstream develop + end + + GH->>CI: pull_request event triggers ci.yml + + CI->>CI: Baseline checks in parallel
(DCO · REUSE · pre-commit · bandit · build · unit tests · docs) + + alt Baseline failure + CI->>GH: baseline-gate ✗ + GH->>Dev: Fix failing job and re-push + Dev->>GH: Push fix (or to fork) + GH->>CI: Re-trigger ci.yml + end + + CI->>CI: Automatic tier triage
(read tier-policy.yml from base branch) + + alt Tier 1: SME-owned paths modified + CI->>CI: Extended pipeline (pytest -m extended) + end + + alt Tier 2: locked paths modified + CI->>CI: Extended + Heavy pipeline (pytest -m heavy) + end + + CI->>GH: CI gate ✓ + CI->>Bot: Post/update sticky PR comment + Bot->>GH: Summary: tier · jobs status · reviewers needed + + Rev->>GH: Review and comments + Dev->>GH: Address comments · re-push + GH->>CI: Re-trigger ci.yml + + Rev->>GH: Approve (N approvals per branch ruleset) + GH->>Develop: Squash merge +``` + +--- + +## Branch protection and approvals + +Four branches, three protection levels, one promotion path. Every +branch is governed by GitHub repository rulesets, never by ad-hoc +settings. + +::::{grid} 1 2 2 4 +:gutter: 2 + +:::{grid-item-card} Fork · `feature/*` +:class-card: sd-border-light + +**Personal** + +Free editing. No approval required. This is where you build the change +before it touches the project. +::: + +:::{grid-item-card} `develop` +:class-card: sd-border-info + +**Integration** + +1 approval · Maintainer + SME review · squash only · +`CI gate` required. +::: + +:::{grid-item-card} `release` +:class-card: sd-border-warning + +**Pre-release** + +2 approvals · Maintainer + SME · GPG/SSH signed commits +· squash only · Heavy CI mandatory. +::: + +:::{grid-item-card} `main` +:class-card: sd-border-danger + +**Production** + +3 approvals · Maintainer + SME + ESA · no admin bypass +· signed commits · squash only. +::: + +:::: + +### Rules enforced everywhere + +- ✓ Require CODEOWNERS review. +- ✓ Require a linear history (squash-only merges). +- ✓ Dismiss stale approvals on every new commit. +- ✓ Block force-pushes. +- ✓ `CI gate` is the required status check on every protected branch. + +```{important} +**PRs targeting `release` need explicit Heavy authorisation.** The +`CI gate` stays red on every `pull_request` event until a maintainer +triggers the workflow via `Actions → Run workflow` with +`run_heavy=true`. This is the explicit consent step before promotion +to `main`. + +Any modification to `VERSION` or `CHANGELOG.md` requires an explicit +approval from the ESA reviewer defined in `CODEOWNERS`. No release +can be merged without this approval. +``` + +### Branching strategy + +- **`main`**: Operational/production branch. Promoted to from `release` + after ESA approval. +- **`release`**: Release candidate branch. Pre-release validation runs + here; Heavy CI is mandatory before promotion to `main`. +- **`develop`**: Main development branch. Latest development state. +- **Feature branches**: `feature/description`, `bugfix/issue-id`, + `docs/topic`. Opened from `develop` on a fork. + +## Dependabot: automated dependency updates + +Dependabot opens PRs every Monday to update pip and GitHub Actions dependencies, always targeting `develop`. These PRs go through the normal CI pipeline. + +**Important rule**: a **major version bump** on a pip dependency is automatically classified as **Tier 1** by the CI, requiring SME review. + +--- + +## Automated CI/CD checks + +All pull requests go through automated checks. The required checks and approvals depend on the contribution tier. + +**Baseline Pipeline (all PRs: 10 parallel jobs):** +- `baseline-marker-signal`: `pytest -m baseline` on `test/baseline/` (feeds the tier decision) +- `baseline-dco`: Signed-off-by trailer on every non-merge commit, identity must match author or committer +- `baseline-reuse`: REUSE / SPDX header compliance +- `baseline-pre-commit`: black, ruff, mypy, detect-secrets hooks +- `baseline-security`: Bandit static analysis +- `baseline-build`: sdist + wheel build (non-blocking during migration) +- `baseline-sensitive-files`: rejects `*.bak` files and files larger than 10 MB +- `baseline-unit-tests`: `pytest -m unit` (non-blocking during migration) +- `baseline-docs`: Sphinx build if `docs/api/conf.py` exists (non-blocking) +- `baseline-dependabot`: verifies `.github/dependabot.yml` exists and signals major bumps + +All 10 jobs feed `baseline-gate`, the aggregate blocking check. + +**Extended Pipeline (Tier 1+):** +- Extended test suite (`pytest -m extended`) +- ⚠ Requires TDS dataset: see [Code standards](code-standards.md) for access procedure + +**Heavy Pipeline (Tier 2):** +- Full scientific regression (`pytest -m heavy`) +- ⚠ Requires TDS dataset: see [Code standards](code-standards.md) for access procedure + +### Automatic tier detection + +The CI/CD pipeline **automatically determines the tier** by analysing the files modified in your PR against the policy defined in `.github/tier-policy.yml`. You do not need to add a label manually. + +**How the tier is decided:** + +- Files listed in `locked_paths` (e.g. `VERSION`, `pyproject.toml`, `.github/workflows/**`, `CODEOWNERS`, test directories) → **Tier 2** automatically +- Files in `sme_owned_paths` (processor directories `bps-*`) → triggers SME review +- A Dependabot PR with a **major version bump** → **Tier 1** automatically +- Everything else → **Tier 0** + +The policy file is always read from the **base branch** (not your PR head), which prevents a PR from modifying its own judge. + +**What runs based on the detected tier:** + +- **Tier 0**: Baseline pipeline only (DCO, REUSE, pre-commit, security, build, unit tests, docs) +- **Tier 1**: Baseline + Extended pipeline (`pytest -m extended`) +- **Tier 2**: Baseline + Extended + Heavy pipeline (`pytest -m heavy`) +- **Tier 3**: ESA governance decision: no automated pipeline + +### Review and approval requirements + +- **Tier 0**: At least **1 core maintainer** approval +- **Tier 1**: **Scientific module expert** approval + **Core maintainer** approval +- **Tier 2**: **Scientific module expert(s)** approval + **Open Science Lead** approval + **ESA representative(s)** approval + **Core maintainer** approval +- **Tier 3**: Explicit ESA decision in consultation with governance group + +**The PR cannot be merged until all required checks pass and all required approvals are obtained.** + +Full policy rules: [`.github/tier-policy.yml`](https://github.com/BioPAL/BPS/blob/main/.github/tier-policy.yml). + +--- + +**Previous:** [Review and integration](review-and-integration.md) | **Next:** [Quality and validation](quality-and-validation.md) diff --git a/docs/contributing/code-of-conduct.md b/docs/contributing/code-of-conduct.md new file mode 100644 index 0000000..6f5d887 --- /dev/null +++ b/docs/contributing/code-of-conduct.md @@ -0,0 +1,142 @@ +# BioPAL Code of Conduct + +This project adheres to a Code of Conduct that all contributors are expected to follow. We are committed to providing a welcoming and inclusive environment for everyone, regardless of background or experience level. + +## Key Principles + +- Be respectful and considerate +- Welcome newcomers and help them learn +- Focus on constructive feedback +- Respect different viewpoints and experiences + +--- + +## Frequently Asked Questions + +### Why have you adopted a Code of Conduct? + +We think the BioPAL community is awesome. If you're familiar with the BioPAL community, you'll probably notice that the Code basically matches what we already do. Think of this as documentation: we're taking implicit expectations about behavior and making them explicit. + +Maintaining a Code of Conduct forces us to consider and articulate what kind of community we want to be, and serves as a constant reminder to put our best foot forward. But most importantly, it serves as a signpost to people looking to join our community that we feel these values are important. + +We know that the BioPAL community is open, friendly, and welcoming. We want to make sure everyone else knows it too. + +### What does it mean to "adopt" a Code of Conduct? + +For the most part, we do not think it means large changes. We think that the text does a really good job describing the way the BioPAL community already conducts itself. We expect that most people will simply continue to behave as they have in the past. + +However, we do expect that people will abide by the spirit and words of the Code of Conduct when in "official" BioPAL spaces. In practice, BioPAL spaces include mailing lists, various communication channels, and events. + +### What happens if someone violates the Code of Conduct? + +We are all stewards of our community, and are encouraged to participate in ways that defend the values highlighted in this document and help others understand when their actions go against these values (by engaging them and directing them to this document if necessary). If that doesn't work, or if you need more help, you can contact [biopal@esa.int](mailto:biopal@esa.int). For more details please see our Reporting Guidelines section below. + +--- + +## Reporting Guide + +If you believe someone is violating the code of conduct we ask that you report it to BioPAL by emailing [biopal@esa.int](mailto:biopal@esa.int). All reports will be kept confidential. In some cases we may determine that a public statement will need to be made. If that's the case, the identities of all involved will remain confidential unless those individuals instruct us otherwise. + +If you believe anyone is in physical danger, please notify appropriate law enforcement first. If you are unsure what law enforcement agency is appropriate, please include this in your report and we will attempt to notify them. + +If you choose to email, please include the following information: + +- Your contact info (not required) +- Names (real, nicknames, or pseudonyms) of any individuals involved. If there were other witnesses besides you, try to include them as well (not required) +- When and where the incident occurred. Please be as specific as possible. +- Your account of what occurred. If there is a publicly available record (e.g. a Slack chat or GitHub issue) please include a link +- Any extra context you believe exists for the incident +- If you believe this incident is ongoing +- Any other information you believe we should have + +**Note:** with anonymous reports, there are inherent limits in our ability to do follow-up or collect additional information. Hence we suggest that if you would like to remain anonymous, you at least create a throw-away email address without your real name. + +### What happens after you file a report? + +You will receive an email from the BioPAL Code of Conduct Committee acknowledging receipt immediately. We promise to acknowledge receipt within 48 hours (and will aim for much quicker than that). + +The committee will immediately communicate to review the incident and determine: +- What happened +- Whether this event constitutes a Code of Conduct violation +- Who the individual(s) involved were +- Whether this is an ongoing situation, or if there is a threat to anyone's physical safety + +If this is determined to be an ongoing incident or a threat to physical safety, the committee's immediate priority will be to protect everyone involved. This means we may delay an "official" response until we believe that the situation has ended and that everyone is physically safe. + +The Code of Conduct committee will then follow the **standard procedure** to arrive at and communicate a resolution. + +### Appealing the Code of Conduct Committee's Response + +To appeal a decision of the Code of Conduct Committee, contact the BioPAL Steering Council at [biopal@esa.int](mailto:biopal@esa.int) with your appeal and they will review the case. + +The Steering Council will gather all relevant information from the Committee and provide a resolution within two weeks. If more time is required, it should inform the appellant within that time frame. + +--- + +## Enforcement + +This is the enforcement manual followed by BioPAL's Code of Conduct Committee. It's used when we respond to an incident to make sure we are consistent and fair. + +### The Code of Conduct Committee + +All responses to reports of conduct violations will be managed by a Code of Conduct Committee ("the committee"). The BioPAL Steering Council is responsible for vetting and appointing the Code of Conduct Committee. During periods where a separate Code of Conduct Committee is not appointed or is not available to handle a code of conduct report promptly, the BioPAL Steering Council will serve as the interim Code of Conduct Committee. + +If a Code of Conduct report involves a member of the Steering Council or Code of Conduct Committee, that member will not participate in the investigation or any decisions related to that report. + + +### Enforcement guidelines and principles + +Enforcing the Code of Conduct impacts our community today and for the future. It's an action that we do not take lightly. When reviewing enforcement measures, the Code of Conduct Committee will keep the following values and guidelines in mind: +- **Act in a personal manner rather than impersonal.** The Committee can engage the parties to understand the situation, while respecting the privacy and any necessary confidentiality of reporters. However, sometimes it is necessary to communicate with one or more individuals directly: the Committee's goal is to improve the health of our community rather than only produce a formal decision. +- **Emphasize empathy for individuals rather than judging behavior**, avoiding binary labels of "good" and "bad/evil". Overt, clear-cut aggression and harassment exists and will be addressed unambiguously. But many scenarios that can prove challenging to resolve are those where normal disagreements devolve into inappropriate behavior from multiple parties. Understanding the full context and finding a path that re-engages all is hard, but ultimately the most productive for our community. +- **Help increase engagement in good discussion practice**: try to identify where discussion may have broken down and provide actionable information, pointers and resources that can help enact positive change on these points. +- **Be mindful of the needs of new members**: provide them with explicit support and consideration, with the aim of increasing participation from underrepresented groups in particular. +- **Individuals come from different cultural backgrounds and native languages.** While lack of intent to harm is not an excuse, try to identify any honest misunderstandings caused by a non-native speaker and help them understand the issue and how to change. Complex discussion in a foreign language can be very intimidating, and we want to grow our diversity also across nationalities and cultures. +- **Our actions will reflect compassion for all individuals.** We will seek to understand, to educate, and, as necessary, take action. + +**Mediation:** voluntary, informal mediation is a tool at our disposal. In contexts such as when two or more parties have all escalated to the point of inappropriate behavior, it may be useful to facilitate a mediation process. This is only an example: the Committee can consider mediation in any case, mindful that the process is meant to be strictly voluntary and no party can be pressured to participate. + +### How the committee will respond to reports + +When a report is sent to the committee they will immediately reply to the reporter to confirm receipt. This reply must be sent within 48 hours, and the group should strive to respond much quicker than that. + +The committee will then review the incident and determine, to the best of their ability: +- What happened. +- Whether this event constitutes a Code of Conduct violation. +- Who are the responsible party(ies). +- Whether this is an ongoing situation, and there is a threat to anyone's physical safety. + +The Code of Conduct Committee should aim to have a resolution agreed upon within one week. In the event that a resolution can't be determined in that time, the committee will respond to the reporter(s) with an update and projected timeline for resolution. + +### Incident Response and Committee Actions + +If the act is ongoing, or involves a threat to anyone's safety (e.g. threats of violence), any committee member may act immediately (before reaching consensus) to address the situation. In ongoing situations, any member may decide to employ any of the tools available to the committee, including bans and blocks. + +If the incident involves physical danger, any member of the committee may -- and should -- act unilaterally to protect the safety of those involved. This can include contacting law enforcement (or other local personnel) and speaking on behalf of the BioPAL Steering Council. + +### Resolutions + +The committee must agree on a resolution by consensus. If the group cannot reach consensus and deadlocks for over two weeks, the group will turn the matter over to the Steering Council for resolution. + +Possible responses may include: +- Taking no further action (if we determine no violations have occurred, or if the matter has been resolved publicly) +- Coordinating voluntary mediation: if all involved parties agree, the Committee may facilitate a mediation process +- Remind publicly, and point out that some behavior/actions/language have been judged inappropriate +- A private reprimand from the committee to the individual(s) involved +- A public reprimand +- A request for a public or private apology +- A "mutually agreed upon hiatus" where the committee asks the individual to temporarily refrain from community participation +- A permanent or temporary ban from some or all BioPAL spaces + +Once a resolution is agreed upon, but before it is enacted, the committee will contact the original reporter and any other affected parties and explain the proposed resolution. + +### Conflicts of Interest + +In the event of any conflict of interest, a committee member must immediately notify the other members, and recuse themselves if necessary. + +--- + +**Previous:** [Release process](release-process.md) + +**Last Updated:** 2025 + diff --git a/docs/contributing/code-standards.md b/docs/contributing/code-standards.md new file mode 100644 index 0000000..3718050 --- /dev/null +++ b/docs/contributing/code-standards.md @@ -0,0 +1,1225 @@ +# Code Standards + +Complete coding standards for BioPAL, including naming conventions, formatting rules, type hints, and test requirements. + +--- + +## Table of Contents + +1. General Guidelines +2. Naming Conventions +3. Formatting Rules +4. Type Hints +5. Test Requirements +6. Documentation Standards +7. Error Handling +8. Logging + +--- + +## General Guidelines + +### Quick Reference Table + +The following table provides a quick reference for code conventions: + +| Element | Convention | Example | +|---------|------------|---------| +| **Variables** | snake_case | `biomass_value`, `incidence_angle` | +| **Functions** | snake_case, verb-based | `calculate_biomass()`, `process_l2_product()` | +| **Classes** | PascalCase | `BiomassProcessor`, `SARDataLoader` | +| **Constants** | UPPER_SNAKE_CASE | `MAX_ITERATIONS`, `DEFAULT_THRESHOLD` | +| **Modules** | snake_case | `biomass_processor.py`, `sar_utils.py` | +| **Type hints** | Required | `def process(data: np.ndarray) -> float:` | +| **Docstrings** | NumPy style | `"""Description.\n\nParameters\n----------\n..."""` | +| **Imports** | Grouped and sorted | `import numpy as np` then `from . import utils` | +| **Line length** | Max 120 characters | Use black for formatting | +| **Tests** | Prefix `test_` | `test_calculate_biomass()` | + +### Core Principles + +**Clear Naming:** +- Use descriptive names for variables, functions, and modules +- Avoid abbreviations unless widely understood +- Be consistent across the codebase + +**Pure Functions:** +- Prefer functions that take explicit inputs and return outputs +- Avoid modifying global state +- Make functions testable and predictable + +**Licensing Compliance:** +- All code must be compatible with Apache License 2.0 +- External dependencies must be license-compatible +- Third-party code must be properly attributed +- See the [Contributions](../about/licensing/contributions.md) and [REUSE compliance](../about/licensing/reuse-compliance.md) pages for complete requirements + +**DRY Principle:** +- Don't Repeat Yourself +- Avoid duplication by refactoring shared logic +- Extract common patterns into utilities + +**Single Responsibility:** +- Keep functions focused on one task +- Keep modules focused on one domain +- Separate concerns clearly + +**Readability:** +- Code should be self-documenting +- Use comments for "why", not "what" +- Prefer clear code over clever code + +--- + +## Naming Conventions + +### Variables + +**Snake Case:** +```python +# Good +biomass_value = 125.5 +incidence_angle = 30.0 +sar_backscatter = np.array([0.1, 0.2, 0.3]) + +# Avoid +biomassValue = 125.5 # camelCase +incidenceAngle = 30.0 +SARBackscatter = np.array([0.1, 0.2, 0.3]) # PascalCase +``` + +**Descriptive Names:** +```python +# Good +above_ground_biomass = calculate_agb(sar_data) +canopy_height_map = process_height_data(lidar_data) + +# Avoid +agb = calc(sd) # Too abbreviated +chm = proc(ld) # Unclear +``` + +**Constants:** +```python +# UPPER_SNAKE_CASE for constants +MAX_ITERATIONS = 100 +DEFAULT_THRESHOLD = 0.5 +EARTH_RADIUS_KM = 6371.0 +``` + +### Functions + +**Snake Case, Verb-Based:** +```python +# Good +def calculate_biomass(sar_data, config): + """Calculate biomass from SAR data.""" + pass + +def process_l2_product(input_file, output_file): + """Process L2 product.""" + pass + +# Avoid +def BiomassCalc(sar_data, config): # PascalCase + pass + +def processL2(input_file, output_file): # camelCase + pass +``` + +**Action-Oriented Names:** +```python +# Good +def validate_input_data(data): + """Validate input data.""" + pass + +def load_configuration(file_path): + """Load configuration from file.""" + pass + +# Avoid +def data_validation(data): # Noun instead of verb + pass + +def config(file_path): # Too generic + pass +``` + +### Classes + +**PascalCase:** +```python +# Good +class BiomassProcessor: + """Process biomass data.""" + pass + +class ConfigurationManager: + """Manage configuration.""" + pass + +# Avoid +class biomass_processor: # snake_case + pass + +class ConfigMgr: # Abbreviation + pass +``` + +### Modules and Packages + +**Snake Case, Lowercase:** +```python +# Good +biomass_l2_core/ +biomass_retrieval.py +uncertainty_quantification.py + +# Avoid +BiomassL2Core/ # PascalCase +BiomassRetrieval.py # PascalCase +``` + +### Private Functions and Variables + +**Leading Underscore:** +```python +# Good +def _internal_helper_function(): + """Internal helper (not part of public API).""" + pass + +_internal_variable = 42 + +# Public API +def public_function(): + """Public function.""" + _internal_helper_function() +``` + +--- + +## Formatting Rules + +### Python Style Guide + +We follow **PEP 8** with modifications enforced by `black`: + +**Line Length:** 120 characters (configured in `pyproject.toml` and `ruff.toml`) + +```python +# Good: Break long lines appropriately +def calculate_biomass( + sar_data: np.ndarray, + incidence_angle: float, + config: dict, + aux_data: Optional[np.ndarray] = None +) -> np.ndarray: + """Calculate biomass.""" + pass + +# Avoid: Lines exceeding 120 characters without breaking +def calculate_biomass(sar_data: np.ndarray, incidence_angle: float, config: dict, aux_data: Optional[np.ndarray] = None, extra_param: Optional[str] = None) -> np.ndarray: + pass +``` + +**Indentation:** 4 spaces (no tabs) + +```python +# Good: 4 spaces +if condition: + do_something() + if nested_condition: + do_nested() + +# Avoid: Tabs or inconsistent indentation +if condition: + do_something() # Tab + do_other() # Mixed +``` + +**Blank Lines:** +- 2 blank lines between top-level functions and classes +- 1 blank line between methods in a class +- Use blank lines to separate logical sections + +```python +# Good +import numpy as np +import xarray as xr + + +def function_one(): + """First function.""" + pass + + +def function_two(): + """Second function.""" + pass + + +class MyClass: + """My class.""" + + def method_one(self): + """First method.""" + pass + + def method_two(self): + """Second method.""" + pass +``` + +**Imports:** +- Group imports: standard library, third-party, local +- Sort imports alphabetically within groups +- Use `ruff` for automatic import sorting + +```python +# Good +import logging +from typing import Dict, Optional + +import numpy as np +import xarray as xr + +from biomass_l2_core.algorithms import calculate_biomass +from biomass_l2_io.readers import read_l1_product +``` + +### Code Formatting Tools + +**black** (v24.10.0): +- Automatic code formatter: handles indentation, quotes, trailing commas, blank lines, and more +- Line length: 120 characters (read from `pyproject.toml`, no need to pass `--line-length`) +- Consistent style across codebase + +```bash +# Format code +black src/ tests/ + +# Check formatting without modifying (CI) +black --check src/ tests/ +``` + +**ruff** (v0.9.1): +- Fast linting and import sorting +- Style checks and auto-fix +- Docstring code formatting enabled (`docstring-code-format = true` in `ruff.toml`) + +```bash +# Lint and auto-fix +ruff check --fix src/ tests/ + +# Check only +ruff check src/ tests/ +``` + +**mypy** (v1.14.1): +- Static type checking +- Catches type errors before runtime +- Configuration (in `pyproject.toml`): + - `python_version = "3.12"` + - `explicit_package_bases = true` + - `namespace_packages = true` + +```bash +# Type check (reads config from pyproject.toml) +mypy src/ +``` + +### Pre-commit Configuration + +Pre-commit hooks automatically enforce formatting and code quality before commits. This ensures consistent code style and catches common issues early. + +#### Setup + +After cloning the repository and installing dependencies: + +```bash +pip install pre-commit +pre-commit install +pre-commit install --hook-type commit-msg # Required for the DCO check +``` + +This installs git hooks that run automatically on every commit. + +#### Configuration + +The actual pinned versions in `.pre-commit-config.yaml`: + +| Hook | Version | +|------|---------| +| black | 24.10.0 | +| ruff | v0.9.1 | +| mypy | v1.14.1 | +| detect-secrets | v1.5.0 | +| reuse | v5.0.2 | + +#### What Pre-commit Checks + +**General checks** (pre-commit standard hooks): +- `trailing-whitespace`: removes trailing whitespace +- `end-of-file-fixer`: ensures files end with a newline +- `check-yaml`: YAML validation (excludes `recipe/meta.yaml`) +- `check-added-large-files`: blocks files larger than 10MB +- `check-merge-conflict`: detects merge conflict markers +- `detect-private-key`: blocks accidental private key commits + +**Python code quality:** +- **black** (v24.10.0): Automatic code formatting +- **ruff** (v0.9.1): Fast linting and import sorting, with `--fix` +- **mypy** (v1.14.1): Type checking (excludes tests, docs, noxfile, recipe, aux_pp2_models) +- **detect-secrets** (v1.5.0): Prevents committing secrets/credentials (baseline: `.secrets.baseline`) + +**License compliance:** +- **reuse** (v5.0.2): Verifies SPDX headers on all source files + +**DCO (commit-msg stage):** +- `check-dco-commit-msg.sh`: Verifies `Signed-off-by:` trailer on every commit message + +#### Running Pre-commit Manually + +To run all hooks on all files: +```bash +pre-commit run --all-files +``` + +To run on staged files only: +```bash +pre-commit run +``` + +To run a specific hook: +```bash +pre-commit run black --all-files +pre-commit run ruff --all-files +``` + +--- + +## DCO: Developer Certificate of Origin + +Every commit (excluding merge commits) must carry a `Signed-off-by:` trailer. This is verified locally by the `check-dco-commit-msg.sh` hook (commit-msg stage) and by the `baseline-dco` job in the CI pipeline. + +**Sign a commit:** +```bash +git commit -s -m "feat: my feature" +``` + +**Enable automatic signing for all commits:** +```bash +git config format.signoff true +``` + +**Remediation if you forgot to sign:** +```bash +git commit --amend --signoff +git push --force-with-lease +``` + +For multiple unsigned commits, use interactive rebase: +```bash +git rebase --signoff HEAD~ +git push --force-with-lease +``` + +--- + +## REUSE / SPDX Compliance + +Every new source file must include an SPDX header. The `reuse` pre-commit hook and the `baseline-reuse` CI job enforce this: a PR with non-compliant files is **blocked**. + +**Python:** +```python +# SPDX-FileCopyrightText: 2026 [Your Organisation] +# +# SPDX-License-Identifier: Apache-2.0 +``` + +**YAML / shell:** +```yaml +# SPDX-FileCopyrightText: 2026 [Your Organisation] +# SPDX-License-Identifier: Apache-2.0 +``` + +License texts are stored in `LICENSES/Apache-2.0.txt` and `LICENSES/MIT.txt` at the repository root. + +**Check compliance locally:** +```bash +pre-commit run reuse --all-files +# or directly: +reuse lint +``` + +Standard: https://reuse.software/ + +--- + +#### Skipping Hooks (Not Recommended) + +If you need to skip hooks for a specific commit (not recommended): +```bash +git commit --no-verify -m "Emergency fix" +``` + +**Note:** Skipping hooks may cause CI to fail. Always fix issues before pushing. + +#### Updating Hooks + +To update hook versions: +```bash +pre-commit autoupdate +``` + +#### Troubleshooting + +If hooks fail: +1. Review the error messages +2. Most hooks auto-fix issues (black, ruff-format) +3. Fix remaining issues manually +4. Re-run: `pre-commit run --all-files` + +If you encounter issues with a specific hook, you can temporarily disable it by commenting it out in `.pre-commit-config.yaml`. + +--- + +## Type Hints + +### Requirements + +**All public functions must have type hints:** + +```python +# Good: Complete type hints +from typing import Dict, List, Optional, Tuple +import numpy as np +import xarray as xr + +def process_data( + input_file: str, + parameters: Dict[str, float], + output_format: Optional[str] = None +) -> xr.Dataset: + """Process input data.""" + pass + +# Avoid: Missing type hints +def process_data(input_file, parameters, output_format=None): + """Process input data.""" + pass +``` + +### Type Hint Examples + +**Basic Types:** +```python +def calculate_value(x: float, y: int) -> float: + """Calculate value.""" + return x * y +``` + +**Collections:** +```python +from typing import List, Dict, Tuple, Optional + +def process_list(items: List[str]) -> List[int]: + """Process list of strings.""" + pass + +def get_config() -> Dict[str, float]: + """Get configuration.""" + pass + +def get_coordinates() -> Tuple[float, float]: + """Get coordinates.""" + pass + +def find_item(key: str) -> Optional[str]: + """Find item, may return None.""" + pass +``` + +**NumPy Arrays:** +```python +import numpy as np +from numpy.typing import NDArray + +def process_array(data: NDArray[np.float64]) -> NDArray[np.float64]: + """Process numpy array.""" + pass +``` + +**xarray:** +```python +import xarray as xr + +def process_dataset(data: xr.Dataset) -> xr.Dataset: + """Process xarray dataset.""" + pass + +def get_variable(ds: xr.Dataset, name: str) -> xr.DataArray: + """Get variable from dataset.""" + pass +``` + +**Complex Types:** +```python +from typing import Union, Callable, Any + +def flexible_input(value: Union[str, int, float]) -> str: + """Accept multiple types.""" + pass + +def apply_function( + data: np.ndarray, + func: Callable[[np.ndarray], np.ndarray] +) -> np.ndarray: + """Apply function to data.""" + pass +``` + +**Generic Types:** +```python +from typing import TypeVar, Generic + +T = TypeVar('T') + +def process_generic(item: T) -> T: + """Process generic type.""" + pass +``` + +### Type Checking + +**Running mypy:** +```bash +# Type checking (reads config from pyproject.toml) +mypy src/ +``` + +**Common Issues:** +- Missing type hints: Add type hints to all public functions +- Incorrect types: Fix type annotations +- Import errors: Install type stubs (e.g. `types-PyYAML` is already included as a dependency) + +--- + +## Test Requirements + +### Test Coverage + +**Minimum Coverage:** ≥ 60% on code touched by the PR. + +This is enforced by the CI pipeline (`--cov-fail-under=60`). + +```bash +# Run tests with coverage +pytest --cov=src --cov-report=html + +# Check coverage threshold (matches CI) +pytest --cov=src --cov-fail-under=60 +``` + +### Test Markers + +All markers are declared in `pytest.ini`. Use them to classify tests and control which pipeline stage runs them: + +| Marker | Pipeline stage | Purpose | +|--------|---------------|---------| +| `unit` | Baseline (every PR) | Fast, isolated tests of individual functions | +| `baseline` | Baseline (every PR) | Quick sanity checks: output must match reference | +| `smoke` | Extended (Tier 1+) | Fast end-to-end sanity run | +| `integration` | Extended (Tier 1+) | Full workflow tests with realistic data | +| `extended` | Extended (Tier 1+) | Broader coverage for code changes | +| `heavy` | Heavy (Tier 2) | Full scientific regression, requires TDS dataset | +| `public` |: | Public API surface tests (legacy compatibility) | + +```python +# Example: tagging a test for the baseline pipeline +@pytest.mark.baseline +def test_output_matches_reference(): + ... + +# Example: tagging a slow test for Tier 2 only +@pytest.mark.heavy +def test_full_scientific_regression(): + ... +``` + +### Test Types + +#### Unit Tests + +**Purpose:** Test individual functions and classes in isolation + +**Location:** `tests/unit/` + +**Example:** +```python +import pytest +import numpy as np +from biomass_l2_core.algorithms import calculate_biomass + +def test_calculate_biomass_basic(): + """Test basic biomass calculation.""" + sar_data = np.array([0.1, 0.2, 0.3]) + incidence_angle = 30.0 + config = {"threshold": 0.5} + + result = calculate_biomass(sar_data, incidence_angle, config) + + assert result.shape == sar_data.shape + assert np.all(result >= 0) + assert np.all(result <= 500) # Max biomass constraint + +def test_calculate_biomass_edge_cases(): + """Test edge cases.""" + # Test with zero values + sar_data = np.array([0.0, 0.1, 0.2]) + result = calculate_biomass(sar_data, 30.0, {"threshold": 0.5}) + assert np.all(result >= 0) + + # Test with invalid input + with pytest.raises(ValueError): + calculate_biomass(np.array([-1.0]), 30.0, {"threshold": 0.5}) +``` + +**Best Practices:** +- Test one thing per test function +- Use descriptive test names +- Test edge cases and error conditions +- Use fixtures for test data +- Keep tests fast and independent + +#### Integration Tests + +**Purpose:** Test end-to-end workflows + +**Location:** `tests/integration/` + +**Example:** +```python +import pytest +from pathlib import Path +from biomass_l2_orchestration.workflows import process_l2_chain + +def test_end_to_end_processing(tmp_path): + """Test complete processing workflow.""" + input_file = "tests/data/l1_product.nc" + output_file = tmp_path / "output.nc" + config_file = "tests/data/config.yaml" + + process_l2_chain(input_file, output_file, config_file) + + assert output_file.exists() + # Verify output format and content + # Check metadata + # Validate scientific results +``` + +**Best Practices:** +- Use realistic test data +- Test complete workflows +- Verify output formats +- Clean up test artifacts + +#### Scientific Regression Tests + +**Purpose:** Ensure no scientific regressions + +**Location:** `tests/scientific_regression/` + +**Example:** +```python +import pytest +import numpy as np +from biomass_l2_core.algorithms import calculate_biomass + +def test_biomass_regression(): + """Test against reference results.""" + # Load reference data + reference_results = np.load("tests/data/reference_biomass.npy") + + # Process with current code + sar_data = np.load("tests/data/test_sar_data.npy") + result = calculate_biomass(sar_data, 30.0, {"threshold": 0.5}) + + # Compare with reference + np.testing.assert_allclose( + result, + reference_results, + rtol=1e-5, + atol=0.1 + ) +``` + +**Best Practices:** +- Use validated reference datasets +- Compare with previous versions +- Check for scientific accuracy +- Document reference data sources + +### Writing Tests + +**Test Structure:** +```python +def test_function_name_description(): + """ + Test description. + + What is being tested and why. + """ + # Arrange: Set up test data + input_data = create_test_data() + config = {"param": 1.0} + + # Act: Execute function + result = function_under_test(input_data, config) + + # Assert: Verify results + assert result is not None + assert result.shape == expected_shape + assert np.all(result >= 0) +``` + +**Test Fixtures:** +```python +import pytest + +@pytest.fixture +def sample_sar_data(): + """Fixture for sample SAR data.""" + return np.array([0.1, 0.2, 0.3, 0.4]) + +@pytest.fixture +def default_config(): + """Fixture for default configuration.""" + return { + "threshold": 0.5, + "max_iterations": 100 + } + +def test_with_fixtures(sample_sar_data, default_config): + """Test using fixtures.""" + result = process_data(sample_sar_data, default_config) + assert result is not None +``` + +**Parametrized Tests:** +```python +@pytest.mark.parametrize("input_value,expected", [ + (0.1, 10.0), + (0.2, 20.0), + (0.3, 30.0), +]) +def test_multiple_cases(input_value, expected): + """Test multiple input/output pairs.""" + result = calculate_value(input_value) + assert result == expected +``` + +### Running Tests + +```bash +# Unit tests (run locally before pushing) +pytest -m unit + +# Baseline tests (mirrors the CI baseline pipeline) +pytest -m baseline + +# Extended tests (Tier 1: code changes) +# ⚠ WARNING: extended tests require a TDS dataset not distributed with the repository. +# A procedure to retrieve the TDS will be made available shortly. +pytest -m extended + +# Heavy tests (Tier 2: scientific changes) +# ⚠ WARNING: heavy tests require a TDS dataset not distributed with the repository. +# A procedure to retrieve the TDS will be made available shortly. +pytest -m heavy + +# Specific test file +pytest tests/unit/test_algorithms.py + +# Specific test function +pytest tests/unit/test_algorithms.py::test_calculate_biomass_basic + +# With coverage (matches CI threshold) +pytest -m unit --cov=src --cov-report=html --cov-fail-under=60 + +# Verbose output +pytest -v + +# Stop on first failure +pytest -x +``` + +### Test Requirements Checklist + +**Before Submitting PR:** +- [ ] Unit tests added for new functions +- [ ] Integration tests for new workflows +- [ ] Scientific regression tests (if applicable) +- [ ] Test coverage meets minimum requirements +- [ ] All tests pass locally +- [ ] Tests are fast and independent +- [ ] Test data is included or documented + +--- + +## Documentation Standards + +### Docstring Format + +**NumPy Style (Required):** + +```python +def calculate_biomass( + sar_data: np.ndarray, + incidence_angle: float, + config: dict +) -> np.ndarray: + """ + Calculate above-ground biomass from SAR backscatter data. + + This function implements the inversion algorithm described in + [Reference Paper, 2023]. The algorithm uses P-band SAR data + to estimate forest biomass with uncertainty quantification. + + Parameters + ---------- + sar_data : np.ndarray + Input SAR backscatter data in linear scale. + Shape: (n_pixels,) or (n_azimuth, n_range) + Expected range: [0.0, 1.0] + incidence_angle : float + Incidence angle in degrees. + Range: [20, 60] + config : dict + Configuration parameters containing: + - threshold: float, detection threshold (default: 0.5) + - max_iterations: int, maximum iterations (default: 100) + - biomass_range: tuple, (min, max) in Mg/ha (default: (0, 500)) + + Returns + ------- + np.ndarray + Calculated biomass values in Mg/ha. + Shape: matches sar_data shape + Range: [0, 500] Mg/ha + + Raises + ------ + ValueError + If sar_data contains negative values or out-of-range data + RuntimeError + If convergence not achieved within max_iterations + + References + ---------- + .. [1] Author et al. (2023). "Biomass Estimation from P-band SAR". + Remote Sensing Journal. + + Examples + -------- + >>> import numpy as np + >>> sar_data = np.array([0.1, 0.2, 0.3]) + >>> angle = 30.0 + >>> config = {"threshold": 0.5} + >>> biomass = calculate_biomass(sar_data, angle, config) + >>> print(biomass) + [12.5 25.3 38.1] + + Notes + ----- + The algorithm assumes forested areas. Non-forested pixels + should be masked before calling this function. + """ + pass +``` + +### Documentation Sections + +**Required Sections:** +- **Summary**: One-line description +- **Parameters**: All parameters with types and descriptions +- **Returns**: Return value with type and description + +**Optional Sections:** +- **Raises**: Exceptions that may be raised +- **See Also**: Related functions +- **References**: Scientific papers or documentation +- **Examples**: Usage examples +- **Notes**: Additional information + +### Inline Comments + +**Use comments for "why", not "what":** + +```python +# Good: Explains why +# Use iterative method because analytical solution is unstable +# for low backscatter values +result = iterative_solve(data, threshold) + +# Avoid: States the obvious +# Calculate result +result = calculate(data) +``` + +--- + +## Error Handling + +### Exception Types + +**Use Appropriate Exception Types:** +```python +# ValueError: Invalid input values +if value < 0: + raise ValueError(f"Value must be non-negative, got {value}") + +# TypeError: Wrong type +if not isinstance(data, np.ndarray): + raise TypeError(f"Expected numpy array, got {type(data)}") + +# FileNotFoundError: Missing files +if not Path(file_path).exists(): + raise FileNotFoundError(f"File not found: {file_path}") + +# RuntimeError: Algorithm failures +if not converged: + raise RuntimeError("Algorithm did not converge") +``` + +### Error Messages + +**Informative Error Messages:** +```python +# Good: Clear and helpful +if sar_data.min() < 0: + raise ValueError( + f"SAR data contains negative values: min={sar_data.min()}. " + "Expected range: [0.0, 1.0]" + ) + +# Avoid: Vague +if sar_data.min() < 0: + raise ValueError("Invalid data") +``` + +### Error Handling Patterns + +**Try-Except Blocks:** +```python +try: + result = process_data(input_file) +except FileNotFoundError: + logger.error(f"Input file not found: {input_file}") + raise +except ValueError as e: + logger.error(f"Invalid input data: {e}") + raise +except Exception as e: + logger.error(f"Unexpected error: {e}", exc_info=True) + raise +``` + +**Custom Exceptions:** +```python +class BiomassCalculationError(Exception): + """Error in biomass calculation.""" + pass + +class ValidationError(Exception): + """Error in data validation.""" + pass + +# Usage +if invalid_condition: + raise BiomassCalculationError("Detailed error message") +``` + +--- + +## Logging + +### Logging Setup + +```python +import logging + +logger = logging.getLogger(__name__) + +def process_data(data): + """Process data with logging.""" + logger.info("Starting data processing") + logger.debug(f"Input data shape: {data.shape}") + + try: + result = process(data) + logger.info("Data processing completed successfully") + return result + except Exception as e: + logger.error(f"Data processing failed: {e}", exc_info=True) + raise +``` + +### Log Levels + +**Use Appropriate Log Levels:** +```python +# DEBUG: Detailed diagnostic information +logger.debug(f"Processing pixel {i}/{total}") + +# INFO: General informational messages +logger.info("Processing started") +logger.info(f"Processed {n_files} files") + +# WARNING: Warning messages +logger.warning("Unusual value detected, using default") +logger.warning(f"Low data quality: {quality_score}") + +# ERROR: Error messages +logger.error("Processing failed") +logger.error(f"Invalid configuration: {config}") + +# CRITICAL: Critical errors +logger.critical("System failure, aborting") +``` + +### Logging Best Practices + +**Include Context:** +```python +# Good: Includes context +logger.info(f"Processing file {file_path} with config {config_name}") + +# Avoid: Missing context +logger.info("Processing") +``` + +**Use Structured Logging:** +```python +# Good: Structured information +logger.info("Processing completed", extra={ + "file": file_path, + "duration": duration, + "pixels": n_pixels +}) + +# Avoid: String concatenation +logger.info(f"Processing completed: file={file_path}, duration={duration}") +``` + +--- + +## Code Review Checklist + +### Before Submitting + +- [ ] Code follows naming conventions +- [ ] Code formatted with `black` +- [ ] Code linted with `ruff` +- [ ] Type hints added to all functions +- [ ] Docstrings added (NumPy style) +- [ ] Tests added and passing +- [ ] Test coverage meets requirements +- [ ] Documentation updated +- [ ] No secrets or sensitive data +- [ ] Pre-commit hooks passing + +### Code Quality + +- [ ] Functions are focused and short +- [ ] No code duplication +- [ ] Error handling is appropriate +- [ ] Logging is used appropriately +- [ ] Performance considerations documented +- [ ] Code is readable and maintainable + +--- + +## Resources + +### Documentation + +- [Getting Started](../getting-started/index.md) - Introduction and getting started guide +- [Licensing](../about/licensing/index.md) - Apache 2.0 license requirements and legal obligations +- [Code of Conduct](code-of-conduct.md) - Community standards and expectations +- [Contributing overview](index.md) - Contribution process and workflows +- [CI automation and contribution tiers](ci-automation-and-contribution-tiers.md) - Pipeline reference, tier detection, branch protection +- [Governance](../governance/index.md) - Roles, responsibilities, and decision-making +- [Architecture](architecture.md) - Monorepo layout and `bps-*` modules +- [Documentation standards](documentation-standards.md) - Documentation writing standards and best practices +- [Communication](../communication/index.md) - Communication channels and meeting schedules + +### External Resources + +- [PEP 8](https://www.python.org/dev/peps/pep-0008/) - Python style guide +- [black](https://black.readthedocs.io/) - Code formatter +- [ruff](https://docs.astral.sh/ruff/) - Fast linter +- [mypy](https://mypy.readthedocs.io/) - Type checker +- [pytest](https://docs.pytest.org/) - Testing framework + +--- + +## Code Review Process + +The following flowchart illustrates the code review process from writing code to merging: + +```{mermaid} +flowchart TD + Code[Code written] --> Lint[Pre-commit hooks
black, ruff, mypy] + Lint --> LintOK{Pass?} + LintOK -->|No| FixLint[Fix errors] + FixLint --> Lint + LintOK -->|Yes| Tests[Run local tests] + + Tests --> TestOK{All pass?} + TestOK -->|No| FixTests[Fix tests] + FixTests --> Tests + TestOK -->|Yes| Docs[Check documentation] + + Docs --> DocOK{Complete?} + DocOK -->|No| AddDocs[Add documentation] + AddDocs --> Docs + DocOK -->|Yes| PR[Create Pull Request] + + PR --> CI[CI/CD Pipeline] + CI --> CIOK{CI passes?} + CIOK -->|No| FixCI[Fix CI issues] + FixCI --> PR + CIOK -->|Yes| Review[Review by maintainers] + + Review --> ReviewOK{Approved?} + ReviewOK -->|No| Address[Address comments] + Address --> PR + ReviewOK -->|Yes| Merge[Merge into develop] + + classDef defaultStyle fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px,color:#333 + class Code,Lint,LintOK,FixLint,Tests,TestOK,FixTests,Docs,DocOK,AddDocs,PR,CI,CIOK,FixCI,Review,ReviewOK,Address,Merge defaultStyle + + style Code fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px + style Lint fill:#e1bee7,stroke:#e1bee7,stroke-width:2px + style Tests fill:#a3d8b0,stroke:#a3d8b0,stroke-width:2px + style Docs fill:#e1bee7,stroke:#e1bee7,stroke-width:2px + style PR fill:#f5f5f5,stroke:#9e9e9e,stroke-width:2px + style CI fill:#ef9a9a,stroke:#e57373,stroke-width:2px + style Review fill:#e1bee7,stroke:#e1bee7,stroke-width:2px + style Merge fill:#a3d8b0,stroke:#a3d8b0,stroke-width:3px +``` + +--- + +**Questions?** Open an issue with the `code-standards` label or contact core maintainers. + +**Last Updated:** 2026 + +--- + +**Previous:** [Architecture](architecture.md) | **Next:** [Documentation standards](documentation-standards.md) + diff --git a/docs/contributing/documentation-standards.md b/docs/contributing/documentation-standards.md new file mode 100644 index 0000000..03a9f56 --- /dev/null +++ b/docs/contributing/documentation-standards.md @@ -0,0 +1,459 @@ +# Documentation Standards + +Complete documentation standards for BioPAL, including docstring formats, writing conventions, documentation types, and best practices. + +--- + +## Table of Contents + +1. Overview +2. Documentation Types +3. Docstring Format (NumPy Style) +4. Writing Conventions +5. Documentation Structure +6. Building and Previewing Documentation +7. Documentation Updates Required +8. Best Practices + +--- + +## Overview + +If you're not the developer type, contributing to the documentation is still of huge value. You don't even have to be an expert on BioPAL to do so! In fact, there are sections of the docs that are worse off after being written by experts. If something in the docs doesn't make sense to you, updating the relevant section after you figure it out is a great way to ensure it will help the next person. + +The BioPAL documentation consists of two main parts: + +- **Docstrings in the code itself**: These are meant to provide a clear explanation of the usage of individual functions, classes, and methods. They follow the **NumPy Docstring Standard**, which is used widely in the Scientific Python community. +- **Standalone documentation files**: These consist of tutorial-like overviews per topic together with other information (getting started, installation, architecture, etc.). These are typically written in Markdown or reStructuredText format. + +**Key principles:** + +- **Docstrings** should focus on *how* to use a function or class +- **Standalone documentation** should provide *why* and *when* to use features, with examples and tutorials +- **Keep documentation up to date**: When you change code, update the corresponding documentation +- **Write for your past self**: Document things that weren't obvious to you when you first learned them + +--- + +## Documentation Types + +### 1. User Documentation + +**Purpose:** How to use the processors + +**Content:** +- Installation guides +- User tutorials +- Usage examples +- Configuration guides +- Troubleshooting + +**Location:** `docs/user/` or similar + +### 2. Developer Documentation + +**Purpose:** How to contribute and extend + +**Content:** +- Contributing guides +- Development workflows +- Architecture documentation +- Extension guides + +**Location:** `docs/developer/` or similar + +### 3. API Documentation + +**Purpose:** Auto-generated from docstrings + +**Content:** +- Function and class references +- Parameter descriptions +- Return value documentation +- Usage examples + +**Location:** Auto-generated from code docstrings + +### 4. Scientific Documentation + +**Purpose:** Algorithm descriptions and validation + +**Content:** +- Algorithm descriptions +- Scientific validation results +- Methodology documentation +- References to papers + +**Location:** `docs/science-guide/` (ATBDs and PFDs). + +--- + +## Docstring Format (NumPy Style) + +All docstrings in BioPAL must follow the **NumPy Docstring Standard**. This format is widely used in the Scientific Python community and provides a clear, structured way to document functions and classes. + +### Complete Docstring Template + +```python +def calculate_biomass( + sar_data: np.ndarray, + incidence_angle: float, + config: dict +) -> np.ndarray: + """ + Calculate above-ground biomass from SAR backscatter data. + + This function implements the inversion algorithm described in + [Reference Paper, 2023]. The algorithm uses P-band SAR data + to estimate forest biomass with uncertainty quantification. + + Parameters + ---------- + sar_data : np.ndarray + Input SAR backscatter data in linear scale. + Shape: (n_pixels,) or (n_azimuth, n_range) + Expected range: [0.0, 1.0] + incidence_angle : float + Incidence angle in degrees. + Range: [20, 60] + config : dict + Configuration parameters containing: + - threshold: float, detection threshold (default: 0.5) + - max_iterations: int, maximum iterations (default: 100) + - biomass_range: tuple, (min, max) in Mg/ha (default: (0, 500)) + + Returns + ------- + np.ndarray + Calculated biomass values in Mg/ha. + Shape: matches sar_data shape + Range: [0, 500] Mg/ha + + Raises + ------ + ValueError + If sar_data contains negative values or out-of-range data + RuntimeError + If convergence not achieved within max_iterations + + References + ---------- + .. [1] Author et al. (2023). "Biomass Estimation from P-band SAR". + Remote Sensing Journal. + + Examples + -------- + >>> import numpy as np + >>> sar_data = np.array([0.1, 0.2, 0.3]) + >>> angle = 30.0 + >>> config = {"threshold": 0.5} + >>> biomass = calculate_biomass(sar_data, angle, config) + >>> print(biomass) + [12.5 25.3 38.1] + + Notes + ----- + The algorithm assumes forested areas. Non-forested pixels + should be masked before calling this function. + """ + pass +``` + +### Required Sections + +**Summary (first line):** +- One-line description of what the function/class does +- Should be a complete sentence ending with a period + +**Parameters:** +- All parameters with types and descriptions +- Include constraints, ranges, and default values +- For complex types (dict, list), describe the structure + +**Returns:** +- Return value with type and description +- Include shape, range, or other constraints + +### Optional Sections + +**Raises:** +- Exceptions that may be raised +- When and why they are raised + +**See Also:** +- Related functions or classes +- Links to relevant documentation + +**References:** +- Scientific papers or documentation +- Use numbered references format + +**Examples:** +- Usage examples +- Should be executable (tested with doctest) +- Show common use cases + +**Notes:** +- Additional information +- Implementation details +- Warnings or important considerations + +--- + +## Writing Conventions + +### For Markdown Files + +**Headings:** +- Use clear, descriptive headings +- Follow a consistent hierarchy (#, ##, ###) +- Use title case for main headings + +**Paragraphs:** +- Keep paragraphs short and focused +- One main idea per paragraph +- Use line breaks for readability + +**Code Blocks:** +- Use code blocks with syntax highlighting +- Include language identifier: ` ```python ` +- Make code examples executable when possible + +**Lists:** +- Use lists for step-by-step instructions +- Use numbered lists for ordered steps +- Use bullet lists for unordered items + +**Links:** +- Link to related documentation +- Use descriptive link text +- Check that links are valid + +**Examples:** +- Include examples where helpful +- Show, don't just tell +- Test examples to ensure they work + +### For reStructuredText Files (if used) + +**Section Markup:** +- `=`, for main headings +- `-`, for sections +- `~`, for subsections + +**Directives:** +- Use `:doc:` directive for internal document links +- Use `:ref:` for cross-references +- Use `:code:` for inline code + +**Follow the [Sphinx reStructuredText documentation](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html)** + +### Inline Comments + +**Use comments for "why", not "what":** + +```python +# Good: Explains why +# Use iterative method because analytical solution is unstable +# for low backscatter values +result = iterative_solve(data, threshold) + +# Avoid: States the obvious +# Calculate result +result = calculate(data) +``` + +--- + +## Documentation Structure + +### Referring to Other Documents + +**In Markdown:** +- Use standard markdown link syntax: `[text](/path/to/doc)` +- Use relative paths for internal links +- Use absolute paths for cross-references + +**In reStructuredText:** +- Use `:doc:` directive for internal document links +- Use `:ref:` for cross-references with labels + +### Including Figures and Files + +**Images:** +- Use standard markdown image syntax: `![alt text](path/to/image.png)` +- Or reStructuredText image directives: `.. image:: path/to/image.png` +- Include alt text for accessibility +- Keep images optimized and appropriately sized + +**Code Files:** +- Reference code files when relevant +- Use code blocks for small snippets +- Link to full files in repository for larger examples + +--- + +## Building and Previewing Documentation + +### Building Documentation Locally + +You can build the documentation locally to preview your changes. The documentation build process includes HTML generation, local serving, and link checking. + +**Basic commands:** +```bash +# Build HTML documentation +make docs + +# Build and serve locally +make docs-serve + +# Check for broken links +make docs-linkcheck +``` + +**Viewing built documentation locally:** + +After building the documentation, you can view it in your browser. The path depends on your build system: + +```bash +# For Sphinx, typically: +open docs/_build/html/index.html # macOS +xdg-open docs/_build/html/index.html # Linux +start docs/_build/html/index.html # Windows +``` + +### View Documentation Preview in CI/CD + +The **`baseline-docs`** job in the CI pipeline (`ci.yml`) automatically runs a Sphinx build if the file `docs/api/conf.py` exists in the module. This job is part of the baseline pipeline and runs on every PR: its result is visible in the PR checks list. + +If you have made updates to the documentation, you can see the build result by: + +1. Clicking on "Details" next to the `baseline-docs` check in the PR checks list +2. Reviewing the Sphinx build output for errors or warnings + +This allows you to verify your documentation changes before they are merged. + +--- + +## Documentation Updates Required + +When contributing, update documentation as needed: + +### Code Changes + +- **User-facing changes**: Update user guides and tutorials +- **API changes**: Update API documentation and docstrings +- **Workflow changes**: Update developer guides + +### Specific Updates + +- **User guides**: If user-facing functionality changes +- **Developer guides**: If development workflow changes +- **API documentation**: If adding/modifying functions or classes +- **Notebooks**: If examples are affected +- **Product definitions**: If products change +- **Architecture docs**: If system architecture changes + +### Checklist + +Before submitting a pull request, verify: + +- [ ] Docstrings updated for new/modified functions +- [ ] User documentation updated (if applicable) +- [ ] Developer documentation updated (if applicable) +- [ ] Examples updated and tested +- [ ] Links are valid +- [ ] Documentation builds without errors + +--- + +## Best Practices + +### General Principles + +**Be consistent:** +- Follow existing documentation style +- Use the same terminology throughout +- Maintain consistent formatting + +**Use examples:** +- Show, don't just tell +- Include working code examples +- Test examples to ensure they work + +**Keep it simple:** +- Write for someone new to the project +- Avoid jargon when possible +- Explain acronyms on first use + +**Update as you go:** +- Don't leave documentation for later +- Update docs when you update code +- Fix documentation bugs promptly + +**Test your examples:** +- Make sure code examples actually work +- Run doctests to verify examples +- Update examples when code changes + +**Link appropriately:** +- Help readers find related information +- Use descriptive link text +- Check that links are valid + +### Writing Style + +**Tone:** +- Professional but friendly +- Clear and direct +- Helpful and encouraging + +**Language:** +- Use active voice when possible +- Write in present tense +- Use second person ("you") for user-facing docs + +**Structure:** +- Start with the most important information +- Use headings to organize content +- Break up long sections + +### Documentation Maintenance + +**Regular reviews:** +- Review documentation periodically +- Update outdated information +- Remove deprecated content + +**Community contributions:** +- Welcome documentation contributions +- Review documentation PRs carefully +- Provide feedback constructively + +--- + +## Resources + +### Documentation + +- [Contributing overview](index.md) - Contribution process and workflows +- [Code standards](code-standards.md) - Coding conventions and best practices +- [Architecture](architecture.md) - System architecture and design + +### External Resources + +- [NumPy Docstring Guide](https://numpydoc.readthedocs.io/en/latest/format.html) +- [Sphinx Documentation](https://www.sphinx-doc.org/) +- [Markdown Guide](https://www.markdownguide.org/) +- [reStructuredText Primer](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) + +--- + +**Questions?** Open an issue with the `documentation` label or contact core maintainers. + +**Last Updated:** 2025 + +--- + +**Previous:** [Code standards](code-standards.md) | **Next:** [Practical workflow](practical-workflow.md) + diff --git a/docs/contributing/implementation.md b/docs/contributing/implementation.md new file mode 100644 index 0000000..c4bb9e4 --- /dev/null +++ b/docs/contributing/implementation.md @@ -0,0 +1,164 @@ +# Implementation + +**Stage 2 of the contribution workflow.** Once your issue carries an approval +label, fork the repository, implement the change, run local checks, sign your +commits, and open a pull request. + +For step-by-step commands, see [Practical workflow](practical-workflow.md). + +--- + +```{raw} html +Contribution Process +Contribution Process +``` + +## Five steps from approved issue to live PR + +You start with an **approved issue in hand** and end with a **pull +request live on GitHub** that the CI has begun classifying. Each step +is small; together they keep the diff focused and the CI stable. + +::::{grid} 1 1 1 1 +:gutter: 2 +:class-container: workflow-steps-list + +:::{grid-item-card} +:class-card: workflow-step-card sd-border-info + +
+ +01 + +
+ +**Fork and branch** + +Fork the repository on GitHub and create a feature branch from an +up-to-date `develop`. Branch prefix: `feature/`, `bugfix/`, or `docs/`. + +
+ +
+::: + +:::{grid-item-card} +:class-card: workflow-step-card sd-border-info + +
+ +02 + +
+ +**Implement and test** + +Code the change inside the approved scope. Add or update tests so the +behaviour is exercised at the right tier (`unit`, `baseline`, +`extended`). + +
+ +
+::: + +:::{grid-item-card} +:class-card: workflow-step-card sd-border-info + +
+ +03 + +
+ +**Local checks** + +Run `ruff`, `mypy`, and `pytest -m unit` locally. Add `baseline` if you +expect the marker output to move. Save the CI a round-trip. + +
+ +
+::: + +:::{grid-item-card} +:class-card: workflow-step-card sd-border-info + +
+ +04 + +
+ +**Commit, signed off** + +Every commit needs a `Signed-off-by:` trailer for the DCO. Group +related changes into atomic commits with clear messages. + +
+ +
+::: + +:::{grid-item-card} +:class-card: workflow-step-card sd-border-success + +
+ +05 + +
+ +**Open the pull request** + +Push the branch and open a PR against `develop` that links the issue +with `Closes #N`. The CI starts and the tier is computed from the diff. + +
+ +
+::: + +:::: + +```{tip} +**Local mirror of the CI gate.** Running +`ruff check . && mypy . && pytest -m unit` before pushing catches more +than 90 % of CI failures. +``` + +## Workflow conventions + +- **Start from develop**: always create feature branches from an + up-to-date `develop`. +- **One feature per branch**: keep changes focused and atomic. +- **Stay in sync**: rebase or merge `develop` regularly to avoid + surprise conflicts. +- **Commit small and often**: small, logical commits make review and + bisect much easier. +- **Mirror the CI locally**: run `pre-commit`, `ruff`, `mypy`, and the + unit suite before pushing. + +## DCO: Developer Certificate of Origin + +Every commit (excluding merge commits) must carry a `Signed-off-by:` trailer. This is a legal statement that you wrote the code and have the right to contribute it under the project's license. + +```bash +# Sign a commit +git commit -s -m "feat: my contribution" + +# Enable automatic signing for all commits +git config format.signoff true +``` + +Remediation if you forgot: +```bash +git commit --amend --signoff +git push --force-with-lease +``` + +The `baseline-dco` CI job blocks any PR containing an unsigned commit. + +--- + +**Previous:** [Proposal and approval](proposal-and-approval.md) | **Next:** [Review and integration](review-and-integration.md) diff --git a/docs/contributing/index.md b/docs/contributing/index.md new file mode 100644 index 0000000..7e42538 --- /dev/null +++ b/docs/contributing/index.md @@ -0,0 +1,335 @@ + + +# Contributing + +Thank you for your interest in contributing to the **BIOMASS Processing +Suite (BPS)**. We welcome contributions from the scientific community, +industrial partners, and external developers. + +## How a contribution flows + +Every contribution follows the same five steps. **No code is written +before the issue has been approved.** This is a deliberate guardrail +that protects contributors from wasted effort and protects the project +from scope creep. + +::::{grid} 1 1 1 1 +:gutter: 2 + +:::{grid-item-card} 1 · Open an issue +:class-card: sd-border-success + +Pick one of the five templates: bug report, feature or enhancement +request, algorithm proposal, documentation issue, or security report. +::: + +:::{grid-item-card} 2 · Triage +:class-card: sd-border-success + +A maintainer reviews the issue and applies routing labels: +`needs-triage`, `needs-sme`, or `needs-discussion`. +::: + +:::{grid-item-card} 3 · Approval (the gate) +:class-card: sd-border-warning + +The issue is labelled `status:approved`, `good-first-issue`, +or `help-wanted`. **Until one of these labels is set, do not code.** +::: + +:::{grid-item-card} 4 · Code and open a PR +:class-card: sd-border-success + +Fork, branch, commit with `Signed-off-by` (DCO), open a pull request +that links the issue. +::: + +:::{grid-item-card} 5 · Review and merge +:class-card: sd-border-success + +CODEOWNERS review, CI green, squash merge. The linked issue closes +automatically. +::: + +:::: + +For questions or open-ended discussions that are not yet a concrete +issue, use [GitHub Discussions](https://github.com/BioPAL/BPS/discussions). +Issues are reserved for actionable items. + +## The three stages + +```{mermaid} +flowchart TD + subgraph Before["1. Proposal and approval"] + direction TB + Backlog["Backlog"] + PathA["Path A
Pick an approved issue"] + PathB["Path B
Propose a new issue"] + Gate{"Wait for the label
status:approved
good-first-issue
help-wanted"} + Backlog --> PathA + Backlog --> PathB + PathA --> Gate + PathB --> Gate + end + + subgraph Building["2. Implementation"] + direction TB + Fork["Fork & branch"] + Implement["Implement + tests"] + Local["Local checks
ruff · mypy · pytest"] + Commit["Commit signed off"] + OpenPR["Open the PR"] + Fork --> Implement --> Local --> Commit --> OpenPR + end + + subgraph After["3. Review and integration"] + direction TB + CI{"CI status"} + Review["Review"] + Merge["Squash merge"] + Released["Released
tag + DOI"] + Iterate["Iterate
push fixes"] + CI -->|green| Review + Review --> Merge + Merge --> Released + CI -->|red| Iterate + Iterate --> CI + end + + Gate -->|approved| Fork + OpenPR --> CI + + classDef gate fill:#FFE082,stroke:#FFB300,color:#000 + class Gate gate +``` + +::::{grid} 1 3 3 3 +:gutter: 3 + +:::{grid-item-card} +:link: proposal-and-approval +:link-type: doc +:class-card: intro-card + + **1. Proposal and approval** +^^^ +Backlog, issue templates, triage, and the approval gate. Do not code until the issue is approved. +::: + +:::{grid-item-card} +:link: implementation +:link-type: doc +:class-card: intro-card + + **2. Implementation** +^^^ +Fork, implement, local checks, DCO-signed commits, and open the pull request. +::: + +:::{grid-item-card} +:link: review-and-integration +:link-type: doc +:class-card: intro-card + + **3. Review and integration** +^^^ +CI green/red loop, review, baseline changes, merge, and release path. +::: + +:::: + +## Foundations + +::::{grid} 1 3 3 3 +:gutter: 3 + +:::{grid-item-card} +:link: ../about/licensing/index +:link-type: doc +:class-card: intro-card + + **Licensing** +^^^ +Apache 2.0 terms, REUSE compliance, SPDX headers, and dependency licence +requirements. Every contribution must comply. +::: + +:::{grid-item-card} +:link: code-of-conduct +:link-type: doc +:class-card: intro-card + + **Code of Conduct** +^^^ +Community standards that govern interactions inside the project. +Required reading for all contributors. +::: + +:::{grid-item-card} +:link: ../governance/index +:link-type: doc +:class-card: intro-card + + **Governance** +^^^ +Roles (Maintainer, SME, ESA), decision authority, and the chain of +approvals. Who decides what, and when. +::: + +:::: + +## Quick reference + +::::{grid} 1 2 2 2 +:gutter: 3 + +:::{grid-item-card} +:link: ci-automation-and-contribution-tiers +:link-type: doc +:class-card: intro-card + + **CI automation and contribution tiers** +^^^ +Tier classification, CI job catalog, branch protection, and judge-from-base policy. +::: + +:::{grid-item-card} +:link: templates-and-checklists +:link-type: doc +:class-card: intro-card + + **Templates and checklists** +^^^ +Issue and PR templates, CODEOWNERS routing, and pre-submission checklists. +::: + +:::{grid-item-card} +:link: quality-and-validation +:link-type: doc +:class-card: intro-card + + **Quality and validation** +^^^ +Testing, scientific validation, backwards compatibility, and documentation expectations. +::: + +:::{grid-item-card} +:link: practical-workflow +:link-type: doc +:class-card: intro-card + + **Practical workflow** +^^^ +Environment setup, Git commands, and code examples for tests and standards. +::: + +:::{grid-item-card} +:link: becoming-a-maintainer +:link-type: doc +:class-card: intro-card + + **Becoming a maintainer** +^^^ +Path from contributor to maintainer: stages, qualities, and responsibilities. +::: + +:::{grid-item-card} +:link: ../communication/index +:link-type: doc +:class-card: intro-card + + **Community** +^^^ +Meetings, GitHub Discussions categories, workshops, and communication channels. +::: + +:::{grid-item-card} +:link: release-process +:link-type: doc +:class-card: intro-card + + **Release process** +^^^ +Maintainer runbook: develop to release to main, versioning, and ESA gate. +::: + +:::: + +## Technical reference + +::::{grid} 1 3 3 3 +:gutter: 3 + +:::{grid-item-card} +:link: architecture +:link-type: doc +:class-card: intro-card + + **Architecture** +^^^ +Monorepo layout, `bps-*` modules, dependency graph, and processor structure. +::: + +:::{grid-item-card} +:link: code-standards +:link-type: doc +:class-card: intro-card + + **Code standards** +^^^ +Naming, formatting, type hints, tests, error handling, and logging. +::: + +:::{grid-item-card} +:link: documentation-standards +:link-type: doc +:class-card: intro-card + + **Documentation standards** +^^^ +Docstrings, writing conventions, and documentation update expectations. +::: + +:::{grid-item-card} +:link: ../about/applicable-documents +:link-type: doc +:class-card: intro-card + + **Interface specifications** +^^^ +Official ICD, IODD, and auxiliary product format PDFs for integrators. +::: + +:::: + +--- + +**Need help?** Use [GitHub Discussions](https://github.com/BioPAL/BPS/discussions). +The [Q&A category](https://github.com/BioPAL/BPS/discussions/categories/q-a) +is the recommended place for usage questions, and +[Scientific discussions](https://github.com/BioPAL/BPS/discussions/categories/scientific-discussions) +is the right space for algorithm and methodology questions. + +```{toctree} +:caption: Contributing +:maxdepth: 1 +:hidden: + +Code of Conduct +Proposal and approval +Implementation +Review and integration +Practical workflow +Templates and checklists +Architecture +Code standards +Documentation standards +CI automation and contribution tiers +Quality and validation +Becoming a maintainer +Release process +``` diff --git a/docs/contributing/journey/implementation.md b/docs/contributing/journey/implementation.md new file mode 100644 index 0000000..127b157 --- /dev/null +++ b/docs/contributing/journey/implementation.md @@ -0,0 +1,9 @@ +--- +orphan: true +--- + + + +# Implementation + +Redirecting to [Implementation](../implementation.md). diff --git a/docs/contributing/journey/index.md b/docs/contributing/journey/index.md new file mode 100644 index 0000000..2a2ce43 --- /dev/null +++ b/docs/contributing/journey/index.md @@ -0,0 +1,9 @@ +--- +orphan: true +--- + + + +# Contribution journey + +Redirecting to [Contributing](../index.md). diff --git a/docs/contributing/journey/proposal-and-approval.md b/docs/contributing/journey/proposal-and-approval.md new file mode 100644 index 0000000..1feba15 --- /dev/null +++ b/docs/contributing/journey/proposal-and-approval.md @@ -0,0 +1,9 @@ +--- +orphan: true +--- + + + +# Proposal and approval + +Redirecting to [Proposal and approval](../proposal-and-approval.md). diff --git a/docs/contributing/journey/review-and-integration.md b/docs/contributing/journey/review-and-integration.md new file mode 100644 index 0000000..000bdd4 --- /dev/null +++ b/docs/contributing/journey/review-and-integration.md @@ -0,0 +1,9 @@ +--- +orphan: true +--- + + + +# Review and integration + +Redirecting to [Review and integration](../review-and-integration.md). diff --git a/docs/contributing/practical-workflow.md b/docs/contributing/practical-workflow.md new file mode 100644 index 0000000..d52cb9d --- /dev/null +++ b/docs/contributing/practical-workflow.md @@ -0,0 +1,653 @@ +# Practical workflow + +Step-by-step instructions with commands and code examples for setting up +your environment and implementing contributions. + +**Prerequisites:** Read the journey pages first: +[Proposal and approval](proposal-and-approval.md), [Implementation](implementation.md), +and [Review and integration](review-and-integration.md). + +--- + +## Table of Contents + +- Version Control, Git, and GitHub +- Getting Started - Setting Up Your Environment +- Development Workflow - Step by Step +- Coding Standards - Code Examples +- Testing Requirements - Code Examples and Commands +- Documentation Standards - Code Examples and Commands + +--- + +## Where to start? + +See [Proposal and approval](proposal-and-approval.md) for the approval gate, issue templates, +and bug-report guidance. + +--- + +## Version Control, Git, and GitHub + + +The code is hosted on GitHub. To contribute you will need to sign up for a [free GitHub account](https://github.com/signup/free). We use [Git](https://git-scm.com/) for version control to allow many people to work together on the project. + +### Learning Git + +See [Learning resources](templates-and-checklists.md#learning-resources)) in Templates & checklists, or [GitHub's setup guide](https://help.github.com/set-up-git-redirect). + +### Getting Started with Git + +[GitHub has instructions for setting up Git](https://help.github.com/set-up-git-redirect) including installing git, setting up your SSH key, and configuring git. All these steps need to be completed before you can work seamlessly between your local repository and GitHub. + +**Note:** The following instructions assume you want to learn how to interact with GitHub via the git command-line utility, but contributors who are new to git may find it easier to use other tools instead such as [GitHub Desktop](https://desktop.github.com/). + +### Basic Git Configuration + +Before you start, make sure Git is configured with your name and email: + +```bash +git config --global user.name "Your Name" +git config --global user.email "your.email@example.com" +``` + +For more information on Git configuration, see the [Git configuration documentation](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration). + +--- + +## Getting Started - Setting Up Your Environment + + +### Prerequisites + +- Python 3.12 +- Git +- Basic understanding of Earth observation and SAR processing (for scientific contributions) +- Familiarity with Python development tools (for code contributions) + +### Setting Up Your Development Environment + +1. **Create an account** on [GitHub](https://github.com/) if you do not already have one. + +2. **Fork the repository** (if contributing externally): + - Go to the repository page on GitHub and hit the `Fork` button near the top of the page. + - This creates a copy of the code under your account on the GitHub server. + +3. **Clone your fork** to your machine: +```bash +git clone https://github.com/your-username/biomass-bps.git +cd biomass-bps +git remote add upstream https://github.com//biomass-bps.git +``` + +If you're an internal contributor, clone directly: +```bash +git clone https://github.com//biomass-bps.git +cd biomass-bps +``` + +4. **Fetch tags** (optional but recommended): +```bash +git fetch --tags upstream +``` + +5. **Create a virtual environment**: +```bash +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +``` + +**Note:** For small changes such as fixing a typo or updating documentation, you don't necessarily need to build and test locally. Push to a feature branch, the CI will run and report any issues. For more complex changes, build and test locally first. + +6. **Install a module in development mode** (repeat per module you work on): +```bash +cd bps- +pip install -e ".[dev]" +``` + +7. **Set up pre-commit hooks**: +```bash +pip install pre-commit +pre-commit install --hook-type commit-msg +pre-commit install +``` +The `--hook-type commit-msg` installs the DCO hook that checks for `Signed-off-by:` on every commit. To run all hooks manually: +```bash +pre-commit run --all-files +``` + +8. **Run tests to verify setup**: +```bash +pytest -m unit +``` + +### Repository Structure + +See [architecture.md](architecture.md#structure-of-a-typical-module)) for the full structure. Each `bps-*` module follows this layout: + +``` +bps-/ +├── src/ # Source code +├── tests/ +│ ├── unit/ # Unit tests (pytest -m unit) +│ ├── baseline/ # Baseline tests (pytest -m baseline) +│ ├── extended/ # Extended tests: TDS required (pytest -m extended) +│ └── heavy/ # Heavy tests: TDS required (pytest -m heavy) +└── pyproject.toml +``` + +--- + +## Development Workflow - Step by Step + +This section provides commands for the build phase. For concepts, see +[Implementation](implementation.md). For branching policy, see +[CI automation and contribution tiers](ci-automation-and-contribution-tiers.md#branching-strategy)). + +Branch from an up-to-date `develop` using `feature/`, `bugfix/`, or `docs/` prefixes. + +### Update the develop branch + +Before starting a new set of changes, fetch all changes from `upstream/develop` (or `origin/develop` if working directly on the main repository), and start a new feature branch from that. From time to time you should fetch the upstream changes from GitHub: + +```bash +git fetch --tags upstream +git merge upstream/develop +``` + +Or if you're working directly on the main repository: + +```bash +git fetch --tags origin +git merge origin/develop +``` + +This will combine your commits with the latest BioPAL git `develop` branch. If this leads to merge conflicts, you must resolve these before submitting your pull request. If you have uncommitted changes, you will need to `git stash` them prior to updating. This will effectively store your changes, which can be reapplied after updating: + +```bash +git stash +git fetch upstream +git merge upstream/develop +git stash pop # Reapply your changes +``` + +### Create a new feature branch + +Create a branch to save your changes, even before you start making changes. You want your `develop` branch to contain the latest development state, and your `main` branch to contain only production-ready code: + +```bash +git checkout -b feature/your-feature-name +``` + +This changes your working directory to the `feature/your-feature-name` branch. Keep any changes in this branch specific to one bug or feature so it is clear what the branch brings to BioPAL. You can have many feature branches and switch between them using the `git checkout` command. + +Generally, you will want to keep your feature branches on your public GitHub fork of BioPAL. To do this, you `git push` this new branch up to your GitHub repo. Generally (if you followed the instructions in these pages, and by default), git will have a link to your fork of the GitHub repo, called `origin`. You push up to your own fork with: + +```bash +git push origin feature/your-feature-name +``` + +In git >= 1.7 you can ensure that the link is correctly set by using the `--set-upstream` option: + +```bash +git push --set-upstream origin feature/your-feature-name +``` + +From now on git will know that `feature/your-feature-name` is related to the `feature/your-feature-name` branch in the GitHub repo. + +### The editing workflow + +1. **Make some changes** to the code or documentation. + +2. **See which files have changed** with `git status`. You'll see a listing like this one: + +```bash +# On branch feature/your-feature-name +# Changes not staged for commit: +# (use "git add ..." to update what will be committed) +# (use "git checkout -- ..." to discard changes in working directory) +# +# modified: src/biomass_l2_core/algorithms.py +# modified: tests/unit/test_algorithms.py +# +# Untracked files: +# (use "git add ..." to include in what will be committed) +# +# new_file.py +``` + +3. **Check what the actual changes are** with `git diff`: + +```bash +git diff # Shows unstaged changes +git diff --staged # Shows staged changes +git diff src/biomass_l2_core/algorithms.py # Shows changes in a specific file +``` + +4. **Build the documentation** (for documentation changes): see [Documentation standards](practical-workflow.md#documentation-standards---code-examples-and-commands)). + +5. **Run the test suite** (for code changes): see [Testing requirements](practical-workflow.md#testing-requirements---code-examples-and-commands)). + +### Step-by-Step Contribution Process + +1. **Create a feature branch** (see above): +```bash +git checkout develop +git pull origin develop # or git pull upstream develop if using fork +git checkout -b feature/your-feature-name +``` + +2. **Make your changes** (see "The editing workflow" above): + - Write code following our [Code standards](code-standards.md) + - Add or update tests + - Update documentation as needed + - Use `git status` and `git diff` to review your changes + +3. **Run local checks**: + + **Note:** If you've set up pre-commit hooks (recommended), many checks will run automatically when you commit. You can also run them manually: + +```bash +# Run all pre-commit hooks manually +pre-commit run --all-files + +# Or run individual checks: +# Format code +black src/ tests/ + +# Lint +ruff check --fix src/ tests/ + +# Type checking +mypy src/ + +# Run unit tests +pytest -m unit + +# Build documentation (if docs/api/conf.py exists) +sphinx-build docs/api docs/_build +``` + +4. **Commit your changes**: +If you have created a new file, it is not being tracked by git. Add it by typing: +```bash +git add path/to/file-to-be-added.py +``` + +Or add all modified files: +```bash +git add . # Adds all changes +# or +git add src/ tests/ # Adds specific directories +``` + +Doing `git status` again should show your files as staged for commit. + +Now you can commit your changes in your local repository. The `-s` flag adds the required `Signed-off-by:` DCO trailer: +```bash +git commit -s -m "Description of your changes" +``` + +**Commit message guidelines:** +- A subject line with < 72 characters +- One blank line +- Optionally, a commit message body +- Use clear, descriptive messages +- Reference related issues: `Fixes #123` or `Related to #456` (you can also use `GH1234` format) +- Keep commits focused and atomic +- Separate style fixes into a different commit to make your pull request more readable + +Example of a good commit message: +``` +Fix biomass calculation for edge cases + +This commit fixes an issue where biomass calculation would fail +for incidence angles below 20 degrees. The fix includes proper +boundary checking and adds corresponding unit tests. + +Fixes #123 +``` + +5. **Push and create a Pull Request**: +```bash +git push origin feature/your-feature-name +``` + +Then open a Pull Request using the template defined in [`https://github.com/BioPAL/BPS/blob/main/.github/PULL_REQUEST_TEMPLATE.md`](https://github.com/BioPAL/BPS/blob/main/.github/PULL_REQUEST_TEMPLATE.md). + +### Updating Your Pull Request + +If you need to make more changes after submitting your PR, you can update your branch and push the changes. The pull request will automatically be updated with the latest code and restart the Continuous Integration tests: + +```bash +git push origin feature/your-feature-name +``` + +This will automatically update your pull request with the latest code and restart the Continuous Integration tests. + +### Delete Your Merged Branch (Optional) + +Once your feature branch is accepted and merged into `develop`, you'll probably want to get rid of the branch. First, update your `develop` branch to check that the merge was successful: + +```bash +git fetch upstream # or git fetch origin if working directly on main repo +git checkout develop +git merge upstream/develop # or git merge origin/develop +``` + +Then you can delete the local branch: + +```bash +git branch -d feature/your-feature-name +``` + +If the branch was squashed into a single commit before merging, you may need to use an upper-case `-D`: + +```bash +git branch -D feature/your-feature-name +``` + +Be careful with this because `git` won't warn you if you accidentally delete an unmerged branch. + +If you didn't delete your branch using GitHub's interface, then it will still exist on GitHub. To delete it there: + +```bash +git push origin --delete feature/your-feature-name +``` + +--- + +## Coding Standards - Code Examples + +This section provides detailed code examples for implementing coding standards. For the conceptual overview, see [Quality and validation](quality-and-validation.md#coding-standards)). + +### Implementing Deprecation Warnings + +**Be especially careful when changing function and method signatures**, because any change may require a deprecation warning. For example, if your pull request means that the argument `old_arg` to `func` is no longer valid, instead of simply raising an error if a user passes `old_arg`, we would instead catch it and emit a deprecation warning: + +```python +import warnings + +def func(new_arg, old_arg=None): + """ + Calculate biomass using new algorithm. + + Parameters + ---------- + new_arg : float + New parameter for calculation + old_arg : float, optional + Deprecated. Use new_arg instead. Will be removed in version 2.0. + """ + if old_arg is not None: + warnings.warn( + "`old_arg` has been deprecated and will be removed in version 2.0. " + "Please use `new_arg` from now on.", + DeprecationWarning, + stacklevel=2 + ) + # Still do what the user intended here for backwards compatibility + # Or map old_arg to new_arg if possible + new_arg = old_arg + + # Continue with new implementation + return calculate_with_new_arg(new_arg) +``` + +**Example of a complete deprecation cycle:** + +```python +# Version 1.0 - Original function +def calculate_biomass_v1(sar_data, config): + """Calculate biomass using original algorithm.""" + # Original implementation + pass + +# Version 1.5 - Add deprecation warning +def calculate_biomass_v1(sar_data, config): + """ + Calculate biomass using original algorithm. + + .. deprecated:: 1.5 + Use :func:`calculate_biomass_v2` instead. This function will be + removed in version 2.0. + """ + warnings.warn( + "calculate_biomass_v1 is deprecated and will be removed in version 2.0. " + "Use calculate_biomass_v2 instead.", + DeprecationWarning, + stacklevel=2 + ) + # Original implementation still works + pass + +# Version 2.0 - Remove deprecated function +# calculate_biomass_v1 is removed, only calculate_biomass_v2 exists +``` + +--- + +## Testing Requirements - Code Examples and Commands + +This section provides detailed code examples and commands for testing. For the conceptual overview, see [Quality and validation](quality-and-validation.md#testing-requirements)). + +### Writing Tests - Code Examples + +All tests should go into the `tests/` subdirectory of the relevant `bps-*` module, under the appropriate subfolder (`unit/`, `baseline/`, `extended/`, `heavy/`). Look at existing tests for inspiration. + +BIOMASS BPS uses [pytest](https://doc.pytest.org/en/latest/) with markers to classify tests. Every test must be decorated with the appropriate marker (defined in `pytest.ini`). + +**Basic unit test example:** +```python +import pytest +import numpy as np + +@pytest.mark.unit +def test_calculate_biomass_basic(): + """Test basic biomass calculation.""" + from bps_l2b_agb_processor.algorithms import calculate_biomass + + sar_data = np.array([0.1, 0.2, 0.3]) + incidence_angle = 30.0 + + result = calculate_biomass(sar_data, incidence_angle) + + assert result.shape == sar_data.shape + assert np.all(result >= 0) +``` + +**Using pytest.parametrize for multiple test cases:** +```python +import pytest +import numpy as np + +@pytest.mark.unit +@pytest.mark.parametrize("incidence_angle", [20.0, 30.0, 40.0, 50.0]) +def test_calculate_biomass_angles(incidence_angle): + """Test biomass calculation with different incidence angles.""" + from bps_l2b_agb_processor.algorithms import calculate_biomass + + sar_data = np.array([0.1, 0.2, 0.3]) + + result = calculate_biomass(sar_data, incidence_angle) + + assert result.shape == sar_data.shape + assert np.all(result >= 0) + assert np.all(np.isfinite(result)) +``` + +**Using fixtures for reusable test data:** +```python +import pytest +import numpy as np + +@pytest.fixture +def sample_sar_data(): + """Fixture providing sample SAR data for tests.""" + return np.array([0.1, 0.2, 0.3, 0.4, 0.5]) + +@pytest.mark.unit +def test_calculate_biomass_with_fixtures(sample_sar_data): + """Test using fixtures for test data.""" + from bps_l2b_agb_processor.algorithms import calculate_biomass + + result = calculate_biomass(sample_sar_data, incidence_angle=30.0) + + assert result.shape == sample_sar_data.shape + assert np.all(result >= 0) +``` + +**Testing with expected exceptions:** +```python +import pytest +import numpy as np + +@pytest.mark.unit +def test_calculate_biomass_invalid_input(): + """Test that invalid input raises appropriate error.""" + from bps_l2b_agb_processor.algorithms import calculate_biomass + + sar_data = np.array([-0.1, 0.2, 0.3]) + + with pytest.raises(ValueError, match="SAR data contains negative values"): + calculate_biomass(sar_data, incidence_angle=30.0) +``` + +### Running Tests - Commands + +Tests are run using pytest markers. Run from the root of the relevant `bps-*` module (after installing it with `pip install -e ".[dev]"`): + +```bash +# Unit tests (no external data required) +pytest -m unit + +# Baseline tests (no external data required) +pytest -m baseline + +# Extended tests: requires TDS dataset +# ⚠ WARNING: TDS not distributed with the repository. A retrieval procedure will be made available shortly. +pytest -m extended + +# Heavy tests: requires TDS dataset +# ⚠ WARNING: TDS not distributed with the repository. A retrieval procedure will be made available shortly. +pytest -m heavy +``` + +**Running specific tests:** + +```bash +# Run tests matching a pattern +pytest -m unit -k "test_calculate_biomass" + +# Run a specific test file +pytest tests/unit/test_algorithms.py + +# Run a specific test function +pytest tests/unit/test_algorithms.py::test_calculate_biomass_basic +``` + +**Running tests with coverage:** + +```bash +# Generate coverage report (minimum threshold: 60%) +pytest -m unit --cov=src --cov-report=html --cov-fail-under=60 + +# View coverage in terminal +pytest -m unit --cov=src --cov-report=term-missing +``` + +**Running tests in parallel:** + +Using [pytest-xdist](https://pypi.python.org/pypi/pytest-xdist), one can speed up local testing on multicore machines by running pytest with the optional `-n` argument: + +```bash +pytest -n 4 # Run tests using 4 parallel workers +``` + +This can significantly reduce the time it takes to locally run tests before submitting a pull request. + +**Other useful pytest options:** + +```bash +# Stop at first failure +pytest -x + +# Show print statements +pytest -s + +# Verbose output +pytest -v + +# Show local variables in tracebacks +pytest -l + +# Run only tests marked with a specific marker +pytest -m "slow" # If you have @pytest.mark.slow markers + +# Run tests and show the slowest 10 tests +pytest --durations=10 +``` + +For more information, see the [pytest documentation](https://doc.pytest.org/en/latest/). + +### Running the Performance Test Suite - Commands + +**Using pytest-benchmark (if available):** + +If the project uses [pytest-benchmark](https://pytest-benchmark.readthedocs.io/), you can run performance benchmarks with: + +```bash +# Run all benchmarks +pytest --benchmark-only + +# Run benchmarks and compare with previous runs +pytest --benchmark-compare + +# Run specific benchmark tests +pytest tests/benchmarks/ -k "biomass_calculation" + +# Run benchmarks and save results +pytest --benchmark-only --benchmark-json=benchmark_results.json +``` + +**Manual performance testing:** + +For manual performance testing, you can use Python's `timeit` module or create simple timing scripts: + +```python +import time +import numpy as np +from biomass_l2_core.algorithms import calculate_biomass + +# Time a function call +start = time.time() +result = calculate_biomass(sar_data, angle, config) +elapsed = time.time() - start +print(f"Calculation took {elapsed:.3f} seconds") +``` + +**Profiling code:** + +To identify performance bottlenecks, you can use profiling tools: + +```bash +# Using cProfile +python -m cProfile -o profile.stats your_script.py + +# Using line_profiler (if installed) +kernprof -l -v your_script.py + +# Using memory_profiler (if installed) +python -m memory_profiler your_script.py +``` + +--- + +## Documentation Standards - Code Examples and Commands + +For complete documentation standards, including docstring format (NumPy style), documentation types, writing conventions for Markdown and reStructuredText, code examples, and commands for building and previewing documentation, please refer to the **[Documentation standards](documentation-standards.md)** documentation. + +--- + +**Previous:** [Documentation standards](documentation-standards.md) | **Next:** [Templates and checklists](templates-and-checklists.md) + diff --git a/docs/contributing/proposal-and-approval.md b/docs/contributing/proposal-and-approval.md new file mode 100644 index 0000000..ef9778b --- /dev/null +++ b/docs/contributing/proposal-and-approval.md @@ -0,0 +1,177 @@ +# Proposal and approval + +**Stage 1 of the contribution workflow.** No code is written before the issue +has been approved. This gate protects contributors from wasted effort and +protects the project from scope creep. + +For open-ended questions or ideas that are not yet actionable, use +[GitHub Discussions](https://github.com/BioPAL/BPS/discussions). Use issues +only for work that needs triage and tracking. + +--- + +## Path A or Path B? + +The contributor arrives with an interest in BIOMASS BPS. The first +decision is whether an issue already exists in the backlog or whether +one needs to be opened. + +```{mermaid} +flowchart TD + Contributor([Contributor with an idea or a problem]) + Backlog[(The backlog
ready-to-pick issues)] + + Contributor -->|existing issue| Backlog + Contributor -->|new idea| PathB1 + + Backlog --> PathA + subgraph PathA[Path A · Pick an existing issue] + direction TB + Cat1[good-first-issue] + Cat2[help-wanted] + Cat3[Bug fix] + Cat4[Scientific feature] + end + PathA --> Gate + + subgraph PathBflow[Path B · Propose a new issue] + direction TB + PathB1[01 · Open the issue
processor, case, expected impact] + PathB2[02 · SME assignment
auto-routed to the right expert] + PathB3[03 · Scientific review
paper, data, prior results] + PathB4{04 · Go or no-go
maintainer + SME
ESA if required} + PathB1 --> PathB2 --> PathB3 --> PathB4 + end + + PathB4 -->|iterate, more evidence| PathB3 + PathB4 -->|rejected| Closed[Issue closed] + PathB4 -->|approved| Gate + + Gate{{"Approval gate
status:approved · good-first-issue · help-wanted"}} + Gate -->|approved, feeds backlog| Backlog + Gate -->|picked up| Code[Stage 2 · Implementation] + + classDef gate fill:#FFE082,stroke:#FFB300,color:#000 + class Gate gate +``` + +### Path A: pick an existing issue + +Browse the [Issues tab](https://github.com/BioPAL/BPS/issues) and look +only at issues that already carry one of the three approval labels: + +- `status:approved`: triaged, scoped, ready to be picked up. +- `good-first-issue`: approved and explicitly suitable for newcomers. +- `help-wanted`: approved and the project actively welcomes external contributions. + +An open issue without one of these labels is still under triage or +discussion. Comment on it to express interest; do not start coding yet. + +### Path B: propose a new issue + +If no approved issue matches what you want to work on, open a new one +using the appropriate +[issue template](https://github.com/BioPAL/BPS/issues/new/choose). +The proposal then walks through four steps: + +::::{grid} 1 2 2 4 +:gutter: 2 + +:::{grid-item-card} 01 · Open the issue +State the processor concerned, the scientific or operational case, +and the expected impact. +::: + +:::{grid-item-card} 02 · SME assignment +The issue is automatically routed to the right Scientific Module Expert +based on CODEOWNERS rules. +::: + +:::{grid-item-card} 03 · Scientific review +The SME evaluates the evidence: peer-reviewed references, data, +prior results, comparison with alternatives. +::: + +:::{grid-item-card} 04 · Go or no-go +The maintainer and SME decide. Significant changes escalate to ESA. +The issue may iterate for more evidence, be rejected, or be approved. +::: + +:::: + +Triage usually completes within five working days. Once scope is +settled, the issue receives `status:approved`, `good-first-issue`, or +`help-wanted` and joins the backlog ready for someone to pick. + +Scientific proposals may require SME review and, for significant +changes, escalation to ESA. See +[Governance](../governance/roles-and-authority.md) for roles and +decision authority. + +--- + +## Choose your issue type + +```{mermaid} +flowchart TD + Q([What are you reporting?]) --> Bug[Bug in processor / CI / docs] + Q --> Feature[Non-scientific feature or tooling] + Q --> Algo[Scientific algorithm or methodology change] + Q --> Docs[Documentation error or gap] + Q --> Sec[Security concern] + Bug --> T1[01 Bug report template] + Feature --> T2[02 Feature or enhancement template] + Algo --> T3[03 Algorithm proposal template] + Docs --> T4[04 Documentation issue template] + Sec --> T5[05 Security report template] +``` + +| Template | When to use | Open | +|----------|-------------|------| +| **Bug report** | A defect in a processor, the CI, or the documentation | [Open](https://github.com/BioPAL/BPS/issues/new?template=01_bug_report.yml) | +| **Feature or enhancement** | A non-scientific feature or tooling improvement | [Open](https://github.com/BioPAL/BPS/issues/new?template=02_feature_request.yml) | +| **Algorithm proposal** | A new scientific algorithm or methodological change (justification required) | [Open](https://github.com/BioPAL/BPS/issues/new?template=03_algorithm_proposal.yml) | +| **Documentation issue** | An error, gap, or improvement in the documentation | [Open](https://github.com/BioPAL/BPS/issues/new?template=04_documentation_issue.yml) | +| **Security report** | A non-sensitive security concern (sensitive issues → [private advisory](https://github.com/BioPAL/BPS/security/advisories/new)) | [Open](https://github.com/BioPAL/BPS/issues/new?template=05_security_report.yml) | + +--- + +## Bug reports and enhancement requests + +Bug reports are an important part of making BIOMASS BPS more stable. +A complete bug report allows others to reproduce the bug and provides +insight into fixing it. + +Trying out the bug-producing code on the `main` branch is often a +worthwhile exercise to confirm that the bug still exists. It is also +worth searching existing bug reports and pull requests to see if the +issue has already been reported and fixed. + +### Submitting a bug report + +Open an issue using the [**Bug report** template](https://github.com/BioPAL/BPS/issues/new?template=01_bug_report.yml). +The template guides you through the structured fields the maintainers need. +For feature requests, algorithm proposals, documentation issues, and security +reports, use the matching template instead. + +If you are reporting a bug, please use the provided template which includes the following: + +1. **Include a short, self-contained code snippet reproducing the problem.** You can format the code nicely by using GitHub Flavored Markdown: + +```python +from bps_common import ... + +# Your code that reproduces the bug +``` + +2. **Include the full version string of BPS and its dependencies.** The global version is tracked in the `VERSION` file at the root of the repository. + +3. **Explain why the current behavior is wrong/not desired and what you expect instead.** + +The issue will then be open to comments/ideas from the team. + +See this [Stack Overflow article for tips on writing a good bug report](https://stackoverflow.com/help/mcve). + +--- + +**Previous:** [Contributing overview](index.md) | **Next:** [Implementation](implementation.md) diff --git a/docs/contributing/quality-and-validation.md b/docs/contributing/quality-and-validation.md new file mode 100644 index 0000000..c567f45 --- /dev/null +++ b/docs/contributing/quality-and-validation.md @@ -0,0 +1,227 @@ +# Quality and validation + +Contributor-facing expectations for tests, scientific validation, coding +standards, and documentation. For the full reference, see +[Code standards](code-standards.md), [Documentation standards](documentation-standards.md), +and [Architecture](architecture.md). + +Command examples live in [Practical workflow](practical-workflow.md). + +--- + +## Testing requirements + +For complete information about test coverage requirements, test types, writing tests, and running tests, please refer to the **[Code standards](code-standards.md)** documentation. + +### Key points for contributors + +- **Minimum coverage**: ≥ 60% on code touched by the PR (`--cov-fail-under=60`) +- **Test types**: Unit, baseline, extended, heavy: see [Code standards](code-standards.md) for the full marker reference +- **Use pytest**: The project uses pytest for all testing + +### Test-driven development (TDD) + +BioPAL strongly encourages contributors to embrace [test-driven development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development). This development process "relies on the repetition of a very short development cycle: first the developer writes an (initially failing) automated test case that defines a desired improvement or new function, then produces the minimum amount of code to pass that test." + +**Why write tests first?** + +- **Clarifies requirements**: Writing tests first forces you to think about what the code should do before implementing it +- **Better design**: Tests help you design cleaner interfaces and APIs +- **Prevents regressions**: Having tests in place ensures that future changes don't break existing functionality +- **Documentation**: Tests serve as executable documentation showing how code should be used +- **Confidence**: Tests give you confidence to refactor and improve code + +**The TDD cycle:** + +1. **Red**: Write a failing test that describes the desired behavior +2. **Green**: Write the minimum code needed to make the test pass +3. **Refactor**: Improve the code while keeping tests passing + +Often the test can be taken from the original GitHub issue. However, it is always worth considering additional use cases and writing corresponding tests. + +Adding tests is one of the most common requests after code is pushed to BioPAL. Therefore, it is worth getting in the habit of writing tests ahead of time so that this is never an issue. + +### Performance testing + +Performance matters and it is worth considering whether your code has introduced performance regressions. BioPAL may include a suite of benchmarking tests to enable easy monitoring of the performance of critical operations. + +**When to run performance tests:** + +- After making changes to core algorithms +- When optimizing code for speed +- Before submitting a pull request that affects performance-critical code +- When investigating performance issues + +**Performance considerations:** + +- **Memory usage**: Monitor memory consumption, especially for large datasets +- **CPU usage**: Check if algorithms can be parallelized +- **I/O operations**: Minimize file read/write operations where possible +- **Vectorization**: Use NumPy vectorized operations instead of Python loops +- **Caching**: Consider caching expensive computations when appropriate + +**Reporting performance changes:** + +If your changes affect performance, include performance metrics in your pull request: + +- Execution time before and after your changes +- Memory usage comparisons +- Any optimizations made +- Benchmark results if available + +For detailed commands on how to write tests, run tests, and use performance testing tools, see [Testing: code examples and commands](practical-workflow.md#testing-requirements---code-examples-and-commands)). + +--- + +## Scientific validation + +### When validation is required + +**Tier 1-2 contributions** must include scientific validation. + +### Reference datasets + +Validation relies on **TDS (Test Data Sets)**: golden datasets used as a reference to compare processor outputs. Tests are organized in three levels within each module: + +- `test/baseline/`: small reference outputs checked on every PR (marker: `baseline`) +- `test/extended/`: broader validation for Tier 1+ (marker: `extended`) +- `test/heavy/`: full scientific regression for Tier 2 (marker: `heavy`) + +⚠ The extended and heavy TDS are not distributed with the repository. A procedure to retrieve them will be made available shortly. In the meantime, these pipelines run exclusively on CI servers. + +### Validation workflow + +All **Tier 1-2 pull requests** are automatically validated using a standardized validation script that runs in the CI/CD pipeline. The validation process works as follows: + +1. **Automatic CI Validation**: When you submit a Tier 1-2 PR, the CI/CD pipeline automatically: + - Executes the validation script on a predetermined Test Data Set (TDS) + - Compares your changes against the **nominal version** (baseline) on the same TDS + - Calculates metrics: RMSE, bias, correlation, etc. + - Generates a validation report with metrics and visualizations + - The report is automatically attached to your PR + +2. **Custom Dataset Validation** (Optional): If you need validation on a specific dataset that is not part of the standard TDS: + - Request access to a specific dataset through the PR description + - The validation will be executed on the requested dataset + - A separate report will be generated for the custom dataset + - This is in addition to (not a replacement for) the standard TDS validation + +**Important**: The main pull request will always be compared with the nominal version on the same predetermined TDS. Custom dataset validations are supplementary and do not replace the standard validation. + +### Validation report structure + +The validation script automatically generates a comprehensive HTML report that includes: + +- **Plots for all variables**: Visualizations showing the results for each variable processed +- **Comparison with nominal version**: Side-by-side comparisons and difference plots between the nominal (baseline) version and your new feature +- **Metrics and statistics**: RMSE, bias, correlation, and other relevant metrics for each variable +- **Summary tables**: Tabulated results for easy review + +The HTML report is automatically generated by the validation script and attached to your PR. No manual notebook creation is required - the entire validation process is automated. + +### Validation report storage + +- **HTML Report**: Automatically generated and attached to the PR +- **Report Location**: Available through the CI/CD pipeline artifacts and linked in the PR +- **Metrics Summary**: Key metrics are also included in the PR description for quick reference + +### Validation execution + +**Standard Validation (Automatic for Tier 1-2):** +- Runs automatically in the CI/CD pipeline for all Tier 1-2 PRs +- Uses the TDS golden dataset consistent across all validations +- Compares your changes against the baseline reference on the same TDS +- Produces a validation report attached to the PR as a CI artefact +- Extended and heavy TDS are not available locally: all Tier 1-2 validations run on CI servers + +**Custom Dataset Validation (On Request):** +- If you need validation on a specific dataset beyond the standard TDS, request it in your PR description +- Custom validations run on CI servers with access to larger datasets +- Requires approval from ESA for dataset access +- Supplementary: does not replace the mandatory TDS validation + +**Data Access:** +- **Tier 0**: Baseline tests only (no TDS required) +- **Tier 1**: Extended TDS validation (automatic via CI) +- **Tier 2**: Heavy TDS validation (automatic via CI) + custom dataset on request + +--- + +## Coding standards + +For complete coding standards, naming conventions, formatting rules, type hints, error handling, and logging guidelines, please refer to the **[Code standards](code-standards.md)** documentation. + +**Key points:** +- Follow PEP 8 with modifications enforced by `black` +- Use type hints for all public functions +- Run `black`, `ruff`, and `mypy` before committing +- Set up pre-commit hooks for automatic code quality checks + +### Backwards compatibility + +Please try to maintain backwards compatibility. BioPAL has a growing number of users with lots of existing code, so don't break it if at all possible. If you think breakage is required, clearly state why as part of the pull request. + +**Principles:** + +- **Avoid breaking changes**: Changes that break existing user code should be avoided unless absolutely necessary +- **Deprecation cycle**: When breaking changes are necessary, use a deprecation cycle to warn users before removing functionality +- **Clear communication**: Document breaking changes clearly in release notes and migration guides +- **Versioning**: Breaking changes should typically be reserved for major version releases + +**Be especially careful when changing function and method signatures**, because any change may require a deprecation warning. Instead of simply raising an error when users pass deprecated arguments, you should catch them and emit a deprecation warning that clearly states what is deprecated, what to use instead, and when it will be removed. + +**Deprecation cycle process:** + +1. **Add deprecation warning**: In the current version, add a `DeprecationWarning` that clearly states: + - What is deprecated + - What to use instead + - When it will be removed (typically next major version) + +2. **Update documentation**: + - Mark deprecated features in the documentation + - Provide migration examples + - Update docstrings with deprecation notices + +3. **Wait for next major version**: Remove the deprecated functionality only in a major version release + +4. **Update release notes**: Clearly document all deprecations and breaking changes + +For code examples showing how to implement deprecation warnings, see [Coding standards: code examples](practical-workflow.md#coding-standards---code-examples)). + +**When breaking changes are acceptable:** + +- **Security vulnerabilities**: Fixing security issues may require breaking changes +- **Scientific correctness**: If the current implementation is scientifically incorrect, breaking changes may be necessary +- **Major architectural improvements**: Significant improvements that benefit the entire project +- **Removing clearly broken or unused features**: Features that are known to be broken or unused + +**Always discuss breaking changes** in an issue or pull request before implementing them, and ensure they are clearly documented. + +--- + +## Documentation standards + +If you're not the developer type, contributing to the documentation is still of huge value. You don't even have to be an expert on BioPAL to do so! In fact, there are sections of the docs that are worse off after being written by experts. If something in the docs doesn't make sense to you, updating the relevant section after you figure it out is a great way to ensure it will help the next person. + +For complete information about documentation standards, docstring format (NumPy style), writing conventions, documentation types, and best practices, please refer to the **[Documentation standards](documentation-standards.md)** documentation. + +**Key points:** +- **Docstrings** should follow the NumPy Docstring Standard +- **Standalone documentation** should provide *why* and *when* to use features, with examples and tutorials +- **Keep documentation up to date**: When you change code, update the corresponding documentation +- **Write for your past self**: Document things that weren't obvious to you when you first learned them + +### Documentation updates required + +When contributing, update: +- User guides (if user-facing changes) +- Developer guides (if workflow changes) +- API documentation (if adding/modifying functions) +- Notebooks (if examples affected) +- Product definitions (if products change) + +For detailed commands on how to build and preview documentation, see [Documentation standards: code examples](practical-workflow.md#documentation-standards---code-examples-and-commands)). + +--- + +**Previous:** [CI automation and contribution tiers](ci-automation-and-contribution-tiers.md) | **Next:** [Architecture](architecture.md) diff --git a/docs/contributing/release-process.md b/docs/contributing/release-process.md new file mode 100644 index 0000000..0305a9e --- /dev/null +++ b/docs/contributing/release-process.md @@ -0,0 +1,153 @@ +# Release Process + +```{warning} +**Maintainers only.** This runbook describes the branch promotion path +(`develop` → `release` → `main`), versioning, tagging, and the ESA approval +gate. Contributors should follow [Implementation](implementation.md) and +[Review and integration](review-and-integration.md); release steps are +executed by maintainers. +``` + +## Overview + +``` +develop → release → main → tag bps--X.Y.Z → release.yml → GitHub Release +``` + +`release` is a long-lived release candidate branch. The promotion path is: + +1. PR from a feature branch into `develop` (daily integration). +2. PR from `develop` into `release` (release candidate assembly, Heavy CI mandatory). +3. PR from `release` into `main` (ESA approval, production promotion). +4. Tag on `main` triggers `release.yml` and produces the GitHub Release. + +Releases are per-module (each `bps-*` package has its own semver tag) but share a global suite version tracked in `VERSION`. + +--- + +## Steps + +### 1. Open a PR from `develop` to `release` + +When the integration branch is ready to become a release candidate, open a PR with `release` as the target branch. + +> **Heavy CI is mandatory on `release` PRs.** The `CI gate` will be red on every `pull_request` event until a maintainer triggers the workflow via `Actions → Run workflow → run_heavy=true`. This is the explicit consent step that records Heavy authorisation before promotion to `main`. + +The PR requires: + +- **2 approvals** (Maintainer + SME, per the `release` branch ruleset) +- **GPG/SSH signed commits** +- **`CI gate`** passing (which itself requires `run_heavy=true` to have been dispatched) + +### 2. Update VERSION and CHANGELOG + +On the same PR, bump the version and update the changelog: + +```bash +nox -s version_update +``` + +Then update `CHANGELOG.md` with the changes for this release. + +> **ESA gate:** both `VERSION` and `CHANGELOG.md` are listed in `CODEOWNERS`. Any PR modifying these files requires explicit approval from the ESA representatives (Klaus Scipal, final authority, and Clement Albinet). See [Repository stewards](../governance/repository-stewards.md). No release can be merged into `main` without this approval. + +Commit with sign-off: + +```bash +git commit -s -m "chore: bump version to x.y.z and update CHANGELOG" +git push +``` + +### 3. Merge to `release` + +Squash merge into `release` once the 2 approvals are in and `CI gate` is green. + +### 4. Open a PR from `release` to `main` + +Once `release` is stable and validated, open a promotion PR to `main`. + +The PR requires: + +- **3 approvals** (Maintainer + SME + ESA, per the `main` branch ruleset) +- **GPG/SSH signed commits** +- **`CI gate`** passing +- **ESA reviewer approval** on `VERSION` and `CHANGELOG.md` + +### 5. Merge to main + +Squash merge onto `main`. The final commit message becomes the release title. + +### 6. Create the tag + +Tag format: `bps--X.Y.Z` + +```bash +git checkout main +git pull origin main +git tag bps-l2b_fh_processor-5.0.0 +git push origin bps-l2b_fh_processor-5.0.0 +``` + +The tag push automatically triggers `release.yml`. + +--- + +## Release Pipeline (automatic) + +Once the tag is pushed, the following jobs run automatically: + +| Job | Description | +|-----|-------------| +| **Build** | `python -m build`: produces `sdist` + `wheel` in `dist/` | +| **SBOM** | Generates a Software Bill of Materials in CycloneDX JSON format (`sbom.json`) | +| **REUSE check** | Final `reuse lint` compliance check | +| **GitHub Release** | Creates a GitHub Release with `dist/*` + `sbom.json` as artefacts and `CHANGELOG.md` as release notes | + +--- + +## PyPI Publication (future) + +Publication to PyPI via OIDC trusted publishing is prepared in `release.yml` but commented out. It will be activated once the PyPI project is configured. + +--- + +## Version Format + +| Scope | Format | Example | +|-------|--------|---------| +| Global BPS suite | `MM.PP` (Major.Patch) | `5.0` | +| Per-module tag | `bps--X.Y.Z` (semver) | `bps-l2b_fh_processor-5.0.0` | + +Version updates across all files are automated via `nox -s version_update`. + +--- + +## SBOM (Software Bill of Materials) + +The CycloneDX SBOM lists all project dependencies and their licenses. It is generated automatically at each release and attached to the GitHub Release artefacts. This supports ESA traceability and open science requirements. + +--- + +## Pre-Release Checklist + +- [ ] PR opened from `develop` to `release` +- [ ] `VERSION` updated via `nox -s version_update` +- [ ] `CHANGELOG.md` updated with all changes for this release +- [ ] Heavy CI dispatched on the `develop → release` PR with `run_heavy=true` +- [ ] `CI gate` green on the `develop → release` PR (baseline + extended + heavy) +- [ ] 2 approvals obtained on the `develop → release` PR (Maintainer + SME) +- [ ] PR opened from `release` to `main` +- [ ] ESA reviewer approval obtained on `VERSION` + `CHANGELOG.md` +- [ ] 3 approvals obtained on the `release → main` PR (Maintainer + SME + ESA) +- [ ] Commits signed with GPG/SSH on `release` and `main` +- [ ] Tag created in the correct format (`bps--X.Y.Z`) after merge to `main` +- [ ] `release.yml` pipeline completed successfully +- [ ] GitHub Release visible with correct artefacts + +--- + +**Last Updated:** 2026 + +--- + +**Previous:** [Becoming a maintainer](becoming-a-maintainer.md) | **Next:** [BioPAL Code of Conduct](code-of-conduct.md) diff --git a/docs/contributing/review-and-integration.md b/docs/contributing/review-and-integration.md new file mode 100644 index 0000000..2d0806c --- /dev/null +++ b/docs/contributing/review-and-integration.md @@ -0,0 +1,116 @@ +# Review and integration + +**Stage 3 of the contribution workflow.** Once your pull request is open, CI runs +automatically and routes reviewers. Iterate until CI is green and all required +approvals are obtained, then the change is squash-merged into `develop`. + +If CI blocks your PR or you need to understand tier classification, see +[CI automation and contribution tiers](ci-automation-and-contribution-tiers.md). + +--- + +## CI green or CI red + +The moment the pull request is opened the CI starts running. From there +the journey forks: a **green path** that ends with a released tag and a +DOI, or a **red path** that loops back to the contributor until the CI +turns green. + +```{mermaid} +flowchart TD + Open([PR opened]) --> CI{CI status} + + %% Path A · green + CI -->|green| GreenPath + subgraph GreenPath[Path A · CI green to release] + direction TB + Review[01 · Review
SME · Maintainer
ESA on Tier 2+] + Approve[02 · Approve
stale approvals dismissed on push] + Merge[03 · Squash merge
develop → release → main] + Released[04 · Released
tagged + GitHub Release + DOI] + Review --> Approve --> Merge --> Released + end + + %% Path B · red + CI -->|red| RedPath + subgraph RedPath[Path B · CI red, iterate, loop back] + direction TB + ReadLog[01 · Read the failing job log] + Iterate[02 · Iterate
address comments · push fixes
sign each commit] + Rerun[03 · Re-run
stale approvals are dismissed] + ReadLog --> Iterate --> Rerun + end + Rerun -.->|loop back| CI + + classDef green fill:#E8F5E9,stroke:#43A047,color:#000 + classDef red fill:#FFEBEE,stroke:#E53935,color:#000 + class GreenPath green + class RedPath red +``` + +When CI fails, read the failing job log, fix the issue, and push again. +Each push re-triggers the pipeline and **dismisses any stale approval**. +The sticky comment from the guidance bot summarises the tier, the job +status, and the reviewers still required. + +## Open and update your pull request + +When you're ready to ask for a code review, file a pull request. Before you do, complete the [pre-submission checklist](templates-and-checklists.md#before-submitting-a-pr)). + +1. **Navigate to your repository on GitHub** at https://github.com/your-username/BPS (or the main repository if working directly) +2. **Click on "Branches"** +3. **Click on the "Compare" button** for your feature branch +4. **Select the "base" and "compare" branches**, if necessary. This will be `develop` and `feature/your-feature-name`, respectively. + +To submit a pull request: + +1. **Navigate to your repository on GitHub** +2. **Click on the "Pull Request" button** +3. **Click on "Commits" and "Files Changed"** to verify the diff one last time +4. **Write a description** following the [PR template](https://github.com/BioPAL/BPS/blob/main/.github/PULL_REQUEST_TEMPLATE.md) +5. **Click "Create Pull Request"** + +### Using draft pull requests + +If you don't think your request is ready to be merged, use GitHub's **Draft PR** feature: + +- Indicate that the code is still work-in-progress +- Allow reviewers to provide early feedback +- Don't block other work or confuse reviewers about readiness +- Can be converted to a regular pull request when ready + +Mention anything you'd like particular attention for, such as a complicated change or some code you are not happy with. + +If you need to make more changes after submitting your PR, push to your branch. The pull request updates automatically and CI re-runs. See [Practical workflow](practical-workflow.md#updating-your-pull-request)) for commands. + +## Accepting an intentional baseline change + +If your PR intentionally changes processor output (e.g. a scientific fix or new algorithm), the baseline check will report a difference and CI may elevate the PR to a higher tier. See [CI automation and contribution tiers](ci-automation-and-contribution-tiers.md#automatic-tier-detection)) for how that works. + +To get the change merged: + +1. Confirm with the relevant SME (auto-assigned via `CODEOWNERS`) that the change is correct. +2. Update `test/baseline/` reference outputs in the same PR so the new behaviour becomes the new baseline. +3. The SME reviews the diff (code + baseline update) and approves via a standard GitHub review. + +There is no `baseline:accepted` label any more; SME approval flows through the native CODEOWNERS-required review on the PR. + +## Responding to reviews + +- Address all reviewer comments promptly (< 3 days) +- Update the PR with requested changes +- CI re-runs automatically on each push +- Iterate until all approvals are obtained + +Release-related files such as `VERSION` and `CHANGELOG.md` require ESA approval via `CODEOWNERS`. See [branch protection rules](ci-automation-and-contribution-tiers.md#branch-protection-and-approvals) for the full policy. + +## After merge + +Your pull request is squash-merged into `develop`, the linked issue closes automatically, and the change follows the normal release path when promoted. + +You may delete your feature branch. See +[Practical workflow](practical-workflow.md#delete-your-merged-branch-optional)) for commands. + +--- + +**Previous:** [Implementation](implementation.md) | **Next:** [CI automation and contribution tiers](ci-automation-and-contribution-tiers.md) diff --git a/docs/contributing/templates-and-checklists.md b/docs/contributing/templates-and-checklists.md new file mode 100644 index 0000000..746a503 --- /dev/null +++ b/docs/contributing/templates-and-checklists.md @@ -0,0 +1,279 @@ +# Templates and checklists + +Structured forms and checklists so contributors are never lost between +the project and GitHub. Four touchpoints guide every contribution: + +1. **Auto tier classification**: every PR is risk-scored on opening from + the diff; no manual tier labels. See [CI automation and contribution tiers](ci-automation-and-contribution-tiers.md). +2. **CODEOWNERS routing**: the right SME is auto-assigned from the files + touched. +3. **Issue and PR templates**: structured forms for every issue type and + pull request (below). +4. **PR guidance comment**: CI posts a sticky summary with tier, job + status, and reviewers needed. + +This page also lists help resources, learning links, and pre-submission +checklists. + +--- + +## Table of Contents + +- Getting Help +- Templates +- Resources +- Checklist for Contributors +- Recognition + +## Getting Help + +For complete information about communication channels and meetings, see +the [Communication](../communication/index.md) page. + +### Quick reference + +| Where | What for | +| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| [GitHub Issues](https://github.com/BioPAL/BPS/issues/new/choose) | Actionable items only: bug reports, feature requests, algorithm proposals, documentation issues, security reports. Use the matching template. | +| [GitHub Discussions](https://github.com/BioPAL/BPS/discussions) | Open ended questions, brainstorming, scientific discussions, governance, community. | +| Office Hours | Planned weekly open session for contributor questions (not scheduled yet). | +| Community Meetings | Planned regular community meetings (not scheduled yet). | + +The recommended place to ask a question is +[GitHub Discussions](https://github.com/BioPAL/BPS/discussions). The Q&A +category is monitored by maintainers and contributors. + +### Before asking for help + +1. Check the [documentation](https://biomass-disc.info/docs). +2. Search [open](https://github.com/BioPAL/BPS/issues) and + [closed](https://github.com/BioPAL/BPS/issues?q=is%3Aissue+is%3Aclosed) + issues. +3. Search [Discussions](https://github.com/BioPAL/BPS/discussions). +4. Review similar PRs. + +### Opening an issue + +Every issue must be filed through one of the five templates. The template +chooser at +[`/issues/new/choose`](https://github.com/BioPAL/BPS/issues/new/choose) +walks you through the options. + +--- + +## Templates + +### Issue templates + +Five issue templates are available at +[`/issues/new/choose`](https://github.com/BioPAL/BPS/issues/new/choose). +Pick the one that matches what you want to report. Each template asks for +exactly the information the maintainers need to triage and route the issue +to the right reviewer. + +| Template | When to use | +| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **01 Bug report** | A defect in a processor, the CI/CD pipeline, or the documentation. | +| **02 Feature or enhancement request** | A non-scientific feature, an enhancement to an existing component, or a tooling improvement. | +| **03 Algorithm proposal** | A new scientific algorithm, a methodological change, or any modification with a scientific impact on the processing chain. Scientific justification is required. | +| **04 Documentation issue** | An error, a gap, an outdated section, a broken link, or a request for clarification in the documentation. | +| **05 Security report** | A non-sensitive security concern, hardening recommendation, or supply-chain issue. Sensitive vulnerabilities go through a [private security advisory](https://github.com/BioPAL/BPS/security/advisories/new) instead. | + +### Discussions categories + +Six categories on +[GitHub Discussions](https://github.com/BioPAL/BPS/discussions) host +the conversations that are not yet actionable issues. + +| Category | When to use | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | +| [Announcements](https://github.com/BioPAL/BPS/discussions/categories/announcements) | Project announcements, releases, governance decisions. Posts restricted to maintainers. | +| [Q&A](https://github.com/BioPAL/BPS/discussions/categories/q-a) | Ask anything about usage, installation, API, processing chain, data formats. Mark the helpful reply as the answer. | +| [Ideas](https://github.com/BioPAL/BPS/discussions/categories/ideas) | Brainstorm a feature or a change before opening a tracking issue. | +| [Scientific discussions](https://github.com/BioPAL/BPS/discussions/categories/scientific-discussions) | Algorithms, validation, methodology, ATBD interpretations, references. | +| [Governance](https://github.com/BioPAL/BPS/discussions/categories/governance) | Project governance, maintainer paths, policy questions, open source strategy. | +| [Show and tell](https://github.com/BioPAL/BPS/discussions/categories/show-and-tell) | Introductions, usage stories, downstream projects, papers, conference talks. | + +### Pull Request template + +Every PR uses the +[standard template](https://github.com/BioPAL/BPS/blob/main/.github/PULL_REQUEST_TEMPLATE.md) +that ships with the repository. It is auto-filled when you open a PR +from the GitHub UI. The template covers: + +- The linked issue and its approval label (`status:approved`, `good-first-issue`, or `help-wanted`). +- A short description of what the PR changes (not the problem, that lives in the issue). +- Notes for reviewers. +- User-facing change flag and release-note sentence. +- Documentation update flag. +- Expected tier (computed automatically by CI, your declaration just helps reviewers spot a mismatch). +- AI assistance disclosure. +- A short checklist of items the CI cannot verify on its own (scope match, single issue, breaking change flagged, reviewer competence). + +### Licensing PR Description Template + +When adding external libraries or dependencies to your PR, include the following information in your PR description: + +```markdown +## Dependencies Added + +- **library-name** (v1.2.3) + - License: MIT + - Purpose: [Brief description] + - Compatibility: Compatible with Apache 2.0 + - License URL: [Link to license] +``` + +For complete licensing requirements, see the [Licensing](../about/licensing/index.md) section. + +--- + +## Resources + +### Documentation + +- [Getting Started](https://biomass-disc.info/docs) - Introduction and getting started guide +- [Code of Conduct](code-of-conduct.md) - Community standards and expectations +- [Contributing overview](index.md) - Journey-based contribution workflow +- [Governance](../governance/index.md) - Roles, responsibilities, and decision-making +- [Processor leads](../governance/processor-leads.md) - Scientific and technical contacts per module +- [Architecture](architecture.md) - Monorepo layout, `bps-*` modules, and processor structure +- [Code standards](code-standards.md) - Coding conventions and best practices +- [Documentation standards](documentation-standards.md) - Documentation writing standards and best practices +- [CI automation and contribution tiers](ci-automation-and-contribution-tiers.md) - Pipeline reference, tier detection, branch protection +- [Release process](release-process.md) - How to prepare and publish a release +- [Communication](../communication/index.md) - Communication channels and meeting schedules +- [Licensing](../about/licensing/index.md) - Apache 2.0 license requirements and legal obligations + +### Learning Resources + +**Git and Version Control:** + +- [GitHub Help Pages](https://help.github.com/) +- [Git Documentation](https://git-scm.com/doc) +- [Atlassian Git Tutorials](https://www.atlassian.com/git/tutorials) +- [GitHub's Git Handbook](https://guides.github.com/introduction/git-handbook/) +- [Pro Git Book](https://git-scm.com/book/en/v2) (free online) + +**Python Development:** + +- [Python Documentation](https://docs.python.org/3/) +- [PEP 8 Style Guide](https://www.python.org/dev/peps/pep-0008/) +- [Real Python](https://realpython.com/) - Python tutorials and guides +- [Python Type Hints](https://docs.python.org/3/library/typing.html) + +**Testing:** + +- [pytest Documentation](https://doc.pytest.org/en/latest/) +- [pytest Best Practices](https://docs.pytest.org/en/latest/explanation/goodpractices.html) +- [Test-Driven Development with Python](https://www.obeythetestinggoat.com/) + +**Scientific Python:** + +- [NumPy Documentation](https://numpy.org/doc/stable/) +- [SciPy Documentation](https://docs.scipy.org/doc/scipy/) +- [xarray Documentation](https://docs.xarray.dev/) - Similar project structure + +**Code Quality:** + +- [Black Code Formatter](https://black.readthedocs.io/) +- [Ruff Linter](https://docs.astral.sh/ruff/) +- [mypy Type Checker](https://mypy.readthedocs.io/) + +### External Resources + +- **FAIR Principles**: [https://www.go-fair.org/fair-principles/](https://www.go-fair.org/fair-principles/) +- **ESA Open Science**: [https://www.esa.int/Science_Exploration/Space_Science/Open_Science](https://www.esa.int/Science_Exploration/Space_Science/Open_Science) +- **Open Source Guides**: [https://opensource.guide/](https://opensource.guide/) + +### Workshops and Training + +- **Onboarding Workshops**: Monthly sessions for new contributors (2h) +- **Technical Training**: Quarterly sessions on tools and processes +- **Hackathons**: Annual community events +- **Intercomparison Exercises**: Regular scientific validation activities + +--- + +## Checklist for Contributors + +### Before Submitting a PR + +**Issue and scope:** + +- [ ] A tracking issue exists for this change. +- [ ] The issue is labelled `status:approved`, `good-first-issue`, or `help-wanted`. +- [ ] The PR closes exactly one issue, and the scope matches what was approved. + +**Code Quality:** + +- [ ] Pre-commit hooks installed and passing (`pre-commit run --all-files`). +- [ ] Code formatted with `ruff format src/ test/`. +- [ ] Linting clean: `ruff check --fix src/ test/`. +- [ ] Type hints present where appropriate; `mypy` passes. +- [ ] All code follows the [Code standards](code-standards.md). +- [ ] No secrets or sensitive data in code. + +**DCO and Licensing:** + +- [ ] All commits carry a `Signed-off-by:` trailer (`git commit -s`). +- [ ] New files include SPDX headers (`SPDX-FileCopyrightText` + `SPDX-License-Identifier: Apache-2.0`). +- [ ] `reuse lint` passes locally (or the pre-commit reuse hook passes). + +**Testing:** + +- [ ] Tests added or modified and passing (`pytest -m baseline` on `test/baseline/`). +- [ ] Baseline reference outputs updated if the behaviour intentionally changed. +- [ ] If targeting `release`, you understand that the `CI gate` will be red until a maintainer triggers the workflow with `run_heavy=true`. + +**Documentation:** + +- [ ] Docstrings added or updated. +- [ ] Documentation site updated if the change is user facing. + +**Pull Request:** + +- [ ] PR template completed fully. +- [ ] Clear description of what changes (not the problem, that lives in the issue). +- [ ] Commit messages are clear and descriptive. +- [ ] Scientific validation completed or summary included for Tier 1 and Tier 2 changes. +- [ ] Branch up to date with `develop`, no merge conflicts. +- [ ] All CI checks passing (`CI gate` green). + +### During Review + +- [ ] Respond to comments promptly (< 3 days) +- [ ] Address all points raised by reviewers +- [ ] Update PR with corrections +- [ ] Communicate clearly about changes +- [ ] Re-run tests after changes +- [ ] Update documentation if requested + +--- + +## Recognition + +Contributors are recognized in: + +- Release notes +- Project documentation +- Community communications +- Annual contributor acknowledgments + +Thank you for contributing to BIOMASS BPS! Your efforts help advance open science and Earth observation capabilities. + +--- + +**Questions?** Ask on +[GitHub Discussions](https://github.com/BioPAL/BPS/discussions). +The [Q&A category](https://github.com/BioPAL/BPS/discussions/categories/q-a) +is the recommended place for usage questions, and +[Scientific discussions](https://github.com/BioPAL/BPS/discussions/categories/scientific-discussions) +for algorithm and methodology topics. Open issues are reserved for +actionable items that match one of the five templates. + +**Last Updated:** 2026 + +--- + +**Previous:** [Practical workflow](practical-workflow.md) | **Next:** [Becoming a maintainer](becoming-a-maintainer.md) diff --git a/docs/developer-guide/architecture.md b/docs/developer-guide/architecture.md new file mode 100644 index 0000000..387be11 --- /dev/null +++ b/docs/developer-guide/architecture.md @@ -0,0 +1,9 @@ +--- +orphan: true +--- + + + +# Architecture + +Redirecting to [Architecture](../contributing/architecture.md). diff --git a/docs/developer-guide/code-standards.md b/docs/developer-guide/code-standards.md new file mode 100644 index 0000000..4bfb628 --- /dev/null +++ b/docs/developer-guide/code-standards.md @@ -0,0 +1,9 @@ +--- +orphan: true +--- + + + +# Code Standards + +Redirecting to [Code standards](../contributing/code-standards.md). diff --git a/docs/developer-guide/documentation-standards.md b/docs/developer-guide/documentation-standards.md new file mode 100644 index 0000000..0930cbe --- /dev/null +++ b/docs/developer-guide/documentation-standards.md @@ -0,0 +1,9 @@ +--- +orphan: true +--- + + + +# Documentation Standards + +Redirecting to [Documentation standards](../contributing/documentation-standards.md). diff --git a/docs/developer-guide/index.md b/docs/developer-guide/index.md new file mode 100644 index 0000000..9e0842b --- /dev/null +++ b/docs/developer-guide/index.md @@ -0,0 +1,12 @@ +--- +orphan: true +--- + + + +# This page has moved + +The developer guide has been merged into [Contributing](../contributing/index.md). + +For official interface specifications (ICD, IODD, auxiliary formats), see +[Applicable documents](../about/applicable-documents.md). diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md new file mode 100644 index 0000000..69b980c --- /dev/null +++ b/docs/getting-started/index.md @@ -0,0 +1,200 @@ + + +# Getting started + +Welcome to **BIOMASS BPS**, the open source Processing Suite for the +ESA BIOMASS Earth Explorer mission. This page is the front door: +it points you to the right resources in less than thirty seconds, +whether you want to use BPS, discuss it, or contribute to it. + +## Find your path + +Choose the path that matches your goal. Each branch links to the +resources described in the sections below. + +```{mermaid} +flowchart TD + start([What brings you here?]) + + start --> u_start + start --> d_start + start --> c_start + + subgraph use_path [Use BPS] + direction TB + u_start[Run processors and products] + u_guides[User Guide · Science Guide · Tutorials] + u_docs[Applicable documents] + u_start --> u_guides --> u_docs + end + + subgraph discuss_path [Ask or discuss] + direction TB + d_start[GitHub Discussions] + d_cats["Q&A · Ideas · Scientific · Governance"] + d_start --> d_cats + end + + subgraph contrib_path [Contribute] + direction TB + c_start[Open or select an issue] + c_gate{Approval label assigned?} + c_wait[Wait for maintainer triage] + c_impl[Implement on a signed branch] + c_pr[Open pull request] + c_review[CODEOWNERS review and CI] + c_merge[Squash merge] + c_start --> c_gate + c_gate -->|no| c_wait --> c_start + c_gate -->|yes| c_impl --> c_pr --> c_review --> c_merge + end +``` + +--- + +## I want to use BPS + +You want to run a processor, understand a data format, or read the +science behind an algorithm. + +::::{grid} 1 2 2 2 +:gutter: 3 +:class-container: intro-grid + +:::{grid-item-card} +:link: ../user-guide/index +:link-type: doc +:class-card: intro-card + + **User Guide** +^^^ +Software User Manual (SUM) and authoritative user reference. +::: + +:::{grid-item-card} +:link: ../science-guide/index +:link-type: doc +:class-card: intro-card + + **Science Guide** +^^^ +ATBDs and Product Format Documents for L1, L2a, and L2b products. +::: + +:::{grid-item-card} +:link: ../tutorials/index +:link-type: doc +:class-card: intro-card + + **Tutorials** +^^^ +Walkthroughs and worked examples. +::: + +:::{grid-item-card} +:link: ../about/applicable-documents +:link-type: doc +:class-card: intro-card + + **Applicable documents** +^^^ +The full list of ATBDs, SUM, ICD, IODD and auxiliary specifications +with PDF download links. +::: + +:::: + +For the published documentation portal, see +[biomass-disc.info](https://www.biomass-disc.info/docs). + +--- + +## I have a question or an idea + +Use [**GitHub Discussions**](https://github.com/BioPAL/BPS/discussions). +Open issues are reserved for actionable items; open Discussions for +everything else. Six categories help you find the right thread. + +| Category | When to use | +|---|---| +| [Q&A](https://github.com/BioPAL/BPS/discussions/categories/q-a) | Usage, installation, API, processing chain, data formats. Mark the helpful reply as the answer. | +| [Ideas](https://github.com/BioPAL/BPS/discussions/categories/ideas) | Brainstorm a feature or a change before opening a tracking issue. | +| [Scientific discussions](https://github.com/BioPAL/BPS/discussions/categories/scientific-discussions) | Algorithms, validation, methodology, ATBD interpretations, references. | +| [Governance](https://github.com/BioPAL/BPS/discussions/categories/governance) | Project governance, maintainer paths, policy. | +| [Show and tell](https://github.com/BioPAL/BPS/discussions/categories/show-and-tell) | Introductions, usage stories, papers, conference talks. | +| [Announcements](https://github.com/BioPAL/BPS/discussions/categories/announcements) | Releases and governance decisions. Read only for external contributors. | + +--- + +## I want to contribute + +Every contribution to BIOMASS BPS follows the same five steps. **No code +is written before the issue has been approved.** This guardrail protects +contributors from wasted effort and protects the project from scope creep. + +### Step 1. Open an issue (or pick an existing one) + +Five [issue templates](https://github.com/BioPAL/BPS/issues/new/choose) +cover every actionable case: + +| Template | When to use | +|---|---| +| Bug report | A defect in a processor, the CI, or the documentation. | +| Feature or enhancement request | A non-scientific feature or tooling improvement. | +| Algorithm proposal | A new scientific algorithm or a methodological change. Justification required. | +| Documentation issue | An error or a gap in the documentation. | +| Security report | A non-sensitive security concern. Sensitive vulnerabilities go through a [private advisory](https://github.com/BioPAL/BPS/security/advisories/new) instead. | + +If an issue that matches what you want to do already exists, pick that +one instead of opening a duplicate. + +### Step 2. Wait for the approval label + +An open issue is **not** an invitation to start coding. Wait until the +issue is labelled with one of these three: + +- `status:approved`: triaged, scoped, ready to be implemented. +- `good-first-issue`: approved and suitable for newcomers. +- `help-wanted`: approved and the project actively welcomes external contributions on it. + +Triage usually completes within five working days. Comment on the issue +to express interest; a maintainer will respond. + +### Step 3. Code on a fork or feature branch + +Once the issue is approved: + +- Fork the repository (or branch directly if you are a maintainer). +- Use a conventional branch prefix: `feat/`, `fix/`, `docs/`, `chore/`, `ci/`, `refactor/`, `test/`. +- Sign off every commit (`git commit -s`) to satisfy the Developer Certificate of Origin. +- Touch only files inside the scope approved on the issue. + +### Step 4. Open a pull request + +Open the PR against `develop`. The PR template guides you through what +to fill in. Make sure `Closes #` appears in the +description so the issue closes automatically on merge. + +### Step 5. Review and merge + +A reviewer with the relevant domain knowledge is assigned through the +CODEOWNERS configuration. Once CI is green and the reviewer approves, +a maintainer merges with squash, and the linked issue closes. + +For the long form, including environment setup, coding standards, the +DCO sign off mechanics, and the tier classification system, see the +[Contributing guide](../contributing/index.md) especially +[Proposal and approval](../contributing/proposal-and-approval.md), +[Implementation](../contributing/implementation.md), and +[Review and integration](../contributing/review-and-integration.md). + +--- + +## Need more help? + +- Read the [Contributing guide](../contributing/index.md) for the long form workflow. +- Check the [Communication page](../communication/index.md) for meeting schedules and community channels. +- Ask in [Q&A](https://github.com/BioPAL/BPS/discussions/categories/q-a) on GitHub Discussions. diff --git a/docs/governance/index.md b/docs/governance/index.md new file mode 100644 index 0000000..0b2a921 --- /dev/null +++ b/docs/governance/index.md @@ -0,0 +1,57 @@ + + +# Governance + +Who decides, who stewards the repository, and who reviews each processor module. + +::::{grid} 1 3 3 3 +:gutter: 3 +:class-container: intro-grid + +:::{grid-item-card} +:link: roles-and-authority +:link-type: doc +:class-card: intro-card + + **Roles and authority** +^^^ +Decision flow, role summary, ESA release gate, and governance principles. +::: + +:::{grid-item-card} +:link: repository-stewards +:link-type: doc +:class-card: intro-card + + **Repository stewards** +^^^ +ESA representatives (Klaus Scipal, final authority; Clement Albinet) and core maintainers with repository access. +::: + +:::{grid-item-card} +:link: processor-leads +:link-type: doc +:class-card: intro-card + + **Processor leads** +^^^ +Scientific and technical contacts per `bps-*` module. +::: + +:::: + +Community channels and meetings are in [Communication](../communication/index.md). +Maintainer release steps are in [Release process](../contributing/release-process.md). + +```{toctree} +:caption: Governance +:maxdepth: 1 +:hidden: + +Roles and authority +Repository stewards +Processor leads +``` diff --git a/docs/governance/processor-leads.md b/docs/governance/processor-leads.md new file mode 100644 index 0000000..3d4377e --- /dev/null +++ b/docs/governance/processor-leads.md @@ -0,0 +1,35 @@ +# Processor leads + +Each `bps-*` module has a **scientific module expert** (when applicable) and a +**technical representative**. GitHub routes reviews through `CODEOWNERS` using +these assignments. If your pull request touches a processor directory, expect +review from the contacts below. + +--- + +## Ownership table + +| Processor | Chain | Scientific rep. | Sci. affiliation | Technical rep. | Tech. affiliation | Notes | +| ------------------------- | -------------- | ----------------- | ---------------- | -------------- | ----------------- | ------------------------------ | +| bps-common | Shared library | — | — | R. Piantanida | Aresys | Shared utilities | +| bps-task-tables | Config | — | — | R. Piantanida | Aresys | Task table definitions | +| bps-transcoder | Infra | — | — | R. Piantanida | Aresys | Data transcoding | +| bps-dockerfiles | Infra | — | — | R. Piantanida | Aresys | Container definitions | +| bps-l1_binaries | L1 | S. Tebaldini | PoliMi | R. Piantanida | Aresys | Pre-built binaries | +| bps-stack_binaries | Stack | S. Tebaldini | PoliMi | R. Piantanida | Aresys | Pre-built binaries | +| bps-l1_pre_processor | L1 | S. Tebaldini | PoliMi | R. Piantanida | Aresys | Pre-processing | +| bps-l1_framing_processor | L1 | S. Tebaldini | PoliMi | R. Piantanida | Aresys | Frame definition | +| bps-l1_core_processor | L1 | S. Tebaldini | PoliMi | R. Piantanida | Aresys | Core focusing and calibration | +| bps-l1_processor | L1 | S. Tebaldini | PoliMi | R. Piantanida | Aresys | End-to-end L1 orchestration | +| bps-stack_pre_processor | Stack | S. Tebaldini | PoliMi | R. Piantanida | Aresys | Stack pre-processing | +| bps-stack_coreg_processor | Stack | S. Tebaldini | PoliMi | R. Piantanida | Aresys | Co-registration | +| bps-stack_cal_processor | Stack | S. Tebaldini | PoliMi | R. Piantanida | Aresys | Stack calibration | +| bps-stack_processor | Stack | S. Tebaldini | PoliMi | R. Piantanida | Aresys | End-to-end stack orchestration | +| bps-l2a_processor | L2a | S. Tebaldini | PoliMi | R. Piantanida | Aresys | Backscatter retrieval | +| bps-l2b_agb_processor | L2b AGB | L. Ulander | Chalmers | R. Piantanida | Aresys | Above-ground biomass | +| bps-l2b_fh_processor | L2b FH | K. Papathanassiou | DLR | R. Piantanida | Aresys | Forest canopy height | +| bps-l2b_fd_processor | L2b FD | L. Ferro-Famil | CESBIO | R. Piantanida | Aresys | Forest disturbance | + +--- + +**Previous:** [Repository stewards](repository-stewards.md) diff --git a/docs/governance/repository-stewards.md b/docs/governance/repository-stewards.md new file mode 100644 index 0000000..9406bb2 --- /dev/null +++ b/docs/governance/repository-stewards.md @@ -0,0 +1,51 @@ +# Repository stewards + +People with governance responsibility on the BPS GitHub repository. +ESA oversight is separate from day-to-day maintainer review. + +Processor-level contacts are in [Processor leads](processor-leads.md). + +--- + +## ESA representatives on the repository + +| Name | Affiliation | Scope | +| --------------- | ----------- | -------------------------------------------- | +| Klaus Scipal | ESA | ESA final authority (disputes, release gate) | +| Clement Albinet | ESA | ESA repository representative | + +Both are required reviewers on `VERSION` and `CHANGELOG.md` via `CODEOWNERS`. Klaus Scipal has final ESA authority on release promotion and governance disputes. + +--- + +## Core maintainers + +Industrial partners with repository write access and merge responsibility. + +| Name | Affiliation | Role | +| ------------------- | ----------- | ----------------------------------- | +| Giovanni Amoroso | Aresys | Project development and maintenance | +| Riccardo Piantanida | Aresys | Technical lead (BPS) | +| Matteo Aletti | Aresys | Core development and maintenance | +| Yoann Rey-Ricord | ACRI-ST | Open Science and community | + +--- + +## Institutional partners + +Organisations that employ maintainers or scientific module experts: + +- [ACRI-ST](https://www.acri-st.fr/en/) +- [Aresys](https://www.aresys.it/) +- [CESBIO](https://www.cesbio.cnrs.fr/) +- [Chalmers University](https://www.chalmers.se/) +- [CNRS](https://crbe.cnrs.fr/) +- [DLR](https://www.dlr.de/) +- [DTU](https://www.dtu.dk/) +- [ESA](https://www.esa.int/) +- [Politecnico di Milano](https://www.polimi.it/en/) +- [Wageningen University & Research](https://www.wur.nl/) + +--- + +**Previous:** [Roles and authority](roles-and-authority.md) | **Next:** [Processor leads](processor-leads.md) diff --git a/docs/governance/roles-and-authority.md b/docs/governance/roles-and-authority.md new file mode 100644 index 0000000..dedabcf --- /dev/null +++ b/docs/governance/roles-and-authority.md @@ -0,0 +1,75 @@ +# Roles and authority + +BIOMASS BPS is ESA mission software developed under contract and released as open +source. Governance balances ESA oversight, maintainer review, and scientific +expertise on processor changes, while keeping the contribution bar low for +documentation and non-scientific fixes. + +For who holds repository access and module routing, see +[Repository stewards](repository-stewards.md) and +[Processor leads](processor-leads.md). + +--- + +## Decision flow + +```{mermaid} +flowchart TD + contrib[Contributor opens PR] --> ci[CI classifies tier] + ci --> maint[Core maintainer review] + ci --> sme[Scientific module expert when required] + maint --> merge{Squash merge?} + sme --> merge + merge -->|Tier 0-1 develop| done[Merged to develop] + merge -->|Release promotion| esa[ESA approval on VERSION and CHANGELOG] + esa --> main[Merged to main] +``` + +Merge rules, branch protection, and tier definitions are in +[CI automation and contribution tiers](../contributing/ci-automation-and-contribution-tiers.md). + +--- + +## Roles and responsibilities + +| Role | Responsibility | Decision scope | +| ----------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- | +| **ESA** | Mission owner, repository oversight, release authority | Final say on disputes; mandatory approval on `VERSION` and `CHANGELOG.md`; Tier 2–3 governance | +| **Open Science Lead** | FAIR strategy, documentation quality, community coordination | Tier 2 Open Science compliance; documentation standards | +| **Core maintainers** | Technical review, merges, release execution | Tier 0–2 technical approval; branch and release operations | +| **Scientific module experts** | Algorithm and baseline validation in their domain | Tier 1–2 scientific approval on owned processors | +| **Contributors** | Code, docs, tests via pull request | Propose changes; respond to review | + +--- + +## ESA release gate + +Any pull request that modifies `VERSION` or `CHANGELOG.md` requires explicit +approval from the ESA representatives defined in +[Repository stewards](repository-stewards.md) and enforced via `CODEOWNERS`. +No promotion to `main` can merge without that approval. + +Step-by-step release commands are in the maintainer runbook: +[Release process](../contributing/release-process.md). + +--- + +## Principles + +- **Transparency**: decisions are documented and traceable. +- **Scientific rigor**: algorithm changes are validated against agreed criteria. +- **Community engagement**: external contributions are welcome when issues are approved. +- **Operational stability**: production branches stay protected and tested. +- **Institutional neutrality**: authority follows contribution and expertise, not affiliation alone. + +--- + +## Changing governance + +The framework is reviewed annually and when ESA or the steward group requests an +update. Proposals go through GitHub Discussions (Governance category) or a +governance issue; ESA has final authority on policy changes. + +--- + +**Previous:** [Governance](index.md) | **Next:** [Repository stewards](repository-stewards.md) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..b2e4ab7 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,121 @@ +--- +html_theme.sidebar_secondary.remove: true +--- + + + +:::{div} index-hero + +
+
+ +
+ +
+
+
+
+

BIOMASS Product Algorithm Laboratory

+

BioPAL documentation

+

Open-source processing software for the ESA BIOMASS Earth Explorer mission.

+ +
+
+
+ +::: + +
+ +**About this site.** [BioPAL](https://github.com/BioPAL) (_BIOMASS Product Algorithm Laboratory_) +hosts this documentation portal. All pages here cover **BPS** (BIOMASS Processing Suite) today. +Documentation for other BioPAL repositories will be added here as it becomes available. + +
+ + + +::::{grid} 1 1 2 2 +:gutter: 3 +:class-container: intro-grid + +:::{grid-item-card} +:link: getting-started/index +:link-type: doc +:class-card: intro-card + + **Get started** +^^^ + +Installation, first steps, and how to find your way around BPS. +::: + +:::{grid-item-card} +:link: user-guide/index +:link-type: doc +:class-card: intro-card + + **User guide** +^^^ + +Software User Manual and authoritative user reference (PDF). +::: + +:::{grid-item-card} +:link: science-guide/index +:link-type: doc +:class-card: intro-card + + **Science guide** +^^^ + +ATBDs and product format documents for every BPS processor. +::: + +:::{grid-item-card} +:link: contributing/index +:link-type: doc +:class-card: intro-card + + **Contribute** +^^^ + +Contribution workflow, standards, governance, and community channels. +::: + +:::: + +```{toctree} +:maxdepth: 2 +:hidden: +:caption: For users + +Get Started +User Guide +Tutorials +Science Guide +Communication +``` + +```{toctree} +:maxdepth: 2 +:hidden: +:caption: For contributors + +Contributing +Governance +About +``` diff --git a/docs/presentations/2026-06-30-first-dev-meeting/.gitignore b/docs/presentations/2026-06-30-first-dev-meeting/.gitignore new file mode 100644 index 0000000..849ddff --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/docs/presentations/2026-06-30-first-dev-meeting/Makefile b/docs/presentations/2026-06-30-first-dev-meeting/Makefile new file mode 100644 index 0000000..3282b56 --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/Makefile @@ -0,0 +1,13 @@ +.PHONY: help build publish serve + +help: + @echo "Targets: build publish serve" + +build: + python3 scripts/build.py + +publish: build + python3 scripts/publish_docs.py + +serve: + bash scripts/serve.sh 8765 diff --git a/docs/presentations/2026-06-30-first-dev-meeting/README.md b/docs/presentations/2026-06-30-first-dev-meeting/README.md new file mode 100644 index 0000000..f55d036 --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/README.md @@ -0,0 +1,47 @@ +# First Developer Meeting — 2026-06-30 + +Slide deck for the **BPS First Developer Meeting** (Open Source & Open Science). Published on the docs site at `/docs/presentations/2026-06-30-first-dev-meeting/`. + +Logos are shared across decks: [`../_shared/logos/`](../_shared/logos/). + +## Quick start + +```bash +cd docs/presentations/2026-06-30-first-dev-meeting +pip install -r requirements.txt # once: PyYAML + +make publish # build dist/ + copy to docs/_extra_static/ +make serve # http://localhost:8765/2026-06-30-first-dev-meeting/index.html +``` + +From the docs tree: + +```bash +cd docs +make html # syncs shared logos, publishes deck, builds site +``` + +Navigation in the deck: **← / →**, **Space**, thumbnail rail, **R** to reset. + +## Source layout + +| Path | Purpose | +|------|---------| +| `source/slides/` | One HTML file per slide | +| `source/deck.yaml` | Slide order, deck title, dimensions | +| `meeting.yaml` | Docs status badge (`planned`, `held`, `cancelled`) | +| `source/css/`, `source/js/` | Shared chrome and interactions | +| `source/assets/` | Session screenshots and diagrams | +| `../_shared/logos/` | Logos used by all meeting decks | +| `dist/` | Generated output (gitignored) | +| `scripts/` | `build`, `publish_docs`, `serve` | + +Edit slides in `source/slides/` and update `source/deck.yaml` if you add, remove, or reorder slides. Then run `make publish` (or `make html` from `docs/`). + +**Session status on the docs site:** edit `meeting.yaml` at the deck root and set `status` to one of `planned`, `held`, or `cancelled`. Rebuild docs with `make html` from `docs/`. + +Published copy lands in `docs/_extra_static/presentations/2026-06-30-first-dev-meeting/`. + +## Docs page + +Community entry point: [`docs/communication/developer-meeting.md`](../../communication/developer-meeting.md) diff --git a/docs/presentations/2026-06-30-first-dev-meeting/meeting.yaml b/docs/presentations/2026-06-30-first-dev-meeting/meeting.yaml new file mode 100644 index 0000000..0b596f4 --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/meeting.yaml @@ -0,0 +1,4 @@ +# Session status shown on docs pages (meetings + developer-meeting). +# Change only `status` — allowed values: +# planned | held | cancelled +status: planned diff --git a/docs/presentations/2026-06-30-first-dev-meeting/requirements.txt b/docs/presentations/2026-06-30-first-dev-meeting/requirements.txt new file mode 100644 index 0000000..1b4d6f3 --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/requirements.txt @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2026 European Space Agency (ESA) +# SPDX-License-Identifier: Apache-2.0 +PyYAML>=6.0 diff --git a/docs/presentations/2026-06-30-first-dev-meeting/scripts/build.py b/docs/presentations/2026-06-30-first-dev-meeting/scripts/build.py new file mode 100644 index 0000000..91c1487 --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/scripts/build.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Assemble source/slides + deck.yaml into dist/index.html.""" + +from __future__ import annotations + +import argparse +import shutil +from pathlib import Path + +import yaml + +ROOT = Path(__file__).resolve().parents[1] +SOURCE = ROOT / "source" +DIST = ROOT / "dist" +DECK_YAML = SOURCE / "deck.yaml" + +FONT_LINKS = """\ + +""" + +CSS_FILES = [ + "css/tokens.css", + "css/chrome.css", + "css/components.css", + "css/interactions.css", +] + + +def copy_tree(src: Path, dst: Path) -> None: + if dst.exists(): + shutil.rmtree(dst) + shutil.copytree(src, dst) + + +def build() -> None: + deck = yaml.safe_load(DECK_YAML.read_text(encoding="utf-8")) + title = deck.get("title", "BPS Presentation") + width = deck.get("width", 1920) + height = deck.get("height", 1080) + + slide_parts: list[str] = [] + for entry in deck["slides"]: + slide_path = SOURCE / entry["file"] + if not slide_path.is_file(): + raise SystemExit(f"Missing slide file: {slide_path}") + slide_parts.append(slide_path.read_text(encoding="utf-8").strip()) + + css_links = "\n".join(f'' for f in CSS_FILES) + + html = f""" + + + + +{title} +{FONT_LINKS} +{css_links} + + + + +{chr(10).join(slide_parts)} + + + + + +""" + + DIST.mkdir(parents=True, exist_ok=True) + (DIST / "index.html").write_text(html, encoding="utf-8") + + copy_tree(SOURCE / "css", DIST / "css") + copy_tree(SOURCE / "js", DIST / "js") + for sub in ("screenshots", "diagrams"): + src = SOURCE / "assets" / sub + if src.is_dir(): + copy_tree(src, DIST / "assets" / sub) + + print(f"Wrote {DIST / 'index.html'} ({len(slide_parts)} slides)") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Build presentation dist/") + parser.parse_args() + if not DECK_YAML.is_file(): + raise SystemExit(f"Missing {DECK_YAML}") + build() + + +if __name__ == "__main__": + main() diff --git a/docs/presentations/2026-06-30-first-dev-meeting/scripts/publish_docs.py b/docs/presentations/2026-06-30-first-dev-meeting/scripts/publish_docs.py new file mode 100644 index 0000000..26a4d78 --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/scripts/publish_docs.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Copy dist/ and shared assets into docs/_extra_static for the Sphinx site.""" + +from __future__ import annotations + +import argparse +import shutil +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +PRESENTATIONS = ROOT.parent +DOCS = PRESENTATIONS.parent +DIST = ROOT / "dist" +DECK_ID = ROOT.name +SHARED_SRC = PRESENTATIONS / "_shared" +STATIC_PRESENTATIONS = DOCS / "_extra_static" / "presentations" +DECK_TARGET = STATIC_PRESENTATIONS / DECK_ID +SHARED_TARGET = STATIC_PRESENTATIONS / "_shared" + + +def publish_shared() -> None: + if not SHARED_SRC.is_dir(): + raise SystemExit(f"Missing shared assets folder: {SHARED_SRC}") + if SHARED_TARGET.exists(): + shutil.rmtree(SHARED_TARGET) + shutil.copytree(SHARED_SRC, SHARED_TARGET) + print(f"Published shared assets → {SHARED_TARGET}") + + +def publish_deck() -> None: + if not (DIST / "index.html").is_file(): + raise SystemExit(f"Missing {DIST / 'index.html'} — run scripts/build.py first") + if DECK_TARGET.exists(): + shutil.rmtree(DECK_TARGET) + shutil.copytree(DIST, DECK_TARGET) + print(f"Published deck → {DECK_TARGET}") + print(f"Live URL (after docs deploy): /docs/presentations/{DECK_ID}/index.html") + + +def publish() -> None: + STATIC_PRESENTATIONS.mkdir(parents=True, exist_ok=True) + publish_shared() + publish_deck() + + +def main() -> None: + parser = argparse.ArgumentParser(description="Publish deck to docs/_extra_static/") + parser.add_argument( + "--shared-only", + action="store_true", + help="Sync docs/presentations/_shared/ only (e.g. after editing a logo)", + ) + args = parser.parse_args() + if args.shared_only: + STATIC_PRESENTATIONS.mkdir(parents=True, exist_ok=True) + publish_shared() + return + publish() + + +if __name__ == "__main__": + main() diff --git a/docs/presentations/2026-06-30-first-dev-meeting/scripts/serve.sh b/docs/presentations/2026-06-30-first-dev-meeting/scripts/serve.sh new file mode 100755 index 0000000..aa23989 --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/scripts/serve.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +DOCS="$(cd "$ROOT/../.." && pwd)" +STATIC="$DOCS/_extra_static/presentations" +PORT="${1:-8765}" + +if [[ ! -f "$STATIC/$(basename "$ROOT")/index.html" ]]; then + echo "Published deck not found — running make publish…" + make -C "$ROOT" publish +fi + +if lsof -iTCP:"$PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then + echo "Port $PORT already in use — deck may already be served." + echo "Use another port: bash scripts/serve.sh 8766" + exit 1 +fi + +DECK_ID="$(basename "$ROOT")" +echo "Serving $STATIC at http://localhost:$PORT/$DECK_ID/index.html" +cd "$STATIC" +exec python3 -m http.server "$PORT" diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/assets/diagrams/cicd-contribution.png b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/diagrams/cicd-contribution.png new file mode 100644 index 0000000..18ebb3b Binary files /dev/null and b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/diagrams/cicd-contribution.png differ diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/assets/diagrams/cicd-pipeline.png b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/diagrams/cicd-pipeline.png new file mode 100644 index 0000000..6f48c58 Binary files /dev/null and b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/diagrams/cicd-pipeline.png differ diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-contributing.png b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-contributing.png new file mode 100644 index 0000000..89cd74e Binary files /dev/null and b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-contributing.png differ diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-governance.png b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-governance.png new file mode 100644 index 0000000..e1b543c Binary files /dev/null and b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-governance.png differ diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-portal-home.png b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-portal-home.png new file mode 100644 index 0000000..965fba7 Binary files /dev/null and b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-portal-home.png differ diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-science-guide.png b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-science-guide.png new file mode 100644 index 0000000..e148562 Binary files /dev/null and b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-science-guide.png differ diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-tutorial.png b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-tutorial.png new file mode 100644 index 0000000..cfa028c Binary files /dev/null and b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-tutorial.png differ diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-user-guide.png b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-user-guide.png new file mode 100644 index 0000000..5bf79cd Binary files /dev/null and b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/docs-user-guide.png differ diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/github-biopal-bps-repo.png b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/github-biopal-bps-repo.png new file mode 100644 index 0000000..a0ed2c4 Binary files /dev/null and b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/github-biopal-bps-repo.png differ diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/github-issue-algorithm.png b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/github-issue-algorithm.png new file mode 100644 index 0000000..89c9041 Binary files /dev/null and b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/github-issue-algorithm.png differ diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/github-issue-template.png b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/github-issue-template.png new file mode 100644 index 0000000..732b592 Binary files /dev/null and b/docs/presentations/2026-06-30-first-dev-meeting/source/assets/screenshots/github-issue-template.png differ diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/css/chrome.css b/docs/presentations/2026-06-30-first-dev-meeting/source/css/chrome.css new file mode 100644 index 0000000..e80998d --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/source/css/chrome.css @@ -0,0 +1,180 @@ +/* Shared slide chrome — ribbon, body area, footer */ + +.slide { + position: relative; + color: #fff; + overflow: hidden; +} + +/* Act ribbon (top bar) */ +.slide-ribbon { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 92px; +} + +.slide-ribbon__accent { + height: 12px; + background: rgba(151, 192, 11, 0.14); + position: relative; + overflow: hidden; +} + +.slide-ribbon__progress { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 0; + background: var(--gr); + transition: width 400ms cubic-bezier(0.25, 0.8, 0.25, 1); + will-change: width; +} + +@media (prefers-reduced-motion: reduce) { + .slide-ribbon__progress { + transition: none; + } +} + +.slide-ribbon__bar { + height: 80px; + background: var(--bg); + display: flex; + align-items: center; + padding: 0 72px; + justify-content: space-between; + border-bottom: 1px solid rgba(255, 255, 255, 0.07); + box-sizing: border-box; +} + +.slide-ribbon__label { + font-weight: 500; + font-size: 40px; + color: rgba(255, 255, 255, 0.7); + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.slide-ribbon__logo { + border-radius: 6px; + padding: 2px 10px; + display: flex; + align-items: center; +} + +.slide-ribbon__logo--light { + background: #fff; +} + +.slide-ribbon__logo img { + height: 48px; + object-fit: contain; +} + +/* Main content area below ribbon */ +.slide-body { + position: absolute; + top: 92px; + left: 72px; + right: 72px; + bottom: 60px; + box-sizing: border-box; +} + +.slide-body--split { + display: flex; + padding-top: 40px; + gap: 56px; +} + +.slide-body--stack { + display: flex; + flex-direction: column; + padding-top: 32px; + gap: 16px; +} + +.slide-body--full { + top: 92px; + left: 0; + right: 0; + display: flex; +} + +/* Standard footer */ +.slide-footer { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 60px; + background: rgba(0, 0, 0, 0.3); + border-top: 1px solid rgba(255, 255, 255, 0.07); + display: flex; + align-items: center; + padding: 0 60px; + box-sizing: border-box; +} + +.slide-footer__tagline { + font-size: 24px; + color: rgba(255, 255, 255, 0.35); + flex: 1; + white-space: nowrap; +} + +.slide-footer__logos { + display: flex; + align-items: center; + gap: 20px; + flex-shrink: 0; +} + +.slide-footer__logos img:nth-child(1) { + height: 26px; + object-fit: contain; + opacity: 0.6; +} + +.slide-footer__logos img:nth-child(2) { + height: 24px; + object-fit: contain; + opacity: 0.85; +} + +.slide-footer__logos img:nth-child(3) { + height: 28px; + object-fit: contain; + opacity: 0.75; +} + +.slide-footer__number { + font-family: var(--fm); + font-size: 24px; + color: rgba(255, 255, 255, 0.3); + flex: 1; + text-align: right; +} + +/* Title slide footer variant */ +.slide-footer--title { + height: 68px; + background: rgba(255, 255, 255, 0.04); + padding: 0 72px; + z-index: 2; +} + +.slide-footer--title .slide-footer__logos img:nth-child(1) { + height: 30px; +} + +.slide-footer--title .slide-footer__logos img:nth-child(2) { + height: 28px; +} + +.slide-footer--title .slide-footer__logos img:nth-child(3) { + height: 32px; +} diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/css/components.css b/docs/presentations/2026-06-30-first-dev-meeting/source/css/components.css new file mode 100644 index 0000000..5db1eb9 --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/source/css/components.css @@ -0,0 +1,178 @@ +/* Shared components — typography, browser mock, cards */ + +.slide-eyebrow { + font-weight: 500; + font-size: var(--tc); + letter-spacing: 0.07em; + text-transform: uppercase; + color: rgba(255, 255, 255, 0.45); + margin-bottom: 8px; +} + +.slide-title { + font-weight: 700; + font-size: var(--tt); + color: #fff; + line-height: 1.1; + margin: 0 0 10px; + letter-spacing: -0.01em; +} + +.slide-title--compact { + margin: 0; + letter-spacing: -0.01em; +} + +.slide-rule { + width: 80px; + height: 4px; + background: var(--tl); + margin-bottom: 28px; + border-radius: 2px; +} + +.slide-rule--short { + width: 56px; + margin-bottom: 8px; +} + +.slide-col { + flex: 0 0 36%; + display: flex; + flex-direction: column; +} + +.slide-col--grow { + flex: 1; + display: flex; + align-items: center; +} + +/* Browser window mock */ +.browser-mock { + background: #152433; + border-radius: 12px; + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 48px rgba(0, 0, 0, 0.4); +} + +.browser-mock--wide { + width: 1107px; + height: 883px; + margin-top: -39px; +} + +.browser-mock__chrome { + height: 40px; + background: #0d1d2b; + display: flex; + align-items: center; + padding: 0 16px; + gap: 8px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.browser-mock__dot { + width: 11px; + height: 11px; + border-radius: 50%; +} + +.browser-mock__dot--close { + background: var(--pk); +} + +.browser-mock__dot--min { + background: #ffd066; +} + +.browser-mock__dot--max { + background: var(--gr); +} + +.browser-mock__url { + flex: 1; + background: rgba(255, 255, 255, 0.07); + border-radius: 4px; + height: 20px; + margin: 0 12px; + display: flex; + align-items: center; + padding: 0 12px; + font-family: var(--fm); + font-size: 13px; + color: rgba(255, 255, 255, 0.4); +} + +.browser-mock__viewport { + height: 640px; + overflow-y: auto; + overflow-x: hidden; + scrollbar-width: thin; + scrollbar-color: rgba(255, 255, 255, 0.2) transparent; +} + +/* Stat / bullet rows */ +.stat-row { + display: flex; + gap: 12px; + align-items: center; +} + +.stat-row__value { + font-family: var(--fm); + font-weight: 700; + font-size: 30px; + color: var(--tl); +} + +.stat-row__label { + font-size: var(--tb); + color: rgba(255, 255, 255, 0.65); +} + +.bullet-row { + display: flex; + gap: 12px; + font-size: var(--tb); + color: rgba(255, 255, 255, 0.75); +} + +.bullet-row i { + color: var(--tl); + flex-shrink: 0; + margin-top: 3px; +} + +.url-pill { + display: inline-flex; + align-items: center; + gap: 10px; + border: 1.5px solid var(--tl); + border-radius: 6px; + padding: 8px 16px; + align-self: flex-start; +} + +.url-pill i { + font-size: 18px; + color: var(--tl); +} + +.url-pill span { + font-family: var(--fm); + font-size: 24px; + color: var(--tl); +} + +.hint-click { + font-size: var(--tc); + color: var(--gr); + font-style: italic; + text-align: right; +} + +.hint-click i { + margin-right: 6px; +} diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/css/interactions.css b/docs/presentations/2026-06-30-first-dev-meeting/source/css/interactions.css new file mode 100644 index 0000000..a0e7591 --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/source/css/interactions.css @@ -0,0 +1,284 @@ +/* Interaction-only animations — no autoplay loops */ + +/* Slide entrance animations (from Claude Design export helmet) */ +@keyframes journey-in { + 0% { opacity: 0; transform: translateY(30px) scale(0.9); } + 100% { opacity: 1; transform: translateY(0) scale(1); } +} + +@keyframes journey-arrow { + 0% { opacity: 0; transform: translateX(-15px); } + 100% { opacity: 1; transform: translateX(0); } +} + +@keyframes journey-specs { + 0% { opacity: 0; transform: translateY(15px); } + 100% { opacity: 1; transform: translateY(0); } +} + +@keyframes journey-loop { + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +/* Chapter dividers — Roman numeral entrance */ +@keyframes chapter-numeral-in { + 0% { + opacity: 0; + transform: scale(0.45); + filter: blur(12px); + } + 70% { + opacity: 1; + transform: scale(1.06); + filter: blur(0); + } + 100% { + opacity: 1; + transform: scale(1); + filter: blur(0); + } +} + +@keyframes chapter-rule-in { + 0% { + opacity: 0; + transform: scaleX(0); + } + 100% { + opacity: 1; + transform: scaleX(1); + } +} + +/* Harmonized stagger entrance — data-deck-stagger + data-stagger-item */ +[data-deck-stagger] [data-stagger-item] { + transform-origin: center center; +} + +.deck-stagger-arrow { + display: inline-flex; + align-items: center; + justify-content: center; +} + +.chapter-numeral, +.chapter-rule, +.chapter-title { + transform-origin: center center; +} + +.deck-browser-mock { + cursor: default; +} + +.deck-browser-viewport { + overflow-y: auto !important; + overflow-x: hidden !important; + scrollbar-width: thin; + scrollbar-color: rgba(0, 122, 148, 0.5) transparent; + align-items: stretch !important; + justify-content: flex-start !important; +} + +.deck-browser-viewport::-webkit-scrollbar { + width: 8px; +} + +.deck-browser-viewport::-webkit-scrollbar-thumb { + background: rgba(0, 122, 148, 0.45); + border-radius: 4px; +} + +.deck-browser-viewport iframe { + display: block; + width: 100%; + min-height: 1800px; + border: 0; + background: #fff; + pointer-events: auto; +} + +.deck-browser-chrome a, +.deck-browser-chrome [data-open-url] { + cursor: pointer; +} + +.deck-browser-chrome [data-open-url]:hover { + color: var(--cy, #00B6F0) !important; +} + +/* Journey hexagons — slide 14 */ +[data-deck-journey] [data-journey-step] { + transition: transform 0.35s ease, filter 0.35s ease, opacity 0.35s ease; + cursor: pointer; +} + +[data-deck-journey] [data-journey-step].deck-dim { + opacity: 0.35; + filter: saturate(0.4); +} + +[data-deck-journey] [data-journey-step].deck-active { + opacity: 1; + filter: none; + transform: scale(1.04); +} + +[data-deck-journey] [data-journey-step].deck-active [data-journey-hex] { + box-shadow: 0 0 28px rgba(0, 182, 240, 0.45); +} + +[data-deck-journey] [data-journey-arrow] { + transition: opacity 0.35s ease, color 0.35s ease; +} + +[data-deck-journey] [data-journey-arrow].deck-lit { + color: var(--cy, #00B6F0) !important; + opacity: 1 !important; +} + +/* Flow slides — 15, 20, 25 */ +[data-deck-flow] [data-flow-step] { + transition: opacity 0.35s ease, border-color 0.35s ease, box-shadow 0.35s ease; + cursor: pointer; +} + +[data-deck-flow] [data-flow-step].deck-dim { + opacity: 0.4; +} + +[data-deck-flow] [data-flow-step].deck-active { + opacity: 1; + box-shadow: 0 0 0 2px rgba(0, 182, 240, 0.35); +} + +[data-deck-flow] [data-flow-path] { + transition: opacity 0.35s ease; + cursor: pointer; +} + +[data-deck-flow] [data-flow-path].deck-dim { + opacity: 0.45; +} + +/* Template chooser — slide 16 */ +[data-deck-templates] [data-template-row] { + transition: border-color 0.3s ease, background 0.3s ease, box-shadow 0.3s ease; + cursor: pointer; +} + +[data-deck-templates] [data-template-row]:hover, +[data-deck-templates] [data-template-row].deck-active { + border-color: rgba(0, 182, 240, 0.55) !important; + background: rgba(0, 182, 240, 0.08) !important; + box-shadow: 0 0 20px rgba(0, 182, 240, 0.15); +} + +/* GitHub Discussions — slide 28 (footer 35) */ +[data-deck-discussions] [data-discussion-row] { + transition: background 0.3s ease, border-color 0.3s ease; + cursor: pointer; + border-radius: 6px; + margin: 0 -8px; + padding-left: 8px !important; + padding-right: 8px !important; +} + +[data-deck-discussions] [data-discussion-row]:hover, +[data-deck-discussions] [data-discussion-row].deck-active { + background: rgba(0, 182, 240, 0.08); + box-shadow: inset 3px 0 0 var(--tl); +} + +/* CI/CD guided tour — slide 22 */ +[data-deck-cicd-tour] [data-cicd-zone] { + transition: border-color 0.35s ease, background 0.35s ease; + cursor: pointer; +} + +[data-deck-cicd-tour] [data-cicd-zone].deck-dim { + opacity: 0.45; +} + +[data-deck-cicd-tour] [data-cicd-zone].deck-active { + opacity: 1; + box-shadow: 0 0 18px rgba(0, 182, 240, 0.2); +} + +[data-deck-cicd-tour] [data-cicd-viewport], +[data-deck-cicd-tour] .deck-cicd-viewport { + overflow: auto !important; + min-height: 0; + flex: 1; + scrollbar-width: thin; + scrollbar-color: rgba(255, 255, 255, 0.2) transparent; + position: relative; +} + +[data-deck-cicd-tour] [data-cicd-viewport]::-webkit-scrollbar, +[data-deck-cicd-tour] .deck-cicd-viewport::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +[data-deck-cicd-tour] [data-cicd-viewport]::-webkit-scrollbar-thumb, +[data-deck-cicd-tour] .deck-cicd-viewport::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 4px; +} + +[data-deck-cicd-tour] [data-cicd-viewport] img[data-cicd-pan] { + display: block; + width: 200%; + height: auto; +} + +[data-deck-cicd-tour] .deck-cicd-hint { + font-style: italic; + color: rgba(0, 182, 240, 0.65); +} + +/* Scroll-sync panels — issue template walkthrough (footer slides 22–24) */ +.deck-scroll-sync { + overflow-y: auto !important; + overflow-x: hidden !important; + scrollbar-width: thin; + scrollbar-color: rgba(0, 122, 148, 0.5) transparent; + flex: 1; + min-height: 0; +} + +.deck-scroll-sync::-webkit-scrollbar { + width: 8px; +} + +.deck-scroll-sync::-webkit-scrollbar-thumb { + background: rgba(0, 122, 148, 0.45); + border-radius: 4px; +} + +.deck-scroll-frame { + display: flex; + flex-direction: column; + min-height: 0; + flex: 1; +} + +[data-sync-items] > * { + transition: background 0.2s ease, border 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; +} + +@media (prefers-reduced-motion: reduce) { + [data-deck-journey] [data-journey-step], + [data-deck-flow] [data-flow-step], + .chapter-numeral, + .chapter-rule, + .chapter-title, + [data-deck-stagger] [data-stagger-item] { + animation: none !important; + opacity: 1 !important; + transform: none !important; + filter: none !important; + } +} diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/css/tokens.css b/docs/presentations/2026-06-30-first-dev-meeting/source/css/tokens.css new file mode 100644 index 0000000..9d635e5 --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/source/css/tokens.css @@ -0,0 +1,23 @@ +/* Design tokens — BioPAL / BPS First Dev Meeting deck */ +:root { + --bg: #0f1923; + --tl: #007a94; + --tl2: #347891; + --cy: #00b6f0; + --gr: #97c00b; + --pk: #ff7e79; + --vm: #5234c9; + --mn: #a3d8b0; + --fs: "Inter", "Helvetica Neue", sans-serif; + --fm: "JetBrains Mono", "IBM Plex Mono", monospace; + --tt: 68px; + --th: 136px; + --tb: 34px; + --tc: 34px; +} + +.slide { + font-family: var(--fs); + background: var(--bg); + color: #fff; +} diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/deck.yaml b/docs/presentations/2026-06-30-first-dev-meeting/source/deck.yaml new file mode 100644 index 0000000..685634a --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/source/deck.yaml @@ -0,0 +1,206 @@ +title: BPS First Developer Meeting — Open Source & Open Science +id: 2026-06-30-first-dev-meeting +date: '2026-06-30' +width: 1920 +height: 1080 +slides: +- id: '01' + file: slides/01-title.html + title: Title + act: I + interactions: [] +- id: CH-I + file: slides/CH-I-act-i.html + title: ACT I + act: chapter + interactions: [] + kind: chapter +- id: '02' + file: slides/02-biomass-bps-transforming-p-band-sar-data-into-global-forest-biomass-maps.html + title: BIOMASS BPS Transforming P-band SAR Data into Global Forest Biomass Maps + act: I + interactions: [] +- id: '03' + file: slides/03-what-is-open-source-open-science.html + title: What is Open Source / Open Science? + act: I + interactions: [] +- id: '05' + file: slides/05-why-open-source-for-an-esa-processor-s-suite.html + title: Why open source for an ESA processor's suite + act: I + interactions: [] +- id: '04' + file: slides/04-five-pillars-one-open-project.html + title: Five pillars, one open project. + act: I + interactions: [] +- id: 04b + file: slides/04b-document-the-project.html + title: Document the project + act: I + interactions: [] +- id: CH-II + file: slides/CH-II-act-ii.html + title: ACT II + act: chapter + interactions: [] + kind: chapter +- id: '06' + file: slides/06-one-site-every-resource-one-click-away.html + title: One site. Every resource. One click away. + act: II + interactions: + - browser +- id: '07' + file: slides/07-find-your-path.html + title: Find your path + act: II + interactions: [] +- id: 08 + file: slides/08-software-user-manual-v4-4-1.html + title: Software User Manual v4.4.1 + act: II + interactions: + - browser +- id: 09 + file: slides/09-run-bps-on-your-own-computer.html + title: Run BPS on your own computer. + act: II + interactions: + - browser +- id: '10' + file: slides/10-atbds-afd-pfds-catalog.html + title: ATBDs AFD & PFDs catalog + act: II + interactions: + - browser +- id: '11' + file: slides/11-five-steps-no-code-before-approval.html + title: Five steps, no code before approval. + act: II + interactions: + - browser +- id: '12' + file: slides/12-roles-stewards-processor-leads.html + title: Roles, stewards, processor leads. + act: II + interactions: + - browser +- id: '13' + file: slides/13-one-repo-one-site-one-source-of-truth.html + title: One repo. One site. One source of truth. + act: II + interactions: [] +- id: CH-III + file: slides/CH-III-act-iii.html + title: ACT III + act: chapter + interactions: [] + kind: chapter +- id: GH-1 + file: slides/GH-1-what-is-github.html + title: What is GitHub? + act: III + interactions: [] +- id: '14' + file: slides/14-how-biomass-runs-on-github.html + title: How BIOMASS runs on GitHub + act: III + interactions: [] +- id: '15' + file: slides/15-before-the-pr.html + title: Before the PR + act: III + interactions: + - flow +- id: '16' + file: slides/16-five-issue-templates-pick-yours.html + title: Five issue templates · pick yours + act: III + interactions: + - templates +- id: '17' + file: slides/17-inside-a-bug-report.html + title: Inside a Bug report + act: III + interactions: [] +- id: '18' + file: slides/18-inside-a-feature-request.html + title: Inside a Feature request + act: III + interactions: [] +- id: '19' + file: slides/19-inside-an-algorithm-proposal.html + title: Inside an Algorithm proposal + act: III + interactions: [] +- id: GH-2 + file: slides/GH-2-github-contribution-process.html + title: GitHub Contribution Process + act: III + interactions: [] +- id: '20' + file: slides/20-building-the-pr.html + title: Building the PR + act: III + interactions: + - flow +- id: '21' + file: slides/21-the-ci-cd-pipeline.html + title: The CI/CD pipeline + act: III + interactions: [] +- id: '22' + file: slides/22-three-zones-one-flow.html + title: Three zones, one flow. + act: III + interactions: + - cicd-tour +- id: '23' + file: slides/23-tier-policy.html + title: Tier policy + act: III + interactions: [] +- id: '24' + file: slides/24-branch-protection-approvals.html + title: Branch protection & approvals + act: III + interactions: [] +- id: '25' + file: slides/25-after-the-pr.html + title: After the PR + act: III + interactions: + - flow +- id: '26' + file: slides/26-no-contributor-is-ever-lost.html + title: No contributor is ever lost. + act: III + interactions: [] +- id: CH-IV + file: slides/CH-IV-act-iv.html + title: ACT IV + act: chapter + interactions: [] + kind: chapter +- id: '27' + file: slides/27-a-project-lives-by-its-community.html + title: A project lives by its community. + act: IV + interactions: [] +- id: '28' + file: slides/28-channels-and-meetings.html + title: Channels and meetings + act: IV + interactions: [] +- id: DEMO + file: slides/DEMO-demo-time.html + title: Demo Time + act: IV + interactions: [] +- id: '30' + file: slides/30-q-a-status-update.html + title: Q&A · Status update + act: IV + interactions: [] diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/js/deck-interactions.js b/docs/presentations/2026-06-30-first-dev-meeting/source/js/deck-interactions.js new file mode 100644 index 0000000..3c65825 --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/source/js/deck-interactions.js @@ -0,0 +1,455 @@ +/** + * BPS First Dev Meeting — interaction layer (click / scroll only, no autoplay). + */ +(() => { + 'use strict'; + + const GH = 'https://github.com/BioPAL/BPS'; + const TEMPLATE_URLS = { + '01': `${GH}/issues/new?template=01_bug_report.yml`, + '02': `${GH}/issues/new?template=02_feature_request.yml`, + '03': `${GH}/issues/new?template=03_algorithm_proposal.yml`, + '04': `${GH}/issues/new?template=04_documentation_issue.yml`, + '05': `${GH}/issues/new?template=05_security_report.yml`, + }; + + const DISCUSSION_URLS = { + announcements: `${GH}/discussions/new?category=announcements`, + 'q-a': `${GH}/discussions/new?category=q-a`, + ideas: `${GH}/discussions/new?category=ideas`, + scientific: `${GH}/discussions/new?category=scientific-discussions`, + governance: `${GH}/discussions/new?category=governance`, + 'show-and-tell': `${GH}/discussions/new?category=show-and-tell`, + general: `${GH}/discussions/new?category=general`, + }; + + const CICD_ZONE_RATIOS = [0, 0.38, 0.72]; + + function openUrl(url) { + if (url) window.open(url, '_blank', 'noopener,noreferrer'); + } + + function stopNav(e) { + e.stopPropagation(); + } + + function clearChildren(el) { + while (el.firstChild) el.removeChild(el.firstChild); + } + + /** Browser mocks: scrollable live-site iframe + clickable chrome. */ + function initBrowserMocks(root) { + root.querySelectorAll('[data-browser-url]').forEach((section) => { + const url = section.getAttribute('data-browser-url'); + if (!url) return; + + section.querySelectorAll('[data-browser-frame]').forEach((frame) => { + const chrome = frame.querySelector('[data-browser-chrome]'); + const viewport = frame.querySelector('[data-browser-viewport]'); + if (!viewport || viewport.dataset.deckReady) return; + + viewport.dataset.deckReady = '1'; + viewport.classList.add('deck-browser-viewport'); + if (!viewport.style.height) viewport.style.height = '640px'; + + clearChildren(viewport); + const iframe = document.createElement('iframe'); + iframe.src = url; + iframe.title = url; + iframe.loading = 'lazy'; + iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups'); + viewport.appendChild(iframe); + + if (chrome) { + chrome.classList.add('deck-browser-chrome'); + chrome.querySelectorAll('a[href]').forEach((a) => { + a.addEventListener('click', stopNav); + }); + chrome.querySelectorAll('[data-open-url]').forEach((el) => { + el.addEventListener('click', (e) => { + stopNav(e); + openUrl(el.getAttribute('data-open-url') || url); + }); + }); + } + + viewport.addEventListener('wheel', stopNav, { passive: true }); + viewport.addEventListener('click', stopNav); + }); + }); + } + + /** Journey hexagons — click advances active step. */ + function initJourney(section) { + if (section.dataset.journeyReady) return; + const steps = [...section.querySelectorAll('[data-journey-step]')]; + if (!steps.length) return; + section.dataset.journeyReady = '1'; + + let active = 0; + const arrows = [...section.querySelectorAll('[data-journey-arrow]')]; + + function paint() { + steps.forEach((step, i) => { + step.classList.toggle('deck-active', i === active); + step.classList.toggle('deck-dim', i !== active); + }); + arrows.forEach((arrow, i) => { + arrow.classList.toggle('deck-lit', i < active); + }); + } + + steps.forEach((step, i) => { + step.addEventListener('click', (e) => { + stopNav(e); + active = i; + paint(); + }); + }); + + paint(); + } + + /** Flow slides — click path, then click steps in sequence. */ + function initFlow(section) { + if (section.dataset.flowReady) return; + const paths = [...section.querySelectorAll('[data-flow-path]')]; + const steps = [...section.querySelectorAll('[data-flow-step]')]; + if (!paths.length && !steps.length) return; + section.dataset.flowReady = '1'; + + let pathIdx = 0; + let stepIdx = 0; + + function paint() { + paths.forEach((p, i) => { + p.classList.toggle('deck-active', i === pathIdx); + p.classList.toggle('deck-dim', paths.length > 1 && i !== pathIdx); + }); + steps.forEach((s, i) => { + const lit = i <= stepIdx; + s.classList.toggle('deck-active', lit); + s.classList.toggle('deck-dim', !lit); + }); + } + + paths.forEach((p, i) => { + p.addEventListener('click', (e) => { + stopNav(e); + pathIdx = i; + stepIdx = 0; + paint(); + }); + }); + + steps.forEach((s, i) => { + s.addEventListener('click', (e) => { + stopNav(e); + stepIdx = i; + paint(); + }); + }); + + paint(); + } + + /** Template rows — click opens GitHub template URL. */ + function initTemplates(section) { + if (section.dataset.templatesReady) return; + section.dataset.templatesReady = '1'; + + section.querySelectorAll('[data-template-row]').forEach((row) => { + const id = row.getAttribute('data-template-id'); + const url = TEMPLATE_URLS[id]; + if (!url) return; + + row.addEventListener('click', (e) => { + stopNav(e); + section.querySelectorAll('[data-template-row]').forEach((r) => r.classList.remove('deck-active')); + row.classList.add('deck-active'); + openUrl(url); + }); + }); + } + + /** Discussion categories — click opens GitHub Discussions new-post URL. */ + function initDiscussions(section) { + if (section.dataset.discussionsReady) return; + section.dataset.discussionsReady = '1'; + + section.querySelectorAll('[data-discussion-row]').forEach((row) => { + const id = row.getAttribute('data-discussion-id'); + const url = DISCUSSION_URLS[id]; + if (!url) return; + + row.addEventListener('click', (e) => { + stopNav(e); + section.querySelectorAll('[data-discussion-row]').forEach((r) => r.classList.remove('deck-active')); + row.classList.add('deck-active'); + openUrl(url); + }); + }); + } + + /** CI/CD tour — scroll the diagram; zone clicks jump to each section. */ + function initCicdTour(section) { + const zones = [...section.querySelectorAll('[data-cicd-zone]')]; + const viewport = section.querySelector('[data-cicd-viewport]'); + const panImg = viewport && viewport.querySelector('[data-cicd-pan]'); + const hint = section.querySelector('[data-cicd-hint]'); + if (!zones.length || !viewport || !panImg) return; + + function maxScroll() { + return Math.max(0, viewport.scrollHeight - viewport.clientHeight); + } + + function paintZones(active) { + zones.forEach((z, i) => { + z.classList.toggle('deck-active', i === active); + z.classList.toggle('deck-dim', i !== active); + }); + if (hint) { + hint.textContent = active < zones.length - 1 + ? 'Scroll or click zones to explore →' + : 'End of tour — scroll or click zones to revisit'; + } + } + + function activeFromScroll() { + const max = maxScroll(); + if (max <= 0) return 0; + const ratio = viewport.scrollTop / max; + let active = 0; + for (let i = CICD_ZONE_RATIOS.length - 1; i >= 0; i--) { + if (ratio >= CICD_ZONE_RATIOS[i] - 0.06) { + active = i; + break; + } + } + return active; + } + + function onScroll() { + paintZones(activeFromScroll()); + } + + function scrollToZone(i) { + const max = maxScroll(); + const ratio = CICD_ZONE_RATIOS[i] ?? 0; + viewport.scrollTo({ top: max * ratio, behavior: 'smooth' }); + paintZones(i); + } + + if (!section.dataset.cicdReady) { + section.dataset.cicdReady = '1'; + viewport.classList.add('deck-cicd-viewport'); + viewport.addEventListener('wheel', stopNav, { passive: true }); + viewport.addEventListener('scroll', onScroll, { passive: true }); + zones.forEach((z, i) => { + z.addEventListener('click', (e) => { + stopNav(e); + scrollToZone(i); + }); + }); + if (hint) hint.classList.add('deck-cicd-hint'); + } + + onScroll(); + } + + const STAGGER_ANIMS = { + in: 'journey-in .6s ease forwards', + arrow: 'journey-arrow .5s ease forwards', + specs: 'journey-specs .5s ease forwards', + numeral: 'chapter-numeral-in .75s cubic-bezier(.2,.8,.2,1) forwards', + }; + + function applyStaggerItem(el, delaySec) { + const variant = el.dataset.staggerVariant || 'in'; + const anim = STAGGER_ANIMS[variant] || STAGGER_ANIMS.in; + el.style.opacity = '0'; + el.style.animation = anim; + el.style.animationDelay = `${delaySec}s`; + } + + /** Staggered entrance — data-stagger-item with optional data-stagger-order / data-stagger-delay. */ + function initStagger(section) { + if (!section.hasAttribute('data-deck-stagger')) return; + if (section.dataset.staggerReady) return; + section.dataset.staggerReady = '1'; + + const base = parseFloat(section.dataset.staggerBase || '0.15'); + const step = parseFloat(section.dataset.staggerStep || '0.11'); + const indexed = [...section.querySelectorAll('[data-stagger-item]')].map((el, i) => ({ el, i })); + indexed.sort((a, b) => { + const oa = a.el.dataset.staggerOrder != null ? parseFloat(a.el.dataset.staggerOrder) : null; + const ob = b.el.dataset.staggerOrder != null ? parseFloat(b.el.dataset.staggerOrder) : null; + if (oa != null && ob != null && oa !== ob) return oa - ob; + if (oa != null && ob == null) return -1; + if (oa == null && ob != null) return 1; + return a.i - b.i; + }); + + let seq = 0; + indexed.forEach(({ el }) => { + const delay = el.dataset.staggerDelay != null + ? parseFloat(el.dataset.staggerDelay) + : base + seq * step; + if (el.dataset.staggerDelay == null) seq += 1; + applyStaggerItem(el, delay); + }); + } + + function replayCssAnimations(section) { + section.querySelectorAll('[style*="animation:"]').forEach((el) => { + const anim = el.style.animation; + if (!anim || anim === 'none') return; + el.style.animation = 'none'; + void el.offsetHeight; + el.style.animation = anim; + }); + } + + /** Scroll-sync — scrolling the screenshot highlights matching form fields. */ + function initScrollSync(section) { + section.querySelectorAll('[data-scroll-sync]').forEach((scroller) => { + const name = scroller.getAttribute('data-scroll-sync'); + const container = section.querySelector(`[data-sync-items="${name}"]`); + if (!container) return; + scroller.classList.add('deck-scroll-sync'); + + const items = [...container.children]; + const color = container.getAttribute('data-sync-color') || '#FF7E79'; + + function paint() { + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + const ratio = maxScroll > 0 ? scroller.scrollTop / maxScroll : 0; + const activeIdx = Math.min(Math.floor(ratio * items.length), items.length - 1); + items.forEach((el, i) => { + if (i === activeIdx) { + el.style.background = color; + el.style.border = 'none'; + el.style.transform = 'scale(1.03)'; + el.style.boxShadow = '0 2px 12px rgba(0,0,0,.3)'; + } else { + el.style.background = 'rgba(255,255,255,.08)'; + el.style.border = '1px solid rgba(255,255,255,.15)'; + el.style.transform = 'scale(1)'; + el.style.boxShadow = 'none'; + } + }); + } + + if (!scroller.dataset.scrollSyncReady) { + scroller.dataset.scrollSyncReady = '1'; + scroller.addEventListener('scroll', paint, { passive: true }); + scroller.addEventListener('wheel', stopNav, { passive: true }); + scroller.addEventListener('click', stopNav); + } + paint(); + }); + } + + function initSection(section) { + if (section.hasAttribute('data-deck-chapter')) replayCssAnimations(section); + if (section.hasAttribute('data-browser-url')) initBrowserMocks(section); + if (section.hasAttribute('data-deck-journey')) initJourney(section); + if (section.hasAttribute('data-deck-flow')) initFlow(section); + if (section.hasAttribute('data-deck-templates')) initTemplates(section); + if (section.hasAttribute('data-deck-discussions')) initDiscussions(section); + if (section.hasAttribute('data-deck-cicd-tour')) initCicdTour(section); + if (section.querySelector('[data-scroll-sync]')) initScrollSync(section); + initStagger(section); + replayCssAnimations(section); + } + + /** Ribbon progress — content slides only (excludes title + chapter dividers). */ + function isContentSlide(section) { + return section.matches('section.slide') + && section.querySelector('.slide-ribbon') + && !section.classList.contains('slide--chapter'); + } + + function ensureRibbonProgress(section) { + const accent = section.querySelector('.slide-ribbon__accent'); + if (!accent) return null; + let fill = accent.querySelector('.slide-ribbon__progress'); + if (!fill) { + fill = document.createElement('div'); + fill.className = 'slide-ribbon__progress'; + fill.setAttribute('aria-hidden', 'true'); + accent.appendChild(fill); + } + return fill; + } + + function initDeckProgress(deck) { + const contentSlides = [...deck.querySelectorAll('section.slide')].filter(isContentSlide); + const total = contentSlides.length; + contentSlides.forEach((slide, i) => { + const fill = ensureRibbonProgress(slide); + if (!fill || total === 0) return; + const pct = ((i + 1) / total) * 100; + slide.dataset.deckProgressPct = String(pct); + fill.style.width = '0%'; + }); + } + + function updateDeckProgress(detail) { + const slide = detail && detail.slide; + if (!slide || !isContentSlide(slide)) return; + const fill = ensureRibbonProgress(slide); + if (!fill) return; + + const target = parseFloat(slide.dataset.deckProgressPct || '0'); + let from = 0; + const prev = detail.previousSlide; + if (prev && isContentSlide(prev)) { + from = parseFloat(prev.dataset.deckProgressPct || '0'); + } + + const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + if (reduced) { + fill.style.width = `${target}%`; + return; + } + + fill.style.transition = 'none'; + fill.style.width = `${from}%`; + void fill.offsetWidth; + fill.style.transition = ''; + fill.style.width = `${target}%`; + } + + function initAll(deck) { + deck.querySelectorAll('section').forEach(initSection); + } + + function bindDeck(deck) { + initDeckProgress(deck); + initAll(deck); + deck.addEventListener('slidechange', (e) => { + if (e.detail) updateDeckProgress(e.detail); + const slide = e.detail && e.detail.slide; + if (slide) initSection(slide); + }); + } + + function boot() { + const deck = document.querySelector('deck-stage'); + if (deck) { + bindDeck(deck); + return; + } + customElements.whenDefined('deck-stage').then(() => { + const el = document.querySelector('deck-stage'); + if (el) bindDeck(el); + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', boot); + } else { + boot(); + } +})(); diff --git a/docs/presentations/2026-06-30-first-dev-meeting/source/js/deck-stage.js b/docs/presentations/2026-06-30-first-dev-meeting/source/js/deck-stage.js new file mode 100644 index 0000000..0dd330c --- /dev/null +++ b/docs/presentations/2026-06-30-first-dev-meeting/source/js/deck-stage.js @@ -0,0 +1,1996 @@ +// @ds-adherence-ignore -- omelette starter scaffold (raw elements/hex/px by design) +/* ═══ THIS PROJECT USES DESIGN COMPONENTS (.dc.html) ═══ + * Reference this stage from your template as an import — NEVER as a + * raw tag plus a + * + * The :not(:defined) rule prevents a flash of the first slide at its + * authored styles before this script runs and attaches the shadow root. + * + * Slides are the direct element children of . Each slide is + * automatically tagged with: + * - data-screen-label="NN Label" (1-indexed, for comment flow) + * - data-om-validate="no_overflowing_text,no_overlapping_text,slide_sized_text" + * + * Speaker notes stay in sync because the component posts {slideIndexChanged: N} + * to the parent — just include the #speaker-notes script tag if asked for notes. + * + * Authoring guidance: + * - Write slide bodies as static HTML inside , with sizing via + * CSS custom properties in a