Repository navigation
Feat/issue 167 unit normalizer - #466
Merged
rosspeili merged 9 commits intoNov 5, 2026
Merged
Conversation
rosspeili
requested changes
Nov 3, 2026
rosspeili
left a comment
Contributor
There was a problem hiding this comment.
@rizzoMartin This implementation of a new skill is top tier! Well done <3
Before merge, please address these two small items:
- Rebase on
main: PR #465 and a badge palette chore recently merged, causing a merge conflict inCHANGELOG.mdunder### Added. Please rebase onto currentmainand keep both unreleased entries. - SPDX Headers: Add
# SPDX-License-Identifier: MITto line 1 ofskill.py,units.py,parser.py, andtest_skill.pyto conform with repo licensing conventions. - (Optional but recommended): Add a standalone runnable demo
examples/unit_normalizer_demo.pyshowcasing batch conversion, compound parsing, and table normalization to match the other registry skills.
Once rebased, this is ready to merge. Excellent work on this bundle.
Adds the directive, output card and fixture, catalog page with five-provider usage examples, category and library rows, sitemap, install extras, agent loops row, and changelog entry. Fixes ARPAHLS#167
Rejects non-finite parsed numbers and results, lets an ambiguous source follow an ambiguous target (gal to oz), skips blank and NaN table cells, parses degree-minute-second strings without spaces, and returns an error envelope for non-object params. Refs ARPAHLS#167
rizzoMartin
force-pushed
the
feat/issue-167-unit-normalizer
branch
from
November 3, 2026 14:14
2f8003e to
29012de
Compare
rosspeili
approved these changes
Nov 5, 2026
rosspeili
left a comment
Contributor
There was a problem hiding this comment.
LGTM @rizzoMartin! This is a textbook level design. Thanks <3
Merging as is.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds
data_engineering/unit_normalizerv0.1.0, the from-scratch replacement for #166 agreed in #167.Models handle a single
10 km -> milesconversion fine. They fail in pipelines. A batch of mixed-unit records either burns output tokens or needs one tool call per value.5 ft 11 ingets read as5.11 ft.20 tonsgets converted under a reading chosen silently from three that are up to 12% apart. This skill does the arithmetic locally and deterministically, one call per batch, and records every reading it had to choose.Scope is the approved directive (comment 6285849865): section 3 plus Exciters 1-6. The items I flagged as proposals in my follow-up comment are not included and are documented as v1 limits: Exciter 7
custom_units,textinside batch records,prefix/composedtrace fields, and unit products such asoz-in. They can follow in separate PRs if approved.Acceptance criteria mapping
normalizeas a batch of oneskill.pyexecute():normalizebuilds a one-record list and shares_convert_record. Test:test_normalize_scalarnormalize_batch, non-blocking errors with recordidskill.py_convert_record. Tests:test_batch_issue_example,test_batch_partial_failureparse_quantity: compound, fractions, ratesparser.py. Tests:TestParser,test_parse_quantity_action{factor, offset}units.pytable andconvert(). Test:test_temperature_offsetsvalueplusdisplay_value, default 6 dpskill.py_display,precision0-12. Test:test_precisiondimension_mismatchwithout crashingskill.py_check_dimensions. Test:test_dimension_mismatchcanonicalize/ omittedto_unittest_canonicalizerisk_varianceunits.pyAMBIGUOUS,validate_assume. Tests:TestAmbiguity,test_ambiguous_row_is_warning_not_readynormalize_table, row and columnartest_table_rows,test_table_columnarcompact: truetest_compact_bounds_output,test_compact_table_omits_tablerequirements: [], stdlib onlytelemetry_normalizerinstructions.md"When not to invoke", catalog page, category hub pipeline linemanifest.yaml. Test:test_manifest_metadatadocs/skills/data_engineering/unit_normalizer.md, both catalog rows, sitemap,CHANGELOG.mdexamples/unit_normalizer_demo.py, registered inexamples/README.md, the agent loops matrix andtests/test_examples_smoke.pyDesign notes for reviewers
degnever validates against anything that is not an angle.rpmanddpscompose as angle per time and canonicalize torad/s(REP 103).m, g, s, L, Pa, J, W, N, bar), applied only after an exact alias lookup misses. Sominis a minute,MPaandmPastay 10^9 apart, andMMisunknown_unit. Case-insensitive lookup applies only to long names (Kilometers,Celsius).20 C 5 Fisunparseable, not a number. A fraction with a decimal denominator (1/2.3") is refused, because it is an optical sensor-format designation, not a length.8 oz -> mLreads fluid ounces, ces, and45° 30'reads arcminutes.assumecome back as cleanreadyrows with no assumption fields, as agreed in the thread.target: [software, hardware]follows the spec. As a resultSkillContextemitsSkillwareHardwareSafetyre_execution=True, even though the skill never actuates anything. This is documented on the catalog page.770fd3f, the first branch commit after the rebase onto current main, the same way # feat(networking): add geo_ip_rotator skill and networking category (#437) #464 did. The SHA will change again on squash and can be reconciled then.skill.py,units.py,parser.py,test_skill.py,__in-License-Identifier: MIT, matchingtelemetry_normalizer.@rin-katsuragi, the hardware-facing pieces are angle handling,
rpm/dps, prefixes, and the'/"arcminute/pinned inTestUnits,TestAmbiguityandtest_dms_without_spaces. The offer to pin your real datasheetstrings as fixtures still stands.Type of Change
skills/skills/skillware/core/loader, env, adaptersskillware/cli.py,docs/usage/cli.mdexamples/*.py, agent loops,examples/README.mdpyproject.toml,MANIFEST.inChecklist (all PRs)
Fixes #…orRefs #…)python -m black --check .,flake8, andruff check .pass locally (or CI-equivalent subset).black -;ruff` is not installed locally, so I am leaving that to CI.pytest skills/andpytest tests/pass locally when relevant. All 52 bundle tests, the new demo smoke test, and the registry, doc, card, issuer, identity, manifest and extras guards pass. The full suite has 21 local failures (async tests, thebio demo,
max_skills_cap), and the same 21 fail onmainin my environment: the venv is missing `pytest-asyncCHANGELOG.mdupdated under[Unreleased]when user-visible behavior changesexamples/README.mdupdated if this PR adds, renames, or removes a runnable scriptpytest tests/test_registry_docs.pywhen skills, examples index, or agent-loops matrix changedpytest tests/test_skill_docs.pywhen catalog Usage Examples or provider snippets changedNew or updated skill
Bundle and metadata
skills/<category>/<skill_name>/(fromtemplates/python_skill/or equivalent)manifest.yaml:name(full ID),version,description,parameters,constitution, realissuershort_description,issuer.github,issuer.org,requirements,env_varsEffect, Directive, Assurance
skill.py(Effect; no ad-hoc LLM-generated execution paths)instructions.md(Directive) explains when and how to use the skillcard.json(Presentation) issuer matches manifest when presenttest_skill.py(Assurance) covers execution and schema expectationsSkillLoader.load_skill("<category>/<skill_name>")succeeds (or deps documented)Documentation and catalog
docs/skills/<category>/<skill_name>.md, category hub row, row indocs/skills/README.md, and `docs/siteConstitution and safety (skills only)
assumed,assumption,alternatives the caller locked the reading withassume`.sults are rejected, andexecute()` returns an envelope for any input instead of raising.Related Issues
Fixes #167