commodutil 4.3.0__tar.gz → 4.3.2__tar.gz
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.
- {commodutil-4.3.0 → commodutil-4.3.2}/PKG-INFO +1 -1
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/__init__.py +1 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/__init__.py +2 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/currency.py +45 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/units.py +7 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil.egg-info/PKG-INFO +1 -1
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_price_conv.py +5 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_standards_currency.py +17 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_standards_units.py +19 -3
- {commodutil-4.3.0 → commodutil-4.3.2}/.coveragerc +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/.github/workflows/1_tests.yml +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/.github/workflows/2_coverage.yml +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/.github/workflows/3_linting.yml +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/.github/workflows/4_release.yml +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/.gitignore +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/.pypirc +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/azure-build-pipelines.yml +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/arb.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/convfactors.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/dates.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/__init__.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/calendar.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/continuous.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/fly.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/quarterly.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/spreads.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/structure.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/util.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forwards.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/pandasutil.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/analysis_types.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/commodities.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/commodity_groups.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/regions.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/stats.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/transforms.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil.egg-info/SOURCES.txt +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil.egg-info/dependency_links.txt +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil.egg-info/requires.txt +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/commodutil.egg-info/top_level.txt +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/pyproject.toml +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/requirements-test.txt +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/requirements.txt +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/requirements_dev.txt +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/setup.cfg +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/__init__.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/conftest.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/__init__.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/conftest.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_calendar.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_continuous.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_fly.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_quarterly.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_spreads.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_structure.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_util.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_arb.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_cl.csv +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_conv.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_dates.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_forwards.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_pandasutils.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_standards_analysis_types.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_standards_commodities.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_standards_commodity_groups.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_standards_regions.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_stats.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_transforms.py +0 -0
- {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_weekly.csv +0 -0
|
@@ -35,6 +35,7 @@ _LAZY_EXPORTS = {
|
|
|
35
35
|
"VALID_CURRENCY_TOKENS": "commodutil.standards.currency",
|
|
36
36
|
"fractional_to_major": "commodutil.standards.currency",
|
|
37
37
|
"is_fractional_currency": "commodutil.standards.currency",
|
|
38
|
+
"normalize_currency_token": "commodutil.standards.currency",
|
|
38
39
|
"split_currency_unit": "commodutil.standards.currency",
|
|
39
40
|
# standards.units (stdlib-only -- cheap, no pint)
|
|
40
41
|
"canonical_quantity_unit": "commodutil.standards.units",
|
|
@@ -28,6 +28,7 @@ from commodutil.standards.currency import (
|
|
|
28
28
|
VALID_CURRENCY_TOKENS,
|
|
29
29
|
fractional_to_major,
|
|
30
30
|
is_fractional_currency,
|
|
31
|
+
normalize_currency_token,
|
|
31
32
|
required_fx_pair,
|
|
32
33
|
split_currency_unit,
|
|
33
34
|
to_symbol,
|
|
@@ -70,6 +71,7 @@ __all__ = [
|
|
|
70
71
|
"VALID_CURRENCY_TOKENS",
|
|
71
72
|
"fractional_to_major",
|
|
72
73
|
"is_fractional_currency",
|
|
74
|
+
"normalize_currency_token",
|
|
73
75
|
"required_fx_pair",
|
|
74
76
|
"split_currency_unit",
|
|
75
77
|
"to_symbol",
|
|
@@ -80,12 +80,56 @@ _VALID_CURRENCY_TOKENS = {
|
|
|
80
80
|
"$",
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
_CURRENCY_TOKEN_LOOKUP = {token.upper(): token for token in _VALID_CURRENCY_TOKENS}
|
|
84
|
+
_CURRENCY_TOKEN_ALIASES = {
|
|
85
|
+
"USC": "USc",
|
|
86
|
+
"US CENTS": "USc",
|
|
87
|
+
"U S CENTS": "USc",
|
|
88
|
+
"GBX": "GBp",
|
|
89
|
+
"GBPENCE": "GBp",
|
|
90
|
+
"GB PENCE": "GBp",
|
|
91
|
+
"PENCE": "GBp",
|
|
92
|
+
"RMB": "CNY",
|
|
93
|
+
}
|
|
94
|
+
|
|
83
95
|
# Public aliases — these are the import names downstream code should use.
|
|
84
96
|
VALID_CURRENCY_TOKENS = _VALID_CURRENCY_TOKENS
|
|
85
97
|
FRACTIONAL_TO_MAJOR = _FRACTIONAL_TO_MAJOR
|
|
86
98
|
FRACTIONAL_CURRENCY_DIVISORS = _FRACTIONAL_CURRENCY_DIVISORS
|
|
87
99
|
|
|
88
100
|
|
|
101
|
+
def _normalized_currency_lookup_key(token: str) -> str:
|
|
102
|
+
return " ".join(str(token).replace(".", " ").strip().upper().split())
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def normalize_currency_token(token: Optional[str]) -> Optional[str]:
|
|
106
|
+
"""Return the canonical commodutil currency token for common aliases.
|
|
107
|
+
|
|
108
|
+
The canonical tokens remain ``VALID_CURRENCY_TOKENS``. This helper accepts
|
|
109
|
+
mixed case and legacy/fractional aliases used in vendor specs, such as
|
|
110
|
+
``USC``/``US cents`` -> ``USc`` and ``GBX``/``pence`` -> ``GBp``. Unknown
|
|
111
|
+
values return ``None`` so callers can distinguish currency-like tokens
|
|
112
|
+
from physical units.
|
|
113
|
+
"""
|
|
114
|
+
if token is None:
|
|
115
|
+
return None
|
|
116
|
+
cleaned = str(token).strip()
|
|
117
|
+
if not cleaned:
|
|
118
|
+
return None
|
|
119
|
+
if cleaned in _VALID_CURRENCY_TOKENS:
|
|
120
|
+
return cleaned
|
|
121
|
+
|
|
122
|
+
lower = cleaned.lower()
|
|
123
|
+
mapped = CURRENCY_MAP.get(lower)
|
|
124
|
+
if mapped:
|
|
125
|
+
return mapped
|
|
126
|
+
|
|
127
|
+
lookup_key = _normalized_currency_lookup_key(cleaned)
|
|
128
|
+
return _CURRENCY_TOKEN_LOOKUP.get(lookup_key) or _CURRENCY_TOKEN_ALIASES.get(
|
|
129
|
+
lookup_key
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
89
133
|
def is_fractional_currency(token: str) -> bool:
|
|
90
134
|
"""Return True if `token` is a recognised fractional currency
|
|
91
135
|
(e.g. 'GBp', 'USc'). Useful for detecting pure-scale conversions that
|
|
@@ -240,6 +284,7 @@ __all__ = [
|
|
|
240
284
|
"FRACTIONAL_TO_MAJOR",
|
|
241
285
|
"FRACTIONAL_CURRENCY_DIVISORS",
|
|
242
286
|
"CURRENCY_MAP",
|
|
287
|
+
"normalize_currency_token",
|
|
243
288
|
"is_fractional_currency",
|
|
244
289
|
"fractional_to_major",
|
|
245
290
|
"split_currency_unit",
|
|
@@ -50,6 +50,10 @@ UNIT_MAP = {
|
|
|
50
50
|
"mt": "mt",
|
|
51
51
|
"tonne": "mt",
|
|
52
52
|
"tonnes": "mt",
|
|
53
|
+
"pound": "lb",
|
|
54
|
+
"pounds": "lb",
|
|
55
|
+
"lb": "lb",
|
|
56
|
+
"lbs": "lb",
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
|
|
@@ -129,6 +133,7 @@ def to_pint_token(unit: Optional[str]) -> Optional[str]:
|
|
|
129
133
|
/ ``CUBIC_METER`` / ``m3`` (case-insensitive exact match) -> ``m^3``.
|
|
130
134
|
- Rate forms: ``m3/...`` / ``M3/...`` -> ``m^3/...``.
|
|
131
135
|
- Energy casing: ``BTU`` -> ``Btu``; ``MMBTU`` -> ``MMBtu``.
|
|
136
|
+
- Pound casing/plurals: ``LBS`` / ``lbs`` -> ``lb``.
|
|
132
137
|
|
|
133
138
|
Other tokens pass through unchanged. Aliases like ``barrel``, ``tonne``,
|
|
134
139
|
``gallon`` are not handled here -- they are registered as pint aliases
|
|
@@ -159,6 +164,8 @@ def to_pint_token(unit: Optional[str]) -> Optional[str]:
|
|
|
159
164
|
u = "Btu"
|
|
160
165
|
if u == "MMBTU":
|
|
161
166
|
u = "MMBtu"
|
|
167
|
+
if u.upper() == "LBS":
|
|
168
|
+
u = "lb"
|
|
162
169
|
return u
|
|
163
170
|
|
|
164
171
|
|
|
@@ -206,6 +206,11 @@ def test_bug2_usc_to_usd_no_fx_required():
|
|
|
206
206
|
assert out == pytest.approx(0.005, rel=1e-9)
|
|
207
207
|
|
|
208
208
|
|
|
209
|
+
def test_usc_per_pound_to_usd_per_pound_no_fx_required():
|
|
210
|
+
out = convfactors.convert_price(71.26, "USc/LBS", "USD/lb")
|
|
211
|
+
assert out == pytest.approx(0.7126, rel=1e-9)
|
|
212
|
+
|
|
213
|
+
|
|
209
214
|
def test_bug2_gbp_pence_to_gbp_no_fx_required():
|
|
210
215
|
"""Bug 2: GBp -> GBP is a pure /100 scale (no FX)."""
|
|
211
216
|
out = convfactors.convert_price(50.0, "GBp/therm", "GBP/therm")
|
|
@@ -7,6 +7,7 @@ from commodutil.standards.currency import (
|
|
|
7
7
|
VALID_CURRENCY_TOKENS,
|
|
8
8
|
fractional_to_major,
|
|
9
9
|
is_fractional_currency,
|
|
10
|
+
normalize_currency_token,
|
|
10
11
|
required_fx_pair,
|
|
11
12
|
split_currency_unit,
|
|
12
13
|
to_symbol,
|
|
@@ -40,6 +41,21 @@ def test_fractional_to_major():
|
|
|
40
41
|
assert fractional_to_major("") == ""
|
|
41
42
|
|
|
42
43
|
|
|
44
|
+
def test_normalize_currency_token():
|
|
45
|
+
assert normalize_currency_token("USD") == "USD"
|
|
46
|
+
assert normalize_currency_token("usd") == "USD"
|
|
47
|
+
assert normalize_currency_token("$") == "$"
|
|
48
|
+
assert normalize_currency_token("USC") == "USc"
|
|
49
|
+
assert normalize_currency_token("U.S. cents") == "USc"
|
|
50
|
+
assert normalize_currency_token("GBX") == "GBp"
|
|
51
|
+
assert normalize_currency_token("pence") == "GBp"
|
|
52
|
+
assert normalize_currency_token("RMB") == "CNY"
|
|
53
|
+
assert normalize_currency_token("us dollars and cents") == "USD"
|
|
54
|
+
assert normalize_currency_token("bbl") is None
|
|
55
|
+
assert normalize_currency_token("") is None
|
|
56
|
+
assert normalize_currency_token(None) is None
|
|
57
|
+
|
|
58
|
+
|
|
43
59
|
def test_fractional_currency_cac_auc_correctly_routed():
|
|
44
60
|
# Regression guard for the pre-2026-05-14 bug where CAc/AUc were valid
|
|
45
61
|
# currency tokens but had no fractional rule, so required_fx_pair
|
|
@@ -155,6 +171,7 @@ def test_legacy_convfactors_currency_shims_are_gone():
|
|
|
155
171
|
"FRACTIONAL_TO_MAJOR",
|
|
156
172
|
"fractional_to_major",
|
|
157
173
|
"is_fractional_currency",
|
|
174
|
+
"normalize_currency_token",
|
|
158
175
|
"split_currency_unit",
|
|
159
176
|
"_split_currency_unit",
|
|
160
177
|
"_FRACTIONAL_CURRENCY_DIVISORS",
|
|
@@ -66,11 +66,20 @@ def test_unit_map_metric_ton_variants():
|
|
|
66
66
|
assert UNIT_MAP["tonnes"] == "mt"
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
def
|
|
70
|
-
"""UNIT_MAP normalises to exactly the canonical units bbl / gal / mt."""
|
|
69
|
+
def test_unit_map_pound_variants():
|
|
71
70
|
from commodutil.standards.units import UNIT_MAP
|
|
72
71
|
|
|
73
|
-
assert
|
|
72
|
+
assert UNIT_MAP["pound"] == "lb"
|
|
73
|
+
assert UNIT_MAP["pounds"] == "lb"
|
|
74
|
+
assert UNIT_MAP["lb"] == "lb"
|
|
75
|
+
assert UNIT_MAP["lbs"] == "lb"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_unit_map_canonical_set():
|
|
79
|
+
"""UNIT_MAP normalises to the canonical physical quote units."""
|
|
80
|
+
from commodutil.standards.units import UNIT_MAP
|
|
81
|
+
|
|
82
|
+
assert set(UNIT_MAP.values()) == {"bbl", "gal", "mt", "lb"}
|
|
74
83
|
|
|
75
84
|
|
|
76
85
|
def test_canonical_quantity_unit_normalizes_common_labels():
|
|
@@ -120,6 +129,13 @@ def test_to_pint_token_energy_casing():
|
|
|
120
129
|
assert to_pint_token("MMBTU") == "MMBtu"
|
|
121
130
|
|
|
122
131
|
|
|
132
|
+
def test_to_pint_token_pound_casing():
|
|
133
|
+
from commodutil.standards.units import to_pint_token
|
|
134
|
+
|
|
135
|
+
assert to_pint_token("LBS") == "lb"
|
|
136
|
+
assert to_pint_token("lbs") == "lb"
|
|
137
|
+
|
|
138
|
+
|
|
123
139
|
def test_to_pint_token_whitespace_stripped():
|
|
124
140
|
from commodutil.standards.units import to_pint_token
|
|
125
141
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|