cry-vetzdravila 1.0.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 (84) hide show
  1. package/.claude/settings.local.json +17 -0
  2. package/CLAUDE.md +82 -0
  3. package/LICENSE.md +16 -0
  4. package/README.md +376 -0
  5. package/a.txt +1643 -0
  6. package/bun.lock +197 -0
  7. package/dist/atcvet/opisATCvetKode.d.ts +23 -0
  8. package/dist/atcvet/podatkiATCvetKode.d.ts +20 -0
  9. package/dist/atcvet/pomeniNivojevATCvet.d.ts +1 -0
  10. package/dist/atcvet/types/AtcvetFile.d.ts +19 -0
  11. package/dist/generated/AtcVet.d.ts +9 -0
  12. package/dist/generated/RegisterZdravil.d.ts +8 -0
  13. package/dist/generated/seznamZdravil.d.ts +8 -0
  14. package/dist/index.d.ts +244 -0
  15. package/dist/index.js +141786 -0
  16. package/dist/register/MIN_OCENA_PODOBNOSTI.d.ts +2 -0
  17. package/dist/register/helper/normalizirajNaziv.d.ts +176 -0
  18. package/dist/register/helper/oblikujRezultatIskanja.d.ts +8 -0
  19. package/dist/register/helper/oceniPodobnost.d.ts +37 -0
  20. package/dist/register/helper/poisciZivalskoVrsto.d.ts +33 -0
  21. package/dist/register/helper/razcleniNaziv.d.ts +2 -0
  22. package/dist/register/podobnaZdravilaPoATC.d.ts +16 -0
  23. package/dist/register/podobnaZdravilaPoUcinkovinah.d.ts +18 -0
  24. package/dist/register/registerZdravil.d.ts +9 -0
  25. package/dist/register/types/KarencaZdravila.d.ts +24 -0
  26. package/dist/register/types/PotUporabeZdravila.d.ts +21 -0
  27. package/dist/register/types/RazclembaZdravila.d.ts +9 -0
  28. package/dist/register/types/RegisterZdravil.d.ts +2 -0
  29. package/dist/register/types/UcinkovinaZdravila.d.ts +20 -0
  30. package/dist/register/types/Zdravilo.d.ts +65 -0
  31. package/dist/register/types/ZdraviloZUtemeljitvijo.d.ts +9 -0
  32. package/dist/register/types/ZivalskeVrste.d.ts +158 -0
  33. package/dist/register/uganiZdravilo.d.ts +43 -0
  34. package/dist/register/zdravilaZaAtcVetKodo.d.ts +8 -0
  35. package/dist/register/zdraviloJeVakcinaZa.d.ts +18 -0
  36. package/docs/vakcine.md +195 -0
  37. package/package.json +39 -0
  38. package/src/atcvet/CLAUDE.md +18 -0
  39. package/src/atcvet/downloadLatestAtcvetPdf.ts +107 -0
  40. package/src/atcvet/opisATCvetKode.ts +116 -0
  41. package/src/atcvet/parseAtcvetPdf.ts +215 -0
  42. package/src/atcvet/podatkiATCvetKode.ts +34 -0
  43. package/src/atcvet/pomeniNivojevATCvet.ts +8 -0
  44. package/src/atcvet/types/AtcvetFile.ts +22 -0
  45. package/src/generate.ts +111 -0
  46. package/src/generated/AtcVet.ts +56704 -0
  47. package/src/generated/seznamZdravil.ts +44833 -0
  48. package/src/importParseAndBuildAll.ts +97 -0
  49. package/src/index.ts +289 -0
  50. package/src/interactive.ts +428 -0
  51. package/src/register/CLAUDE.md +230 -0
  52. package/src/register/MIN_OCENA_PODOBNOSTI.ts +3 -0
  53. package/src/register/downloadRegister.ts +148 -0
  54. package/src/register/helper/analizaVakcin.ts +90 -0
  55. package/src/register/helper/checkVrste.ts +72 -0
  56. package/src/register/helper/hashString.ts +27 -0
  57. package/src/register/helper/normalizirajNaziv.ts +493 -0
  58. package/src/register/helper/oblikujRezultatIskanja.ts +15 -0
  59. package/src/register/helper/oceniPodobnost.ts +194 -0
  60. package/src/register/helper/poisciZivalskoVrsto.ts +100 -0
  61. package/src/register/helper/razcleniNaziv.ts +105 -0
  62. package/src/register/helper/testNormalizacije.ts +89 -0
  63. package/src/register/helper/testPodobnosti.ts +238 -0
  64. package/src/register/helper/testVakcin.ts +103 -0
  65. package/src/register/parseRegister.ts +464 -0
  66. package/src/register/podobnaZdravilaPoATC.ts +71 -0
  67. package/src/register/podobnaZdravilaPoUcinkovinah.ts +136 -0
  68. package/src/register/registerZdravil.ts +22 -0
  69. package/src/register/stats.ts +114 -0
  70. package/src/register/types/KarencaZdravila.ts +26 -0
  71. package/src/register/types/PotUporabeZdravila.ts +21 -0
  72. package/src/register/types/RazclembaZdravila.ts +10 -0
  73. package/src/register/types/RegisterRaw.ts +23 -0
  74. package/src/register/types/RegisterZdravil.ts +3 -0
  75. package/src/register/types/UcinkovinaZdravila.ts +21 -0
  76. package/src/register/types/Zdravilo.ts +84 -0
  77. package/src/register/types/ZdraviloZUtemeljitvijo.ts +11 -0
  78. package/src/register/types/ZivalskeVrste.ts +7 -0
  79. package/src/register/uganiZdravilo.ts +142 -0
  80. package/src/register/zdravilaZaAtcVetKodo.ts +28 -0
  81. package/src/register/zdraviloJeVakcinaZa.ts +202 -0
  82. package/src/test/testPodobnosti.test.ts +126 -0
  83. package/src/test/zdravila.json +38693 -0
  84. package/tsconfig.json +18 -0
