@akredit/acik-mcp 0.1.0 → 0.5.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 (33) hide show
  1. package/README.md +94 -8
  2. package/dist/apps/acik-mcp/src/araclar.d.ts +2 -28
  3. package/dist/apps/acik-mcp/src/araclar.js +29 -114
  4. package/dist/apps/acik-mcp/src/bologna.d.ts +291 -0
  5. package/dist/apps/acik-mcp/src/bologna.js +1693 -0
  6. package/dist/apps/acik-mcp/src/genel-ayristir.d.ts +92 -0
  7. package/dist/apps/acik-mcp/src/genel-ayristir.js +489 -0
  8. package/dist/apps/acik-mcp/src/http.d.ts +31 -3
  9. package/dist/apps/acik-mcp/src/http.js +90 -17
  10. package/dist/apps/acik-mcp/src/index.d.ts +7 -0
  11. package/dist/apps/acik-mcp/src/index.js +7 -0
  12. package/dist/apps/acik-mcp/src/kaynak-dogrula.d.ts +94 -0
  13. package/dist/apps/acik-mcp/src/kaynak-dogrula.js +460 -0
  14. package/dist/apps/acik-mcp/src/kaynak.d.ts +72 -0
  15. package/dist/apps/acik-mcp/src/kaynak.js +155 -0
  16. package/dist/apps/acik-mcp/src/kisisel-veri.d.ts +16 -2
  17. package/dist/apps/acik-mcp/src/kisisel-veri.js +78 -15
  18. package/dist/apps/acik-mcp/src/not-tablosu.d.ts +39 -0
  19. package/dist/apps/acik-mcp/src/not-tablosu.js +268 -0
  20. package/dist/apps/acik-mcp/src/promptlar.d.ts +48 -0
  21. package/dist/apps/acik-mcp/src/promptlar.js +323 -0
  22. package/dist/apps/acik-mcp/src/sunucu.d.ts +12 -4
  23. package/dist/apps/acik-mcp/src/sunucu.js +108 -24
  24. package/dist/apps/acik-mcp/src/temel.d.ts +59 -0
  25. package/dist/apps/acik-mcp/src/temel.js +10 -0
  26. package/dist/apps/acik-mcp/src/yardim.d.ts +56 -0
  27. package/dist/apps/acik-mcp/src/yardim.js +381 -0
  28. package/dist/packages/baglayicilar/src/bologna-proliz/ayristir.d.ts +29 -0
  29. package/dist/packages/baglayicilar/src/bologna-proliz/ayristir.js +200 -0
  30. package/dist/packages/cekirdek/src/bologna/taslak.d.ts +19 -3
  31. package/dist/packages/cekirdek/src/bologna/taslak.js +70 -9
  32. package/dist/packages/cekirdek/src/turler.d.ts +8 -0
  33. package/package.json +5 -3
