Generated Narrative with Details
id : f001
identifier : 6323 (OFFICIAL)
status : final
code : Glucose [Moles/volume] in Blood (Details : {LOINC code '15074-8' = 'Glucose [Moles/volume] in Blood', given as 'Glucose [Moles/volume] in Blood'})
subject : P. van de Heuvel
effective : 02/04/2013 9:30:10 AM --> (ongoing)
issued : 03/04/2013 3:30:10 PM
performer : A. Langeveld
value : 6.3 mmol/l (Details: UCUM code mmol/L = 'mmol/L')
interpretation : High (Details : {http://hl7.org/fhir/v2/0078 code 'H' = 'High', given as 'High'})
ReferenceRanges - Low High * 3.1 mmol/l (Details: UCUM code mmol/L = 'mmol/L') 6.2 mmol/l (Details: UCUM code mmol/L = 'mmol/L')
"
7 | },
8 | "identifier": [
9 | {
10 | "use": "official",
11 | "system": "http://www.bmc.nl/zorgportal/identifiers/observations",
12 | "value": "6323"
13 | }
14 | ],
15 | "status": "final",
16 | "code": {
17 | "coding": [
18 | {
19 | "system": "http://loinc.org",
20 | "code": "15074-8",
21 | "display": "Glucose [Moles/volume] in Blood"
22 | }
23 | ]
24 | },
25 | "subject": {
26 | "reference": "Patient/f001",
27 | "display": "P. van de Heuvel"
28 | },
29 | "effectivePeriod": {
30 | "start": "2013-04-02T09:30:10+01:00"
31 | },
32 | "issued": "2013-04-03T15:30:10+01:00",
33 | "performer": [
34 | {
35 | "reference": "Practitioner/f005",
36 | "display": "A. Langeveld"
37 | }
38 | ],
39 | "valueQuantity": {
40 | "value": 6.3,
41 | "unit": "mmol/l",
42 | "system": "http://unitsofmeasure.org",
43 | "code": "mmol/L"
44 | },
45 | "referenceRange": [
46 | {
47 | "low": {
48 | "value": 3.1,
49 | "unit": "mmol/l",
50 | "system": "http://unitsofmeasure.org",
51 | "code": "mmol/L"
52 | },
53 | "high": {
54 | "value": 6.2,
55 | "unit": "mmol/l",
56 | "system": "http://unitsofmeasure.org",
57 | "code": "mmol/L"
58 | }
59 | }
60 | ]
61 | }
--------------------------------------------------------------------------------
/loinc2hpo-core/src/main/java/org/monarchinitiative/loinc2hpocore/codesystems/Outcome.java:
--------------------------------------------------------------------------------
1 | package org.monarchinitiative.loinc2hpocore.codesystems;
2 |
3 | import java.util.Objects;
4 |
5 | public class Outcome implements Comparable {
6 |
7 |
8 | private final ShortCode code;
9 | private final String outcome;
10 |
11 |
12 | public Outcome(ShortCode code, String outcome) {
13 | this.code = code;
14 | this.outcome = outcome;
15 | }
16 |
17 | public Outcome(ShortCode code) {
18 | this.code = code;
19 | this.outcome = code.shortForm();
20 | }
21 |
22 | public ShortCode getCode() {
23 | return code;
24 | }
25 |
26 | public String getOutcome() {
27 | return outcome;
28 | }
29 |
30 |
31 | public static Outcome nominal(String outcomeString) {
32 | return new Outcome(ShortCode.NOM, outcomeString);
33 | }
34 |
35 | public boolean isNominal() {
36 | return this.code.equals(ShortCode.NOM);
37 | }
38 |
39 | public boolean isQuantitative() {
40 | return this.code.equals(ShortCode.L) || this.code.equals(ShortCode.N) || this.code.equals(ShortCode.H);
41 | }
42 |
43 | public boolean isOrdinal() {
44 | return this.code.equals(ShortCode.NEG) || this.code.equals(ShortCode.POS);
45 | }
46 |
47 | public static Outcome LOW() {
48 | return new Outcome(ShortCode.L);
49 | }
50 |
51 | public static Outcome NORMAL() {
52 | return new Outcome(ShortCode.N);
53 | }
54 |
55 | public static Outcome HIGH() {
56 | return new Outcome(ShortCode.H);
57 | }
58 |
59 | public static Outcome POSITIVE() {
60 | return new Outcome(ShortCode.POS);
61 | }
62 |
63 | public static Outcome NEGATIVE() {
64 | return new Outcome(ShortCode.NEG);
65 | }
66 |
67 | @Override
68 | public int hashCode() {
69 | return Objects.hash(this.code, this.outcome);
70 | }
71 |
72 | @Override
73 | public boolean equals(Object obj) {
74 | if (! (obj instanceof Outcome)) {
75 | return false;
76 | }
77 | Outcome that = (Outcome) obj;
78 | return this.code.equals(that.code) && this.outcome.equals(that.outcome);
79 | }
80 |
81 | @Override
82 | public String toString() {
83 | if (code.equals(ShortCode.NOM)) {
84 | return "Nom: " + outcome;
85 | } else {
86 | return code.name();
87 | }
88 | }
89 |
90 | @Override
91 | public int compareTo(Outcome that) {
92 | int res = this.code.compareTo(that.code);
93 | return res != 0 ? res : this.outcome.compareTo(that.outcome);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/test/resources/json/glucoseHigh.fhir:
--------------------------------------------------------------------------------
1 | {
2 | "resourceType": "Observation",
3 | "id": "f001",
4 | "text": {
5 | "status": "generated",
6 | "div": "Generated Narrative with Details
id : f001
identifier : 6323 (OFFICIAL)
status : final
code : Glucose [Moles/volume] in Blood (Details : {LOINC code '15074-8' = 'Glucose [Moles/volume] in Blood', given as 'Glucose [Moles/volume] in Blood'})
subject : P. van de Heuvel
effective : 02/04/2013 9:30:10 AM --> (ongoing)
issued : 03/04/2013 3:30:10 PM
performer : A. Langeveld
value : 6.3 mmol/l (Details: UCUM code mmol/L = 'mmol/L')
interpretation : High (Details : {http://hl7.org/fhir/v2/0078 code 'H' = 'High', given as 'High'})
ReferenceRanges - Low High * 3.1 mmol/l (Details: UCUM code mmol/L = 'mmol/L') 6.2 mmol/l (Details: UCUM code mmol/L = 'mmol/L')
"
7 | },
8 | "identifier": [
9 | {
10 | "use": "official",
11 | "system": "http://www.bmc.nl/zorgportal/identifiers/observations",
12 | "value": "6323"
13 | }
14 | ],
15 | "status": "final",
16 | "code": {
17 | "coding": [
18 | {
19 | "system": "http://loinc.org",
20 | "code": "15074-8",
21 | "display": "Glucose [Moles/volume] in Blood"
22 | }
23 | ]
24 | },
25 | "subject": {
26 | "reference": "Patient/f001",
27 | "display": "P. van de Heuvel"
28 | },
29 | "effectivePeriod": {
30 | "start": "2013-04-02T09:30:10+01:00"
31 | },
32 | "issued": "2013-04-03T15:30:10+01:00",
33 | "performer": [
34 | {
35 | "reference": "Practitioner/f005",
36 | "display": "A. Langeveld"
37 | }
38 | ],
39 | "valueQuantity": {
40 | "value": 6.3,
41 | "unit": "mmol/l",
42 | "system": "http://unitsofmeasure.org",
43 | "code": "mmol/L"
44 | },
45 | "interpretation": {
46 | "coding": [
47 | {
48 | "system": "http://hl7.org/fhir/v2/0078",
49 | "code": "H",
50 | "display": "High"
51 | }
52 | ]
53 | },
54 | "referenceRange": [
55 | {
56 | "low": {
57 | "value": 3.1,
58 | "unit": "mmol/l",
59 | "system": "http://unitsofmeasure.org",
60 | "code": "mmol/L"
61 | },
62 | "high": {
63 | "value": 6.2,
64 | "unit": "mmol/l",
65 | "system": "http://unitsofmeasure.org",
66 | "code": "mmol/L"
67 | }
68 | }
69 | ]
70 | }
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/test/resources/json/glucoseLow.fhir:
--------------------------------------------------------------------------------
1 | {
2 | "resourceType": "Observation",
3 | "id": "f001",
4 | "text": {
5 | "status": "generated",
6 | "div": "Generated Narrative with Details
id : f001
identifier : 6323 (OFFICIAL)
status : final
code : Glucose [Moles/volume] in Blood (Details : {LOINC code '15074-8' = 'Glucose [Moles/volume] in Blood', given as 'Glucose [Moles/volume] in Blood'})
subject : P. van de Heuvel
effective : 02/04/2013 9:30:10 AM --> (ongoing)
issued : 03/04/2013 3:30:10 PM
performer : A. Langeveld
value : 6.3 mmol/l (Details: UCUM code mmol/L = 'mmol/L')
interpretation : High (Details : {http://hl7.org/fhir/v2/0078 code 'H' = 'High', given as 'High'})
ReferenceRanges - Low High * 3.1 mmol/l (Details: UCUM code mmol/L = 'mmol/L') 6.2 mmol/l (Details: UCUM code mmol/L = 'mmol/L')
"
7 | },
8 | "identifier": [
9 | {
10 | "use": "official",
11 | "system": "http://www.bmc.nl/zorgportal/identifiers/observations",
12 | "value": "6323"
13 | }
14 | ],
15 | "status": "final",
16 | "code": {
17 | "coding": [
18 | {
19 | "system": "http://loinc.org",
20 | "code": "15074-8",
21 | "display": "Glucose [Moles/volume] in Blood"
22 | }
23 | ]
24 | },
25 | "subject": {
26 | "reference": "Patient/f001",
27 | "display": "P. van de Heuvel"
28 | },
29 | "effectivePeriod": {
30 | "start": "2013-04-02T09:30:10+01:00"
31 | },
32 | "issued": "2013-04-03T15:30:10+01:00",
33 | "performer": [
34 | {
35 | "reference": "Practitioner/f005",
36 | "display": "A. Langeveld"
37 | }
38 | ],
39 | "valueQuantity": {
40 | "value": 2.3,
41 | "unit": "mmol/l",
42 | "system": "http://unitsofmeasure.org",
43 | "code": "mmol/L"
44 | },
45 | "interpretation": {
46 | "coding": [
47 | {
48 | "system": "http://hl7.org/fhir/v2/0078",
49 | "code": "L",
50 | "display": "Low"
51 | }
52 | ]
53 | },
54 | "referenceRange": [
55 | {
56 | "low": {
57 | "value": 3.1,
58 | "unit": "mmol/l",
59 | "system": "http://unitsofmeasure.org",
60 | "code": "mmol/L"
61 | },
62 | "high": {
63 | "value": 6.2,
64 | "unit": "mmol/l",
65 | "system": "http://unitsofmeasure.org",
66 | "code": "mmol/L"
67 | }
68 | }
69 | ]
70 | }
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/test/resources/json/glucoseNormal.fhir:
--------------------------------------------------------------------------------
1 | {
2 | "resourceType": "Observation",
3 | "id": "f001",
4 | "text": {
5 | "status": "generated",
6 | "div": "Generated Narrative with Details
id : f001
identifier : 6323 (OFFICIAL)
status : final
code : Glucose [Moles/volume] in Blood (Details : {LOINC code '15074-8' = 'Glucose [Moles/volume] in Blood', given as 'Glucose [Moles/volume] in Blood'})
subject : P. van de Heuvel
effective : 02/04/2013 9:30:10 AM --> (ongoing)
issued : 03/04/2013 3:30:10 PM
performer : A. Langeveld
value : 6.3 mmol/l (Details: UCUM code mmol/L = 'mmol/L')
interpretation : High (Details : {http://hl7.org/fhir/v2/0078 code 'H' = 'High', given as 'High'})
ReferenceRanges - Low High * 3.1 mmol/l (Details: UCUM code mmol/L = 'mmol/L') 6.2 mmol/l (Details: UCUM code mmol/L = 'mmol/L')
"
7 | },
8 | "identifier": [
9 | {
10 | "use": "official",
11 | "system": "http://www.bmc.nl/zorgportal/identifiers/observations",
12 | "value": "6323"
13 | }
14 | ],
15 | "status": "final",
16 | "code": {
17 | "coding": [
18 | {
19 | "system": "http://loinc.org",
20 | "code": "15074-8",
21 | "display": "Glucose [Moles/volume] in Blood"
22 | }
23 | ]
24 | },
25 | "subject": {
26 | "reference": "Patient/f001",
27 | "display": "P. van de Heuvel"
28 | },
29 | "effectivePeriod": {
30 | "start": "2013-04-02T09:30:10+01:00"
31 | },
32 | "issued": "2013-04-03T15:30:10+01:00",
33 | "performer": [
34 | {
35 | "reference": "Practitioner/f005",
36 | "display": "A. Langeveld"
37 | }
38 | ],
39 | "valueQuantity": {
40 | "value": 5.3,
41 | "unit": "mmol/l",
42 | "system": "http://unitsofmeasure.org",
43 | "code": "mmol/L"
44 | },
45 | "interpretation": {
46 | "coding": [
47 | {
48 | "system": "http://hl7.org/fhir/v2/0078",
49 | "code": "N",
50 | "display": "Normal"
51 | }
52 | ]
53 | },
54 | "referenceRange": [
55 | {
56 | "low": {
57 | "value": 3.1,
58 | "unit": "mmol/l",
59 | "system": "http://unitsofmeasure.org",
60 | "code": "mmol/L"
61 | },
62 | "high": {
63 | "value": 6.2,
64 | "unit": "mmol/l",
65 | "system": "http://unitsofmeasure.org",
66 | "code": "mmol/L"
67 | }
68 | }
69 | ]
70 | }
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/test/resources/json/glucoseAbnormal.fhir:
--------------------------------------------------------------------------------
1 | {
2 | "resourceType": "Observation",
3 | "id": "f001",
4 | "text": {
5 | "status": "generated",
6 | "div": "Generated Narrative with Details
id : f001
identifier : 6323 (OFFICIAL)
status : final
code : Glucose [Moles/volume] in Blood (Details : {LOINC code '15074-8' = 'Glucose [Moles/volume] in Blood', given as 'Glucose [Moles/volume] in Blood'})
subject : P. van de Heuvel
effective : 02/04/2013 9:30:10 AM --> (ongoing)
issued : 03/04/2013 3:30:10 PM
performer : A. Langeveld
value : 6.3 mmol/l (Details: UCUM code mmol/L = 'mmol/L')
interpretation : High (Details : {http://hl7.org/fhir/v2/0078 code 'H' = 'High', given as 'High'})
ReferenceRanges - Low High * 3.1 mmol/l (Details: UCUM code mmol/L = 'mmol/L') 6.2 mmol/l (Details: UCUM code mmol/L = 'mmol/L')
"
7 | },
8 | "identifier": [
9 | {
10 | "use": "official",
11 | "system": "http://www.bmc.nl/zorgportal/identifiers/observations",
12 | "value": "6323"
13 | }
14 | ],
15 | "status": "final",
16 | "code": {
17 | "coding": [
18 | {
19 | "system": "http://loinc.org",
20 | "code": "15074-8",
21 | "display": "Glucose [Moles/volume] in Blood"
22 | }
23 | ]
24 | },
25 | "subject": {
26 | "reference": "Patient/f001",
27 | "display": "P. van de Heuvel"
28 | },
29 | "effectivePeriod": {
30 | "start": "2013-04-02T09:30:10+01:00"
31 | },
32 | "issued": "2013-04-03T15:30:10+01:00",
33 | "performer": [
34 | {
35 | "reference": "Practitioner/f005",
36 | "display": "A. Langeveld"
37 | }
38 | ],
39 | "valueQuantity": {
40 | "value": 6.3,
41 | "unit": "mmol/l",
42 | "system": "http://unitsofmeasure.org",
43 | "code": "mmol/L"
44 | },
45 | "interpretation": {
46 | "coding": [
47 | {
48 | "system": "http://hl7.org/fhir/v2/0078",
49 | "code": "A",
50 | "display": "Abnormal"
51 | }
52 | ]
53 | },
54 | "referenceRange": [
55 | {
56 | "low": {
57 | "value": 3.1,
58 | "unit": "mmol/l",
59 | "system": "http://unitsofmeasure.org",
60 | "code": "mmol/L"
61 | },
62 | "high": {
63 | "value": 6.2,
64 | "unit": "mmol/l",
65 | "system": "http://unitsofmeasure.org",
66 | "code": "mmol/L"
67 | }
68 | }
69 | ]
70 | }
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/test/resources/json/hemoglobin.fhir:
--------------------------------------------------------------------------------
1 | {
2 | "resourceType": "Observation",
3 | "id": "f005",
4 | "text": {
5 | "status": "generated",
6 | "div": "Generated Narrative with Details
id : f005
identifier : 6327 (OFFICIAL)
status : final
code : Hemoglobin [Mass/volume] in Blood (Details : {LOINC code '718-7' = 'Hemoglobin [Mass/volume] in Blood', given as 'Hemoglobin [Mass/volume] in Blood'})
subject : P. van de Heuvel
effective : 05/04/2013 10:30:10 AM --> 05/04/2013 10:30:10 AM
issued : 05/04/2013 3:30:10 PM
performer : A. Langeveld
value : 7.2 g/dl (Details: UCUM code g/dL = 'g/dL')
interpretation : Low (Details : {http://hl7.org/fhir/v2/0078 code 'L' = 'Low', given as 'Low'})
ReferenceRanges - Low High * 7.5 g/dl (Details: UCUM code g/dL = 'g/dL') 10 g/dl (Details: UCUM code g/dL = 'g/dL')
"
7 | },
8 | "identifier": [
9 | {
10 | "use": "official",
11 | "system": "http://www.bmc.nl/zorgportal/identifiers/observations",
12 | "value": "6327"
13 | }
14 | ],
15 | "status": "final",
16 | "code": {
17 | "coding": [
18 | {
19 | "system": "http://loinc.org",
20 | "code": "718-7",
21 | "display": "Hemoglobin [Mass/volume] in Blood"
22 | }
23 | ]
24 | },
25 | "subject": {
26 | "reference": "Patient/f001",
27 | "display": "P. van de Heuvel"
28 | },
29 | "effectivePeriod": {
30 | "start": "2013-04-05T10:30:10+01:00",
31 | "end": "2013-04-05T10:30:10+01:00"
32 | },
33 | "issued": "2013-04-05T15:30:10+01:00",
34 | "performer": [
35 | {
36 | "reference": "Practitioner/f005",
37 | "display": "A. Langeveld"
38 | }
39 | ],
40 | "valueQuantity": {
41 | "value": 7.2,
42 | "unit": "g/dl",
43 | "system": "http://unitsofmeasure.org",
44 | "code": "g/dL"
45 | },
46 | "interpretation": {
47 | "coding": [
48 | {
49 | "system": "http://hl7.org/fhir/v2/0078",
50 | "code": "L",
51 | "display": "Low"
52 | }
53 | ]
54 | },
55 | "referenceRange": [
56 | {
57 | "low": {
58 | "value": 7.5,
59 | "unit": "g/dl",
60 | "system": "http://unitsofmeasure.org",
61 | "code": "g/dL"
62 | },
63 | "high": {
64 | "value": 10,
65 | "unit": "g/dl",
66 | "system": "http://unitsofmeasure.org",
67 | "code": "g/dL"
68 | }
69 | }
70 | ]
71 | }
--------------------------------------------------------------------------------
/loinc2hpo-core/src/test/java/org/monarchinitiative/loinc2hpocore/loinc/LoincEntryTest.java:
--------------------------------------------------------------------------------
1 | package org.monarchinitiative.loinc2hpocore.loinc;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.monarchinitiative.loinc2hpocore.annotation.LoincScale;
5 |
6 | import java.util.Arrays;
7 | import java.util.List;
8 | import java.util.stream.Collectors;
9 |
10 | import static org.junit.jupiter.api.Assertions.assertEquals;
11 |
12 |
13 | /*
14 | "LOINC_NUM","COMPONENT","PROPERTY","TIME_ASPCT","SYSTEM","SCALE_TYP","METHOD_TYP","CLASS","CLASSTYPE","LONG_COMMON_NAME","SHORTNAME","EXTERNAL_COPYRIGHT_NOTICE","STATUS","VersionFirstReleased","VersionLastChanged"
15 |
16 | */
17 | public class LoincEntryTest {
18 |
19 | @Test
20 | void testConstruction() {
21 | String [] entryFields = {"10000-8","R wave duration.lead AVR","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead AVR","R wave dur L-AVR","","ACTIVE","1.0i","2.48"};
22 | List quotedEntryFields = Arrays.stream(entryFields).map(w -> String.format("\"%s\"", w)).collect(Collectors.toList());
23 | String entryLine1 = String.join(",", quotedEntryFields);
24 | LoincEntry entry = LoincEntry.fromQuotedCsvLine(entryLine1);
25 | assertEquals("R wave duration.lead AVR", entry.getComponent());
26 | assertEquals("Pt", entry.getTimeAspect());
27 | LoincId id = new LoincId("10000-8");
28 | assertEquals(id, entry.getLoincId());
29 | assertEquals("EKG",entry.getMethod());
30 | }
31 |
32 | @Test
33 | void testDichlorophenoxyacetate() {
34 | String [] fields = { "9806-1","2,4-Dichlorophenoxyacetate","MCnc","Pt","Urine","Qn","","DRUG/TOX","1","2,4-Dichlorophenoxyacetate [Mass/volume] in Urine","2,4D Ur-mCnc","","ACTIVE","1.0i","2.42"};
35 | List quotedEntryFields = Arrays.stream(fields).map(w -> String.format("\"%s\"", w)).collect(Collectors.toList());
36 | String line = String.join(",",quotedEntryFields);
37 | LoincEntry entry = LoincEntry.fromQuotedCsvLine(line);
38 | LoincId loincId = new LoincId("9806-1");
39 | assertEquals(loincId, entry.getLoincId());
40 | assertEquals("2,4-Dichlorophenoxyacetate", entry.getComponent());
41 | assertEquals("MCnc", entry.getProperty());
42 | assertEquals("Pt", entry.getTimeAspect());
43 | assertEquals("Urine", entry.getSystem());
44 | assertEquals(LoincScale.QUANTITATIVE, entry.getScale());
45 | assertEquals("", entry.getMethod());
46 | assertEquals("2,4-Dichlorophenoxyacetate [Mass/volume] in Urine", entry.getLongName());
47 | LoincLongName lln = entry.getLoincLongName();
48 | assertEquals("Urine", lln.getLoincTissue());
49 | assertEquals("2,4-Dichlorophenoxyacetate", lln.getLoincParameter());
50 | }
51 |
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/test/resources/json/glucoseConflictingInterpretation.fhir:
--------------------------------------------------------------------------------
1 | {
2 | "resourceType": "Observation",
3 | "id": "f001",
4 | "text": {
5 | "status": "generated",
6 | "div": "Generated Narrative with Details
id : f001
identifier : 6323 (OFFICIAL)
status : final
code : Glucose [Moles/volume] in Blood (Details : {LOINC code '15074-8' = 'Glucose [Moles/volume] in Blood', given as 'Glucose [Moles/volume] in Blood'})
subject : P. van de Heuvel
effective : 02/04/2013 9:30:10 AM --> (ongoing)
issued : 03/04/2013 3:30:10 PM
performer : A. Langeveld
value : 6.3 mmol/l (Details: UCUM code mmol/L = 'mmol/L')
interpretation : High (Details : {http://hl7.org/fhir/v2/0078 code 'H' = 'High', given as 'High'})
ReferenceRanges - Low High * 3.1 mmol/l (Details: UCUM code mmol/L = 'mmol/L') 6.2 mmol/l (Details: UCUM code mmol/L = 'mmol/L')
"
7 | },
8 | "identifier": [
9 | {
10 | "use": "official",
11 | "system": "http://www.bmc.nl/zorgportal/identifiers/observations",
12 | "value": "6323"
13 | }
14 | ],
15 | "status": "final",
16 | "code": {
17 | "coding": [
18 | {
19 | "system": "http://loinc.org",
20 | "code": "15074-8",
21 | "display": "Glucose [Moles/volume] in Blood"
22 | }
23 | ]
24 | },
25 | "subject": {
26 | "reference": "Patient/f001",
27 | "display": "P. van de Heuvel"
28 | },
29 | "effectivePeriod": {
30 | "start": "2013-04-02T09:30:10+01:00"
31 | },
32 | "issued": "2013-04-03T15:30:10+01:00",
33 | "performer": [
34 | {
35 | "reference": "Practitioner/f005",
36 | "display": "A. Langeveld"
37 | }
38 | ],
39 | "valueQuantity": {
40 | "value": 6.3,
41 | "unit": "mmol/l",
42 | "system": "http://unitsofmeasure.org",
43 | "code": "mmol/L"
44 | },
45 | "interpretation": {
46 | "coding": [
47 | {
48 | "system": "http://hl7.org/fhir/v2/0078",
49 | "code": "H",
50 | "display": "High"
51 | },
52 | {
53 | "system": "http://hl7.org/fhir/v2/0078",
54 | "code": "L",
55 | "display": "Low"
56 | }
57 | ]
58 | },
59 | "referenceRange": [
60 | {
61 | "low": {
62 | "value": 3.1,
63 | "unit": "mmol/l",
64 | "system": "http://unitsofmeasure.org",
65 | "code": "mmol/L"
66 | },
67 | "high": {
68 | "value": 6.2,
69 | "unit": "mmol/l",
70 | "system": "http://unitsofmeasure.org",
71 | "code": "mmol/L"
72 | }
73 | }
74 | ]
75 | }
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/test/resources/json/erythrocyte.fhir:
--------------------------------------------------------------------------------
1 | {
2 | "resourceType": "Observation",
3 | "id": "f004",
4 | "text": {
5 | "status": "generated",
6 | "div": "Generated Narrative with Details
id : f004
identifier : 6326 (OFFICIAL)
status : final
code : Erythrocytes [#/volume] in Blood by Automated count (Details : {LOINC code '789-8' = 'Erythrocytes [#/volume] in Blood by Automated count', given as 'Erythrocytes [#/volume] in Blood by Automated count'})
subject : P. van de Heuvel
effective : 02/04/2013 10:30:10 AM --> 05/04/2013 10:30:10 AM
issued : 03/04/2013 3:30:10 PM
performer : A. Langeveld
value : 4.12 10^12/L (Details: UCUM code 10*12/L = '10*12/L')
interpretation : Low (Details : {http://hl7.org/fhir/v2/0078 code 'L' = 'Low', given as 'Low'})
ReferenceRanges - Text * 12-14 y Male: 4.4 - 5.2 x 10^12/L ; 12-14 y Female: 4.2 - 4.8 x 10^12/L ; 15-17 y Male: 4.6 - 5.4 x 10^12/L ; 15-17 y Female: 4.2 - 4.8 x 10^12/L ; 18-64 y Male: 4.6 - 5.4 x 10^12/L ; 18-64 y Female: 4.0 - 4.8 x 10^12/L ; 65-74 y Male: 4.3 - 5.3 x 10^12/L ; 65-74 y Female: 4.1 - 4.9 x 10^12/L
"
7 | },
8 | "identifier": [
9 | {
10 | "use": "official",
11 | "system": "http://www.bmc.nl/zorgportal/identifiers/observations",
12 | "value": "6326"
13 | }
14 | ],
15 | "status": "final",
16 | "code": {
17 | "coding": [
18 | {
19 | "system": "http://loinc.org",
20 | "code": "789-8",
21 | "display": "Erythrocytes [#/volume] in Blood by Automated count"
22 | }
23 | ]
24 | },
25 | "subject": {
26 | "reference": "Patient/f001",
27 | "display": "P. van de Heuvel"
28 | },
29 | "effectivePeriod": {
30 | "start": "2013-04-02T10:30:10+01:00",
31 | "end": "2013-04-05T10:30:10+01:00"
32 | },
33 | "issued": "2013-04-03T15:30:10+01:00",
34 | "performer": [
35 | {
36 | "reference": "Practitioner/f005",
37 | "display": "A. Langeveld"
38 | }
39 | ],
40 | "valueQuantity": {
41 | "value": 4.12,
42 | "unit": "10^12/L",
43 | "system": "http://unitsofmeasure.org",
44 | "code": "10*12/L"
45 | },
46 | "interpretation": {
47 | "coding": [
48 | {
49 | "system": "http://hl7.org/fhir/v2/0078",
50 | "code": "L",
51 | "display": "Low"
52 | }
53 | ]
54 | },
55 | "referenceRange": [
56 | {
57 | "text": " 12-14 y Male: 4.4 - 5.2 x 10^12/L ; 12-14 y Female: 4.2 - 4.8 x 10^12/L ; 15-17 y Male: 4.6 - 5.4 x 10^12/L ; 15-17 y Female: 4.2 - 4.8 x 10^12/L ; 18-64 y Male: 4.6 - 5.4 x 10^12/L ; 18-64 y Female: 4.0 - 4.8 x 10^12/L ; 65-74 y Male: 4.3 - 5.3 x 10^12/L ; 65-74 y Female: 4.1 - 4.9 x 10^12/L "
58 | }
59 | ]
60 | }
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/test/java/fhir/Dstu3ObservationTest.java:
--------------------------------------------------------------------------------
1 | package fhir;
2 |
3 | import org.hl7.fhir.dstu3.model.Observation;
4 | import org.junit.jupiter.api.Test;
5 | import org.monarchinitiative.loinc2hpocore.codesystems.Outcome;
6 | import org.monarchinitiative.loinc2hpocore.loinc.LoincId;
7 | import org.monarchinitiative.loinc2hpofhir.fhir2hpo.ObservationDtu3;
8 | import org.monarchinitiative.loinc2hpofhir.fhir2hpo.Uberobservation;
9 |
10 | import java.io.IOException;
11 | import java.util.Optional;
12 |
13 | import static fhir.TestBase.importDstu3Observation;
14 | import static org.junit.jupiter.api.Assertions.*;
15 |
16 | public class Dstu3ObservationTest {
17 |
18 |
19 |
20 | @Test
21 | void testGlucoseHigh() throws IOException {
22 | String jsonPath = "json/glucoseHigh.fhir";
23 | Observation glucoseHigh = importDstu3Observation(jsonPath);
24 | Uberobservation uberobservation = new ObservationDtu3(glucoseHigh);
25 | LoincId expectedLoincId = new LoincId("15074-8");
26 | Optional opt = uberobservation.getLoincId();
27 | assertTrue(opt.isPresent());
28 | assertEquals(expectedLoincId, opt.get());
29 | Optional opt2 = uberobservation.getOutcome();
30 | assertTrue(opt2.isPresent());
31 | assertEquals(Outcome.HIGH(), opt2.get());
32 | assertNotEquals(Outcome.LOW(), opt2.get());
33 | assertNotEquals(Outcome.NORMAL(), opt2.get());
34 | }
35 |
36 | @Test
37 | void testGlucoseLow() throws IOException {
38 | String jsonPath = "json/glucoseLow.fhir";
39 | Observation glucoseAbnormal = importDstu3Observation(jsonPath);
40 | Uberobservation uberobservation = new ObservationDtu3(glucoseAbnormal);
41 | LoincId expectedLoincId = new LoincId("15074-8");
42 | Optional opt = uberobservation.getLoincId();
43 | assertTrue(opt.isPresent());
44 | assertEquals(expectedLoincId, opt.get());
45 | Optional opt2 = uberobservation.getOutcome();
46 | assertTrue(opt2.isPresent());
47 | assertEquals(Outcome.LOW(), opt2.get());
48 | assertNotEquals(Outcome.NORMAL(), opt2.get());
49 | assertNotEquals(Outcome.HIGH(), opt2.get());
50 | }
51 |
52 | @Test
53 | void testGlucoseNormal() throws IOException {
54 | String jsonPath = "json/glucoseNormal.fhir";
55 | Observation glucoseAbnormal = importDstu3Observation(jsonPath);
56 | Uberobservation uberobservation = new ObservationDtu3(glucoseAbnormal);
57 | LoincId expectedLoincId = new LoincId("15074-8");
58 | Optional opt = uberobservation.getLoincId();
59 | assertTrue(opt.isPresent());
60 | assertEquals(expectedLoincId, opt.get());
61 | Optional opt2 = uberobservation.getOutcome();
62 | assertTrue(opt2.isPresent());
63 | assertEquals(Outcome.NORMAL(), opt2.get());
64 | assertNotEquals(Outcome.LOW(), opt2.get());
65 | assertNotEquals(Outcome.HIGH(), opt2.get());
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/loinc2hpo-core/src/main/java/org/monarchinitiative/loinc2hpocore/exception/Loinc2HpoRuntimeException.java:
--------------------------------------------------------------------------------
1 | package org.monarchinitiative.loinc2hpocore.exception;
2 |
3 | import org.monarchinitiative.loinc2hpocore.loinc.LoincId;
4 |
5 | public class Loinc2HpoRuntimeException extends RuntimeException {
6 |
7 |
8 | public Loinc2HpoRuntimeException() { super();}
9 | public Loinc2HpoRuntimeException(String msg) { super(msg);}
10 |
11 |
12 |
13 | public static Loinc2HpoRuntimeException unrecognizedCode(String code) {
14 | return new Loinc2HpoRuntimeException("Unrecognized result code: \"" + code + "\"");
15 | }
16 |
17 | public static Loinc2HpoRuntimeException noCodeFound() {
18 | return new Loinc2HpoRuntimeException("No results code found.");
19 | }
20 |
21 | public static Loinc2HpoRuntimeException internalCodeNotFound(String externalCound) {
22 | return new Loinc2HpoRuntimeException("Could not find internal code to match \"" + externalCound + "\".");
23 | }
24 |
25 |
26 | public static Loinc2HpoRuntimeException ambiguousResults() {
27 | return new Loinc2HpoRuntimeException("Multiple matching codes found (should never happen).");
28 | }
29 |
30 | public static Loinc2HpoRuntimeException notAnnotated(LoincId loincId) {
31 | return new Loinc2HpoRuntimeException("Could not find annotation for " + loincId.toString());
32 | }
33 |
34 | public static Loinc2HpoRuntimeException outComenotAnnotated(LoincId loincId) {
35 | return new Loinc2HpoRuntimeException("Could not find annotation for " + loincId.toString());
36 | }
37 |
38 | public static Loinc2HpoRuntimeException malformedLoincCode(String line) {
39 | return new Loinc2HpoRuntimeException("malformedLoincCode: \"" + line + "\"");
40 | }
41 |
42 | public static Loinc2HpoRuntimeException subjectNotFound() {
43 | return new Loinc2HpoRuntimeException("Could not find subject element");
44 | }
45 |
46 | public static Loinc2HpoRuntimeException ambiguousSubject() {
47 | return new Loinc2HpoRuntimeException("Found more than one subject element");
48 | }
49 |
50 | public static Loinc2HpoRuntimeException referenceRangeNotFound() {
51 | return new Loinc2HpoRuntimeException("Did not find a reference range");
52 | }
53 |
54 | public static Loinc2HpoRuntimeException ambiguousReferenceRange() {
55 | return new Loinc2HpoRuntimeException("Found more than one reference range");
56 | }
57 |
58 | public static Loinc2HpoRuntimeException unrecognizedLoincCodeException() {
59 | return new Loinc2HpoRuntimeException("Unrecognize LOINC code");
60 | }
61 |
62 | public static Loinc2HpoRuntimeException loincCodeNotFound() {
63 | return new Loinc2HpoRuntimeException("LOINC code not found");
64 | }
65 |
66 | public static Loinc2HpoRuntimeException missingPanelComponent() {
67 | return new Loinc2HpoRuntimeException("Missing LOINC panel component");
68 | }
69 |
70 |
71 | public static Exception malFormedAnnotationLine(String line, int length) {
72 | return new Loinc2HpoRuntimeException(String.format("Malformed line with %d fields: %s", length, line));
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/loinc2hpo-core/src/main/java/org/monarchinitiative/loinc2hpocore/annotation/OrdinalHpoAnnotation.java:
--------------------------------------------------------------------------------
1 | package org.monarchinitiative.loinc2hpocore.annotation;
2 |
3 | import org.monarchinitiative.loinc2hpocore.codesystems.Outcome;
4 | import org.monarchinitiative.loinc2hpocore.exception.Loinc2HpoRuntimeException;
5 | import org.monarchinitiative.loinc2hpocore.loinc.LoincId;
6 |
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.Optional;
10 |
11 |
12 | public class OrdinalHpoAnnotation implements LoincAnnotation {
13 | private final LoincId loincId;
14 | private final Loinc2HpoAnnotation negative;
15 | private final Loinc2HpoAnnotation positive;
16 |
17 | public OrdinalHpoAnnotation(Loinc2HpoAnnotation absent, Loinc2HpoAnnotation present) {
18 | this.negative = absent;
19 | this.positive = present;
20 | // assumption is that both annotation have the same LoincId, which will be true
21 | // unless there is some insanity
22 | this.loincId = this.negative.getLoincId();
23 | }
24 |
25 |
26 | @Override
27 | public Optional getOutcome(Outcome outcome) {
28 | switch (outcome.getCode()) {
29 | case NEG:
30 | return Optional.of(new Hpo2Outcome(negative.getHpoTermId(), Outcome.NEGATIVE()));
31 | case POS:
32 | return Optional.of(new Hpo2Outcome(positive.getHpoTermId(), Outcome.POSITIVE()));
33 | default:
34 | return Optional.empty();
35 | }
36 | }
37 |
38 | @Override
39 | public LoincId getLoincId() {
40 | return this.loincId;
41 | }
42 |
43 | @Override
44 | public List allAnnotations() {
45 | return List.of(negative, positive);
46 | }
47 |
48 | private static String getDebugInfo(Map outcomeMap) {
49 | StringBuilder sb = new StringBuilder("Malformed Ordinal outcomes\nn=").append(outcomeMap.size());
50 | for (var oc : outcomeMap.values()) {
51 | sb.append("\t[ERROR] ").append(oc).append("\n");
52 | }
53 | return sb.toString();
54 | }
55 |
56 |
57 | public static LoincAnnotation fromOutcomeMap(Map outcomeMap) {
58 | // there are only two possible Ordinal outcomes, so we just need to check for size
59 | if (outcomeMap.size() == 2 && outcomeMap.containsKey(Outcome.NEGATIVE()) && outcomeMap.containsKey(Outcome.POSITIVE())) {
60 | return new OrdinalHpoAnnotation(outcomeMap.get(Outcome.NEGATIVE()), outcomeMap.get(Outcome.POSITIVE()));
61 | } else {
62 | String msg = String.format("Could not create LoincAnnotation for ordinal: %s",
63 | getDebugInfo(outcomeMap));
64 | throw new Loinc2HpoRuntimeException(msg);
65 | }
66 | }
67 |
68 | @Override
69 | public LoincScale scale() {
70 | return LoincScale.ORDINAL;
71 | }
72 |
73 | @Override
74 | public String toString() {
75 | return loincId + "\n\t" +
76 | (negative == null? " NEG: n/a" : "NEG: " + negative) + "\n\t" +
77 | (positive == null? " POS: n/a" : "POS: " + positive);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/loinc2hpo-core/src/main/java/org/monarchinitiative/loinc2hpocore/io/LoincTableCoreParser.java:
--------------------------------------------------------------------------------
1 | package org.monarchinitiative.loinc2hpocore.io;
2 |
3 | import org.monarchinitiative.loinc2hpocore.exception.Loinc2HpoRuntimeException;
4 | import org.monarchinitiative.loinc2hpocore.loinc.LoincEntry;
5 | import org.monarchinitiative.loinc2hpocore.loinc.LoincId;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import java.io.BufferedReader;
10 | import java.io.FileReader;
11 | import java.io.IOException;
12 | import java.util.HashMap;
13 | import java.util.Map;
14 |
15 | /**
16 | * Parse LoincTableCore.csv file to obtain {@link org.monarchinitiative.loinc2hpocore.loinc.LoincEntry} objects
17 | */
18 | public class LoincTableCoreParser {
19 | private static final Logger LOGGER = LoggerFactory.getLogger(LoincTableCoreParser.class);
20 |
21 | private final Map loincEntries;
22 |
23 | private final int malformed;
24 | /** Count of LOINC codes with scales other than Qn, Ord, and Nom, which we skip. */
25 | private final int invalidScale;
26 |
27 | public LoincTableCoreParser(String pathToLoincCoreTable) {
28 | Map tmp = new HashMap<>();
29 | int count_malformed = 0;
30 | int count_invalid_scale = 0;
31 | int n=0;
32 | try (BufferedReader br = new BufferedReader(new FileReader(pathToLoincCoreTable))){
33 | String line;
34 | String header=br.readLine();
35 | if (! header.equals(LoincEntry.header)) {
36 | LOGGER.error(String.format("Malformed header line (%s) in Loinc File %s",header,pathToLoincCoreTable));
37 | throw new Loinc2HpoRuntimeException("Malformed LoincTableCore.tsv header line");
38 | }
39 | while ((line=br.readLine())!=null) {
40 | n++;
41 | try {
42 | LoincEntry entry = LoincEntry.fromQuotedCsvLine(line);
43 | if (entry.getScale().validForLoinc2Hpo()) {
44 | tmp.put(entry.getLoincId(), entry);
45 | } else {
46 | count_invalid_scale++;
47 | }
48 | } catch (Loinc2HpoRuntimeException e) {
49 | LOGGER.error(e.getMessage());
50 | LOGGER.error("Line {}: {}", n, line);
51 | count_malformed++;
52 | }
53 | }
54 | } catch (IOException e) {
55 | e.printStackTrace();
56 | }
57 |
58 | LOGGER.info(tmp.size() + " LOINC entries were created");
59 | malformed = count_malformed;
60 | invalidScale = count_invalid_scale;
61 | if (count_malformed>0) {
62 | LOGGER.error(count_malformed + " LOINC entries are malformed");
63 | }
64 | this.loincEntries = Map.copyOf(tmp); // immutable copy
65 | }
66 |
67 | public Map getLoincEntries() {
68 | return loincEntries;
69 | }
70 |
71 | public int getMalformed() {
72 | return malformed;
73 | }
74 |
75 | public int getInvalidScale() {
76 | return invalidScale;
77 | }
78 |
79 | public static Map load(String pathToLoincCoreTable) {
80 | LoincTableCoreParser parser = new LoincTableCoreParser(pathToLoincCoreTable);
81 | return parser.getLoincEntries();
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/test/java/fhir/FhirOutcomeCodeTest.java:
--------------------------------------------------------------------------------
1 | package fhir;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.monarchinitiative.loinc2hpocore.codesystems.ShortCode;
5 | import org.monarchinitiative.loinc2hpofhir.fhir2hpo.FhirOutcomeCode;
6 |
7 | import static org.junit.jupiter.api.Assertions.assertEquals;
8 |
9 | public class FhirOutcomeCodeTest {
10 |
11 | @Test
12 | public void low() {
13 | ShortCode low = ShortCode.L;
14 | String fhirOffScaleLow = "L";
15 | assertEquals(low, FhirOutcomeCode.fhir2shortcode(fhirOffScaleLow));
16 | }
17 |
18 | @Test
19 | public void offScaleLow() {
20 | ShortCode low = ShortCode.L;
21 | String fhirOffScaleLow = "<";
22 | assertEquals(low, FhirOutcomeCode.fhir2shortcode(fhirOffScaleLow));
23 | }
24 | @Test
25 | public void criticalLow() {
26 | ShortCode low = ShortCode.L;
27 | String fhirOffScaleLow = "LL";
28 | assertEquals(low, FhirOutcomeCode.fhir2shortcode(fhirOffScaleLow));
29 | }
30 |
31 | @Test
32 | public void significantLow() {
33 | ShortCode low = ShortCode.L;
34 | String fhirOffScaleLow = "LU";
35 | assertEquals(low, FhirOutcomeCode.fhir2shortcode(fhirOffScaleLow));
36 | }
37 |
38 | @Test
39 | public void high() {
40 | ShortCode high = ShortCode.H;
41 | String fhirOffScaleHigh = "H";
42 | assertEquals(high, FhirOutcomeCode.fhir2shortcode(fhirOffScaleHigh));
43 | }
44 |
45 | @Test
46 | public void offScaleHigh() {
47 | ShortCode high = ShortCode.H;
48 | String fhirOffScaleHigh = ">";
49 | assertEquals(high, FhirOutcomeCode.fhir2shortcode(fhirOffScaleHigh));
50 | }
51 |
52 |
53 | @Test
54 | public void criticallyHigh() {
55 | ShortCode high = ShortCode.H;
56 | String fhirOffScaleHigh = "HH";
57 | assertEquals(high, FhirOutcomeCode.fhir2shortcode(fhirOffScaleHigh));
58 | }
59 |
60 | @Test
61 | public void significantlyHigh() {
62 | ShortCode high = ShortCode.H;
63 | String fhirOffScaleHigh = "HU";
64 | assertEquals(high, FhirOutcomeCode.fhir2shortcode(fhirOffScaleHigh));
65 | }
66 |
67 |
68 | @Test
69 | public void abnormal() {
70 | ShortCode pos = ShortCode.POS;
71 | String fhirAbnormal = "A";
72 | assertEquals(pos, FhirOutcomeCode.fhir2shortcode(fhirAbnormal));
73 | }
74 |
75 | @Test
76 | public void criticallyAbnormal() {
77 | ShortCode pos = ShortCode.POS;
78 | String fhirCriticallyAbnormal = "AA";
79 | assertEquals(pos, FhirOutcomeCode.fhir2shortcode(fhirCriticallyAbnormal));
80 | }
81 |
82 | @Test
83 | public void positive() {
84 | ShortCode pos = ShortCode.POS;
85 | String fhirPositive = "POS";
86 | assertEquals(pos, FhirOutcomeCode.fhir2shortcode(fhirPositive));
87 | }
88 |
89 | @Test
90 | public void detected() {
91 | ShortCode pos = ShortCode.POS;
92 | String fhirDetected = "DET";
93 | assertEquals(pos, FhirOutcomeCode.fhir2shortcode(fhirDetected));
94 | }
95 |
96 | @Test
97 | public void negative() {
98 | ShortCode neg = ShortCode.NEG;
99 | String fhirNotDetected = "NEG";
100 | assertEquals(neg, FhirOutcomeCode.fhir2shortcode(fhirNotDetected));
101 | }
102 |
103 | @Test
104 | public void notDetected() {
105 | ShortCode neg = ShortCode.NEG;
106 | String fhirNotDetected = "ND";
107 | assertEquals(neg, FhirOutcomeCode.fhir2shortcode(fhirNotDetected));
108 | }
109 |
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/docs/getting_started.rst:
--------------------------------------------------------------------------------
1 | Getting started
2 | ===============
3 |
4 | Overview
5 | --------
6 |
7 | loinc2hpo is a Java library. It requires at least Java 11. It is intended to be used by other applications
8 | for specific purposes. This tutorial shows how to install loinc2hpo and how to use it in a typical Java program.
9 |
10 | Installation
11 | ------------
12 | First clone the library from GitHub. ::
13 |
14 | git clone https://github.com/monarch-initiative/loinc2hpo.git
15 |
16 | Now use maven to install the library. ::
17 |
18 | cd loinc2hpo
19 | mvn install
20 |
21 | We plan to place loinc2hpo on maven central in the future, which will make this step unnecessary.
22 |
23 | Using loinc2hpo in maven projects
24 | ---------------------------------
25 |
26 | To use the loinc2hpo in your own Java project, add the following to your pom file.
27 |
28 | .. code-block:: XML
29 |
30 |
31 | 1.7.0
32 |
33 | (...)
34 |
35 |
36 | org.monarchinitiative
37 | loinc2hpo-core
38 | ${loinc2hpo.version}
39 |
40 |
41 | org.monarchinitiative
42 | loinc2hpo-fhir
43 | ${loinc2hpo.version}
44 |
45 |
46 |
47 | The ``loinc2hpo-fhir`` module is only required for working with FHIR of course.
48 |
49 |
50 | Core module
51 | ~~~~~~~~~~~
52 |
53 | The loinc2hpo annotation file referenced in the following code is available from the
54 | `loinc2hpoAnnotation repository `_.
55 | To run the code, ingest the annotation file and then pass
56 |
57 |
58 |
59 | .. code-block:: java
60 |
61 | import org.monarchinitiative.loinc2hpocore.codesystems.ShortCode;
62 | import org.monarchinitiative.phenol.ontology.data.TermId;
63 | import org.monarchinitiative.loinc2hpocore.Loinc2Hpo;
64 | import org.monarchinitiative.loinc2hpocore.loinc.LoincId;
65 | import org.monarchinitiative.loinc2hpocore.codesystems.Outcome;
66 |
67 | String annot_file = "loinc2hpo-annotations.tsv";
68 | Loinc2Hpo loinc2Hpo = new Loinc2Hpo(annot_file);
69 | LoincId loincId = new LoincId("26515-7");
70 | Optional opt = loinc2Hpo.query(loincId, Outcome.LOW());
71 | if (opt.isPresent()) {
72 | Hpo2Outcome hpo2outcome = opt.get();
73 | TermId hpoId = hpo2outcome.getHpoId();
74 | Outcome outcome = hpo2outcome.outcome();
75 | // do something with the HPO term and the outcome (low in this example).
76 | }
77 |
78 |
79 | FHIR module
80 | ~~~~~~~~~~~
81 |
82 | The FHIR module is intended to be used with `HAPI FHIR `_.
83 | It can be used with the FHIR specifications DSTU3, R4, or R5.
84 |
85 | .. code-block:: java
86 |
87 | import org.monarchinitiative.loinc2hpofhir.Loinc2HpoFhir;
88 | import org.hl7.fhir.r5.model.*;
89 | import org.monarchinitiative.loinc2hpocore.codesystems.Outcome;
90 | import org.monarchinitiative.loinc2hpocore.loinc.LoincId;
91 |
92 | String annot_file = "loinc2hpo-annotations.tsv";
93 | Loinc2HpoFhir loinc2hpoFHIR = new Loinc2HpoFhir(String path);
94 | // The following is a R5 Observation
95 | Observation observation = getObservationFromSomewhere(); // your code does this
96 | Optional opt = loinc2hpoFHIR.r5(observation);
97 | if (opt.isPresent()) {
98 | Hpo2Outcome hpo2outcome = opt.get();
99 | TermId hpoId = hpo2outcome.getHpoId();
100 | Outcome outcome = hpo2outcome.outcome();
101 | // do something with the HPO term and the outcome.
102 | }
103 |
104 |
105 | The ``Loinc2HpoFhir`` has analogous methods called ``dstu3`` and ``r4`` for the other
106 | FHIR versions.
--------------------------------------------------------------------------------
/loinc2hpo-core/src/main/java/org/monarchinitiative/loinc2hpocore/io/Loinc2HpoAnnotationParser.java:
--------------------------------------------------------------------------------
1 | package org.monarchinitiative.loinc2hpocore.io;
2 |
3 |
4 | import org.monarchinitiative.loinc2hpocore.annotation.*;
5 | import org.monarchinitiative.loinc2hpocore.codesystems.ShortCode;
6 | import org.monarchinitiative.loinc2hpocore.exception.Loinc2HpoRuntimeException;
7 | import org.monarchinitiative.loinc2hpocore.loinc.LoincId;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | import java.io.*;
12 | import java.util.*;
13 | import java.util.function.Function;
14 | import java.util.stream.Collectors;
15 |
16 | /**
17 | * This class is responsible for parsing the {@code loinc2hpo-annotations.tsv} file that is available
18 | * at https://github.com/TheJacksonLaboratory/loinc2hpoAnnotation
19 | * @author Peter Robinson, Aaron Zhang
20 | */
21 | public class Loinc2HpoAnnotationParser {
22 | private final static Logger LOGGER = LoggerFactory.getLogger(Loinc2HpoAnnotationParser.class);
23 |
24 | private final List entries;
25 |
26 | public Loinc2HpoAnnotationParser(String path) {
27 | entries = importAnnotations(path);
28 | }
29 |
30 | private List importAnnotations(String path) {
31 | List entries = new ArrayList<>();
32 | try (BufferedReader reader = new BufferedReader(new FileReader(path))){
33 | String line = reader.readLine(); // header
34 | if (!line.equals(String.join("\t", Loinc2HpoAnnotation.headerFields))){
35 | String msg = String.format("Annotation header (%s) does not match expected fields (%s)",
36 | line, String.join("\t", Loinc2HpoAnnotation.headerFields));
37 | throw new Loinc2HpoRuntimeException(msg);
38 | }
39 | while ((line = reader.readLine()) != null){
40 | entries.add(Loinc2HpoAnnotation.fromAnnotationLine(line));
41 | }
42 | } catch (IOException e) {
43 | throw new Loinc2HpoRuntimeException(e.getMessage());
44 | }
45 | return entries;
46 | }
47 |
48 | /* Export the list of annotations to file. Intended for use by the loinc2hpominer tool. */
49 | public static void exportToTsv(List annotations, String path) throws IOException {
50 | File outfile = new File(path);
51 | LOGGER.info("Writing annotation data to {}", outfile.getAbsoluteFile());
52 | Collections.sort(annotations);
53 | BufferedWriter bw = new BufferedWriter(new FileWriter(outfile));
54 | String header = String.join("\t", Loinc2HpoAnnotation.headerFields);
55 | bw.write(header + "\n");
56 | for (var ann : annotations) {
57 | bw.write(ann.toTsv() + "\n");
58 | }
59 | bw.close();
60 | }
61 |
62 |
63 |
64 | public List getEntries() {
65 | return entries;
66 | }
67 |
68 | public Map loincToHpoAnnotationMap() {
69 | Map> result = entries.stream()
70 | .collect(Collectors.groupingBy(Loinc2HpoAnnotation::getLoincId,
71 | Collectors.mapping(Function.identity(),
72 | Collectors.toList())));
73 | Map outcomesMap = new TreeMap<>();
74 | for (var e : result.entrySet()) {
75 | LoincId loincId = e.getKey();
76 | List outcomes = e.getValue();
77 | LoincAnnotation lannot = Loinc2HpoAnnotation.outcomes2LoincAnnotation(outcomes);
78 | outcomesMap.put(loincId, lannot);
79 | }
80 | return outcomesMap;
81 | }
82 |
83 |
84 |
85 |
86 | public static List load(String path) {
87 | Loinc2HpoAnnotationParser parser = new Loinc2HpoAnnotationParser(path);
88 | return parser.getEntries();
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | loinc2hpo Documentation
2 | =======================
3 |
4 | Loinc2hpo is a Java library designed to convert the findings of laboratory tests to HPO codes.
5 | For instance, if the test is `LOINC 26515-7 Platelets [#/volume] in Blood `_
6 | and the outcome of the test is an abnormally low value, then we can infer the
7 | `Human Phenotype Ontology (HPO) `_ term
8 | `Thrombocytopenia HP:0001873 `_.
9 |
10 | The goal of this library is to encode EHR (Electronic Health Record) laboratory
11 | data using HPO terms to extend the kinds of analysis that can be performed.
12 | Laboratory results can be leveraged as phenotypic features for analysis.
13 |
14 | .. image:: images/mission.png
15 | :align: center
16 | :scale: 60 %
17 |
18 | The library currently has three modules.
19 |
20 | loinc2hpo-core
21 | ==============
22 |
23 | This library contains the core functionality. It imports the annotation file from
24 | the `loinc2hpoAnnotation `_ repository
25 | (loinc2hpo-annotations.tsv), and for any combination of LOINC Id (laboratory test) and
26 | outcome, it finds the appropriate HPO term if one exists. We use set of internal codes
27 | to represent lab outcomes.
28 |
29 | +---------+------------------------------------------------------------------------------+
30 | | Code | Explanation |
31 | +=========+==============================================================================+
32 | | L | Low (below normal range). Used for quantitative tests (Qn). |
33 | +---------+------------------------------------------------------------------------------+
34 | | H | High (above normal range). Used for quantitative tests (Qn). |
35 | +---------+------------------------------------------------------------------------------+
36 | | N | Normal (within normal range). Used for quantitative tests (Qn). |
37 | +---------+------------------------------------------------------------------------------+
38 | | NEG | Negative (not present, a normal result). Used for ordinal tests (Ord) |
39 | +---------+------------------------------------------------------------------------------+
40 | | POS | Positive (present, an abnormal result). Used for ordinal tests (Ord) |
41 | +---------+------------------------------------------------------------------------------+
42 | | NOM | Nominal (an abnormal result). Used for nominal tests (Nom) |
43 | +---------+------------------------------------------------------------------------------+
44 |
45 | loinc2hpo-fhir
46 | ==============
47 |
48 | This library provides an interface that extracts LOINC-encoded data from
49 | `FHIR - Fast Healthcare Interoperability Resources `_ data. Specifically,
50 | it provides an interface that takes a FHIR `Observation `_
51 | and attempts to extract a LOINC code and an outcome; if successful, these are passed to the
52 | core loinc2hpo module to get the corresponding HPO term.
53 |
54 | loinc2hpo-cli
55 | =============
56 |
57 | This is a command-line interface tool that can be used to obtain descriptive statistics or
58 | perform quality control of the input files.
59 |
60 | Contents
61 | ========
62 |
63 | .. toctree::
64 | :maxdepth: 1
65 |
66 | intro_to_LOINC
67 | intro_to_FHIR
68 | FHIR_mapping.rst
69 | getting_started
70 |
71 | GitHub repo
72 | -----------
73 | The source code of Loinc2hpo can be found at GitHub:
74 | https://github.com/monarch-initiative/loinc2hpo
75 |
76 | Contact
77 | -------
78 |
79 | Peter Robinson
80 | peter.robinson@jax.org
81 |
82 | `The Jackson Laboratory `_
83 | 10 Discovery Drive
84 | Farmington, CT
85 | USA
86 |
87 | Xingmin Aaron Zhang
88 | kingmanzhang@gmail.com
89 |
90 | Curation
91 | --------
92 |
93 | We have developed a JavaFX application to curate loinc2hpo data. This app is not needed to
94 | use the library, but may be of interest to potential contributors: https://github.com/pnrobinson/loinc2hpoMiner.
95 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2017, The Jackson Laboratory
4 | All rights reserved.
5 |
6 | This product includes all or a portion of the LOINC Table, LOINC Table Core, LOINC Panels and Forms File, LOINC Answer File, LOINC Part File, LOINC Group File, LOINC Document Ontology File, LOINC Hierarchies, LOINC Linguistic Variants File, LOINC/RSNA Radiology Playbook, LOINC/IEEE Medical Device Code Mapping Table, and LOINC Display Name File or is derived from one or more of the foregoing, subject to a license from Regenstrief Institute, Inc. Your use of the LOINC Table, LOINC Table Core, LOINC Panels and Forms File, LOINC Answer File, LOINC Part File, LOINC Group File, LOINC Document Ontology File, LOINC Hierarchies, LOINC Linguistic Variants File, LOINC/RSNA Radiology Playbook, LOINC/IEEE Medical Device Code Mapping Table, and LOINC Display Name File also is subject to this license, a copy of which is available https://loinc.org/license/. The current complete LOINC Table, LOINC Table Core, LOINC Panels and Forms File, LOINC Answer File, LOINC Part File, LOINC Group File, LOINC Document Ontology File, LOINC Hierarchies, LOINC Linguistic Variants File, LOINC/RSNA Radiology Playbook, LOINC/IEEE Medical Device Code Mapping Table, and LOINC Display Name File are available for download at http://loinc.org. The LOINC Table and LOINC codes are copyright © 1995-2018, Regenstrief Institute, Inc. and the Logical Observation Identifiers Names and Codes (LOINC) Committee. The LOINC Table, LOINC Table Core, LOINC Panels and Forms File, LOINC Answer File, LOINC Part File, LOINC Group File, LOINC Document Ontology File, LOINC Hierarchies, LOINC Linguistic Variants File, LOINC/RSNA Radiology Playbook, LOINC/IEEE Medical Device Code Mapping Table, and LOINC Display Name File are copyright © 1995-2018, Regenstrief Institute, Inc. All rights reserved. THE LOINC TABLE (IN ALL FORMATS), LOINC TABLE CORE, LOINC PANELS AND FORMS FILE, LOINC ANSWER FILE, LOINC PART FILE, LOINC GROUP FILE, LOINC DOCUMENT ONTOLOGY FILE, LOINC HIERARCHIES, LOINC LINGUISTIC VARIANTS FILE, LOINC/RSNA RADIOLOGY PLAYBOOK, LOINC/IEEE MEDICAL DEVICE CODE MAPPING TABLE, AND LOINC DISPLAY NAME FILE ARE PROVIDED "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. LOINC® is a registered United States trademark of Regenstrief Institute, Inc. A small portion of the LOINC Table may include content (e.g., survey instruments) that is subject to copyrights owned by third parties. Such content has been mapped to LOINC terms under applicable copyright and terms of use. Notice of such third-party copyright and license terms would need to be included if such content is included.
7 |
8 | Redistribution and use in source and binary forms, with or without
9 | modification, are permitted provided that the following conditions are met:
10 |
11 | * Redistributions of source code must retain the above copyright notice, this
12 | list of conditions and the following disclaimer.
13 |
14 | * Redistributions in binary form must reproduce the above copyright notice,
15 | this list of conditions and the following disclaimer in the documentation
16 | and/or other materials provided with the distribution.
17 |
18 | * Neither the name of the copyright holder nor the names of its
19 | contributors may be used to endorse or promote products derived from
20 | this software without specific prior written permission.
21 |
22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 |
--------------------------------------------------------------------------------
/docs/intro_to_LOINC.rst:
--------------------------------------------------------------------------------
1 | Introduction to LOINC
2 | =====================
3 |
4 | `LOINC `_ (Logical Observation Identifiers Names and Codes)
5 | provides a set of universal names and ID codes for identifying laboratory and clinical
6 | test results.
7 |
8 | LOINC currently provides ~92,000 entries that define the names and IDs of laboratory tests.
9 | The following shows three examples of LOINC codes:
10 |
11 | .. image:: images/loinc_examples.png
12 |
13 | Each LOINC entry represents a laboratory test.
14 |
15 | Parts of LOINC entry
16 | --------------------
17 |
18 | - ``LOINC``: unique identifier
19 | - ``Name``: structured term name
20 | - ``Component``: defines the analyte in the test
21 | - ``Property``: defines "kinds of quantities". It can be divided into five several categories, mass, substance, catalytic activity, and number or counts. Each category is further divided into subclasses, for example MCnc or "mass concentration" is a subclass of "mass" property, while ``NCnc`` or "number of concentration (count/vol)" and ``Naric`` or "number aeric (number per area)" are subclasses of counts.
22 | - ``Time``: defines whether a measurement was made at a moment, or aggregated from a series of physiologic states. The three examples are all ``PT`` ("points"), meaning that they are measurements at a single time point. As an example, a test on "daily urine amount" will be labeled as ``24H`` ("24 hours").
23 | - ``Aspect``: defines a modifier for a measurement over a duration. For example, "8H^max heart rate" means the "max" heart rate measured during an 8-hour period. Min, max, first, last, mean typically appear here.
24 | - ``System``: can be considered as the specimen for the test, such as "serum", "blood", "urine", "cerebrospinal fluid" etc.
25 | - ``Scale``" defines the scale of the measurement. Scale is the most important information for our application. The following table summarizes possible values of ``scale``.
26 | - ``Method``: defines the method used for the measurement.
27 |
28 |
29 | Table 1: LOINC Scale Types
30 |
31 | +----------------+------+-------------------------------------------------------------------------------------+
32 | | Scale Type | Abbr.| Description |
33 | +================+======+=====================================================================================+
34 | | Quantitative | Qn | The result of the test is a numeric value that relates to a continuous numeric |
35 | | | | scale. |
36 | +----------------+------+-------------------------------------------------------------------------------------+
37 | | Ordinal | Ord | Ordered categorical responses, e.g., positive, negative; |
38 | +----------------+------+-------------------------------------------------------------------------------------+
39 | | Quantitative | OrdQn| Test can be reported as either Ord or Qn. |
40 | +----------------+------+-------------------------------------------------------------------------------------+
41 | | Nominal | Nom | Nominal or categorical responses that do not have a natural ordering. |
42 | +----------------+------+-------------------------------------------------------------------------------------+
43 | | Narrative | Nar | Text narrative. |
44 | +----------------+------+-------------------------------------------------------------------------------------+
45 | | “Multi” | Multi| Many separate results structured as one text “glob” |
46 | +----------------+------+-------------------------------------------------------------------------------------+
47 | | Document | Doc | A document that could be in many formats (XML, narrative, etc.) |
48 | +----------------+------+-------------------------------------------------------------------------------------+
49 | | Set | Set | Used for clinical attachments |
50 | +----------------+------+-------------------------------------------------------------------------------------+
51 |
52 |
53 |
54 | ``Qn``, ``Ord`` and ``Nom`` are the three most frequently used LOINC codes,
55 | accounting for about 99% of data. ``Qn`` typically makes up about 80% of cases.
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # loinc2hpo Changelog
2 |
3 |
4 |
5 | ## current
6 | * v 1.1.8
7 |
8 | ## v1.1.8
9 | * Update to phenol 1.5.0
10 |
11 | ### loinc2hpo-core
12 | * Adding enums for FHIR observation codes
13 |
14 | ## v0.0.2
15 |
16 | ### loinc2hpogui
17 |
18 | * Added FHIR to HPO conversion dialog
19 |
20 | ### loinc2hpo-core
21 |
22 | * added FHIR parsing
23 |
24 | ## v1.0.1
25 | This is the first official release. Many features are added.
26 |
27 | ### loinc2hpogui
28 | Multiple features are added. This will be the baseline for future tracking.
29 |
30 | ### loinc2hpo-core
31 |
32 | * The core change is completely switching to FHIR to parse `observation` and `patient` resources.
33 |
34 | * Redesigned the annotation class.
35 | - Use Code (system/namespace, code) to ensure uniqueness
36 | - An HPO term for a coded result is wrapped in a class that also indicate whether the term should be negated.
37 | - A complete annotation for a Loinc code contains many `Code` - `(HPO term, isNegated)` list.
38 |
39 | * Redesigned the logic process from observation to HPO term.
40 | - The app first looks at whether the observation has an interpretation field.
41 | - If it does, it will first try to find an annotation for the interpretation code directly;
42 | - if it fails, it will try to convert convert the interpretation code to the internal code and then find the corresponding HPO term.
43 |
44 | - If the app fails the last step, it will try to use the raw value and interpret it with the reference ranges.
45 |
46 | ## v1.0.2
47 |
48 | * Build Jar with all dependencies with maven-assembly-plugin
49 |
50 | * Add META-INF/services to Core module because it appears that is what Jar requires
51 |
52 | ## v1.0.3
53 |
54 | * Refactor pom files.
55 |
56 | * Refactor gitignore file.
57 |
58 | ## v1.0.4
59 |
60 | * Allow adding multiple labels to Github issues
61 |
62 | * Disable the function to clear annotation fields during manual query
63 |
64 | * Loinc entries change color if they have been annotated
65 |
66 | * Add tooltips to HPO listview and treeview
67 |
68 | * Allow user to switch to previously selected Loinc list
69 |
70 | * Allow user to categorize Loinc entries
71 |
72 | ## v1.1.0
73 |
74 | * New develop version
75 |
76 | Additional changes for this version
77 |
78 | * Change menu `Edit` to `Configuration`
79 | - [ ] update tutorial
80 |
81 | * Create new features that allow user to manipulate a session
82 |
83 | * Automatically retrieve information from auto-saved data for last session
84 |
85 | ## v1.1.1
86 |
87 | * Session data now only saves terms for low, intermediate, and high value, instead for all 6 internal codes
88 |
89 | * Basic and Advanced data are stored separately
90 |
91 | ## v1.1.2
92 |
93 | * Session data now saves to a universal TSV file
94 |
95 | * Restrict internal mappings
96 |
97 | Qn will not be mapped to "Presence" or "Absence" and Ord (of "Presence" type) will not be mapped to "high", "low", "normal"
98 |
99 | * Internal codes changed match FHIR
100 |
101 | "system" is renamed to "FHIR";
102 | Code for "presence" changed from "P" to "POS", code for "not presence" changed from "NP" to "NEG" to be consistent with FHIR
103 |
104 | * Show version in "About" message
105 |
106 | * Refactor to remove deprecated classes
107 |
108 | * Allow user to specify the path to hp OWL and Obo
109 |
110 | ## v1.1.3
111 |
112 | * Add function to simulate patient data (patient and observation resources)
113 |
114 | * Add function to allow uploading simulated data to hapi-fhir server
115 |
116 | * Add function to allow downloading patient data from fhir server
117 |
118 | ## v1.1.4
119 |
120 | * Add function to restart the app when necessary
121 |
122 | * Prevent app from crashing when user's local HPO is outdated
123 |
124 | * Added classes to appTempData patient phenopacket (phenotype only)
125 | * Refactored to use phenol 1.0.0
126 |
127 | ## v1.1.5
128 |
129 | * Add feature to copy annotation for one LOINC and paste to multiple selections of similar LOINC
130 |
131 | * Add function to annotate LOINC panels
132 |
133 | * Add function to convert FHIR messages for LOINC panels
134 |
135 | * Add a LOINC list for tests with "unspecified specimen". Messages on those should be not converted to HPO.
136 |
137 | ## v1.1.6
138 |
139 | * Add feature to allow easy addition of LOINC lists: allow user to change colors of LOINC lists
140 |
141 | * Added HPO parser for owl format.
142 |
143 | * Refactored to use phenol 1.2.6
144 |
145 | ## v1.1.7
146 |
147 | * Add algorithms to appTempData patients
148 |
149 |
--------------------------------------------------------------------------------
/loinc2hpo-core/src/test/resources/LoincTableCoreTiny.csv:
--------------------------------------------------------------------------------
1 | "LOINC_NUM","COMPONENT","PROPERTY","TIME_ASPCT","SYSTEM","SCALE_TYP","METHOD_TYP","CLASS","CLASSTYPE","LONG_COMMON_NAME","SHORTNAME","EXTERNAL_COPYRIGHT_NOTICE","STATUS","VersionFirstReleased","VersionLastChanged"
2 | "10000-8","R wave duration.lead AVR","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead AVR","R wave dur L-AVR","","ACTIVE","1.0i","2.48"
3 | "10001-6","R wave duration.lead I","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead I","R wave dur L-I","","ACTIVE","1.0i","2.48"
4 | "10002-4","R wave duration.lead II","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead II","R wave dur L-II","","ACTIVE","1.0i","2.48"
5 | "10003-2","R wave duration.lead III","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead III","R wave dur L-III","","ACTIVE","1.0i","2.48"
6 | "10004-0","R wave duration.lead V1","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead V1","R wave dur L-V1","","ACTIVE","1.0i","2.48"
7 | "10005-7","R wave duration.lead V2","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead V2","R wave dur L-V2","","ACTIVE","1.0i","2.48"
8 | "10006-5","R wave duration.lead V3","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead V3","R wave dur L-V3","","ACTIVE","1.0i","2.48"
9 | "10007-3","R wave duration.lead V4","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead V4","R wave dur L-V4","","ACTIVE","1.0i","2.48"
10 | "10008-1","R wave duration.lead V5","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead V5","R wave dur L-V5","","ACTIVE","1.0i","2.48"
11 | "1000-9","DBG Ab","PrThr","Pt","Ser/Plas^BPU","Ord","","BLDBK","1","DBG Ab [Presence] in Serum or Plasma from Blood product unit","DBG Ab SerPl BPU Ql","","ACTIVE","1.0","2.56"
12 | "10009-9","R wave duration.lead V6","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead V6","R wave dur L-V6","","ACTIVE","1.0i","2.48"
13 | "10010-7","R' wave amplitude.lead AVF","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead AVF","R' wave Amp L-AVF","","ACTIVE","1.0i","2.48"
14 | "10011-5","R' wave amplitude.lead AVL","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead AVL","R' wave Amp L-AVL","","ACTIVE","1.0i","2.48"
15 | "10012-3","R' wave amplitude.lead AVR","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead AVR","R' wave Amp L-AVR","","ACTIVE","1.0i","2.48"
16 | "10013-1","R' wave amplitude.lead I","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead I","R' wave Amp L-I","","ACTIVE","1.0i","2.48"
17 | "10014-9","R' wave amplitude.lead II","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead II","R' wave Amp L-II","","ACTIVE","1.0i","2.48"
18 | "10015-6","R' wave amplitude.lead III","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead III","R' wave Amp L-III","","ACTIVE","1.0i","2.48"
19 | "10016-4","R' wave amplitude.lead V1","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead V1","R' wave Amp L-V1","","ACTIVE","1.0i","2.48"
20 | "1001-7","DBG Ab","PrThr","Pt","Ser/Plas^Donor","Ord","","BLDBK","1","DBG Ab [Presence] in Serum or Plasma from Donor","DBG Ab SerPl Donr Ql","","ACTIVE","1.0","2.56"
21 | "10017-2","R' wave amplitude.lead V2","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead V2","R' wave Amp L-V2","","ACTIVE","1.0i","2.48"
22 | "10018-0","R' wave amplitude.lead V3","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead V3","R' wave Amp L-V3","","ACTIVE","1.0i","2.48"
23 | "10019-8","R' wave amplitude.lead V4","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead V4","R' wave Amp L-V4","","ACTIVE","1.0i","2.48"
24 | "10020-6","R' wave amplitude.lead V5","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead V5","R' wave Amp L-V5","","ACTIVE","1.0i","2.48"
25 | "10021-4","R' wave amplitude.lead V6","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead V6","R' wave Amp L-V6","","ACTIVE","1.0i","2.48"
26 | "10022-2","R' wave duration.lead AVF","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave duration in lead AVF","R' wave dur L-AVF","","ACTIVE","1.0i","2.48"
27 | "10023-0","R' wave duration.lead AVL","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave duration in lead AVL","R' wave dur L-AVL","","ACTIVE","1.0i","2.48"
28 | "10024-8","R' wave duration.lead AVR","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave duration in lead AVR","R' wave dur L-AVR","","ACTIVE","1.0i","2.48"
29 | "789-8","Erythrocytes [#/volume] in Blood by Automated count","NCnc","Pt","Bld","Qn","Automated count","2","Erythrocytes [#/volume] in Blood by Automated count","RBC # Bld Auto","","ACTIVE","1.0i","2.48"
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/test/resources/LoincTableCoreTiny.csv:
--------------------------------------------------------------------------------
1 | "LOINC_NUM","COMPONENT","PROPERTY","TIME_ASPCT","SYSTEM","SCALE_TYP","METHOD_TYP","CLASS","CLASSTYPE","LONG_COMMON_NAME","SHORTNAME","EXTERNAL_COPYRIGHT_NOTICE","STATUS","VersionFirstReleased","VersionLastChanged"
2 | "10000-8","R wave duration.lead AVR","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead AVR","R wave dur L-AVR","","ACTIVE","1.0i","2.48"
3 | "10001-6","R wave duration.lead I","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead I","R wave dur L-I","","ACTIVE","1.0i","2.48"
4 | "10002-4","R wave duration.lead II","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead II","R wave dur L-II","","ACTIVE","1.0i","2.48"
5 | "10003-2","R wave duration.lead III","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead III","R wave dur L-III","","ACTIVE","1.0i","2.48"
6 | "10004-0","R wave duration.lead V1","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead V1","R wave dur L-V1","","ACTIVE","1.0i","2.48"
7 | "10005-7","R wave duration.lead V2","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead V2","R wave dur L-V2","","ACTIVE","1.0i","2.48"
8 | "10006-5","R wave duration.lead V3","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead V3","R wave dur L-V3","","ACTIVE","1.0i","2.48"
9 | "10007-3","R wave duration.lead V4","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead V4","R wave dur L-V4","","ACTIVE","1.0i","2.48"
10 | "10008-1","R wave duration.lead V5","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead V5","R wave dur L-V5","","ACTIVE","1.0i","2.48"
11 | "1000-9","DBG Ab","PrThr","Pt","Ser/Plas^BPU","Ord","","BLDBK","1","DBG Ab [Presence] in Serum or Plasma from Blood product unit","DBG Ab SerPl BPU Ql","","ACTIVE","1.0","2.56"
12 | "10009-9","R wave duration.lead V6","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead V6","R wave dur L-V6","","ACTIVE","1.0i","2.48"
13 | "10010-7","R' wave amplitude.lead AVF","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead AVF","R' wave Amp L-AVF","","ACTIVE","1.0i","2.48"
14 | "10011-5","R' wave amplitude.lead AVL","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead AVL","R' wave Amp L-AVL","","ACTIVE","1.0i","2.48"
15 | "10012-3","R' wave amplitude.lead AVR","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead AVR","R' wave Amp L-AVR","","ACTIVE","1.0i","2.48"
16 | "10013-1","R' wave amplitude.lead I","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead I","R' wave Amp L-I","","ACTIVE","1.0i","2.48"
17 | "10014-9","R' wave amplitude.lead II","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead II","R' wave Amp L-II","","ACTIVE","1.0i","2.48"
18 | "10015-6","R' wave amplitude.lead III","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead III","R' wave Amp L-III","","ACTIVE","1.0i","2.48"
19 | "10016-4","R' wave amplitude.lead V1","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead V1","R' wave Amp L-V1","","ACTIVE","1.0i","2.48"
20 | "1001-7","DBG Ab","PrThr","Pt","Ser/Plas^Donor","Ord","","BLDBK","1","DBG Ab [Presence] in Serum or Plasma from Donor","DBG Ab SerPl Donr Ql","","ACTIVE","1.0","2.56"
21 | "10017-2","R' wave amplitude.lead V2","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead V2","R' wave Amp L-V2","","ACTIVE","1.0i","2.48"
22 | "10018-0","R' wave amplitude.lead V3","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead V3","R' wave Amp L-V3","","ACTIVE","1.0i","2.48"
23 | "10019-8","R' wave amplitude.lead V4","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead V4","R' wave Amp L-V4","","ACTIVE","1.0i","2.48"
24 | "10020-6","R' wave amplitude.lead V5","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead V5","R' wave Amp L-V5","","ACTIVE","1.0i","2.48"
25 | "10021-4","R' wave amplitude.lead V6","Elpot","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave amplitude in lead V6","R' wave Amp L-V6","","ACTIVE","1.0i","2.48"
26 | "10022-2","R' wave duration.lead AVF","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave duration in lead AVF","R' wave dur L-AVF","","ACTIVE","1.0i","2.48"
27 | "10023-0","R' wave duration.lead AVL","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave duration in lead AVL","R' wave dur L-AVL","","ACTIVE","1.0i","2.48"
28 | "10024-8","R' wave duration.lead AVR","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R' wave duration in lead AVR","R' wave dur L-AVR","","ACTIVE","1.0i","2.48"
29 | "789-8","Erythrocytes [#/volume] in Blood by Automated count","NCnc","Pt","Bld","Qn","Automated count","2","Erythrocytes [#/volume] in Blood by Automated count","RBC # Bld Auto","","ACTIVE","1.0i","2.48"
--------------------------------------------------------------------------------
/loinc2hpo-core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | loinc2hpo
7 | org.monarchinitiative
8 | 2.0.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | loinc2hpo-core
13 | jar
14 |
15 | Loinc2Hpo Core
16 | A library for mapping LOINC codes to HPO terms
17 |
18 |
19 | UTF-8
20 |
21 |
22 |
23 |
24 |
25 | org.monarchinitiative.phenol
26 | phenol-core
27 |
28 |
29 | org.monarchinitiative.phenol
30 | phenol-io
31 |
32 |
33 | org.junit.jupiter
34 | junit-jupiter-engine
35 | test
36 |
37 |
38 | org.slf4j
39 | slf4j-api
40 |
41 |
42 |
43 |
44 | loinc2hpo-core
45 |
46 |
47 | src/main/resources
48 |
50 | true
51 |
52 |
53 |
54 |
55 | org.apache.maven.plugins
56 | maven-compiler-plugin
57 | 3.7.0
58 |
59 | ${java.version}
60 | ${java.version}
61 |
62 |
63 |
64 |
65 | org.apache.maven.plugins
66 | maven-surefire-plugin
67 |
68 |
69 |
70 |
71 | maven-resources-plugin
72 | 3.2.0
73 |
74 |
75 | copy-resources
76 | validate
77 |
78 | copy-resources
79 |
80 |
81 | ${project.build.directory}/resources
82 |
83 |
84 | src/main/resources
85 |
87 | true
88 |
89 | application.properties
90 | example.settings
91 | test.settings
92 | log4j2.xml
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | org.apache.maven.plugins
104 | maven-jar-plugin
105 | 3.2.0
106 |
107 |
108 |
109 | true
110 | lib/
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/loinc2hpo-fhir/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | loinc2hpo
7 | org.monarchinitiative
8 | 2.0.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | loinc2hpo-fhir
13 | jar
14 |
15 | Loinc2Hpo FHIR
16 | FHIR interface to loinc2hpo
17 |
18 |
19 | UTF-8
20 | 6.4.0
21 |
22 |
23 |
24 |
25 |
26 | org.monarchinitiative
27 | loinc2hpo-core
28 | ${project.parent.version}
29 |
30 |
31 | org.slf4j
32 | slf4j-api
33 |
34 |
35 |
36 | ca.uhn.hapi.fhir
37 | hapi-fhir-base
38 | ${hapi-fhir.version}
39 |
40 |
41 | ca.uhn.hapi.fhir
42 | hapi-fhir-structures-dstu3
43 | ${hapi-fhir.version}
44 |
45 |
46 | ca.uhn.hapi.fhir
47 | hapi-fhir-structures-r4
48 | ${hapi-fhir.version}
49 |
50 |
51 | ca.uhn.hapi.fhir
52 | hapi-fhir-structures-r5
53 | ${hapi-fhir.version}
54 |
55 |
56 |
57 |
58 | org.junit.jupiter
59 | junit-jupiter-engine
60 | test
61 |
62 |
63 |
64 |
65 | loinc2hpo-fhir
66 |
67 |
68 | src/main/resources
69 |
70 | true
71 |
72 |
73 |
74 |
75 | org.apache.maven.plugins
76 | maven-surefire-plugin
77 |
78 |
79 | org.apache.maven.plugins
80 | maven-compiler-plugin
81 |
82 |
83 |
84 |
85 | maven-resources-plugin
86 | 3.2.0
87 |
88 |
89 | copy-resources
90 | validate
91 |
92 | copy-resources
93 |
94 |
95 | ${project.build.directory}/resources
96 |
97 |
98 | src/main/resources
99 |
101 | true
102 |
103 | application.properties
104 | example.settings
105 | test.settings
106 | log4j2.xml
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | org.apache.maven.plugins
118 | maven-jar-plugin
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # loinc2hpo documentation build configuration file, created by
5 | # sphinx-quickstart on Tue Mar 13 16:28:40 2018.
6 | #
7 | # This file is execfile()d with the current directory set to its
8 | # containing dir.
9 | #
10 | # Note that not all possible configuration values are present in this
11 | # autogenerated file.
12 | #
13 | # All configuration values have a default; values that are commented out
14 | # serve to show the default.
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | #
20 | # import os
21 | # import sys
22 | # sys.path.insert(0, os.path.abspath('.'))
23 |
24 |
25 | # -- General configuration ------------------------------------------------
26 |
27 | # If your documentation needs a minimal Sphinx version, state it here.
28 | #
29 | # needs_sphinx = '1.0'
30 |
31 | # Add any Sphinx extension module names here, as strings. They can be
32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
33 | # ones.
34 | extensions = []
35 |
36 | # Add any paths that contain templates here, relative to this directory.
37 | templates_path = ['_templates']
38 |
39 | # The suffix(es) of source filenames.
40 | # You can specify multiple suffix as a list of string:
41 | #
42 | # source_suffix = ['.rst', '.md']
43 | source_suffix = '.rst'
44 |
45 | # The master toctree document.
46 | master_doc = 'index'
47 |
48 | # General information about the project.
49 | project = 'loinc2hpo'
50 | copyright = '2021, Peter Robinson, Aaron Zhang'
51 | author = 'Peter Robinson, Aaron Zhang'
52 |
53 | # The version info for the project you're documenting, acts as replacement for
54 | # |version| and |release|, also used in various other places throughout the
55 | # built documents.
56 | #
57 | # The short X.Y version.
58 | version = '1.7.0'
59 | # The full version, including alpha/beta/rc tags.
60 | release = '1.7.0'
61 |
62 | # The language for content autogenerated by Sphinx. Refer to documentation
63 | # for a list of supported languages.
64 | #
65 | # This is also used if you do content translation via gettext catalogs.
66 | # Usually you set "language" from the command line for these cases.
67 | language = None
68 |
69 | # List of patterns, relative to source directory, that match files and
70 | # directories to ignore when looking for source files.
71 | # This patterns also effect to html_static_path and html_extra_path
72 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'LICENSE.rst']
73 |
74 | # The name of the Pygments (syntax highlighting) style to use.
75 | pygments_style = 'sphinx'
76 |
77 | # If true, `todo` and `todoList` produce output, else they produce nothing.
78 | todo_include_todos = False
79 |
80 |
81 | # -- Options for HTML output ----------------------------------------------
82 |
83 | # The theme to use for HTML and HTML Help pages. See the documentation for
84 | # a list of builtin themes.
85 | #
86 | html_theme = "sphinx_rtd_theme"
87 |
88 |
89 | # Theme options are theme-specific and customize the look and feel of a theme
90 | # further. For a list of options available for each theme, see the
91 | # documentation.
92 | #
93 | # html_theme_options = {}
94 |
95 | # Add any paths that contain custom static files (such as style sheets) here,
96 | # relative to this directory. They are copied after the builtin static files,
97 | # so a file named "default.css" will overwrite the builtin "default.css".
98 | html_static_path = ['_static']
99 | # File list in _static
100 | html_css_files = ['loinc2hpo.css']
101 |
102 |
103 | # -- Options for HTMLHelp output ------------------------------------------
104 |
105 | # Output file base name for HTML help builder.
106 | htmlhelp_basename = 'loinc2hpodoc'
107 |
108 |
109 | # -- Options for LaTeX output ---------------------------------------------
110 |
111 | latex_elements = {
112 | # The paper size ('letterpaper' or 'a4paper').
113 | #
114 | # 'papersize': 'letterpaper',
115 |
116 | # The font size ('10pt', '11pt' or '12pt').
117 | #
118 | # 'pointsize': '10pt',
119 |
120 | # Additional stuff for the LaTeX preamble.
121 | #
122 | # 'preamble': '',
123 |
124 | # Latex figure (float) alignment
125 | #
126 | # 'figure_align': 'htbp',
127 | }
128 |
129 | # Grouping the document tree into LaTeX files. List of tuples
130 | # (source start file, target name, title,
131 | # author, documentclass [howto, manual, or own class]).
132 | latex_documents = [
133 | (master_doc, 'loinc2hpo.tex', 'loinc2hpo Documentation',
134 | 'Peter Robinson, Aaron Zhang', 'manual'),
135 | ]
136 |
137 |
138 | # -- Options for manual page output ---------------------------------------
139 |
140 | # One entry per manual page. List of tuples
141 | # (source start file, name, description, authors, manual section).
142 | man_pages = [
143 | (master_doc, 'loinc2hpo', 'loinc2hpo Documentation',
144 | [author], 1)
145 | ]
146 |
147 |
148 | # -- Options for Texinfo output -------------------------------------------
149 |
150 | # Grouping the document tree into Texinfo files. List of tuples
151 | # (source start file, target name, title, author,
152 | # dir menu entry, description, category)
153 | texinfo_documents = [
154 | (master_doc, 'loinc2hpo', 'loinc2hpo Documentation',
155 | author, 'loinc2hpo', 'One line description of project.',
156 | 'Miscellaneous'),
157 | ]
158 |
159 |
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/main/java/org/monarchinitiative/loinc2hpofhir/fhir2hpo/ObservationDtu3.java:
--------------------------------------------------------------------------------
1 | package org.monarchinitiative.loinc2hpofhir.fhir2hpo;
2 |
3 | import org.apache.commons.lang.NotImplementedException;
4 | import org.hl7.fhir.dstu3.model.CodeableConcept;
5 | import org.hl7.fhir.dstu3.model.Coding;
6 |
7 | import org.hl7.fhir.dstu3.model.Observation;
8 | import org.monarchinitiative.loinc2hpocore.codesystems.Outcome;
9 | import org.monarchinitiative.loinc2hpocore.codesystems.ShortCode;
10 | import org.monarchinitiative.loinc2hpocore.loinc.LoincId;
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 |
14 | import java.util.List;
15 | import java.util.Optional;
16 | import java.util.stream.Collectors;
17 |
18 | public class ObservationDtu3 implements Uberobservation {
19 | private static final Logger LOGGER = LoggerFactory.getLogger(ObservationDtu3.class);
20 | private final org.hl7.fhir.dstu3.model.Observation observation;
21 |
22 | public ObservationDtu3(org.hl7.fhir.dstu3.model.Observation dstu3Observation) {
23 | this.observation = dstu3Observation;
24 | }
25 |
26 | @Override
27 | public Optional getLoincId() {
28 | LoincId loincId;
29 | for (Coding coding : observation.getCode().getCoding()) {
30 | if (coding.getSystem().equals("http://loinc.org")) {
31 | loincId = new LoincId(coding.getCode());
32 | return Optional.of(loincId);
33 | }
34 | }
35 | return Optional.empty();
36 | }
37 |
38 | private Outcome getOutcome(ShortCode code, Observation observation) {
39 | if (code.equals(ShortCode.NOM)) {
40 | throw new NotImplementedException("TODO");
41 | }
42 | switch (code) {
43 | case H: return Outcome.HIGH();
44 | case L: return Outcome.LOW();
45 | case N: return Outcome.NORMAL();
46 | case NEG: return Outcome.NEGATIVE();
47 | case POS: return Outcome.POSITIVE();
48 | default:
49 | throw new NotImplementedException("TODO");
50 | }
51 | }
52 |
53 |
54 | @Override
55 | public Optional getOutcome() {
56 | if (observation.hasInterpretation()){
57 | List codes = this.observation.getInterpretation().getCoding().
58 | stream().map(Coding::getCode).distinct().
59 | collect(Collectors.toList());
60 | if (codes.size() > 1) {
61 | LOGGER.error("Multiple interpretation codes returned");
62 | return Optional.empty();
63 | }
64 | ShortCode code = ShortCode.fromShortCode(codes.get(0));
65 | Outcome outcome = getOutcome(code, observation);
66 | return Optional.of(outcome);
67 | } else if (observation.hasValueCodeableConcept()){
68 | return getOutcomeFromCodedValue();
69 | } else if (observation.hasValueQuantity()){
70 | return getOutcomeFromValueQuantity();
71 | } else {
72 | LOGGER.error("Unable to handle observation {}", observation);
73 | return Optional.empty();
74 | }
75 | }
76 |
77 |
78 | Optional getOutcomeFromCodedValue() {
79 | CodeableConcept codeableConcept = this.observation.getValueCodeableConcept();
80 | if (codeableConcept == null) { // should never happen
81 | LOGGER.error("Codable concept null in getOutcomeFromCodedValue");
82 | }
83 | List codings = codeableConcept != null ? codeableConcept.getCoding() : List.of();
84 | for (Coding coding : codings) {
85 | String code = coding.getCode();
86 | String system = coding.getSystem();
87 | String display = coding.getDisplay();
88 | String outcomeString = code + ":" + system + ":" + display;
89 | Outcome outcome = Outcome.nominal(outcomeString);
90 | return Optional.of(outcome);
91 | }
92 | return Optional.empty();
93 | }
94 |
95 | Optional getOutcomeFromValueQuantity() {
96 | List references =
97 | this.observation.getReferenceRange();
98 |
99 | if (references.size() == 0) {
100 | LOGGER.error("Reference range not found");
101 | return Optional.empty();
102 | }
103 |
104 | if (references.size() >= 2){
105 | LOGGER.error("Reference range had more than two entries");
106 | return Optional.empty();
107 | // TODO sometimes this is observed.
108 | //An exception: three reference sizes
109 | //it can happen when there is actually one range but coded in three ranges
110 | //e.g. normal 20-30
111 | //in this case, one range ([20, 30]) is sufficient;
112 | //however, it is written as three ranges: ( , 20) [20, 30] (30, )
113 | }
114 | Observation.ObservationReferenceRangeComponent targetReference = references.get(0);
115 | double low = targetReference.hasLow() ?
116 | targetReference.getLow().getValue().doubleValue() : Double.MIN_VALUE;
117 | double high = targetReference.hasHigh() ?
118 | targetReference.getHigh().getValue().doubleValue() : Double.MAX_VALUE;
119 | double observed = this.observation.getValueQuantity().getValue().doubleValue();
120 | if (observed < low) {
121 | return Optional.of(Outcome.LOW());
122 | } else if (observed > high) {
123 | return Optional.of(Outcome.HIGH());
124 | } else {
125 | return Optional.of(Outcome.NORMAL());
126 | }
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/main/java/org/monarchinitiative/loinc2hpofhir/fhir2hpo/ObservationR5.java:
--------------------------------------------------------------------------------
1 | package org.monarchinitiative.loinc2hpofhir.fhir2hpo;
2 |
3 | import org.apache.commons.lang.NotImplementedException;
4 | import org.hl7.fhir.r5.model.Observation;
5 | import org.hl7.fhir.r5.model.CodeableConcept;
6 | import org.hl7.fhir.r5.model.Coding;
7 | import org.monarchinitiative.loinc2hpocore.codesystems.Outcome;
8 | import org.monarchinitiative.loinc2hpocore.codesystems.ShortCode;
9 | import org.monarchinitiative.loinc2hpocore.loinc.LoincId;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import java.util.Collection;
14 | import java.util.List;
15 | import java.util.Optional;
16 | import java.util.stream.Collectors;
17 |
18 | public class ObservationR5 implements Uberobservation {
19 | private static final Logger LOGGER = LoggerFactory.getLogger(ObservationR5.class);
20 |
21 | private final org.hl7.fhir.r5.model.Observation observation;
22 |
23 |
24 | public ObservationR5(org.hl7.fhir.r5.model.Observation observation) {
25 | this.observation = observation;
26 | }
27 |
28 | @Override
29 | public Optional getLoincId() {
30 | LoincId loincId;
31 | for (Coding coding : observation.getCode().getCoding()) {
32 | if (coding.getSystem().equals("http://loinc.org")) {
33 | loincId = new LoincId(coding.getCode());
34 | return Optional.of(loincId);
35 | }
36 | }
37 | return Optional.empty();
38 | }
39 |
40 | private Outcome getOutcome(ShortCode code, org.hl7.fhir.r5.model.Observation observation) {
41 | if (code.equals(ShortCode.NOM)) {
42 | throw new NotImplementedException("TODO");
43 | }
44 | switch (code) {
45 | case H: return Outcome.HIGH();
46 | case L: return Outcome.LOW();
47 | case N: return Outcome.NORMAL();
48 | case NEG: return Outcome.NEGATIVE();
49 | case POS: return Outcome.POSITIVE();
50 | default:
51 | throw new NotImplementedException("TODO");
52 | }
53 | }
54 |
55 |
56 | @Override
57 | public Optional getOutcome() {
58 | if (observation.hasInterpretation()){
59 | List codes = this.observation.getInterpretation().stream()
60 | .distinct()
61 | .map(CodeableConcept::getCoding)
62 | .flatMap(Collection::stream)
63 | .map(org.hl7.fhir.r5.model.Coding::getCode)
64 | .collect(Collectors.toList());
65 | if (codes.size() > 1) {
66 | LOGGER.error("Multiple interpretation codes returned");
67 | return Optional.empty();
68 | }
69 | ShortCode code = ShortCode.fromShortCode(codes.get(0));
70 | Outcome outcome = getOutcome(code, observation);
71 | return Optional.of(outcome);
72 | } else if (observation.hasValueCodeableConcept()) {
73 | return getOutcomeFromCodedValue();
74 | } else if (observation.hasValueQuantity()) {
75 | return getOutcomeFromValueQuantity();
76 | } else {
77 | LOGGER.error("Unable to handle observation {}", observation);
78 | return Optional.empty();
79 | }
80 | }
81 |
82 | Optional getOutcomeFromCodedValue() {
83 | CodeableConcept codeableConcept = this.observation.getValueCodeableConcept();
84 | if (codeableConcept == null) { // should never happen
85 | LOGGER.error("Codable concept null in getOutcomeFromCodedValue");
86 | }
87 | List codings = codeableConcept != null ? codeableConcept.getCoding() : List.of();
88 | for (Coding coding : codings) {
89 | String code = coding.getCode();
90 | String system = coding.getSystem();
91 | String display = coding.getDisplay();
92 | String outcomeString = code + ":" + system + ":" + display;
93 | Outcome outcome = Outcome.nominal(outcomeString);
94 | return Optional.of(outcome);
95 | }
96 | return Optional.empty();
97 | }
98 |
99 | Optional getOutcomeFromValueQuantity() {
100 | List references =
101 | this.observation.getReferenceRange();
102 |
103 | if (references.size() == 0) {
104 | LOGGER.error("Reference range not found");
105 | return Optional.empty();
106 | }
107 |
108 | if (references.size() >= 2) {
109 | LOGGER.error("Reference range had more than two entries");
110 | return Optional.empty();
111 | // TODO sometimes this is observed.
112 | //An exception: three reference sizes
113 | //it can happen when there is actually one range but coded in three ranges
114 | //e.g. normal 20-30
115 | //in this case, one range ([20, 30]) is sufficient;
116 | //however, it is written as three ranges: ( , 20) [20, 30] (30, )
117 | }
118 | Observation.ObservationReferenceRangeComponent targetReference = references.get(0);
119 | double low = targetReference.hasLow() ?
120 | targetReference.getLow().getValue().doubleValue() : Double.MIN_VALUE;
121 | double high = targetReference.hasHigh() ?
122 | targetReference.getHigh().getValue().doubleValue() : Double.MAX_VALUE;
123 | double observed = this.observation.getValueQuantity().getValue().doubleValue();
124 | if (observed < low) {
125 | return Optional.of(Outcome.LOW());
126 | } else if (observed > high) {
127 | return Optional.of(Outcome.HIGH());
128 | } else {
129 | return Optional.of(Outcome.NORMAL());
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/main/java/org/monarchinitiative/loinc2hpofhir/fhir2hpo/ObservationR4.java:
--------------------------------------------------------------------------------
1 | package org.monarchinitiative.loinc2hpofhir.fhir2hpo;
2 |
3 | import org.apache.commons.lang.NotImplementedException;
4 | import org.hl7.fhir.r4.model.CodeableConcept;
5 | import org.hl7.fhir.r4.model.Coding;
6 | import org.hl7.fhir.r4.model.Observation;
7 | import org.monarchinitiative.loinc2hpocore.codesystems.Outcome;
8 | import org.monarchinitiative.loinc2hpocore.codesystems.ShortCode;
9 | import org.monarchinitiative.loinc2hpocore.loinc.LoincId;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import java.util.Collection;
14 | import java.util.List;
15 | import java.util.Optional;
16 | import java.util.stream.Collectors;
17 |
18 | public class ObservationR4 implements Uberobservation {
19 | private static final Logger LOGGER = LoggerFactory.getLogger(ObservationR4.class);
20 | private final org.hl7.fhir.r4.model.Observation observation;
21 |
22 |
23 | public ObservationR4(org.hl7.fhir.r4.model.Observation observation) {
24 | this.observation = observation;
25 | }
26 |
27 | @Override
28 | public Optional getLoincId() {
29 | LoincId loincId;
30 | for (Coding coding : observation.getCode().getCoding()) {
31 | if (coding.getSystem().equals("http://loinc.org")) {
32 | loincId = new LoincId(coding.getCode());
33 | return Optional.of(loincId);
34 | }
35 | }
36 | return Optional.empty();
37 | }
38 |
39 | private Outcome getOutcome(ShortCode code, org.hl7.fhir.r4.model.Observation observation) {
40 | if (code.equals(ShortCode.NOM)) {
41 | throw new NotImplementedException("TODO");
42 | }
43 | switch (code) {
44 | case H:
45 | return Outcome.HIGH();
46 | case L:
47 | return Outcome.LOW();
48 | case N:
49 | return Outcome.NORMAL();
50 | case NEG:
51 | return Outcome.NEGATIVE();
52 | case POS:
53 | return Outcome.POSITIVE();
54 | default:
55 | throw new NotImplementedException("TODO");
56 | }
57 | }
58 |
59 |
60 | @Override
61 | public Optional getOutcome() {
62 | if (observation.hasInterpretation()) {
63 | List codes = this.observation.getInterpretation().stream()
64 | .distinct()
65 | .map(CodeableConcept::getCoding)
66 | .flatMap(Collection::stream)
67 | .map(Coding::getCode)
68 | .collect(Collectors.toList());
69 | if (codes.size() > 1) {
70 | LOGGER.error("Multiple interpretation codes returned");
71 | return Optional.empty();
72 | }
73 | ShortCode code = ShortCode.fromShortCode(codes.get(0));
74 | Outcome outcome = getOutcome(code, observation);
75 | return Optional.of(outcome);
76 | } else if (observation.hasValueCodeableConcept()) {
77 | return getOutcomeFromCodedValue();
78 | } else if (observation.hasValueQuantity()) {
79 | return getOutcomeFromValueQuantity();
80 | } else {
81 | LOGGER.error("Unable to handle observation {}", observation);
82 | return Optional.empty();
83 | }
84 | }
85 |
86 | Optional getOutcomeFromCodedValue() {
87 | CodeableConcept codeableConcept = this.observation.getValueCodeableConcept();
88 | if (codeableConcept == null) { // should never happen
89 | LOGGER.error("Codable concept null in getOutcomeFromCodedValue");
90 | }
91 | List codings = codeableConcept != null ? codeableConcept.getCoding() : List.of();
92 | for (Coding coding : codings) {
93 | String code = coding.getCode();
94 | String system = coding.getSystem();
95 | String display = coding.getDisplay();
96 | String outcomeString = code + ":" + system + ":" + display;
97 | Outcome outcome = Outcome.nominal(outcomeString);
98 | return Optional.of(outcome);
99 | }
100 | return Optional.empty();
101 | }
102 |
103 | Optional getOutcomeFromValueQuantity() {
104 | List references =
105 | this.observation.getReferenceRange();
106 |
107 | if (references.size() == 0) {
108 | LOGGER.error("Reference range not found");
109 | return Optional.empty();
110 | }
111 |
112 | if (references.size() >= 2) {
113 | LOGGER.error("Reference range had more than two entries");
114 | return Optional.empty();
115 | // TODO sometimes this is observed.
116 | //An exception: three reference sizes
117 | //it can happen when there is actually one range but coded in three ranges
118 | //e.g. normal 20-30
119 | //in this case, one range ([20, 30]) is sufficient;
120 | //however, it is written as three ranges: ( , 20) [20, 30] (30, )
121 | }
122 | Observation.ObservationReferenceRangeComponent targetReference = references.get(0);
123 | double low = targetReference.hasLow() ?
124 | targetReference.getLow().getValue().doubleValue() : Double.MIN_VALUE;
125 | double high = targetReference.hasHigh() ?
126 | targetReference.getHigh().getValue().doubleValue() : Double.MAX_VALUE;
127 | double observed = this.observation.getValueQuantity().getValue().doubleValue();
128 | if (observed < low) {
129 | return Optional.of(Outcome.LOW());
130 | } else if (observed > high) {
131 | return Optional.of(Outcome.HIGH());
132 | } else {
133 | return Optional.of(Outcome.NORMAL());
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/loinc2hpo-core/src/main/java/org/monarchinitiative/loinc2hpocore/annotation/QuantitativeLoincAnnotation.java:
--------------------------------------------------------------------------------
1 | package org.monarchinitiative.loinc2hpocore.annotation;
2 |
3 | import org.monarchinitiative.loinc2hpocore.codesystems.Outcome;
4 | import org.monarchinitiative.loinc2hpocore.exception.Loinc2HpoRuntimeException;
5 | import org.monarchinitiative.loinc2hpocore.loinc.LoincId;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.Map;
10 | import java.util.Optional;
11 |
12 | /**
13 | * This class is for LOINC2HPO annotations with the {@code Qn} scale. Most of these annotations
14 | * have values for Low, Normal, and High results, but some only have two relevant values. For instance,
15 | * LOINC 6047-5 (Bloodworm IgE Ab [Units/volume] in Serum) does not have a "low" value, because there
16 | * is no abnormal low level of this analyte. In this case, we use the fromNormalAndHigh factory method,
17 | * store a null pointer for {@link #low} and return Optional.empty() for this value (which should actually
18 | * never be queried in the real world). Similarly, in some occasions we just have Low and Normal.
19 | * We do expect to have a normal annotation in all cases, and if not, an Exception is thrown.
20 | * @author Peter Robinson
21 | */
22 | public class QuantitativeLoincAnnotation implements LoincAnnotation {
23 |
24 | private final LoincId loincId;
25 | private final Loinc2HpoAnnotation low;
26 | private final Loinc2HpoAnnotation normal;
27 | private final Loinc2HpoAnnotation high;
28 |
29 |
30 | public QuantitativeLoincAnnotation(Loinc2HpoAnnotation low,
31 | Loinc2HpoAnnotation normal,
32 | Loinc2HpoAnnotation high) {
33 | this.low = low;
34 | this.normal = normal;
35 | this.high = high;
36 | // assumption is that all annotation have the same LoincId
37 | // we assume that although some annotation have just two values there will always be a
38 | // normal value (this is enforced by the parser and not checked here)
39 | this.loincId = this.normal.getLoincId();
40 | }
41 |
42 |
43 | private static String getDebugInfo(Map outcomeMap) {
44 | StringBuilder sb = new StringBuilder("Malformed Quantitivate outcomes\nn=").append(outcomeMap.size());
45 | for (var oc : outcomeMap.values()) {
46 | sb.append("\t[ERROR] ").append(oc).append("\n");
47 | }
48 | return sb.toString();
49 | }
50 |
51 |
52 |
53 |
54 | /**
55 | * Create between 1 and 3 components. Note that we demand there be a NORMAL annotation,
56 | * but LOW and HIGH are optional.
57 | * @param outcomeMap outcomes and annotations for a LOINC test
58 | * @return corresponding {@link LoincAnnotation} object
59 | */
60 | public static LoincAnnotation fromOutcomeMap(Map outcomeMap) {
61 | if (! outcomeMap.containsKey(Outcome.NORMAL())) {
62 | String msg = String.format("Attempt to create Quantitative LoincAnnotation without Normal annotation: %s",
63 | getDebugInfo(outcomeMap));
64 | throw new Loinc2HpoRuntimeException(msg);
65 | }
66 | if (outcomeMap.size() == 3) {
67 | return new QuantitativeLoincAnnotation(outcomeMap.get(Outcome.LOW()),
68 | outcomeMap.get(Outcome.NORMAL()),
69 | outcomeMap.get(Outcome.HIGH()));
70 | } else if (outcomeMap.size() == 2 &&
71 | outcomeMap.containsKey(Outcome.NORMAL()) &&
72 | outcomeMap.containsKey(Outcome.HIGH())) {
73 | return new QuantitativeLoincAnnotation(null, outcomeMap.get(Outcome.NORMAL()),
74 | outcomeMap.get(Outcome.HIGH()));
75 | } else if (outcomeMap.size() == 2 &&
76 | outcomeMap.containsKey(Outcome.LOW()) &&
77 | outcomeMap.containsKey(Outcome.NORMAL())) {
78 | return new QuantitativeLoincAnnotation(outcomeMap.get(Outcome.LOW()),
79 | outcomeMap.get(Outcome.NORMAL()), null);
80 | } else if (outcomeMap.size() == 1 && outcomeMap.containsKey(Outcome.NORMAL())) {
81 | return new QuantitativeLoincAnnotation(null, outcomeMap.get(Outcome.NORMAL()), null);
82 | }
83 | String msg = String.format("\"Unable to create Quantitative LoincAnnotation annotation: %s",
84 | getDebugInfo(outcomeMap));
85 | throw new Loinc2HpoRuntimeException(msg);
86 | }
87 |
88 |
89 |
90 | @Override
91 | public Optional getOutcome(Outcome outcome) {
92 | switch (outcome.getCode()) {
93 | case L:
94 | if (low == null) return Optional.empty();
95 | else return Optional.of(new Hpo2Outcome(low.getHpoTermId(), Outcome.LOW()));
96 | case N:
97 | return Optional.of(new Hpo2Outcome(normal.getHpoTermId(), Outcome.NORMAL()));
98 | case H:
99 | if (high == null) return Optional.empty();
100 | return Optional.of(new Hpo2Outcome(high.getHpoTermId(), Outcome.HIGH()));
101 | default:
102 | return Optional.empty();
103 | }
104 | }
105 |
106 | @Override
107 | public LoincId getLoincId() {
108 | return this.loincId;
109 | }
110 |
111 | @Override
112 | public List allAnnotations() {
113 | List allAnnots = new ArrayList<>();
114 | if (low != null) {
115 | allAnnots.add(low);
116 | }
117 | allAnnots.add(normal);
118 | if (high != null) {
119 | allAnnots.add(high);
120 | }
121 | return allAnnots;
122 | }
123 |
124 | @Override
125 | public LoincScale scale() {
126 | return LoincScale.QUANTITATIVE;
127 | }
128 |
129 | @Override
130 | public String toString() {
131 | return loincId + "\n\t" +
132 | (low == null? " L: n/a" : "L: " + low) + "\n\t" +
133 | (normal == null? " N: n/a" : "N: " + normal)+ "\n\t" +
134 | (high == null? " H: n/a" : "H: " + high);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/loinc2hpo-core/src/main/java/org/monarchinitiative/loinc2hpocore/loinc/LoincEntry.java:
--------------------------------------------------------------------------------
1 | package org.monarchinitiative.loinc2hpocore.loinc;
2 |
3 | import org.monarchinitiative.loinc2hpocore.annotation.LoincScale;
4 | import org.monarchinitiative.loinc2hpocore.exception.Loinc2HpoRuntimeException;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.List;
11 | import java.util.stream.Collectors;
12 |
13 | public class LoincEntry {
14 | private static final Logger logger = LoggerFactory.getLogger(LoincEntry.class);
15 |
16 | private final LoincId loincId;
17 |
18 | private final String component;
19 |
20 | private final String property;
21 |
22 | private final String timeAspect;
23 |
24 | private final String system;
25 |
26 | private final LoincScale scale;
27 |
28 | private final String method;
29 |
30 | private final LoincLongName loincLongName;
31 |
32 | private static final int MIN_FIELDS_LOINC=10;
33 |
34 | public final static String [] headerFields = {
35 | "LOINC_NUM", "COMPONENT", "PROPERTY", "TIME_ASPCT", "SYSTEM",
36 | "SCALE_TYP", "METHOD_TYP","CLASS", "CLASSTYPE","LONG_COMMON_NAME",
37 | "SHORTNAME",
38 | "EXTERNAL_COPYRIGHT_NOTICE",
39 | "STATUS","VersionFirstReleased", "VersionLastChanged"};
40 | public final static String header = Arrays.stream(headerFields)
41 | .map(w -> String.format("\"%s\"",w))
42 | .collect(Collectors.joining(","));
43 |
44 | private final static int LOINC_ID_FIELD = 0;
45 | private final static int COMPONENT_FIELD = 1;
46 | private final static int PROPERTY_FIELD = 2;
47 | private final static int TIMEASPECT_FIELD = 3;
48 | private final static int SYSTEM_FIELD = 4;
49 | private final static int SCALETYP_FIELD = 5;
50 | private final static int METHODTYP_FIELD = 6;
51 | private final static int LONG_COMMON_NAME_FIELD = 9;
52 |
53 |
54 |
55 |
56 |
57 |
58 | private static final String HEADER_LINE="FLAG\t#LOINC.id\tLOINC.scale\tHPO.low\tHPO.wnl\tHPO.high\tnote";
59 |
60 |
61 | public LoincEntry(LoincId loincId, String comp, String property, String timeAspect, String system,
62 | LoincScale scale, String method, LoincLongName longName) {
63 | this.loincId = loincId;
64 | this.component = comp;
65 | this.property = property;
66 | this.timeAspect = timeAspect;
67 | this.system = system;
68 | this.scale = scale;
69 | this.method = method;
70 | this.loincLongName = longName;
71 | }
72 |
73 |
74 | public LoincId getLoincId(){ return loincId;}
75 | public String getComponent() { return component; }
76 | public String getProperty() { return property; }
77 | public String getTimeAspect() { return timeAspect; }
78 | public String getMethod() { return method; }
79 | public LoincScale getScale() { return scale; }
80 | public String getSystem() { return system; }
81 | public String getLongName() { return loincLongName.getName(); }
82 | public LoincLongName getLoincLongName() {
83 | return this.loincLongName;
84 | }
85 |
86 | /**
87 | * Method to check that a LOINC is Ord and the outcome is either "Presence" or "Absence"
88 | * @return true if the LOINC is "Ord" and the outcome is either "Presence" or "Absence"
89 | */
90 | public boolean isPresentOrd() {
91 | return this.loincLongName.getLoincType().startsWith("Presen");
92 | }
93 |
94 | @Override
95 | public boolean equals(Object obj){
96 | if (this.loincId != null && obj instanceof LoincEntry) {
97 | LoincEntry other = (LoincEntry) obj;
98 | return this.loincId.equals(other.getLoincId());
99 | }
100 | return false;
101 | }
102 |
103 | @Override
104 | public int hashCode() {
105 | return this.loincId.hashCode();
106 | }
107 |
108 | /**
109 | * Split on comma, but not if the comma occurs within a quoted string
110 | * @param line input line such as "9806-1","2,4-Dichlorophenoxyacetate","MCnc","Pt",...
111 | * @return List of String without quotes
112 | */
113 | private static List splitQuotedCsvLine(String line) {
114 | List result = new ArrayList<>();
115 | int start = 0;
116 | boolean inQuotes = false;
117 | for (int current = 0; current < line.length(); current++) {
118 | if (line.charAt(current) == '\"') inQuotes = !inQuotes; // toggle state
119 | else if (line.charAt(current) == ',' && !inQuotes) {
120 | result.add(line.substring(start+1, current-1));// the +1 and -1 remove the quotes
121 | start = current + 1;
122 | }
123 | }
124 | result.add(line.substring(start));
125 | return result;
126 | }
127 |
128 |
129 | /**
130 | * Structure of file: see {@link #headerFields}
131 | * @param line e.g., "10000-8","R wave duration.lead AVR","Time","Pt","Heart","Qn","EKG","EKG.MEAS","2","R wave duration in lead AVR","R wave dur L-AVR","","ACTIVE","1.0i","2.48"
132 | * @return corresponding line
133 | */
134 | public static LoincEntry fromQuotedCsvLine(String line) {
135 | List fieldsWithNoQuotes = splitQuotedCsvLine(line);
136 | if (fieldsWithNoQuotes.size() quantitativeCodes = Set.of(ShortCode.H, ShortCode.N, ShortCode.L);
37 | private final Set ordinalCodes = Set.of(ShortCode.POS, ShortCode.NEG);
38 | private final Set nominalCodes = Set.of(ShortCode.NOM);
39 |
40 |
41 |
42 | @Override
43 | public void run() {
44 | System.out.println(annotPath);
45 | Loinc2HpoAnnotationParser parser = new Loinc2HpoAnnotationParser(annotPath);
46 | List entries = parser.getEntries();
47 | System.out.println("[INFO] Got " + entries.size() + " LOINC annotations.");
48 | Ontology ontology = OntologyLoader.loadOntology(new File(hpJsonPath));
49 | System.out.println("[INFO] Got " + ontology.countNonObsoleteTerms() + " HPO terms in hp.json.");
50 | checkValidityOfHpoTerms(entries, ontology);
51 |
52 | Map mymap = parser.loincToHpoAnnotationMap();
53 | System.out.println("[INFO] " + mymap.size() + " annotated LOINC terms");
54 | checkValidityOfLoincAnnotations(mymap);
55 |
56 | }
57 |
58 |
59 | private void checkValidityOfHpoTerms(List entries, Ontology ontology) {
60 | int good = 0;
61 | for (var entry : entries) {
62 | TermId tid = entry.getHpoTermId();
63 | if (! ontology.containsTerm(tid)) {
64 | System.err.println("[ERROR] HPO does not contain TermId " + tid.getValue());
65 | } else if ( ! ontology.getPrimaryTermId(tid).equals(tid)) {
66 | System.err.println("[ERROR] Obsolete TermId (" + tid.getValue() + ") used instead of " +
67 | ontology.getPrimaryTermId(tid) + ".");
68 | } else {
69 | good++;
70 | }
71 | }
72 | System.out.printf("[INFO] %d well-formed HPO terms used in LOINC annotations\n", good);
73 | }
74 |
75 | private void checkValidityOfLoincAnnotations(Map annotmap) {
76 | int malformed = 0;
77 | for (var annot : annotmap.values()) {
78 | if (annot.scale().equals(LoincScale.QUANTITATIVE)) {
79 | malformed += checkInvalidQuantitative(annot);
80 | } else if (annot.scale().equals(LoincScale.ORDINAL)) {
81 | malformed += checkInvalidOrdinal(annot);
82 | } else if (annot.scale().equals(LoincScale.NOMINAL)) {
83 | malformed += checkInvalidNominal(annot);
84 | } else {
85 | // should never haqppen
86 | throw new Loinc2HpoRuntimeException("Unrecognized loinc scale");
87 | }
88 | }
89 | System.out.printf("[INFO] %s well formed, %d malformed annotations.\n",
90 | annotmap.size() - malformed, malformed);
91 | }
92 |
93 | /**
94 | * @param annot
95 | * @return 1 if malformed, 0 if OK
96 | */
97 | private int checkInvalidQuantitative(LoincAnnotation annot) {
98 | List annots = annot.allAnnotations();
99 | for (var l2h : annots) {
100 | Outcome out = l2h.getOutcome();
101 | if (! quantitativeCodes.contains(out.getCode())) {
102 | System.err.printf("[ERROR] Malformed QN outcome code (\"%s\"): %s\n", out.getCode(), annot);
103 | return 1;
104 | }
105 | }
106 | return 0;//ok if we get here
107 | }
108 |
109 | /**
110 | * @param annot
111 | * @return 1 if malformed, 0 if OK
112 | */
113 | private int checkInvalidOrdinal(LoincAnnotation annot) {
114 | List annots = annot.allAnnotations();
115 | for (var l2h : annots) {
116 | Outcome out = l2h.getOutcome();
117 | if (! ordinalCodes.contains(out.getCode())) {
118 | System.err.printf("[ERROR] Malformed ordinal outcome code (\"%s\"): %s\n", out.getCode(), annot);
119 | return 1;
120 | }
121 | }
122 | return 0;//ok if we get here
123 | }
124 |
125 | /**
126 | * @param annot
127 | * @return 1 if malformed, 0 if OK
128 | */
129 | private int checkInvalidNominal(LoincAnnotation annot) {
130 | List annots = annot.allAnnotations();
131 | for (var l2h : annots) {
132 | Outcome out = l2h.getOutcome();
133 | if (! nominalCodes.contains(out.getCode())) {
134 | System.err.printf("[ERROR] Malformed nominal outcome code: %s\n", annot);
135 | return 1;
136 | }
137 | }
138 | return 0;//ok if we get here
139 | }
140 |
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/loinc2hpo-fhir/src/test/java/fhir/TestBase.java:
--------------------------------------------------------------------------------
1 | package fhir;
2 |
3 | import ca.uhn.fhir.context.FhirContext;
4 | import ca.uhn.fhir.parser.IParser;
5 | import org.hl7.fhir.dstu3.model.*;
6 |
7 | import java.io.File;
8 | import java.io.FileNotFoundException;
9 | import java.io.FileReader;
10 | import java.io.IOException;
11 | import java.net.URL;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | public class TestBase {
16 | /**
17 | * This page is part of the FHIR Specification (v4.0.1: R4 - Mixed Normative and STU).
18 | * This is the current published version. It defines v2 ABNORMAL FLAGS,
19 | * HL7-defined code system of concepts which specify a categorical assessment of an
20 | * observation value.
21 | * It is being communicated in FHIR in Observation.interpretation.
22 | */
23 | public static final String hl7Version2Table0078 = "http://hl7.org/fhir/v2/0078";
24 |
25 | private static final FhirContext ctxDstu3 = FhirContext.forDstu3();
26 | private static final IParser jsonparserDstu3 = ctxDstu3.newJsonParser();
27 |
28 | public static org.hl7.fhir.dstu3.model.Observation importDstu3Observation(String path) throws IOException {
29 | URL url = TestBase.class.getClassLoader().getResource(path);
30 | if (url == null) {
31 | throw new FileNotFoundException("Could not find " + path + " for testing");
32 | }
33 | File f = new File(url.getFile());
34 | if (! f.isFile()) {
35 | throw new FileNotFoundException("Could not find " + path + " for testing");
36 | }
37 | return (Observation) jsonparserDstu3.parseResource(new FileReader(f));
38 | }
39 |
40 |
41 | protected Observation lowHemoglobinObservation() {
42 | Patient patient = getPatient();
43 | // Create an observation object
44 | Observation observation = new Observation();
45 | observation.setStatus(Observation.ObservationStatus.FINAL);
46 | observation
47 | .getCode()
48 | .addCoding()
49 | .setSystem("http://loinc.org")
50 | .setCode("789-8")
51 | .setDisplay("Erythrocytes [#/volume] in Blood by Automated count");
52 | observation.setValue(
53 | new SimpleQuantity()
54 | .setValue(4.12)
55 | .setUnit("10 trillion/L")
56 | .setSystem("http://unitsofmeasure.org")
57 | .setCode("3*12/L"));
58 | Coding coding = new Coding().setSystem(hl7Version2Table0078).setCode("L").setDisplay("Low");
59 | List codings = new ArrayList<>();
60 | codings.add(coding);
61 | observation.setInterpretation(
62 | new CodeableConcept()
63 | .setCoding(codings)
64 | );
65 | // The observation refers to the patient using the ID, which is already set to a temporary UUID
66 | observation.setSubject(new Reference(patient.getId()));
67 | return observation;
68 | }
69 |
70 |
71 | /**
72 | * A normal range in adults is generally considered to be 4.35 to 5.65 million red blood cells
73 | * per microliter (mcL)
74 | * of blood for men and 3.92 to 5.13 million red blood cells per mcL of blood for women.
75 | * @return A FHIR Observation (dstu3) for high hemoglobin with reference range but no interpretation
76 | */
77 | protected Observation highHemoglobinWithValueRangeObservation() {
78 | Patient patient = getPatient();
79 | // Create an observation object
80 | Observation observation = new Observation();
81 | observation.setStatus(Observation.ObservationStatus.FINAL);
82 | observation
83 | .getCode()
84 | .addCoding()
85 | .setSystem("http://loinc.org")
86 | .setCode("789-8")
87 | .setDisplay("Erythrocytes [#/volume] in Blood by Automated count");
88 | observation.setValue(
89 | new SimpleQuantity()
90 | .setValue(6.17)
91 | .setUnit("10 trillion/L")
92 | .setSystem("http://unitsofmeasure.org")
93 | .setCode("3*12/L"));
94 | SimpleQuantity low = new SimpleQuantity();
95 | low.setValue(3.92).setSystem("http://unitsofmeasure.org").setUnit("10 trillion/L").setCode("3*12/L");
96 | observation.getReferenceRangeFirstRep().setLow(low);
97 | SimpleQuantity high = new SimpleQuantity();
98 | high.setValue(5.13).setSystem("http://unitsofmeasure.org").setUnit("10 trillion/L").setCode("3*12/L");
99 | observation.getReferenceRangeFirstRep().setHigh(high);
100 | observation.setSubject(new Reference(patient.getId()));
101 | return observation;
102 | }
103 |
104 | /**
105 | * Create a Patient object to user to build Observations.
106 | * @return Patient object with random data.
107 | */
108 | private Patient getPatient() {
109 | Patient patient = new Patient();
110 | patient.addName().setFamily("Smith").addGiven("Rob").addGiven("Bruce");
111 | patient.setGender(Enumerations.AdministrativeGender.MALE);
112 | patient.setActive(true);
113 | CodeableConcept codeableConcept = new CodeableConcept();
114 | Coding coding = new Coding();
115 | coding.setSystem( "https://fhir.acme.io/fhir/code/thing" );
116 | coding.setCode( "thing" );
117 | coding.setDisplay("Thing stuff...");
118 | List codings = new ArrayList<>();
119 | codings.add(coding);
120 | codeableConcept.setCoding( codings );
121 | Reference assigner = new Reference();
122 | assigner.setDisplay("Patient thing");
123 | Identifier thing = new Identifier();
124 | thing.setUse( Identifier.IdentifierUse.USUAL );
125 | thing.setType( codeableConcept );
126 | thing.setSystem( "https://debug.acme.io/thing/0" );
127 | thing.setValue( "99" );
128 | thing.setAssigner( assigner );
129 | List< Identifier > identifiers = patient.getIdentifier();
130 | identifiers.add( thing );
131 | patient.setIdentifier( identifiers );
132 | return patient;
133 | }
134 |
135 |
136 | protected Observation ecoliNoInterpretationBloodCulture() {
137 | Patient patient = getPatient();
138 | Observation observation = new Observation();
139 | observation.setStatus(Observation.ObservationStatus.FINAL);
140 | observation
141 | .getCode()
142 | .addCoding()
143 | .setSystem("http://loinc.org")
144 | .setCode("600-7")
145 | .setDisplay("Bacteria identified in Blood by Culture");
146 | CodeableConcept codeableConcept = new CodeableConcept();
147 | codeableConcept.addCoding(new Coding()
148 | .setSystem("http://snomed.info/sct").setCode("112283007").setDisplay("Escherichia coli"));
149 | observation.setValue(codeableConcept);
150 | observation.setSubject(new Reference(patient.getId()));
151 | return observation;
152 | }
153 |
154 |
155 |
156 | }
157 |
--------------------------------------------------------------------------------
/loinc2hpo-core/src/main/java/org/monarchinitiative/loinc2hpocore/annotation/Loinc2HpoAnnotation.java:
--------------------------------------------------------------------------------
1 | package org.monarchinitiative.loinc2hpocore.annotation;
2 |
3 | import org.monarchinitiative.loinc2hpocore.codesystems.Outcome;
4 | import org.monarchinitiative.loinc2hpocore.codesystems.ShortCode;
5 | import org.monarchinitiative.loinc2hpocore.exception.Loinc2HpoRuntimeException;
6 | import org.monarchinitiative.loinc2hpocore.loinc.LoincId;
7 | import org.monarchinitiative.phenol.ontology.data.TermId;
8 |
9 | import java.util.Comparator;
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.Optional;
13 | import java.util.function.Function;
14 | import java.util.stream.Collectors;
15 |
16 | /**
17 | * Represents a line from the {@code loinc2hpo-annotations.tsv} file
18 | * This file has the following format
19 | *
20 | * loincId loincScale outcome hpoTermId supplementalTermId curation comment
21 | * 600-7 s NOMINAL POS HP:0031864
22 | * 600-7 s NOMINAL H HP:0031864
23 | * 600-7 s NOMINAL N HP:0031864
24 | * (...)
25 | *
26 | */
27 | public class Loinc2HpoAnnotation implements Comparable {
28 |
29 | private final LoincId loincId;
30 | private final LoincScale loincScale;
31 | private final Outcome outcomeCode;
32 | private final TermId hpoTermId;
33 | private final String biocuration;
34 | /** Term to provide additional information. Can be null. */
35 | private final TermId supplementalOntologyTermId;
36 | private final String comment;
37 |
38 | public Loinc2HpoAnnotation(LoincId loincId,
39 | LoincScale loincScale,
40 | Outcome code,
41 | TermId hpoTermId,
42 | TermId supplementalOntologyTermId,
43 | String biocuration,
44 | String comment) {
45 | this.loincId = loincId;
46 | this.loincScale = loincScale;
47 | this.outcomeCode = code;
48 | this.hpoTermId = hpoTermId;
49 | this.supplementalOntologyTermId = supplementalOntologyTermId;
50 | this.biocuration = biocuration;
51 | this.comment = comment;
52 | }
53 |
54 | public Loinc2HpoAnnotation(LoincId loincId,
55 | LoincScale loincScale,
56 | Outcome code,
57 | TermId hpoTermId,
58 | String biocuration,
59 | String comment) {
60 | this.loincId = loincId;
61 | this.loincScale = loincScale;
62 | this.outcomeCode = code;
63 | this.hpoTermId = hpoTermId;
64 | this.biocuration = biocuration;
65 | this.supplementalOntologyTermId = null;
66 | this.comment = comment;
67 | }
68 |
69 | public LoincId getLoincId() {
70 | return loincId;
71 | }
72 |
73 | public LoincScale getLoincScale() {
74 | return loincScale;
75 | }
76 |
77 | public Outcome getOutcome() {
78 | return outcomeCode;
79 | }
80 |
81 | public TermId getHpoTermId() {
82 | return hpoTermId;
83 | }
84 |
85 | public String getBiocuration() {
86 | return biocuration;
87 | }
88 |
89 | public Optional getSupplementalOntologyTermId() {
90 | return Optional.ofNullable(supplementalOntologyTermId);
91 | }
92 |
93 | public String getSupplementalOntologyTermIdAsString() {
94 | if (supplementalOntologyTermId == null) {
95 | return ".";
96 | } else {
97 | return supplementalOntologyTermId.getValue();
98 | }
99 | }
100 |
101 | public String getComment() {
102 | if (comment == null || comment.isEmpty()) return ".";
103 | return comment;
104 | }
105 |
106 |
107 |
108 | public static final String [] headerFields = {"loincId", "loincScale", "outcome", "hpoTermId",
109 | "supplementalTermId", "curation", "comment"};
110 | private static final int EXPECTED_NUMBER_OF_FIELDS = headerFields.length;
111 |
112 | /**
113 | * @return A tab-separate values line for the loinc2hpo-annotation.tsv file.
114 | */
115 | public String toTsv() {
116 | return String.format("%s\t%s\t%s\t%s\t%s\t%s\t%s",
117 | loincId,
118 | loincScale.shortName(),
119 | outcomeCode.getOutcome(),
120 | hpoTermId.getValue(),
121 | getSupplementalOntologyTermIdAsString(),
122 | biocuration,
123 | getComment()
124 | );
125 | }
126 |
127 |
128 | public static Loinc2HpoAnnotation fromAnnotationLine(String line) {
129 | String [] fields = line.split("\t");
130 | if (fields.length != EXPECTED_NUMBER_OF_FIELDS) {
131 | System.err.printf("Malformed line with %d fields: %s%n", fields.length, line);
132 |
133 | }
134 | LoincId loincId = new LoincId(fields[0]);
135 | LoincScale scale = LoincScale.fromString(fields[1]);
136 |
137 | TermId hpoId = TermId.of(fields[3]);
138 | String curation = fields[5];
139 | String comment = fields[6];
140 | String outcomeString = fields[2];
141 | Outcome outcome;
142 | if (scale.equals(LoincScale.NOMINAL)) {
143 | outcome = Outcome.nominal(outcomeString);
144 | } else {
145 | outcome = new Outcome(ShortCode.fromShortCode(outcomeString));
146 | }
147 | if (fields[4].equals(".")) {
148 | return new Loinc2HpoAnnotation(loincId,scale,outcome,hpoId,curation,comment);
149 | }
150 | TermId supplementalId = TermId.of(fields[4]);
151 | return new Loinc2HpoAnnotation(loincId, scale, outcome, hpoId, supplementalId, curation,comment);
152 | }
153 |
154 | private static LoincScale getScale(List outcomes) {
155 | List scales = outcomes.stream()
156 | .map(Loinc2HpoAnnotation::getLoincScale)
157 | .distinct().collect(Collectors.toList());
158 | if (scales.size() == 0) {
159 | // should never happen
160 | throw new Loinc2HpoRuntimeException("Could not extract LoincScale");
161 | } else if (scales.size() > 1) {
162 | // should never happen
163 | throw new Loinc2HpoRuntimeException("Extracted more than one LoincScale");
164 | }
165 | return scales.get(0);
166 | }
167 |
168 | public static LoincAnnotation outcomes2LoincAnnotation(List outcomes) {
169 | int n = outcomes.size();
170 | // map with all of the outcomes for the current LOINC test
171 | Map outcomeMap = outcomes.stream()
172 | .collect(Collectors.toMap(Loinc2HpoAnnotation::getOutcome, Function.identity()));
173 | LoincScale scale = getScale(outcomes);
174 | if (scale.equals(LoincScale.QUANTITATIVE)) {
175 | return QuantitativeLoincAnnotation.fromOutcomeMap(outcomeMap);
176 | } else if (scale.equals(LoincScale.ORDINAL)) {
177 | return OrdinalHpoAnnotation.fromOutcomeMap(outcomeMap);
178 | } else if (scale.equals(LoincScale.NOMINAL)) {
179 | return new NominalLoincAnnotation(outcomeMap);
180 | }
181 | // if we get here, we did not match and there is some problem
182 | for (var oc : outcomes) {
183 | System.err.println("[ERROR] " + oc);
184 | }
185 | StringBuilder sb = new StringBuilder("Malformed outcomes\nn=").append(outcomes.size());
186 | for (var oc : outcomes) {
187 | sb.append("\t[ERROR] ").append(oc).append("\n");
188 | }
189 | throw new Loinc2HpoRuntimeException(sb.toString());
190 | }
191 |
192 |
193 | @Override
194 | public int compareTo(Loinc2HpoAnnotation that) {
195 | //return Comparator.comparing(getLoincId()).thenComparing(getOutcome());
196 | int res = this.loincId.compareTo(that.loincId);
197 | return res != 0 ? res : this.outcomeCode.compareTo(that.outcomeCode);
198 | }
199 |
200 | @Override
201 | public String toString() {
202 | return toTsv();
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/docs/FHIR_mapping.rst:
--------------------------------------------------------------------------------
1 | ============
2 | FHIR Mapping
3 | ============
4 |
5 | loinc2hpo can be used to convert LOINC-encoded laboratory results from FHIR.
6 |
7 |
8 | Mapping LOINC to candidate HPO terms
9 | ====================================
10 |
11 | LOINC observations have four main categories, quantitative(Qn), ordinal(Ord),
12 | nominal (Nom) and narrative(Nar).
13 | The majority of them are ``Qn`` (~50%) and ``Ord`` (~26%) and a smaller percentage
14 | are ``Nom`` and ``Nar`` type (<10%). Currently, loinc2hpo maps QN, Ord, and Nom codes
15 | but is not able to map Nar codes.
16 |
17 | FHIR Interpretation codes
18 | =========================
19 |
20 | Test outcomes can represented with a code from the
21 | `FHIR interpretation code valueset `_.
22 | HPO terms are qualitative. For instance,
23 | `Thrombocytopenia HP:0001873 `_ does not
24 | indicate whether there is a mild, moderate or severe degree of low blood platelets. Therefore
25 | we map FHIR codes for different degrees of severity to the same internal code (these codes
26 | are shown on the index page of this documentation).
27 |
28 |
29 | Table 1: FHIR interpretation code set Mapping to internal code system
30 |
31 | +------------------------------------+---------------------------+
32 | |FHIR interpretation code value set |internal code |
33 | +-------+----------------------------+---------------------------+
34 | |Code | Meaning |Code | Meaning |
35 | +=======+============================+========+==================+
36 | |< |Off scale low |L |low |
37 | +-------+----------------------------+--------+------------------+
38 | |> |Off scale high |H |high |
39 | +-------+----------------------------+--------+------------------+
40 | |A |Abnormal |A |abnormal |
41 | +-------+----------------------------+--------+------------------+
42 | |AA |Critically abnormal |A |abnormal |
43 | +-------+----------------------------+--------+------------------+
44 | |AC |Anti-complementary |POS |present |
45 | | |substances present | | |
46 | +-------+----------------------------+--------+------------------+
47 | |B |Better |N |normal |
48 | +-------+----------------------------+--------+------------------+
49 | |D |Significant change down |L |low |
50 | +-------+----------------------------+--------+------------------+
51 | |DET |Detected |POS |present |
52 | +-------+----------------------------+--------+------------------+
53 | |H |High |H |high |
54 | +-------+----------------------------+--------+------------------+
55 | |HH |Critically high |H |high |
56 | +-------+----------------------------+--------+------------------+
57 | |HM |Hold for Medical Review |U |unknown |
58 | +-------+----------------------------+--------+------------------+
59 | |HU |Very high |H |high |
60 | +-------+----------------------------+--------+------------------+
61 | |I |Intermediate |N |normal |
62 | +-------+----------------------------+--------+------------------+
63 | |IE |Insufficient evidence |U |unknown |
64 | +-------+----------------------------+--------+------------------+
65 | |IND |Indeterminate |U |unknown |
66 | +-------+----------------------------+--------+------------------+
67 | |L |Low |L |low |
68 | +-------+----------------------------+--------+------------------+
69 | |LL |Critically low |L |low |
70 | +-------+----------------------------+--------+------------------+
71 | |LU |Very low |L |low |
72 | +-------+----------------------------+--------+------------------+
73 | |MS |Moderately susceptible. |U |unknown |
74 | | |(microbiology) | | |
75 | +-------+----------------------------+--------+------------------+
76 | |N |Normal |N |normal |
77 | +-------+----------------------------+--------+------------------+
78 | |ND |Not Detected |NEG |not present |
79 | +-------+----------------------------+--------+------------------+
80 | |NEG |Negative |NEG |not present |
81 | +-------+----------------------------+--------+------------------+
82 | |NR |Non-reactive |NEG |not present |
83 | +-------+----------------------------+--------+------------------+
84 | |NS |Non-susceptible |U |unknown |
85 | +-------+----------------------------+--------+------------------+
86 | |null |No range defined or normal |U |unknown |
87 | | |ranges don't apply | | |
88 | +-------+----------------------------+--------+------------------+
89 | |OBX |Interpretation qualifiers |U |unknown |
90 | | |in separate OBX segments | | |
91 | +-------+----------------------------+--------+------------------+
92 | |POS |Positive |POS |positive |
93 | +-------+----------------------------+--------+------------------+
94 | |QCF |Quality Control Failure |U |unknown |
95 | +-------+----------------------------+--------+------------------+
96 | |R |Resistant |U |unknown |
97 | +-------+----------------------------+--------+------------------+
98 | |RR |Reactive |POS |present |
99 | +-------+----------------------------+--------+------------------+
100 | |S |Susceptible |U |unknown |
101 | +-------+----------------------------+--------+------------------+
102 | |SDD |Susceptible-dose dependent |U |unknown |
103 | +-------+----------------------------+--------+------------------+
104 | |SYN-R |Synergy - resistant |U |unknown |
105 | +-------+----------------------------+--------+------------------+
106 | |SYN-S |Synergy - susceptible |U |unknown |
107 | +-------+----------------------------+--------+------------------+
108 | |TOX |Cytotoxic substance present |POS |present |
109 | +-------+----------------------------+--------+------------------+
110 | |U |Significant change up |H |high |
111 | +-------+----------------------------+--------+------------------+
112 | |VS |Very susceptible. |U |unknown |
113 | | |(microbiology) | | |
114 | +-------+----------------------------+--------+------------------+
115 | |W |Worse |A |abnormal |
116 | +-------+----------------------------+--------+------------------+
117 | |WR |Weakly reactive |POS |present |
118 | +-------+----------------------------+--------+------------------+
119 |
120 |
121 | The following graph summarizes the mapping.
122 |
123 | .. image:: images/annotation_scheme.png
124 | :width: 400
125 | :alt: Annotation scheme
126 |
127 | Nominal outcomes
128 | ================
129 |
130 | Nominal observations can use coding systems that are more difficult to handle.
131 | For example, `Loinc 600-7 `_ (Bacteria identified in Blood by Culture)
132 | may use a SNOMED concept to represent the
133 | finding that *Staphylococcus aureus* is detected::
134 |
135 | "coding":[{
136 | "system": "http://snomed.info/sct",
137 | "code": "3092008",
138 | "display": "Staphylococcus aureus"
139 | }]
140 |
141 | We currently map this to `Bacteremia HP:0031864 `_,
142 | but this term does not contain information about which bacterium was idenfitied in the blood.
143 |
144 | We are currently extending the annotations to enable one to indicate what kind of bacteremia. In the above mentioned case,
145 | one would use the NCBI `Taxonomy ID: 1280 `_
146 | for *Staphylococcus aureus*.
147 |
148 |
149 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------