Skip to content

Commit 35d0ecb

Browse files
committed
Add check_property_for_process_allocation
1 parent 391173f commit 35d0ecb

File tree

3 files changed

+147
-85
lines changed

3 files changed

+147
-85
lines changed

multifunctional/__init__.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"allocation_before_writing",
55
"allocation_strategies",
66
"check_property_for_allocation",
7+
"check_property_for_process_allocation",
78
"generic_allocation",
89
"list_available_properties",
910
"MaybeMultifunctionalProcess",
@@ -37,8 +38,9 @@
3738
from .utils import allocation_before_writing
3839
from .custom_allocation import (
3940
add_custom_property_allocation_to_project,
40-
list_available_properties,
4141
check_property_for_allocation,
42+
check_property_for_process_allocation,
43+
list_available_properties,
4244
)
4345

4446
DATABASE_BACKEND_MAPPING["multifunctional"] = MultifunctionalDatabase

multifunctional/custom_allocation.py

+102-84
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
from dataclasses import dataclass
44
from enum import Enum
55
from numbers import Number
6-
from typing import List, Union
6+
from typing import List, Union, Optional
77

88
from blinker import signal
99
from bw2data import Database, databases
10-
from bw2data.backends import Exchange
10+
from bw2data.backends import Exchange, Node
1111
from bw2data.project import ProjectDataset, projects
1212

1313
from . import allocation_strategies
@@ -87,111 +87,129 @@ def list_available_properties(database_label: str):
8787
return results
8888

8989

