ioaudit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ioaudit/__init__.py +23 -0
- ioaudit/_accounting_plan.py +494 -0
- ioaudit/_balance_core.py +393 -0
- ioaudit/_residuals.py +196 -0
- ioaudit/_serialization.py +82 -0
- ioaudit/_version.py +3 -0
- ioaudit/accounting.py +189 -0
- ioaudit/audit.py +209 -0
- ioaudit/coefficients.py +223 -0
- ioaudit/components.py +309 -0
- ioaudit/conventions.py +200 -0
- ioaudit/exceptions.py +13 -0
- ioaudit/file_diagnostics.py +535 -0
- ioaudit/metadata.py +53 -0
- ioaudit/model.py +163 -0
- ioaudit/orientation.py +209 -0
- ioaudit/provenance.py +181 -0
- ioaudit/reference.py +165 -0
- ioaudit/results.py +397 -0
- ioaudit/scale.py +781 -0
- ioaudit/signs.py +143 -0
- ioaudit/stability.py +241 -0
- ioaudit/structure.py +1181 -0
- ioaudit/zero_output.py +126 -0
- ioaudit-0.1.0.dist-info/METADATA +1303 -0
- ioaudit-0.1.0.dist-info/RECORD +29 -0
- ioaudit-0.1.0.dist-info/WHEEL +5 -0
- ioaudit-0.1.0.dist-info/licenses/LICENSE +21 -0
- ioaudit-0.1.0.dist-info/top_level.txt +1 -0
ioaudit/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""ioaudit: transparent diagnostics for input-output tables."""
|
|
2
|
+
|
|
3
|
+
from .audit import audit
|
|
4
|
+
from .conventions import AccountingConvention
|
|
5
|
+
from .exceptions import IOAuditError, IONumericalError, IOValidationError
|
|
6
|
+
from .file_diagnostics import DelimitedFileReport, inspect_csv, inspect_delimited
|
|
7
|
+
from .model import IOSystem, TradeFlows
|
|
8
|
+
from .results import AuditReport
|
|
9
|
+
from ._version import __version__
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"IOSystem",
|
|
13
|
+
"TradeFlows",
|
|
14
|
+
"AccountingConvention",
|
|
15
|
+
"AuditReport",
|
|
16
|
+
"audit",
|
|
17
|
+
"DelimitedFileReport",
|
|
18
|
+
"inspect_csv",
|
|
19
|
+
"inspect_delimited",
|
|
20
|
+
"IOAuditError",
|
|
21
|
+
"IOValidationError",
|
|
22
|
+
"IONumericalError",
|
|
23
|
+
]
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"""Compile declared IO accounting semantics into reusable calculation plans."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from ._balance_core import (
|
|
11
|
+
inflow_adjustment,
|
|
12
|
+
normalize_tolerance,
|
|
13
|
+
outflow_adjustment,
|
|
14
|
+
output_adjustment_vector,
|
|
15
|
+
resolve_input_adjustment,
|
|
16
|
+
trade_side,
|
|
17
|
+
vector,
|
|
18
|
+
)
|
|
19
|
+
from .conventions import AccountingConvention
|
|
20
|
+
from .structure import _all_finite, _core_inputs_are_safe, _shape_of
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class BalanceSidePlan:
|
|
25
|
+
"""One compiled accounting identity."""
|
|
26
|
+
|
|
27
|
+
available: bool = False
|
|
28
|
+
axis: int = 0
|
|
29
|
+
offset: np.ndarray | None = None
|
|
30
|
+
equation: str = ""
|
|
31
|
+
reason: str | None = None
|
|
32
|
+
uses: frozenset[str] = frozenset()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class AccountingPlan:
|
|
37
|
+
"""Validated inputs and equations shared by all residual diagnostics."""
|
|
38
|
+
|
|
39
|
+
y: np.ndarray | None = None
|
|
40
|
+
v: np.ndarray | None = None
|
|
41
|
+
output_adjustment: np.ndarray | None = None
|
|
42
|
+
output: BalanceSidePlan = field(
|
|
43
|
+
default_factory=lambda: BalanceSidePlan(axis=1)
|
|
44
|
+
)
|
|
45
|
+
input: BalanceSidePlan = field(
|
|
46
|
+
default_factory=lambda: BalanceSidePlan(axis=0)
|
|
47
|
+
)
|
|
48
|
+
tolerance: dict[str, float] | None = None
|
|
49
|
+
notes: list[str] = field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _has_component_risk(components: Any, field_name: str) -> bool:
|
|
53
|
+
if components is None:
|
|
54
|
+
return False
|
|
55
|
+
for collection_name in ("double_count_risk", "component_label_risks"):
|
|
56
|
+
for item in getattr(components, collection_name, []) or []:
|
|
57
|
+
if item.get("field") == field_name:
|
|
58
|
+
return True
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _sum_vectors(*values: np.ndarray) -> np.ndarray:
|
|
63
|
+
"""Add accounting offsets while converting overflow into a finite-check result."""
|
|
64
|
+
|
|
65
|
+
with np.errstate(over="ignore", invalid="ignore"):
|
|
66
|
+
result = np.asarray(values[0], dtype=float).copy()
|
|
67
|
+
for value in values[1:]:
|
|
68
|
+
result = result + np.asarray(value, dtype=float)
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _validated_component(
|
|
73
|
+
value: Any,
|
|
74
|
+
*,
|
|
75
|
+
field_name: str,
|
|
76
|
+
n: int,
|
|
77
|
+
sectors: list[Any],
|
|
78
|
+
components: Any,
|
|
79
|
+
) -> tuple[np.ndarray | None, str | None]:
|
|
80
|
+
if _has_component_risk(components, field_name):
|
|
81
|
+
return (
|
|
82
|
+
None,
|
|
83
|
+
f"{field_name} contains an ambiguous subtotal or component label",
|
|
84
|
+
)
|
|
85
|
+
return vector(value, expected=field_name, n=n, sectors=sectors)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
_OUTPUT_TRADE_LABELS = frozenset(
|
|
89
|
+
{
|
|
90
|
+
"imports",
|
|
91
|
+
"international imports",
|
|
92
|
+
"imports of goods and services",
|
|
93
|
+
"interregional inflows",
|
|
94
|
+
"combined inflows",
|
|
95
|
+
"移入",
|
|
96
|
+
"輸入",
|
|
97
|
+
"移輸入",
|
|
98
|
+
"exports",
|
|
99
|
+
"international exports",
|
|
100
|
+
"exports of goods and services",
|
|
101
|
+
"interregional outflows",
|
|
102
|
+
"combined outflows",
|
|
103
|
+
"移出",
|
|
104
|
+
"輸出",
|
|
105
|
+
"移輸出",
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _output_adjustment_component_labels(value: Any) -> list[Any] | None:
|
|
111
|
+
"""Return semantic component labels for a two-dimensional adjustment block."""
|
|
112
|
+
|
|
113
|
+
if value is None:
|
|
114
|
+
return None
|
|
115
|
+
columns = getattr(value, "columns", None)
|
|
116
|
+
if columns is None:
|
|
117
|
+
return None
|
|
118
|
+
labels = list(columns)
|
|
119
|
+
return labels if labels and all(isinstance(label, str) for label in labels) else None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _output_adjustment_trade_conflict(io: Any, value: Any) -> str | None:
|
|
123
|
+
"""Reject ambiguous overlap between output adjustments and trade flows."""
|
|
124
|
+
|
|
125
|
+
trade = getattr(io, "trade", None)
|
|
126
|
+
if trade is None or not trade.has_any or value is None:
|
|
127
|
+
return None
|
|
128
|
+
labels = _output_adjustment_component_labels(value)
|
|
129
|
+
if labels is None:
|
|
130
|
+
return (
|
|
131
|
+
"output_adjustments were supplied with TradeFlows but their "
|
|
132
|
+
"component labels are unavailable; possible trade double-counting "
|
|
133
|
+
"cannot be ruled out"
|
|
134
|
+
)
|
|
135
|
+
from .structure import _normalize_label
|
|
136
|
+
|
|
137
|
+
normalized = {_normalize_label(label) for label in labels}
|
|
138
|
+
overlaps = sorted(normalized & _OUTPUT_TRADE_LABELS)
|
|
139
|
+
if overlaps:
|
|
140
|
+
return (
|
|
141
|
+
"output_adjustments overlap with declared trade component(s): "
|
|
142
|
+
+ ", ".join(overlaps)
|
|
143
|
+
)
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _append_output_adjustment(
|
|
148
|
+
side: BalanceSidePlan,
|
|
149
|
+
adjustment: np.ndarray | None,
|
|
150
|
+
) -> BalanceSidePlan:
|
|
151
|
+
"""Add a validated signed output adjustment to an available identity."""
|
|
152
|
+
|
|
153
|
+
if adjustment is None or not side.available:
|
|
154
|
+
return side
|
|
155
|
+
side.offset = _sum_vectors(side.offset, adjustment)
|
|
156
|
+
side.uses = frozenset((*side.uses, "output_adjustments"))
|
|
157
|
+
side.equation = f"{side.equation} + output_adjustments (signed)"
|
|
158
|
+
return side
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _resolve_output(
|
|
162
|
+
io: Any,
|
|
163
|
+
*,
|
|
164
|
+
y: np.ndarray | None,
|
|
165
|
+
y_reason: str | None,
|
|
166
|
+
convention: AccountingConvention,
|
|
167
|
+
n: int,
|
|
168
|
+
sectors: list[Any],
|
|
169
|
+
plan: AccountingPlan,
|
|
170
|
+
output_adjustment: np.ndarray | None,
|
|
171
|
+
output_adjustment_reason: str | None,
|
|
172
|
+
) -> BalanceSidePlan:
|
|
173
|
+
side = BalanceSidePlan(axis=1)
|
|
174
|
+
if y is None:
|
|
175
|
+
side.reason = y_reason or "Y is unavailable"
|
|
176
|
+
return side
|
|
177
|
+
|
|
178
|
+
output_representation = convention.output_representation
|
|
179
|
+
raw_output_adjustment = getattr(io, "output_adjustments", None)
|
|
180
|
+
if output_representation == "unknown":
|
|
181
|
+
side.reason = "output_representation='unknown'; output-side adjustments are not declared"
|
|
182
|
+
return side
|
|
183
|
+
if output_representation == "complete" and raw_output_adjustment is not None:
|
|
184
|
+
side.reason = (
|
|
185
|
+
output_adjustment_reason
|
|
186
|
+
or "output_adjustments were supplied but output_representation='complete'"
|
|
187
|
+
)
|
|
188
|
+
return side
|
|
189
|
+
if output_representation == "adjustments_required":
|
|
190
|
+
if raw_output_adjustment is None:
|
|
191
|
+
side.reason = "output_adjustments are required but were not supplied"
|
|
192
|
+
return side
|
|
193
|
+
if output_adjustment is None:
|
|
194
|
+
side.reason = output_adjustment_reason or "output_adjustments are unavailable"
|
|
195
|
+
return side
|
|
196
|
+
|
|
197
|
+
representation = convention.trade_representation
|
|
198
|
+
if convention.transaction_scope == "total":
|
|
199
|
+
# ``unknown`` means that no conflicting declaration was made. An
|
|
200
|
+
# explicit separate/outflows-in-Y declaration conflicts with a total
|
|
201
|
+
# transaction table and is therefore kept auditable as SKIPPED.
|
|
202
|
+
if representation in {"separate", "outflows_in_Y"}:
|
|
203
|
+
side.reason = (
|
|
204
|
+
"transaction_scope='total' is incompatible with the declared "
|
|
205
|
+
f"trade_representation={representation!r}"
|
|
206
|
+
)
|
|
207
|
+
side.equation = (
|
|
208
|
+
"SKIPPED because transaction_scope='total' does not "
|
|
209
|
+
"support explicitly declared "
|
|
210
|
+
f"trade_representation={representation!r}"
|
|
211
|
+
)
|
|
212
|
+
return side
|
|
213
|
+
side.available = True
|
|
214
|
+
side.offset = y
|
|
215
|
+
side.equation = "x = row_sum(Z) + row_sum(Y)"
|
|
216
|
+
side.uses = frozenset({"Y"})
|
|
217
|
+
plan.notes.append("inflow_sign not applicable")
|
|
218
|
+
if getattr(io, "trade", None) is not None and io.trade.has_any:
|
|
219
|
+
plan.notes.append(
|
|
220
|
+
"trade flows were supplied but excluded by the total transaction scope"
|
|
221
|
+
)
|
|
222
|
+
return _append_output_adjustment(side, output_adjustment)
|
|
223
|
+
|
|
224
|
+
if convention.transaction_scope == "unknown":
|
|
225
|
+
side.reason = "transaction_scope is unknown"
|
|
226
|
+
return side
|
|
227
|
+
if convention.import_treatment == "unknown":
|
|
228
|
+
side.reason = "import_treatment is unknown"
|
|
229
|
+
return side
|
|
230
|
+
if representation == "unknown":
|
|
231
|
+
side.reason = "trade_representation is unknown"
|
|
232
|
+
return side
|
|
233
|
+
if convention.import_treatment == "none" and representation != "embedded":
|
|
234
|
+
side.reason = (
|
|
235
|
+
"import_treatment='none' is incompatible with the declared "
|
|
236
|
+
f"trade_representation={representation!r}"
|
|
237
|
+
)
|
|
238
|
+
side.equation = (
|
|
239
|
+
"SKIPPED because import_treatment='none' does not support "
|
|
240
|
+
"explicitly declared "
|
|
241
|
+
f"trade_representation={representation!r}"
|
|
242
|
+
)
|
|
243
|
+
return side
|
|
244
|
+
if convention.import_treatment == "none":
|
|
245
|
+
side.available = True
|
|
246
|
+
side.offset = y
|
|
247
|
+
side.equation = "x = row_sum(Z) + row_sum(Y)"
|
|
248
|
+
side.uses = frozenset({"Y"})
|
|
249
|
+
plan.notes.append("inflow_sign not applicable")
|
|
250
|
+
plan.notes.append("outflow_sign not applicable")
|
|
251
|
+
if getattr(io, "trade", None) is not None and io.trade.has_any:
|
|
252
|
+
plan.notes.append(
|
|
253
|
+
"trade flows were supplied but excluded because import_treatment='none'"
|
|
254
|
+
)
|
|
255
|
+
return _append_output_adjustment(side, output_adjustment)
|
|
256
|
+
if representation == "embedded":
|
|
257
|
+
side.available = True
|
|
258
|
+
side.offset = y
|
|
259
|
+
side.equation = "x = row_sum(Z) + row_sum(Y) (trade embedded in Y)"
|
|
260
|
+
side.uses = frozenset({"Y"})
|
|
261
|
+
plan.notes.append("inflow_sign not applicable")
|
|
262
|
+
plan.notes.append("outflow_sign not applicable")
|
|
263
|
+
if getattr(io, "trade", None) is not None and io.trade.has_any:
|
|
264
|
+
plan.notes.append(
|
|
265
|
+
"trade flows were supplied but excluded because trade_representation='embedded'"
|
|
266
|
+
)
|
|
267
|
+
return _append_output_adjustment(side, output_adjustment)
|
|
268
|
+
|
|
269
|
+
trade = getattr(io, "trade", None)
|
|
270
|
+
inflows, reason, inflow_label = trade_side(
|
|
271
|
+
trade,
|
|
272
|
+
side="inflows",
|
|
273
|
+
scope=convention.external_flow_scope,
|
|
274
|
+
n=n,
|
|
275
|
+
sectors=sectors,
|
|
276
|
+
)
|
|
277
|
+
if inflows is None:
|
|
278
|
+
side.reason = reason
|
|
279
|
+
return side
|
|
280
|
+
signed_inflow = inflow_adjustment(inflows, convention.inflow_sign)
|
|
281
|
+
if signed_inflow is None:
|
|
282
|
+
side.reason = "inflow_sign='unknown'; no sign inference is performed"
|
|
283
|
+
return side
|
|
284
|
+
|
|
285
|
+
if representation == "outflows_in_Y":
|
|
286
|
+
side.available = True
|
|
287
|
+
side.offset = _sum_vectors(y, signed_inflow)
|
|
288
|
+
side.uses = frozenset({"Y", "inflows"})
|
|
289
|
+
side.equation = (
|
|
290
|
+
"x = row_sum(Z) + row_sum(Y) + inflow (signed)"
|
|
291
|
+
if convention.inflow_sign == "negative"
|
|
292
|
+
else "x = row_sum(Z) + row_sum(Y) - inflow (positive magnitude)"
|
|
293
|
+
)
|
|
294
|
+
plan.notes.append("outflow_sign not applicable")
|
|
295
|
+
return _append_output_adjustment(side, output_adjustment)
|
|
296
|
+
if representation != "separate":
|
|
297
|
+
side.reason = f"unsupported trade_representation={representation!r}"
|
|
298
|
+
return side
|
|
299
|
+
|
|
300
|
+
outflows, reason, outflow_label = trade_side(
|
|
301
|
+
trade,
|
|
302
|
+
side="outflows",
|
|
303
|
+
scope=convention.external_flow_scope,
|
|
304
|
+
n=n,
|
|
305
|
+
sectors=sectors,
|
|
306
|
+
)
|
|
307
|
+
if outflows is None:
|
|
308
|
+
side.reason = reason
|
|
309
|
+
return side
|
|
310
|
+
signed_outflow = outflow_adjustment(outflows, convention.outflow_sign)
|
|
311
|
+
if signed_outflow is None:
|
|
312
|
+
side.reason = "outflow_sign='unknown'; no sign inference is performed"
|
|
313
|
+
return side
|
|
314
|
+
|
|
315
|
+
side.available = True
|
|
316
|
+
side.offset = _sum_vectors(y, signed_inflow, signed_outflow)
|
|
317
|
+
side.uses = frozenset({"Y", "inflows", "outflows"})
|
|
318
|
+
inflow_term = (
|
|
319
|
+
f"- inflow ({inflow_label}, positive magnitude)"
|
|
320
|
+
if convention.inflow_sign == "positive"
|
|
321
|
+
else f"+ inflow ({inflow_label}, negative signed)"
|
|
322
|
+
)
|
|
323
|
+
outflow_term = (
|
|
324
|
+
f"+ outflow ({outflow_label}, positive magnitude)"
|
|
325
|
+
if convention.outflow_sign == "positive"
|
|
326
|
+
else f"- outflow ({outflow_label}, negative signed)"
|
|
327
|
+
)
|
|
328
|
+
side.equation = (
|
|
329
|
+
f"x = row_sum(Z) + row_sum(Y) {outflow_term} {inflow_term}"
|
|
330
|
+
)
|
|
331
|
+
return _append_output_adjustment(side, output_adjustment)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _resolve_input(
|
|
335
|
+
io: Any,
|
|
336
|
+
*,
|
|
337
|
+
v: np.ndarray | None,
|
|
338
|
+
v_reason: str | None,
|
|
339
|
+
convention: AccountingConvention,
|
|
340
|
+
n: int,
|
|
341
|
+
sectors: list[Any],
|
|
342
|
+
plan: AccountingPlan,
|
|
343
|
+
) -> BalanceSidePlan:
|
|
344
|
+
side = BalanceSidePlan(axis=0)
|
|
345
|
+
if v is None:
|
|
346
|
+
side.reason = v_reason or "V is unavailable"
|
|
347
|
+
return side
|
|
348
|
+
adjustment, reason, _source, note = resolve_input_adjustment(io, n=n)
|
|
349
|
+
if note:
|
|
350
|
+
plan.notes.append(note)
|
|
351
|
+
|
|
352
|
+
representation = convention.input_representation
|
|
353
|
+
if representation == "unknown":
|
|
354
|
+
side.reason = (
|
|
355
|
+
"input_representation='unknown'; the completeness of V and any "
|
|
356
|
+
"input-side adjustments is not declared"
|
|
357
|
+
)
|
|
358
|
+
return side
|
|
359
|
+
if representation == "complete":
|
|
360
|
+
if _source is not None:
|
|
361
|
+
side.reason = (
|
|
362
|
+
"input-side adjustment was supplied but input_representation='complete'; "
|
|
363
|
+
"the adjustment's relationship to V is ambiguous"
|
|
364
|
+
)
|
|
365
|
+
return side
|
|
366
|
+
side.available = True
|
|
367
|
+
side.offset = v
|
|
368
|
+
side.equation = "x = column_sum(Z) + column_sum(V)"
|
|
369
|
+
side.uses = frozenset({"V"})
|
|
370
|
+
return side
|
|
371
|
+
if representation == "adjustments_required":
|
|
372
|
+
if adjustment is None:
|
|
373
|
+
side.reason = reason or "input_adjustments are unavailable"
|
|
374
|
+
return side
|
|
375
|
+
side.available = True
|
|
376
|
+
side.offset = _sum_vectors(v, adjustment)
|
|
377
|
+
side.equation = "x = column_sum(Z) + column_sum(V) + input_adjustment"
|
|
378
|
+
side.uses = frozenset({"V", "input_adjustments"})
|
|
379
|
+
return side
|
|
380
|
+
side.reason = f"unsupported input_representation={representation!r}"
|
|
381
|
+
return side
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def compile_accounting_plan(
|
|
385
|
+
io: Any,
|
|
386
|
+
*,
|
|
387
|
+
z: Any,
|
|
388
|
+
x: np.ndarray | None,
|
|
389
|
+
convention: AccountingConvention | None,
|
|
390
|
+
sectors: list[Any],
|
|
391
|
+
components: Any = None,
|
|
392
|
+
structure: Any = None,
|
|
393
|
+
tolerance: dict[str, float] | None = None,
|
|
394
|
+
) -> AccountingPlan:
|
|
395
|
+
"""Resolve all declared accounting inputs exactly once."""
|
|
396
|
+
|
|
397
|
+
plan = AccountingPlan(
|
|
398
|
+
tolerance=normalize_tolerance(tolerance),
|
|
399
|
+
)
|
|
400
|
+
z_shape = _shape_of(z) if z is not None else ()
|
|
401
|
+
if (
|
|
402
|
+
z is None
|
|
403
|
+
or x is None
|
|
404
|
+
or len(z_shape) != 2
|
|
405
|
+
or z_shape[0] != z_shape[1]
|
|
406
|
+
or getattr(x, "ndim", None) != 1
|
|
407
|
+
or len(x) != z_shape[0]
|
|
408
|
+
or not _all_finite(z)
|
|
409
|
+
or not _all_finite(x)
|
|
410
|
+
or (structure is not None and not _core_inputs_are_safe(structure))
|
|
411
|
+
):
|
|
412
|
+
reason = "Z, x, or their sector labels are not safely aligned"
|
|
413
|
+
plan.output.reason = reason
|
|
414
|
+
plan.input.reason = reason
|
|
415
|
+
return plan
|
|
416
|
+
|
|
417
|
+
n = z_shape[0]
|
|
418
|
+
plan.y, y_reason = _validated_component(
|
|
419
|
+
getattr(io, "Y", None),
|
|
420
|
+
field_name="Y",
|
|
421
|
+
n=n,
|
|
422
|
+
sectors=sectors,
|
|
423
|
+
components=components,
|
|
424
|
+
)
|
|
425
|
+
plan.v, v_reason = _validated_component(
|
|
426
|
+
getattr(io, "V", None),
|
|
427
|
+
field_name="V",
|
|
428
|
+
n=n,
|
|
429
|
+
sectors=sectors,
|
|
430
|
+
components=components,
|
|
431
|
+
)
|
|
432
|
+
output_adjustment_raw = getattr(io, "output_adjustments", None)
|
|
433
|
+
if _has_component_risk(components, "output_adjustments"):
|
|
434
|
+
plan.output_adjustment = None
|
|
435
|
+
output_adjustment_reason = (
|
|
436
|
+
"output_adjustments contains an ambiguous subtotal or component label"
|
|
437
|
+
)
|
|
438
|
+
output_note = None
|
|
439
|
+
else:
|
|
440
|
+
plan.output_adjustment, output_adjustment_reason, output_note = output_adjustment_vector(
|
|
441
|
+
output_adjustment_raw,
|
|
442
|
+
name="output_adjustments",
|
|
443
|
+
n=n,
|
|
444
|
+
sectors=sectors,
|
|
445
|
+
)
|
|
446
|
+
if output_note:
|
|
447
|
+
plan.notes.append(output_note)
|
|
448
|
+
if output_adjustment_raw is not None:
|
|
449
|
+
conflict_reason = _output_adjustment_trade_conflict(
|
|
450
|
+
io, output_adjustment_raw
|
|
451
|
+
)
|
|
452
|
+
if conflict_reason:
|
|
453
|
+
plan.output_adjustment = None
|
|
454
|
+
output_adjustment_reason = conflict_reason
|
|
455
|
+
plan.notes.append(conflict_reason)
|
|
456
|
+
elif plan.output_adjustment is None and output_adjustment_reason:
|
|
457
|
+
plan.notes.append(output_adjustment_reason)
|
|
458
|
+
if y_reason and plan.y is None:
|
|
459
|
+
plan.notes.append(y_reason)
|
|
460
|
+
if v_reason and plan.v is None:
|
|
461
|
+
plan.notes.append(v_reason)
|
|
462
|
+
if convention is None:
|
|
463
|
+
reason = "AccountingConvention was not supplied"
|
|
464
|
+
plan.output.reason = reason
|
|
465
|
+
plan.input.reason = reason
|
|
466
|
+
return plan
|
|
467
|
+
plan.output = _resolve_output(
|
|
468
|
+
io,
|
|
469
|
+
y=plan.y,
|
|
470
|
+
y_reason=y_reason,
|
|
471
|
+
convention=convention,
|
|
472
|
+
n=n,
|
|
473
|
+
sectors=sectors,
|
|
474
|
+
plan=plan,
|
|
475
|
+
output_adjustment=plan.output_adjustment,
|
|
476
|
+
output_adjustment_reason=output_adjustment_reason,
|
|
477
|
+
)
|
|
478
|
+
plan.input = _resolve_input(
|
|
479
|
+
io,
|
|
480
|
+
v=plan.v,
|
|
481
|
+
v_reason=v_reason,
|
|
482
|
+
convention=convention,
|
|
483
|
+
n=n,
|
|
484
|
+
sectors=sectors,
|
|
485
|
+
plan=plan,
|
|
486
|
+
)
|
|
487
|
+
return plan
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
__all__ = [
|
|
491
|
+
"AccountingPlan",
|
|
492
|
+
"BalanceSidePlan",
|
|
493
|
+
"compile_accounting_plan",
|
|
494
|
+
]
|