fhirsmith 0.3.0

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.
Files changed (277) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/FHIRsmith.png +0 -0
  3. package/README.md +277 -0
  4. package/config-template.json +144 -0
  5. package/library/folder-setup.js +58 -0
  6. package/library/html-server.js +166 -0
  7. package/library/html.js +835 -0
  8. package/library/i18nsupport.js +259 -0
  9. package/library/languages.js +779 -0
  10. package/library/logger-telnet.js +205 -0
  11. package/library/logger.js +279 -0
  12. package/library/package-manager.js +876 -0
  13. package/library/utilities.js +196 -0
  14. package/library/version-utilities.js +1056 -0
  15. package/npmprojector/config-example.json +13 -0
  16. package/npmprojector/indexer.js +394 -0
  17. package/npmprojector/npmprojector.js +395 -0
  18. package/npmprojector/readme.md +174 -0
  19. package/npmprojector/watcher.js +335 -0
  20. package/package.json +119 -0
  21. package/packages/package-crawler.js +846 -0
  22. package/packages/packages-template.html +126 -0
  23. package/packages/packages.js +2838 -0
  24. package/passwords.ini +2 -0
  25. package/publisher/publisher-template.html +208 -0
  26. package/publisher/publisher.js +2167 -0
  27. package/publisher/task-draft.js +458 -0
  28. package/registry/api.js +735 -0
  29. package/registry/crawler.js +637 -0
  30. package/registry/model.js +513 -0
  31. package/registry/readme.md +243 -0
  32. package/registry/registry-data.json +121015 -0
  33. package/registry/registry-template.html +126 -0
  34. package/registry/registry.js +1395 -0
  35. package/registry/test-runner.js +237 -0
  36. package/root-template.html +124 -0
  37. package/server.js +524 -0
  38. package/shl/private-key.pem +5 -0
  39. package/shl/public-key.pem +18 -0
  40. package/shl/shl.js +1125 -0
  41. package/shl/vhl.js +69 -0
  42. package/static/FHIRsmith128.png +0 -0
  43. package/static/FHIRsmith16.png +0 -0
  44. package/static/FHIRsmith32.png +0 -0
  45. package/static/FHIRsmith64.png +0 -0
  46. package/static/assets/css/bootstrap-fhir.css +5302 -0
  47. package/static/assets/css/bootstrap-glyphicons.css +2 -0
  48. package/static/assets/css/bootstrap.css +4097 -0
  49. package/static/assets/css/jquery-ui.css +523 -0
  50. package/static/assets/css/jquery-ui.structure.css +863 -0
  51. package/static/assets/css/jquery-ui.structure.min.css +5 -0
  52. package/static/assets/css/jquery-ui.theme.css +439 -0
  53. package/static/assets/css/jquery-ui.theme.min.css +5 -0
  54. package/static/assets/css/jquery.ui.all.css +7 -0
  55. package/static/assets/css/modules.css +18 -0
  56. package/static/assets/css/project.css +367 -0
  57. package/static/assets/css/pygments-manni.css +66 -0
  58. package/static/assets/css/tags.css +74 -0
  59. package/static/assets/css/xml.css +2 -0
  60. package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
  61. package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
  62. package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
  63. package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
  64. package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
  65. package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
  66. package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
  67. package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
  68. package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
  69. package/static/assets/ico/favicon.ico +0 -0
  70. package/static/assets/ico/favicon.png +0 -0
  71. package/static/assets/images/fhir-logo-www.png +0 -0
  72. package/static/assets/images/fhir-logo.png +0 -0
  73. package/static/assets/images/hl7-logo.png +0 -0
  74. package/static/assets/images/logo_ansinew.jpg +0 -0
  75. package/static/assets/images/search.png +0 -0
  76. package/static/assets/images/stripe.png +0 -0
  77. package/static/assets/images/target.png +0 -0
  78. package/static/assets/images/tx-registry-root.gif +0 -0
  79. package/static/assets/images/tx-registry.png +0 -0
  80. package/static/assets/images/tx-server.png +0 -0
  81. package/static/assets/images/tx-version.png +0 -0
  82. package/static/assets/js/bootstrap.min.js +6 -0
  83. package/static/assets/js/fhir-gw.js +259 -0
  84. package/static/assets/js/fhir.js +2 -0
  85. package/static/assets/js/html5shiv.js +8 -0
  86. package/static/assets/js/jcookie.js +96 -0
  87. package/static/assets/js/jquery-ui.min.js +6 -0
  88. package/static/assets/js/jquery.js +10716 -0
  89. package/static/assets/js/jquery.min.js +2 -0
  90. package/static/assets/js/jquery.ui.core.js +314 -0
  91. package/static/assets/js/jquery.ui.draggable.js +825 -0
  92. package/static/assets/js/jquery.ui.mouse.js +162 -0
  93. package/static/assets/js/jquery.ui.resizable.js +842 -0
  94. package/static/assets/js/jquery.ui.widget.js +268 -0
  95. package/static/assets/js/json2.js +487 -0
  96. package/static/assets/js/jtip.js +97 -0
  97. package/static/assets/js/respond.min.js +6 -0
  98. package/static/assets/js/statuspage.js +70 -0
  99. package/static/assets/js/xml.js +2 -0
  100. package/static/dist/js/bootstrap.js +1964 -0
  101. package/static/favicon.png +0 -0
  102. package/static/fhir.css +626 -0
  103. package/static/icon-fhir-16.png +0 -0
  104. package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  105. package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  106. package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
  107. package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  108. package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  109. package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  110. package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  111. package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  112. package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  113. package/static/images/ui-icons_222222_256x240.png +0 -0
  114. package/static/images/ui-icons_228ef1_256x240.png +0 -0
  115. package/static/images/ui-icons_ef8c08_256x240.png +0 -0
  116. package/static/images/ui-icons_ffd27a_256x240.png +0 -0
  117. package/static/images/ui-icons_ffffff_256x240.png +0 -0
  118. package/static/js/jquery.effects.blind.js +49 -0
  119. package/static/js/jquery.effects.bounce.js +78 -0
  120. package/static/js/jquery.effects.clip.js +54 -0
  121. package/static/js/jquery.effects.core.js +763 -0
  122. package/static/js/jquery.effects.drop.js +50 -0
  123. package/static/js/jquery.effects.explode.js +79 -0
  124. package/static/js/jquery.effects.fade.js +32 -0
  125. package/static/js/jquery.effects.fold.js +56 -0
  126. package/static/js/jquery.effects.highlight.js +50 -0
  127. package/static/js/jquery.effects.pulsate.js +51 -0
  128. package/static/js/jquery.effects.scale.js +178 -0
  129. package/static/js/jquery.effects.shake.js +57 -0
  130. package/static/js/jquery.effects.slide.js +50 -0
  131. package/static/js/jquery.effects.transfer.js +45 -0
  132. package/static/js/jquery.ui.accordion.js +611 -0
  133. package/static/js/jquery.ui.autocomplete.js +612 -0
  134. package/static/js/jquery.ui.button.js +416 -0
  135. package/static/js/jquery.ui.datepicker.js +1823 -0
  136. package/static/js/jquery.ui.dialog.js +878 -0
  137. package/static/js/jquery.ui.droppable.js +296 -0
  138. package/static/js/jquery.ui.position.js +252 -0
  139. package/static/js/jquery.ui.progressbar.js +109 -0
  140. package/static/js/jquery.ui.selectable.js +266 -0
  141. package/static/js/jquery.ui.slider.js +666 -0
  142. package/static/js/jquery.ui.sortable.js +1077 -0
  143. package/static/js/jquery.ui.tabs.js +758 -0
  144. package/stats.js +80 -0
  145. package/test-cache/vsac/vsac-valuesets.db +0 -0
  146. package/token/nginx_passport_setup.md +383 -0
  147. package/token/security_guide.md +294 -0
  148. package/token/token-template.html +330 -0
  149. package/token/token.js +1300 -0
  150. package/translations/Messages.properties +1510 -0
  151. package/translations/Messages_ar.properties +1399 -0
  152. package/translations/Messages_de.properties +836 -0
  153. package/translations/Messages_es.properties +737 -0
  154. package/translations/Messages_fr.properties +1 -0
  155. package/translations/Messages_ja.properties +893 -0
  156. package/translations/Messages_nl.properties +1357 -0
  157. package/translations/Messages_pt.properties +1302 -0
  158. package/translations/Messages_ru.properties +1 -0
  159. package/translations/Messages_uz.properties +1 -0
  160. package/translations/Messages_zh.properties +1 -0
  161. package/translations/rendering-phrases.properties +1128 -0
  162. package/translations/rendering-phrases_ar.properties +1091 -0
  163. package/translations/rendering-phrases_de.properties +6 -0
  164. package/translations/rendering-phrases_es.properties +6 -0
  165. package/translations/rendering-phrases_fr.properties +624 -0
  166. package/translations/rendering-phrases_ja.properties +21 -0
  167. package/translations/rendering-phrases_nl.properties +970 -0
  168. package/translations/rendering-phrases_pt.properties +1020 -0
  169. package/translations/rendering-phrases_ru.properties +1094 -0
  170. package/translations/rendering-phrases_uz.properties +1 -0
  171. package/translations/rendering-phrases_zh.properties +1 -0
  172. package/tx/README.md +418 -0
  173. package/tx/cm/cm-api.js +110 -0
  174. package/tx/cm/cm-database.js +735 -0
  175. package/tx/cm/cm-package.js +325 -0
  176. package/tx/cs/cs-api.js +789 -0
  177. package/tx/cs/cs-areacode.js +615 -0
  178. package/tx/cs/cs-country.js +1110 -0
  179. package/tx/cs/cs-cpt.js +785 -0
  180. package/tx/cs/cs-cs.js +1579 -0
  181. package/tx/cs/cs-currency.js +539 -0
  182. package/tx/cs/cs-db.js +1321 -0
  183. package/tx/cs/cs-hgvs.js +329 -0
  184. package/tx/cs/cs-lang.js +465 -0
  185. package/tx/cs/cs-loinc.js +1485 -0
  186. package/tx/cs/cs-mimetypes.js +238 -0
  187. package/tx/cs/cs-ndc.js +704 -0
  188. package/tx/cs/cs-omop.js +1025 -0
  189. package/tx/cs/cs-provider-api.js +43 -0
  190. package/tx/cs/cs-provider-list.js +37 -0
  191. package/tx/cs/cs-rxnorm.js +808 -0
  192. package/tx/cs/cs-snomed.js +1102 -0
  193. package/tx/cs/cs-ucum.js +514 -0
  194. package/tx/cs/cs-unii.js +271 -0
  195. package/tx/cs/cs-uri.js +218 -0
  196. package/tx/cs/cs-usstates.js +305 -0
  197. package/tx/dev.fhir.org.yml +14 -0
  198. package/tx/fixtures/test-cases-setup.json +18 -0
  199. package/tx/fixtures/test-cases.yml +16 -0
  200. package/tx/html/codesystem-operations.liquid +25 -0
  201. package/tx/html/home-metrics.liquid +247 -0
  202. package/tx/html/operations-form.liquid +148 -0
  203. package/tx/html/search-form.liquid +62 -0
  204. package/tx/html/tx-template.html +133 -0
  205. package/tx/html/valueset-operations.liquid +54 -0
  206. package/tx/importers/atc-to-fhir.js +316 -0
  207. package/tx/importers/import-loinc.module.js +1536 -0
  208. package/tx/importers/import-ndc.module.js +1088 -0
  209. package/tx/importers/import-rxnorm.module.js +898 -0
  210. package/tx/importers/import-sct.module.js +2457 -0
  211. package/tx/importers/import-unii.module.js +601 -0
  212. package/tx/importers/readme.md +453 -0
  213. package/tx/importers/subset-loinc.module.js +1081 -0
  214. package/tx/importers/subset-rxnorm.module.js +938 -0
  215. package/tx/importers/tx-import-base.js +351 -0
  216. package/tx/importers/tx-import-settings.js +310 -0
  217. package/tx/importers/tx-import.js +357 -0
  218. package/tx/library/canonical-resource.js +88 -0
  219. package/tx/library/capabilitystatement.js +292 -0
  220. package/tx/library/codesystem.js +774 -0
  221. package/tx/library/conceptmap.js +568 -0
  222. package/tx/library/designations.js +932 -0
  223. package/tx/library/errors.js +77 -0
  224. package/tx/library/extensions.js +117 -0
  225. package/tx/library/namingsystem.js +322 -0
  226. package/tx/library/operation-outcome.js +127 -0
  227. package/tx/library/parameters.js +105 -0
  228. package/tx/library/renderer.js +1559 -0
  229. package/tx/library/terminologycapabilities.js +418 -0
  230. package/tx/library/ucum-parsers.js +1029 -0
  231. package/tx/library/ucum-service.js +370 -0
  232. package/tx/library/ucum-types.js +1099 -0
  233. package/tx/library/valueset.js +543 -0
  234. package/tx/library.js +676 -0
  235. package/tx/ocl/cm-ocl.js +106 -0
  236. package/tx/ocl/cs-ocl.js +39 -0
  237. package/tx/ocl/vs-ocl.js +105 -0
  238. package/tx/operation-context.js +568 -0
  239. package/tx/params.js +613 -0
  240. package/tx/provider.js +403 -0
  241. package/tx/sct/ecl.js +1560 -0
  242. package/tx/sct/expressions.js +2077 -0
  243. package/tx/sct/structures.js +1396 -0
  244. package/tx/tx-html.js +1063 -0
  245. package/tx/tx.fhir.org.yml +39 -0
  246. package/tx/tx.js +927 -0
  247. package/tx/vs/vs-api.js +112 -0
  248. package/tx/vs/vs-database.js +786 -0
  249. package/tx/vs/vs-package.js +358 -0
  250. package/tx/vs/vs-vsac.js +366 -0
  251. package/tx/workers/batch-validate.js +129 -0
  252. package/tx/workers/batch.js +361 -0
  253. package/tx/workers/closure.js +32 -0
  254. package/tx/workers/expand.js +1845 -0
  255. package/tx/workers/lookup.js +407 -0
  256. package/tx/workers/metadata.js +467 -0
  257. package/tx/workers/operations.js +34 -0
  258. package/tx/workers/read.js +164 -0
  259. package/tx/workers/search.js +384 -0
  260. package/tx/workers/subsumes.js +334 -0
  261. package/tx/workers/translate.js +492 -0
  262. package/tx/workers/validate.js +2504 -0
  263. package/tx/workers/worker.js +904 -0
  264. package/tx/xml/capabilitystatement-xml.js +63 -0
  265. package/tx/xml/codesystem-xml.js +62 -0
  266. package/tx/xml/conceptmap-xml.js +65 -0
  267. package/tx/xml/namingsystem-xml.js +65 -0
  268. package/tx/xml/operationoutcome-xml.js +127 -0
  269. package/tx/xml/parameters-xml.js +312 -0
  270. package/tx/xml/terminologycapabilities-xml.js +64 -0
  271. package/tx/xml/valueset-xml.js +64 -0
  272. package/tx/xml/xml-base.js +603 -0
  273. package/vcl/vcl-parser.js +1098 -0
  274. package/vcl/vcl.js +253 -0
  275. package/windows-install.js +19 -0
  276. package/xig/xig-template.html +124 -0
  277. package/xig/xig.js +3049 -0
