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