ucon 0.6.2__tar.gz → 0.6.4__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.
- {ucon-0.6.2 → ucon-0.6.4}/PKG-INFO +1 -1
- {ucon-0.6.2 → ucon-0.6.4}/ROADMAP.md +1 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_unit_parsing.py +65 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon/units.py +17 -3
- {ucon-0.6.2 → ucon-0.6.4}/ucon.egg-info/PKG-INFO +1 -1
- {ucon-0.6.2 → ucon-0.6.4}/.github/workflows/publish.yaml +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/.github/workflows/tests.yaml +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/.gitignore +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/LICENSE +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/Makefile +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/NOTICE +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/README.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/decisions/001-unity-distance-metric-for-nearest-scale.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/decisions/002-composite-units.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/decisions/003-composable-unit-algebra.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/decisions/004-unit-algebra-naming.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/decisions/005-pseudo-dimension-tuple-values.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/decisions/006-pydantic-integration-pattern.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/examples/basis-transform-fantasy-units.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/explainers/exponent-scale-relationship.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/explainers/type-operation-matrix.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/explainers/why-algebraic-closure-matters.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/explainers/why-type-safety-matters.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/proposals/interface-unifying-the-value-layer.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/proposals/project_unified-algebraic-core.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/proposals/support-for-fractional-exponents.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/docs/proposals/unified-unit-presentation.md +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/pyproject.toml +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/requirements.txt +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/setup.cfg +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/setup.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/__init__.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/__init__.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/conversion/__init__.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/conversion/test_graph.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/conversion/test_map.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/mcp/__init__.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/mcp/test_server.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_algebra.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_basis_transform.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_core.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_default_graph_conversions.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_dimensionless_units.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_graph_basis_transform.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_logmap.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_nines.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_pickle.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_pydantic.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_quantity.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_rebased_unit.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_uncertainty.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_unit_system.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_units.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/tests/ucon/test_vector_fraction.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon/__init__.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon/algebra.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon/core.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon/graph.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon/maps.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon/mcp/__init__.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon/mcp/server.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon/pydantic.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon/quantity.py +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon.egg-info/SOURCES.txt +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon.egg-info/dependency_links.txt +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon.egg-info/entry_points.txt +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon.egg-info/requires.txt +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/ucon.egg-info/top_level.txt +0 -0
- {ucon-0.6.2 → ucon-0.6.4}/uv.lock +0 -0
|
@@ -244,6 +244,7 @@ Building on v0.5.x baseline:
|
|
|
244
244
|
- [x] `parse("kg * m / s^2")` → `UnitProduct` (completed in v0.6.0 via `get_unit_by_name()`)
|
|
245
245
|
- [x] Alias resolution (`meters`, `metre`, `m` all work) (completed in v0.6.0)
|
|
246
246
|
- [ ] Uncertainty parsing: `parse("1.234 ± 0.005 m")`
|
|
247
|
+
- [ ] Revisit priority alias architecture (v0.6.x uses `_PRIORITY_ALIASES` / `_PRIORITY_SCALED_ALIASES` for `min`, `mcg`; consider "exact match first" or longest-match strategy if list grows)
|
|
247
248
|
|
|
248
249
|
**Outcomes:**
|
|
249
250
|
- Human-friendly unit input for interactive and configuration use cases
|
|
@@ -329,5 +329,70 @@ class TestPriorityAliases(unittest.TestCase):
|
|
|
329
329
|
self.assertEqual(result, units.inch)
|
|
330
330
|
|
|
331
331
|
|
|
332
|
+
class TestPriorityScaledAliases(unittest.TestCase):
|
|
333
|
+
"""Test priority scaled aliases for domain-specific conventions.
|
|
334
|
+
|
|
335
|
+
Some domains use non-standard abbreviations that include an implicit
|
|
336
|
+
scale, like 'mcg' for microgram in medical contexts.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
def test_mcg_is_microgram(self):
|
|
340
|
+
"""'mcg' should parse as microgram (medical convention)."""
|
|
341
|
+
from ucon.core import Dimension
|
|
342
|
+
result = get_unit_by_name("mcg")
|
|
343
|
+
self.assertIsInstance(result, UnitProduct)
|
|
344
|
+
self.assertEqual(result.dimension, Dimension.mass)
|
|
345
|
+
self.assertAlmostEqual(result.fold_scale(), 1e-6, places=15)
|
|
346
|
+
|
|
347
|
+
def test_mcg_to_mg(self):
|
|
348
|
+
"""Conversion from mcg to mg should work."""
|
|
349
|
+
from ucon.core import Number
|
|
350
|
+
dose = Number(500, unit=get_unit_by_name("mcg"))
|
|
351
|
+
result = dose.to(get_unit_by_name("mg"))
|
|
352
|
+
self.assertAlmostEqual(result.quantity, 0.5, places=9)
|
|
353
|
+
|
|
354
|
+
def test_mcg_to_ug(self):
|
|
355
|
+
"""mcg and µg should be equivalent."""
|
|
356
|
+
from ucon.core import Number
|
|
357
|
+
dose = Number(1, unit=get_unit_by_name("mcg"))
|
|
358
|
+
result = dose.to(get_unit_by_name("µg"))
|
|
359
|
+
self.assertAlmostEqual(result.quantity, 1.0, places=9)
|
|
360
|
+
|
|
361
|
+
def test_mcg_in_composite(self):
|
|
362
|
+
"""'mcg' should work in composite units."""
|
|
363
|
+
result = get_unit_by_name("mcg/mL")
|
|
364
|
+
self.assertIsInstance(result, UnitProduct)
|
|
365
|
+
from ucon.core import Dimension
|
|
366
|
+
self.assertEqual(result.dimension, Dimension.density)
|
|
367
|
+
|
|
368
|
+
def test_mcg_per_kg_per_min(self):
|
|
369
|
+
"""'mcg/kg/min' style dosing units (requires chained division support)."""
|
|
370
|
+
# This tests mcg works; chained division is a separate issue
|
|
371
|
+
result = get_unit_by_name("mcg")
|
|
372
|
+
self.assertIsInstance(result, UnitProduct)
|
|
373
|
+
|
|
374
|
+
def test_cc_is_milliliter(self):
|
|
375
|
+
"""'cc' should parse as milliliter (1 cc = 1 mL)."""
|
|
376
|
+
from ucon.core import Dimension
|
|
377
|
+
result = get_unit_by_name("cc")
|
|
378
|
+
self.assertIsInstance(result, UnitProduct)
|
|
379
|
+
self.assertEqual(result.dimension, Dimension.volume)
|
|
380
|
+
self.assertAlmostEqual(result.fold_scale(), 0.001, places=10)
|
|
381
|
+
|
|
382
|
+
def test_cc_to_mL(self):
|
|
383
|
+
"""Conversion from cc to mL should be 1:1."""
|
|
384
|
+
from ucon.core import Number
|
|
385
|
+
vol = Number(5, unit=get_unit_by_name("cc"))
|
|
386
|
+
result = vol.to(get_unit_by_name("mL"))
|
|
387
|
+
self.assertAlmostEqual(result.quantity, 5.0, places=9)
|
|
388
|
+
|
|
389
|
+
def test_cc_to_L(self):
|
|
390
|
+
"""Conversion from cc to L should work."""
|
|
391
|
+
from ucon.core import Number
|
|
392
|
+
vol = Number(1000, unit=get_unit_by_name("cc"))
|
|
393
|
+
result = vol.to(get_unit_by_name("L"))
|
|
394
|
+
self.assertAlmostEqual(result.quantity, 1.0, places=9)
|
|
395
|
+
|
|
396
|
+
|
|
332
397
|
if __name__ == '__main__':
|
|
333
398
|
unittest.main()
|
|
@@ -208,6 +208,11 @@ _UNIT_REGISTRY_CASE_SENSITIVE: Dict[str, Unit] = {}
|
|
|
208
208
|
# Prevents ambiguous parses like "min" -> milli-inch instead of minute.
|
|
209
209
|
_PRIORITY_ALIASES: set = {'min'}
|
|
210
210
|
|
|
211
|
+
# Priority scaled aliases that map to a specific (unit, scale) tuple.
|
|
212
|
+
# Used for medical conventions like "mcg" -> (gram, Scale.micro).
|
|
213
|
+
# Populated by _build_registry() after units are defined.
|
|
214
|
+
_PRIORITY_SCALED_ALIASES: Dict[str, Tuple[Unit, Scale]] = {}
|
|
215
|
+
|
|
211
216
|
# Scale prefix mapping (shorthand -> Scale)
|
|
212
217
|
# Sorted by length descending for greedy matching
|
|
213
218
|
_SCALE_PREFIXES: Dict[str, Scale] = {
|
|
@@ -260,6 +265,10 @@ def _build_registry() -> None:
|
|
|
260
265
|
_UNIT_REGISTRY[alias.lower()] = obj
|
|
261
266
|
_UNIT_REGISTRY_CASE_SENSITIVE[alias] = obj
|
|
262
267
|
|
|
268
|
+
# Register priority scaled aliases (medical conventions)
|
|
269
|
+
_PRIORITY_SCALED_ALIASES['mcg'] = (gram, Scale.micro) # microgram
|
|
270
|
+
_PRIORITY_SCALED_ALIASES['cc'] = (liter, Scale.milli) # cubic centimeter = 1 mL
|
|
271
|
+
|
|
263
272
|
|
|
264
273
|
def _parse_exponent(s: str) -> Tuple[str, float]:
|
|
265
274
|
"""
|
|
@@ -299,8 +308,8 @@ def _lookup_factor(s: str) -> Tuple[Unit, Scale]:
|
|
|
299
308
|
Look up a single unit factor, handling scale prefixes.
|
|
300
309
|
|
|
301
310
|
Prioritizes prefix+unit interpretation over direct unit lookup,
|
|
302
|
-
except for priority aliases (like 'min') which are checked first
|
|
303
|
-
to avoid ambiguous parses.
|
|
311
|
+
except for priority aliases (like 'min', 'mcg') which are checked first
|
|
312
|
+
to avoid ambiguous parses or to handle domain-specific conventions.
|
|
304
313
|
|
|
305
314
|
This means "kg" returns (gram, Scale.kilo) rather than (kilogram, Scale.one).
|
|
306
315
|
|
|
@@ -311,6 +320,7 @@ def _lookup_factor(s: str) -> Tuple[Unit, Scale]:
|
|
|
311
320
|
- 'kg' -> (gram, Scale.kilo)
|
|
312
321
|
- 'mL' -> (liter, Scale.milli)
|
|
313
322
|
- 'min' -> (minute, Scale.one) # priority alias, not milli-inch
|
|
323
|
+
- 'mcg' -> (gram, Scale.micro) # medical convention for microgram
|
|
314
324
|
|
|
315
325
|
Returns:
|
|
316
326
|
Tuple of (unit, scale).
|
|
@@ -318,7 +328,11 @@ def _lookup_factor(s: str) -> Tuple[Unit, Scale]:
|
|
|
318
328
|
Raises:
|
|
319
329
|
UnknownUnitError: If the unit cannot be resolved.
|
|
320
330
|
"""
|
|
321
|
-
# Check priority aliases first (
|
|
331
|
+
# Check priority scaled aliases first (e.g., "mcg" -> microgram)
|
|
332
|
+
if s in _PRIORITY_SCALED_ALIASES:
|
|
333
|
+
return _PRIORITY_SCALED_ALIASES[s]
|
|
334
|
+
|
|
335
|
+
# Check priority aliases (prevents "min" -> milli-inch)
|
|
322
336
|
if s in _PRIORITY_ALIASES:
|
|
323
337
|
if s in _UNIT_REGISTRY_CASE_SENSITIVE:
|
|
324
338
|
return _UNIT_REGISTRY_CASE_SENSITIVE[s], Scale.one
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ucon-0.6.2 → ucon-0.6.4}/NOTICE
RENAMED
|
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
|