@akredit/acik-mcp 0.1.0 → 0.4.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/README.md +94 -8
- package/dist/apps/acik-mcp/src/araclar.d.ts +2 -28
- package/dist/apps/acik-mcp/src/araclar.js +27 -114
- package/dist/apps/acik-mcp/src/bologna.d.ts +209 -0
- package/dist/apps/acik-mcp/src/bologna.js +1321 -0
- package/dist/apps/acik-mcp/src/genel-ayristir.d.ts +83 -0
- package/dist/apps/acik-mcp/src/genel-ayristir.js +411 -0
- package/dist/apps/acik-mcp/src/http.js +4 -2
- package/dist/apps/acik-mcp/src/index.d.ts +6 -0
- package/dist/apps/acik-mcp/src/index.js +6 -0
- package/dist/apps/acik-mcp/src/kaynak-dogrula.d.ts +47 -0
- package/dist/apps/acik-mcp/src/kaynak-dogrula.js +319 -0
- package/dist/apps/acik-mcp/src/kaynak.d.ts +72 -0
- package/dist/apps/acik-mcp/src/kaynak.js +155 -0
- package/dist/apps/acik-mcp/src/kisisel-veri.d.ts +14 -2
- package/dist/apps/acik-mcp/src/kisisel-veri.js +63 -15
- package/dist/apps/acik-mcp/src/not-tablosu.d.ts +33 -0
- package/dist/apps/acik-mcp/src/not-tablosu.js +243 -0
- package/dist/apps/acik-mcp/src/promptlar.d.ts +25 -0
- package/dist/apps/acik-mcp/src/promptlar.js +253 -0
- package/dist/apps/acik-mcp/src/sunucu.d.ts +12 -4
- package/dist/apps/acik-mcp/src/sunucu.js +103 -24
- package/dist/apps/acik-mcp/src/temel.d.ts +54 -0
- package/dist/apps/acik-mcp/src/temel.js +10 -0
- package/dist/apps/acik-mcp/src/yardim.d.ts +56 -0
- package/dist/apps/acik-mcp/src/yardim.js +371 -0
- package/dist/packages/baglayicilar/src/bologna-proliz/ayristir.d.ts +29 -0
- package/dist/packages/baglayicilar/src/bologna-proliz/ayristir.js +200 -0
- package/package.json +4 -2
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BolognaDersSayfasi, BolognaProgramCiktisi, BolognaProgramMatrisi } from "../../../cekirdek/src/index.js";
|
|
2
|
+
/** HTML parçasını düz metne çevirir. */
|
|
3
|
+
export declare function metin(html: string): string;
|
|
4
|
+
export type HtmlTablosu = string[][];
|
|
5
|
+
/** Sayfadaki en içteki tabloları satır ve hücre dizisine çevirir. */
|
|
6
|
+
export declare function tablolar(html: string): HtmlTablosu[];
|
|
7
|
+
/** Metindeki ilk sayıyı alır ("% 40" → 40, "2,5" → 2.5). */
|
|
8
|
+
export declare const sayi: (s: unknown) => number | null;
|
|
9
|
+
export interface BolognaAdresi {
|
|
10
|
+
taban: string;
|
|
11
|
+
sunit: string;
|
|
12
|
+
dil: string;
|
|
13
|
+
}
|
|
14
|
+
/** Program adresinden taban adresi ve program kimliğini çıkarır. */
|
|
15
|
+
export declare function adresCoz(adres: string): BolognaAdresi;
|
|
16
|
+
export declare function programCiktilari(html: string): BolognaProgramCiktisi[];
|
|
17
|
+
export declare const EPOSTA_YERINE = "[e-posta ay\u0131kland\u0131]";
|
|
18
|
+
/** Nesnedeki bütün metinlerden e-posta adreslerini ayıklar. */
|
|
19
|
+
export declare function epostaAyikla<T>(x: T): T;
|
|
20
|
+
export declare function programMatrisi(html: string): BolognaProgramMatrisi;
|
|
21
|
+
/** Ders listesi sayfasından ders kodu → Proliz ders kimliği eşlemesi. */
|
|
22
|
+
export declare function dersKimlikleri(html: string): Record<string, string>;
|
|
23
|
+
/** Bir ders bilgi sayfasını ayrıştırır. E-posta adresleri çıktıdan ayıklanır (KVKK: en az kişisel veri). */
|
|
24
|
+
export declare function dersSayfasiCoz(html: string): BolognaDersSayfasi;
|
|
25
|
+
/**
|
|
26
|
+
* TYYÇ matrisinde program çıktısı sütunlarındaki dolu hücreleri sayar.
|
|
27
|
+
* Satırlar "etiket, alt no, P1..Pn, alt no, etiket" ya da etiketsiz "alt no, P1..Pn, alt no" biçimindedir.
|
|
28
|
+
*/
|
|
29
|
+
export declare function tyycIsaretSayisi(html: string, pSayisi: number): number | null;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Bologna Bilgi Paketi (Proliz OBS) sayfa ayrıştırıcıları.
|
|
2
|
+
// apps/cli/lib/bologna.mjs içindeki işlevlerin davranışı birebir korunarak TypeScript'e taşınmış hâlidir.
|
|
3
|
+
const VARLIKLAR = { nbsp: " ", amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
4
|
+
const ETIKET = /<(?:[^>"']|"[^"]*"|'[^']*')*>/g;
|
|
5
|
+
const BETIK = /<(script|style)\b(?:[^>"']|"[^"]*"|'[^']*')*>[\s\S]*?<\/\1>/gi;
|
|
6
|
+
/** HTML parçasını düz metne çevirir. */
|
|
7
|
+
export function metin(html) {
|
|
8
|
+
return html
|
|
9
|
+
.replace(BETIK, "")
|
|
10
|
+
.replace(/<br\s*\/?>/gi, " ")
|
|
11
|
+
.replace(ETIKET, " ")
|
|
12
|
+
.replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)))
|
|
13
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, n) => String.fromCodePoint(Number.parseInt(n, 16)))
|
|
14
|
+
.replace(/&([a-z]+);/gi, (m, a) => VARLIKLAR[a.toLowerCase()] ?? m)
|
|
15
|
+
.replace(/\s+/g, " ")
|
|
16
|
+
.trim();
|
|
17
|
+
}
|
|
18
|
+
/** Sayfadaki en içteki tabloları satır ve hücre dizisine çevirir. */
|
|
19
|
+
export function tablolar(html) {
|
|
20
|
+
const temiz = html.replace(BETIK, "");
|
|
21
|
+
const sonuc = [];
|
|
22
|
+
const re = /<table\b(?:[^>"']|"[^"]*"|'[^']*')*>((?:(?!<table\b)[\s\S])*?)<\/table>/gi;
|
|
23
|
+
for (let m = re.exec(temiz); m; m = re.exec(temiz)) {
|
|
24
|
+
const satirlar = [...(m[1] ?? "").matchAll(/<tr\b(?:[^>"']|"[^"]*"|'[^']*')*>([\s\S]*?)<\/tr>/gi)].map((r) => [...(r[1] ?? "").matchAll(/<t[dh]\b(?:[^>"']|"[^"]*"|'[^']*')*>([\s\S]*?)<\/t[dh]>/gi)].map((c) => metin(c[1] ?? "")));
|
|
25
|
+
if (satirlar.some((s) => s.length))
|
|
26
|
+
sonuc.push(satirlar);
|
|
27
|
+
}
|
|
28
|
+
return sonuc;
|
|
29
|
+
}
|
|
30
|
+
/** Metindeki ilk sayıyı alır ("% 40" → 40, "2,5" → 2.5). */
|
|
31
|
+
export const sayi = (s) => {
|
|
32
|
+
const m = String(s ?? "")
|
|
33
|
+
.replace(",", ".")
|
|
34
|
+
.match(/-?\d+(\.\d+)?/);
|
|
35
|
+
return m ? Number(m[0]) : null;
|
|
36
|
+
};
|
|
37
|
+
/** Program adresinden taban adresi ve program kimliğini çıkarır. */
|
|
38
|
+
export function adresCoz(adres) {
|
|
39
|
+
const u = new URL(adres);
|
|
40
|
+
const sunit = u.searchParams.get("curSunit");
|
|
41
|
+
const i = u.pathname.toLowerCase().indexOf("/bologna/");
|
|
42
|
+
if (!sunit || i < 0)
|
|
43
|
+
throw new Error("Bologna program adresi bekleniyor (içinde /bologna/ ve curSunit olmalı).");
|
|
44
|
+
return { taban: `${u.origin}${u.pathname.slice(0, i + 9)}`, sunit, dil: u.searchParams.get("lang") || "tr" };
|
|
45
|
+
}
|
|
46
|
+
export function programCiktilari(html) {
|
|
47
|
+
const t = tablolar(html).find((t) => /Program Öğrenme Çıktıları|Program Outcomes/i.test(t[0]?.join(" ") ?? ""));
|
|
48
|
+
if (!t)
|
|
49
|
+
return [];
|
|
50
|
+
return t
|
|
51
|
+
.slice(1)
|
|
52
|
+
.filter((r) => r.length >= 2 && sayi(r[0]) !== null)
|
|
53
|
+
.map((r) => ({ kod: `P${sayi(r[0])}`, metin: r[1] ?? "" }));
|
|
54
|
+
}
|
|
55
|
+
/** Ders kodu: harf önekli ("HUK101", "HUK 101") ya da sayısal ("1000.020105", EBYÜ canlı yapısı). */
|
|
56
|
+
const DERS_KODU = /^(?:[A-ZÇĞİÖŞÜ]{2,}\s*\d|\d{3,}\.\d{3,})/;
|
|
57
|
+
const DERS_KODU_ARA = /\b([A-ZÇĞİÖŞÜ]{2,}\s?\d{3}[A-Z]?|\d{3,}\.\d{3,})\b/;
|
|
58
|
+
const YUZDE = /^%\s*\d/;
|
|
59
|
+
const EPOSTA = /[\p{L}\d._%+-]+@[\p{L}\d-]+(?:\.[\p{L}\d-]+)+/gu;
|
|
60
|
+
export const EPOSTA_YERINE = "[e-posta ayıklandı]";
|
|
61
|
+
/** Nesnedeki bütün metinlerden e-posta adreslerini ayıklar. */
|
|
62
|
+
export function epostaAyikla(x) {
|
|
63
|
+
if (typeof x === "string")
|
|
64
|
+
return x.replace(EPOSTA, EPOSTA_YERINE);
|
|
65
|
+
if (Array.isArray(x))
|
|
66
|
+
return x.map(epostaAyikla);
|
|
67
|
+
if (x && typeof x === "object")
|
|
68
|
+
return Object.fromEntries(Object.entries(x).map(([k, v]) => [k, epostaAyikla(v)]));
|
|
69
|
+
return x;
|
|
70
|
+
}
|
|
71
|
+
export function programMatrisi(html) {
|
|
72
|
+
const satirlar = tablolar(html).flat();
|
|
73
|
+
const baslik = satirlar.find((r) => r.includes("Ders Kodu") && r.some((h) => /^P\d+$/.test(h)));
|
|
74
|
+
if (!baslik)
|
|
75
|
+
return { pkodlar: [], dersler: [] };
|
|
76
|
+
const pBas = baslik.findIndex((h) => /^P\d+$/.test(h));
|
|
77
|
+
const pkodlar = baslik.slice(pBas);
|
|
78
|
+
let yariyil = null;
|
|
79
|
+
const dersler = [];
|
|
80
|
+
for (const r of satirlar) {
|
|
81
|
+
const ilk = r[0] ?? "";
|
|
82
|
+
if (r.length === 1 && /Yarıyıl|Semester/i.test(ilk)) {
|
|
83
|
+
yariyil = sayi(ilk);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (r.length < pBas + 1 || r === baslik || ilk === "Ders Kodu")
|
|
87
|
+
continue;
|
|
88
|
+
const kod = ilk.replace(/^\[G\]\s*/, "");
|
|
89
|
+
if (!DERS_KODU.test(kod))
|
|
90
|
+
continue;
|
|
91
|
+
dersler.push({
|
|
92
|
+
kod,
|
|
93
|
+
ad: r[1] ?? "",
|
|
94
|
+
zorunlu: /Zorunlu|Compulsory/i.test(r[2] ?? ""),
|
|
95
|
+
grup: r[3] || null,
|
|
96
|
+
yariyil,
|
|
97
|
+
grupBasligi: ilk.startsWith("[G]"),
|
|
98
|
+
degerler: Object.fromEntries(pkodlar.map((p, i) => [p, r[pBas + i] ?? ""])),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return { pkodlar, dersler };
|
|
102
|
+
}
|
|
103
|
+
/** Ders listesi sayfasından ders kodu → Proliz ders kimliği eşlemesi. */
|
|
104
|
+
export function dersKimlikleri(html) {
|
|
105
|
+
const kimlik = {};
|
|
106
|
+
for (const tr of html.split(/<tr\b/i).slice(1)) {
|
|
107
|
+
const id = tr.match(/prolizOpenCourseDetails\('?(\d+)/);
|
|
108
|
+
const kod = metin(tr).match(DERS_KODU_ARA);
|
|
109
|
+
if (id?.[1] && kod?.[1] && !kimlik[kod[1]])
|
|
110
|
+
kimlik[kod[1]] = id[1];
|
|
111
|
+
}
|
|
112
|
+
return kimlik;
|
|
113
|
+
}
|
|
114
|
+
/** Ders sayfasının ham ayrıştırması (e-posta ayıklanmadan). */
|
|
115
|
+
function dersSayfasiCozHam(html) {
|
|
116
|
+
const t = tablolar(html);
|
|
117
|
+
const bul = (re) => t.find((x) => re.test((x[0] ?? []).join(" ")));
|
|
118
|
+
const ust = bul(/Yarıyıl.*Kodu|Semester.*Code/i);
|
|
119
|
+
const [yariyil, kod, ad, tul, kredi, akts, guncelleme] = ust?.[1] ?? [];
|
|
120
|
+
const bilgiler = Object.fromEntries((bul(/Dersin Dili|Language/i) ?? []).filter((r) => r.length >= 2).map((r) => [r[0] ?? "", r[1] ?? ""]));
|
|
121
|
+
const degerlendirme = (bul(/Yarıyıl Çalışmaları|Term Studies/i) ?? [])
|
|
122
|
+
.slice(1)
|
|
123
|
+
.filter((r) => r.length >= 3 && !/Toplam|Total/i.test(r[0] ?? ""))
|
|
124
|
+
.map((r) => ({ ad: r[0] ?? "", sayi: sayi(r[1]), katki: sayi(r[2]) }));
|
|
125
|
+
const isyukuTablosu = bul(/İş Yükü|Workload/i) ?? [];
|
|
126
|
+
const isyuku = isyukuTablosu
|
|
127
|
+
.slice(1)
|
|
128
|
+
.filter((r) => r.length >= 4 && sayi(r[3]) !== null && !/Toplam|Total|AKTS|ECTS/i.test(r[0] ?? ""))
|
|
129
|
+
.map((r) => ({ ad: r[0] ?? "", sayi: sayi(r[1]), sure: sayi(r[2]), toplam: sayi(r[3]) }));
|
|
130
|
+
// Canlı Proliz sayfasında (EBYÜ, 2026-10-27) alan yüzdesi tablosu başlıksızdır: ["Sosyal Bilimler", "%100", ""].
|
|
131
|
+
const dersYapisi = (t.find((x) => x.some((r) => /Alan Bilgisi|Field/i.test(r[0] ?? ""))) ??
|
|
132
|
+
t.find((x) => x.length > 0 && x.every((r) => r.length >= 2 && YUZDE.test(r[1] ?? ""))) ??
|
|
133
|
+
[])
|
|
134
|
+
.filter((r) => r.length >= 2 && /%/.test(r[1] ?? ""))
|
|
135
|
+
.map((r) => ({ alan: r[0] ?? "", yuzde: sayi(r[1]) }));
|
|
136
|
+
const ciktiTablosu = bul(/Sıra No|No.*A[cç]ıklama|Description/i) ?? [];
|
|
137
|
+
const ciktilar = ciktiTablosu
|
|
138
|
+
.slice(1)
|
|
139
|
+
.filter((r) => r.length >= 2 && sayi(r[0]) !== null && r[1])
|
|
140
|
+
.map((r) => ({ kod: `Ö${sayi(r[0])}`, metin: r[1] ?? "" }));
|
|
141
|
+
const haftalar = (bul(/Hafta|Week/i) ?? []).slice(1).filter((r) => sayi(r[0]) !== null && r[1]).length;
|
|
142
|
+
// Katkı satırları "Ö3" ya da çıktının türüne göre "Bi3" (bilgi), "Be3" (beceri), "Ye3" (yetkinlik) diye adlandırılır.
|
|
143
|
+
const katkiTablosu = t.find((x) => (x[0] ?? []).some((h) => /^P\d+$/.test(h)) &&
|
|
144
|
+
x.some((r) => /^(Tüm|All|Ö\d+|Bi\d+|Be\d+|Ye\d+)$/.test(r[0] ?? "")));
|
|
145
|
+
const katki = {};
|
|
146
|
+
if (katkiTablosu) {
|
|
147
|
+
const pk = (katkiTablosu[0] ?? []).slice(1);
|
|
148
|
+
for (const r of katkiTablosu.slice(1)) {
|
|
149
|
+
if (!r[0])
|
|
150
|
+
continue;
|
|
151
|
+
const ad = /^(Ö|Bi|Be|Ye)\d+$/.test(r[0]) ? `Ö${sayi(r[0])}` : r[0];
|
|
152
|
+
katki[ad] = Object.fromEntries(pk.map((p, i) => [p, r[i + 1] ?? ""]));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
yariyil: sayi(yariyil),
|
|
157
|
+
...(kod !== undefined ? { kod } : {}),
|
|
158
|
+
...(ad !== undefined ? { ad } : {}),
|
|
159
|
+
...(tul !== undefined ? { tul } : {}),
|
|
160
|
+
kredi: sayi(kredi),
|
|
161
|
+
akts: sayi(akts),
|
|
162
|
+
...(guncelleme !== undefined ? { guncelleme } : {}),
|
|
163
|
+
bilgiler,
|
|
164
|
+
degerlendirme,
|
|
165
|
+
isyuku,
|
|
166
|
+
dersYapisi,
|
|
167
|
+
ciktilar,
|
|
168
|
+
haftalar,
|
|
169
|
+
katki,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/** Bir ders bilgi sayfasını ayrıştırır. E-posta adresleri çıktıdan ayıklanır (KVKK: en az kişisel veri). */
|
|
173
|
+
export function dersSayfasiCoz(html) {
|
|
174
|
+
return epostaAyikla(dersSayfasiCozHam(html));
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* TYYÇ matrisinde program çıktısı sütunlarındaki dolu hücreleri sayar.
|
|
178
|
+
* Satırlar "etiket, alt no, P1..Pn, alt no, etiket" ya da etiketsiz "alt no, P1..Pn, alt no" biçimindedir.
|
|
179
|
+
*/
|
|
180
|
+
export function tyycIsaretSayisi(html, pSayisi) {
|
|
181
|
+
if (!pSayisi)
|
|
182
|
+
return null;
|
|
183
|
+
// Canlı yapı (EBYÜ, 2026-10-27): 14 hücreli satır "alan, alt no, P1..Pn, alt no, alan"; rowspan yüzünden
|
|
184
|
+
// sonraki satırlar 12 hücreli "alt no, P1..Pn, alt no". Ayırıcı boş satırlar (alt no yok) sayılmaz.
|
|
185
|
+
// Hiç P hücresi okunamadıysa null (veri yok), okunup hepsi boşsa 0 döner.
|
|
186
|
+
let dolu = 0;
|
|
187
|
+
let okunan = 0;
|
|
188
|
+
for (const r of tablolar(html).flat()) {
|
|
189
|
+
const ic = r.length === pSayisi + 4 && /^\d+$/.test(r[1] ?? "")
|
|
190
|
+
? r.slice(2, 2 + pSayisi)
|
|
191
|
+
: r.length === pSayisi + 2 && /^\d+$/.test(r[0] ?? "")
|
|
192
|
+
? r.slice(1, 1 + pSayisi)
|
|
193
|
+
: null;
|
|
194
|
+
if (!ic)
|
|
195
|
+
continue;
|
|
196
|
+
okunan += ic.length;
|
|
197
|
+
dolu += ic.filter((h) => h && h !== "-").length;
|
|
198
|
+
}
|
|
199
|
+
return okunan ? dolu : null;
|
|
200
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akredit/acik-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "Herkese açık Akredit MCP sunucusu: hesapsız, salt okuma, deterministik araçlar (ölçüt, Bologna denetimi, ÇK/PÇ hesabı, rapor dil denetimi). Kurum verisine erişmez, model çağırmaz.",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"model-context-protocol",
|
|
10
10
|
"akreditasyon",
|
|
11
11
|
"kalite-guvencesi",
|
|
12
|
-
"bologna"
|
|
12
|
+
"bologna",
|
|
13
|
+
"akts"
|
|
13
14
|
],
|
|
14
15
|
"homepage": "https://akredit.org/mcp-kullan",
|
|
15
16
|
"engines": {
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
"zod": "^4.6.7"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
38
|
+
"@akredit/baglayicilar": "0.1.0",
|
|
37
39
|
"@akredit/cekirdek": "0.1.0",
|
|
38
40
|
"@akredit/cerceveler": "0.1.0",
|
|
39
41
|
"@akredit/olcme": "0.1.0",
|