diff --git a/.github/ISSUE_TEMPLATE/adr-template.yaml b/.github/ISSUE_TEMPLATE/adr-template.yaml new file mode 100644 index 00000000..9ea42075 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/adr-template.yaml @@ -0,0 +1,46 @@ +name: Architecture decision record (ADR) +description: Open a new draft ADR +labels: ["ADR"] +title: "We will " +body: + - type: textarea + id: context + validations: + required: true + attributes: + label: Context + description: > # Turn new lines into spaces + Describe the motivating factors behind this decision: social, political, + technical, etc. Why does this decision need to be made? This section should + be a statement of facts. + - type: textarea + id: decision + validations: + required: true + attributes: + label: Decision + description: > + What was decided? This should be an active-voice statement. For example, + "We will use the US Web Design System." + - type: textarea + id: consequences + validations: + required: true + attributes: + label: Consequences + description: > + What are the consequences of this decision? Consequences can be + positive, negative, or neutral, but all known consequences of this + decision should be listed here. + value: | # Preserve new lines + #### Positive + + - + + #### Neutral + + - + + #### Negative + + - diff --git a/.github/ISSUE_TEMPLATE/sdr-template.yaml b/.github/ISSUE_TEMPLATE/sdr-template.yaml new file mode 100644 index 00000000..8fc4a4b4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/sdr-template.yaml @@ -0,0 +1,46 @@ +name: Scientific decision record (SDR) +description: Open a new draft SDR +labels: ["SDR"] +title: "We will " +body: + - type: textarea + id: context + validations: + required: true + attributes: + label: Context + description: > # Turn new lines into spaces + Describe the motivating factors behind this decision: social, political, + scientific, etc. Why does this decision need to be made? This section should + be a statement of facts. + - type: textarea + id: decision + validations: + required: true + attributes: + label: Decision + description: > + What was decided? This should be an active-voice statement. For example, + "We will recalculate heights for RAOB observations." + - type: textarea + id: consequences + validations: + required: true + attributes: + label: Consequences + description: > + What are the consequences of this decision? Consequences can be + positive, negative, or neutral, but all known consequences of this + decision should be listed here. + value: | # Preserve new lines + #### Positive + + - + + #### Neutral + + - + + #### Negative + + - diff --git a/.github/workflows/adr-accepted.yaml b/.github/workflows/adr-accepted.yaml new file mode 100644 index 00000000..f4f8e51a --- /dev/null +++ b/.github/workflows/adr-accepted.yaml @@ -0,0 +1,110 @@ +name: ADR Accepted +on: + issues: + types: + - closed + +jobs: + main: + name: Create ADR + runs-on: ubuntu-latest + + # Only run this workflow if the closed issue has the "ADR: accepted" label + if: "${{ contains(github.event.issue.labels.*.name, 'ADR: accepted') }}" + + steps: + - name: checkout main branch + uses: actions/checkout@v4 + with: + ref: main + + - name: get ADR number + id: next + run: | + LAST_ADR=$(ls docs/decisions/architecture/*.md | grep -Eo "architecture/adr-[0-9]+-" | sort | tail -n1 | grep -Eo "[0-9]+") + LAST_ADR=$(echo "$LAST_ADR" | sed -E 's/^0+//') + NEXT_ADR=$(($LAST_ADR + 1)) + NEXT_ADR=$(printf "%04i" "$NEXT_ADR") + echo "number=$NEXT_ADR" >> "$GITHUB_OUTPUT" + + - name: write the ADR + id: create-adr + uses: actions/github-script@v7 + with: + script: | + const fs = require("fs/promises"); + + // Use the GitHub toJSON expression to get these as escaped strings. + // In Javascript, this will preserve line breaks, and using template + // strings further down will preserve all the kinds of quotes. + const title = ${{ toJSON(github.event.issue.title) }}; + const body = ${{ toJSON(github.event.issue.body )}}; + + const slug = title + .toLowerCase() + .trim() + .replace(/[^\w\s-]/g, "") // get rid of non-ascii characters + .replace(/[\s_-]+/g, "-") // convert whitespace and underscore to dash + .replace(/^-+|-+$/g, ""); // git rid of leading/trailing dashes + + const filename = `docs/decisions/architecture/adr-${{ steps.next.outputs.number }}-${slug}.md`; + + // Get the current date as an ISO8601 string, split at the timestamp, + // and only keep the date portion. + const [date] = new Date().toISOString().split("T"); + + const adr = `# ${ title } + + Date: ${ date } + + ### Status + + Accepted + + ${ body } + `; + + # Set outputs for the next step to use + core.setOutput('filename', filename); + core.setOutput('adr', adr); + + await fs.writeFile(filename, adr, { encoding: 'utf-8' }); + + - name: branch, commit, and open PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BRANCH="adr-auto-${{ steps.next.outputs.number }}" + git config --global user.email "mats.gsl@noaa.gov" + git config --global user.name "GSL Verification team ADR automation" + # Check if the branch already exists, exit-code 2 means it doesn't, and that creating a new branch is safe. + set +e # override set -e since we're hoping for an exit code + git ls-remote --exit-code --heads origin refs/heads/$BRANCH > /dev/null + branch_exists=$? + set -e + if [ $branch_exists -eq 2 ]; then + git checkout -b $BRANCH + git add docs/decisions/architecture/*.md + git commit -m "add ADR ${{ steps.next.outputs.number }}: ${{ github.event.issue.title }}" + git push -f origin $BRANCH + gh pr create \ + --title "Add ADR ${{ steps.next.outputs.number }} to the repo" \ + --label "ADR" \ + --body "This pull request was opened automatically because #${{ github.event.issue.number }} was closed after being marked as an approved ADR. It contains a markdown file capturing the ADR body at the time the issue was closed. Please verify that the markdown is correct before merging!" || true + else + echo "Error - Branch $BRANCH already exists, PR will need to be created manually. ADR text is below. Exiting." + echo " --- " + echo "Filename: ${{ steps.create-adr.outputs.filename }}" + echo " --- Start File --- " + echo "${{ steps.create-adr.outputs.adr }}" + echo " --- End File --- " + + # Create a step summary + echo "# Error" >> $GITHUB_STEP_SUMMARY + echo "Branch $BRANCH already exists, PR will need to be created manually. ADR contents are below. Exiting." >> $GITHUB_STEP_SUMMARY + echo "## Filename" >> $GITHUB_STEP_SUMMARY + echo "\`${{ steps.create-adr.outputs.filename }}\`" >> $GITHUB_STEP_SUMMARY + echo "## ADR Contents" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.create-adr.outputs.adr }}" >> $GITHUB_STEP_SUMMARY + exit 1 + fi diff --git a/.github/workflows/adr-proposed.yaml b/.github/workflows/adr-proposed.yaml new file mode 100644 index 00000000..022d737e --- /dev/null +++ b/.github/workflows/adr-proposed.yaml @@ -0,0 +1,35 @@ +name: ADR proposed +on: + issues: + types: + - labeled + +jobs: + main: + name: ADR proposed + runs-on: ubuntu-latest + + # Only run this workflow if the issue is open and has the "ADR" label + if: "${{github.event.issue.state == 'open' && contains(github.event.issue.labels.*.name, 'ADR') }}" + + steps: + - name: add comment if appropriate + uses: actions/github-script@v7 + with: + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + const botHasCommented = comments.some(({ user: { login }}) => login === 'github-actions[bot]'); + + if(!botHasCommented) { + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'This issue appears to be a draft or in-progress ADR. When it is completed and accepted, add the `ADR: Accepted` label and close the issue. This will start a process to create the ADR document and add it to the repo automatically. If you close the issue before adding the label, simply re-open and re-close it.' + }); + } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6228280b..4454508b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,7 @@ on: push: paths-ignore: - "meta_update_middleware/**" + - "docs/**" tags: - "[0-9]+.[0-9]+.[0-9]+" - "[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" @@ -10,6 +11,7 @@ on: pull_request: paths-ignore: - "meta_update_middleware/**" + - "docs/**" workflow_dispatch: # Manually env: REGISTRY: ghcr.io/noaa-gsl/vxingest diff --git a/.github/workflows/sdr-accepted.yaml b/.github/workflows/sdr-accepted.yaml new file mode 100644 index 00000000..a6c04fb8 --- /dev/null +++ b/.github/workflows/sdr-accepted.yaml @@ -0,0 +1,111 @@ +name: SDR Accepted +on: + issues: + types: + - closed + +jobs: + main: + name: Create SDR + runs-on: ubuntu-latest + + # Only run this workflow if the closed issue has the "SDR: accepted" label + if: "${{ contains(github.event.issue.labels.*.name, 'SDR: accepted') }}" + + steps: + - name: checkout main branch + uses: actions/checkout@v4 + with: + ref: main + + - name: get SDR number + id: next + run: | + LAST_SDR=$(ls docs/decisions/scientific/*.md | grep -Eo "scientific/sdr-[0-9]+-" | sort | tail -n1 | grep -Eo "[0-9]+") + LAST_SDR=$(echo "$LAST_SDR" | sed -E 's/^0+//') + NEXT_SDR=$(($LAST_SDR + 1)) + NEXT_SDR=$(printf "%04i" "$NEXT_SDR") + echo "number=$NEXT_SDR" >> "$GITHUB_OUTPUT" + + - name: write the SDR + id: create-sdr + uses: actions/github-script@v7 + with: + script: | + const fs = require("fs/promises"); + + // Use the GitHub toJSON expression to get these as escaped strings. + // In Javascript, this will preserve line breaks, and using template + // strings further down will preserve all the kinds of quotes. + const title = ${{ toJSON(github.event.issue.title) }}; + const body = ${{ toJSON(github.event.issue.body )}}; + + const slug = title + .toLowerCase() + .trim() + .replace(/[^\w\s-]/g, "") // get rid of non-ascii characters + .replace(/[\s_-]+/g, "-") // convert whitespace and underscore to dash + .replace(/^-+|-+$/g, ""); // git rid of leading/trailing dashes + + const filename = `docs/decisions/scientific/sdr-${{ steps.next.outputs.number }}-${slug}.md`; + + // Get the current date as an ISO8601 string, split at the timestamp, + // and only keep the date portion. + const [date] = new Date().toISOString().split("T"); + + const sdr = `# ${ title } + + Date: ${ date } + + ### Status + + Accepted + + ${ body } + `; + + # Set outputs for the next step to use + core.setOutput('filename', filename); + core.setOutput('sdr', sdr); + + await fs.writeFile(filename, sdr, { encoding: 'utf-8' }); + + - name: branch, commit, and open PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BRANCH="sdr-auto-${{ steps.next.outputs.number }}" + git config --global user.email "mats.gsl@noaa.gov" + git config --global user.name "GSL Verification team SDR automation" + # Check if the branch already exists, exit-code 2 means it doesn't, and that creating a new branch is safe. + set +e # override set -e since we're hoping for an exit code + git ls-remote --exit-code --heads origin refs/heads/$BRANCH > /dev/null + branch_exists=$? + set -e + if [ $branch_exists -eq 2 ]; then + git checkout -b $BRANCH + git add docs/decisions/scientific/*.md + git commit -m "add SDR ${{ steps.next.outputs.number }}: ${{ github.event.issue.title }}" + git push -f origin $BRANCH + gh pr create \ + --title "Add SDR ${{ steps.next.outputs.number }} to the repo" \ + --label "SDR" \ + --body "This pull request was opened automatically because #${{ github.event.issue.number }} was closed after being marked as an approved SDR. It contains a markdown file capturing the SDR body at the time the issue was closed. Please verify that the markdown is correct before merging!" || true + exit 0 + else + echo "Error - Branch $BRANCH already exists, PR will need to be created manually. SDR text is below. Exiting." + echo " --- " + echo "Filename: ${{ steps.create-sdr.outputs.filename }}" + echo " --- Start File --- " + echo "${{ steps.create-sdr.outputs.sdr }}" + echo " --- End File --- " + + # Create a step summary + echo "# Error" >> $GITHUB_STEP_SUMMARY + echo "Branch $BRANCH already exists, PR will need to be created manually. SDR contents are below. Exiting." >> $GITHUB_STEP_SUMMARY + echo "## Filename" >> $GITHUB_STEP_SUMMARY + echo "\`${{ steps.create-sdr.outputs.filename }}\`" >> $GITHUB_STEP_SUMMARY + echo "## SDR Contents" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.create-sdr.outputs.sdr }}" >> $GITHUB_STEP_SUMMARY + exit 1 + fi diff --git a/.github/workflows/sdr-proposed.yaml b/.github/workflows/sdr-proposed.yaml new file mode 100644 index 00000000..02909d12 --- /dev/null +++ b/.github/workflows/sdr-proposed.yaml @@ -0,0 +1,35 @@ +name: SDR proposed +on: + issues: + types: + - labeled + +jobs: + main: + name: SDR proposed + runs-on: ubuntu-latest + + # Only run this workflow if the issue is open and has the "SDR" label + if: "${{github.event.issue.state == 'open' && contains(github.event.issue.labels.*.name, 'SDR') }}" + + steps: + - name: add comment if appropriate + uses: actions/github-script@v7 + with: + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + const botHasCommented = comments.some(({ user: { login }}) => login === 'github-actions[bot]'); + + if(!botHasCommented) { + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'This issue appears to be a draft or in-progress SDR. When it is completed and accepted, add the `SDR: Accepted` label and close the issue. This will start a process to create the SDR document and add it to the repo automatically. If you close the issue before adding the label, simply re-open and re-close it.' + }); + } diff --git a/docs/decisions/README.md b/docs/decisions/README.md new file mode 100644 index 00000000..834cf77d --- /dev/null +++ b/docs/decisions/README.md @@ -0,0 +1,49 @@ +# Decisions + +This part of our documentation records decisions we have made for the VxIngest project. We've chosen to: + +- Use the Architecture/Any Decision Record format for this purpose +- Make a distinction between "architecture" and "scientific" decision records + - Architecture decisions are decisions focused on software & infrastructure patterns & use. + - Scientific decisions are decisions focused on how to calculate & handle data + +This is largely inspired by the work done in the beta.weather.gov rewrite. You can [see their usage of ADRs in their GitHub repo here](https://github.com/weather-gov/weather.gov/tree/main/docs/architecture/decisions) + +## Purpose + +Decision Records are intended to be a useful record for current and new project members. They aren't intended to capture every trivial decision we make as that would quickly become noisy. Decision Records should capture why the team made certain significant decisions. Think of the audience as being yourself two or more years from now, a new project member with no context on the project trying to come up to speed, or a scientist trying to understand how we verify certain values. + +Decision records are supposed to be lightweight, so should be fairly quick to fill out. (However, the discussion prior to recording the decision may be more involved) + +## Format + +The format we will follow is largely [described by Michael Nygard in this article](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions) and is referenced in the issue template. + +**Title** The documents have concise titles that reflect the decision and start with "We will ...". For example, "We will record scientific decisions" + +**Status** Either `Accepted` or `Superseded`. If `Superseded`, reference its replacement like so: `Superseded by [ADR 0000](adr-0000-the-adr-title.md)` + +**Context** A statement of facts describing the motivating factors behind the decision: social, political, technical, scientific, etc. Why does this decision need to be made? + +**Decision** What was decided? This should be an active voice statement. For example, "We will ..." + +**Consequences** This section describes the resulting context, after applying the decision. All consequences should be listed here, not just the "positive" ones. A particular decision may have positive, negative, and neutral consequences, but all of them affect the team and project in the future. + +**Filename** Decision records will be stored in Markdown files. The fileanames will be lower-case copies of the titles, solely with ASCII characters, and with spaces/underscores swapped for dashes (`-`). The title is prefaced by `adr-XXXX-` or `sdr-XXXX-` where `XXXX` is the ADR/SDR number. For example, `adr-0001-we-will-record-scientific-decisions.md`. + +## Process + +To open an ADR or SDR, you will need to: + +1. First, make a significant decision as a team. We will most likely have discussed the core of the decision before the decision record is made. +2. Open a new issue and choose the appropriate ADR or SDR template. +3. Fill out the issue template to record the decision. +4. Ask for feed back in the issue. You can use `@` mentions to loop people in to the conversation. +5. Incorporate feedback by editing the ADR/SDR issue body until the decision is well captured. +6. Once the decision has been made, there are two routes: + + - Reject the decision - if we decide not to move forward with the decision, close the issue. + - Accept the decision - if we decide to accept the decision, add the appropriate `ADR/SDR: accepted` label and close the issue + +7. A PR with the Decision Record will be automatically created. Review the generated Markdown for sanity and merge it promptly. +8. If a decision record supersedes another, include a link to the superseded decision in the Decision Record's `Context` section. The old/superseded decision record's `Status` section will need to be manually updated in the PR to link to the new decision record. Use a relative markdown link like `Supersedes [ADR 0000](adr-0000-superseded-adr-file.md)`, instead of a GitHub URL to do the linking. diff --git a/docs/decisions/architecture/adr-0001-we-will-record-architecture-decisions.md b/docs/decisions/architecture/adr-0001-we-will-record-architecture-decisions.md new file mode 100644 index 00000000..cf568bac --- /dev/null +++ b/docs/decisions/architecture/adr-0001-we-will-record-architecture-decisions.md @@ -0,0 +1,21 @@ +# We will record architecture decisions + +Date: 2024-10-08 + +## Status + +Proposed + +## Context + +We need to record the architectural decisions made on this project. + +## Decision + +To record significant architecture decisions in this project, we will use Architecture/Any +Decision Records as [described by Michael Nygard in this +article](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). + +## Consequences + +See Michael Nygard's article, linked above. diff --git a/docs/decisions/scientific/sdr-0001-we-will-record-scientific-decisions.md b/docs/decisions/scientific/sdr-0001-we-will-record-scientific-decisions.md new file mode 100644 index 00000000..561d3d07 --- /dev/null +++ b/docs/decisions/scientific/sdr-0001-we-will-record-scientific-decisions.md @@ -0,0 +1,21 @@ +# We will record scientific decisions + +Date: 2024-10-08 + +## Status + +Proposed + +## Context + +We need to record the scientific decisions made on this project. + +## Decision + +To record significant scientific decisions in this project, we will use Architecture/Any +Decision Records as [described by Michael Nygard in this +article](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). + +## Consequences + +See Michael Nygard's article, linked above. diff --git a/docs/decisions/scientific/sdr-0002-we-will-recalculate-all-heights-for-raobs.md b/docs/decisions/scientific/sdr-0002-we-will-recalculate-all-heights-for-raobs.md new file mode 100644 index 00000000..a587631a --- /dev/null +++ b/docs/decisions/scientific/sdr-0002-we-will-recalculate-all-heights-for-raobs.md @@ -0,0 +1,33 @@ +# We will recalculate all heights for radiosonde observations + +Date: 2024-10-09 + +## Status + +Accepted + +## Context + +This decision was actually made a couple of years ago but we are recording it now. Because height data from radiosondes (RAOBS) was unreliable a decision was made with the advice of senior meteorologists to always recalculate all height values. Currently all height values are calculated from the lowest reported height using the hypsometric equation and the reported temperature, pressure, and mixing ratio (derived from the specific humidity). + +Once the heights are calculated, all variables, including heights, are interpolated to mandatory pressure levels using logarithmic interpolation. + +## Decision + +We will always recalculate heights using the [hypsometric equation](https://unidata.github.io/MetPy/latest/api/generated/metpy.calc.thickness_hydrostatic.html), and interpolate heights and variables to mandatory levels based on the calculated heights. + +## Consequences + +### Positive + +- We will have consistent height levels for computing further verification statistics +- The height levels will be derived from values we trust more than the recorded height; like temperature, pressure, and specific humidity. + +### Neutral + +- It is important to understand this interpolation technique when comparing our RAOB data to the data in the raw PrepBufr-formatted RAOB observation files. They do not compare one to one. + +### Negative + +- The height levels will be derived from other data, rather than using the measured values. This could differ from other ways the RAOB observation data is used in models. +- We will be further interpolating the observations to the mandatory levels.