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.
Files changed (69) hide show
  1. {commodutil-4.3.0 → commodutil-4.3.2}/PKG-INFO +1 -1
  2. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/__init__.py +1 -0
  3. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/__init__.py +2 -0
  4. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/currency.py +45 -0
  5. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/units.py +7 -0
  6. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil.egg-info/PKG-INFO +1 -1
  7. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_price_conv.py +5 -0
  8. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_standards_currency.py +17 -0
  9. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_standards_units.py +19 -3
  10. {commodutil-4.3.0 → commodutil-4.3.2}/.coveragerc +0 -0
  11. {commodutil-4.3.0 → commodutil-4.3.2}/.github/workflows/1_tests.yml +0 -0
  12. {commodutil-4.3.0 → commodutil-4.3.2}/.github/workflows/2_coverage.yml +0 -0
  13. {commodutil-4.3.0 → commodutil-4.3.2}/.github/workflows/3_linting.yml +0 -0
  14. {commodutil-4.3.0 → commodutil-4.3.2}/.github/workflows/4_release.yml +0 -0
  15. {commodutil-4.3.0 → commodutil-4.3.2}/.gitignore +0 -0
  16. {commodutil-4.3.0 → commodutil-4.3.2}/.pypirc +0 -0
  17. {commodutil-4.3.0 → commodutil-4.3.2}/azure-build-pipelines.yml +0 -0
  18. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/arb.py +0 -0
  19. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/convfactors.py +0 -0
  20. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/dates.py +0 -0
  21. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/__init__.py +0 -0
  22. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/calendar.py +0 -0
  23. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/continuous.py +0 -0
  24. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/fly.py +0 -0
  25. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/quarterly.py +0 -0
  26. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/spreads.py +0 -0
  27. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/structure.py +0 -0
  28. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forward/util.py +0 -0
  29. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/forwards.py +0 -0
  30. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/pandasutil.py +0 -0
  31. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/analysis_types.py +0 -0
  32. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/commodities.py +0 -0
  33. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/commodity_groups.py +0 -0
  34. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/standards/regions.py +0 -0
  35. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/stats.py +0 -0
  36. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil/transforms.py +0 -0
  37. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil.egg-info/SOURCES.txt +0 -0
  38. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil.egg-info/dependency_links.txt +0 -0
  39. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil.egg-info/requires.txt +0 -0
  40. {commodutil-4.3.0 → commodutil-4.3.2}/commodutil.egg-info/top_level.txt +0 -0
  41. {commodutil-4.3.0 → commodutil-4.3.2}/pyproject.toml +0 -0
  42. {commodutil-4.3.0 → commodutil-4.3.2}/requirements-test.txt +0 -0
  43. {commodutil-4.3.0 → commodutil-4.3.2}/requirements.txt +0 -0
  44. {commodutil-4.3.0 → commodutil-4.3.2}/requirements_dev.txt +0 -0
  45. {commodutil-4.3.0 → commodutil-4.3.2}/setup.cfg +0 -0
  46. {commodutil-4.3.0 → commodutil-4.3.2}/tests/__init__.py +0 -0
  47. {commodutil-4.3.0 → commodutil-4.3.2}/tests/conftest.py +0 -0
  48. {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/__init__.py +0 -0
  49. {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/conftest.py +0 -0
  50. {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_calendar.py +0 -0
  51. {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_continuous.py +0 -0
  52. {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_fly.py +0 -0
  53. {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_quarterly.py +0 -0
  54. {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_spreads.py +0 -0
  55. {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_structure.py +0 -0
  56. {commodutil-4.3.0 → commodutil-4.3.2}/tests/forward/test_util.py +0 -0
  57. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_arb.py +0 -0
  58. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_cl.csv +0 -0
  59. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_conv.py +0 -0
  60. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_dates.py +0 -0
  61. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_forwards.py +0 -0
  62. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_pandasutils.py +0 -0
  63. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_standards_analysis_types.py +0 -0
  64. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_standards_commodities.py +0 -0
  65. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_standards_commodity_groups.py +0 -0
  66. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_standards_regions.py +0 -0
  67. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_stats.py +0 -0
  68. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_transforms.py +0 -0
  69. {commodutil-4.3.0 → commodutil-4.3.2}/tests/test_weekly.csv +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commodutil
3
- Version: 4.3.0
3
+ Version: 4.3.2
4
4
  Summary: common commodity/oil analytics utils
5
5
  Author-email: aeorxc <author@example.com>
6
6
  Project-URL: Homepage, https://dev.azure.com/RWEST-MFI-TE/Oil/_git/commodutil
@@ -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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commodutil
3
- Version: 4.3.0
3
+ Version: 4.3.2
4
4
  Summary: common commodity/oil analytics utils
5
5
  Author-email: aeorxc <author@example.com>
6
6
  Project-URL: Homepage, https://dev.azure.com/RWEST-MFI-TE/Oil/_git/commodutil
@@ -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 test_unit_map_canonical_set_only_three():
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 set(UNIT_MAP.values()) == {"bbl", "gal", "mt"}
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