rxome-generator 1.0.3 → 1.0.4-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/JSON_INPUT_FORMAT.md +380 -0
- package/README.html +834 -0
- package/README.md +50 -19
- package/demos/demo_overfull.json +144 -0
- package/index.js +3 -3
- package/lib/package-info.js +4 -0
- package/lib/phenopackets.js +1951 -0
- package/lib/rxome-api-demo.js +26 -0
- package/lib/rxome-api.js +257 -0
- package/lib/rxome-api.test.js +9 -9
- package/lib/{rxome-generator.cjs → rxome-generator.js} +94 -80
- package/lib/rxome-generator.test.js +26 -17
- package/package.json +11 -2
- package/rxcode.js +12 -5
- package/rxcode.test.js +38 -39
- package/lib/rxome-api-demo.cjs +0 -60
- package/lib/rxome-api.cjs +0 -192
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# RxOME QR Code Generator -- JSON Input Format
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The RxOME QR code generator accepts a JSON object following the
|
|
6
|
+
**PhenoPacket v2** standard with RxOME-specific extensions. The input
|
|
7
|
+
describes a patient's clinical and genomic data, which is then
|
|
8
|
+
protobuf-encoded, encrypted, and rendered as a QR code.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Top-Level Structure
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"id": "string",
|
|
17
|
+
"comment": "string",
|
|
18
|
+
"subject": { ... },
|
|
19
|
+
"phenotypicFeatures": [ ... ],
|
|
20
|
+
"compressedFeatures": { ... },
|
|
21
|
+
"interpretations": [ ... ],
|
|
22
|
+
"diagnosis": { ... },
|
|
23
|
+
"metaData": { ... },
|
|
24
|
+
"credentials": { ... }
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
All top-level fields are optional except `credentials` (required for QR
|
|
29
|
+
generation but stripped before encoding).
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Field Reference
|
|
34
|
+
|
|
35
|
+
### Root Fields
|
|
36
|
+
|
|
37
|
+
| Field | Type | Required | Description |
|
|
38
|
+
|-------|------|----------|-------------|
|
|
39
|
+
| `id` | string | No | Unique QR code / record identifier |
|
|
40
|
+
| `comment` | string | No | Free-text remarks |
|
|
41
|
+
| `subject` | object | No | Patient demographics |
|
|
42
|
+
| `phenotypicFeatures` | array | No | HPO terms (detailed form) |
|
|
43
|
+
| `compressedFeatures` | object | No | HPO terms (compact form, preferred) |
|
|
44
|
+
| `interpretations` | array | No | Genomic findings and diagnoses |
|
|
45
|
+
| `diagnosis` | object | No | Disease/condition information |
|
|
46
|
+
| `metaData` | object | No | Creator info, timestamps, schema version |
|
|
47
|
+
| `credentials` | object | **Yes** | API credentials (never encoded in QR) |
|
|
48
|
+
|
|
49
|
+
> **Note:** Supply either `phenotypicFeatures` or `compressedFeatures`,
|
|
50
|
+
> not both. If `phenotypicFeatures` is provided it is automatically
|
|
51
|
+
> converted to `compressedFeatures` during preprocessing.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### `subject` -- Patient Information
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
"subject": {
|
|
59
|
+
"id": "proband A",
|
|
60
|
+
"dateOfBirth": "1994-01-01T00:00:00Z",
|
|
61
|
+
"sex": "FEMALE",
|
|
62
|
+
"gender": { "id": "...", "label": "..." },
|
|
63
|
+
"alternateIds": ["ID-2"],
|
|
64
|
+
"vitalStatus": { ... },
|
|
65
|
+
"karyotypicSex": "XX",
|
|
66
|
+
"taxonomy": { "id": "NCBITaxon:9606", "label": "Homo sapiens" },
|
|
67
|
+
"timeAtLastEncounter": { ... }
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Sex values:** `"UNKNOWN_SEX"` / `0`, `"FEMALE"` / `1`, `"MALE"` / `2`,
|
|
72
|
+
`"OTHER_SEX"` / `3`. String values are converted to numbers during
|
|
73
|
+
sanitisation.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### `phenotypicFeatures` -- Detailed HPO Terms
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
"phenotypicFeatures": [
|
|
81
|
+
{
|
|
82
|
+
"type": { "id": "HP:0030084", "label": "Clinodactyly" },
|
|
83
|
+
"excluded": false,
|
|
84
|
+
"severity": { "id": "HP:0012825", "label": "Mild" },
|
|
85
|
+
"modifiers": [],
|
|
86
|
+
"onset": { ... },
|
|
87
|
+
"resolution": { ... },
|
|
88
|
+
"evidence": [],
|
|
89
|
+
"description": "..."
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Set `excluded: true` to indicate a feature is explicitly absent.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### `compressedFeatures` -- Compact HPO Terms (Preferred)
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
"compressedFeatures": {
|
|
102
|
+
"includes": ["HP:0030084", "HP:0000555", "HP:0000486"],
|
|
103
|
+
"excludes": ["HP:0031360"]
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
This is the preferred input form. It contains only the HPO IDs, separated
|
|
108
|
+
into present (`includes`) and absent (`excludes`) features.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### `interpretations` -- Genomic Findings
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
"interpretations": [
|
|
116
|
+
{
|
|
117
|
+
"id": "interpretation.id",
|
|
118
|
+
"progressStatus": "SOLVED",
|
|
119
|
+
"diagnosis": {
|
|
120
|
+
"disease": {
|
|
121
|
+
"id": "OMIM:263750",
|
|
122
|
+
"label": "Optional disease label"
|
|
123
|
+
},
|
|
124
|
+
"genomicInterpretations": [
|
|
125
|
+
{
|
|
126
|
+
"subjectOrBiosampleId": "optional.id",
|
|
127
|
+
"interpretationStatus": 0,
|
|
128
|
+
"variantInterpretation": {
|
|
129
|
+
"acmgPathogenicityClassification": "PATHOGENIC",
|
|
130
|
+
"variationDescriptor": {
|
|
131
|
+
"geneContext": {
|
|
132
|
+
"valueId": "HGNC:9884",
|
|
133
|
+
"symbol": "RB1",
|
|
134
|
+
"alternateIds": ["5925"],
|
|
135
|
+
"description": "..."
|
|
136
|
+
},
|
|
137
|
+
"expressions": [
|
|
138
|
+
{ "syntax": "hgvs.c", "value": "NM_000321.2:c.958C>T" },
|
|
139
|
+
{ "syntax": "iscn", "value": "..." }
|
|
140
|
+
],
|
|
141
|
+
"allelicState": {
|
|
142
|
+
"id": "GENO:0000135",
|
|
143
|
+
"label": "heterozygous"
|
|
144
|
+
},
|
|
145
|
+
"extensions": [
|
|
146
|
+
{ "name": "test-type", "value": "Exome, short read" },
|
|
147
|
+
{ "name": "cnv", "value": "1" },
|
|
148
|
+
{ "name": "meth", "value": "1" },
|
|
149
|
+
{ "name": "af", "value": "0.5" },
|
|
150
|
+
{ "name": "rl", "value": "42" },
|
|
151
|
+
{ "name": "chr", "value": "13q14.2" },
|
|
152
|
+
{ "name": "site", "value": "..." },
|
|
153
|
+
{ "name": "upd", "value": "..." }
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### Enum Values
|
|
165
|
+
|
|
166
|
+
**progressStatus:**
|
|
167
|
+
`"UNKNOWN_PROGRESS"` (0), `"IN_PROGRESS"` (1), `"COMPLETED"` (2),
|
|
168
|
+
`"SOLVED"` (3), `"UNSOLVED"` (4)
|
|
169
|
+
|
|
170
|
+
**acmgPathogenicityClassification:**
|
|
171
|
+
`"NOT_PROVIDED"` (0), `"BENIGN"` (1), `"LIKELY_BENIGN"` (2),
|
|
172
|
+
`"UNCERTAIN_SIGNIFICANCE"` (3), `"LIKELY_PATHOGENIC"` (4),
|
|
173
|
+
`"PATHOGENIC"` (5), `"RISK_ALLELE"` (999)
|
|
174
|
+
|
|
175
|
+
> **Note that `RISK_ALLELE` is a RxOME specific extension of the PhenoPackage standard.**
|
|
176
|
+
|
|
177
|
+
**allelicState (Zygosity, GENO ontology):**
|
|
178
|
+
|
|
179
|
+
| Code | Meaning |
|
|
180
|
+
|------|---------|
|
|
181
|
+
| `GENO_0000137` | Unspecified zygosity |
|
|
182
|
+
| `GENO_0000136` | Homozygous |
|
|
183
|
+
| `GENO_0000135` | Heterozygous |
|
|
184
|
+
| `GENO_0000402` | Compound heterozygous |
|
|
185
|
+
| `GENO_0000134` | Hemizygous |
|
|
186
|
+
| `GENO_0000604` | Hemizygous X-linked |
|
|
187
|
+
| `GENO_0000605` | Hemizygous Y-linked |
|
|
188
|
+
| `GENO_0000606` | Hemizygous insertion-linked |
|
|
189
|
+
| `GENO_0000392` | Aneusomic zygosity |
|
|
190
|
+
| `GENO_0000393` | Trisomic homozygous |
|
|
191
|
+
| `GENO_0000394` | Trisomic heterozygous |
|
|
192
|
+
| `GENO_0000602` | Homoplasmic |
|
|
193
|
+
| `GENO_0000603` | Heteroplasmic |
|
|
194
|
+
| `GENO_0000964` | Mosaic |
|
|
195
|
+
|
|
196
|
+
#### RxOME Extension Fields
|
|
197
|
+
|
|
198
|
+
These are carried in the `extensions` array of a `variationDescriptor`:
|
|
199
|
+
|
|
200
|
+
| Name | Values | Description |
|
|
201
|
+
|------|--------|-------------|
|
|
202
|
+
| `test-type` | Free text (e.g. "Exome, short read", "Multigene panel") | Genetic test performed |
|
|
203
|
+
| `cnv` | `0` = not provided, `1` = deletion, `2` = duplication | Copy-number variation |
|
|
204
|
+
| `meth` | `0` = not provided, `1` = hyper, `2` = hypo, `3` = intermediate | Methylation status |
|
|
205
|
+
| `af` | Numeric string | Allele frequency |
|
|
206
|
+
| `rl` | Numeric string | Repeat length |
|
|
207
|
+
| `chr` | String (e.g. "13q14.2") | Chromosomal region |
|
|
208
|
+
| `site` | String | Methylation site |
|
|
209
|
+
| `upd` | String | Uniparental disomy |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### `metaData`
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
"metaData": {
|
|
217
|
+
"created": "2021-05-14T10:35:00Z",
|
|
218
|
+
"createdBy": "MGZ Munich",
|
|
219
|
+
"submittedBy": "clinician@mgz-muenchen.de",
|
|
220
|
+
"pseudonym": "WDJ6GW2MMZ6A",
|
|
221
|
+
"phenopacketSchemaVersion": "2.0",
|
|
222
|
+
"resources": [],
|
|
223
|
+
"externalReferences": []
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
### `credentials` -- API Access (Never in QR Code)
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
"credentials": {
|
|
233
|
+
"keyId": "rxome",
|
|
234
|
+
"key": "Rf7VbeUBQmjvAagwsWx6riaZYc7h4OBD4CuxYyZ5bgA=",
|
|
235
|
+
"keyFile": "/path/to/key",
|
|
236
|
+
"user": "clinician@mgz-muenchen.de"
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Provide either `key` (Base64-encoded Ed25519 private key) or `keyFile`
|
|
241
|
+
(path to key file), not both. This object is stripped before any data
|
|
242
|
+
enters the QR code.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Encoding Pipeline: Plain vs. Protobuf-Compressed Fields
|
|
247
|
+
|
|
248
|
+
The QR code contains two distinct areas: **plain-text metadata** (the
|
|
249
|
+
outer JSON envelope) and an **encrypted payload** (protobuf-encoded
|
|
250
|
+
medical data). The pipeline is:
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
Input JSON
|
|
254
|
+
|
|
|
255
|
+
v
|
|
256
|
+
[1] Whitelist -- keep only recognised PhenoPacket fields; strip credentials
|
|
257
|
+
|
|
|
258
|
+
v
|
|
259
|
+
[2] Sanitise -- convert string enums to numeric (sex, progressStatus, ACMG)
|
|
260
|
+
|
|
|
261
|
+
v
|
|
262
|
+
[3] HPO compress -- convert phenotypicFeatures[] to compressedFeatures{}
|
|
263
|
+
|
|
|
264
|
+
v
|
|
265
|
+
[4] Protobuf encode -- encode entire medical object to binary (PhenoPacket v2 schema)
|
|
266
|
+
|
|
|
267
|
+
v
|
|
268
|
+
[5] Base64
|
|
269
|
+
|
|
|
270
|
+
v
|
|
271
|
+
[6] PGP encrypt (OpenPGP, Curve25519)
|
|
272
|
+
|
|
|
273
|
+
v
|
|
274
|
+
[7] Assemble QR JSON envelope
|
|
275
|
+
|
|
|
276
|
+
v
|
|
277
|
+
[8] Render QR code (PNG)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### What Ends Up in the QR Code
|
|
281
|
+
|
|
282
|
+
The final QR code contains a JSON string with this structure:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"createdBy": "MGZ Munich",
|
|
287
|
+
"labid": "rxome",
|
|
288
|
+
"keyver": "2",
|
|
289
|
+
"apiver": "1.0",
|
|
290
|
+
"pseudonym": "WDJ6GW2MMZ6A",
|
|
291
|
+
"payload": "<PGP-encrypted, Base64-encoded protobuf binary>"
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Field Classification: Plain-Text vs. Protobuf+Encrypted
|
|
296
|
+
|
|
297
|
+
| Field | Storage in QR | Explanation |
|
|
298
|
+
|-------|--------------|-------------|
|
|
299
|
+
| **PLAIN-TEXT (QR envelope, readable without decryption)** | |
|
|
300
|
+
| `createdBy` | plain | Lab/creator name from metaData |
|
|
301
|
+
| `submittedBy` | plain | Submitter email from metaData |
|
|
302
|
+
| `created` | plain | Creation timestamp from metaData |
|
|
303
|
+
| `labid` | plain | Lab identifier from credentials.keyId |
|
|
304
|
+
| `keyver` | plain | Encryption key version (from API) |
|
|
305
|
+
| `apiver` | plain | API version string |
|
|
306
|
+
| `pseudonym` | plain | Patient pseudonym (from API) |
|
|
307
|
+
| **PROTOBUF-ENCODED + ENCRYPTED (inside `payload`)** | |
|
|
308
|
+
| `id` | protobuf + encrypted | Record identifier |
|
|
309
|
+
| `comment` | protobuf + encrypted | Free-text remarks |
|
|
310
|
+
| `subject` | protobuf + encrypted | All patient demographics (DOB, sex, ...) |
|
|
311
|
+
| `compressedFeatures` | protobuf + encrypted | HPO phenotype terms (includes + excludes) |
|
|
312
|
+
| `interpretations` | protobuf + encrypted | All genomic findings, variants, diagnoses |
|
|
313
|
+
| `diagnosis` | protobuf + encrypted | Disease/condition information |
|
|
314
|
+
| `metaData` | protobuf + encrypted | Full metadata structure (without pseudonym) |
|
|
315
|
+
| **NEVER IN QR** | |
|
|
316
|
+
| `credentials` | not stored | Stripped before encoding; used only for API auth |
|
|
317
|
+
|
|
318
|
+
> **Key insight:** The outer QR envelope carries only non-sensitive
|
|
319
|
+
> routing metadata (who created it, pseudonym, key version). All clinical
|
|
320
|
+
> and genomic data is protobuf-serialised, Base64-encoded, then
|
|
321
|
+
> PGP-encrypted before being placed in the `payload` field.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Complete Example
|
|
326
|
+
|
|
327
|
+
```json
|
|
328
|
+
{
|
|
329
|
+
"id": "QR-Code ID",
|
|
330
|
+
"comment": "useful remarks",
|
|
331
|
+
"subject": {
|
|
332
|
+
"id": "proband A",
|
|
333
|
+
"dateOfBirth": "1994-01-01T00:00:00Z",
|
|
334
|
+
"sex": "FEMALE"
|
|
335
|
+
},
|
|
336
|
+
"compressedFeatures": {
|
|
337
|
+
"includes": ["HP:0030084", "HP:0000555", "HP:0000486"],
|
|
338
|
+
"excludes": ["HP:0031360"]
|
|
339
|
+
},
|
|
340
|
+
"interpretations": [
|
|
341
|
+
{
|
|
342
|
+
"id": "interpretation.id",
|
|
343
|
+
"progressStatus": "SOLVED",
|
|
344
|
+
"diagnosis": {
|
|
345
|
+
"disease": { "id": "OMIM:263750" },
|
|
346
|
+
"genomicInterpretations": [
|
|
347
|
+
{
|
|
348
|
+
"variantInterpretation": {
|
|
349
|
+
"acmgPathogenicityClassification": "PATHOGENIC",
|
|
350
|
+
"variationDescriptor": {
|
|
351
|
+
"geneContext": {
|
|
352
|
+
"valueId": "HGNC:9884",
|
|
353
|
+
"symbol": "RB1"
|
|
354
|
+
},
|
|
355
|
+
"expressions": [
|
|
356
|
+
{ "syntax": "hgvs.c", "value": "NM_000321.2:c.958C>T" }
|
|
357
|
+
],
|
|
358
|
+
"allelicState": { "id": "GENO:0000135" },
|
|
359
|
+
"extensions": [
|
|
360
|
+
{ "name": "test-type", "value": "Exome, short read" }
|
|
361
|
+
]
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
],
|
|
369
|
+
"metaData": {
|
|
370
|
+
"created": "2021-05-14T10:35:00Z",
|
|
371
|
+
"createdBy": "MGZ Munich",
|
|
372
|
+
"submittedBy": "clinician@mgz-muenchen.de"
|
|
373
|
+
},
|
|
374
|
+
"credentials": {
|
|
375
|
+
"key": "Rf7VbeUBQmjvAagwsWx6riaZYc7h4OBD4CuxYyZ5bgA=",
|
|
376
|
+
"keyId": "rxome",
|
|
377
|
+
"user": "clinician@mgz-muenchen.de"
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
```
|