Skip to content
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

add Unit read API for loading by symbol rather than fully-qualified record UUID #387

Open
wants to merge 4 commits into
base: sprout
Choose a base branch
from

Conversation

pospi
Copy link
Member

@pospi pospi commented Feb 10, 2023

I'm not sure how you might feel about this addition; but it seems like a necessary bit of ergonomics for hREA-based apps.

I want to be able to declare this as a data dependency in my view component(s):

  query {
    hours: unit(id: "hours") {
      id
      label
      symbol
    }
    minutes: unit(id: "minute") {
      id
      label
      symbol
    }
    seconds: unit(id: "second") {
      id
      label
      symbol
    }
  }

The key parameter is the id in each query. The only way of interacting currently is to pass the values directly; i.e. the universally-unique combination of (DnaHash, String), which necessitates query variables:

  units: ApolloQueryController<CoreUnitsCheckResponse> = new ApolloQueryController(this, HasCoreUnits, {
    variables: {
      hours: `${dnaHashB64}:hours`,
      minutes: `${dnaHashB64}:minutes`,
      seconds: `${dnaHashB64}:seconds`,
    }
  })

Where would dnaHashB64 come from? This seems like an implementation detail of the Valueflows network, which client applications should not have to interact with at all. So, we should be able to refer to units by well-known strings for simplified retrieval. I think with this addition to the top-level query we can take the wrapped UnitIds from this request and link to other records as needed in mutations.

@pospi pospi added the enhancement New feature or request label Feb 10, 2023
@pospi pospi self-assigned this Feb 10, 2023
@fosterlynn
Copy link

I don't think VF should care, seems to me that retrieval by whatever is cleanest should be done, and really whatever people prefer. And noting that id's will be different things in different technical ecosystems, although there should be a unique URL id for each unit in OM2.

I don't think all the units in OM2 have symbols, you'll probably have to substitute whatever seems best.

@pospi
Copy link
Member Author

pospi commented Feb 12, 2023

Hrmm, there may be some additions or revisions needed for the GraphQL Unit API here.

Problem 1: equivalence

There currently cannot be any guarantee of equivalence or availability of different Units in different networks, since they are just records. This seems advisable / unavoidable since every collaboration space has different needs and (for example) a coffee shop baking cakes probably will never need to measure light-seconds or megatons.

However this does also mean that we cannot know that an 'hours' Unit created in one network is actually the same unit of measure as 'hours' created elsewhere.

Problem 2: grammar standardisation

there should be a unique URL id for each unit in OM2

This is currently the case per network but presents an issue with associating units back to OM. To avoid falsely indicating equivalence between units and to enable referential integrity checks against the zome, unit IDs are returned as universally-unique strings which include the DnaHash. In this way, an 'hours' created in one space is automatically different to an 'hours' created elsewhere, and the correct associations must be made by the application when storing other referencing records. The IDs themselves are meaningless.

I was going to currently specify associations to OM2 by Unit.label, for example { label: "http://www.ontology-of-units-of-measure.org/resource/om-2/hour", symbol: "h" }. But at best that seems like an awkward workaround.

By providing such associations to external vocabs we would be able to solve equivalence since two different 'hour's can be said to be equivalent if they both correspond to http://www.ontology-of-units-of-measure.org/resource/om-2/hour.

Question 1: how to associate units with OM2?

Should we have a better way of making this association? Some field on Unit that behaves like classifiedAs in other record types?

Question 2: how to load selections of available units in applications?

App developers are likely to want to be able to perform logic such as, "Does the 'hours' Unit exist? If not, create it."

This PR was an attempt to facilitate that by allowing units to be queried directly by symbol (hopefully the unique part of a unit) for retrieval, without modifying the GraphQL spec. However maybe it's not the best approach.

Better may be adding filter parameters to the root-level units() query, where filters may include symbol. "Does the 'hours' Unit exist?" would then be achievable via query { units(filter: { symbol: ['hours'] }) { ... } }.

Further work may involve integration of the OM ontology more directly into applications. For example, we might want to deny creating custom units with any OM symbol assigned; which would be a way of ensuring that any 'hours' in any network corresponds to http://www.ontology-of-units-of-measure.org/resource/om-2/hour.

Either this kind of validation, or we could add a filter parameter for classifiedAs when implemented, which enables more exacting queries. "Does the 'hours' Unit exist?" would become "Does the 'hours' Unit from the OM ontology exist?".

@fosterlynn
Copy link

Question 1: how to associate units with OM2?

I wonder if we (meaning you of course :) in the graphql ) could add a field to Unit just for the OM2 url? Use label for the actual short label. Maybe also VF itself could put a recommendation for that into one of those kinds of sections, especially once it is tried.

Question 2: how to load selections of available units in applications?

This is a hard one. And yes, different networks will want different sets of units. I have always pictured some kind of utility for populating OM2 units into a DNA/DHT, where any network who needs units for whatever reason could pick out what they want from some big query-able list. What you are suggesting also makes sense because they will run into needing new ones as they work.

Just making sure you know about these references:

