The UPI is a way to take a set of well-known Property identifiers and encode them in a way such that if two parties both have the same data, they can create the identifiers and match on them. This is useful for deduplicating data.
UPIs can also easily be searched on by parts and be used with URIs as part of a browser location or API URL, since they are friendly with existing URI standards.
Uniform Resource Names are a type of Uniform Resource Identifier (URI) that allow globally unique identifiers to be created using a namespace.
They are a general Internet standard from IANA and IETF, which are in many enterprise-scale applications such as AWS and LinkedIn.
RESO has a reserved URN which includes v1 UPI identifiers. See section 3.4.2.
In order to avoid collisions with special characters and preserve data passed in the URN, UPIv2 takes the following approach:
The delimiters are the components of the UPI!
What does this mean, in practice?
The UPI includes Country as part of constituent data. This means it has a component called :country:
which is also a delimiter. This avoids the need for special delimiters.
Assume we have the following UPI:
urn:reso:upi:2.0:country:US:stateorprovince:CA:county:06037:subcounty::propertytype:Residential:subpropertytype::parcelnumber: [abc] 1-2 :: 3:456 :subparcelnumber:
Let’s look at the individual parts:
urn:reso:upi:
is the stem of the UPI, which includes both the URN prefix and upi namespace2.0
is the version of the UPIcountry
is US
in this case (ISO Country Code)stateorprovince
is CA
for California (USPS)county
is a FIPS county codesubcounty
is an optional FIPS sub-county code, however it’s empty here (:subcounty::
)propertytype
is an optional Data Dictionary type, in this case Residentialsubpropertytype
is empty in this caseparcelnumber
shows a number of weird characters, including brackets, colons, and spaces, but since the components are the delimiters, the original values are preservedsubparcelnumber
is empty in this case (no values after the last :
)This UPI can be decoded into a RESO Common Format payload using the decode
function.
// Assume we're calling from the node REPL
// and in the root of the project directory
> const { decode } = require(".");
> const upi = 'urn:reso:upi:2.0:country:US:stateorprovince:CA:county:06037:subcounty::propertytype:Residential:subpropertytype::parcelnumber: [abc] 1-2 :: 3:456 :subparcelnumber:';
> const upiData = decode({ upi });
> upiData
{
'@reso.context': 'urn:reso:metadata:2.0:resource:property',
Country: 'US',
StateOrProvince: 'CA',
County: '06037',
SubCounty: null,
PropertyType: 'Residential',
SubPropertyType: null,
ParcelNumber: ' [abc] 1-2 :: 3:456 ',
SubParcelNumber: null
}
Note that any item without data in it is null, and that all special characters are preserved in the ParcelNumber. This would be true if special characters were used in any of the values, not just ParcelNumber.
Now let’s say we have some standardized RESO data and we want to create a UPI from it.
We can do so using the encode
function:
// Assume we're calling from the node REPL
// and in the root of the project directory
> const { encode } = require(".");
> const upiData = {
'@reso.context': 'urn:reso:metadata:2.0:resource:property',
Country: 'US',
StateOrProvince: 'CA',
County: '06037',
SubCounty: null,
PropertyType: 'Residential',
SubPropertyType: null,
ParcelNumber: ' [abc] 1-2 :: 3:456 ',
SubParcelNumber: null
};
> const upi = encode({upiData});
> upi
'urn:reso:upi:2.0:country:US:stateorprovince:CA:county:06037:subcounty::propertytype:Residential:subpropertytype::parcelnumber: [abc] 1-2 :: 3:456 :subparcelnumber:'
version
is included, different formulas could be used for different versions and accommodate future changes or improvements without making backwards-breaking changes.1.0
in place of 2.0
, above, or we can assume that if the data in the DD field does not start with urn:reso:upi
, that it’s in the v1 format.:country:
and what its value is, and that the second value is :stateorprovince:
, etc.In the U.S., Parcel Numbers are a matter of public record. However, in other countries / scenarios, there may be some data that cannot be conveyed due to intellectual property concerns or for other reasons.
The matching and deduplication aspects of the UPI still work even when hashed since if the same components and data were used between two records, their hashes would be the same.
As for choice of hashes, since we’re dealing with particularly sensitive data that others wouldn’t want shared, one-way hashing (i.e. cryptographic hashing) is a natural choice since it sufficiently obscures the source data. They’re also NIST and global standards used in large-scale production environments like GitHub, Blockchain and Ethereum, and have support out of the box in programming languages and frameworks.
One-way hashes also offer collision-resistance, which is important for the universality of the UPI.
Let’s assume we have the UPI created in earlier examples:
urn:reso:upi:2.0:country:US:stateorprovince:CA:county:06037:subcounty::propertytype:Residential:subpropertytype::parcelnumber: [abc] 1-2 :: 3:456 :subparcelnumber:
To create a UPI hash from this value, use the hash
function:
// Assume we're calling from the node REPL
// and in the root of the project directory
> const { hash } = require('.'),
upi = 'urn:reso:upi:2.0:country:US:stateorprovince:CA:county:06037:subcounty::propertytype:Residential:subpropertytype::parcelnumber: [abc] 1-2 :: 3:456 :subparcelnumber:'
> const upiHash = hash(upi);
> upiHash
'urn:reso:upi:2.0:sha3-256-hash:427c883322af677b76d72d43d9a00c3bedd6a1ede20e43c614f710abf85549a9'
Note that the component representing the UPI hash also includes the method that was used for hashing. This seems practical, and offers the ability to support different kinds of hashing, should the need arise.
To install from GitHub:
npm i RESOStandards/reso-upi-v2
If you would like to run the project locally, install Node and git if you don’t have them already.
> mkdir [PROJECT_LOCATION]
> cd [PROJECT_LOCATION]
> git clone https://github.com/RESOStandards/reso-upi-v2.git
> cd reso-upi-v2
> npm install
> npm install mocha
To check the installation, run:
> npm run test
For examples of how to use this library, see ./test.
If you would like to suggest changes, please open a ticket.
If you have changes to contribute, fork the repo, clone locally, make changes, then make a PR against this repo.