@@ -0,0 +1,92 @@
1
+ export type GenelAlan = "kod" | "ad" | "yariyil" | "tul" | "kredi" | "akts" | "dil" | "tur" | "onKosul" | "amac" | "icerik" | "yontem" | "notlar" | "ciktilar" | "haftalar" | "degerlendirme" | "isyuku" | "kaynaklar" | "katki" | "dersYapisi" | "kisi";
2
+ /** Bir hücre ya da satır başı bir etiket mi? Tam eşleşme (sondaki iki nokta ve parantez yok sayılır). */
3
+ export declare function etiketAlani(s: string): GenelAlan | null;
4
+ export type Satir = string[];
5
+ /** HTML ya da düz metni satır ve hücrelere çevirir. Tablo hücreleri sekmeyle ayrılır. */
6
+ export declare function satirlara(girdi: string): Satir[];
7
+ /**
8
+ * Sekmeli satırlardan ÖÇ-PÇ katkı matrisi: başlıkta en az iki P kodu, altında Ö satırları. Değerler "1"-"5" ya da
9
+ * boş. Başlıkta satır etiketi sütunu yoksa veri satırları bir sütun kaydırılır. Bulunamazsa boş nesne.
10
+ */
11
+ export declare function katkiMatrisi(satirlar: Satir[]): Record<string, Record<string, string>>;
12
+ /** Sayfada İngilizce alan var mı (İngilizce etiket ya da "(İngilizce)" alanı). Bologna araçlarının ingilizce değeri. */
13
+ export declare function ingilizceAlanVar(girdi: string): boolean;
14
+ /** Proliz ders sayfasının düz metni mi (Ctrl+A, Ctrl+C)? */
15
+ export declare function prolizMetniMi(girdi: string): boolean;
16
+ export interface Bolum {
17
+ alan: GenelAlan;
18
+ /** Sayfadaki etiket, olduğu gibi (ör. "Dersi Verenler"). */
19
+ etiket?: string;
20
+ /** Etiketle aynı satırdaki değer (hücreler). */
21
+ deger: string[];
22
+ /** Etiketten sonraki satırlar. */
23
+ satirlar: Satir[];
24
+ }
25
+ /**
26
+ * Satırları etiketli bölümlere ayırır. Başlık satırı (bütün hücreleri etiket olan) bir sonraki satırla
27
+ * eşlenir: "Ders Kodu | Ders Adı | AKTS" ve altında "KRG101 | ... | 5".
28
+ */
29
+ export declare function bolumlere(satirlar: Satir[]): {
30
+ bolumler: Bolum[];
31
+ sahipsiz: Satir[];
32
+ };
33
+ /**
34
+ * Düz metin ya da HTML'den görev bölümlerini (Dersi Verenler, Koordinatör, iletişim) yetkililer listesine, kaynaklar
35
+ * bölümünü künye listesine ayırır. Değerler olduğu gibi korunur; hiçbir şey atılmaz.
36
+ */
37
+ export declare function genelAyikla(girdi: string): {
38
+ metin: string;
39
+ kaynaklar: string[];
40
+ yetkililer: {
41
+ rol: string;
42
+ deger: string;
43
+ }[];
44
+ };
45
+ export interface GenelDers {
46
+ kod?: string;
47
+ ad?: string;
48
+ yariyil: number | null;
49
+ tul?: string;
50
+ kredi: number | null;
51
+ akts: number | null;
52
+ amac?: string;
53
+ icerik?: string;
54
+ yontem?: string;
55
+ notlar?: string;
56
+ ciktilar: {
57
+ kod: string;
58
+ metin: string;
59
+ }[];
60
+ degerlendirme: {
61
+ ad: string;
62
+ sayi: number | null;
63
+ katki: number | null;
64
+ }[];
65
+ isyuku: {
66
+ ad: string;
67
+ sayi: number | null;
68
+ sure: number | null;
69
+ toplam: number | null;
70
+ }[];
71
+ dersYapisi: {
72
+ alan: string;
73
+ yuzde: number | null;
74
+ }[];
75
+ haftalar: number;
76
+ haftaKonulari: string[];
77
+ katki: Record<string, Record<string, string>>;
78
+ }
79
+ export interface GenelSonuc {
80
+ ders: GenelDers;
81
+ bilgiler: {
82
+ dil?: string;
83
+ tur?: string;
84
+ onKosul?: string;
85
+ };
86
+ bulunan: string[];
87
+ bulunamayan: string[];
88
+ guven: "yüksek" | "orta" | "düşük";
89
+ }
90
+ export declare const GENEL_ALAN_ADLARI: Record<string, string>;
91
+ /** Genel ayrıştırıcı. `kaynakSayisi` bölümü sayısı ayrıca verilir (künyeler ayıklamada ayrılır). */
92
+ export declare function genelAyristir(girdi: string, kaynakSayisi?: number): GenelSonuc;
@@ -0,0 +1,489 @@
1
+ // Proliz dışı Bologna sayfaları için genel ayrıştırıcı. HTML ya da düz metin alır; Türkçe ve İngilizce alan
2
+ // etiketlerine (Amaç/Objective, İçerik/Content, Öğrenme Çıktıları/Learning Outcomes...) göre bölümlere ayırır.
3
+ // Ağ yoktur, model yoktur: yalnız etiket eşleme ve sayı okuma. Bulunamayan alan boş kalır, tahmin edilmez.
4
+ //
5
+ // Kişi satırları (Dersi Veren, Instructor, e-posta, telefon) ayrıştırmadan önce bellekte atılır. Kaynaklar
6
+ // bölümü künye olarak ayrı alınır (yazar adları yayımlanmış künyedir).
7
+ //
8
+ // Düz metin (dış inceleme 2026-11-05): tarayıcıda Ctrl+A, Ctrl+C ile kopyalanan sayfada tablolar sekmeyle ayrılır.
9
+ // ÖÇ-PÇ katkı matrisi ve ders yapısı bu sekmeli metinden de okunur; Proliz sayfasının düz metni tanınır.
10
+ import { metin as htmlMetni, sayi } from "../../../packages/baglayicilar/src/bologna-proliz/ayristir.js";
11
+ /** Etiketler küçük harfle, Türkçe karakterle yazılır. Uzun etiket önce denenir. */
12
+ const ETIKETLER = {
13
+ kod: ["ders kodu", "dersin kodu", "kodu", "kod", "course code", "code"],
14
+ ad: ["ders adı", "dersin adı", "adı", "course name", "course title", "name"],
15
+ yariyil: ["yarıyıl", "dönem", "semester"],
16
+ tul: ["t+u+l", "t+u+l saat", "teori+uygulama+laboratuvar", "t+p+l", "theory+practice+lab"],
17
+ kredi: ["kredi", "ulusal kredi", "yerel kredi", "credit", "credits", "local credit", "national credit"],
18
+ akts: ["akts", "akts kredisi", "ects", "ects credit", "ects credits"],
19
+ dil: ["dersin dili", "öğretim dili", "dil", "language", "language of instruction", "course language"],
20
+ tur: ["dersin türü", "ders türü", "türü", "tür", "course type", "type of course", "type"],
21
+ onKosul: ["ön koşul", "ön koşullar", "ön koşul dersleri", "önkoşul", "önkoşullar", "prerequisite", "prerequisites"],
22
+ amac: [
23
+ "dersin amacı",
24
+ "amaç",
25
+ "amacı",
26
+ "dersin amaçları",
27
+ "objective",
28
+ "objectives",
29
+ "course objective",
30
+ "course objectives",
31
+ "aim",
32
+ "aims",
33
+ ],
34
+ icerik: [
35
+ "dersin içeriği",
36
+ "içerik",
37
+ "içeriği",
38
+ "ders içeriği",
39
+ "content",
40
+ "contents",
41
+ "course content",
42
+ "course contents",
43
+ "course description",
44
+ ],
45
+ yontem: [
46
+ "yöntem ve teknikler",
47
+ "öğretim yöntem ve teknikleri",
48
+ "öğretim yöntemleri",
49
+ "yöntemler",
50
+ "yöntem",
51
+ "teaching methods",
52
+ "methods",
53
+ "learning and teaching methods",
54
+ "planned learning activities and teaching methods",
55
+ ],
56
+ notlar: ["ders notları", "course notes", "lecture notes"],
57
+ ciktilar: [
58
+ "öğrenme çıktıları",
59
+ "dersin öğrenme çıktıları",
60
+ "ders öğrenme çıktıları",
61
+ "learning outcomes",
62
+ "course learning outcomes",
63
+ ],
64
+ haftalar: [
65
+ "haftalık konular",
66
+ "haftalık ders konuları",
67
+ "haftalık ders akışı",
68
+ "haftalık ders planı",
69
+ "ders akışı",
70
+ "weekly topics",
71
+ "weekly schedule",
72
+ "course schedule",
73
+ "weekly subjects",
74
+ ],
75
+ degerlendirme: [
76
+ "değerlendirme",
77
+ "değerlendirme sistemi",
78
+ "değerlendirme yöntemleri",
79
+ "ölçme ve değerlendirme",
80
+ "assessment",
81
+ "assessment methods",
82
+ "evaluation",
83
+ "grading",
84
+ ],
85
+ isyuku: [
86
+ "akts iş yükü",
87
+ "akts iş yükü tablosu",
88
+ "iş yükü",
89
+ "ects workload",
90
+ "workload",
91
+ "ects allocated based on student workload",
92
+ ],
93
+ kaynaklar: [
94
+ "kaynaklar",
95
+ "kaynak",
96
+ "ders kitabı",
97
+ "önerilen kaynaklar",
98
+ "diğer kaynaklar",
99
+ "yardımcı kaynaklar",
100
+ "references",
101
+ "resources",
102
+ "textbook",
103
+ "textbooks",
104
+ "recommended sources",
105
+ "reading list",
106
+ ],
107
+ katki: [
108
+ "program çıktısına katkı",
109
+ "program çıktısına katkısı",
110
+ "prg.çıktısına katkısı",
111
+ "prg. çıktısına katkısı",
112
+ "program çıktılarına katkı",
113
+ "dersin program çıktılarına katkısı",
114
+ "öğrenme çıktılarının program çıktılarına katkısı",
115
+ "ders öğrenme çıktıları ve program çıktıları ilişkisi",
116
+ "contribution to program outcomes",
117
+ "contribution of learning outcomes to programme outcomes",
118
+ ],
119
+ dersYapisi: ["ders yapısı", "dersin yapısı", "ders kategorisi", "course structure", "course category"],
120
+ kisi: [
121
+ "dersi veren",
122
+ "dersi verenler",
123
+ "dersi veren öğretim elemanı",
124
+ "dersi veren öğretim elemanları",
125
+ "öğretim elemanı",
126
+ "öğretim elemanları",
127
+ "öğretim üyesi",
128
+ "dersin sorumlusu",
129
+ "dersin koordinatörü",
130
+ "dersin yardımcıları",
131
+ "dersin yetkilileri",
132
+ "koordinatör",
133
+ "sorumlu",
134
+ "iletişim",
135
+ "e-posta",
136
+ "eposta",
137
+ "telefon",
138
+ "instructor",
139
+ "instructors",
140
+ "lecturer",
141
+ "assistant",
142
+ "assistants",
143
+ "coordinator",
144
+ "course coordinator",
145
+ "contact",
146
+ "e-mail",
147
+ "email",
148
+ "phone",
149
+ ],
150
+ };
151
+ /** Türkçe küçük harf; noktasız ı da i sayılır, böylece "Instructor" ve "AMACI" aynı biçimde eşlenir. */
152
+ const kucuk = (s) => s.toLocaleLowerCase("tr").replace(/ı/gu, "i");
153
+ const sade = (s) => kucuk(s)
154
+ .replace(/[::*•\-–]+\s*$/u, "")
155
+ .replace(/\s*\(.*?\)\s*$/u, "")
156
+ .replace(/\s+/gu, " ")
157
+ .trim();
158
+ const ETIKET_TABLOSU = Object.entries(ETIKETLER)
159
+ .flatMap(([alan, xs]) => xs.map((x) => [kucuk(x), alan]))
160
+ .sort((a, b) => b[0].length - a[0].length);
161
+ /** Bir hücre ya da satır başı bir etiket mi? Tam eşleşme (sondaki iki nokta ve parantez yok sayılır). */
162
+ export function etiketAlani(s) {
163
+ const k = sade(s);
164
+ if (!k || k.length > 60)
165
+ return null;
166
+ return ETIKET_TABLOSU.find(([e]) => e === k)?.[1] ?? null;
167
+ }
168
+ /** "Etiket: değer" biçiminde satır başı etiketi. */
169
+ function onekEtiketi(s) {
170
+ const m = s.match(/^([^::]{2,60})[::]\s*(.*)$/u);
171
+ if (!m?.[1])
172
+ return null;
173
+ const alan = etiketAlani(m[1]);
174
+ return alan ? { alan, kalan: m[2] ?? "" } : null;
175
+ }
176
+ /** Program çıktısı kodu (P1, PÇ2, PO3) ve ders çıktısı kodu (Ö1, ÖÇ2, LO3) ya da "Tüm" satırı. */
177
+ const P_KODU = /^(?:P|PÇ|PC|PO|PY)\s*-?\s*(\d{1,2})$/iu;
178
+ const O_KODU = /^(?:Ö|ÖÇ|DÖÇ|LO|CLO|ÇK)\s*-?\s*(\d{1,2})$|^(Tüm|All)$/iu;
179
+ /** Katkı matrisinin başlık ya da değer satırı mı? Böyle satırlarda boş hücre "-" olarak korunur (sütun hizası). */
180
+ function matrisSatiri(h) {
181
+ if (h.filter((x) => P_KODU.test(x)).length >= 2)
182
+ return true;
183
+ const ilk = h.findIndex((x) => x !== "");
184
+ return (ilk >= 0 &&
185
+ O_KODU.test(h[ilk] ?? "") &&
186
+ h.length - ilk >= 3 &&
187
+ h.slice(ilk + 1).every((x) => x === "" || /^[1-5-]$/u.test(x)));
188
+ }
189
+ /** HTML ya da düz metni satır ve hücrelere çevirir. Tablo hücreleri sekmeyle ayrılır. */
190
+ export function satirlara(girdi) {
191
+ const htmlMi = /<\/?(table|tr|td|th|p|div|br|li|h\d|body|span)\b/iu.test(girdi);
192
+ const duz = htmlMi
193
+ ? girdi
194
+ .replace(/<(script|style)\b[\s\S]*?<\/\1>/giu, "")
195
+ .replace(/<\/t[dh]>/giu, "\u0001")
196
+ .replace(/<\/tr>|<br\s*\/?>|<\/p>|<\/li>|<\/div>|<\/h\d>|<\/table>/giu, "\n")
197
+ .split("\n")
198
+ .map((s) => s
199
+ .split("\u0001")
200
+ .map((h) => htmlMetni(h))
201
+ .join("\t"))
202
+ .join("\n")
203
+ : girdi;
204
+ return duz
205
+ .split(/\r?\n/u)
206
+ .map((s) => {
207
+ const h = s.split("\t").map((x) => x.replace(/\s+/gu, " ").trim());
208
+ return matrisSatiri(h) ? h.map((x) => x || "-") : h.filter(Boolean);
209
+ })
210
+ .filter((r) => r.some((x) => x && x !== "-"));
211
+ }
212
+ /**
213
+ * Sekmeli satırlardan ÖÇ-PÇ katkı matrisi: başlıkta en az iki P kodu, altında Ö satırları. Değerler "1"-"5" ya da
214
+ * boş. Başlıkta satır etiketi sütunu yoksa veri satırları bir sütun kaydırılır. Bulunamazsa boş nesne.
215
+ */
216
+ export function katkiMatrisi(satirlar) {
217
+ for (let i = 0; i < satirlar.length; i++) {
218
+ const baslik = satirlar[i] ?? [];
219
+ const sutunlar = baslik
220
+ .map((h, j) => ({ j, m: h.match(P_KODU) }))
221
+ .filter((x) => x.m !== null);
222
+ if (sutunlar.length < 2)
223
+ continue;
224
+ const kayma = P_KODU.test(baslik[0] ?? "") ? 1 : 0;
225
+ const sonuc = {};
226
+ for (let k = i + 1; k < satirlar.length; k++) {
227
+ const r = satirlar[k] ?? [];
228
+ const m = (r[0] ?? "").match(O_KODU);
229
+ if (!m)
230
+ break;
231
+ const anahtar = m[2] ? "Tüm" : `Ö${Number(m[1])}`;
232
+ sonuc[anahtar] = Object.fromEntries(sutunlar.map(({ j, m: pm }) => {
233
+ const v = (r[j + kayma] ?? "").trim();
234
+ return [`P${Number(pm[1])}`, /^[1-5]$/u.test(v) ? v : ""];
235
+ }));
236
+ }
237
+ if (Object.keys(sonuc).length)
238
+ return sonuc;
239
+ }
240
+ return {};
241
+ }
242
+ const INGILIZCE_ALAN = /\b(course objectives?|learning outcomes|course contents?|weekly (topics|schedule|subjects)|ects workload|assessment methods?|teaching methods|course description)\b|\(\s*([iİ]ngilizce|english)\s*\)|[iİ]ngilizce (amaç|içerik|adı)/iu;
243
+ /** Sayfada İngilizce alan var mı (İngilizce etiket ya da "(İngilizce)" alanı). Bologna araçlarının ingilizce değeri. */
244
+ export function ingilizceAlanVar(girdi) {
245
+ return INGILIZCE_ALAN.test(girdi.slice(0, 400_000));
246
+ }
247
+ const PROLIZ_METNI = /Yarıyıl\s*\t\s*Kodu|Prg\.\s*Çıktısına\s*Katkısı|Sür\.\s*Kalkınma\s*Amaçları|Semester\s*\t\s*Code/iu;
248
+ /** Proliz ders sayfasının düz metni mi (Ctrl+A, Ctrl+C)? */
249
+ export function prolizMetniMi(girdi) {
250
+ return !/<\/?(table|tr|td)\b/iu.test(girdi) && PROLIZ_METNI.test(girdi);
251
+ }
252
+ /**
253
+ * Satırları etiketli bölümlere ayırır. Başlık satırı (bütün hücreleri etiket olan) bir sonraki satırla
254
+ * eşlenir: "Ders Kodu | Ders Adı | AKTS" ve altında "KRG101 | ... | 5".
255
+ */
256
+ export function bolumlere(satirlar) {
257
+ const bolumler = [];
258
+ const sahipsiz = [];
259
+ let aktif = null;
260
+ for (let i = 0; i < satirlar.length; i++) {
261
+ const r = satirlar[i] ?? [];
262
+ const alanlar = r.map(etiketAlani);
263
+ const sonraki = satirlar[i + 1];
264
+ if (r.length >= 2 &&
265
+ alanlar.every(Boolean) &&
266
+ sonraki &&
267
+ sonraki.length === r.length &&
268
+ !sonraki.some((h) => etiketAlani(h))) {
269
+ r.forEach((_, j) => {
270
+ bolumler.push({ alan: alanlar[j], etiket: r[j] ?? "", deger: [sonraki[j] ?? ""], satirlar: [] });
271
+ });
272
+ aktif = null;
273
+ i++;
274
+ continue;
275
+ }
276
+ const ilk = r[0] ?? "";
277
+ const tam = etiketAlani(ilk);
278
+ const onek = tam ? null : onekEtiketi(ilk);
279
+ if (tam || onek) {
280
+ const deger = tam ? r.slice(1) : [onek?.kalan ?? "", ...r.slice(1)].filter(Boolean);
281
+ const etiket = tam ? ilk : ilk.slice(0, ilk.length - (onek?.kalan.length ?? 0));
282
+ aktif = { alan: (tam ?? onek?.alan), etiket, deger, satirlar: [] };
283
+ bolumler.push(aktif);
284
+ continue;
285
+ }
286
+ if (aktif)
287
+ aktif.satirlar.push(r);
288
+ else
289
+ sahipsiz.push(r);
290
+ }
291
+ return { bolumler, sahipsiz };
292
+ }
293
+ /** Kaynak satırından numara ya da imi atar. */
294
+ const maddeAt = (s) => s.replace(/^\s*(\[\d+\]|\d{1,2}[.)-]|[-•*–])\s*/u, "").trim();
295
+ /**
296
+ * Düz metin ya da HTML'den görev bölümlerini (Dersi Verenler, Koordinatör, iletişim) yetkililer listesine, kaynaklar
297
+ * bölümünü künye listesine ayırır. Değerler olduğu gibi korunur; hiçbir şey atılmaz.
298
+ */
299
+ export function genelAyikla(girdi) {
300
+ const { bolumler, sahipsiz } = bolumlere(satirlara(girdi));
301
+ const kaynaklar = [];
302
+ const yetkililer = [];
303
+ const kalan = sahipsiz.map((r) => r.join("\t"));
304
+ for (const b of bolumler) {
305
+ if (b.alan === "kisi") {
306
+ const deger = [b.deger.join(" "), ...b.satirlar.map((r) => r.join(" "))]
307
+ .map((x) => x.trim())
308
+ .filter(Boolean)
309
+ .join(", ");
310
+ const rol = (b.etiket ?? "").replace(/[:\s]+$/u, "").trim() || "Dersin yetkilileri";
311
+ if (deger)
312
+ yetkililer.push({ rol: rol.slice(0, 80), deger: deger.slice(0, 1000) });
313
+ continue;
314
+ }
315
+ if (b.alan === "kaynaklar") {
316
+ for (const x of [b.deger.join(" "), ...b.satirlar.map((r) => r.join(" "))])
317
+ for (const parca of x.split(/\s*;\s*(?=\S)/u)) {
318
+ const k = maddeAt(parca);
319
+ if (k.length >= 5)
320
+ kaynaklar.push(k);
321
+ }
322
+ continue;
323
+ }
324
+ const etiket = ETIKETLER[b.alan][0] ?? b.alan;
325
+ kalan.push([`${etiket}:`, ...b.deger].join("\t"));
326
+ for (const r of b.satirlar)
327
+ kalan.push(r.join("\t"));
328
+ }
329
+ return { metin: kalan.join("\n"), kaynaklar, yetkililer };
330
+ }
331
+ const ALAN_ADI = {
332
+ kod: "Ders kodu",
333
+ ad: "Ders adı",
334
+ tul: "T+U+L",
335
+ kredi: "Kredi",
336
+ akts: "AKTS",
337
+ dil: "Dersin dili",
338
+ tur: "Dersin türü",
339
+ onKosul: "Ön koşul",
340
+ amac: "Dersin amacı",
341
+ icerik: "Dersin içeriği",
342
+ yontem: "Yöntem ve teknikler",
343
+ notlar: "Ders notları",
344
+ ciktilar: "Öğrenme çıktıları",
345
+ haftalar: "Haftalık konular",
346
+ degerlendirme: "Değerlendirme",
347
+ isyuku: "AKTS iş yükü",
348
+ kaynaklar: "Kaynaklar",
349
+ katki: "Program çıktısına katkı",
350
+ dersYapisi: "Ders yapısı",
351
+ };
352
+ export const GENEL_ALAN_ADLARI = ALAN_ADI;
353
+ const sayilar = (s) => [...s.replace(/(\d),(\d)/gu, "$1.$2").matchAll(/(?<![\p{L}\d])%?\s*(\d+(?:\.\d+)?)/gu)].map((m) => Number(m[1]));
354
+ const TOPLAM = /^(toplam|total|genel toplam|akts|ects)/iu;
355
+ const HAFTA = /^(?:(\d{1,2})\s*(?:\.|\)|-)?\s*(?:hafta|week)?|(?:hafta|week)\s*(\d{1,2}))(?![\d+])/iu;
356
+ /** Tablo satırı ("Ara Sınav | 1 | %40") ya da düz satır ("Ara Sınav 1 %40"): ad ve sayılar. */
357
+ function adVeSayilar(r) {
358
+ if (r.length > 1)
359
+ return { ad: (r[0] ?? "").trim(), n: sayilar(r.slice(1).join(" ")) };
360
+ const m = (r[0] ?? "").match(/^(.*?\p{L}[^\d%]*?)\s*[:=-]?\s+((?:%?\s*\d+(?:[.,]\d+)?\s*%?\s*[x×]?\s*)+)$/u);
361
+ return m ? { ad: (m[1] ?? "").trim(), n: sayilar(m[2] ?? "") } : { ad: (r[0] ?? "").trim(), n: [] };
362
+ }
363
+ /** Bir bölümün düz metni. */
364
+ const bolumMetni = (b) => [b.deger.join(" "), ...b.satirlar.map((r) => r.join(" "))].filter(Boolean).join("\n").trim();
365
+ /** Genel ayrıştırıcı. `kaynakSayisi` bölümü sayısı ayrıca verilir (künyeler ayıklamada ayrılır). */
366
+ export function genelAyristir(girdi, kaynakSayisi = 0) {
367
+ const satirlar = satirlara(girdi);
368
+ const { bolumler } = bolumlere(satirlar);
369
+ const ilk = (alan) => bolumler.find((b) => b.alan === alan);
370
+ const tekDeger = (alan) => {
371
+ const b = ilk(alan);
372
+ if (!b)
373
+ return undefined;
374
+ const d = (b.deger[0] ?? b.satirlar[0]?.[0] ?? "").trim();
375
+ return d || undefined;
376
+ };
377
+ const metinAlani = (alan) => {
378
+ const b = ilk(alan);
379
+ const m = b ? bolumMetni(b) : "";
380
+ return m || undefined;
381
+ };
382
+ // T+U+L: etiketten ya da metnin herhangi bir yerinden "3+0+0" kalıbı.
383
+ const tulHam = tekDeger("tul") ??
384
+ satirlar
385
+ .flat()
386
+ .join(" ")
387
+ .match(/\b\d{1,2}\s*\+\s*\d{1,2}\s*\+\s*\d{1,2}\b/u)?.[0];
388
+ const tul = tulHam?.match(/\d{1,2}\s*\+\s*\d{1,2}\s*\+\s*\d{1,2}/u)?.[0]?.replace(/\s/gu, "");
389
+ const ciktiBolumu = ilk("ciktilar");
390
+ const ciktiSatirlari = ciktiBolumu ? [ciktiBolumu.deger, ...ciktiBolumu.satirlar].filter((r) => r.length) : [];
391
+ const NUMARA = /^(?:ö|öç|döç|lo|clo)?\s*(\d{1,2})\s*[.):-]?\s*/iu;
392
+ const numarali = ciktiSatirlari.filter((r) => NUMARA.test(r[0] ?? "") && (r.length > 1 || (r[0] ?? "").replace(NUMARA, "").length > 3));
393
+ const ciktilar = (numarali.length ? numarali : ciktiSatirlari.filter((r) => /^[-•*–]/u.test(r[0] ?? "")))
394
+ .map((r) => {
395
+ const ilkHucre = r[0] ?? "";
396
+ const metin = r.length > 1 && /^(?:ö|öç|döç|lo|clo)?\s*\d{1,2}\s*[.):-]?\s*$/iu.test(ilkHucre)
397
+ ? r.slice(1).join(" ")
398
+ : maddeAt(ilkHucre.replace(NUMARA, "")) + (r.length > 1 ? ` ${r.slice(1).join(" ")}` : "");
399
+ return metin.trim();
400
+ })
401
+ .filter((m) => m.length > 3)
402
+ .map((metin, i) => ({ kod: `Ö${i + 1}`, metin }));
403
+ const haftaBolumu = ilk("haftalar");
404
+ const haftaKonulari = haftaBolumu
405
+ ? haftaBolumu.satirlar
406
+ .filter((r) => HAFTA.test(r.join(" ")) && r.join(" ").replace(HAFTA, "").trim().length > 1)
407
+ .map((r) => r
408
+ .join(" ")
409
+ .replace(HAFTA, "")
410
+ .replace(/^[\s.:)-]+/u, "")
411
+ .trim())
412
+ : [];
413
+ const satirTablosu = (alan) => {
414
+ const b = ilk(alan);
415
+ return b ? [b.deger, ...b.satirlar].filter((r) => r.length) : [];
416
+ };
417
+ const degerlendirme = satirTablosu("degerlendirme")
418
+ .map(adVeSayilar)
419
+ .filter((x) => x.ad && /\p{L}/u.test(x.ad) && x.n.length >= 1 && !TOPLAM.test(x.ad))
420
+ .map(({ ad, n }) => ({ ad, sayi: n.length >= 2 ? (n[0] ?? null) : 1, katki: n.at(-1) ?? null }));
421
+ const isyuku = satirTablosu("isyuku")
422
+ .map(adVeSayilar)
423
+ .filter((x) => x.ad && /\p{L}/u.test(x.ad) && x.n.length >= 2 && !TOPLAM.test(x.ad))
424
+ .map(({ ad, n }) => {
425
+ const s = n[0] ?? null;
426
+ const sure = n[1] ?? null;
427
+ return {
428
+ ad,
429
+ sayi: s,
430
+ sure,
431
+ toplam: n.length >= 3 ? (n[2] ?? null) : s !== null && sure !== null ? s * sure : null,
432
+ };
433
+ });
434
+ const katki = katkiMatrisi(satirlar);
435
+ const dersYapisi = satirTablosu("dersYapisi")
436
+ .map(adVeSayilar)
437
+ .filter((x) => x.ad && /\p{L}/u.test(x.ad) && x.n.length >= 1 && !TOPLAM.test(x.ad))
438
+ .map(({ ad, n }) => ({ alan: ad, yuzde: n.at(-1) ?? null }));
439
+ const bilgiler = {
440
+ ...(tekDeger("dil") ? { dil: tekDeger("dil") } : {}),
441
+ ...(tekDeger("tur") ? { tur: tekDeger("tur") } : {}),
442
+ ...(metinAlani("onKosul") ? { onKosul: metinAlani("onKosul") } : {}),
443
+ };
444
+ const ders = {
445
+ ...(tekDeger("kod") ? { kod: tekDeger("kod") } : {}),
446
+ ...(tekDeger("ad") ? { ad: tekDeger("ad") } : {}),
447
+ yariyil: sayi(tekDeger("yariyil")),
448
+ ...(tul ? { tul } : {}),
449
+ kredi: sayi(tekDeger("kredi")),
450
+ akts: sayi(tekDeger("akts")),
451
+ ...(metinAlani("amac") ? { amac: metinAlani("amac") } : {}),
452
+ ...(metinAlani("icerik") ? { icerik: metinAlani("icerik") } : {}),
453
+ ...(metinAlani("yontem") ? { yontem: metinAlani("yontem") } : {}),
454
+ ...(metinAlani("notlar") ? { notlar: metinAlani("notlar") } : {}),
455
+ ciktilar,
456
+ degerlendirme,
457
+ isyuku,
458
+ dersYapisi,
459
+ haftalar: haftaKonulari.length,
460
+ haftaKonulari,
461
+ katki,
462
+ };
463
+ const varMi = {
464
+ kod: Boolean(ders.kod),
465
+ ad: Boolean(ders.ad),
466
+ tul: Boolean(ders.tul),
467
+ kredi: ders.kredi !== null,
468
+ akts: ders.akts !== null,
469
+ dil: Boolean(bilgiler.dil),
470
+ tur: Boolean(bilgiler.tur),
471
+ onKosul: Boolean(bilgiler.onKosul),
472
+ amac: Boolean(ders.amac),
473
+ icerik: Boolean(ders.icerik),
474
+ yontem: Boolean(ders.yontem),
475
+ notlar: Boolean(ders.notlar),
476
+ ciktilar: ciktilar.length > 0,
477
+ haftalar: haftaKonulari.length > 0,
478
+ degerlendirme: degerlendirme.length > 0,
479
+ isyuku: isyuku.length > 0,
480
+ kaynaklar: kaynakSayisi > 0,
481
+ katki: Object.keys(katki).length > 0,
482
+ dersYapisi: dersYapisi.length > 0,
483
+ };
484
+ const bulunan = Object.keys(varMi).filter((k) => varMi[k]);
485
+ const bulunamayan = Object.keys(varMi).filter((k) => !varMi[k]);
486
+ const temel = ["ad", "akts", "amac", "icerik", "ciktilar", "haftalar", "degerlendirme", "isyuku"].filter((k) => varMi[k]).length;
487
+ const guven = temel >= 7 ? "yüksek" : temel >= 4 ? "orta" : "düşük";
488
+ return { ders, bilgiler, bulunan, bulunamayan, guven };
489
+ }
@@ -17,11 +17,29 @@ export interface AcikHttpSecenekleri {
17
17
  };
