diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f5a8323 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: pip + directory: / + schedule: + interval: monthly + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly diff --git a/.github/workflows/check-formatting-and-lint.yml b/.github/workflows/check-formatting-and-lint.yml new file mode 100644 index 0000000..7657c07 --- /dev/null +++ b/.github/workflows/check-formatting-and-lint.yml @@ -0,0 +1,31 @@ +name: Lint and test + +on: + push: + branches: [main] + pull_request: + branches: ["**"] + +jobs: + test: + name: Test + runs-on: ubuntu-22.04 + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -r requirements-test.txt + - name: Check formatting + run: ruff format --diff . + - name: Lint with ruff + run: ruff check --target-version=py310 . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..454796e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Git Ignore File + +**.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8f0bb86 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.12-bookworm + +COPY entrypoint.py /entrypoint.py +COPY requirements.txt /requirements.txt + +RUN pip install -r /requirements.txt + +ENTRYPOINT [ "/entrypoint.py" ] diff --git a/README.md b/README.md new file mode 100644 index 0000000..15ffcd0 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# Carbon Emissions Calculator for PCBA +An actions repository for demonstrating the calculation of the sum carbon emission for a PCBA given an input BOM and a data source with component emissions data + +## Usage + +Add the following step to your actions: + +```yaml +- name: Generate carbon emissions report for a PCBA given its BOM + uses: https://hub.allspice.io/Actions/carbon-emission-calculator@v1 + with: + bom_file: bom.csv +``` + +## Input BOM + +The input BOM to this Action is assumed to be generated from the py-allspice BOM generation utility. The column names referenced and used in this Action script assume the naming convention as populated by the py-allspice BOM generation function. The user is to adjust the expected column positions and naming conventions when using their own BOM file input. + +A typical workflow is to use the [BOM generation Actions add-on](https://hub.allspice.io/Actions/generate-bom) to generate the BOM first, and use the generated BOM as an input to this Action. diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..70158a5 --- /dev/null +++ b/action.yml @@ -0,0 +1,14 @@ +name: "Calculate the carbon sum emission figure for the components in a PCBA given a data source with emissions data" +description: > + Calculate the carbon sum emission figure for the components in a PCBA given a data source with emissions data +inputs: + bom_file: + description: "Path to the BOM CSV file" + required: true +runs: + using: "docker" + image: "Dockerfile" + args: + - ${{ inputs.bom_file }} + env: + ALLSPICE_AUTH_TOKEN: ${{ github.token }} diff --git a/entrypoint.py b/entrypoint.py new file mode 100755 index 0000000..175dc9e --- /dev/null +++ b/entrypoint.py @@ -0,0 +1,77 @@ +#! /usr/bin/env python3 + +from argparse import ArgumentParser +import requests +import csv + +ALLSPICE_DEMO_CARBON_EMISSION_DATA_URL = "https://hub.allspice.io/AllSpice-Demos/Demo-Data-Source/raw/branch/main/Carbon-Emissions-Figures-Archimajor/archimajor-carbon-emissions-figures.csv" + + +################################################################################ +def get_carbon_emission_data_dict_from_source(url): + # Post the request and get the response + response = requests.get(url) + # Get the text representation of the CSV data + data_text = response.text + # Ingest emissions data CSV into a dictionary + emission_data = {} + data_reader = csv.reader(data_text.splitlines(), delimiter=",", quotechar='"') + for row in data_reader: + emission_data[str(row[0])] = row[1] + # Return the data + return emission_data + + +################################################################################ +def query_demo_carbon_emission_data_for_mfr_part_number(data, part_number): + # Look up part number in dictionary, return emission figure if exists + try: + return data[part_number] + # Return 0 as a default if part doesn't exist in data source + except KeyError: + return 0.0 + + +################################################################################ +if __name__ == "__main__": + # Initialize argument parser + parser = ArgumentParser() + parser.add_argument("bom_file", help="Path to the BOM file") + args = parser.parse_args() + + # Read the BOM file into list + with open(args.bom_file, newline="") as bomfile: + # Comma delimited file with " as quote character to be included + bomreader = csv.reader(bomfile, delimiter=",", quotechar='"') + # Save as a list + bom_line_items = list(bomreader) + # Skip the header + del bom_line_items[0] + + # Initialize list of BOM item emissions data + bom_items_emissions_data = [] + + print("- Fetching carbon emissions data from demo data source") + print("") + emission_data = get_carbon_emission_data_dict_from_source( + ALLSPICE_DEMO_CARBON_EMISSION_DATA_URL + ) + + # Fetch emissions figures for all parts in the BOM + for line_item in bom_line_items: + print("- Fetching info for " + line_item[0] + "... ", end="") + # Search for emission figure for a part in demo data source + emission_figure = query_demo_carbon_emission_data_for_mfr_part_number( + emission_data, line_item[0] + ) + # Add the obtained figure to the list of BOM items part data + bom_items_emissions_data.append((line_item[0], float(emission_figure))) + # Print the obtained figure + print(str(emission_figure) + "\n", end="", flush=True) + + # Report the sum of all component emissions + print("") + total_emission_for_pcba_BOM = 0.0 + for bom_item in bom_items_emissions_data: + total_emission_for_pcba_BOM += bom_item[1] + print("Total emissions from BOM parts: " + str(total_emission_for_pcba_BOM)) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0836784 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.ruff] +exclude = [] diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000..7bf99f1 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1 @@ +ruff==0.4.7 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d80d9fc --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests==2.32.3