Skip to content

Commit

Permalink
Merge pull request #25 from BuildingSync/task/dependency-update
Browse files Browse the repository at this point in the history
Added ruff, updated python compatibility
  • Loading branch information
axelstudios authored Sep 26, 2024
2 parents b57a15a + 64a8cb1 commit 979f6de
Show file tree
Hide file tree
Showing 27 changed files with 2,660 additions and 1,528 deletions.
30 changes: 18 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,22 @@ on:

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
python-version: [ "3.9", "3.12" ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: "3.9"
python-version: ${{ matrix.python-version }}
- name: Install poetry
uses: abatilo/actions-poetry@v2.0.0
uses: abatilo/actions-poetry@v3
with:
poetry-version: 1.4.0
poetry-version: 1.8.3
- name: Setup Python
run: |
pip install --upgrade pip
Expand All @@ -28,19 +34,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: "3.9"
python-version: "3.12"
- name: Run pre-commit
uses: pre-commit/action@v2.0.0
uses: pre-commit/action@v3.0.1
with:
extra_args: --all-files
- name: Install poetry
uses: abatilo/actions-poetry@v2.0.0
uses: abatilo/actions-poetry@v3
with:
poetry-version: 1.4.0
poetry-version: 1.8.3
- name: type check
run: |
poetry install
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Project files
.idea/
.vscode/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
30 changes: 14 additions & 16 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.2.3
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: check-added-large-files
args: ['--maxkb=2000']
args: [ '--maxkb=2000' ]
- id: check-ast
- id: check-json
- id: check-merge-conflict
- id: check-xml
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
# - id: requirements-txt-fixer
- id: flake8
args:
- '--max-line-length=140' # default of Black
- '--per-file-ignores=buildingsync_asset_extractor/lighting_processing/building_space_type_to_lpd.py:E501 buildingsync_asset_extractor/lighting_processing/building_type_to_lpd.py:E501'
- id: mixed-line-ending
- id: pretty-format-json
args: ['--autofix', '--no-sort-keys']
- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.10.1
hooks:
- id: isort
args: ['-m=VERTICAL_HANGING_INDENT'] # vertical hanging
args: [ '--autofix', '--no-sort-keys' ]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.1.1
rev: v1.11.2
hooks:
- id: mypy
args: [ "--install-types", "--non-interactive", "--ignore-missing-imports" ]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.7
hooks:
- id: mypy
args: ["--install-types", "--non-interactive", "--ignore-missing-imports"]
# Run the linter
- id: ruff
args: [ --fix, --exit-non-zero-on-fix, --output-format=full ]
# Run the formatter
- id: ruff-format
136 changes: 88 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ This package processes a BuildingSync file to extract asset information that can
```bash
pip install buildingsync-asset-extractor
```

### Install from source

[Poetry](https://python-poetry.org/) is required to install buildingsync-asset-extractor.

```bash
# Copy repo
git clone https://github.com/BuildingSync/BuildingSync-asset-extractor.git
Expand All @@ -29,26 +32,35 @@ poetry run buildingsync_asset_extractor

BuildingSync version 2.4.0.

The pre-importer will identify assets defined in the `asset_definitions.json` file stored in the `config` directory. There are various methods of calculating assets:
The pre-importer will identify assets defined in the `asset_definitions.json` file stored in the `config` directory.
There are various methods of calculating assets:

1. `sqft`. The sqft method will calculate a 'primary' and 'secondary' value for the asset based on the area it serves. This is calculated from the floor areas defined in each `Section` element. `Conditioned` floor area values will be used if present; `Gross` otherwise.
1. `sqft`. The sqft method will calculate a 'primary' and 'secondary' value for the asset based on the area it serves.
This is calculated from the floor areas defined in each `Section` element. `Conditioned` floor area values will be
used if present; `Gross` otherwise.

1. `num`. The num method will sum up all assets of the specified type and return a single overall number.

1. `avg`. The avg method will return an average value for all assets of the specified type found.

1. `avg_sqft`. The avg_sqft method will return a weighted average value for all assets of the specified type found based on the area they serve.
1. `avg_sqft`. The avg_sqft method will return a weighted average value for all assets of the specified type found based
on the area they serve.

1. `age_oldest`, `age_newest`, `age_average`. The age method will retrieve the 'YearOfManufacture' (or 'YearInstalled' if not present) element of a specified equipment type and return either the oldest or newest, or average age (year) as specified. Average age is calculated by a weighted average using the following (in order): capacity, served space area, regular average.
1. `age_oldest`, `age_newest`, `age_average`. The age method will retrieve the 'YearOfManufacture' (or 'YearInstalled'
if not present) element of a specified equipment type and return either the oldest or newest, or average age (year)
as specified. Average age is calculated by a weighted average using the following (in order): capacity, served space
area, regular average.

1. `custom`. Use this method for particular asset that do not fit in the other categories; i.e. Heating Efficiency. Note that a dedicated method may need to be written to support this type of asset.
1. `custom`. Use this method for particular asset that do not fit in the other categories; i.e. Heating Efficiency. Note
that a dedicated method may need to be written to support this type of asset.

When an asset has a unit associated with it, a separate asset will be generated to store the unit information. That asset will be named the same as the original asset, with ' Units' appended at the end.
When an asset has a unit associated with it, a separate asset will be generated to store the unit information. That
asset will be named the same as the original asset, with ' Units' appended at the end.

To test usage:

```bash
python buildingsync_asset_extractor/main.py
python buildingsync_asset_extractor/main.py
```

This will extract assets from `tests/files/testfile.xml` and save the results to `assets_output.json`
Expand All @@ -58,102 +70,128 @@ There are 2 methods of initializing the Processor: with either a filename or dat
```bash
bp = BSyncProcessor(filename=filename)
```

or

```bash
bp = BSyncProcessor(data=file_data)
```

#### Assumptions

1. Assuming 1 building per file
1. Assuming sqft method uses "Conditioned" floor area for calculations. If not present, uses "Gross"
1. Assuming averages that use served space area must be defined in Sections (LinkedSectionIDs). LinkedBuildingID is not used.
1. Assuming averages that use served space area must be defined in Sections (LinkedSectionIDs). LinkedBuildingID is not
used.

#### TODO
1. thermal zones: when spaces are listed within them with spaces (or multiple thermal zones), this would change the average setpoint calculations. Is this an exception or a normal case to handle?

1. thermal zones: when spaces are listed within them with spaces (or multiple thermal zones), this would change the
average setpoint calculations. Is this an exception or a normal case to handle?

#### Assets Definitions File

This file is used to specify what assets to extract from a BuildingSync XML file. By default, the file found in `config/asset_definitions.json` is used, but a custom file can be specified with the `set_asset_defs_file` method in the `BSyncProcessor` class.
This file is used to specify what assets to extract from a BuildingSync XML file. By default, the file found in
`config/asset_definitions.json` is used, but a custom file can be specified with the `set_asset_defs_file` method in the
`BSyncProcessor` class.

There are currently 5 types of assets that can be extracted:

1. sqft: Sqft assets take into account the floor area served by a specific asset and returns 'Primary' and 'Secondary' values. For example: Primary HVAC System and Secondary HVAC System.
1. sqft: Sqft assets take into account the floor area served by a specific asset and returns 'Primary' and 'Secondary'
values. For example: Primary HVAC System and Secondary HVAC System.

1. avg_sqft: Avg_sqft assets compute a weighted average to get the an average asset value. For example: Average Heating Setpoint.
1. avg_sqft: Avg_sqft assets compute a weighted average to get the an average asset value. For example: Average Heating
Setpoint.

1. num: Num assets count the total number of the specified asset found. For example, Total number of lighting systems.
1. num: Num assets count the total number of the specified asset found. For example, Total number of lighting systems.

1. age_oldest, age_newest, and age_average: These types return the oldest or newest asset, or average age of a specific type. For example: Oldest Boiler.
1. age_oldest, age_newest, and age_average: These types return the oldest or newest asset, or average age of a specific
type. For example: Oldest Boiler.

1. custom: For asset that need particular handling, such as Heating Efficiency. The current assets that have custom methods are:
- Heating System Efficiency
- Cooling System Efficiency
- Lighting System Efficiency
- Water Heater Efficiency
- Heating Fuel Type
1. custom: For asset that need particular handling, such as Heating Efficiency. The current assets that have custom
methods are:
- Heating System Efficiency
- Cooling System Efficiency
- Lighting System Efficiency
- Water Heater Efficiency
- Heating Fuel Type

The schema for the assets definition JSON file is in `schemas/asset_definitions_schema.json`.

#### Extracted Assets File

The schema for the extracted assets JSON file is in `schemas/extracted_assets_schema.json`.

This file lists the extracted assets information in name, value, units triples. Names will match the `export_name` listed in the asset_definitions JSON file, except for assets of type 'sqft', which will be prepended by 'Primary' and 'Secondary'.
This file lists the extracted assets information in name, value, units triples. Names will match the `export_name`
listed in the asset_definitions JSON file, except for assets of type 'sqft', which will be prepended by 'Primary' and '
Secondary'.

### BUILDINGSYNC to CTS Spreadsheet Processor

This functionality takes in a list of buildingsync filepaths, runs the process to extract relevant information, aggregate at the Facility level, and generate an XLSX file in the expected CTS format. This is the `CTS Comprehensive Evaluation Upload Template`.
This functionality takes in a list of buildingsync filepaths, runs the process to extract relevant information,
aggregate at the Facility level, and generate an XLSX file in the expected CTS format. This is the
`CTS Comprehensive Evaluation Upload Template`.

To test usage:

```bash
python buildingsync_asset_extractor/cts_main.py
python buildingsync_asset_extractor/cts_main.py
```

This will process the 2 primary schools XML files (that will be aggregated into 1 facility) and the office XML file (which will be another facility) found in `tests/files/` and generate a XLSX containing 2 facilities. The resulting file will be saved to `tests\output\cts_output.xlsx`.
This will process the 2 primary schools XML files (that will be aggregated into 1 facility) and the office XML file (
which will be another facility) found in `tests/files/` and generate a XLSX containing 2 facilities. The resulting file
will be saved to `tests\output\cts_output.xlsx`.

###
- BuildingSync files are aggregated by facility based on the Facility ID in the file. This ID can be found in the `<Facility>` section, within the `<UserDefinedFields>` subsection:

```
<UserDefinedFields>
<UserDefinedField>
<FieldName>Agency Designated Covered Facility ID</FieldName>
<FieldValue>ABC 123</FieldValue>
</UserDefinedField>
<UserDefinedField>
<FieldName>Sub-agency Acronym</FieldName>
<FieldValue>ABC</FieldValue>
</UserDefinedField>
<UserDefinedField>
<FieldName>Facility Name</FieldName>
<FieldValue>Test Facility</FieldValue>
</UserDefinedField>
</UserDefinedFields>

- BuildingSync files are aggregated by facility based on the Facility ID in the file. This ID can be found in the
`<Facility>` section, within the `<UserDefinedFields>` subsection:

```xml
<UserDefinedFields>
<UserDefinedField>
<FieldName>Agency Designated Covered Facility ID</FieldName>
<FieldValue>ABC 123</FieldValue>
</UserDefinedField>
<UserDefinedField>
<FieldName>Sub-agency Acronym</FieldName>
<FieldValue>ABC</FieldValue>
</UserDefinedField>
<UserDefinedField>
<FieldName>Facility Name</FieldName>
<FieldValue>Test Facility</FieldValue>
</UserDefinedField>
</UserDefinedFields>
```
- The process will extract the measures that are part of the `cheapest` scenario within each file. The measures will be aggregated at the Facility level and the number of measures in each category will be added to the spreadsheet. If there is no cost, no measures will be counted.
- More information about the BuildingSync to CTS mappings can be found in the [cts_map page](buildingsync_asset_extractor/cts/cts_map.md).

- The process will extract the measures that are part of the `cheapest` scenario within each file. The measures will be
aggregated at the Facility level and the number of measures in each category will be added to the spreadsheet. If
there is no cost, no measures will be counted.
- More information about the BuildingSync to CTS mappings can be found in
the [cts_map page](buildingsync_asset_extractor/cts/cts_map.md).

## Developing

### Pre-commit

This project uses `pre-commit <https://pre-commit.com/>`_ to ensure code consistency.
To enable pre-commit on every commit run the following from the command line from within the git checkout of the BuildingSync-asset-extractor
To enable pre-commit on every commit run the following from the command line from within the git checkout of the
BuildingSync-asset-extractor

```bash
pre-commit install
pre-commit install
```

To run pre-commit against the files without calling git commit, then run the following. This is useful when cleaning up the repo before committing.
To run pre-commit against the files without calling git commit, then run the following. This is useful when cleaning up
the repo before committing.

```bash
pre-commit run --all-files
pre-commit run --all-files
```

### Testing

poetry run pytest
poetry run pytest

## Releasing

Expand All @@ -167,7 +205,9 @@ poetry publish -r testpypi
# install from testpypi
pip install --index-url https://test.pypi.org/simple/ buildingsync-asset-extractor
```

If everything looks good, publish to pypi:

```bash
poetry publish
```
Expand Down
5 changes: 3 additions & 2 deletions buildingsync_asset_extractor/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@


def unify_units(
system_datas: list[SystemData], to_units: Optional[str] = None
system_datas: list[SystemData],
to_units: Optional[str] = None,
) -> list[SystemData]:
if to_units is None:
to_units = system_datas[0].cap_units
for sd in system_datas:
if sd.cap is not None:
try:
sd.cap = convert(float(sd.cap), sd.cap_units, to_units) # type: ignore
sd.cap = convert(float(sd.cap), sd.cap_units, to_units) # type: ignore[arg-type, assignment]
sd.cap_units = to_units
except BSyncProcessorError:
pass
Expand Down
2 changes: 1 addition & 1 deletion buildingsync_asset_extractor/cts/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from buildingsync_asset_extractor.cts.cts import building_sync_to_cts # NOQA
from buildingsync_asset_extractor.cts.cts import building_sync_to_cts
8 changes: 3 additions & 5 deletions buildingsync_asset_extractor/cts/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path
from typing import Iterable, Optional

from lxml import etree as ETree
from lxml import etree as ETree # noqa: N812

# Gets or creates a logger
logging.basicConfig()
Expand Down Expand Up @@ -38,10 +38,7 @@ class FacilityAppearance:
@functools.cached_property
def cheapest_package_of_measures_scenario(self) -> Optional[PackageOfMeasuresScenario]:
# get the measures for reference
measures_by_id = {
m.get("ID"): Measure(m)
for m in self.etree.findall("./Measures/Measure", self.etree.nsmap)
}
measures_by_id = {m.get("ID"): Measure(m) for m in self.etree.findall("./Measures/Measure", self.etree.nsmap)}

cheapest_package_of_measures_scenario = None
cheapest_cost = None
Expand Down Expand Up @@ -72,6 +69,7 @@ def __init__(self, etree: ETree, path: Path) -> None:
@dataclass
class Facility:
"""Name of the facility and it's appearances in each file"""

name: str
appearances: list[FacilityAppearance] = field(default_factory=list)

Expand Down
Loading

0 comments on commit 979f6de

Please sign in to comment.