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.
- package/.claude/settings.local.json +17 -0
- package/CLAUDE.md +82 -0
- package/LICENSE.md +16 -0
- package/README.md +376 -0
- package/a.txt +1643 -0
- package/bun.lock +197 -0
- package/dist/atcvet/opisATCvetKode.d.ts +23 -0
- package/dist/atcvet/podatkiATCvetKode.d.ts +20 -0
- package/dist/atcvet/pomeniNivojevATCvet.d.ts +1 -0
- package/dist/atcvet/types/AtcvetFile.d.ts +19 -0
- package/dist/generated/AtcVet.d.ts +9 -0
- package/dist/generated/RegisterZdravil.d.ts +8 -0
- package/dist/generated/seznamZdravil.d.ts +8 -0
- package/dist/index.d.ts +244 -0
- package/dist/index.js +141786 -0
- package/dist/register/MIN_OCENA_PODOBNOSTI.d.ts +2 -0
- package/dist/register/helper/normalizirajNaziv.d.ts +176 -0
- package/dist/register/helper/oblikujRezultatIskanja.d.ts +8 -0
- package/dist/register/helper/oceniPodobnost.d.ts +37 -0
- package/dist/register/helper/poisciZivalskoVrsto.d.ts +33 -0
- package/dist/register/helper/razcleniNaziv.d.ts +2 -0
- package/dist/register/podobnaZdravilaPoATC.d.ts +16 -0
- package/dist/register/podobnaZdravilaPoUcinkovinah.d.ts +18 -0
- package/dist/register/registerZdravil.d.ts +9 -0
- package/dist/register/types/KarencaZdravila.d.ts +24 -0
- package/dist/register/types/PotUporabeZdravila.d.ts +21 -0
- package/dist/register/types/RazclembaZdravila.d.ts +9 -0
- package/dist/register/types/RegisterZdravil.d.ts +2 -0
- package/dist/register/types/UcinkovinaZdravila.d.ts +20 -0
- package/dist/register/types/Zdravilo.d.ts +65 -0
- package/dist/register/types/ZdraviloZUtemeljitvijo.d.ts +9 -0
- package/dist/register/types/ZivalskeVrste.d.ts +158 -0
- package/dist/register/uganiZdravilo.d.ts +43 -0
- package/dist/register/zdravilaZaAtcVetKodo.d.ts +8 -0
- package/dist/register/zdraviloJeVakcinaZa.d.ts +18 -0
- package/docs/vakcine.md +195 -0
- package/package.json +39 -0
- package/src/atcvet/CLAUDE.md +18 -0
- package/src/atcvet/downloadLatestAtcvetPdf.ts +107 -0
- package/src/atcvet/opisATCvetKode.ts +116 -0
- package/src/atcvet/parseAtcvetPdf.ts +215 -0
- package/src/atcvet/podatkiATCvetKode.ts +34 -0
- package/src/atcvet/pomeniNivojevATCvet.ts +8 -0
- package/src/atcvet/types/AtcvetFile.ts +22 -0
- package/src/generate.ts +111 -0
- package/src/generated/AtcVet.ts +56704 -0
- package/src/generated/seznamZdravil.ts +44833 -0
- package/src/importParseAndBuildAll.ts +97 -0
- package/src/index.ts +289 -0
- package/src/interactive.ts +428 -0
- package/src/register/CLAUDE.md +230 -0
- package/src/register/MIN_OCENA_PODOBNOSTI.ts +3 -0
- package/src/register/downloadRegister.ts +148 -0
- package/src/register/helper/analizaVakcin.ts +90 -0
- package/src/register/helper/checkVrste.ts +72 -0
- package/src/register/helper/hashString.ts +27 -0
- package/src/register/helper/normalizirajNaziv.ts +493 -0
- package/src/register/helper/oblikujRezultatIskanja.ts +15 -0
- package/src/register/helper/oceniPodobnost.ts +194 -0
- package/src/register/helper/poisciZivalskoVrsto.ts +100 -0
- package/src/register/helper/razcleniNaziv.ts +105 -0
- package/src/register/helper/testNormalizacije.ts +89 -0
- package/src/register/helper/testPodobnosti.ts +238 -0
- package/src/register/helper/testVakcin.ts +103 -0
- package/src/register/parseRegister.ts +464 -0
- package/src/register/podobnaZdravilaPoATC.ts +71 -0
- package/src/register/podobnaZdravilaPoUcinkovinah.ts +136 -0
- package/src/register/registerZdravil.ts +22 -0
- package/src/register/stats.ts +114 -0
- package/src/register/types/KarencaZdravila.ts +26 -0
- package/src/register/types/PotUporabeZdravila.ts +21 -0
- package/src/register/types/RazclembaZdravila.ts +10 -0
- package/src/register/types/RegisterRaw.ts +23 -0
- package/src/register/types/RegisterZdravil.ts +3 -0
- package/src/register/types/UcinkovinaZdravila.ts +21 -0
- package/src/register/types/Zdravilo.ts +84 -0
- package/src/register/types/ZdraviloZUtemeljitvijo.ts +11 -0
- package/src/register/types/ZivalskeVrste.ts +7 -0
- package/src/register/uganiZdravilo.ts +142 -0
- package/src/register/zdravilaZaAtcVetKodo.ts +28 -0
- package/src/register/zdraviloJeVakcinaZa.ts +202 -0
- package/src/test/testPodobnosti.test.ts +126 -0
- package/src/test/zdravila.json +38693 -0
- 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
|
+
|