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.
@@ -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
+ ```