Committing More Data to the Units of Measurement Repo (TS)

Using the TypeScript SDK to add more data to units of measurement.

This guide is a continuation of the previous Units of Measurement Repo.

It is necessary to have the types which are created in that guide in your repo, because the SDK is based on the types which are associated with your repo.

Synopsis

WarmHub was founded in the United States of America. US Customary units are colloquially used in this country, despite the International of Units (SI units) having been the officially preferred system for several decades. Therefore, it is a common scenario in the daily lives of many people in the United States to convert between US Customary and SI units. Our objective is to commit more data to our repo, matching the structure of the previous data we have created.

Setup

Download the SDK from the repo you created previously by visiting app.warmhub.com/[your org name]/[your repo name]/about.

Click on "TypeScript SDK." Then, unpack the archive, and prepare the project by adding your WarmHub personal access token to the .env file and building the project.

For more details, see Committing Data Using TypeScript SDK.

Adding Individual Instances

We will review the basic structure of making a commit in the SDK by posting some articles. As a reminder, commits can have any combinations of additions, revisions, and retractions.

Adding Fully Qualified Objects

In the src/index.ts file of your generated SDK, there will be some boilerplate which imports the Client object associated with your repo. We will be posting commits using the client's commit() method. In a similar to the first commit we made in the first Units of Measurement Repo guide, we can construct a TypeScript object and post it to our repo. It should look similar to the following:

import 'dotenv/config';
import { [YOUR ORG NAME][YOUR REPO NAME]Client } from "./schemas";

const client = new [YOUR ORG NAME][YOUR REPO NAME]Client()

Below the boilerplate, insert the following code:

import { Commit, Unit } from "./schemas";

const unitCommit: Commit = new Commit();

const mile: Unit = {
  type: "{YOUR ORG NAME}/{YOUR REPO NAME}/Unit",
  $id: "Mile",
  GoverningBody: "NIST",
  PhysicalProperty: "Length",
};

unitCommit.add(mile);

client.commit(unitCommit);

Your development environment might raise an error about the type property being invalid if that feature is available to you and you've run the build script in your SDK. To disambiguate between types of the same name in different repos, you'll have to insert your org name and your repo name into the type property above.

You can post this commit by running the start script:

npm start

If the commit was posted successfully, you will see no further console output. However, if you refresh your repo page in the WarmHub app, the second/data page should now contain one Article:

Using References

When using the Reference types, it's necessary to append "Ref" to the end of the type name, just like when posting a commit using the JSON. We will now post a UnitConversionPair object converting from our previously added mile to a kilometer.

First, prepare the URL which we need to use to reference the existing Article by copying the WID from the instance page for the Article. Then, remove the previous code used to post the "Mile" Article, leaving behind the lines instantiating the Client, and add the following:

import { Commit, UnitConversionPair } from "./schemas";

const unitPairCommit: Commit = new Commit();

const fromMileToKilometer: UnitConversionPair = {
  type: "{YOUR ORG NAME}/{YOUR REPO NAME}/UnitConversionPair",
  $id: "FromMileToKilometer",
  From: {
    type: "{YOUR ORG NAME}/{YOUR REPO NAME}/UnitRef",
    $wref: [Mile Article $WID]
  },
  To: {
    type: "{YOUR ORG NAME}/{YOUR REPO NAME}/Unit",
    $id: "Kilometer",
    GoverningBody: "BIPM",
    PhysicalProperty: "length",
  },
};

unitPairCommit.add(fromMileToKilometer);

client.commit(unitPairCommit);

Make sure to insert your org and repo into the type property, insert the $WID for the Mile Article, and update your imports at the top of the file.

After this commit, you should have three more articles (Mile, Kilometer, and FromMileToKilometer):

You can search for "Mile" or "Kilometer" to filter results

Reading Errors

Commit errors will be outputted to the console, and indicate the cause of the commit. When an error occurs, none of the changes in your commit will have been applied in WarmHub. As an example, try running the previous code unchanged another time. Because we are declaring that the UnitConversionPair has a given $id for the second time, it violates the constraint that the $id are unique for the type. Therefore, you will get an error message starting with

/path/to/your/ts_sdk/src/schemas.ts:000
      throw new CommitError('Failed to commit', { message: body.message, cause: body.data })
            ^


