├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── docs
├── Makefile
├── make.bat
└── source
│ ├── _static
│ └── custom.css
│ ├── abstract.rst
│ ├── categories.rst
│ ├── conf.py
│ ├── ec3_wrapper_logo.svg
│ ├── epds.rst
│ ├── getting-started.rst
│ ├── index.rst
│ ├── materials.rst
│ ├── projects.rst
│ └── utilities.rst
├── ec3
├── __init__.py
├── ec3_api.py
├── ec3_categories.py
├── ec3_epds.py
├── ec3_materials.py
├── ec3_projects.py
├── ec3_urls.py
├── ec3_utils.py
└── py.typed
├── ec3_materials_plot.ipynb
├── ec3_overview.ipynb
├── examples
└── ec3_concrete_epd_study.ipynb
├── pyproject.toml
├── readthedocs.yaml
├── requirements-dev.txt
├── requirements.txt
├── setup.cfg
└── setup.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.ipynb linguist-detectable=false
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | .env.*
3 | .vscode
4 | .venv
5 |
6 | build/
7 | dist/
8 | tests/
9 | docs/build
10 | ec3_tests.ipynb
11 | .python-version
12 | *__pycache__
13 | *.egg
14 | *egg-info
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Jared Friedman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EC3 Python Wrapper
2 |
3 |
4 | This is a Python wrapper for working with the Building Transparency EC3 API.
5 | Documentation for the EC3 API can be found at [https://buildingtransparency.org/ec3/manage-apps/api-doc/api](https://buildingtransparency.org/ec3/manage-apps/api-doc/api)
6 |
7 | This project is still early in development. Further documentation and functionality will continue to evolve as the project progresses.
8 |
9 | ## Installing
10 |
11 | ```
12 | pip install ec3-python-wrapper
13 | ```
14 |
15 | ## Documentation
16 |
17 | For documentation on how to use the EC3 Python Wrapper visit:
18 | [https://ec3-python-wrapper.readthedocs.io/](https://ec3-python-wrapper.readthedocs.io/)
19 |
20 | ### Usage Examples
21 |
22 | Some Jupyter Notebook examples are provided here:
23 |
24 | * Overview of some available functions: [ec3_overview.ipynb](ec3_overview.ipynb)
25 | * Querying and plotting materials: [ec3_materials_plot.ipynb](ec3_materials_plot.ipynb)
26 |
27 | A Streamlit example app can be found here:
28 | * EC3 Concrete Carbon Comparison by postal Code: [](https://jbf1212-example-app-ec3-wrapper-ec3-app-bd2ea4.streamlit.app/)
29 |
30 | ## Credits
31 |
32 | * Obviously none of this would exist without the great efforts of the [Building Transparency](https://buildingtransparency.org/ec3) team and the EC3 database.
33 | * Much of how this library has been structured was modeled off of the amazing work done on the [pyAirtable module](https://github.com/gtalarico/pyairtable).
34 |
35 | ## License
36 | [MIT](https://opensource.org/licenses/MIT)
37 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | %SPHINXBUILD% >NUL 2>NUL
14 | if errorlevel 9009 (
15 | echo.
16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17 | echo.installed, then set the SPHINXBUILD environment variable to point
18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
19 | echo.may add the Sphinx directory to PATH.
20 | echo.
21 | echo.If you don't have Sphinx installed, grab it from
22 | echo.https://www.sphinx-doc.org/
23 | exit /b 1
24 | )
25 |
26 | if "%1" == "" goto help
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/source/_static/custom.css:
--------------------------------------------------------------------------------
1 | .wy-side-nav-search, .wy-nav-top {
2 | background: #EECD7A;
3 | }
4 |
5 | .wy-side-nav-search .wy-dropdown > a img.logo, .wy-side-nav-search > a img.logo {
6 | width: 275px;
7 | }
--------------------------------------------------------------------------------
/docs/source/abstract.rst:
--------------------------------------------------------------------------------
1 | EC3 Abstract Class
2 | ==================
3 | The EC3Abstract class contains the base functionality of interacting with the EC3 API.
4 | This class gets inherited and extended upon by by other major classes in the wrapper.
5 |
6 | EC3Abstract
7 | ************
8 |
9 | .. autoclass:: ec3.ec3_api.EC3Abstract
10 | :members:
--------------------------------------------------------------------------------
/docs/source/categories.rst:
--------------------------------------------------------------------------------
1 | Categories Queries
2 | ==================
3 |
4 | The EC3Categories class can be used for retrieving Categories in the EC3 database.
5 |
6 | EC3 Categories represent the categories as EC3 has defined and organized them for their interface.
7 | These categories will not map 1:1 with other systems such as Masterformat.
8 |
9 | Current functionality provides the ability to get the entire category tree as well as categories by id.
10 |
11 |
12 | EC3Categories
13 | *************
14 |
15 | .. autoclass:: ec3.ec3_categories.EC3Categories
16 | :members:
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | sys.path.insert(0, os.path.abspath('../..'))
4 |
5 | from ec3 import __version__ as version
6 |
7 | # -- Project information -----------------------------------------------------
8 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
9 |
10 | project = "EC3 Python Wrapper"
11 | copyright = "2024, Jared Friedman"
12 | author = "Jared Friedman"
13 |
14 | # -- General configuration ---------------------------------------------------
15 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
16 |
17 | extensions = [
18 | "sphinx.ext.duration",
19 | "sphinx.ext.doctest",
20 | "sphinx.ext.autodoc",
21 | "sphinx.ext.autosummary",
22 | "sphinx.ext.intersphinx",
23 | "sphinx.ext.napoleon",
24 | ]
25 |
26 | intersphinx_mapping = {
27 | "python": ("https://docs.python.org/3/", None),
28 | "sphinx": ("https://www.sphinx-doc.org/en/master/", None),
29 | }
30 | intersphinx_disabled_domains = ["std"]
31 |
32 | templates_path = ["_templates"]
33 |
34 | # Auto Create Api Reference
35 | autoapi_dirs = ["../ec3"]
36 | autoapi_add_toctree_entry = True
37 | autoapi_options = [
38 | "members",
39 | "undoc-members",
40 | "private-members",
41 | "show-inheritance",
42 | "show-module-summary",
43 | "special-members",
44 | "imported-members",
45 | ]
46 |
47 | # -- Options for HTML output -------------------------------------------------
48 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
49 |
50 | html_theme = "sphinx_rtd_theme"
51 | html_static_path = ["_static"]
52 | html_logo = "ec3_wrapper_logo.svg"
53 | html_theme_options = {
54 | 'logo_only': True,
55 | 'display_version': False
56 | }
57 |
58 | html_css_files = ["custom.css"]
59 |
60 | # -- Options for EPUB output
61 | epub_show_urls = "footnote"
62 |
63 |
64 | ################################
65 | # CUSTOM
66 | ################################
67 | napoleon_google_docstring = True
68 | napoleon_include_init_with_doc = True
69 | napoleon_attr_annotations = True
70 |
71 |
72 | __version__ = version.split("-", 0)
73 | __release__ = version
74 |
--------------------------------------------------------------------------------
/docs/source/ec3_wrapper_logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
90 |
--------------------------------------------------------------------------------
/docs/source/epds.rst:
--------------------------------------------------------------------------------
1 | EPD Queries
2 | ==================
3 |
4 | The EC3epds class can be used for simplifying queries of EPDs in the EC3 database.
5 |
6 | The primary method currently setup for this class is the 'get_epds' method.
7 | When using this the user should pass a dictionary of parameters and values for querying.
8 |
9 | There are a large number of fields listed in the EC3 documentation that
10 | can be used to query EPDs. Users should refer to that documentation
11 | for the field names and values expected.
12 |
13 | A small number of commonly used fields have been built into the class.
14 | Refer to documentation below to see further details.
15 |
16 | EC3epds
17 | ********
18 |
19 | .. autoclass:: ec3.ec3_epds.EC3epds
20 | :members:
--------------------------------------------------------------------------------
/docs/source/getting-started.rst:
--------------------------------------------------------------------------------
1 | Getting Started
2 | ======================================
3 |
4 | Installation
5 | ************
6 |
7 | To use ec3-python-wrapper, first install it using pip:
8 |
9 | .. code-block:: console
10 |
11 | $ pip install ec3-python-wrapper
12 |
13 |
14 | Authentication
15 | **************
16 |
17 | Usage of the EC3 Python Wrapper assumes you have obtained a bearer token from Building Transparency.
18 | To obtain your token `follow the instructions found here `_
19 |
20 | Your token should be securely stored.
21 | A common way to do this, is to `store it as an environment variable `_,
22 | and load it using ``os.environ``:
23 |
24 | .. code-block:: python
25 |
26 | import os
27 | api_key = os.environ["EC3_KEY"]
28 |
29 |
30 | Quickstart
31 | **********
32 |
33 | The following is a simple example of querying for a list of materials.
34 | Refer to the `Building Transparency API documentation `_ for valid fields to search.
35 |
36 | .. code-block:: python
37 |
38 | >>> import os
39 | >>> from ec3 import EC3Materials
40 | >>> token = os.environ['EC3_KEY']
41 | >>> ec3_materials = EC3Materials(bearer_token=token, ssl_verify=False)
42 | >>> mat_param_dict = {"lightweight":True, "concrete_compressive_strength_at_28d__target":"5000 psi", "jurisdiction":"US"}
43 | >>> ec3_materials.return_fields = ["id", "concrete_compressive_strength_28d", "gwp"]
44 | >>> mat_records = ec3_materials.get_materials(return_all=True, params=mat_param_dict)
45 |
46 |
47 | Examples
48 | **********
49 |
50 | Some Jupyter Notebook examples can be found over in the github repository:
51 |
52 | * Example 1: `Overview Notebook `_
53 | * Example 2: `Material Plotting Notebook `_
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | Welcome to EC3 Python Wrapper's documentation!
2 | ==============================================
3 |
4 | .. container:: .large
5 |
6 | This is a Python wrapper for working with the `Building Transparency EC3 API `_
7 |
8 | Latest Release: |version|
9 |
10 | .. note::
11 |
12 | This project is still in the early stages.
13 |
14 | .. toctree::
15 | :maxdepth: 2
16 | :caption: Getting Started
17 |
18 | getting-started
19 |
20 | .. toctree::
21 | :maxdepth: 1
22 | :caption: Contents
23 |
24 | abstract
25 | materials
26 | epds
27 | projects
28 | categories
29 | utilities
30 |
31 | _______________________________________________
32 |
33 | Contribute
34 | **********
35 | https://github.com/jbf1212/ec3-python-wrapper
36 |
37 | License
38 | *******
39 | `MIT License `_
--------------------------------------------------------------------------------
/docs/source/materials.rst:
--------------------------------------------------------------------------------
1 | Material Queries
2 | ==================
3 | The EC3Materials class is meant to simplify the querying of materials from the EC3 database
4 |
5 | The primary method currently setup for this class is the 'get_materials' method.
6 | When using this the user should pass a dictionary of parameters and values for querying.
7 |
8 | There are a large number of fields listed in the EC3 documentation that
9 | can be used to query materials. Users should refer to that documentation
10 | for the field names and values expected.
11 |
12 | A small number of commonly used fields have been built into the class.
13 | Refer to documentation below to see further details.
14 |
15 | EC3Materials
16 | ************
17 |
18 | .. autoclass:: ec3.ec3_materials.EC3Materials
19 | :members:
--------------------------------------------------------------------------------
/docs/source/projects.rst:
--------------------------------------------------------------------------------
1 | Project Queries
2 | ==================
3 |
4 | The EC3Projects class can be used for retrieving project records in the EC3 database.
5 |
6 | Usage of this class assumes that you have projects setup already in your EC3 account.
7 | The simplest method for returning projects will likely be by using the get_project_by_name method.
8 |
9 | Users should refer to the EC3 documentation for the field names and values expected for projects.
10 |
11 |
12 | EC3Projects
13 | ***********
14 |
15 | .. autoclass:: ec3.ec3_projects.EC3Projects
16 | :members:
--------------------------------------------------------------------------------
/docs/source/utilities.rst:
--------------------------------------------------------------------------------
1 | Utilities
2 | ==================
3 |
4 | Module for utilities that can be used throughout library
5 |
6 | .. automodule:: ec3.ec3_utils
7 | :members:
--------------------------------------------------------------------------------
/ec3/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.0.9"
2 |
3 | from .ec3_materials import EC3Materials
4 | from .ec3_epds import EC3epds
5 | from .ec3_urls import EC3URLs
6 | from .ec3_projects import EC3Projects
7 | from .ec3_categories import EC3Categories
8 |
--------------------------------------------------------------------------------
/ec3/ec3_api.py:
--------------------------------------------------------------------------------
1 | import abc
2 | import requests
3 |
4 |
5 | class EC3Abstract(metaclass=abc.ABCMeta):
6 | """
7 | Represents the abstract class for the Building Transparency Api
8 |
9 | This class get inherited by other major classes.
10 |
11 | :ivar int page_size: Specifies the page size to return. Max allowed by api is 250, defaults to 100
12 | :ivar int max_records: Specifies the maximum number of records to return, defaults to 100
13 | :ivar bool remove_nulls: Keep as True to remove fields with null values. Set to False to return all fields, defaults to True
14 |
15 | """
16 |
17 | def __init__(self, bearer_token, response_format="json", ssl_verify=True):
18 | """
19 | Args:
20 | bearer_token (str): EC3 bearer token for the user
21 | response_format (str, optional): Defaults to "json".
22 | ssl_verify (bool, optional): Defaults to True.
23 | """
24 |
25 | session = requests.Session()
26 | self.bearer_token = bearer_token
27 | self.session = session
28 | self.session.headers.update({"Authorization": "Bearer {}".format(bearer_token)})
29 |
30 | self.page_size = 100
31 | self.max_records = 100
32 |
33 | self.format = response_format
34 | self._ssl_verify = ssl_verify
35 | if not ssl_verify:
36 | # ignore any unresolved references
37 | requests.packages.urllib3.disable_warnings()
38 |
39 | self.remove_nulls = True
40 |
41 | def _process_response(self, response):
42 | """
43 | Processes the response. Removes null values if set to do so.
44 |
45 | Args:
46 | response (requests.models.Response): response from Api
47 |
48 | Returns:
49 | json: Processed records as json
50 | """
51 | try:
52 | response.raise_for_status()
53 | except requests.exceptions.HTTPError as exc:
54 | err_msg = str(exc)
55 |
56 | # Attempt to get Error message from response
57 | try:
58 | error_dict = response.json()
59 | except ValueError:
60 | pass
61 | else:
62 | if "error" in error_dict:
63 | err_msg += " [Error: {}]".format(error_dict["error"])
64 | exc.args = (*exc.args, err_msg)
65 | raise exc
66 | else:
67 | # if user put in anything other than True or False, assume True
68 | if type(self.remove_nulls) != bool:
69 | self.remove_nulls = True
70 |
71 | if self.remove_nulls is True:
72 | ec3_response = response.json()
73 | if isinstance(ec3_response, dict):
74 | cleaned_response = self._remove_nulls(ec3_response)
75 | else:
76 | cleaned_response = [self._remove_nulls(d) for d in ec3_response]
77 | return cleaned_response
78 | else:
79 | return response.json()
80 |
81 | def _request(self, method, url, params=None):
82 | if params:
83 | response = self.session.request(
84 | method, url, verify=self._ssl_verify, params=params["params"]
85 | )
86 | else:
87 | response = self.session.request(method, url, verify=self._ssl_verify)
88 |
89 | return self._process_response(response)
90 |
91 | def _get_records(self, url, **params):
92 | """
93 | Returns the requested number of records.
94 |
95 | This will only return the first (or specified #) page when number of records exceeds page size.
96 | """
97 | data = self._request("get", url, params=params)
98 | requested_records = data
99 | page_number = 1
100 |
101 | while len(data) == self.page_size and len(requested_records) < self.max_records:
102 | page_number += 1
103 | params["params"]["page_number"] = page_number
104 |
105 | try:
106 | data = self._request("get", url, params=params)
107 | except requests.exceptions.HTTPError:
108 | break
109 |
110 | if data:
111 | requested_records.extend(data)
112 |
113 | if len(requested_records) > self.max_records:
114 | return requested_records[0 : self.max_records]
115 | else:
116 | return requested_records
117 |
118 | def _get_all(self, url, **params):
119 | """
120 | Returns all the records as a single list
121 | """
122 | hold_val1 = self.max_records
123 | self.max_records = 10000000 # temp max value
124 |
125 | data = self._get_records(url, **params)
126 | all_records = data
127 | page_number = 1
128 |
129 | while len(data) == self.page_size:
130 | page_number += 1
131 | params["params"]["page_number"] = page_number
132 |
133 | try:
134 | data = self._get_records(url, **params)
135 | except requests.exceptions.HTTPError:
136 | break
137 |
138 | if data:
139 | all_records.extend(data)
140 |
141 | self.max_records = hold_val1
142 |
143 | return all_records
144 |
145 | def _remove_nulls(self, response_dict):
146 | """
147 | Removes key/value pairs where value is None
148 |
149 | Args:
150 | response_dict (dict): Response dictionary
151 |
152 | Returns:
153 | dict: Cleaned version of input dictionary
154 | """
155 | for key, value in list(response_dict.items()):
156 | if value is None:
157 | del response_dict[key]
158 | elif isinstance(value, dict):
159 | self._remove_nulls(value)
160 | return response_dict
161 |
--------------------------------------------------------------------------------
/ec3/ec3_categories.py:
--------------------------------------------------------------------------------
1 | from .ec3_api import EC3Abstract
2 | from .ec3_urls import EC3URLs
3 |
4 |
5 | class EC3Categories(EC3Abstract):
6 | """
7 | Wraps functionality of EC3 Categories
8 |
9 | Usage:
10 | >>> ec3_categories = EC3Categories(bearer_token=token, ssl_verify=False)
11 | >>> ec3_categories.get_all_categories()
12 | """
13 |
14 | def __init__(self, bearer_token, response_format="json", ssl_verify=True):
15 | super().__init__(
16 | bearer_token, response_format=response_format, ssl_verify=ssl_verify
17 | )
18 |
19 | self.url = EC3URLs(response_format=response_format)
20 |
21 | def get_all_categories(self):
22 | """
23 | Gets the entire categories tree
24 |
25 | Returns:
26 | dict: Dictionary of entire category tree
27 | """
28 | return super()._request("get", self.url.categories_tree_url())
29 |
30 | def get_category_by_id(self, category_id):
31 | """
32 | Returns the category from a category id
33 |
34 | Args:
35 | category_id (str): Category ID
36 |
37 | Returns:
38 | dict: Returns a category by ID with the whole sub-categories tree
39 | """
40 | return super()._request(
41 | "get", self.url.categories_id_url().format(category_id=category_id)
42 | )
43 |
--------------------------------------------------------------------------------
/ec3/ec3_epds.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | from .ec3_api import EC3Abstract
4 | from .ec3_urls import EC3URLs
5 | from .ec3_categories import EC3Categories
6 | from .ec3_utils import get_masterformat_category_dict, get_displayname_category_dict
7 |
8 |
9 | class EC3epds(EC3Abstract):
10 | """
11 | Wraps functionality of EC3 EPDs
12 |
13 | :ivar list return_fields: List of the fields you would like returned (EC3 returns everything by default), defaults to []
14 | :ivar str sort_by: Optional name of return field to sort results by, defaults to ""
15 | :ivar bool only_valid: If True will return only EPDs that are currently valid (set to False to also return expired EPDs), defaults to True
16 | :ivar list masterformat_filter: Optional list of Masterformat Category names to filter by (ex: ["03 21 00 Reinforcement Bars"]), defaults to []
17 | :ivar list display_name_filter: Optional list of Display Name Categories to filter by (ex: ["Ready Mix"]), defaults to []
18 |
19 | Usage:
20 | >>> ec3_epds = EC3epds(bearer_token=token, ssl_verify=False)
21 | >>> ec3_epd_list = ec3_epds.get_epds(params=epd_param_dict)
22 | """
23 |
24 | def __init__(self, bearer_token, response_format="json", ssl_verify=True):
25 | super().__init__(
26 | bearer_token, response_format=response_format, ssl_verify=ssl_verify
27 | )
28 |
29 | self.return_fields = []
30 | self.sort_by = ""
31 | self.only_valid = True
32 | self.category_tree = None
33 | self.masterformat_filter = (
34 | []
35 | ) # Currently EC3 requires you to go through category class for this
36 | self.display_name_filter = []
37 |
38 | self.url = EC3URLs(response_format=response_format)
39 |
40 | def _process_params(self, params):
41 | params["params"]["page_size"] = self.page_size
42 |
43 | if self.return_fields:
44 | fields_string = ",".join(self.return_fields)
45 | params["params"]["fields"] = fields_string
46 |
47 | # NOTE "sort_by" is not currently working as expected when passing multiple fields.
48 | # Setting up to expect a single string field temporarily.
49 | if self.sort_by:
50 | params["params"]["sort_by"] = self.sort_by
51 |
52 | if self.only_valid:
53 | params["params"]["date_validity_ends__gt"] = datetime.today().strftime(
54 | "%Y-%m-%d"
55 | )
56 |
57 | if self.masterformat_filter or self.display_name_filter:
58 | ec3_categories = EC3Categories(
59 | bearer_token=self.bearer_token, ssl_verify=False
60 | )
61 | self.category_tree = ec3_categories.get_all_categories()
62 |
63 | if self.masterformat_filter:
64 | masterformat_dict = get_masterformat_category_dict(self.category_tree)
65 |
66 | category_ids = [masterformat_dict[i] for i in self.masterformat_filter]
67 | params["params"]["category"] = category_ids
68 |
69 | if self.display_name_filter:
70 | display_name_dict = get_displayname_category_dict(self.category_tree)
71 |
72 | category_ids = [display_name_dict[i] for i in self.display_name_filter]
73 | category_ids = list(set(category_ids)) # Remove duplicates
74 | params["params"]["category"] = category_ids
75 |
76 | return params
77 |
78 | def get_epds(self, return_all=False, **params):
79 | """
80 | Returns matching EPDs
81 |
82 | Args:
83 | return_all (bool, optional): Set to True to return all matches. Defaults to False, which will return the quantity specified in page_size.
84 |
85 | Returns:
86 | list: List of dictionaries of matching EPD records
87 | """
88 | processed_params = self._process_params(params)
89 |
90 | if return_all:
91 | return super()._get_all(self.url.epds_url(), **processed_params)
92 | else:
93 | return super()._get_records(self.url.epds_url(), **processed_params)
94 |
95 | def get_epd_by_xpduuid(self, epd_xpd_uuid):
96 | """
97 | Returns the epd from an Open xPD UUID
98 |
99 | Args:
100 | epd_xpd_uuid (str): Open xPD UUID (Example: EC300001)
101 | Returns:
102 | dict: Dictionary of the matching EPD record
103 | """
104 | return super()._request(
105 | "get", self.url.epds_xpd_uuid_url().format(epd_xpd_uuid=epd_xpd_uuid)
106 | )
107 |
--------------------------------------------------------------------------------
/ec3/ec3_materials.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | import json
3 |
4 | from .ec3_api import EC3Abstract
5 | from .ec3_urls import EC3URLs
6 | from .ec3_categories import EC3Categories
7 | from .ec3_utils import postal_to_latlong, get_masterformat_category_dict
8 |
9 |
10 | class EC3Materials(EC3Abstract):
11 | """
12 | Wraps functionality of EC3 Materials
13 |
14 | :ivar list return_fields: List of the fields you would like returned (EC3 returns everything by default), defaults to []
15 | :ivar str sort_by: Optional name of return field to sort results by, defaults to ""
16 | :ivar bool only_valid: If True will return only Materials with EPDs that are currently valid (set to False to also return materials with expired EPDs), defaults to True
17 | :ivar list masterformat_filter: Optional list of Masterformat Category names to filter by (ex: ["03 21 00 Reinforcement Bars"]), defaults to []
18 |
19 | Usage:
20 | >>> ec3_materials = EC3Materials(bearer_token=token, ssl_verify=False)
21 | >>> ec3_mat_list = ec3_materials.get_materials(params=mat_param_dict)
22 | """
23 |
24 | def __init__(self, bearer_token, response_format="json", ssl_verify=True):
25 | super().__init__(
26 | bearer_token, response_format=response_format, ssl_verify=ssl_verify
27 | )
28 |
29 | self.return_fields = []
30 | self.sort_by = ""
31 | self.only_valid = True
32 | self.masterformat_filter = (
33 | []
34 | ) # Currently EC3 requires you to go through category class for this
35 |
36 | self.url = EC3URLs(response_format=response_format)
37 |
38 | def _process_params(self, params):
39 | params["params"]["page_size"] = self.page_size
40 |
41 | if self.return_fields:
42 | fields_string = ",".join(self.return_fields)
43 | params["params"]["fields"] = fields_string
44 |
45 | # FIXME seems like this is not return full dictionaries for nested values
46 |
47 | # NOTE "sort_by" is not currently working as expected when passing multiple fields.
48 | # Setting up to expect a single string field temporarily.
49 | if self.sort_by:
50 | params["params"]["sort_by"] = self.sort_by
51 |
52 | if self.masterformat_filter:
53 | ec3_categories = EC3Categories(
54 | bearer_token=self.bearer_token, ssl_verify=False
55 | )
56 | whole_tree = ec3_categories.get_all_categories()
57 | masterformat_dict = get_masterformat_category_dict(whole_tree)
58 |
59 | category_ids = [masterformat_dict[i] for i in self.masterformat_filter]
60 | params["params"]["category"] = category_ids
61 |
62 | return params
63 |
64 | def get_materials(self, return_all=False, **params):
65 | """
66 | Returns matching materials
67 |
68 | Args:
69 | return_all (bool, optional): Set to True to return all matches. Defaults to False, which will return the quantity specified in max_records.
70 |
71 | Returns:
72 | list: List of dictionaries of matching material records
73 | """
74 | if self.only_valid:
75 | params["params"]["epd__date_validity_ends__gt"] = datetime.today().strftime(
76 | "%Y-%m-%d"
77 | )
78 |
79 | processed_params = self._process_params(params)
80 |
81 | if return_all:
82 | return super()._get_all(self.url.materials_url(), **processed_params)
83 | else:
84 | return super()._get_records(self.url.materials_url(), **processed_params)
85 |
86 | def convert_query_to_mf_string(self, category_name, field_dict_list, pragma=None):
87 | """
88 | Converts a dictionary of material search parameters to a pragma string for use in the EC3 API
89 | This function includes a POST request that requires an API key with write access
90 |
91 | Args:
92 | category_name (str): EC3 category name (see https://docs.open-epd-forum.org/en/data-format/materials/ for list of valid category names)
93 | field_dict_list (list): List of dictionaries of search parameters (format: [{"field": "field_name", "op": "operator", "arg": "argument"}])
94 | pragma (list, optional): List of dictionaries of pragma parameters. Defaults to eMF 2.0/1 and TRACI 2.1.
95 |
96 | Returns:
97 | str: string formatted to work with MaterialFilter pragma in EC3 API
98 | """
99 | payload_dict = {}
100 |
101 | if pragma is None:
102 | pragma = [
103 | {"name": "eMF", "args": ["2.0/1"]},
104 | {"name": "lcia", "args": ["TRACI 2.1"]},
105 | ]
106 |
107 | payload_dict["pragma"] = pragma
108 | payload_dict["category"] = category_name
109 | payload_dict["filter"] = field_dict_list
110 |
111 | payload = json.dumps(payload_dict)
112 |
113 | mf_url = (
114 | self.url.materials_convert_matfilter_url()
115 | + "?output=string&output_style=compact"
116 | )
117 |
118 | headers = {
119 | "Content-Type": "application/json",
120 | "Authorization": f"Bearer {self.bearer_token}",
121 | }
122 | response = self.session.request(
123 | "post", mf_url, verify=self._ssl_verify, data=payload, headers=headers
124 | )
125 |
126 | return response
127 |
128 | def get_materials_mf(self, category_name, mf_list, return_all=False, **params):
129 | """
130 | Returns matching materials using filters
131 |
132 | Args:
133 | category_name (str): Open EPD category name (see https://docs.open-epd-forum.org/en/data-format/materials/ for list of valid category names)
134 | mf_list (list): List of dictionaries of search parameters (format: [{"field": "field_name", "op": "operator", "arg": "argument"}])
135 | return_all (bool, optional): Set to True to return all matches. Defaults to False, which will return the quantity specified in max_records.
136 |
137 | Returns:
138 | list: List of dictionaries of matching material records
139 | """
140 | if self.only_valid:
141 | mf_list.append(
142 | {
143 | "field": "epd__date_validity_ends",
144 | "op": "gt",
145 | "arg": datetime.today().strftime("%Y-%m-%d"),
146 | }
147 | )
148 |
149 | mf_response = self.convert_query_to_mf_string(category_name, mf_list)
150 | mf_response_json = mf_response.json()
151 | mf_string = mf_response_json["material_filter_str"]
152 |
153 | params["params"] = {}
154 | params["params"]["mf"] = mf_string
155 |
156 | processed_params = self._process_params(params)
157 |
158 | if return_all:
159 | return super()._get_all(self.url.materials_url(), **processed_params)
160 | else:
161 | return super()._get_records(self.url.materials_url(), **processed_params)
162 |
163 | def get_materials_within_region(
164 | self,
165 | postal_code,
166 | country_code="US",
167 | plant_distance="100 mi",
168 | return_all=False,
169 | **params,
170 | ):
171 | """
172 | Returns only materials from plants within provided distance of postal code.
173 | This adds the "latitude", "longitude", and "plant_distance_lt" keys to your parameter dictionary.
174 |
175 | Args:
176 | postal_code (int): postal code
177 | country_code (str, optional): Two letter country code.. Defaults to 'US'.
178 | plant_distance (str, optional): Distance to plant with units in string ('mi' or 'km'). Defaults to "100 mi".
179 | return_all (bool, optional): Set to True to return all matches. Defaults to False, which will return the quantity specified in max_records.
180 |
181 | Returns:
182 | list: List of dictionaries of matching material records within distance provided from postal code
183 | """
184 | lat, long = postal_to_latlong(postal_code, country_code)
185 | params["params"]["latitude"] = lat
186 | params["params"]["longitude"] = long
187 | params["params"]["plant__distance__lt"] = plant_distance
188 |
189 | return self.get_materials(return_all=return_all, **params)
190 |
191 | def get_materials_within_region_mf(
192 | self,
193 | category_name,
194 | mf_list,
195 | postal_code,
196 | country_code="US",
197 | plant_distance="100 mi",
198 | return_all=False,
199 | **params,
200 | ):
201 | """
202 | Returns only materials from plants within provided distance of postal code.
203 | This adds the "latitude", "longitude", and "plant_distance_lt" keys to your parameter dictionary.
204 |
205 | Args:
206 | category_name (str): Open EPD category name (see https://docs.open-epd-forum.org/en/data-format/materials/ for list of valid category names)
207 | mf_list (list): List of dictionaries of search parameters (format: [{"field": "field_name", "op": "operator", "arg": "argument"}])
208 | postal_code (int): postal code
209 | country_code (str, optional): Two letter country code.. Defaults to 'US'.
210 | plant_distance (str, optional): Distance to plant with units in string ('mi' or 'km'). Defaults to "100 mi".
211 | return_all (bool, optional): Set to True to return all matches. Defaults to False, which will return the quantity specified in max_records.
212 |
213 | Returns:
214 | list: List of dictionaries of matching material records within distance provided from postal code
215 | """
216 | lat, long = postal_to_latlong(postal_code, country_code)
217 |
218 | mf_list.extend(
219 | [
220 | {"field": "latitude", "op": "exact", "arg": lat},
221 | {"field": "longitude", "op": "exact", "arg": long},
222 | {"field": "plant__distance", "op": "lt", "arg": plant_distance},
223 | ]
224 | )
225 |
226 | return self.get_materials_mf(
227 | category_name, mf_list, return_all=return_all, **params
228 | )
229 |
230 | # NOTE Querying materials by "open_xpd_uuid" does not appear to currently work with the api
231 | # def get_material_by_xpduuid(self, epd_xpd_uuid):
232 | # """
233 | # Returns the material from an Open xPD UUID of an EPD
234 |
235 | # Args:
236 | # epd_xpd_uuid (str): Open xPD UUID (Example: EC300001)
237 |
238 | # Returns:
239 | # list: List of dictionaries of matching material records
240 | # """
241 | # return super()._request(
242 | # "get", self.url.materials_xpd_uuid_url().format(epd_xpd_uuid=epd_xpd_uuid)
243 | # )
244 |
--------------------------------------------------------------------------------
/ec3/ec3_projects.py:
--------------------------------------------------------------------------------
1 | from .ec3_api import EC3Abstract
2 | from .ec3_urls import EC3URLs
3 |
4 |
5 | class EC3Projects(EC3Abstract):
6 | """
7 | Wraps functionality of EC3 Projects
8 |
9 | :ivar str sort_by: Optional name of return field to sort results by, defaults to ""
10 |
11 | Usage:
12 | >>> ec3_project_list = EC3Projects(bearer_token=token, ssl_verify=False)
13 | >>> ec3_project_list.get_projects(params=project_param_dict)
14 | """
15 |
16 | def __init__(self, bearer_token, response_format="json", ssl_verify=True):
17 | super().__init__(
18 | bearer_token, response_format=response_format, ssl_verify=ssl_verify
19 | )
20 |
21 | self.sort_by = ""
22 |
23 | self.url = EC3URLs(response_format=response_format)
24 |
25 | def _process_params(self, params):
26 | params["params"]["page_size"] = self.page_size
27 |
28 | # NOTE "sort_by" is not currently working as expected when passing multiple fields.
29 | # Setting up to expect a single string field temporarily.
30 | if self.sort_by:
31 | params["params"]["sort_by"] = self.sort_by
32 |
33 | return params
34 |
35 | def get_projects(self, return_all=False, **params):
36 | """
37 | Returns matching Projects in your EC3 account
38 |
39 | Args:
40 | return_all (bool, optional): Set to True to return all matches. Defaults to False, which will return the quantity specified in page_size.
41 |
42 | Returns:
43 | list: List of dictionaries of matching Project records
44 | """
45 | processed_params = self._process_params(params)
46 |
47 | if return_all:
48 | return super()._get_all(self.url.projects_url(), **processed_params)
49 | else:
50 | return super()._get_records(self.url.projects_url(), **processed_params)
51 |
52 | def get_project_by_id(self, project_id):
53 | """
54 | Returns the project from a project id
55 |
56 | Args:
57 | project_id (str): Entity ID
58 |
59 | Returns:
60 | list: List of dictionaries of matching Project records
61 | """
62 | return super()._request(
63 | "get", self.url.projects_id_url().format(project_id=project_id)
64 | )
65 |
66 | def get_projects_by_name(self, project_name):
67 | """
68 | Returns a list of projects with a name equivalent to the input
69 | If your exact project name is put here you should get a list with one item.
70 |
71 | Args:
72 | project_name (str): Search term for your EC3 project name
73 |
74 | Returns:
75 | list: List of dictionaries of matching Project records
76 | """
77 | return super()._request(
78 | "get", self.url.projects_name_url().format(project_name=project_name)
79 | )
80 |
--------------------------------------------------------------------------------
/ec3/ec3_urls.py:
--------------------------------------------------------------------------------
1 | class EC3URLs:
2 | """
3 | This class is intended to store commonly used EC3 URLs.
4 | Should EC3 URLs change in the future they can be updated here
5 | """
6 |
7 | def __init__(self, response_format="json"):
8 | self.format = response_format
9 |
10 | self.base_url = "https://buildingtransparency.org/api/"
11 |
12 | # materials
13 | self.materials = "materials"
14 | self.material_statistics = "materials/statistics.{format}".format(
15 | format=self.format
16 | )
17 | self.material_statistics_cached = "materials/statistics/cached.{format}".format(
18 | format=self.format
19 | )
20 | self.material_epds_xpd_uuid = "materials?open_xpd_uuid={epd_xpd_uuid}".format(
21 | epd_xpd_uuid="{epd_xpd_uuid}"
22 | )
23 |
24 | self.materials_convert_matfilter = "materials/convert-query".format(
25 | format=self.format
26 | )
27 |
28 | # epds
29 | self.epds = "epds"
30 | self.epds_xpd_uuid = "epds/{epd_xpd_uuid}".format(
31 | format=self.format, epd_xpd_uuid="{epd_xpd_uuid}"
32 | )
33 | self.epds_id = "epds/{id}.{format}".format(format=self.format, id="{id}")
34 |
35 | # projects
36 | self.projects = "projects"
37 | self.projects_id = "projects?id={project_id}".format(
38 | format=self.format, project_id="{project_id}"
39 | )
40 | self.projects_name = "projects?name__like={project_name}".format(
41 | format=self.format, project_name="{project_name}"
42 | )
43 |
44 | # categories
45 | self.categories_tree = "categories/root"
46 | self.categories_id = "categories/{category_id}".format(
47 | format=self.format, category_id="{category_id}"
48 | )
49 |
50 | def base_url(self):
51 | """
52 | Returns the base url
53 | """
54 | return self.base_url
55 |
56 | ### MATERIALS ###
57 | def materials_url(self):
58 | """
59 | Combines the base URL and materials API URL
60 | """
61 | return self.base_url + self.materials
62 |
63 | def material_statistics_url(self):
64 | """
65 | Combines the base URL and materials statistics API URL
66 | """
67 | return self.base_url + self.material_statistics
68 |
69 | def material_statistics_cached_url(self):
70 | """
71 | Combines the base URL and materials statistics cached API URL
72 | """
73 | return self.base_url + self.material_statistics_cached
74 |
75 | def materials_xpd_uuid_url(self):
76 | """
77 | Combines the base URL and epds_xpd_uuid API URL
78 | """
79 | return self.base_url + self.material_epds_xpd_uuid
80 |
81 | def materials_convert_matfilter_url(self):
82 | """
83 | Combines the base URL and materials convert filter API URL
84 | """
85 | return self.base_url + self.materials_convert_matfilter
86 |
87 | ### EPDs ###
88 | def epds_url(self):
89 | """
90 | Combines the base URL and epds API URL
91 | """
92 | return self.base_url + self.epds
93 |
94 | def epds_xpd_uuid_url(self):
95 | """
96 | Combines the base URL and epds_xpd_uuid API URL
97 | """
98 | return self.base_url + self.epds_xpd_uuid
99 |
100 | def epds_id_url(self):
101 | """
102 | Combines the base URL and epds_id API URL
103 | """
104 | return self.base_url + self.epds_id
105 |
106 | def epds_product_classes_url(self):
107 | """
108 | Combines the base URL and epds_id API URL
109 | """
110 | return self.base_url + self.product_classes
111 |
112 | ### PROJECTS ###
113 | def projects_url(self):
114 | """
115 | Combines the base URL and projects API URL
116 | """
117 | return self.base_url + self.projects
118 |
119 | def projects_id_url(self):
120 | """
121 | Combines the base URL and projects ID API URL
122 | """
123 | return self.base_url + self.projects_id
124 |
125 | def projects_name_url(self):
126 | """
127 | Combines the base URL and projects ID API URL
128 | """
129 | return self.base_url + self.projects_name
130 |
131 | ### CATEGORIES ###
132 | def categories_tree_url(self):
133 | """
134 | Combines the base URL and categories url for the whole categories tree
135 | """
136 | return self.base_url + self.categories_tree
137 |
138 | def categories_id_url(self):
139 | """
140 | Combines the base URL and projects ID API URL
141 | """
142 | return self.base_url + self.categories_id
143 |
--------------------------------------------------------------------------------
/ec3/ec3_utils.py:
--------------------------------------------------------------------------------
1 | import pgeocode
2 |
3 |
4 | def postal_to_latlong(postal_code, country_code="US"):
5 | """
6 | Converts postal code to latitude and longitude returned as array.
7 | Refer to pgeocode documentation for supported country codes.
8 | If not found, then coordinates for Null Island are returned (0,0).
9 |
10 | Args:
11 | postal_code (int): postal code
12 | country_code (str, optional): Two letter country code. Defaults to 'US'.
13 |
14 | Returns:
15 | A tuple containing a float (latitude) and a float (longitude)
16 | """
17 | lat = 0
18 | long = 0
19 | nomi = pgeocode.Nominatim(country_code)
20 | if nomi:
21 | df = nomi.query_postal_code(str(postal_code))
22 | lat = df["latitude"].item()
23 | long = df["longitude"].item()
24 | return (lat, long)
25 |
26 |
27 | def recursive_dict_list_return(dict_item, key_name, out_keys, outlist=[]):
28 | """
29 | Recursively loops through a nested json/dictionary based on the key name
30 |
31 | This is intended for where the key_name may occur at multiple levels of nesting.
32 | For example, the "subcategories" key occurs at multiple levels of the EC3 categories tree.
33 | Using this function allows you to return a flattened list of dictionaries with the
34 | desired keys defined in the "out_keys" argument.
35 |
36 | Args:
37 | dict_item (dict): Dictionary with nested data to crawl through
38 | key_name (str): Name of key to crawl through nested dictionary
39 | out_keys (list[str]): List of key names to return
40 | outlist (list, optional): List to return (can be left empty if starting a new list)
41 |
42 | Returns:
43 | list: List of dictionaries with keys provided in out_keys
44 | """
45 | if (key_name in dict_item.keys()) and dict_item[key_name]:
46 | new_dict = {k: dict_item[k] for k in out_keys}
47 | outlist.append(new_dict)
48 |
49 | for d in dict_item[key_name]:
50 | recursive_dict_list_return(d, key_name, out_keys, outlist=outlist)
51 |
52 | elif key_name in dict_item:
53 | new_dict = {k: dict_item[k] for k in out_keys}
54 | outlist.append(new_dict)
55 |
56 | return outlist
57 |
58 |
59 | def get_masterformat_category_dict(category_tree):
60 | """
61 | Get a dictionary with masterformat codes as the keys and ids as the values
62 |
63 | Args:
64 | category_tree (dict): This should be a nested dictionary of all or part of the category tree
65 |
66 | Returns:
67 | dict: Dictionary with masterformat codes as keys (ex: {'03 00 00 Concrete': '484df282d43f4b0e855fad6b351ce006'})
68 | """
69 | category_dict_list = recursive_dict_list_return(
70 | category_tree, "subcategories", ["masterformat", "id"]
71 | )
72 |
73 | masterformat_dict = {
74 | i.get("masterformat"): i.get("id")
75 | for i in category_dict_list
76 | if i.get("masterformat") and i.get("id")
77 | }
78 | return masterformat_dict
79 |
80 |
81 | def get_displayname_category_dict(category_tree):
82 | """
83 | Get a dictionary with display names as the keys and ids as the values
84 |
85 | Args:
86 | category_tree (dict): This should be a nested dictionary of all or part of the category tree
87 |
88 | Returns:
89 | dict: Dictionary with display names as keys (ex: {'Ready Mix': '6991a61b52b24e59b1244fe9dee59e9b'})
90 | """
91 | category_dict_list = recursive_dict_list_return(
92 | category_tree, "subcategories", ["display_name", "id"]
93 | )
94 | display_name_dict = {
95 | i.get("display_name"): i.get("id")
96 | for i in category_dict_list
97 | if i.get("display_name") and i.get("id")
98 | }
99 | return display_name_dict
100 |
--------------------------------------------------------------------------------
/ec3/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbf1212/ec3-python-wrapper/0bd738db18744c8d2cf2df049ef65665db644e52/ec3/py.typed
--------------------------------------------------------------------------------
/ec3_materials_plot.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "metadata": {},
7 | "source": [
8 | "# Example: Query and Plot Materials"
9 | ]
10 | },
11 | {
12 | "attachments": {},
13 | "cell_type": "markdown",
14 | "metadata": {},
15 | "source": [
16 | "### Imports and Token Assignment"
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": 2,
22 | "metadata": {},
23 | "outputs": [],
24 | "source": [
25 | "import os\n",
26 | "import pandas as pd\n",
27 | "import seaborn as sns\n",
28 | "\n",
29 | "token = os.environ['EC3_KEY']"
30 | ]
31 | },
32 | {
33 | "attachments": {},
34 | "cell_type": "markdown",
35 | "metadata": {},
36 | "source": [
37 | "### Import EC3Materials and Setup Query"
38 | ]
39 | },
40 | {
41 | "cell_type": "code",
42 | "execution_count": 3,
43 | "metadata": {},
44 | "outputs": [],
45 | "source": [
46 | "from ec3 import EC3Materials\n",
47 | "ec3_materials = EC3Materials(bearer_token=token, ssl_verify=False)"
48 | ]
49 | },
50 | {
51 | "cell_type": "markdown",
52 | "metadata": {},
53 | "source": [
54 | "### Querying Materials Within Region"
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "execution_count": 6,
60 | "metadata": {},
61 | "outputs": [],
62 | "source": [
63 | "#Conduct a search of normal weights concrete mixes between 2000 psi and 8000 psi within a radius of a given postal code\n",
64 | "mat_filters = [\n",
65 | " {\n",
66 | " \"field\": \"concrete_compressive_strength_at_28d\",\n",
67 | " \"op\": \"gt\",\n",
68 | " \"arg\": \"2000 psi\"\n",
69 | " },\n",
70 | " {\n",
71 | " \"field\": \"concrete_compressive_strength_at_28d\",\n",
72 | " \"op\": \"lt\",\n",
73 | " \"arg\": \"8000 psi\"\n",
74 | " },\n",
75 | " {\n",
76 | " \"field\": \"lightweight\",\n",
77 | " \"op\": \"exact\",\n",
78 | " \"arg\": False\n",
79 | " }\n",
80 | "]\n",
81 | "\n",
82 | "ec3_materials.return_fields = [\"id\", \"concrete_compressive_strength_28d\", \"gwp\"]\n",
83 | "ec3_materials.sort_by = \"concrete_compressive_strength_28d\" #This will sort the responses based on the field assiged to the 'sort_by' property\n",
84 | "ec3_materials.only_valid = True\n",
85 | "postal_code = 11232\n",
86 | "\n",
87 | "#NOTE The following query may take a few minutes to return all responses\n",
88 | "mat_records = ec3_materials.get_materials_within_region_mf(\"ReadyMix\", mat_filters, postal_code, plant_distance=\"10 mi\", return_all=True)\n"
89 | ]
90 | },
91 | {
92 | "attachments": {},
93 | "cell_type": "markdown",
94 | "metadata": {},
95 | "source": [
96 | "### Clean the Data"
97 | ]
98 | },
99 | {
100 | "cell_type": "code",
101 | "execution_count": 8,
102 | "metadata": {},
103 | "outputs": [],
104 | "source": [
105 | "#The following code will convert all the compressive strengths to the same units and round to the nearest 500 psi\n",
106 | "\n",
107 | "mpa_to_psi = 145.03773773 #conversion for megapascal\n",
108 | "\n",
109 | "converted_records = []\n",
110 | "for rec in mat_records:\n",
111 | " new_dict = {}\n",
112 | " split_strength = rec[\"concrete_compressive_strength_28d\"].split()\n",
113 | " if split_strength[1] == \"MPa\":\n",
114 | " conc_strength = float(split_strength[0]) * mpa_to_psi\n",
115 | " elif split_strength[1] == \"psi\":\n",
116 | " conc_strength = float(split_strength[0])\n",
117 | " elif split_strength[1] == \"ksi\":\n",
118 | " conc_strength = float(split_strength[0]) * 1000\n",
119 | " else:\n",
120 | " continue #unknown unit\n",
121 | "\n",
122 | " rounded_strength = int(round(conc_strength/ 500.0) * 500.0)\n",
123 | "\n",
124 | " new_dict[\"Compressive_Strength\"] = rounded_strength\n",
125 | " new_dict[\"GWP\"] = float(rec[\"gwp\"].split()[0])\n",
126 | " converted_records.append(new_dict)"
127 | ]
128 | },
129 | {
130 | "attachments": {},
131 | "cell_type": "markdown",
132 | "metadata": {},
133 | "source": [
134 | "### Create Pandas Dataframe and Plot with Seaborn"
135 | ]
136 | },
137 | {
138 | "cell_type": "code",
139 | "execution_count": 9,
140 | "metadata": {},
141 | "outputs": [
142 | {
143 | "data": {
144 | "text/html": [
145 | "