@@ -0,0 +1,464 @@
1
+ /**
2
+ * Parsanje xlsx datoteke registra veterinarskih zdravil
3
+ */
4
+
5
+ import * as XLSX from "xlsx";
6
+ import { Register } from "./types/RegisterRaw";
7
+ import { Zdravilo, XLSX_COLUMNS } from "./types/Zdravilo";
8
+ import { UcinkovinaZdravila } from "./types/UcinkovinaZdravila";
9
+ import { PotUporabeZdravila } from "./types/PotUporabeZdravila";
10
+ import { KarencaZdravila } from "./types/KarencaZdravila";
11
+ import { ZivalskeVrste } from "./types/ZivalskeVrste";
12
+ import { MozneKarenceZa } from "cry-klikvet-logic";
13
+ import { findNadrejenaVrsta, normalizeVrsta } from "./helper/poisciZivalskoVrsto";
14
+ import { zdraviloJeVakcinaZa } from "./zdraviloJeVakcinaZa";
15
+ import { hashString } from "./helper/hashString";
16
+
17
+ type XlsxRow = (string | number | undefined)[];
18
+
19
+ /**
20
+ * Parsanje niza z ločilom "-N\" kjer je N številka
21
+ * Primer: " -1\vrednost1 -2\vrednost2" => ["vrednost1", "vrednost2"]
22
+ */
23
+ function parseNumberedList(value: string | undefined): string[] {
24
+ if (!value || typeof value !== "string") return [];
25
+
26
+ const items: string[] = [];
27
+ // Poišči vse vrednosti med -N\ in naslednjim -N\ ali koncem niza
28
+ const regex = /-\d+\\([^-]*?)(?=-\d+\\|$)/g;
29
+ let match;
30
+
31
+ while ((match = regex.exec(value)) !== null) {
32
+ const item = match[1].trim();
33
+ if (item && item !== "null") {
34
+ items.push(item);
35
+ }
36
+ }
37
+
38
+ return items;
39
+ }
40
+
41
+ /**
42
+ * Parsanje učinkovin iz stolpcev xlsx
43
+ */
44
+ function parseUcinkovine(
45
+ ucinkovineStr: string | undefined,
46
+ kolicineStr: string | undefined,
47
+ dodatneInfoStr: string | undefined
48
+ ): UcinkovinaZdravila[] {
49
+ const ucinkovine: UcinkovinaZdravila[] = [];
50
+
51
+ if (!ucinkovineStr) return ucinkovine;
52
+
53
+ // Parsiraj vse tri stolpce
54
+ const ucinkList = parseNumberedList(ucinkovineStr);
55
+ const kolList = parseNumberedList(kolicineStr);
56
+ const dodatneList = parseNumberedList(dodatneInfoStr);
57
+
58
+ for (let i = 0; i < ucinkList.length; i++) {
59
+ const ucinkovina = ucinkList[i];
60
+ const kolicinaRaw = kolList[i] || "";
61
+ const dodatneInfo = dodatneList[i];
62
+
63
+ // Parsiraj količino in enoto (npr. "200,0 mg" => 200.0, "mg")
64
+ const kolMatch = kolicinaRaw.match(/^([\d,\.]+)\s*(.*)$/);
65
+ let kolicina: number | undefined;
66
+ let enota = "";
67
+
68
+ if (kolMatch) {
69
+ // Zamenjaj vejico s piko za decimalno število
70
+ kolicina = parseFloat(kolMatch[1].replace(",", "."));
71
+ enota = kolMatch[2].trim();
72
+ }
73
+
74
+ ucinkovine.push({
75
+ ucinkovina,
76
+ kolicina,
77
+ enota,
78
+ dodatneInformacije: dodatneInfo && dodatneInfo !== "null" ? dodatneInfo : undefined,
79
+ });
80
+ }
81
+
82
+ return ucinkovine;
83
+ }
84
+
85
+ /**
86
+ * Slovar za preslikavo ključnih besed v kodo poti uporabe
87
+ */
88
+ const POTI_KODE_MAP: Record<string, string> = {
89
+ subkutana: "SC",
90
+ intramuskularna: "IM",
91
+ intravenska: "IV",
92
+ peroralna: "PO",
93
+ dermalna: "TOP",
94
+ kožna: "TOP",
95
+ "v vodo za pitje": "PO",
96
+ intramamilarna: "IMM",
97
+ intramamarna: "IMM",
98
+ inhalacijska: "INH",
99
+ okularna: "OC",
100
+ "v oko": "OC",
101
+ rektalna: "REC",
102
+ intrauterina: "IU",
103
+ intranazalna: "IN",
104
+ "v nos": "IN",
105
+ lokalna: "LOC",
106
+ transdermalna: "TD",
107
+ };
108
+
109
+ /**
110
+ * Izlušči kode iz opisa poti uporabe
111
+ * Primer: "Intramuskularna/subkutana uporaba" => ["IM", "SC"]
112
+ */
113
+ function extractKodeFromOpis(opis: string): string[] {
114
+ const kode: string[] = [];
115
+ const opisLower = opis.toLowerCase();
116
+
117
+ for (const [keyword, koda] of Object.entries(POTI_KODE_MAP)) {
118
+ if (opisLower.includes(keyword)) {
119
+ if (!kode.includes(koda)) {
120
+ kode.push(koda);
121
+ }
122
+ }
123
+ }
124
+
125
+ return kode;
126
+ }
127
+
128
+ /**
129
+ * Parsanje poti uporabe
130
+ * Primer: "-1082\Intramuskularna/subkutana uporaba" => { sifra: 1082, opis: "Intramuskularna/subkutana uporaba", koda: ["IM", "SC"] }
131
+ */
132
+ function parsePotiUporabe(value: string | undefined): PotUporabeZdravila[] {
133
+ if (!value || typeof value !== "string") return [];
134
+
135
+ const poti: PotUporabeZdravila[] = [];
136
+ // Format: -SIFRA\Opis
137
+ const regex = /-(\d+)\\([^-]*?)(?=-\d+\\|$)/g;
138
+ let match;
139
+
140
+ while ((match = regex.exec(value)) !== null) {
141
+ const sifra = parseInt(match[1]);
142
+ const opis = match[2].trim();
143
+
144
+ poti.push({
145
+ sifra,
146
+ opis,
147
+ koda: extractKodeFromOpis(opis),
148
+ });
149
+ }
150
+
151
+ return poti;
152
+ }
153
+
154
+
155
+ /**
156
+ * Parsanje ciljnih živalskih vrst
157
+ * Primer: " -1\ #Govedo -2\ #Prašiči" => ["govedo", "prašiči"]
158
+ */
159
+ function parseCiljneVrste(value: string | undefined): string[] {
160
+ const vrste = parseNumberedList(value);
161
+ return [...new Set(vrste.map(normalizeVrsta))]; // Unikatne vrednosti
162
+ }
163
+
164
+ /**
165
+ * Parsanje ene karence
166
+ * Format: "507-piščanci (matične jate)\60-meso in organi\28,0\dni\null"
167
+ * ali: "-#Govedo\60-meso in organi\89,0\dni\null"
168
+ */
169
+ function parseKarenca(value: string | undefined): KarencaZdravila | null {
170
+ if (!value || typeof value !== "string" || value.trim() === "") return null;
171
+
172
+ const parts = value.split("\\");
173
+ if (parts.length < 3) return null;
174
+
175
+ // Prvi del: vrsta (lahko s šifro, npr. "507-piščanci" ali "-#Govedo")
176
+ const vrstaPart = parts[0];
177
+ let vrsta = vrstaPart;
178
+
179
+ // Odstrani morebitno šifro na začetku (npr. "507-" ali "-#")
180
+ const vrstaMatch = vrstaPart.match(/^-?#?(\d*-)?(.+)$/);
181
+ if (vrstaMatch) {
182
+ vrsta = vrstaMatch[2];
183
+ }
184
+ vrsta = normalizeVrsta(vrsta);
185
+
186
+ // Drugi del: karenca za (npr. "60-meso in organi" ali "30-mleko")
187
+ const karencaZaPart = parts[1];
188
+ const karencaMatch = karencaZaPart.match(/^(\d+)-(.+)$/);
189
+
190
+ if (!karencaMatch) return null;
191
+
192
+ const karencaZaKoda = karencaMatch[1];
193
+ const karencaZaOpis = karencaMatch[2];
194
+
195
+ // Tretji del: vrednost (npr. "28,0" ali "null")
196
+ const vrednostStr = parts[2];
197
+ if (vrednostStr === "null" || vrednostStr.trim() === "") return null;
198
+
199
+ const vrednost = parseFloat(vrednostStr.replace(",", "."));
200
+ if (isNaN(vrednost)) return null;
201
+
202
+ // Preslikaj karencaZa v ustrezen ključ
203
+ const karencaZaMap: Record<string, MozneKarenceZa> = {
204
+ "meso in organi": "meso",
205
+ meso: "meso",
206
+ mleko: "mleko",
207
+ jajca: "jajca",
208
+ med: "med",
209
+ };
210
+
211
+ const karencaZa = karencaZaMap[karencaZaOpis.toLowerCase()] || ("meso" as MozneKarenceZa);
212
+
213
+ // Poskusi najti vrsto v ZivalskeVrste
214
+ // ZivalskeVrste je objekt z objekti, ki imajo lastnost "vrsta" in "_match" regex
215
+ const vrstaKey = Object.keys(ZivalskeVrste).find((k) => {
216
+ const vrstaDef = ZivalskeVrste[k as keyof typeof ZivalskeVrste];
217
+ if (!vrstaDef) return false;
218
+
219
+ // Preveri po ključu
220
+ if (k.toLowerCase() === vrsta) return true;
221
+
222
+ // Preveri po regex _match če obstaja
223
+ if (vrstaDef._match && vrstaDef._match.test(vrsta)) return true;
224
+
225
+ // Preveri po lastnosti vrsta
226
+ if (typeof vrstaDef.vrsta === "string" && vrstaDef.vrsta.toLowerCase() === vrsta) return true;
227
+
228
+ return false;
229
+ });
230
+
231
+ return {
232
+ vrsta: (vrstaKey as keyof typeof ZivalskeVrste) || (vrsta as keyof typeof ZivalskeVrste),
233
+ karence: {
234
+ [karencaZa]: vrednost,
235
+ },
236
+ };
237
+ }
238
+
239
+
240
+ /**
241
+ * Združi karence za isto vrsto in dodaj nadrejene vrste
242
+ */
243
+ function mergeKarence(karence: (KarencaZdravila | null)[]): KarencaZdravila[] {
244
+ const merged = new Map<string, KarencaZdravila>();
245
+
246
+ for (const k of karence) {
247
+ if (!k) continue;
248
+
249
+ const key = `${k.vrsta}-${k.pot || ""}`;
250
+ const existing = merged.get(key);
251
+
252
+ if (existing) {
253
+ // Združi karence
254
+ existing.karence = { ...existing.karence, ...k.karence };
255
+ } else {
256
+ merged.set(key, { ...k });
257
+ }
258
+ }
259
+
260
+ // Dodaj nadrejene vrste
261
+ const result = Array.from(merged.values());
262
+ const toAdd: KarencaZdravila[] = [];
263
+
264
+ for (const karenca of result) {
265
+ const nadrejena = findNadrejenaVrsta(karenca.vrsta);
266
+ if (nadrejena) {
267
+ const nadrejenaKey = `${nadrejena}-${karenca.pot || ""}`;
268
+ // Dodaj nadrejeno samo če še ne obstaja
269
+ if (!merged.has(nadrejenaKey)) {
270
+ toAdd.push({
271
+ vrsta: nadrejena as keyof typeof ZivalskeVrste,
272
+ pot: karenca.pot,
273
+ karence: { ...karenca.karence },
274
+ });
275
+ merged.set(nadrejenaKey, toAdd[toAdd.length - 1]);
276
+ } else {
277
+ // Združi karence z obstoječo nadrejeno vrsto
278
+ const existing = merged.get(nadrejenaKey)!;
279
+ existing.karence = { ...existing.karence, ...karenca.karence };
280
+ }
281
+ }
282
+ }
283
+
284
+ return [...result, ...toAdd];
285
+ }
286
+
287
+ /**
288
+ * Parsanje pomožnih snovi
289
+ */
290
+ function parsePomocneSnovi(value: string | undefined): string[] {
291
+ if (!value || typeof value !== "string") return [];
292
+
293
+ // Format: -1\snov1-2\snov2
294
+ const snovi: string[] = [];
295
+ const regex = /-\d+\\([^-]*?)(?=-\d+\\|$)/g;
296
+ let match;
297
+
298
+ while ((match = regex.exec(value)) !== null) {
299
+ const snov = match[1].trim();
300
+ if (snov) {
301
+ snovi.push(snov);
302
+ }
303
+ }
304
+
305
+ return snovi;
306
+ }
307
+
308
+ /**
309
+ * Parsanje Excel datuma (serijsko število) v string YYYY-MM-DD
310
+ */
311
+ function parseExcelDate(value: number | string | undefined): string | null {
312
+ if (value === undefined || value === null) return null;
313
+
314
+ let date: Date | null = null;
315
+
316
+ if (typeof value === "number") {
317
+ // Excel serijski datum (dni od 1. januarja 1900)
318
+ const excelEpoch = new Date(1899, 11, 30); // 30. december 1899
319
+ date = new Date(excelEpoch.getTime() + value * 24 * 60 * 60 * 1000);
320
+ } else if (typeof value === "string") {
321
+ date = new Date(value);
322
+ if (isNaN(date.getTime())) return null;
323
+ }
324
+
325
+ if (!date) return null;
326
+
327
+ // Format YYYY-MM-DD
328
+ const year = date.getFullYear();
329
+ const month = String(date.getMonth() + 1).padStart(2, "0");
330
+ const day = String(date.getDate()).padStart(2, "0");
331
+ return `${year}-${month}-${day}`;
332
+ }
333
+
334
+ /**
335
+ * Parsanje ene vrstice xlsx v Zdravilo objekt
336
+ */
337
+ function parseRow(row: XlsxRow): Zdravilo {
338
+ const getString = (idx: number): string => {
339
+ const val = row[idx];
340
+ return typeof val === "string" ? val.trim() : String(val ?? "");
341
+ };
342
+
343
+ // Parsiraj karence (stolpci 16-54)
344
+ const karenceRaw: (KarencaZdravila | null)[] = [];
345
+ for (let i = XLSX_COLUMNS.KARENCA_START; i <= XLSX_COLUMNS.KARENCA_END; i++) {
346
+ karenceRaw.push(parseKarenca(getString(i)));
347
+ }
348
+
349
+ const zdravilo: Zdravilo = {
350
+ id: hashString(getString(XLSX_COLUMNS.IME_ZDRAVILA))!,
351
+ ime: getString(XLSX_COLUMNS.IME_ZDRAVILA),
352
+ imetnikDovoljenja: getString(XLSX_COLUMNS.IMETNIK_DOVOLJENJA),
353
+ sproscanjeZdravila: getString(XLSX_COLUMNS.SPROSCANJE_ZDRAVILA),
354
+ stevilkaDovoljenja: getString(XLSX_COLUMNS.STEVILKA_DZP),
355
+ datumVeljavnosti: parseExcelDate(row[XLSX_COLUMNS.DATUM_VELJAVNOSTI]),
356
+ rezimIzdaje: getString(XLSX_COLUMNS.REZIM_IZDAJE),
357
+ atcKoda: getString(XLSX_COLUMNS.ATC),
358
+ potiUporabe: parsePotiUporabe(getString(XLSX_COLUMNS.POTI_UPORABE)),
359
+ farmacevtskaOblika: getString(XLSX_COLUMNS.FARMACEVTSKA_OBLIKA),
360
+ pakiranja: getString(XLSX_COLUMNS.PAKIRANJA),
361
+ enotaKolicine: getString(XLSX_COLUMNS.ENOTA_KOLICINE),
362
+ ucinkovine: parseUcinkovine(
363
+ getString(XLSX_COLUMNS.UCINKOVINE),
364
+ getString(XLSX_COLUMNS.KOLICINE_UCINKOVIN),
365
+ getString(XLSX_COLUMNS.UCINKOVINE_DODATNE_INFO)
366
+ ),
367
+ pomocneSnovi: parsePomocneSnovi(getString(XLSX_COLUMNS.POMOCNE_SNOVI)),
368
+ ciljneVrste: parseCiljneVrste(getString(XLSX_COLUMNS.CILJNE_VRSTE)),
369
+ karence: mergeKarence(karenceRaw),
370
+ };
371
+
372
+ // Izpolni polje vakcinaZa če je zdravilo vakcina
373
+ const vakcinaZa = zdraviloJeVakcinaZa(zdravilo);
374
+ if (vakcinaZa) {
375
+ zdravilo.vakcinaZa = vakcinaZa;
376
+ }
377
+
378
+ return zdravilo;
379
+ }
380
+
381
+ /**
382
+ * Izlušči datum iz imena datoteke
383
+ * Primer: "20-11-2025.xlsx" => Date(2025, 10, 20)
384
+ */
385
+ function parseDateFromFilename(filepath: string): Date {
386
+ const filename = filepath.split("/").pop() || "";
387
+ const match = filename.match(/(\d{1,2})-(\d{1,2})-(\d{4})\.xlsx/);
388
+
389
+ if (match) {
390
+ const [, day, month, year] = match;
391
+ return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
392
+ }
393
+
394
+ return new Date();
395
+ }
396
+
397
+ /**
398
+ * Glavna funkcija za parsanje xlsx datoteke v Register objekt
399
+ */
400
+ export function parseRegister(filepath: string): Register {
401
+ console.log(`Parsiram datoteko: ${filepath}`);
402
+
403
+ const workbook = XLSX.readFile(filepath);
404
+ const sheetName = workbook.SheetNames[0];
405
+ const sheet = workbook.Sheets[sheetName];
406
+
407
+ const data = XLSX.utils.sheet_to_json<XlsxRow>(sheet, { header: 1 });
408
+
409
+ // Preskoči naslovno vrstico
410
+ const rows = data.slice(1);
411
+
412
+ console.log(`Najdenih ${rows.length} zdravil`);
413
+
414
+ const zdravila: Zdravilo[] = [];
415
+
416
+ for (const row of rows) {
417
+ // Preskoči prazne vrstice
418
+ if (!row[0]) continue;
419
+
420
+ try {
421
+ const zdravilo = parseRow(row);
422
+ zdravila.push(zdravilo);
423
+ } catch (error) {
424
+ console.error(`Napaka pri parsanju vrstice: ${row[0]}`, error);
425
+ }
426
+ }
427
+
428
+ console.log(`Uspešno parsanih ${zdravila.length} zdravil`);
429
+
430
+ return {
431
+ datumDatoteke: parseDateFromFilename(filepath),
432
+ datumParsanja: new Date(),
433
+ izvornaDateteka: filepath,
434
+ zdravila,
435
+ steviloZdravil: zdravila.length,
436
+ };
437
+ }
438
+
439
+ /**
440
+ * Shrani register v JSON datoteko
441
+ */
442
+ export async function saveRegisterToJson(register: Register, outputPath: string): Promise<void> {
443
+ const json = JSON.stringify(register, null, 2);
444
+ await Bun.write(outputPath, json);
445
+ console.log(`Register shranjen v: ${outputPath}`);
446
+ }
447
+
448
+ // Če se izvaja direktno
449
+ if (import.meta.main) {
450
+ const filepath = process.argv[2] || "./data/register.xlsx";
451
+
452
+ try {
453
+ const register = parseRegister(filepath);
454
+ console.log(`\nStatistika:`);
455
+ console.log(` Število zdravil: ${register.steviloZdravil}`);
456
+ console.log(` Datum datoteke: ${register.datumDatoteke.toLocaleDateString("sl-SI")}`);
457
+
458
+ // Shrani v JSON
459
+ await saveRegisterToJson(register, "./data/register.json");
460
+ } catch (error) {
461
+ console.error("Napaka:", error);
462
+ process.exit(1);
463
+ }
464
+ }
@@ -0,0 +1,71 @@
1
+ import { seznamZdravil } from "../generated/seznamZdravil";
2
+ import type { Zdravilo } from "./types/Zdravilo";
3
+ import { uganiZdraviloInUtemelji } from "./uganiZdravilo";
4
+
5
+ /**
6
+ * Dolžina ATCvet kode za vsak nivo
7
+ */
8
+ const NIVO_DOLZINA: Record<number, number> = {
9
+ 1: 2, // QA
10
+ 2: 4, // QA01
11
+ 3: 5, // QA01A
12
+ 4: 7, // QA01AA
13
+ 5: 8, // QA01AA01
14
+ };
15
+
16
+ /**
17
+ * Vrne seznam zdravil, ki so na istem ATC nivoju `nivo` kot dano zdravilo.
18
+ *
19
+ * Vrnjeni seznam ne vsebuje zdravila samega.
20
+ *
21
+ * @param zdravilo Zdravilo, za katerega iščemo podobna zdravila
22
+ * @param nivo ATCvet nivo za primerjavo (1-5), privzeto 5 (kemijska učinkovina)
23
+ * @returns Seznam zdravil z enako ATCvet kodo na danem nivoju
24
+ * @example
25
+ * ```typescript
26
+ * // Poišči vsa zdravila z enako kemijsko podskupino (nivo 4)
27
+ * const podobna = podobnaZdravilaPoATC(mojeZdravilo, 4);
28
+ * ```
29
+ */
30
+ export function podobnaZdravilaPoATC(zdravilo: Zdravilo, nivo = 5): Zdravilo[] {
31
+ const dolzina = NIVO_DOLZINA[nivo];
32
+ if (!dolzina) {
33
+ return [];
34
+ }
35
+
36
+ // Če zdravilo nima dovolj dolge ATC kode, vrni prazen seznam
37
+ if (!zdravilo.atcKoda || zdravilo.atcKoda.length < dolzina) {
38
+ return [];
39
+ }
40
+
41
+ const mojPrefiks = zdravilo.atcKoda.substring(0, dolzina);
42
+ const rezultat: Zdravilo[] = [];
43
+
44
+ for (const kandidat of seznamZdravil) {
45
+ // Preskoči isto zdravilo
46
+ if (kandidat.stevilkaDovoljenja === zdravilo.stevilkaDovoljenja) {
47
+ continue;
48
+ }
49
+
50
+ // Preveri ali ima kandidat enak prefiks
51
+ if (kandidat.atcKoda &&
52
+ kandidat.atcKoda.length >= dolzina &&
53
+ kandidat.atcKoda.substring(0, dolzina) === mojPrefiks) {
54
+ rezultat.push(kandidat);
55
+ }
56
+ }
57
+
58
+ // Uredi po podobnosti z vhodnim zdravilom
59
+ if (rezultat.length === 0) {
60
+ return [];
61
+ }
62
+
63
+ const isciMed = rezultat.map(z => z.id);
64
+ const urejeni = uganiZdraviloInUtemelji(zdravilo.ime, {
65
+ minOcena: 0,
66
+ isciMed,
67
+ limit: rezultat.length
68
+ });
69
+
70
+ return urejeni.map(r => r.zdravilo);
71
+ }
@@ -0,0 +1,136 @@
1
+ import { AtcVet } from "../generated/AtcVet";
2
+ import { seznamZdravil } from "../generated/seznamZdravil";
3
+ import type { Zdravilo } from "./types/Zdravilo";
4
+ import { uganiZdraviloInUtemelji } from "./uganiZdravilo";
5
+
6
+ /**
7
+ * Cache za ATCvet kode na nivoju 5 (kemijska učinkovina)
8
+ */
9
+ let atcvetNivo5Cache: Map<string, string> | null = null;
10
+
11
+ /**
12
+ * Pripravi cache ATCvet kod na nivoju 5
13
+ * Ključ je ATCvet koda, vrednost je slovenski opis (učinkovina)
14
+ */
15
+ function pripraviAtcvetCache(): Map<string, string> {
16
+ if (atcvetNivo5Cache !== null) {
17
+ return atcvetNivo5Cache;
18
+ }
19
+
20
+ atcvetNivo5Cache = new Map();
21
+ for (const row of AtcVet) {
22
+ if (row.level === 5) {
23
+ atcvetNivo5Cache.set(row.code, row.desc_si.toLowerCase());
24
+ }
25
+ }
26
+
27
+ return atcvetNivo5Cache;
28
+ }
29
+
30
+ /**
31
+ * Normalizira ime učinkovine za primerjavo
32
+ */
33
+ function normalizirajUcinkovino(ucinkovina: string): string {
34
+ return ucinkovina
35
+ .toLowerCase()
36
+ .trim()
37
+ .replace(/\s+/g, " ");
38
+ }
39
+
40
+ /**
41
+ * Vrne množico učinkovin za zdravilo iz registra
42
+ */
43
+ function ucinkovineIzRegistra(zdravilo: Zdravilo): Set<string> {
44
+ const ucinkovine = new Set<string>();
45
+ for (const u of zdravilo.ucinkovine) {
46
+ ucinkovine.add(normalizirajUcinkovino(u.ucinkovina));
47
+ }
48
+ return ucinkovine;
49
+ }
50
+
51
+ /**
52
+ * Vrne učinkovino iz ATCvet kode na nivoju 5 (kemijska učinkovina)
53
+ */
54
+ function ucinkovinaIzAtcvet(atcKoda: string): string | null {
55
+ const cache = pripraviAtcvetCache();
56
+ if (!atcKoda || atcKoda.length < 8) {
57
+ return null;
58
+ }
59
+ const koda = atcKoda.substring(0, 8);
60
+ const ucinkovina = cache.get(koda);
61
+ return ucinkovina ? normalizirajUcinkovino(ucinkovina) : null;
62
+ }
63
+
64
+ /**
65
+ * Vrne seznam zdravil, ki imajo vsaj eno učinkovino enako kot dano zdravilo.
66
+ *
67
+ * Podatek o učinkovinah pridobimo iz zdravila samega in mu dodamo učinkovine
68
+ * po nivoju 5 ATC tega zdravila. Enako storimo z vsemi zdravili Registra.
69
+ *
70
+ * Vrnjeni seznam ne vsebuje zdravila samega.
71
+ *
72
+ * @param zdravilo Zdravilo, za katerega iščemo zdravila s podobnimi učinkovinami.
73
+ * @returns Seznam zdravil z vsaj eno skupno učinkovino
74
+ * @example
75
+ * ```typescript
76
+ * // Poišči vsa zdravila z enako učinkovino
77
+ * const podobna = podobnaZdravilaPoUcinkovinah(mojeZdravilo);
78
+ * ```
79
+ */
80
+ export function podobnaZdravilaPoUcinkovinah(zdravilo: Zdravilo): Zdravilo[] {
81
+ // Zberi učinkovine iz vhodnega zdravila
82
+ const mojeUcinkovine = ucinkovineIzRegistra(zdravilo);
83
+
84
+ // Dodaj učinkovino iz ATCvet kode (nivo 5)
85
+ const atcvetUcinkovina = ucinkovinaIzAtcvet(zdravilo.atcKoda);
86
+ if (atcvetUcinkovina) {
87
+ mojeUcinkovine.add(atcvetUcinkovina);
88
+ }
89
+
90
+ if (mojeUcinkovine.size === 0) {
91
+ return [];
92
+ }
93
+
94
+ const rezultat: Zdravilo[] = [];
95
+
96
+ for (const kandidat of seznamZdravil) {
97
+ // Preskoči isto zdravilo
98
+ if (kandidat.stevilkaDovoljenja === zdravilo.stevilkaDovoljenja) {
99
+ continue;
100
+ }
101
+
102
+ // Zberi učinkovine kandidata
103
+ const kandidatUcinkovine = ucinkovineIzRegistra(kandidat);
104
+ const kandidatAtcvetUcinkovina = ucinkovinaIzAtcvet(kandidat.atcKoda);
105
+ if (kandidatAtcvetUcinkovina) {
106
+ kandidatUcinkovine.add(kandidatAtcvetUcinkovina);
107
+ }
108
+
109
+ // Preveri ali ima kandidat vsaj eno skupno učinkovino
110
+ let imaSkupno = false;
111
+ for (const ucinkovina of mojeUcinkovine) {
112
+ if (kandidatUcinkovine.has(ucinkovina)) {
113
+ imaSkupno = true;
114
+ break;
115
+ }
116
+ }
117
+
118
+ if (imaSkupno) {
119
+ rezultat.push(kandidat);
120
+ }
121
+ }
122
+
123
+ // Uredi po podobnosti z vhodnim zdravilom
124
+ if (rezultat.length === 0) {
125
+ return [];
126
+ }
127
+
128
+ const isciMed = rezultat.map(z => z.id);
129
+ const urejeni = uganiZdraviloInUtemelji(zdravilo.ime, {
130
+ minOcena: 0,
131
+ isciMed,
132
+ limit: rezultat.length
133
+ });
134
+
135
+ return urejeni.map(r => r.zdravilo);
136
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Seznam veterinarskih zdravil registriranih v Sloveniji
3
+ *
4
+ * Generirano: 2026-01-02T17:40:16.336Z
5
+ * Število zdravil: 879
6
+ */
7
+
8
+ import type { RegisterZdravil } from "..";
9
+ import type { Zdravilo } from "./types/Zdravilo";
10
+ import { seznamZdravil } from "../generated/seznamZdravil";
11
+
12
+ const registerZdravil: RegisterZdravil = new Map<number, Zdravilo>();
13
+ seznamZdravil.forEach(z => {
14
+ if (registerZdravil.has(z.id)) {
15
+ throw new Error(`registerZdravil: podvojen id zdravila ${z.id}`);
16
+ }
17
+ registerZdravil.set(z.id, z)
18
+ });
19
+
20
+ export { registerZdravil };
21
+
22
+