90-
def check_property_for_allocation(
91-
database_label: str, property_label: str
90+
def check_property_for_process_allocation(
91+
process: Node, property_label: str, messages: Optional[List[PropertyMessage]] = None
9292
) -> Union[bool, List[PropertyMessage]]:
9393
"""
94-
Check that the given property is present for all functional edges in `multifunctional`
95-
processes.
94+
Check that the given property is present for all functional edges in a given process.
9695
97-
`database_label`: String label of an existing database.
96+
`process`: Multifunctional process `Node`.
9897
`property_label`: String label of the property to be used for allocation.
9998
10099
If all the needed data is present, returns `True`.
101100
102101
If there is missing data, returns a list of `PropertyMessage` objects.
103102
"""
104-
if database_label not in databases:
105-
raise ValueError(f"Database `{database_label}` not defined in this project")
103+
if messages is None:
104+
messages = []
106105

107-
db = Database(database_label)
108-
messages = []
106+
if process['type'] != "multifunctional":
107+
return True
109108

110-
for ds in filter(lambda x: x["type"] == "multifunctional", db):
111-
for edge in filter(lambda x: x.get("functional"), ds.exchanges()):
112-
properties = _get_unified_properties(edge)
113-
if (
114-
property_label not in properties
115-
and edge.input["type"] != "readonly_process"
116-
):
117-
messages.append(
118-
PropertyMessage(
119-
level=logging.WARNING,
120-
process_id=ds.id,
121-
product_id=edge.input.id,
122-
message_type=MessageType.MISSING_PRODUCT_PROPERTY,
123-
message=f"""Product is missing a property value for `{property_label}`.
124-
Missing values are treated as zeros.
125-
Please define this property for the product:
126-
{edge.input}
127-
Referenced by multifunctional process:
128-
{ds}
109+
for edge in filter(lambda x: x.get("functional"), process.exchanges()):
110+
properties = _get_unified_properties(edge)
111+
if (
112+
property_label not in properties
113+
and edge.input["type"] != "readonly_process"
114+
):
115+
messages.append(
116+
PropertyMessage(
117+
level=logging.WARNING,
118+
process_id=process.id,
119+
product_id=edge.input.id,
120+
message_type=MessageType.MISSING_PRODUCT_PROPERTY,
121+
message=f"""Product is missing a property value for `{property_label}`.
122+
Missing values are treated as zeros.
123+
Please define this property for the product:
124+
{edge.input}
125+
Referenced by multifunctional process:
126+
{process}
129127
130128
""",
131-
)
132129
)
133-
elif property_label not in properties:
134-
messages.append(
135-
PropertyMessage(
136-
level=logging.WARNING,
137-
process_id=ds.id,
138-
product_id=edge.input.id,
139-
message_type=MessageType.MISSING_EDGE_PROPERTY,
140-
message=f"""Functional edge is missing a property value for `{property_label}`.
141-
Missing values are treated as zeros.
142-
Please define this property for the edge:
143-
{edge}
144-
Found in multifunctional process:
145-
{ds}
130+
)
131+
elif property_label not in properties:
132+
messages.append(
133+
PropertyMessage(
134+
level=logging.WARNING,
135+
process_id=process.id,
136+
product_id=edge.input.id,
137+
message_type=MessageType.MISSING_EDGE_PROPERTY,
138+
message=f"""Functional edge is missing a property value for `{property_label}`.
139+
Missing values are treated as zeros.
140+
Please define this property for the edge:
141+
{edge}
142+
Found in multifunctional process:
143+
{process}
146144
147145
""",
148-
)
149146
)
150-
elif (
151-
not isinstance(properties[property_label], Number)
152-
or isinstance(properties[property_label], bool)
153-
and edge.input["type"] != "readonly_process"
154-
):
155-
messages.append(
156-
PropertyMessage(
157-
level=logging.CRITICAL,
158-
process_id=ds.id,
159-
product_id=edge.input.id,
160-
message_type=MessageType.NONNUMERIC_PRODUCT_PROPERTY,
161-
message=f"""Found non-numeric value `{properties[property_label]}` in property `{property_label}`.
162-
Please redefine this property for the product:
163-
{edge.input}
164-
Referenced by multifunctional process:
165-
{ds}
147+
)
148+
elif (
149+
not isinstance(properties[property_label], Number)
150+
or isinstance(properties[property_label], bool)
151+
and edge.input["type"] != "readonly_process"
152+
):
153+
messages.append(
154+
PropertyMessage(
155+
level=logging.CRITICAL,
156+
process_id=process.id,
157+
product_id=edge.input.id,
158+
message_type=MessageType.NONNUMERIC_PRODUCT_PROPERTY,
159+
message=f"""Found non-numeric value `{properties[property_label]}` in property `{property_label}`.
160+
Please redefine this property for the product:
161+
{edge.input}
162+
Referenced by multifunctional process:
163+
{process}
166164
167165
""",
168-
)
169166
)
170-
elif not isinstance(properties[property_label], Number) or isinstance(
171-
properties[property_label], bool
172-
):
173-
messages.append(
174-
PropertyMessage(
175-
level=logging.CRITICAL,
176-
process_id=ds.id,
177-
product_id=edge.input.id,
178-
message_type=MessageType.NONNUMERIC_EDGE_PROPERTY,
179-
message=f"""Found non-numeric value `{properties[property_label]}` in property `{property_label}`.
180-
Please redefine this property for the edge:
181-
{edge}
182-
Found in multifunctional process:
183-
{ds}
167+
)
168+
elif not isinstance(properties[property_label], Number) or isinstance(
169+
properties[property_label], bool
170+
):
171+
messages.append(
172+
PropertyMessage(
173+
level=logging.CRITICAL,
174+
process_id=process.id,
175+
product_id=edge.input.id,
176+
message_type=MessageType.NONNUMERIC_EDGE_PROPERTY,
177+
message=f"""Found non-numeric value `{properties[property_label]}` in property `{property_label}`.
178+
Please redefine this property for the edge:
179+
{edge}
180+
Found in multifunctional process:
181+
{process}
184182
185183
""",
186-
)
187-
)
188-
else:
189-
print(
190-
"No problem with:",
191-
properties,
192-
property_label,
193-
isinstance(properties[property_label], Number),
194184
)
185+
)
186+
187+
return messages or True
188+
189+
190+
def check_property_for_allocation(
191+
database_label: str, property_label: str
192+
) -> Union[bool, List[PropertyMessage]]:
193+
"""
194+
Check that the given property is present for all functional edges in `multifunctional`
195+
processes.
196+
197+
`database_label`: String label of an existing database.
198+
`property_label`: String label of the property to be used for allocation.
199+
200+
If all the needed data is present, returns `True`.
201+
202+
If there is missing data, returns a list of `PropertyMessage` objects.
203+
"""
204+
if database_label not in databases:
205+
raise ValueError(f"Database `{database_label}` not defined in this project")
206+
207+
db = Database(database_label)
208+
messages = []
209+
210+
for ds in filter(lambda x: x["type"] == "multifunctional", db):
211+
check_property_for_process_allocation(ds, property_label, messages)
212+
195213
return messages or True
196214

197215

tests/test_custom_allocation.py

+42
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
add_custom_property_allocation_to_project,
99
allocation_strategies,
1010
check_property_for_allocation,
11+
check_property_for_process_allocation,
1112
list_available_properties,
1213
MultifunctionalDatabase,
1314
)
@@ -74,6 +75,47 @@ def test_check_property_for_allocation_failure(errors):
7475
assert (err.level, err.message_type, err.product_id, err.process_id) in expected
7576

7677

78+
def test_check_process_property_for_allocation_failure(errors):
79+
msg_list = []
80+
check_property_for_process_allocation(get_node(code="1"), "mass", msg_list)
81+
expected = {
82+
(
83+
logging.WARNING,
84+
MessageType.MISSING_PRODUCT_PROPERTY,
85+
get_node(code="a").id,
86+
get_node(code="1").id,
87+
),
88+
(
89+
logging.CRITICAL,
90+
MessageType.NONNUMERIC_PRODUCT_PROPERTY,
91+
get_node(code="b").id,
92+
get_node(code="1").id,
93+
),
94+
(
95+
logging.CRITICAL,
96+
MessageType.NONNUMERIC_EDGE_PROPERTY,
97+
get_node(code="first one here").id,
98+
get_node(code="1").id,
99+
),
100+
(
101+
logging.WARNING,
102+
MessageType.MISSING_EDGE_PROPERTY,
103+
get_node(code="second one here").id,
104+
get_node(code="1").id,
105+
),
106+
}
107+
assert len(msg_list) == 4
108+
for err in msg_list:
109+
assert (err.level, err.message_type, err.product_id, err.process_id) in expected
110+
111+
112+
def test_check_process_property_for_allocation_failure_process_type(errors):
113+
msg_list = []
114+
result = check_property_for_process_allocation(get_node(code="a"), "mass", msg_list)
115+
assert result is True
116+
assert not msg_list
117+
118+
77119
@bw2test
78120
def test_check_property_for_allocation_failure_boolean():
79121
DATA = {

0 commit comments

Comments
 (0)