@@ -0,0 +1,316 @@
1
+ const fs = require('fs');
2
+ const { XMLParser } = require('fast-xml-parser');
3
+
4
+ // Configuration
5
+ const ATC_FILE = process.argv[2] || '2025_ATC.xml';
6
+ const DDD_FILE = process.argv[3] || '2025_ATC_ddd.xml';
7
+ const OUTPUT_FILE = process.argv[4] || 'atc-codesystem.json';
8
+
9
+ const PROPERTY_GROUP_EXT_URL = 'http://hl7.org/fhir/property.group';
10
+
11
+ // Parse XML files
12
+ function parseXML(filePath) {
13
+ const xml = fs.readFileSync(filePath, 'utf-8');
14
+ const parser = new XMLParser({
15
+ ignoreAttributes: false,
16
+ attributeNamePrefix: '',
17
+ allowBooleanAttributes: true
18
+ });
19
+ return parser.parse(xml);
20
+ }
21
+
22
+ // Extract rows from parsed XML
23
+ function extractRows(parsed) {
24
+ const data = parsed.xml['rs:data'];
25
+ let rows = data['z:row'];
26
+ if (!Array.isArray(rows)) {
27
+ rows = [rows];
28
+ }
29
+ return rows;
30
+ }
31
+
32
+ // Get parent code for hierarchy
33
+ function getParentCode(code) {
34
+ const len = code.trim().length;
35
+ switch (len) {
36
+ case 1: return null; // No parent
37
+ case 3: return code.substring(0, 1); // A01 -> A
38
+ case 4: return code.substring(0, 3); // A01A -> A01
39
+ case 5: return code.substring(0, 4); // A01AA -> A01A
40
+ case 7: return code.substring(0, 5); // A01AA01 -> A01AA
41
+ default: return null;
42
+ }
43
+ }
44
+
45
+ // Build DDD lookup map (ATCCode -> array of DDD entries)
46
+ function buildDDDMap(dddRows) {
47
+ const map = new Map();
48
+
49
+ for (const row of dddRows) {
50
+ const code = row.ATCCode?.trim();
51
+ if (!code) continue;
52
+
53
+ const entry = {
54
+ ddd: row.DDD,
55
+ unit: row.UnitType,
56
+ admRoute: row.AdmCode,
57
+ comment: row.DDDComment
58
+ };
59
+
60
+ if (!map.has(code)) {
61
+ map.set(code, []);
62
+ }
63
+ map.get(code).push(entry);
64
+ }
65
+
66
+ return map;
67
+ }
68
+
69
+ // Create property with optional group extension
70
+ function createProperty(code, value, groupId = null, valueType = 'string') {
71
+ const prop = {
72
+ code: code
73
+ };
74
+
75
+ if (valueType === 'code') {
76
+ prop.valueCode = value;
77
+ } else {
78
+ prop.valueString = value;
79
+ }
80
+
81
+ if (groupId !== null) {
82
+ prop.extension = [{
83
+ url: PROPERTY_GROUP_EXT_URL,
84
+ valueCode: String(groupId)
85
+ }];
86
+ }
87
+
88
+ return prop;
89
+ }
90
+
91
+ // Build concept from ATC row with DDD data
92
+ function buildConcept(atcRow, dddMap) {
93
+ const code = atcRow.ATCCode?.trim();
94
+ const name = atcRow.Name?.trim();
95
+ const comment = atcRow.Comment?.trim();
96
+
97
+ if (!code) return null;
98
+
99
+ const concept = {
100
+ code: code,
101
+ display: name
102
+ };
103
+
104
+ const properties = [];
105
+
106
+ // Add comment property (non-grouped) if present
107
+ if (comment) {
108
+ properties.push(createProperty('comment', comment));
109
+ }
110
+
111
+ // Add DDD properties with grouping
112
+ const dddEntries = dddMap.get(code) || [];
113
+ let groupId = 1;
114
+
115
+ for (const entry of dddEntries) {
116
+ // Only create group if there's actual DDD data
117
+ const hasDDD = entry.ddd !== undefined && entry.ddd !== null && entry.ddd;
118
+ const hasUnit = entry.unit !== undefined && entry.unit !== null && entry.unit;
119
+ const hasRoute = entry.admRoute !== undefined && entry.admRoute !== null && entry.admRoute;
120
+ const hasComment = entry.comment !== undefined && entry.comment !== null && entry.comment;
121
+
122
+ // Skip if no meaningful data
123
+ if (!hasDDD && !hasUnit && !hasRoute && !hasComment) continue;
124
+
125
+ if (hasDDD) {
126
+ properties.push(createProperty('dddValue', entry.ddd, groupId));
127
+ }
128
+ if (hasUnit) {
129
+ properties.push(createProperty('dddUnit', entry.unit, groupId));
130
+ }
131
+ if (hasRoute) {
132
+ properties.push(createProperty('dddAdmRoute', entry.admRoute, groupId, 'code'));
133
+ }
134
+ if (hasComment) {
135
+ properties.push(createProperty('dddComment', entry.comment, groupId));
136
+ }
137
+
138
+ groupId++;
139
+ }
140
+
141
+ if (properties.length > 0) {
142
+ concept.property = properties;
143
+ }
144
+
145
+ return concept;
146
+ }
147
+
148
+ // Main conversion function
149
+ function convertATCtoFHIR(atcFile, dddFile) {
150
+ console.log(`Parsing ${atcFile}...`);
151
+ const atcParsed = parseXML(atcFile);
152
+ const atcRows = extractRows(atcParsed);
153
+ console.log(` Found ${atcRows.length} ATC codes`);
154
+
155
+ console.log(`Parsing ${dddFile}...`);
156
+ const dddParsed = parseXML(dddFile);
157
+ const dddRows = extractRows(dddParsed);
158
+ console.log(` Found ${dddRows.length} DDD entries`);
159
+
160
+ console.log('Building DDD lookup map...');
161
+ const dddMap = buildDDDMap(dddRows);
162
+ console.log(` ${dddMap.size} codes have DDD data`);
163
+
164
+ console.log('Building FHIR CodeSystem...');
165
+
166
+ const codeSystem = {
167
+ resourceType: 'CodeSystem',
168
+ id: 'atc',
169
+ url: 'http://www.whocc.no/atc',
170
+ identifier: [
171
+ {
172
+ use: 'official',
173
+ system: 'urn:ietf:rfc:3986',
174
+ value: 'urn:oid:2.16.840.1.113883.6.73'
175
+ }
176
+ ],
177
+ version: '2025',
178
+ name: 'ATC_classification_system',
179
+ title: 'ATC classification system',
180
+ status: 'active',
181
+ experimental: false,
182
+ date: '2025-01-07',
183
+ publisher: 'WHO Collaborating Centre for Drug Statistics Methodology - Norwegian Institute of Public Health',
184
+ contact: [
185
+ {
186
+ name: 'WHO Collaborating Centre for Drug Statistics Methodology - Norwegian Institute of Public Health',
187
+ telecom: [
188
+ {
189
+ system: 'url',
190
+ value: 'http://www.whocc.no'
191
+ }
192
+ ]
193
+ }
194
+ ],
195
+ description: 'Anatomical Therapeutic Chemical (ATC) classification system',
196
+ copyright: 'WHO Collaborating Centre for Drug Statistics Methodology, Oslo, Norway. Use of all or parts of the material requires reference to the WHO Collaborating Centre for Drug Statistics Methodology. Copying and distribution for commercial purposes is not allowed. Changing or manipulating the material is not allowed.',
197
+ caseSensitive: true,
198
+ hierarchyMeaning: 'is-a',
199
+ content: 'complete',
200
+ count: 0, // Will be set after building concepts
201
+ property: [
202
+ {
203
+ code: 'comment',
204
+ description: 'General comment or note about the ATC code',
205
+ type: 'string'
206
+ },
207
+ {
208
+ code: 'dddValue',
209
+ description: 'Defined Daily Dose numeric value',
210
+ type: 'string'
211
+ },
212
+ {
213
+ code: 'dddUnit',
214
+ description: 'Unit of measurement for the DDD (mg, g, mcg, ml, etc.)',
215
+ type: 'string'
216
+ },
217
+ {
218
+ code: 'dddAdmRoute',
219
+ description: 'Administration route code (O=Oral, P=Parenteral, R=Rectal, N=Nasal, TD=Transdermal, V=Vaginal, etc.)',
220
+ type: 'code'
221
+ },
222
+ {
223
+ code: 'dddComment',
224
+ description: 'Qualifier or comment for the DDD entry (e.g., formulation type)',
225
+ type: 'string'
226
+ }
227
+ ],
228
+ concept: []
229
+ };
230
+
231
+ // Build flat concepts first
232
+ const conceptMap = new Map();
233
+ for (const row of atcRows) {
234
+ const concept = buildConcept(row, dddMap);
235
+ if (concept) {
236
+ conceptMap.set(concept.code, concept);
237
+ }
238
+ }
239
+
240
+ // Build nested hierarchy
241
+ const rootConcepts = [];
242
+
243
+ for (const [code, concept] of conceptMap) {
244
+ const parentCode = getParentCode(code);
245
+
246
+ if (parentCode === null) {
247
+ // Top-level concept
248
+ rootConcepts.push(concept);
249
+ } else {
250
+ // Find parent and add as child
251
+ const parent = conceptMap.get(parentCode);
252
+ if (parent) {
253
+ if (!parent.concept) {
254
+ parent.concept = [];
255
+ }
256
+ parent.concept.push(concept);
257
+ } else {
258
+ // Parent not found, add to root (shouldn't happen with valid data)
259
+ console.warn(`Warning: Parent ${parentCode} not found for ${code}`);
260
+ rootConcepts.push(concept);
261
+ }
262
+ }
263
+ }
264
+
265
+ codeSystem.concept = rootConcepts;
266
+ codeSystem.count = conceptMap.size;
267
+
268
+ console.log(` Generated ${conceptMap.size} concepts in nested hierarchy`);
269
+
270
+ return codeSystem;
271
+ }
272
+
273
+ // Run conversion
274
+ try {
275
+ const codeSystem = convertATCtoFHIR(ATC_FILE, DDD_FILE);
276
+
277
+ console.log(`Writing to ${OUTPUT_FILE}...`);
278
+ fs.writeFileSync(OUTPUT_FILE, JSON.stringify(codeSystem, null, 2));
279
+ console.log('Done!');
280
+
281
+ // Print some stats
282
+ function countConcepts(concepts) {
283
+ let count = 0;
284
+ for (const c of concepts) {
285
+ count++;
286
+ if (c.concept) {
287
+ count += countConcepts(c.concept);
288
+ }
289
+ }
290
+ return count;
291
+ }
292
+
293
+ function countWithDDD(concepts) {
294
+ let count = 0;
295
+ for (const c of concepts) {
296
+ if (c.property?.some(p => p.code === 'dddValue')) {
297
+ count++;
298
+ }
299
+ if (c.concept) {
300
+ count += countWithDDD(c.concept);
301
+ }
302
+ }
303
+ return count;
304
+ }
305
+
306
+ const totalConcepts = countConcepts(codeSystem.concept);
307
+ const withDDD = countWithDDD(codeSystem.concept);
308
+ console.log(`\nStatistics:`);
309
+ console.log(` Total concepts: ${totalConcepts}`);
310
+ console.log(` Top-level concepts: ${codeSystem.concept.length}`);
311
+ console.log(` Concepts with DDD data: ${withDDD}`);
312
+
313
+ } catch (error) {
314
+ console.error('Error:', error.message);
315
+ process.exit(1);
316
+ }