@pospi
Copy link
Member Author

pospi commented Feb 13, 2023

Thanks for that second link, it was worth taking the time to look through the exported definitions. Not all units having a symbol requires some dealing with, since the symbol is currently configured as the element's (unique) index.

What do you think of this, as an example of inserting core time-based units?

const timeUnits = {
  hours: {
    label: 'hour',
    symbol: 'h',
    description: 'The hour is a unit of time defined as 3600 second.',
    classifiedAs: ['http://www.ontology-of-units-of-measure.org/resource/om-2/hour'],
  },
  minutes: {
    label: 'minute',
    symbol: 'min',
    description: 'The minute (time) is a unit of time defined as 60 second.',
    classifiedAs: ['http://www.ontology-of-units-of-measure.org/resource/om-2/minute-Time'],
  },
  seconds: {
    label: 'second',
    symbol: 's',
    description: 'The second is a unit of time defined as the duration of 9 192 631 770 periods of the radiation corresponding to the transition between the two hyperfine levels of the ground state of the cesium 133 atom.',
    classifiedAs: ['http://www.ontology-of-units-of-measure.org/resource/om-2/second-Time'],
  },
}

I think the best thing to do in terms of indexing might actually be to re-key on label since that fields appears to be unique for every unit.

@pospi
Copy link
Member Author

pospi commented Feb 13, 2023

Could also consider ommitting description, changing classifiedAs: [URI!] to definedAs: URI (or whatever other core semantic meaning for this, there is probably something in the rdf vocab itself). But the point is from 1:many to 1:1 and we would setup a unique index on that field; whilst allowing both label and symbol to be non-unique.

@adaburrows
Copy link
Member

I want to point out that there's a lot more to the OM2 model than any of us have referenced in the code (and more than I've actually seen documented anywhere).

Each unit has a well known key as its ID such as om:kilometrePerHour. That means this value forms a natural key for the unit being used.

Each unit has a mapping to the UCUM ontology defined in https://github.com/HajoRijgersberg/OM/blob/master/om-2-ucum.ttl.

There is the first most straightforward hierarchy of 161 objects that subclass om:Unit:

One possible traversal down this chain of subclassing looks like:

Mathematical relationships can be defined between om:SingularUnits. For instance, om:degree has a reference definition of 1.745329e-2 radians:

    <!-- hasDefinition --> <om:hasFactor rdf:datatype="&xsd;float">1.745329e-2</om:hasFactor>
    <!-- hasDefinition --> <om:hasUnit rdf:resource="&om;radian"/>
    <om:hasDimension rdf:resource="&om;dimensionOne"/>

The spec defines a hierarchy of prefixes:

  • om:Prefix
    • om:SIPrefix
      • defines SI prefixes from yocto (10^-24) to yotta (10^24) along with their om:hasFactor values.

There is enough information to use a symbolic system to perform dimensional analysis and convert between units based on the dimensions of each measurement used in a calculation in order to normalize the results.

This does exempt the units starting at om:barFood through om:wineGlass. These units have no conversion factors or relationships to any other unit. However, if the particular network wanted to have specific conversion factors for these sorts of things, there's nothing stopping them from doing that, other than it should probably be another layer that stores that besides modifying the units directly.

The spec defines 24 om:ApplicationAreas which each form a hierarchy of grouping of units and their hierarchies above. For instance, let's look at a path through to the om:geometry hierarchy:

There are groupings of 76 different om:Dimensions, such as [om:lengthDimension](https://github.com/HajoRijgersberg/OM/blob/master/om-2.0.rdf#L4729].

This means there's enough data in the ontology to both allow narrowing down and selecting units based on application area and dimension type.

My suggestion would be to have some configuration mechanism of configuring which units are used by the particular organizations/groups/agents. We could also allow aliases for all units and conversions for the unit-less units (eg. om:barFood). That way we could probably still look up the units by their canonical OM2 URI fragment instead of having random names for everything in every group.

It's funny, OM2 seems to have everything but the om:kitchenSink.

@fosterlynn
Copy link

Maybe I left this comment in the wrong place, so referencing it here. Sorry for any confusion over many issues in different places.

Could also consider ommitting description, changing classifiedAs: [URI!] to definedAs: URI (or whatever other core semantic meaning for this, there is probably something in the rdf vocab itself). But the point is from 1:many to 1:1 and we would setup a unique index on that field; whilst allowing both label and symbol to be non-unique.

Something like that makes sense to me. Suggest:

  hour: {
    label: 'hour',
    symbol: 'h',
    comment: 'The hour is a unit of time defined as 3600 second.',
    definedAs: 'http://www.ontology-of-units-of-measure.org/resource/om-2/hour',
  }

I like having the description handy (maybe useful for mouse-overs), although in OM2 it looks like they use rdfs:comment, and seems like we should use their terms. (I thought I saw definition there, but that isn't in their rawgit version.) I like definedAs better than classifiedAs, since it is more creating a cross reference than a classification. I made definedAs not be a list, agree with your suggestion.

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

Successfully merging this pull request may close these issues.

3 participants