18
18
  /** Günlük yazıcı. Varsayılan: stdout'a tek satır JSON. */
19
19
  gunluk?: (k: GunlukKaydi) => void;
20
- /** Ters vekilin yazdığı istemci adresi başlığı. Varsayılan x-forwarded-for. */
21
- adresBasligi?: string;
20
+ /**
21
+ * x-forwarded-for listesinde sondan kaç öğenin güvenilen vekillerce yazıldığı. Varsayılan 1: istemci adresi
22
+ * listenin son öğesidir (vekil gerçek adresi sona ekler; baştaki öğeler istemcinin kendi yazdığıdır, sahtelenebilir).
23
+ */
24
+ guvenilenVekil?: number;
25
+ /** kaynak_dogrula için adres başına ayrı kova (dış katalog sorgusu pahalıdır). Varsayılan dakikada 5. */
26
+ kaynakHizi?: {
27
+ kapasite: number;
28
+ saniyede: number;
29
+ };
22
30
  }
31
+ /**
32
+ * İstemci adresi. Sıra: cf-connecting-ip (Cloudflare yazar, istemci değiştiremez), x-real-ip (ters vekil yazar),
33
+ * x-forwarded-for'un sondan `guvenilenVekil`. öğesi. İlk öğe asla kullanılmaz: istemci onu istediği gibi yazabilir.
34
+ * Not: cf-connecting-ip'ye ancak köken sunucuya yalnız Cloudflare üzerinden erişiliyorsa güvenilebilir.
35
+ */
36
+ export declare function istemciAdresi(basliklar: Headers, guvenilenVekil?: number): string;
23
37
  export declare const CORS_BASLIKLARI: Record<string, string>;
