Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { applySliceMatch, matchesSlice, extractSliceSimplified, validateRequired
export type observation_bodyweightProfileParams = {
status: ("registered" | "preliminary" | "final" | "amended" | "corrected" | "cancelled" | "entered-in-error" | "unknown");
subject: Reference<"Patient">;
category?: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[];
}

// CanonicalURL: http://hl7.org/fhir/StructureDefinition/bodyweight (pkg: hl7.fhir.r4.core#4.0.1)
Expand All @@ -41,10 +42,13 @@ export class observation_bodyweightProfile {
}

static createResource (args: observation_bodyweightProfileParams) : Observation {
const categoryDefaults = [{"coding":{"code":"vital-signs","system":"http://terminology.hl7.org/CodeSystem/observation-category"}}] as unknown[]
const categoryWithDefaults = [...(args.category ?? [])] as unknown[]
if (!categoryWithDefaults.some(item => matchesSlice(item, {"coding":{"code":"vital-signs","system":"http://terminology.hl7.org/CodeSystem/observation-category"}} as Record<string, unknown>))) categoryWithDefaults.push(categoryDefaults[0]!)
const resource: Observation = {
resourceType: "Observation",
category: [{"coding":{"code":"vital-signs","system":"http://terminology.hl7.org/CodeSystem/observation-category"}}],
code: {"coding":[{"code":"29463-7","system":"http://loinc.org"}]},
category: categoryWithDefaults,
status: args.status,
subject: args.subject,
meta: { profile: ["http://hl7.org/fhir/StructureDefinition/bodyweight"] },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ export interface observation_bp extends Observation {
}

export type Observation_bp_Category_VSCatSliceInput = Omit<CodeableConcept, "coding">;
export type Observation_bp_Component_SystolicBPSliceInput = Omit<ObservationComponent, "code">;
export type Observation_bp_Component_DiastolicBPSliceInput = Omit<ObservationComponent, "code">;
export type Observation_bp_Component_SystolicBPSliceInput = Omit<ObservationComponent, "code" | "value" | "valueQuantity" | "valueCodeableConcept" | "valueString" | "valueBoolean" | "valueInteger" | "valueRange" | "valueRatio" | "valueSampledData" | "valueTime" | "valueDateTime" | "valuePeriod"> & Quantity;
export type Observation_bp_Component_DiastolicBPSliceInput = Omit<ObservationComponent, "code" | "value" | "valueQuantity" | "valueCodeableConcept" | "valueString" | "valueBoolean" | "valueInteger" | "valueRange" | "valueRatio" | "valueSampledData" | "valueTime" | "valueDateTime" | "valuePeriod"> & Quantity;

import { applySliceMatch, matchesSlice, extractSliceSimplified, validateRequired, validateExcluded, validateFixedValue, validateSliceCardinality, validateEnum, validateReference } from "../../profile-helpers";
import { applySliceMatch, matchesSlice, extractSliceSimplified, wrapSliceChoice, flattenSliceChoice, validateRequired, validateExcluded, validateFixedValue, validateSliceCardinality, validateEnum, validateReference } from "../../profile-helpers";

export type observation_bpProfileParams = {
status: ("registered" | "preliminary" | "final" | "amended" | "corrected" | "cancelled" | "entered-in-error" | "unknown");
subject: Reference<"Patient">;
category?: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[];
component?: ObservationComponent[];
}

// CanonicalURL: http://hl7.org/fhir/StructureDefinition/bp (pkg: hl7.fhir.r4.core#4.0.1)
Expand All @@ -44,11 +46,18 @@ export class observation_bpProfile {
}

static createResource (args: observation_bpProfileParams) : Observation {
const categoryDefaults = [{"coding":{"code":"vital-signs","system":"http://terminology.hl7.org/CodeSystem/observation-category"}}] as unknown[]
const categoryWithDefaults = [...(args.category ?? [])] as unknown[]
if (!categoryWithDefaults.some(item => matchesSlice(item, {"coding":{"code":"vital-signs","system":"http://terminology.hl7.org/CodeSystem/observation-category"}} as Record<string, unknown>))) categoryWithDefaults.push(categoryDefaults[0]!)
const componentDefaults = [{"code":{"coding":{"code":"8480-6","system":"http://loinc.org"}}},{"code":{"coding":{"code":"8462-4","system":"http://loinc.org"}}}] as unknown[]
const componentWithDefaults = [...(args.component ?? [])] as unknown[]
if (!componentWithDefaults.some(item => matchesSlice(item, {"code":{"coding":{"code":"8480-6","system":"http://loinc.org"}}} as Record<string, unknown>))) componentWithDefaults.push(componentDefaults[0]!)
if (!componentWithDefaults.some(item => matchesSlice(item, {"code":{"coding":{"code":"8462-4","system":"http://loinc.org"}}} as Record<string, unknown>))) componentWithDefaults.push(componentDefaults[1]!)
const resource: Observation = {
resourceType: "Observation",
category: [{"coding":{"code":"vital-signs","system":"http://terminology.hl7.org/CodeSystem/observation-category"}}],
code: {"coding":[{"code":"85354-9","system":"http://loinc.org"}]},
component: [{"code":{"coding":{"code":"8480-6","system":"http://loinc.org"}}},{"code":{"coding":{"code":"8462-4","system":"http://loinc.org"}}}],
category: categoryWithDefaults,
component: componentWithDefaults,
status: args.status,
subject: args.subject,
meta: { profile: ["http://hl7.org/fhir/StructureDefinition/bp"] },
Expand Down Expand Up @@ -155,7 +164,7 @@ export class observation_bpProfile {

public setSystolicBP (input?: Observation_bp_Component_SystolicBPSliceInput): this {
const match = {"code":{"coding":{"code":"8480-6","system":"http://loinc.org"}}} as Record<string, unknown>
const value = applySliceMatch((input ?? {}) as Record<string, unknown>, match) as unknown as ObservationComponent
const value = applySliceMatch(wrapSliceChoice((input ?? {}) as Record<string, unknown>, "valueQuantity"), match) as unknown as ObservationComponent
const list = (this.resource.component ??= [])
const index = list.findIndex((item) => matchesSlice(item, match))
if (index === -1) {
Expand All @@ -168,7 +177,7 @@ export class observation_bpProfile {

public setDiastolicBP (input?: Observation_bp_Component_DiastolicBPSliceInput): this {
const match = {"code":{"coding":{"code":"8462-4","system":"http://loinc.org"}}} as Record<string, unknown>
const value = applySliceMatch((input ?? {}) as Record<string, unknown>, match) as unknown as ObservationComponent
const value = applySliceMatch(wrapSliceChoice((input ?? {}) as Record<string, unknown>, "valueQuantity"), match) as unknown as ObservationComponent
const list = (this.resource.component ??= [])
const index = list.findIndex((item) => matchesSlice(item, match))
if (index === -1) {
Expand Down Expand Up @@ -202,7 +211,7 @@ export class observation_bpProfile {
if (!list) return undefined
const item = list.find((item) => matchesSlice(item, match))
if (!item) return undefined
return extractSliceSimplified(item as unknown as Record<string, unknown>, ["code"]) as Observation_bp_Component_SystolicBPSliceInput
return flattenSliceChoice(item as unknown as Record<string, unknown>, ["code"], "valueQuantity") as Observation_bp_Component_SystolicBPSliceInput
}

public getSystolicBPRaw (): ObservationComponent | undefined {
Expand All @@ -219,7 +228,7 @@ export class observation_bpProfile {
if (!list) return undefined
const item = list.find((item) => matchesSlice(item, match))
if (!item) return undefined
return extractSliceSimplified(item as unknown as Record<string, unknown>, ["code"]) as Observation_bp_Component_DiastolicBPSliceInput
return flattenSliceChoice(item as unknown as Record<string, unknown>, ["code"], "valueQuantity") as Observation_bp_Component_DiastolicBPSliceInput
}

public getDiastolicBPRaw (): ObservationComponent | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type observation_vitalsignsProfileParams = {
status: ("registered" | "preliminary" | "final" | "amended" | "corrected" | "cancelled" | "entered-in-error" | "unknown");
code: CodeableConcept<("85353-1" | "9279-1" | "8867-4" | "2708-6" | "8310-5" | "8302-2" | "9843-4" | "29463-7" | "39156-5" | "85354-9" | "8480-6" | "8462-4" | "8478-0" | string)>;
subject: Reference<"Patient">;
category?: CodeableConcept<("social-history" | "vital-signs" | "imaging" | "laboratory" | "procedure" | "survey" | "exam" | "therapy" | "activity" | string)>[];
}

// CanonicalURL: http://hl7.org/fhir/StructureDefinition/vitalsigns (pkg: hl7.fhir.r4.core#4.0.1)
Expand All @@ -41,9 +42,12 @@ export class observation_vitalsignsProfile {
}

static createResource (args: observation_vitalsignsProfileParams) : Observation {
const categoryDefaults = [{"coding":{"code":"vital-signs","system":"http://terminology.hl7.org/CodeSystem/observation-category"}}] as unknown[]
const categoryWithDefaults = [...(args.category ?? [])] as unknown[]
if (!categoryWithDefaults.some(item => matchesSlice(item, {"coding":{"code":"vital-signs","system":"http://terminology.hl7.org/CodeSystem/observation-category"}} as Record<string, unknown>))) categoryWithDefaults.push(categoryDefaults[0]!)
const resource: Observation = {
resourceType: "Observation",
category: [{"coding":{"code":"vital-signs","system":"http://terminology.hl7.org/CodeSystem/observation-category"}}],
category: categoryWithDefaults,
status: args.status,
code: args.code,
subject: args.subject,
Expand Down
18 changes: 18 additions & 0 deletions examples/typescript-r4/fhir-types/profile-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,24 @@ export const extractSliceSimplified = <T extends Record<string, unknown>>(slice:
return result as Partial<T>;
}

export const wrapSliceChoice = (input: Record<string, unknown>, choiceVariant: string): Record<string, unknown> => {
if (Object.keys(input).length === 0) return input;
return { [choiceVariant]: input };
}

export const flattenSliceChoice = (slice: Record<string, unknown>, matchKeys: string[], choiceVariant: string): Record<string, unknown> => {
const result = { ...slice } as Record<string, unknown>;
for (const key of matchKeys) {
delete result[key];
}
const variantValue = result[choiceVariant];
delete result[choiceVariant];
if (isRecord(variantValue)) {
Object.assign(result, variantValue);
}
return result;
}

export const validateRequired = (r: Record<string, unknown>, field: string, path: string): string | undefined => {
return r[field] === undefined || r[field] === null ? `${path}: required field '${field}' is missing` : undefined;
}
Expand Down
71 changes: 60 additions & 11 deletions examples/typescript-r4/profile-bp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ describe("blood pressure profile", () => {
});

test("setSystolicBP / getSystolicBP / getSystolicBPRaw", () => {
profile.setSystolicBP({
valueQuantity: { value: 120, unit: "mmHg", system: "http://unitsofmeasure.org", code: "mm[Hg]" },
});
profile.setSystolicBP({ value: 120, unit: "mmHg", system: "http://unitsofmeasure.org", code: "mm[Hg]" });

expect(profile.getSystolicBP()).toEqual({
valueQuantity: { value: 120, unit: "mmHg", system: "http://unitsofmeasure.org", code: "mm[Hg]" },
value: 120,
unit: "mmHg",
system: "http://unitsofmeasure.org",
code: "mm[Hg]",
});

expect(profile.getSystolicBPRaw() as unknown).toEqual({
Expand All @@ -59,12 +60,13 @@ describe("blood pressure profile", () => {
});

test("setDiastolicBP / getDiastolicBP / getDiastolicBPRaw", () => {
profile.setDiastolicBP({
valueQuantity: { value: 80, unit: "mmHg", system: "http://unitsofmeasure.org", code: "mm[Hg]" },
});
profile.setDiastolicBP({ value: 80, unit: "mmHg", system: "http://unitsofmeasure.org", code: "mm[Hg]" });

expect(profile.getDiastolicBP()).toEqual({
valueQuantity: { value: 80, unit: "mmHg", system: "http://unitsofmeasure.org", code: "mm[Hg]" },
value: 80,
unit: "mmHg",
system: "http://unitsofmeasure.org",
code: "mm[Hg]",
});

expect(profile.getDiastolicBPRaw() as unknown).toEqual({
Expand All @@ -87,7 +89,7 @@ describe("blood pressure profile", () => {
});

test("setSystolicBP replaces an existing systolic component", () => {
profile.setSystolicBP({ valueQuantity: { value: 130, unit: "mmHg" } });
profile.setSystolicBP({ value: 130, unit: "mmHg" });

expect(profile.toResource().component).toHaveLength(2);
expect(profile.getSystolicBPRaw()!.valueQuantity!.value).toBe(130);
Expand Down Expand Up @@ -116,8 +118,8 @@ describe("blood pressure profile", () => {
.setVSCat({ text: "Vital Signs" })
.setEffectiveDateTime("2024-06-15")
.setSubject({ reference: "Patient/pt-2" })
.setSystolicBP({ valueQuantity: { value: 120, unit: "mmHg" } })
.setDiastolicBP({ valueQuantity: { value: 80, unit: "mmHg" } });
.setSystolicBP({ value: 120, unit: "mmHg" })
.setDiastolicBP({ value: 80, unit: "mmHg" });

expect(result).toBe(profile);
expect(profile.getStatus()).toBe("final");
Expand All @@ -137,4 +139,51 @@ describe("blood pressure profile", () => {
expect(rawCode.coding).toEqual({ code: "8480-6", system: "http://loinc.org" });
expect(raw.valueQuantity).toBeUndefined();
});

test("create() with custom category preserves user values and adds required VSCat", () => {
const custom = bpProfile.create({
status: "final",
subject: { reference: "Patient/pt-1" },
category: [{ text: "My Category" }],
});
const obs = custom.toResource();
// User category kept, VSCat auto-added
expect(obs.category).toHaveLength(2);
expect(obs.category![0]!.text).toBe("My Category");
expect((obs.category![1] as Record<string, unknown>).coding).toEqual({
code: "vital-signs",
system: "http://terminology.hl7.org/CodeSystem/observation-category",
});
});

test("create() with empty category still adds required VSCat", () => {
const custom = bpProfile.create({
status: "final",
subject: { reference: "Patient/pt-1" },
category: [],
});
const obs = custom.toResource();
expect(obs.category).toHaveLength(1);
expect((obs.category![0] as Record<string, unknown>).coding).toEqual({
code: "vital-signs",
system: "http://terminology.hl7.org/CodeSystem/observation-category",
});
});

test("create() with category already containing VSCat does not duplicate it", () => {
const custom = bpProfile.create({
status: "final",
subject: { reference: "Patient/pt-1" },
category: [
{
coding: {
code: "vital-signs",
system: "http://terminology.hl7.org/CodeSystem/observation-category",
} as unknown,
} as never,
],
});
const obs = custom.toResource();
expect(obs.category).toHaveLength(1);
});
});
Loading