From 293664143dcd799bfea3a182513007380ee5286b Mon Sep 17 00:00:00 2001 From: Emma Etherington Date: Tue, 6 Sep 2022 13:56:58 -0400 Subject: [PATCH] feat: generate defaults by template type, make adtech and publisher take corporate emissions parameter not call nested add in corporate emissions standalone calculation --- .github/workflows/defaults_release.yaml | 30 ++++++-- .gitignore | 4 +- README.md | 10 ++- docs/adTechModel.md | 2 +- docs/corporate.md | 25 +++++++ poetry.lock | 20 +++--- requirements.txt | 34 +++++++++ scope3_methodology/adtech_model.py | 45 +++++++++--- scope3_methodology/compute_defaults.py | 95 +++++++++++++------------ scope3_methodology/corporate.py | 77 ++++++++++++++++++-- scope3_methodology/publisher.py | 56 ++++++++++----- templates/ad_tech_platform.yaml | 4 ++ templates/publisher.yaml | 2 +- 13 files changed, 304 insertions(+), 100 deletions(-) create mode 100644 requirements.txt mode change 100755 => 100644 scope3_methodology/compute_defaults.py mode change 100644 => 100755 scope3_methodology/corporate.py create mode 100644 templates/ad_tech_platform.yaml diff --git a/.github/workflows/defaults_release.yaml b/.github/workflows/defaults_release.yaml index 2acc00ff..d06f2bc3 100644 --- a/.github/workflows/defaults_release.yaml +++ b/.github/workflows/defaults_release.yaml @@ -49,17 +49,37 @@ jobs: poetry install env: POETRY_VIRTUALENVS_CREATE: false - - name: Generate Defaults File + - name: Generate Defaults Files run: python ./scope3_methodology/compute_defaults.py - - name: Upload Defaults Files to Release - id: upload_defaults_files + - name: Upload ATP Defaults File to Release + id: upload_atp_defaults_files uses: actions/upload-release-asset@v1.0.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: defaults.yaml - asset_name: methodology_model_defaults.yaml + asset_path: atp-defaults.yaml + asset_name: methodology_atp_defaults.yaml + asset_content_type: text/plain + - name: Upload Organization Defaults File to Release + id: upload_org_defaults_files + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: organization-defaults.yaml + asset_name: methodology_organization_defaults.yaml + asset_content_type: text/plain + - name: Upload Property Defaults Files to Release + id: upload_prop_defaults_files + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: property-defaults.yaml + asset_name: methodology_property_defaults.yaml asset_content_type: text/plain - name: Publish Release uses: eregon/publish-release@v1 diff --git a/.gitignore b/.gitignore index 04a0b2ca..99ad3af0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Output files -defaults.yaml +atp-defaults.yaml +organization-defaults.yaml +property-defaults.yaml # git *.swp diff --git a/README.md b/README.md index b17ec230..c6563df8 100644 --- a/README.md +++ b/README.md @@ -61,15 +61,21 @@ To write defaults from latest sources: ./scope3_methodology/compute_defaults.py ``` +To compute the corporate emissions, pass in its YAML file: + +```sh +./scope3_methodology/corporate.py --verbose [--publisher] [--adTechPlatform] [your_model.yaml] +``` + To compute the emissions for an ad tech company, pass in its YAML file: ```sh -./scope3_methodology/adtech_model.py -v sources/companies/criteo/data.yaml +./scope3_methodology/adtech_model.py -v [--corporateEmissionsG] [--corporateEmissionsGPerRequest] sources/companies/criteo/data.yaml ``` To compute the emissions for publisher, pass in its YAML file: ```sh -./scope3_methodology/publisher.py -vsources/companies/theguardian/data.yaml +./scope3_methodology/publisher.py -v [--corporateEmissionsG] [--corporateEmissionsGPerImp] sources/companies/theguardian/data.yaml ``` diff --git a/docs/adTechModel.md b/docs/adTechModel.md index c4cdfc81..7693d649 100644 --- a/docs/adTechModel.md +++ b/docs/adTechModel.md @@ -42,7 +42,7 @@ The file must have: To model emissions, make sure defaults have been computed. Then run: ```sh -./scope3_methodology/adtech_model.py --verbose [your_model.yaml] +./scope3_methodology/adtech_model.py --verbose [--corporateEmissionsG] [--corporateEmissionsGPerRequest] [your_model.yaml] ``` If you want to see how the secondary emissions would be modeled, pass in `-p N` where N is the number of partners to simulate. diff --git a/docs/corporate.md b/docs/corporate.md index 98fee2cd..832fc463 100644 --- a/docs/corporate.md +++ b/docs/corporate.md @@ -14,3 +14,28 @@ Includes home office emissions ## Increased detail in financial data + + +# Usage +Create a YAML file that describes the company you would like to model. The YAML file should look like this: +``` +company: + name: Criteo + products: + - product: + name: Criteo + template: network + identifier: criteo.com + sources: + - source: + facts: + - fact: + employees: 2810 +``` +The file must have: +- Some number of facts, with `employees` required at a minimum unless you provide full corporate emissions data + +To model emissions, make sure defaults have been computed. Then run: +```sh +./scope3_methodology/corporate.py --verbose [--publisher] [--adTechPlatform] [your_model.yaml] +``` \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index c4eace38..2c7526f3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,6 +1,6 @@ [[package]] name = "astroid" -version = "2.12.5" +version = "2.12.9" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false @@ -162,7 +162,7 @@ pyflakes = ">=2.5.0,<2.6.0" [[package]] name = "identify" -version = "2.5.3" +version = "2.5.5" description = "File identification library for Python" category = "dev" optional = false @@ -361,14 +361,14 @@ python-versions = ">=3.6" [[package]] name = "pylint" -version = "2.15.0" +version = "2.15.2" description = "python code static checker" category = "dev" optional = false python-versions = ">=3.7.2" [package.dependencies] -astroid = ">=2.12.4,<=2.14.0-dev0" +astroid = ">=2.12.9,<=2.14.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = ">=0.2" isort = ">=4.2.5,<6" @@ -555,8 +555,8 @@ content-hash = "b2e9bae2213bdd958b79c456414376ca32ceee2af51849a21bcb5c2c155a1d6d [metadata.files] astroid = [ - {file = "astroid-2.12.5-py3-none-any.whl", hash = "sha256:d612609242996c4365aeb0345e61edba34363eaaba55f1c0addf6a98f073bef6"}, - {file = "astroid-2.12.5.tar.gz", hash = "sha256:396c88d0a58d7f8daadf730b2ce90838bf338c6752558db719ec6f99c18ec20e"}, + {file = "astroid-2.12.9-py3-none-any.whl", hash = "sha256:27a22f40e45af6d362498647a0940e8ae9c35f71cb572a1b6f8f810122a11918"}, + {file = "astroid-2.12.9.tar.gz", hash = "sha256:0dafbfcf4ebdecd3c8f6d742c9d9c88508229ca823d5c98ab872d964f3321e56"}, ] attrs = [ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, @@ -680,8 +680,8 @@ flake8 = [ {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, ] identify = [ - {file = "identify-2.5.3-py2.py3-none-any.whl", hash = "sha256:25851c8c1370effb22aaa3c987b30449e9ff0cece408f810ae6ce408fdd20893"}, - {file = "identify-2.5.3.tar.gz", hash = "sha256:887e7b91a1be152b0d46bbf072130235a8117392b9f1828446079a816a05ef44"}, + {file = "identify-2.5.5-py2.py3-none-any.whl", hash = "sha256:ef78c0d96098a3b5fe7720be4a97e73f439af7cf088ebf47b620aeaa10fadf97"}, + {file = "identify-2.5.5.tar.gz", hash = "sha256:322a5699daecf7c6fd60e68852f36f2ecbb6a36ff6e6e973e0d2bb6fca203ee6"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, @@ -850,8 +850,8 @@ pyflakes = [ {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, ] pylint = [ - {file = "pylint-2.15.0-py3-none-any.whl", hash = "sha256:4b124affc198b7f7c9b5f9ab690d85db48282a025ef9333f51d2d7281b92a6c3"}, - {file = "pylint-2.15.0.tar.gz", hash = "sha256:4f3f7e869646b0bd63b3dfb79f3c0f28fc3d2d923ea220d52620fd625aed92b0"}, + {file = "pylint-2.15.2-py3-none-any.whl", hash = "sha256:cc3da458ba810c49d330e09013dec7ced5217772dec8f043ccdd34dae648fde8"}, + {file = "pylint-2.15.2.tar.gz", hash = "sha256:f63404a2547edb5247da263748771ac9a806ed1de4174cda01293c08ddbc2999"}, ] pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..0ef9ed67 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,34 @@ +pyyaml==6.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \ + --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \ + --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \ + --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \ + --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \ + --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \ + --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \ + --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \ + --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \ + --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \ + --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \ + --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \ + --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \ + --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \ + --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \ + --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \ + --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \ + --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \ + --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \ + --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \ + --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \ + --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \ + --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \ + --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \ + --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \ + --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \ + --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \ + --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \ + --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \ + --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \ + --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \ + --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \ + --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5 diff --git a/scope3_methodology/adtech_model.py b/scope3_methodology/adtech_model.py index 31daa1fa..6432a957 100755 --- a/scope3_methodology/adtech_model.py +++ b/scope3_methodology/adtech_model.py @@ -4,7 +4,6 @@ import logging import yaml -from corporate import get_corporate_emissions from utils import get_fact_or_default, get_facts_from_sources, log_result, log_step from yaml.loader import SafeLoader @@ -74,17 +73,17 @@ def getProductInfo(key: str, default: float | None, product: dict[str, float], d def getCorporateEmissionsPerBidRequest( - corporateAllocation: float, + corporate_emissions_g: float, + corporate_allocation: float, facts: dict[str, float], defaults: dict[str, float], depth: int, ) -> float: - corporateEmissionsG = get_corporate_emissions(facts, defaults, depth - 1) * 1000000 bidRequests = ( get_fact_or_default("bid requests processed billion per month", facts, defaults, depth - 1) * 1000000000 ) - corporateEmissionsPerBidRequest = corporateAllocation * corporateEmissionsG / bidRequests + corporateEmissionsPerBidRequest = corporate_allocation * corporate_emissions_g / bidRequests log_result( "corporate emissions g per bid request", f"{corporateEmissionsPerBidRequest}:.8f", @@ -180,15 +179,12 @@ def getServerEmissionsPerBidRequest( def getPrimaryEmissionsPerBidRequest( + corporateEmissionsPerBidRequest: float, serverAllocation: float, - corporateAllocation: float, facts: dict[str, float], defaults: dict[str, float], depth: int, ) -> float: - corporateEmissionsPerBidRequest = getCorporateEmissionsPerBidRequest( - corporateAllocation, facts, defaults, depth - 1 - ) dataTransferEmissionsPerBidRequest = getDataTransferEmissionsPerBidRequest( facts, defaults, depth - 1 ) @@ -299,8 +295,8 @@ def main(): parser.add_argument( "-d", "--defaultsFile", - default="defaults.yaml", - help="Set the defaults file to use (overrides defaults.yaml)", + default="atp-defaults.yaml", + help="Set the defaults file to use (overrides atp-defaults.yaml)", ) parser.add_argument("-v", "--verbose", action="store_true", help="Show derivation of output") parser.add_argument( @@ -312,6 +308,20 @@ def main(): nargs="?", help="Simulate distribution partners", ) + parser.add_argument( + "--corporateEmissionsG", + const=1, + type=float, + nargs="?", + help="Provide the corporate emissionsfor organization", + ) + parser.add_argument( + "--corporateEmissionsGPerImp", + const=1, + type=float, + nargs="?", + help="Provide the corporate emissions for organization per bid request", + ) parser.add_argument("companyFile", nargs=1, help="The company file to parse in YAML format") args = parser.parse_args() if args.verbose: @@ -332,6 +342,12 @@ def main(): raise Exception("No 'sources' field found in company file") facts = get_facts_from_sources(document["company"]["sources"]) + corporate_emissions_g = args.corporateEmissionsG + corporate_emissions_g_per_imp = args.corporateEmissionsGPerImp + + if not corporate_emissions_g and not corporate_emissions_g_per_imp: + raise Exception("Must provide either --corporateEmissionsG or --corporateEmissionsGPerImp") + depth = 4 if args.verbose else 0 productModels = [] for product in document["company"]["products"]: @@ -346,8 +362,15 @@ def main(): if template not in defaultsDocument["defaults"]: raise Exception(f"Template {template} not found in defaults") defaults = defaultsDocument["defaults"][template] + + corporate_emissions_per_bid_request = corporate_emissions_g_per_imp + if corporate_emissions_g: + corporate_emissions_per_bid_request = getCorporateEmissionsPerBidRequest( + corporate_emissions_g, corporateAllocation, facts, defaults, depth - 1 + ) + primaryEmissionsPerBidRequest = getPrimaryEmissionsPerBidRequest( - serverAllocation, corporateAllocation, facts, defaults, depth + corporate_emissions_per_bid_request, serverAllocation, facts, defaults, depth ) primaryEmissionsPerCookieSync = getPrimaryEmissionsPerCookieSync( serverAllocation, facts, defaults, depth diff --git a/scope3_methodology/compute_defaults.py b/scope3_methodology/compute_defaults.py old mode 100755 new mode 100644 index 5fbbae87..f8a10f92 --- a/scope3_methodology/compute_defaults.py +++ b/scope3_methodology/compute_defaults.py @@ -9,15 +9,50 @@ from yaml.loader import SafeLoader -# flake8: noqa: C901 +def computeDefaults( + templateType: str, + templates: dict[str, dict[str, float]], + facts: dict[str, float], + defaultsFile: str, + model_inputs: set[str], + dry_run: bool, +): + globalDefaults: dict[str, float] = { + # TODO - get some actual data on this from customers + "bid request size in bytes": 10000, + } + + templateDefaults = {} + + for name, template in templates.items(): + if str(template["type"]) != templateType: + continue + defaults: dict[str, float] = {} + for key in facts: + if key in model_inputs: + fact_sum = sum(fact.value for fact in facts[key]) # type: ignore + defaults[key] = round(fact_sum / len(facts[key]), 4) # type: ignore + + for input in model_inputs: + if input not in defaults: + if input in template: + defaults[input] = template[input] + elif input in globalDefaults: + defaults[input] = globalDefaults[input] + + templateDefaults[name] = defaults + + output = yaml.dump({"defaults": templateDefaults}, Dumper=yaml.Dumper) + if dry_run: + print(output) + else: + writeStream = open(defaultsFile, "w") + writeStream.write(output) + writeStream.close() + + def main(): parser = argparse.ArgumentParser(description="Compute defaults from known sources") - parser.add_argument( - "-d", - "--defaultsFile", - default="defaults.yaml", - help="Set the defaults file to output to (overrides defaults.yaml)", - ) parser.add_argument( "--dry-run", action="store_true", @@ -25,9 +60,9 @@ def main(): ) args = parser.parse_args() - corporate_model_inputs = get_corporate_keys() - - property_model_inputs = { + templateKeys = {} + templateKeys["organization"] = get_corporate_keys() + templateKeys["property"] = { "quality impressions per duration s", "revenue allocation to digital pct", "revenue allocation to ads pct", @@ -36,13 +71,7 @@ def main(): "computer active electricity use watts", "computer idle electricity use watts", } - - atp_model_inputs = corporate_model_inputs.union(get_ad_tech_model_keys()) - - globalDefaults: dict[str, float] = { - # TODO - get some actual data on this from customers - "bid request size in bytes": 10000, - } + templateKeys["atp"] = get_ad_tech_model_keys() # get a list of all facts from our sources facts = get_all_facts() @@ -61,36 +90,8 @@ def main(): name = document["template"]["name"] templates[name] = document["template"] - templateDefaults = {} - - for name, template in templates.items(): - model_inputs: set[str] - if template["type"] == "publisher": - model_inputs = corporate_model_inputs - elif template["type"] == "property": - model_inputs = property_model_inputs - else: - model_inputs = atp_model_inputs - defaults: dict[str, float] = {} - for key in facts: - if key in model_inputs: - defaults[key] = round(sum(fact.value for fact in facts[key]) / len(facts[key]), 4) - - for input in model_inputs: - if input not in defaults: - if input in template: - defaults[input] = template[input] - elif input in globalDefaults: - defaults[input] = globalDefaults[input] - templateDefaults[name] = defaults - - output = yaml.dump({"defaults": templateDefaults}, Dumper=yaml.Dumper) - if args.dry_run: - print(output) - else: - writeStream = open(args.defaultsFile, "w") - writeStream.write(output) - writeStream.close() + for t in templateKeys: + computeDefaults(t, templates, facts, t + "-defaults.yaml", templateKeys[t], args.dry_run) if __name__ == "__main__": diff --git a/scope3_methodology/corporate.py b/scope3_methodology/corporate.py old mode 100644 new mode 100755 index bd47b05d..d48737e4 --- a/scope3_methodology/corporate.py +++ b/scope3_methodology/corporate.py @@ -1,4 +1,11 @@ -from utils import get_fact_or_default, log_result +#!/usr/bin/env python +""" Compute corporate emissions for an organization """ +import argparse +import logging + +import yaml +from utils import get_fact_or_default, get_facts_from_sources, log_result +from yaml.loader import SafeLoader def get_corporate_keys() -> set[str]: @@ -12,9 +19,7 @@ def get_corporate_keys() -> set[str]: } -def get_corporate_emissions( - facts: dict[str, float], defaults: dict[str, float], depth: int -) -> float: +def compute_emissions(facts: dict[str, float], defaults: dict[str, float], depth: int) -> float: if "corporate emissions mt per month" in facts: return get_fact_or_default("corporate emissions mt per month", facts, defaults, depth) if "employees" not in facts: @@ -43,3 +48,67 @@ def get_corporate_emissions( ) log_result("corporate emissions mt per month", f"{corporateEmissions:.2f}", depth) return corporateEmissions + + +def main(): + # Parse command line to get company file + parser = argparse.ArgumentParser(description="Compute corporate emissions") + parser.add_argument( + "-d", + "--defaultsFile", + default="organization-defaults.yaml", + help="Set the defaults file to use (overrides organization-defaults.yaml)", + ) + parser.add_argument("-v", "--verbose", action="store_true", help="Show derivation of output") + parser.add_argument("companyFile", nargs=1, help="The company file to parse in YAML format") + parser.add_argument( + "-p", + "--publisher", + const=1, + default=False, + type=bool, + nargs="?", + help="Whether to compute corporate emissions as if the company is a publisher", + ) + parser.add_argument( + "-a", + "--adTechPlatform", + const=1, + default=False, + type=bool, + nargs="?", + help="Whether to compute corporate emissions as if the company is a ad tech platform", + ) + + args = parser.parse_args() + if args.verbose: + logging.basicConfig(level=logging.INFO) + + # Load defaults + defaultsStream = open(args.defaultsFile, "r") + defaultsDocument = yaml.load(defaultsStream, Loader=SafeLoader) + + # Load facts about the company + stream = open(args.companyFile[0], "r") + document = yaml.load(stream, Loader=SafeLoader) + if "company" not in document: + raise Exception("No 'company' field found in company file") + if "products" not in document["company"]: + raise Exception("No 'products' field found in company file") + if "sources" not in document["company"]: + raise Exception("No 'sources' field found in company file") + facts = get_facts_from_sources(document["company"]["sources"]) + + depth = 4 if args.verbose else 0 + orgEmissions = {} + if args.publisher: + defaults = defaultsDocument["defaults"]["publisher"] + orgEmissions["publisher"] = compute_emissions(facts, defaults, depth - 1) * 1000000 + if args.adTechPlatform: + defaults = defaultsDocument["defaults"]["adTechPlatform"] + orgEmissions["adTechPlatform"] = compute_emissions(facts, defaults, depth - 1) * 1000000 + print(yaml.dump({"corporateEmissions": orgEmissions}, Dumper=yaml.Dumper)) + + +if __name__ == "__main__": + main() diff --git a/scope3_methodology/publisher.py b/scope3_methodology/publisher.py index b4b42a4c..28d317f6 100755 --- a/scope3_methodology/publisher.py +++ b/scope3_methodology/publisher.py @@ -4,7 +4,6 @@ import logging import yaml -from corporate import get_corporate_emissions from utils import get_fact_or_default, get_facts_from_sources, log_result from yaml.loader import SafeLoader @@ -13,8 +12,8 @@ parser.add_argument( "-d", "--defaultsFile", - default="defaults.yaml", - help="Set the defaults file to use (overrides defaults.yaml)", + default="property-defaults.yaml", + help="Set the defaults file to use (overrides property-defaults.yaml)", ) parser.add_argument( "-e", @@ -61,8 +60,23 @@ nargs="?", help="Simulate multiple auctions", ) +parser.add_argument( + "--corporateEmissionsG", + const=1, + type=float, + nargs="?", + help="Provide the corporate emissions for organization", +) +parser.add_argument( + "--corporateEmissionsGPerImp", + const=1, + type=float, + nargs="?", + help="Provide the corporate emissions for organization per impression", +) parser.add_argument("-v", "--verbose", action="store_true", help="Show derivation of output") parser.add_argument("companyFile", nargs=1, help="The company file to parse in YAML format") + args = parser.parse_args() if args.verbose: @@ -83,12 +97,6 @@ raise Exception("No 'sources' field found in company file") facts: dict[str, float] = get_facts_from_sources(document["company"]["sources"]) -if "publisher" not in defaultsDocument["defaults"]: - raise Exception("Publisher template not found") -corporate_emissions = ( - get_corporate_emissions(facts, defaultsDocument["defaults"]["publisher"], 0) * 1000000 -) - depth = 4 if args.verbose else 0 @@ -111,12 +119,15 @@ def __init__( self.page_load_electricity_kwh = page_load_electricity_kwh self.client_device_emissions_g = client_device_emissions_g - def set_corporate_emissions(self, emissions: float) -> None: - self.corporate_emissions_per_impression = round( - emissions * self.ad_revenue_allocation / self.impressions, 6 - ) + def set_corporate_emissions_per_impression(self, emissionsG: float, emissionsGPerImp) -> None: + if emissionsG: + self.corporate_emissions_per_impression = round( + emissionsG * self.ad_revenue_allocation / self.impressions, 6 + ) + else: + self.corporate_emissions_per_impression = round(emissionsGPerImp, 6) log_result( - f"{self.identifier} corporate emissions g", + f"{self.identifier} corporate emissions g per impression", self.corporate_emissions_per_impression, 1, ) @@ -214,7 +225,6 @@ def get_energy_from_page_load( log_result("client_device_emissions", client_device_emissions_per_impression, 2) # TODO - simulate auctions to multiple ad tech partners w/ cookie syncs - properties.append( Property( identifier, @@ -226,9 +236,19 @@ def get_energy_from_page_load( ) ) +corporate_emissions_g = args.corporateEmissionsG +corporate_emissions_g_per_imp = args.corporateEmissionsGPerImp + +if not corporate_emissions_g and not corporate_emissions_g_per_imp: + raise Exception("Must provide either --corporateEmissionsG or --corporateEmissionsGPerImp") + for property in properties: - property.set_corporate_emissions( - corporate_emissions * property.impressions / publisher_impressions - ) + if corporate_emissions_g: + property.set_corporate_emissions_per_impression( + corporate_emissions_g * property.impressions / publisher_impressions, None + ) + else: + property.set_corporate_emissions_per_impression(None, corporate_emissions_g_per_imp) + print(yaml.dump({"properties": properties}, Dumper=yaml.Dumper)) diff --git a/templates/ad_tech_platform.yaml b/templates/ad_tech_platform.yaml new file mode 100644 index 00000000..d73c10ba --- /dev/null +++ b/templates/ad_tech_platform.yaml @@ -0,0 +1,4 @@ +--- +template: + name: adTechPlatform + type: organization diff --git a/templates/publisher.yaml b/templates/publisher.yaml index b563527a..f61c4655 100644 --- a/templates/publisher.yaml +++ b/templates/publisher.yaml @@ -1,4 +1,4 @@ --- template: name: publisher - type: publisher + type: organization