24
- /** Süreç içi kova. Adres sayısı sınırlıdır; dolan tablo en eski kayıtlardan boşaltılır. */
38
+ /**
39
+ * Süreç içi kova. Adres sayısı sınırlıdır. Tablo dolunca önce süresi geçmiş (jetonu yeniden dolmuş, yani yeni
40
+ * gelen bir adresle aynı durumdaki) kayıtlar silinir; yine doluysa yeni adres reddedilir. Böylece çok sayıda sahte
41
+ * adresle eski kayıtları itip gerçek adreslerin sayacı sıfırlanamaz.
42
+ */
25
43
  export declare class AdresKovasi {
26
44
  private kovalar;
27
45
  private kapasite;
@@ -32,5 +50,15 @@ export declare class AdresKovasi {
32
50
  izin: boolean;
33
51
  bekle: number;
34
52
  };
53
+ /** Jetonu yeniden kapasiteye dolmuş kayıtları siler. */
54
+ temizle(simdiMs: number): void;
55
+ get boyut(): number;
35
56
  }
57
+ /** Gövdeyi bayt sayarak okur; sınır aşılınca akışı iptal eder. content-length olmasa da (chunked) bellek sınırlı kalır. */
58
+ export declare function govdeOku(istek: Request, enBuyuk: number): Promise<{
59
+ tur: "tamam";
60
+ tampon: Uint8Array;
61
+ } | {
62
+ tur: "buyuk";
63
+ }>;
36
64
  export declare function acikMcpIsleyici(s?: AcikHttpSecenekleri): (istek: Request) => Promise<Response>;