Skip to content

Commit

Permalink
feat: pdt healthcert schema v2.0 (#38)
Browse files Browse the repository at this point in the history
* feat: pdt schema v2.0 (draft 1)

* fix: unique id

* fix: art should not have Observation (Accredited Laboratory)

* fix: lower case for version

* fix: nric-fin

* fix: add url for accredited laboratory

* fix: add ser type

* fix: rename samples

* feat: fhir lite schema script

* fix: lint errors

* feat: schema test for pdt v2.0

* fix: add bundle ref and custom sorting of definitions

* feat: rename root types for v1 and v2

* fix: typo

* fix: rename for clarity

* feat: add provider and endorsed schema

* fix: include logo

* fix: update sample document

* feat: add clinic provider samples

* feat: add endorsed samples

* fix: exported types

* fix: address review comments

* refactor: improve code readability

* fix: address review comments
  • Loading branch information
HJunyuan authored Aug 27, 2021
1 parent e94e144 commit aa5ebb6
Show file tree
Hide file tree
Showing 13 changed files with 5,966 additions and 3 deletions.
91 changes: 91 additions & 0 deletions scripts/lite-fhir-schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require("fs");
const fhir = require("../src/sg/gov/moh/fhir/4.0.1/schema.json");

const filename = "src/sg/gov/moh/fhir/4.0.1/lite-schema.json";

const resources = ["Bundle", "Device", "Observation", "Organization", "Patient", "Practitioner", "Specimen"];
const resourceQueue = [...resources];
const resourcesFetched = new Set(["ResourceList"]); // Skip ResourceList

const schemaHeader = {
$schema: "http://json-schema.org/draft-07/schema#",
$id: "https://schemata.openattestation.com/sg/gov/moh/fhir/4.0.1/lite-schema.json#",
// id: "http://hl7.org/fhir/json-schema/4.0",
title: "Lite FHIR schema for Notarise.gov.sg HealthCerts",
description: "see http://hl7.org/fhir/json.html#schema for information about the FHIR Json Schemas",
$ref: "#/definitions/Bundle"
};

/** ====== Helper Functions ====== **/

function sanitiseRef(ref = "") {
return ref.replace("#/definitions/", "");
}

function findUniqueNestedReferences(obj = {}, results = []) {
const searchKey = "$ref";
const references = results;

for (const [key, value] of Object.entries(obj)) {
if (key === searchKey && typeof value !== "object") {
references.push(sanitiseRef(value));
} else if (typeof value === "object") {
findUniqueNestedReferences(value, references);
}
}

return [...new Set(references)];
}

/** ====== Main Script ====== **/
const definitionEntries = [];

while (resourceQueue.length > 0) {
const resource = resourceQueue.shift();

// 1. Extract resource definition
const definition = fhir.definitions[resource];
const properties = definition.properties
? Object.keys(definition.properties)
// Filter out properties that start with "_" (since they are not needed)
.filter(key => !key.startsWith("_"))
.reduce((obj, key) => ({ ...obj, [key]: definition.properties[key] }), {})
: undefined;
definitionEntries.push([resource, { ...definition, properties }]);

// 2. Find nested references
const nestedReferences = findUniqueNestedReferences(definition.properties).filter(
// Filter out definitions that have already been fetched
r => !resourcesFetched.has(r)
);
resourceQueue.push(...nestedReferences);
resourcesFetched.add(...nestedReferences, resource);
}

// 3. Custom ResourceList: To limit the type of resources that can exist in Bundle_Entry and .contained
const resourceList = {
oneOf: resources
.map(r => ({
$ref: `#/definitions/${r}`
}))
.sort()
};

/** ====== Export! ====== **/

const liteSchema = {
...schemaHeader,
definitions: {
ResourceList: resourceList,
...Object.fromEntries(
definitionEntries
// Sort definitions alphabetically
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
)
}
};
fs.writeFile(filename, JSON.stringify(liteSchema, null, 2), err => {
if (err) console.error(err);
else console.log(`Exported to ${filename} with ${Object.keys(liteSchema.definitions).length} definitions.`);
});
3 changes: 2 additions & 1 deletion scripts/post-install.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const generate = ({ path, rootTypeName }) => {
if (fs.existsSync(quicktype) && process.env.npm_config_production !== "true") {
generate({ path: "sg/gov/tech/geekout/1.0", rootTypeName: "Geekout" });
generate({ path: "sg/gov/tech/notarise/1.0", rootTypeName: "Notarise" });
generate({ path: "sg/gov/moh/pdt-healthcert/1.0", rootTypeName: "PDTHealthCert" });
generate({ path: "sg/gov/moh/pdt-healthcert/1.0", rootTypeName: "PDTHealthCertV1" });
generate({ path: "sg/gov/moh/pdt-healthcert/2.0", rootTypeName: "PDTHealthCertV2" });
generate({ path: "sg/gov/moh/vaccination-healthcert/1.0", rootTypeName: "VaccinationHealthCert" });
} else {
console.log("Not running quicktype");
Expand Down
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as geekout from "./__generated__/sg/gov/tech/geekout/1.0/schema";
import * as notarise from "./__generated__/sg/gov/tech/notarise/1.0/schema";
import * as pdtHealthcert from "./__generated__/sg/gov/moh/pdt-healthcert/1.0/schema";
import * as pdtHealthCertV1 from "./__generated__/sg/gov/moh/pdt-healthcert/1.0/schema";
import * as pdtHealthCertV2 from "./__generated__/sg/gov/moh/pdt-healthcert/2.0/schema";
import * as vaccinationHealthcert from "./__generated__/sg/gov/moh/vaccination-healthcert/1.0/schema";

export { geekout, notarise, pdtHealthcert, vaccinationHealthcert };
export { geekout, notarise, pdtHealthCertV1, pdtHealthCertV2, vaccinationHealthcert };
Loading

0 comments on commit aa5ebb6

Please sign in to comment.