CommitError: Failed to commit
    at warmhubunit_repo_walkthroughClient.commit (/path/to/your/ts_sdk/src/schemas.ts:000:00)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
  cause: {
    message: 'Duplicate item found at top level by WREF resolution: FromMileToKilometer (Phase: parsing)', 
       
[... some more]

Another case is if you have a field which fails validation. A case of this may be if you attempt to add a value of the wrong type, such as if you post a string to a property which is of a Number type. We can see what this looks like by putting a non-path segment safe value into the $id field. Reusing our previous example, replace $id: 'FromMileToKilometer with something with in a space in it, such as $id: 'Spaces are not allowed here' .

Attempting to have the client post this content again will generate an error message similar to:

/path/to/your/ts_sdk/src/schemas.ts:000
      throw new CommitError('Failed to commit', { message: body.message, cause: body.data })
            ^


CommitError: Failed to commit
    at warmhubunit_repo_walkthroughClient.commit (/path/to/your/ts_sdk/src/schemas.ts:000)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
  cause: {
    message: 'Validation failed (Phase: parsing)',
    cause: {
      errorType: 'ValidationError',
      phase: 'parsing',
      pretty: "✖ Invalid string: must match pattern /^[a-zA-Z0-9\\-._~!$&'()*+,;=]+$/\n" +
        '  → at additions[0].$id',
    
    [... some more]

Forming a Large Commit

A more valuable utility of programmatically defining your commits in the TypeScript SDK is that it's easier to construct large sets of information.

Adding UnitConversion Evidence

Because US Customary units are all defined based on the SI unit of meter, we can use their absolute definitions to convert between all permutations of the units. In this example, we will convert between inch, yard, foot, meter, centimeter, and millimeter. If we were to write this by hand in JSON, it would amount to P(6, 2), or 30 UnitConversion objects.

First, we can define our base measurements, our Unit Articles, and the Source we are using in our UnitConversion evidence later. Please insert the org and repo name where indicated to match your repo's org and repo name.

import { Agency, Commit, Unit, UnitConversion } from "./schemas";

const siUnitNames: string[] = ["Meter", "Centimeter", "Millimeter"];
const customaryUnitNames: string[] = ["Inch", "Foot", "Yard"];

// The United States' NIST defines US Customary units based on SI Units. If we
// capture the absolute lengths of each of these units, with the identity set
// to the value of a meter, we can calculate the Factor required in each
// UnitConversion object, creating an evidence for each permutation of unit pair
const absoluteValues: Record<string, number> = {
  Meter: 1,
  Centimeter: 0.01,
  Millimeter: 0.001,
  Inch: 0.0254,
  Foot: 0.3048,
  Yard: 0.9144,
};

// Create a string: Unit (Article) record. SI units are defined by the BIPM, but
// US Customary units are defined by NIST.
const units: Record<string, Unit> = {
  ...Object.fromEntries(
    siUnitNames.map((name) => [
      name,
      {
        type: "{YOUR ORG NAME}/{YOUR REPO NAME}/Unit",
        $id: name,
        GoverningBody: "BIPM",
        PhysicalProperty: "Length",
      },
    ])
  ),
  ...Object.fromEntries(
    customaryUnitNames.map((name) => [
      name,
      {
        type: "{YOUR ORG NAME}/{YOUR REPO NAME}/Unit",
        $id: name,
        GoverningBody: "NIST",
        PhysicalProperty: "Length",
      },
    ])
  ),
};

// Our source for all of these conversions is NIST Special Publication 811,
// which acknowledges SI units and converts between them and US Customary units.
const source: Agency = {
  type: "{YOUR ORG NAME}/{YOUR REPO NAME}/Agency",
  $id: "NIST",
  link: "https://www.nist.gov/pml/special-publication-811/nist-guide-si-appendix-b-conversion-factors/nist-guide-si-appendix-b8",
};

Recall that the UnitConversion type has the following properties, in addition to the type and $id properties:

  • Factor: The number of "To" unit which is equivalent to one "From" unil

    • In the first guide, FromKilogramToTon has a Factor of 0.001 because 1 kg = 0.001 ton.

  • Offset: The number by which to offset the conversion. In this case, we are converting lengths, so there is 0 offset.

  • about: The UnitConversionPair which the evidence is about.

  • sources : We will use the same Agency for all of the conversions.

Now, we can iterate through our unit names and use the records we have created to calculate the factor for every permutation of UnitConversionPair. Please make sure to insert your org and repo name into the snippet where indicated.

const allUnitNames: string[] = [...siUnitNames, ...customaryUnitNames];
const conversionCommit: Commit = new Commit();

for (const fromUnit of allUnitNames) {
  for (const toUnit of allUnitNames) {
    if (fromUnit !== toUnit) {
      // Avoid identity conversions
      const conversion: UnitConversion = {
        type: "{YOUR ORG NAME}/{YOUR REPO NAME}/UnitConversion",
        $id: `UnitConversionFrom${fromUnit}To${toUnit}`,
        Factor: absoluteValues[fromUnit] / absoluteValues[toUnit],
        Offset: 0,
        about: [
          {
            type: "{YOUR ORG NAME}/{YOUR REPO NAME}/UnitConversionPair",
            $id: `From${fromUnit}To${toUnit}`,
            From: units[fromUnit],
            To: units[toUnit],
          },
        ],
        sources: [source],
      };

      conversionCommit.add(conversion);
    }
  }
}

client.commit(conversionCommit);

Now, after running the start script, the 30 UnitConversion Evidence instances (and thus 30 UnitConversionPair Article instances, 1 Agency Source instance, and 5 Unit Article instances) should be added to your repo. For example, app.warmhub.com/{YOUR ORG NAME}/{YOUR REPO NAME}/data/UnitConversion/UnitConversionFromMeterToInch should have a factor of about 39.37, as there are 39.37 inches in 1 meter.

You can also see the posted evidence on your data page:

A filter of "inch" is applied, so only the conversions containing "inch" in the $id appear.

Using $id for Claims at Scale

When we posted the UnitConversion Evidence instances, note that we set the $id field to a known pattern of UnitConversionFrom[fromUnit]To[toUnit]. Therefore, we can utilize how we know that $id is unique and which set of $id we have posted previously to quickly make our claims about whether the last 30 UnitConversion objects can be converted back and forth.

From the first guide, note that CanBeConvertedBackAndForth Claims are bidirectional. So, we will have to calculate the combinations, not permutations of our six units to make our 15 (6 choose 2) claims.

You can remove the code from before, as we've already posted those instances, and don't want to generate a duplicate error trying to commit them again.

import { CanBeConvertedBackAndForth, Commit } from "./schemas";

const allUnitNames: string[] = [
  "Meter",
  "Centimeter",
  "Millimeter",
  "Inch",
  "Foot",
  "Yard",
];

let uniqueUnitPairs: [string, string][] = [];

for (let i = 0; i < allUnitNames.length; i++) {
  for (let j = i + 1; j < allUnitNames.length; j++) {
    uniqueUnitPairs.push([allUnitNames[i], allUnitNames[j]]);
  }
}

Then, we can construct our claims made purely out of the references. Our source states the reversibility of all of the conversions, so all of them are true.

Please ensure you have inserted your org and repo name into the snippet.

const reversibleCommit: Commit = new Commit();

for (const unitPair of uniqueUnitPairs) {
  const [firstUnit, secondUnit] = unitPair;

  const claim: CanBeConvertedBackAndForth = {
    type: "{YOUR ORG NAME}/{YOUR REPO NAME}/CanBeConvertedBackAndForth",
    $id: `Can${firstUnit}And${secondUnit}BeConvertedBackAndForth`,
    IsReversibleConversion: true,
    about: [
      {
        type: "{YOUR ORG NAME}/{YOUR REPO NAME}/UnitConversionPairRef",
        $wref: `http://repo.warmhub.com/{YOUR ORG NAME}/{YOUR REPO NAME}/data/UnitConversionPair/From${firstUnit}To${secondUnit}`,
      },
      {
        type: "{YOUR ORG NAME}/{YOUR REPO NAME}/UnitConversionPairRef",
        $wref: `http://repo.warmhub.com/{YOUR ORG NAME}/{YOUR REPO NAME}/data/UnitConversionPair/From${secondUnit}To${firstUnit}`,
      },
    ],
    basis: [
      {
        type: "{YOUR ORG NAME}/{YOUR REPO NAME}/UnitConversionRef",
        $wref: `http://repo.warmhub.com/{YOUR ORG NAME}/{YOUR REPO NAME}/data/UnitConversion/UnitConversionFrom${firstUnit}To${secondUnit}`,
      },
      {
        type: "{YOUR ORG NAME}/{YOUR REPO NAME}/UnitConversionRef",
        $wref: `http://repo.warmhub.com/{YOUR ORG NAME}/{YOUR REPO NAME}/data/UnitConversion/UnitConversionFrom${secondUnit}To${firstUnit}`,
      },
    ],
  };

  reversibleCommit.add(claim);
}

client.commit(reversibleCommit);

Now, the claims you've made should show up in your repo's data page:

Last updated