Skip to content

issue#105 add UCUM support, start with arithemitc function support #106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

vfrank66
Copy link
Contributor

No description provided.

}

// ConvertUnit converts a value from one unit to another
func ConvertUnit(fromVal float64, fromUnit, toUnit string) (float64, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we changed this API to be ConvertUnit(fromQuantity result.Quantity, newUnit string) (result.Quantity, error)?

This would enable callers to to do something like
foo, err = ConvertUnit(foo, bar.unit)
which i thing would be slightly better for usability.

@@ -0,0 +1,36 @@
// Copyright 2024 Google LLC
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per the guidance at https://google.github.io/styleguide/go/best-practices.html#util-packages
I would recommend we avoid naming this class helpers, as that doesn't really help the reader know what value this file provides.

I think a better option here would be calling this units.go, the functions this file provides seem to all be related to handling logic related to units. Additionally, doing this would allow us to roll some of the other unit specific logic from ucum.go into this file so ucum.go can provide all the public APIs for this module and units.go can provide logic and constants for units.

WDYT?

}

// FixUnit applies both empty and date unit fixes
func FixUnit(unit string) string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of using the verb normalize or clean here in stread of fix?

package ucum

// FixEmptyUnit handles null/empty units, replacing them with "1" (dimensionless unit)
func FixEmptyUnit(unit string) string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this function as well as FixCQLDateUnit be public, or should the public api of ucum.go just call the fixUnit function at the public boundaries?

// See the License for the specific language governing permissions and
// limitations under the License.

// Package ucum provides UCUM (Unified Code for Units of Measure) support for the CQL engine.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind adding a link to https://unitsofmeasure.org/ucum as well as https://cql.hl7.org/02-authorsguide.html#quantities here? :)

valid, msg := ucum.CheckUnit(unitStr, true, true)
if !valid {
// Just log a warning and continue - don't block parsing
fmt.Printf("Warning: %s\n", msg)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above here.


// CQLToUCUMDateUnits maps CQL date units to their UCUM equivalents
var CQLToUCUMDateUnits = map[string]string{
"years": "a_g",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is right, but i cant remember / find the right docs on some of the specifics here. Can you add comments on why the _g is added?

https://cql.hl7.org/02-authorsguide.html#quantities seems to suggest that "a" is the equivalent here. But I feel like I've also seen this syntax before.

Copy link
Contributor Author

@vfrank66 vfrank66 May 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_g = gregorian
_j = julian
https://github.com/LHNCBC/ucum-lhc/blob/master/data/ucum.csv#L278

This is the npm package used in the javascript cql engine, which is where I started. Since go does not have a ucum package this file was intended to be a minimal replacement for not having the equivalent package.

If would ask about the ucum support vs fhir support vs the cql spec on unit conversion that I do not have any answers to 😖

conversionFunc := ctx.GetChild(0).(antlr.TerminalNode).GetText()
expr := v.VisitExpression(ctx.Expression())

if conversionFunc == "convert" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider reversing this case,
if conversionFunc != "covert" { ... }
that way we can unnest most of this function's code.

@@ -1235,3 +1235,78 @@ func (v *visitor) VisitAggregateExpressionTerm(ctx *cql.AggregateExpressionTermC
return v.badExpression(fmt.Sprintf("unsupported aggregate expression: %s", name), ctx)
}
}

func (v *visitor) VisitConversionExpressionTerm(ctx *cql.ConversionExpressionTermContext) model.IExpression {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a lot of cases in this, can we get some tests that hit this code?

valid, msg := ucum.CheckUnit(unit, true, true)
if !valid {
// Log warning but proceed
fmt.Printf("Warning: %s\n", msg)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the spec https://cql.hl7.org/09-b-cqlreference.html#toquantity the result should be null here.

When adding tests please make sure this case is covered.

@evan-gordon
Copy link
Collaborator

Thanks for taking a crack at this! The UCUM standard is a complicated space, it might take a few rounds of reviews here to get this to a good spot, so this is a great start!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants