@akredit/acik-mcp 0.4.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.
- package/README.md +5 -5
- package/dist/apps/acik-mcp/src/araclar.js +2 -0
- package/dist/apps/acik-mcp/src/bologna.d.ts +91 -9
- package/dist/apps/acik-mcp/src/bologna.js +477 -105
- package/dist/apps/acik-mcp/src/genel-ayristir.d.ts +10 -1
- package/dist/apps/acik-mcp/src/genel-ayristir.js +85 -7
- package/dist/apps/acik-mcp/src/http.d.ts +31 -3
- package/dist/apps/acik-mcp/src/http.js +86 -15
- package/dist/apps/acik-mcp/src/index.d.ts +1 -0
- package/dist/apps/acik-mcp/src/index.js +1 -0
- package/dist/apps/acik-mcp/src/kaynak-dogrula.d.ts +51 -4
- package/dist/apps/acik-mcp/src/kaynak-dogrula.js +199 -58
- package/dist/apps/acik-mcp/src/kisisel-veri.d.ts +3 -1
- package/dist/apps/acik-mcp/src/kisisel-veri.js +24 -9
- package/dist/apps/acik-mcp/src/not-tablosu.d.ts +6 -0
- package/dist/apps/acik-mcp/src/not-tablosu.js +40 -15
- package/dist/apps/acik-mcp/src/promptlar.d.ts +23 -0
- package/dist/apps/acik-mcp/src/promptlar.js +98 -28
- package/dist/apps/acik-mcp/src/sunucu.d.ts +1 -1
- package/dist/apps/acik-mcp/src/sunucu.js +10 -5
- package/dist/apps/acik-mcp/src/temel.d.ts +5 -0
- package/dist/apps/acik-mcp/src/yardim.js +28 -18
- package/dist/packages/cekirdek/src/bologna/taslak.d.ts +19 -3
- package/dist/packages/cekirdek/src/bologna/taslak.js +70 -9
- package/dist/packages/cekirdek/src/turler.d.ts +8 -0
- package/package.json +2 -2
|
@@ -11,7 +11,8 @@ import { dersSayfasiCoz, programCiktilari, tablolar } from "../../../packages/ba
|
|
|
11
11
|
import { bolognaDenetle, taslakDenetle, } from "../../../packages/cekirdek/src/index.js";
|
|
12
12
|
import { DOGRULANMADI as DOGRULANMADI_ISARETI } from "../../../packages/cerceveler/src/index.js";
|
|
13
13
|
import { z } from "zod";
|
|
14
|
-
import { GENEL_ALAN_ADLARI, genelAyikla, genelAyristir } from "./genel-ayristir.js";
|
|
14
|
+
import { GENEL_ALAN_ADLARI, genelAyikla, genelAyristir, ingilizceAlanVar, prolizMetniMi } from "./genel-ayristir.js";
|
|
15
|
+
import { kaynakImzasiDogru } from "./kaynak-dogrula.js";
|
|
15
16
|
import { girisBilgisiAyikla } from "./kisisel-veri.js";
|
|
16
17
|
import { AracHatasi, arac, KOD_KAYNAGI } from "./temel.js";
|
|
17
18
|
// ---------------------------------------------------------------- Alan modeli
|
|
@@ -91,10 +92,11 @@ export const ALANLAR = {
|
|
|
91
92
|
sekme: "Öğrenme Çıktıları",
|
|
92
93
|
kurallar: [
|
|
93
94
|
"K4: Her çıktı ölçülebilir bir eylem fiiliyle biter (açıklar, uygular, tasarlar). 'Bilgi sahibi olur', 'öğrenir', 'kavrar' olmaz.",
|
|
94
|
-
"
|
|
95
|
+
"K4: '-ebilme/-abilme' (tasarlayabilme) ve '-ebilir' kalıpları kabul edilir. 'Sahip olur', 'farkında olur', 'haberdar olur', 'vakıf olur' ölçülemez.",
|
|
96
|
+
"K5: 4-10 çıktı geçerli, 5-8 önerilir (Akredit önerisi). Ö1'den kesintisiz. Her çıktının türü Bilgi, Beceri ya da Yetkinlik; çoğu Beceri, en az biri Yetkinlik.",
|
|
95
97
|
"İngilizcesi tam çeviri olsun.",
|
|
96
98
|
],
|
|
97
|
-
sinir: { enAzCikti:
|
|
99
|
+
sinir: { enAzCikti: 4, enCokCikti: 10, onerilenEnAz: 5, onerilenEnCok: 8 },
|
|
98
100
|
kaliplar: [
|
|
99
101
|
"[Konu] kavramlarını örneklerle açıklar. (Bilgi)",
|
|
100
102
|
"[Yöntem] kullanarak [problem türünü] çözer. (Beceri)",
|
|
@@ -109,6 +111,7 @@ export const ALANLAR = {
|
|
|
109
111
|
kurallar: [
|
|
110
112
|
"Katkılar toplamı tam %100.",
|
|
111
113
|
"Uygulamalı derste (U ya da L sıfırdan büyük) uygulama, proje, ödev ya da laboratuvar kalemi olur.",
|
|
114
|
+
"Değerlendirme yöntemi ve yüzdeleri resmî taahhüttür: sayfadan okunur ya da hocaya sorulur, asla tahmin edilmez. Kaynağını degerlendirmeDayanagi olarak verin ('sayfa' ya da 'hoca'); teyitsiz yüzde OBS'ye yazılmaz.",
|
|
112
115
|
],
|
|
113
116
|
sinir: { toplamYuzde: 100 },
|
|
114
117
|
kaliplar: ["Ara Sınav 1 %40, Yarıyıl Sonu Sınavı 1 %60", "Ara Sınav 1 %30, Proje 1 %20, Yarıyıl Sonu Sınavı 1 %50"],
|
|
@@ -119,12 +122,12 @@ export const ALANLAR = {
|
|
|
119
122
|
ad: "AKTS iş yükü",
|
|
120
123
|
sekme: DERS_BILGILERI,
|
|
121
124
|
kurallar: [
|
|
122
|
-
"K8: Toplam iş yükü (sayı x süre toplamı)
|
|
125
|
+
"K8: Toplam iş yükü (sayı x süre toplamı) AKTS başına 25-30 saat aralığında olur. Kurum AKTS başına saat değerini (bolen) verdiyse toplam tam AKTS x o değerdir.",
|
|
123
126
|
"AKTS'yi değiştirmeyin, saat dağılımını değiştirin.",
|
|
124
127
|
"Ders Süresi satırının haftalık süresi T+U+L toplamına eşit olur.",
|
|
125
|
-
"
|
|
128
|
+
"Yalnız derste gerçekten olan etkinliklere saat verilir (ödev yoksa ödev satırı yok).",
|
|
126
129
|
],
|
|
127
|
-
sinir: {
|
|
130
|
+
sinir: { aralik: "25-30 saat/AKTS", kurumDegeri: "verilirse tam eşitlik" },
|
|
128
131
|
kaliplar: [
|
|
129
132
|
"Ders Süresi 14 x (T+U+L)",
|
|
130
133
|
"Sınıf Dışı Ders Çalışma Süresi 14 x [saat]",
|
|
@@ -185,7 +188,8 @@ export const ALANLAR = {
|
|
|
185
188
|
sekme: "Diğer Kaynaklar",
|
|
186
189
|
kurallar: [
|
|
187
190
|
"En az 2 gerçek kaynak (yazar, yıl ve eser adı).",
|
|
188
|
-
"
|
|
191
|
+
"Her kaynak { kunye, dogrulama } olarak verilir: dogrulama ya kaynak_dogrula sonucudur (durum, baglanti, imza) ya da 'hoca_beyani'. Düz metin kaynak doğrulanmamış sayılır ve OBS'ye yazılmaz.",
|
|
192
|
+
"Katalogda bulunmayan (çoğu Türkçe kitap) eser yalnız hoca kullandığını söylerse, hocanın verdiği bilgiyle 'hoca_beyani' olarak yazılır.",
|
|
189
193
|
"Yazar adları yayımlanmış künyedir; bu alanda serbesttir. E-posta ve telefon yazılmaz.",
|
|
190
194
|
],
|
|
191
195
|
sinir: { enAz: 2 },
|
|
@@ -228,8 +232,13 @@ const SECENEK_KURALI = "Sunucu metin üretmez. Seçenekleri asistan üretir: 2-3
|
|
|
228
232
|
const KAYNAK_KURALLAR = KOD_KAYNAGI("packages/cekirdek/src/bologna/taslak.ts", "Akredit Bologna taslak kuralları");
|
|
229
233
|
const KAYNAK_SAYFA = KOD_KAYNAGI("packages/cekirdek/src/bologna/denetim.ts", "Akredit Bologna sayfa denetim kuralları");
|
|
230
234
|
const KAYNAK_AYRISTIRICI = KOD_KAYNAGI("packages/baglayicilar/src/bologna-proliz/ayristir.ts", "Akredit Bologna (Proliz) sayfa ayrıştırıcısı");
|
|
231
|
-
/**
|
|
232
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Bologna sayfası hocanın kendi yayımlanmış ders bilgisidir. Giriş bilgisine ek olarak T.C. kimlik no, öğrenci
|
|
237
|
+
* numarası ve IBAN reddedilir (dış inceleme 2026-11-05); yayımlanmış öğretim elemanı adları serbesttir.
|
|
238
|
+
*/
|
|
239
|
+
const BOLOGNA_KAPSAMI = "ogrenci";
|
|
240
|
+
/** Künye kuralıyla (kişi adı ve uzun numara serbest) denetlenen alanlar: kaynaklar ve görev satırları. */
|
|
241
|
+
const BOLOGNA_KUNYE_ALANLARI = ["kaynaklar", "yetkililer"];
|
|
233
242
|
// ---------------------------------------------------------------- Ortak hesap ve eşleme
|
|
234
243
|
const TUL = z
|
|
235
244
|
.string()
|
|
@@ -295,6 +304,33 @@ const IkiDilli = z.object({
|
|
|
295
304
|
tr: z.string().max(6000),
|
|
296
305
|
en: z.string().max(6000).optional(),
|
|
297
306
|
});
|
|
307
|
+
const KaynakDogrulamasi = z.union([
|
|
308
|
+
z.literal("hoca_beyani"),
|
|
309
|
+
z.object({
|
|
310
|
+
durum: z.string().max(40),
|
|
311
|
+
baglanti: z.string().max(600),
|
|
312
|
+
imza: z.string().max(80).optional(),
|
|
313
|
+
}),
|
|
314
|
+
]);
|
|
315
|
+
const KaynakOgesi = z.union([
|
|
316
|
+
z.string().trim().min(5).max(600),
|
|
317
|
+
z.object({ kunye: z.string().trim().min(5).max(600), dogrulama: KaynakDogrulamasi }),
|
|
318
|
+
]);
|
|
319
|
+
/** Kaynağın doğrulama durumu. Düz metin ya da imzası bu sürece ait olmayan kayıt doğrulanmamıştır. */
|
|
320
|
+
export function kaynakDurumu(k) {
|
|
321
|
+
if (typeof k === "string")
|
|
322
|
+
return "dogrulanmamis";
|
|
323
|
+
if (k.dogrulama === "hoca_beyani")
|
|
324
|
+
return "hoca_beyani";
|
|
325
|
+
const d = k.dogrulama;
|
|
326
|
+
return d.durum === "katalogda_bulundu" &&
|
|
327
|
+
/^https:\/\//u.test(d.baglanti) &&
|
|
328
|
+
d.imza !== undefined &&
|
|
329
|
+
kaynakImzasiDogru(k.kunye.trim(), d.baglanti, d.imza)
|
|
330
|
+
? "katalogda_bulundu"
|
|
331
|
+
: "dogrulanmamis";
|
|
332
|
+
}
|
|
333
|
+
export const kunyeMetni = (k) => (typeof k === "string" ? k : k.kunye).trim();
|
|
298
334
|
const TaslakAlanlari = z.object({
|
|
299
335
|
amac: IkiDilli.optional(),
|
|
300
336
|
icerik: IkiDilli.optional(),
|
|
@@ -337,11 +373,15 @@ const TaslakAlanlari = z.object({
|
|
|
337
373
|
.optional()
|
|
338
374
|
.describe("Ö kodu, P kodu, 1-5 ya da boş"),
|
|
339
375
|
ska: z.array(z.number().int().min(1).max(17)).max(17).optional(),
|
|
376
|
+
degerlendirmeDayanagi: z
|
|
377
|
+
.enum(["sayfa", "hoca", "oneri"])
|
|
378
|
+
.optional()
|
|
379
|
+
.describe("Değerlendirme yöntem ve yüzdelerinin kaynağı: 'sayfa' (OBS sayfasından okundu), 'hoca' (hoca söyledi), 'oneri' (asistan önerisi, teyitsiz). Teyitsiz yüzde OBS'ye yazılmaz."),
|
|
340
380
|
kaynaklar: z
|
|
341
|
-
.array(
|
|
381
|
+
.array(KaynakOgesi)
|
|
342
382
|
.max(30)
|
|
343
383
|
.optional()
|
|
344
|
-
.describe("
|
|
384
|
+
.describe("Her kaynak { kunye, dogrulama }: dogrulama kaynak_dogrula sonucu ({ durum, baglanti, imza }) ya da 'hoca_beyani'. Düz metin doğrulanmamış sayılır."),
|
|
345
385
|
kaynakSayisi: z
|
|
346
386
|
.number()
|
|
347
387
|
.int()
|
|
@@ -356,7 +396,14 @@ const DersTemeli = z.object({
|
|
|
356
396
|
ad: z.string().trim().max(200).optional(),
|
|
357
397
|
akts: z.number().min(0).max(60).optional(),
|
|
358
398
|
tul: TUL.optional().describe("Teori+Uygulama+Laboratuvar, ör. '2+1+0'"),
|
|
359
|
-
bolen: z
|
|
399
|
+
bolen: z
|
|
400
|
+
.number()
|
|
401
|
+
.int()
|
|
402
|
+
.min(20)
|
|
403
|
+
.max(40)
|
|
404
|
+
.optional()
|
|
405
|
+
.describe("Kurumun AKTS başına saat değeri; verilmezse 25-30 aralığı kabul edilir"),
|
|
406
|
+
donem: z.string().trim().max(40).optional().describe("Dönem, ör. 2026-2027 Güz; OBS geri okumasında denetlenir"),
|
|
360
407
|
yetkililer: z
|
|
361
408
|
.array(YetkiliSemasi)
|
|
362
409
|
.max(20)
|
|
@@ -371,7 +418,6 @@ export function alanBulgulari(ders, alanlar, s) {
|
|
|
371
418
|
...(ders.ad ? { ad: ders.ad } : {}),
|
|
372
419
|
...(ders.akts !== undefined ? { akts: ders.akts } : {}),
|
|
373
420
|
...(ders.tul ? { tul: ders.tul } : {}),
|
|
374
|
-
bolen: ders.bolen,
|
|
375
421
|
...(alanlar.amac ? { amac: ikiDil(alanlar.amac, s.ingilizce) } : {}),
|
|
376
422
|
...(alanlar.icerik ? { icerik: ikiDil(alanlar.icerik, s.ingilizce) } : {}),
|
|
377
423
|
...(alanlar.yontem ? { yontem: ikiDil(alanlar.yontem, s.ingilizce) } : {}),
|
|
@@ -408,13 +454,32 @@ export function alanBulgulari(ders, alanlar, s) {
|
|
|
408
454
|
const sonuc = new Map();
|
|
409
455
|
const ekle = (a, b) => sonuc.set(a, [...(sonuc.get(a) ?? []), b]);
|
|
410
456
|
if (verilen.has("kaynaklar") && alanlar.kaynaklar)
|
|
411
|
-
for (const [i, k] of alanlar.kaynaklar.entries())
|
|
412
|
-
if (
|
|
457
|
+
for (const [i, k] of alanlar.kaynaklar.entries()) {
|
|
458
|
+
if (kaynakDurumu(k) === "dogrulanmamis")
|
|
459
|
+
ekle("kaynaklar", {
|
|
460
|
+
duzey: "hata",
|
|
461
|
+
kural: "Kaynaklar",
|
|
462
|
+
aciklama: `${i + 1}. kaynak doğrulanmamış; doğrulanmamış kaynak yazılamaz. kaynak_dogrula sonucunu (durum, baglanti, imza) aynen verin ya da hoca bu eseri kullandığını söylediyse dogrulama: 'hoca_beyani' yazın.`,
|
|
463
|
+
kuralTuru: "Akredit önerisi",
|
|
464
|
+
dayanak: "Değişmez ilke 1-2: kanıtsız iddia ve uydurma yok.",
|
|
465
|
+
});
|
|
466
|
+
if (!KUNYE_YILI.test(kunyeMetni(k)))
|
|
413
467
|
ekle("kaynaklar", {
|
|
414
468
|
duzey: "uyari",
|
|
415
469
|
kural: "Kaynaklar",
|
|
416
470
|
aciklama: `${i + 1}. kaynakta yıl görünmüyor. Yılı hocaya sorun ya da kaynak_bicimle ile biçimlendirin.`,
|
|
417
471
|
});
|
|
472
|
+
}
|
|
473
|
+
if (verilen.has("degerlendirme") &&
|
|
474
|
+
alanlar.degerlendirmeDayanagi !== "sayfa" &&
|
|
475
|
+
alanlar.degerlendirmeDayanagi !== "hoca")
|
|
476
|
+
ekle("degerlendirme", {
|
|
477
|
+
duzey: "uyari",
|
|
478
|
+
kural: "Değerlendirme",
|
|
479
|
+
aciklama: "Değerlendirme yöntem ve yüzdeleri teyitsiz (sayfadan okunmadı, hoca söylemedi). Hocaya tek mesajda sorun; teyitsiz yüzde OBS'ye yazılmaz.",
|
|
480
|
+
kuralTuru: "Akredit önerisi",
|
|
481
|
+
dayanak: "Değişmez ilke 2-3: değerlendirme oranları resmî taahhüttür, uydurulmaz; beyan hocadadır.",
|
|
482
|
+
});
|
|
418
483
|
// İş yükü AKTS olmadan denetlenemez; cekirdek "akts boş" diye temel bulgu verir, burada alana bağlanır.
|
|
419
484
|
if (verilen.has("isyuku") && ders.akts === undefined)
|
|
420
485
|
ekle("isyuku", {
|
|
@@ -422,7 +487,7 @@ export function alanBulgulari(ders, alanlar, s) {
|
|
|
422
487
|
kural: "K8",
|
|
423
488
|
aciklama: "Dersin AKTS değeri verilmedi; iş yükü hedefi hesaplanamaz.",
|
|
424
489
|
});
|
|
425
|
-
for (const b of taslakDenetle(t, {
|
|
490
|
+
for (const b of taslakDenetle(t, { kuralSeti: "guncel", kurumBoleni: ders.bolen }).bulgular) {
|
|
426
491
|
const a = bulguAlani(b);
|
|
427
492
|
if (a === "temel" || a === "yetkililer")
|
|
428
493
|
continue;
|
|
@@ -451,7 +516,17 @@ const KUNYE_YILI = /\((?:\d{4}[a-z]?|t\.y\.|n\.d\.|baskıda|in press)\)|\b(1[5-9
|
|
|
451
516
|
/** Bütün sayılar burada hesaplanır; asistan toplam yazmaz (ilke 2). */
|
|
452
517
|
export function bolognaHesabi(ders, alanlar) {
|
|
453
518
|
const saat = tulSaati(ders.tul);
|
|
454
|
-
const
|
|
519
|
+
const hedefAraligi = ders.akts === undefined
|
|
520
|
+
? null
|
|
521
|
+
: ders.bolen
|
|
522
|
+
? [ders.akts * ders.bolen, ders.akts * ders.bolen]
|
|
523
|
+
: [ders.akts * 25, ders.akts * 30];
|
|
524
|
+
const hedef = hedefAraligi ? hedefAraligi[1] : null;
|
|
525
|
+
const hedefMetni = !hedefAraligi
|
|
526
|
+
? ""
|
|
527
|
+
: ders.bolen
|
|
528
|
+
? `${ders.akts} x ${ders.bolen} (kurum değeri) = ${hedefAraligi[0]}`
|
|
529
|
+
: `${ders.akts} AKTS için ${hedefAraligi[0]}-${hedefAraligi[1]} (AKTS başına 25-30)`;
|
|
455
530
|
const isyuku = alanlar.isyuku
|
|
456
531
|
? (() => {
|
|
457
532
|
const satirlar = alanlar.isyuku.map((x) => ({
|
|
@@ -462,19 +537,29 @@ export function bolognaHesabi(ders, alanlar) {
|
|
|
462
537
|
}));
|
|
463
538
|
const toplam = yuvarla(satirlar.reduce((t, x) => t + x.toplam, 0), 2);
|
|
464
539
|
const dersSuresi = alanlar.isyuku.find((x) => /ders süresi/iu.test(x.ad));
|
|
540
|
+
const aralikta = hedefAraligi ? toplam >= hedefAraligi[0] && toplam <= hedefAraligi[1] : null;
|
|
541
|
+
const fark = !hedefAraligi
|
|
542
|
+
? null
|
|
543
|
+
: toplam < hedefAraligi[0]
|
|
544
|
+
? yuvarla(hedefAraligi[0] - toplam, 2)
|
|
545
|
+
: toplam > hedefAraligi[1]
|
|
546
|
+
? yuvarla(hedefAraligi[1] - toplam, 2)
|
|
547
|
+
: 0;
|
|
465
548
|
return {
|
|
466
549
|
satirlar,
|
|
467
550
|
toplam,
|
|
468
551
|
hedef,
|
|
469
|
-
|
|
552
|
+
hedefAraligi,
|
|
553
|
+
aralikta,
|
|
554
|
+
fark,
|
|
470
555
|
aktsBasinaSaat: ders.akts ? yuvarla(toplam / ders.akts) : null,
|
|
471
556
|
dersSuresiHaftalikBeklenen: saat,
|
|
472
557
|
dersSuresiHaftalikVerilen: dersSuresi ? dersSuresi.sure : null,
|
|
473
|
-
aciklama:
|
|
558
|
+
aciklama: hedefAraligi === null
|
|
474
559
|
? "AKTS verilmediği için hedef hesaplanmadı."
|
|
475
|
-
:
|
|
476
|
-
? `Toplam ${toplam} saat, hedef ${
|
|
477
|
-
: `Toplam ${toplam} saat, hedef ${
|
|
560
|
+
: aralikta
|
|
561
|
+
? `Toplam ${toplam} saat, hedef ${hedefMetni}. Uyumlu.`
|
|
562
|
+
: `Toplam ${toplam} saat, hedef ${hedefMetni}. ${fark !== null && fark > 0 ? `${fark} saat ekleyin` : `${yuvarla(-(fark ?? 0), 2)} saat azaltın`}; AKTS'yi değiştirmeyin.`,
|
|
478
563
|
};
|
|
479
564
|
})()
|
|
480
565
|
: null;
|
|
@@ -547,8 +632,10 @@ function alanDurumlari(d, sayfaBulgulari) {
|
|
|
547
632
|
...(d.kaynakSayisi !== undefined || d.kaynaklar
|
|
548
633
|
? { kaynakSayisi: d.kaynaklar?.length ?? d.kaynakSayisi ?? 0 }
|
|
549
634
|
: {}),
|
|
635
|
+
// Sayfadaki değerlendirme yayımlanmış hâlidir; teyitsiz uyarısı burada anlamsızdır.
|
|
636
|
+
degerlendirmeDayanagi: "sayfa",
|
|
550
637
|
};
|
|
551
|
-
const b = alanBulgulari({ akts: d.akts ?? undefined
|
|
638
|
+
const b = alanBulgulari({ akts: d.akts ?? undefined }, alanlar, { ingilizce: false });
|
|
552
639
|
// Sayfada çıktı türü görünmez; türle ilgili kurallar burada denetlenmez.
|
|
553
640
|
const turKurali = /Yetkinlik|Beceri|türü/u;
|
|
554
641
|
const nedenler = (a) => (b.get(a) ?? []).filter((x) => x.duzey === "hata" && !turKurali.test(x.aciklama)).map((x) => x.aciklama);
|
|
@@ -653,7 +740,7 @@ export const bolognaDenetleAraci = arac({
|
|
|
653
740
|
aciklama: "Bir dersin Bologna bilgi paketini denetler: hangi alan boş, hangisi zayıf, neden. AKTS ve iş yükü uyumu, değerlendirme toplamı, öğrenme çıktıları, haftalar ve katkı tablosu. Kurallar Akredit'in iç kurallarıdır, karar komisyonundur.",
|
|
654
741
|
sonrakiAdim: "Doldurulacak alanları hocaya numaralı gösterin ve sorun: 1) alan alan seçenek göstererek dolduralım 2) yalnız boş olanlar 3) numaraları kendim seçeyim 4) Hepsini akıllıca doldur (önerilen). 4'te her alanı kendiniz doldurup denetleyin, sonunda tek özet gösterin. Her alan için bologna_alan_rehberi'ni çağırın.",
|
|
655
742
|
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
656
|
-
kunyeAlanlari:
|
|
743
|
+
kunyeAlanlari: BOLOGNA_KUNYE_ALANLARI,
|
|
657
744
|
girdi: z.object({
|
|
658
745
|
ders: BolognaDersGirdisi,
|
|
659
746
|
programCiktilari: z
|
|
@@ -669,8 +756,16 @@ export const bolognaDenetleAraci = arac({
|
|
|
669
756
|
.tuple([z.number().min(1), z.number().max(60)])
|
|
670
757
|
.optional()
|
|
671
758
|
.describe("AKTS başına saat aralığı; varsayılan [25, 30]"),
|
|
759
|
+
kurumBoleni: z
|
|
760
|
+
.number()
|
|
761
|
+
.int()
|
|
762
|
+
.min(20)
|
|
763
|
+
.max(40)
|
|
764
|
+
.optional()
|
|
765
|
+
.describe("Kurumun AKTS başına saat değeri; verilirse aralık yerine bu değer kullanılır (taslak denetimiyle aynı)"),
|
|
672
766
|
}),
|
|
673
|
-
calistir: ({ ders, programCiktilari, matrisSatiri, aktsSaat }, b) => {
|
|
767
|
+
calistir: ({ ders, programCiktilari, matrisSatiri, aktsSaat: aralik, kurumBoleni }, b) => {
|
|
768
|
+
const aktsSaat = kurumBoleni ? [kurumBoleni, kurumBoleni] : aralik;
|
|
674
769
|
const { amac: _a, icerik: _i, yontem: _y, notlar: _n, kaynakSayisi: _k, kaynaklar: _kk, ...sayfaAlanlari } = ders;
|
|
675
770
|
const sayfa = {
|
|
676
771
|
...sayfaAlanlari,
|
|
@@ -701,7 +796,15 @@ export const bolognaDenetleAraci = arac({
|
|
|
701
796
|
tyycIsaret: null,
|
|
702
797
|
dersler: [{ ...sayfa, zorunlu: ders.zorunlu, bos: false, kaynak: "kullanici-girdisi" }],
|
|
703
798
|
};
|
|
704
|
-
let bulgular = bolognaDenetle(veri, { simdi: b.simdi().getTime(), ...(aktsSaat ? { aktsSaat } : {}) })
|
|
799
|
+
let bulgular = bolognaDenetle(veri, { simdi: b.simdi().getTime(), ...(aktsSaat ? { aktsSaat } : {}) }).map((x) => ({
|
|
800
|
+
...x,
|
|
801
|
+
kuralTuru: x.kural === "akts-isyuku" && kurumBoleni ? "kurum" : "Akredit önerisi",
|
|
802
|
+
dayanak: x.kural === "akts-isyuku"
|
|
803
|
+
? kurumBoleni
|
|
804
|
+
? "Kurumun verdiği AKTS başına saat değeri."
|
|
805
|
+
: "AKTS başına 25-30 saat yaygın uygulamadır; resmî metinle DOĞRULANMADI."
|
|
806
|
+
: "Akredit iç kuralı (packages/cekirdek/src/bologna/denetim.ts); resmî dayanağı DOĞRULANMADI.",
|
|
807
|
+
}));
|
|
705
808
|
// Program düzeyi kuralları yalnız program çıktıları verildiyse anlamlıdır.
|
|
706
809
|
if (!programCiktilari)
|
|
707
810
|
bulgular = bulgular.filter((x) => x.konu !== "-");
|
|
@@ -740,9 +843,16 @@ const alanRehberi = arac({
|
|
|
740
843
|
akts: z.number().min(0).max(60).optional(),
|
|
741
844
|
tul: TUL.optional().describe("ör. '2+1+0'"),
|
|
742
845
|
hafta: z.number().int().min(10).max(20).default(14).describe("Ders haftası sayısı; varsayılan 14"),
|
|
743
|
-
bolen: z
|
|
846
|
+
bolen: z
|
|
847
|
+
.number()
|
|
848
|
+
.int()
|
|
849
|
+
.min(20)
|
|
850
|
+
.max(40)
|
|
851
|
+
.optional()
|
|
852
|
+
.describe("Kurumun AKTS başına saati; verilmezse 30 ile hesaplanır, 25-30 aralığı kabul edilir"),
|
|
744
853
|
}),
|
|
745
|
-
calistir: ({ alan, akts, tul, hafta, bolen }) => {
|
|
854
|
+
calistir: ({ alan, akts, tul, hafta, bolen: kurum }) => {
|
|
855
|
+
const bolen = kurum ?? 30;
|
|
746
856
|
const secilen = alan === "hepsi" ? [...ALAN_KODLARI] : [alan];
|
|
747
857
|
const saat = tulSaati(tul);
|
|
748
858
|
const hesap = secilen.includes("isyuku") && akts !== undefined
|
|
@@ -752,6 +862,7 @@ const alanRehberi = arac({
|
|
|
752
862
|
return {
|
|
753
863
|
hedefSaat: hedef,
|
|
754
864
|
formul: `${akts} AKTS x ${bolen} = ${hedef} saat`,
|
|
865
|
+
kabulAraligi: kurum ? [hedef, hedef] : [akts * 25, akts * 30],
|
|
755
866
|
dersSuresiSatiri: saat === null ? null : { sayi: hafta, sure: saat, toplam: dersSuresi },
|
|
756
867
|
kalanSaat: dersSuresi === null ? null : hedef - dersSuresi,
|
|
757
868
|
not: dersSuresi === null
|
|
@@ -787,10 +898,10 @@ const alanRehberi = arac({
|
|
|
787
898
|
const taslakDenetleAraci = arac({
|
|
788
899
|
ad: "bologna_taslak_denetle",
|
|
789
900
|
baslik: "Bologna taslağını denetle",
|
|
790
|
-
aciklama: "Asistanın yazdığı 1-3 seçeneği (A, B, C) Akredit kurallarıyla denetler.
|
|
791
|
-
sonrakiAdim: "
|
|
901
|
+
aciklama: "Asistanın yazdığı 1-3 seçeneği (A, B, C) Akredit kurallarıyla BİÇİM yönünden denetler (sayılar, uzunluklar, fiil, toplamlar, kaynak doğrulaması). İçeriğin doğruluğunu bilemez: içerik teyidi hocadadır. Her seçenek için biçim sonucu ve eksik nedenlerini döndürür; her kuralın türü (resmî, kurum, Akredit önerisi) ve dayanağı vardır. İş yükü toplamını ve yüzde toplamlarını kendisi hesaplar.",
|
|
902
|
+
sonrakiAdim: "Biçim denetiminden geçen seçenekleri hocaya 'öneri' etiketiyle kısa gösterin (A, B, C; ilki önerilen) ve içeriği hocanın teyit edeceğini söyleyin. Geçmeyeni düzeltip yeniden denetleyin, hocaya göstermeyin. Seçim bitince bologna_kopya_metni ile OBS metnini verin.",
|
|
792
903
|
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
793
|
-
kunyeAlanlari:
|
|
904
|
+
kunyeAlanlari: BOLOGNA_KUNYE_ALANLARI,
|
|
794
905
|
girdi: z.object({
|
|
795
906
|
ders: DersTemeli,
|
|
796
907
|
secenekler: z
|
|
@@ -798,7 +909,10 @@ const taslakDenetleAraci = arac({
|
|
|
798
909
|
.min(1)
|
|
799
910
|
.max(3),
|
|
800
911
|
programCiktilari: z.array(z.string().max(10)).max(40).optional().describe("Programın P kodları: P1, P2, ..."),
|
|
801
|
-
ingilizce: z
|
|
912
|
+
ingilizce: z
|
|
913
|
+
.boolean()
|
|
914
|
+
.default(false)
|
|
915
|
+
.describe("İngilizce karşılıklar da denetlensin mi. bologna_ayristir'in ingilizce değerini verin (sayfada İngilizce alan varsa true)."),
|
|
802
916
|
}),
|
|
803
917
|
calistir: ({ ders, secenekler, programCiktilari, ingilizce }) => {
|
|
804
918
|
const sonuclar = secenekler.map((s) => {
|
|
@@ -806,10 +920,17 @@ const taslakDenetleAraci = arac({
|
|
|
806
920
|
const hepsi = [...b.entries()].flatMap(([alan, xs]) => xs.map((x) => ({ alan, ...x })));
|
|
807
921
|
const eksikler = hepsi
|
|
808
922
|
.filter((x) => x.duzey === "hata")
|
|
809
|
-
.map((x) => ({
|
|
923
|
+
.map((x) => ({
|
|
924
|
+
alan: x.alan,
|
|
925
|
+
kural: x.kural,
|
|
926
|
+
neden: x.aciklama,
|
|
927
|
+
kuralTuru: x.kuralTuru ?? "Akredit önerisi",
|
|
928
|
+
dayanak: x.dayanak ?? "",
|
|
929
|
+
}));
|
|
810
930
|
return {
|
|
811
931
|
etiket: s.etiket,
|
|
812
|
-
|
|
932
|
+
bicimDenetimi: eksikler.length === 0 ? "geçti" : "geçmedi",
|
|
933
|
+
icerikTeyidi: "hoca",
|
|
813
934
|
eksikler,
|
|
814
935
|
uyarilar: hepsi
|
|
815
936
|
.filter((x) => x.duzey !== "hata")
|
|
@@ -819,13 +940,13 @@ const taslakDenetleAraci = arac({
|
|
|
819
940
|
});
|
|
820
941
|
return {
|
|
821
942
|
sonuc: {
|
|
822
|
-
|
|
943
|
+
bicimdenGecenler: sonuclar.filter((x) => x.bicimDenetimi === "geçti").map((x) => x.etiket),
|
|
823
944
|
secenekler: sonuclar,
|
|
824
|
-
not: "
|
|
945
|
+
not: "Biçim denetiminden geçti; içerik teyidi: hoca. Sonuç yalnız Akredit'in biçim kurallarına uyulduğunu söyler; içeriğin doğruluğuna ve derse uygunluğuna hoca karar verir.",
|
|
825
946
|
resmiDayanak: RESMI_DAYANAK,
|
|
826
947
|
},
|
|
827
948
|
kaynak: [KAYNAK_KURALLAR],
|
|
828
|
-
...(sonuclar.some((x) => x.
|
|
949
|
+
...(sonuclar.some((x) => x.bicimDenetimi === "geçti")
|
|
829
950
|
? {}
|
|
830
951
|
: {
|
|
831
952
|
sonrakiAdim: "Hiçbir seçenek geçmedi. Eksik nedenlerine göre düzeltip yeniden denetleyin; hocaya henüz göstermeyin.",
|
|
@@ -899,10 +1020,13 @@ export function ayristirOnAyikla(g) {
|
|
|
899
1020
|
const { html: _h, metin: _m, ...kalan } = o;
|
|
900
1021
|
const satir = bolognaSatirAyikla(giris.metin);
|
|
901
1022
|
const ek = ileti ? { ileti } : {};
|
|
1023
|
+
// Biçim ve dil ipucu ayıklamadan ÖNCE okunur: genel ayıklama etiketleri sadeleştirir.
|
|
1024
|
+
const sayfaIpucu = { prolizMetni: prolizMetniMi(giris.metin), ingilizce: ingilizceAlanVar(giris.metin) };
|
|
902
1025
|
if (prolizMi(giris.metin))
|
|
903
1026
|
return {
|
|
904
1027
|
girdi: {
|
|
905
1028
|
...kalan,
|
|
1029
|
+
sayfaIpucu,
|
|
906
1030
|
html: satir.html,
|
|
907
1031
|
kaynaklar: [...onceki, ...satir.kaynaklar],
|
|
908
1032
|
yetkililer: [...oncekiY, ...satir.yetkililer],
|
|
@@ -915,6 +1039,7 @@ export function ayristirOnAyikla(g) {
|
|
|
915
1039
|
return {
|
|
916
1040
|
girdi: {
|
|
917
1041
|
...kalan,
|
|
1042
|
+
sayfaIpucu,
|
|
918
1043
|
metin: r.metin,
|
|
919
1044
|
kaynaklar: [...onceki, ...satir.kaynaklar, ...r.kaynaklar],
|
|
920
1045
|
yetkililer: [...oncekiY, ...satir.yetkililer, ...r.yetkililer],
|
|
@@ -927,9 +1052,9 @@ const ayristirAraci = arac({
|
|
|
927
1052
|
ad: "bologna_ayristir",
|
|
928
1053
|
baslik: "Bologna sayfasını ayrıştır",
|
|
929
1054
|
aciklama: "Yapıştırılan Bologna ders sayfasını ya da program çıktıları sayfasını çözer. Önce Proliz biçimini dener, olmazsa her OBS için genel ayrıştırıcı çalışır (HTML ya da düz metin, Türkçe ya da İngilizce etiketler). Bulunan ve bulunamayan alanları, güven düzeyini söyler. Adres çekmez.",
|
|
930
|
-
sonrakiAdim: "Çıkan ders verisini bologna_denetle'ye verin (girdi hazır: bolognaDenetleGirdisi).
|
|
1055
|
+
sonrakiAdim: "Çıkan ders verisini bologna_denetle'ye verin (girdi hazır: bolognaDenetleGirdisi). Sonraki çağrılarda ingilizce değerini aynen verin. Değerlendirme yöntem ve yüzdeleri, uygulama/lab, kaynaklar, amaç ve içerik sayfada yoksa bunları hocaya TEK mesajda, numaralı ve varsayılan seçenekli sorun; uydurmayın.",
|
|
931
1056
|
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
932
|
-
kunyeAlanlari:
|
|
1057
|
+
kunyeAlanlari: BOLOGNA_KUNYE_ALANLARI,
|
|
933
1058
|
onAyikla: ayristirOnAyikla,
|
|
934
1059
|
girdi: z.object({
|
|
935
1060
|
html: z
|
|
@@ -942,8 +1067,12 @@ const ayristirAraci = arac({
|
|
|
942
1067
|
kaynaklar: z.array(z.string().max(600)).max(30).optional().describe("Ayrıca verilen künyeler"),
|
|
943
1068
|
yetkililer: z.array(YetkiliSemasi).max(20).optional().describe("Sayfadaki görev satırları, olduğu gibi"),
|
|
944
1069
|
tur: z.enum(["otomatik", "ders", "program_ciktilari"]).default("otomatik"),
|
|
1070
|
+
sayfaIpucu: z
|
|
1071
|
+
.object({ prolizMetni: z.boolean(), ingilizce: z.boolean() })
|
|
1072
|
+
.optional()
|
|
1073
|
+
.describe("Sunucu doldurur; göndermeyin"),
|
|
945
1074
|
}),
|
|
946
|
-
calistir: ({ html, metin, kaynaklar = [], yetkililer = [], tur }) => {
|
|
1075
|
+
calistir: ({ html, metin, kaynaklar = [], yetkililer = [], tur, sayfaIpucu }) => {
|
|
947
1076
|
if (!html && !metin)
|
|
948
1077
|
throw new AracHatasi("Sayfanın HTML'ini ya da metnini yapıştırın.");
|
|
949
1078
|
const kaynakAlani = {
|
|
@@ -997,6 +1126,7 @@ const ayristirAraci = arac({
|
|
|
997
1126
|
tur: "ders",
|
|
998
1127
|
bicim: "proliz",
|
|
999
1128
|
guven: "yüksek",
|
|
1129
|
+
ingilizce: sayfaIpucu?.ingilizce ?? ingilizceAlanVar(html),
|
|
1000
1130
|
bolognaDenetleGirdisi: { ders },
|
|
1001
1131
|
bulunamayan: bulunamayan.map((k) => GENEL_ALAN_ADLARI[k] ?? k),
|
|
1002
1132
|
okunamayan: bulunamayan.length
|
|
@@ -1008,7 +1138,8 @@ const ayristirAraci = arac({
|
|
|
1008
1138
|
kaynak: [KAYNAK_AYRISTIRICI],
|
|
1009
1139
|
};
|
|
1010
1140
|
}
|
|
1011
|
-
const
|
|
1141
|
+
const ham = html ?? metin ?? "";
|
|
1142
|
+
const g = genelAyristir(ham, kaynaklar.length);
|
|
1012
1143
|
if (g.bulunan.length < 2)
|
|
1013
1144
|
throw new AracHatasi("Sayfa tanınmadı: ders alanlarından en çok biri bulundu. Ders bilgi paketi sayfasının tamamını yapıştırın (sayfada Ctrl+A, Ctrl+C, sonra buraya Ctrl+V) ya da alanları tek tek yazın.");
|
|
1014
1145
|
const { haftaKonulari, ...dersAlanlari } = g.ders;
|
|
@@ -1020,8 +1151,9 @@ const ayristirAraci = arac({
|
|
|
1020
1151
|
return {
|
|
1021
1152
|
sonuc: {
|
|
1022
1153
|
tur: "ders",
|
|
1023
|
-
bicim: "genel",
|
|
1154
|
+
bicim: (sayfaIpucu?.prolizMetni ?? prolizMetniMi(ham)) ? "proliz_duz_metin" : "genel",
|
|
1024
1155
|
guven: g.guven,
|
|
1156
|
+
ingilizce: sayfaIpucu?.ingilizce ?? ingilizceAlanVar(ham),
|
|
1025
1157
|
bolognaDenetleGirdisi: { ders },
|
|
1026
1158
|
bilgiler: g.bilgiler,
|
|
1027
1159
|
haftaKonulari,
|
|
@@ -1054,7 +1186,7 @@ const durumAraci = arac({
|
|
|
1054
1186
|
aciklama: "Hocanın seçtiği alanların bütününe bakar: hangi zorunlu alan tamam, hangisi eksik ya da kurala uymuyor, kaç alan kaldı. Eksik kalmadıysa 'tamamlandı' der. Her seçimden sonra ve OBS'ye yazdıktan sonra çağrılır.",
|
|
1055
1187
|
sonrakiAdim: "Kalan alan sayısını hocaya tek cümleyle söyleyin ve sıradaki alana geçin. Kalan yoksa OBS'ye yazma adımına geçin.",
|
|
1056
1188
|
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
1057
|
-
kunyeAlanlari:
|
|
1189
|
+
kunyeAlanlari: BOLOGNA_KUNYE_ALANLARI,
|
|
1058
1190
|
girdi: z.object({
|
|
1059
1191
|
ders: DersTemeli,
|
|
1060
1192
|
alanlar: TaslakAlanlari,
|
|
@@ -1063,7 +1195,7 @@ const durumAraci = arac({
|
|
|
1063
1195
|
.max(40)
|
|
1064
1196
|
.optional()
|
|
1065
1197
|
.describe("Verilirse katkı tablosu da zorunlu alan sayılır"),
|
|
1066
|
-
ingilizce: z.boolean().default(
|
|
1198
|
+
ingilizce: z.boolean().default(false).describe("bologna_ayristir'in ingilizce değeri"),
|
|
1067
1199
|
}),
|
|
1068
1200
|
calistir: ({ ders, alanlar, programCiktilari, ingilizce }) => {
|
|
1069
1201
|
const zorunlu = [...ZORUNLU_ALANLAR, ...(programCiktilari?.length ? ["katki"] : [])];
|
|
@@ -1079,12 +1211,19 @@ const durumAraci = arac({
|
|
|
1079
1211
|
const genel = (b.get("genel") ?? []).filter((x) => x.duzey === "hata").map((x) => x.aciklama);
|
|
1080
1212
|
const kalan = satirlar.filter((s) => s.durum !== "tamam");
|
|
1081
1213
|
const tamamlandi = kalan.length === 0 && genel.length === 0;
|
|
1214
|
+
const teyitBekleyen = [
|
|
1215
|
+
...(alanlar.degerlendirme && alanlar.degerlendirmeDayanagi !== "sayfa" && alanlar.degerlendirmeDayanagi !== "hoca"
|
|
1216
|
+
? ["Değerlendirme yöntem ve yüzdeleri"]
|
|
1217
|
+
: []),
|
|
1218
|
+
];
|
|
1082
1219
|
return {
|
|
1083
1220
|
sonuc: {
|
|
1084
|
-
tamamlandi,
|
|
1221
|
+
bicimDenetimi: tamamlandi ? "geçti" : "geçmedi",
|
|
1222
|
+
icerikTeyidi: "hoca",
|
|
1085
1223
|
kalanAlanSayisi: kalan.length,
|
|
1224
|
+
teyitBekleyen,
|
|
1086
1225
|
ozet: tamamlandi
|
|
1087
|
-
?
|
|
1226
|
+
? `Biçim denetiminden geçti; içerik teyidi: hoca. Zorunlu alanların hepsi dolu ve biçim kurallarına uyuyor.${teyitBekleyen.length ? ` Teyit bekleyen: ${teyitBekleyen.join(", ")}.` : ""}`
|
|
1088
1227
|
: `${kalan.length} alan kaldı: ${kalan.map((s) => s.ad).join(", ")}.`,
|
|
1089
1228
|
alanlar: satirlar,
|
|
1090
1229
|
genelEksikler: genel,
|
|
@@ -1092,113 +1231,295 @@ const durumAraci = arac({
|
|
|
1092
1231
|
(ad === "Sürdürülebilir Kalkınma Amaçları" && alanlar.ska) ||
|
|
1093
1232
|
(ad === "Ön koşul" && alanlar.onKosul !== undefined))),
|
|
1094
1233
|
hesap: bolognaHesabi(ders, alanlar),
|
|
1095
|
-
hatirlatma: "Dersin Yetkilileri
|
|
1234
|
+
hatirlatma: "Dersin Yetkilileri alanına dokunulmaz. OBS'ye yazmadan önce alan alan önceki ve yeni değer özeti gösterilir; hoca bir kez 'kaydet' der. Kayıttan sonra sayfa geri okunur ve obs_karsilastir ile doğrulanır.",
|
|
1096
1235
|
},
|
|
1097
1236
|
kaynak: [KAYNAK_KURALLAR],
|
|
1098
1237
|
...(tamamlandi
|
|
1099
1238
|
? {
|
|
1100
|
-
sonrakiAdim: "Hocaya sorun: 1)
|
|
1239
|
+
sonrakiAdim: "bologna_kopya_metni ile yazma paketini alın (sayfadaki önceki değerleri oncekiDegerler olarak verin). Hocaya alan alan önceki/yeni değer özetini gösterip sorun: 1) formu doldurayım, Kaydet'e siz basın 2) formu ben doldurup kaydedeyim (bir kez 'kaydet' demeniz yeter) 3) alan alan kopyalayın 4) bitir.",
|
|
1101
1240
|
}
|
|
1102
1241
|
: {}),
|
|
1103
1242
|
};
|
|
1104
1243
|
},
|
|
1105
1244
|
});
|
|
1106
1245
|
// ---------------------------------------------------------------- is_yuku_oner
|
|
1107
|
-
/**
|
|
1246
|
+
/**
|
|
1247
|
+
* Değerlendirme kalemi → iş yükü satırı. Yalnız sayfadaki (ya da hocanın verdiği) değerlendirme kalemlerinden
|
|
1248
|
+
* satır kurulur; derste olmayan etkinlik (ör. ödev yoksa ödev satırı) eklenmez (dış inceleme 2026-11-05).
|
|
1249
|
+
*/
|
|
1250
|
+
const ETKINLIK_ESLEME = [
|
|
1251
|
+
[/kısa ?sınav|quiz/iu, "Kısa Sınav Hazırlık"],
|
|
1252
|
+
[/ara ?sınav|vize|midterm/iu, "Ara Sınav Hazırlık"],
|
|
1253
|
+
[/yarıyıl sonu|final|dönem sonu|genel sınav/iu, "Yarıyıl Sonu Sınavı Hazırlık"],
|
|
1254
|
+
[/ödev|homework|assignment/iu, "Ödev"],
|
|
1255
|
+
[/proje|project/iu, "Proje"],
|
|
1256
|
+
[/laboratuvar|\blab\b|uygulama|practice/iu, "Uygulama / Laboratuvar Hazırlık"],
|
|
1257
|
+
[/sunum|seminer|presentation/iu, "Sunum Hazırlık"],
|
|
1258
|
+
[/rapor|report/iu, "Rapor Hazırlık"],
|
|
1259
|
+
];
|
|
1260
|
+
/** Değerlendirme kalemlerini iş yükü etkinliklerine çevirir. Eşleşmeyen kalem satır üretmez. */
|
|
1261
|
+
export function etkinliklereCevir(deg) {
|
|
1262
|
+
const harita = new Map();
|
|
1263
|
+
for (const k of deg) {
|
|
1264
|
+
const es = ETKINLIK_ESLEME.find(([re]) => re.test(k.ad));
|
|
1265
|
+
if (!es)
|
|
1266
|
+
continue;
|
|
1267
|
+
const sayi = Math.max(1, Math.round(k.sayi ?? 1));
|
|
1268
|
+
const agirlik = k.katki && k.katki > 0 ? k.katki : 1;
|
|
1269
|
+
const onceki = harita.get(es[1]);
|
|
1270
|
+
if (onceki) {
|
|
1271
|
+
onceki.sayi += sayi;
|
|
1272
|
+
onceki.agirlik += agirlik;
|
|
1273
|
+
onceki.kaynak += `, ${k.ad}`;
|
|
1274
|
+
}
|
|
1275
|
+
else
|
|
1276
|
+
harita.set(es[1], { ad: es[1], sayi, agirlik, kaynak: `Değerlendirme kalemi: ${k.ad}` });
|
|
1277
|
+
}
|
|
1278
|
+
return [...harita.values()];
|
|
1279
|
+
}
|
|
1108
1280
|
const IS_YUKU_BICIMLERI = [
|
|
1109
1281
|
{
|
|
1110
1282
|
ad: "Dengeli",
|
|
1111
|
-
aciklama: "
|
|
1112
|
-
|
|
1113
|
-
["Sınıf Dışı Ders Çalışma Süresi", "haftalik", 0.6],
|
|
1114
|
-
["Ara Sınav Hazırlık", "tek", 0.15],
|
|
1115
|
-
["Yarıyıl Sonu Sınavı Hazırlık", "tek", 0.25],
|
|
1116
|
-
],
|
|
1283
|
+
aciklama: "Kalan saatin %60'ı haftalık çalışmaya, kalanı değerlendirme etkinliklerine.",
|
|
1284
|
+
haftalikPay: 0.6,
|
|
1117
1285
|
},
|
|
1118
1286
|
{
|
|
1119
|
-
ad: "
|
|
1120
|
-
aciklama: "
|
|
1121
|
-
|
|
1122
|
-
["Sınıf Dışı Ders Çalışma Süresi", "haftalik", 0.45],
|
|
1123
|
-
["Ödev / Proje", "tek", 0.25],
|
|
1124
|
-
["Ara Sınav Hazırlık", "tek", 0.1],
|
|
1125
|
-
["Yarıyıl Sonu Sınavı Hazırlık", "tek", 0.2],
|
|
1126
|
-
],
|
|
1287
|
+
ad: "Etkinlik ağırlıklı",
|
|
1288
|
+
aciklama: "Kalan saatin %45'i haftalık çalışmaya, kalanı değerlendirme etkinliklerine.",
|
|
1289
|
+
haftalikPay: 0.45,
|
|
1127
1290
|
},
|
|
1128
1291
|
{
|
|
1129
|
-
ad: "
|
|
1130
|
-
aciklama: "
|
|
1131
|
-
|
|
1132
|
-
["Sınıf Dışı Ders Çalışma Süresi", "haftalik", 0.5],
|
|
1133
|
-
["Ara Sınav Hazırlık", "tek", 0.2],
|
|
1134
|
-
["Yarıyıl Sonu Sınavı Hazırlık", "tek", 0.3],
|
|
1135
|
-
],
|
|
1292
|
+
ad: "Haftalık çalışma ağırlıklı",
|
|
1293
|
+
aciklama: "Kalan saatin %75'i haftalık çalışmaya, kalanı değerlendirme etkinliklerine.",
|
|
1294
|
+
haftalikPay: 0.75,
|
|
1136
1295
|
},
|
|
1137
1296
|
];
|
|
1138
|
-
/**
|
|
1139
|
-
|
|
1297
|
+
/**
|
|
1298
|
+
* İş yükü dağılım önerileri (deterministik). Satırlar yalnız ders süresi, haftalık çalışma ve verilen değerlendirme
|
|
1299
|
+
* kalemlerinden kurulur. Kurum değeri (bolen) verildiyse toplam tam AKTS × bolen olmaya çalışılır; verilmediyse
|
|
1300
|
+
* AKTS × 30 hedeflenir ve AKTS başına 25-30 saat aralığı kabul edilir.
|
|
1301
|
+
*/
|
|
1302
|
+
export function isYukuDagilimlari(akts, tul, hafta = 14, bolen, degerlendirme) {
|
|
1140
1303
|
const saat = tulSaati(tul);
|
|
1141
1304
|
if (saat === null)
|
|
1142
1305
|
throw new AracHatasi("T+U+L '2+1+0' biçiminde olmalı.");
|
|
1143
|
-
const hedef = Math.round(akts * bolen);
|
|
1306
|
+
const hedef = Math.round(akts * (bolen ?? 30));
|
|
1307
|
+
const aralik = bolen ? [hedef, hedef] : [Math.round(akts * 25), hedef];
|
|
1144
1308
|
const dersSuresi = hafta * saat;
|
|
1145
1309
|
const kalan = hedef - dersSuresi;
|
|
1146
1310
|
if (kalan < 0)
|
|
1147
|
-
throw new AracHatasi(`Ders süresi (${hafta} x ${saat} = ${dersSuresi} saat) hedefi (${
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1311
|
+
throw new AracHatasi(`Ders süresi (${hafta} x ${saat} = ${dersSuresi} saat) hedefi (${hedef} saat) aşıyor. AKTS ya da T+U+L bilgisini bölüm başkanlığıyla denetleyin; AKTS'yi siz değiştirmeyin.`);
|
|
1312
|
+
const etkinlikler = etkinliklereCevir(degerlendirme ?? []);
|
|
1313
|
+
const bicimler = etkinlikler.length
|
|
1314
|
+
? IS_YUKU_BICIMLERI
|
|
1315
|
+
: [
|
|
1316
|
+
{
|
|
1317
|
+
ad: "Yalnız haftalık çalışma",
|
|
1318
|
+
aciklama: "Değerlendirme kalemi verilmediği için etkinlik satırı eklenmedi.",
|
|
1319
|
+
haftalikPay: 1,
|
|
1320
|
+
},
|
|
1321
|
+
];
|
|
1322
|
+
return bicimler.map((b, i) => {
|
|
1323
|
+
const satirlar = [
|
|
1324
|
+
{ ad: "Ders Süresi", sayi: hafta, sure: saat, kaynak: "sayfa (T+U+L)", varsayim: `${hafta} hafta x T+U+L` },
|
|
1325
|
+
];
|
|
1326
|
+
const haftalik = Math.floor((kalan * b.haftalikPay) / hafta);
|
|
1327
|
+
if (haftalik > 0)
|
|
1328
|
+
satirlar.push({
|
|
1329
|
+
ad: "Sınıf Dışı Ders Çalışma Süresi",
|
|
1330
|
+
sayi: hafta,
|
|
1331
|
+
sure: haftalik,
|
|
1332
|
+
kaynak: "Akredit önerisi",
|
|
1333
|
+
varsayim: `Kalan saatin yaklaşık %${Math.round(b.haftalikPay * 100)}'i haftalık çalışmaya`,
|
|
1334
|
+
});
|
|
1335
|
+
let dagitilan = haftalik * hafta;
|
|
1336
|
+
const toplamAgirlik = etkinlikler.reduce((t, e) => t + e.agirlik, 0);
|
|
1337
|
+
const etkinlikSaati = kalan - dagitilan;
|
|
1338
|
+
etkinlikler.forEach((e, j) => {
|
|
1339
|
+
const son = j === etkinlikler.length - 1;
|
|
1340
|
+
const pay = son ? kalan - dagitilan : Math.round((etkinlikSaati * e.agirlik) / toplamAgirlik);
|
|
1341
|
+
const sure = Math.floor(pay / e.sayi);
|
|
1342
|
+
if (sure <= 0)
|
|
1343
|
+
return;
|
|
1344
|
+
satirlar.push({
|
|
1345
|
+
ad: e.ad,
|
|
1346
|
+
sayi: e.sayi,
|
|
1347
|
+
sure,
|
|
1348
|
+
kaynak: e.kaynak,
|
|
1349
|
+
varsayim: "Saat, değerlendirmedeki katkı yüzdesiyle orantılı (Akredit önerisi)",
|
|
1350
|
+
});
|
|
1351
|
+
dagitilan += sure * e.sayi;
|
|
1165
1352
|
});
|
|
1166
1353
|
const toplam = satirlar.reduce((t, x) => t + x.sayi * x.sure, 0);
|
|
1167
1354
|
return {
|
|
1168
1355
|
secenek: i + 1,
|
|
1169
1356
|
ad: b.ad,
|
|
1170
1357
|
aciklama: b.aciklama,
|
|
1358
|
+
etiket: "öneri",
|
|
1171
1359
|
isyuku: satirlar,
|
|
1172
1360
|
toplam,
|
|
1173
1361
|
hedef,
|
|
1362
|
+
kabulAraligi: aralik,
|
|
1174
1363
|
tamEsit: toplam === hedef,
|
|
1364
|
+
aralikta: toplam >= aralik[0] && toplam <= aralik[1],
|
|
1175
1365
|
};
|
|
1176
1366
|
});
|
|
1177
1367
|
}
|
|
1178
1368
|
const isYukuOnerAraci = arac({
|
|
1179
1369
|
ad: "is_yuku_oner",
|
|
1180
1370
|
baslik: "AKTS iş yükü önerisi",
|
|
1181
|
-
aciklama: "AKTS
|
|
1182
|
-
sonrakiAdim: "
|
|
1371
|
+
aciklama: "AKTS, T+U+L ve sayfadaki değerlendirme kalemlerinden iş yükü dağılımı ÖNERİR. Yalnız derste olan etkinliklere satır açar (ödev yoksa ödev satırı yok); her satırda kaynak ve varsayım yazar. Sayfadan okunan iş yükü verilirse ayrı gösterir, öneriyle karıştırmaz. Hesap deterministiktir; AKTS'yi değiştirmez.",
|
|
1372
|
+
sonrakiAdim: "Önce sayfadan okunan iş yükü varsa onu gösterin. Sonra önerileri 'öneri' etiketiyle 1, 2, 3 diye kısa gösterin (1 önerilen), satırların kaynak ve varsayımını yazın. Değerlendirme kalemleri bilinmiyorsa önce hocaya sorun. Seçileni bologna_taslak_denetle ile denetleyin.",
|
|
1373
|
+
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
1183
1374
|
girdi: z.object({
|
|
1184
1375
|
akts: z.number().min(1).max(60),
|
|
1185
1376
|
tul: TUL.describe("ör. '2+1+0'"),
|
|
1186
1377
|
hafta: z.number().int().min(10).max(20).default(14),
|
|
1187
|
-
bolen: z
|
|
1378
|
+
bolen: z
|
|
1379
|
+
.number()
|
|
1380
|
+
.int()
|
|
1381
|
+
.min(20)
|
|
1382
|
+
.max(40)
|
|
1383
|
+
.optional()
|
|
1384
|
+
.describe("Kurumun AKTS başına saati; verilmezse AKTS x 30 hedeflenir, 25-30 aralığı kabul edilir"),
|
|
1385
|
+
degerlendirme: z
|
|
1386
|
+
.array(z.object({
|
|
1387
|
+
ad: z.string().max(200),
|
|
1388
|
+
sayi: z.number().min(0).max(50).nullable().optional(),
|
|
1389
|
+
katki: z.number().min(0).max(100).nullable().optional(),
|
|
1390
|
+
}))
|
|
1391
|
+
.max(20)
|
|
1392
|
+
.optional()
|
|
1393
|
+
.describe("Sayfadaki (ya da hocanın söylediği) değerlendirme kalemleri; yalnız bunlara etkinlik satırı açılır"),
|
|
1394
|
+
sayfaIsYuku: z
|
|
1395
|
+
.array(z.object({ ad: z.string().max(200), sayi: z.number().min(0).max(100), sure: z.number().min(0).max(200) }))
|
|
1396
|
+
.max(30)
|
|
1397
|
+
.optional()
|
|
1398
|
+
.describe("Sayfadan okunan mevcut iş yükü satırları; öneriden ayrı gösterilir"),
|
|
1188
1399
|
}),
|
|
1189
|
-
calistir: ({ akts, tul, hafta, bolen }) => {
|
|
1190
|
-
const secenekler = isYukuDagilimlari(akts, tul, hafta, bolen);
|
|
1400
|
+
calistir: ({ akts, tul, hafta, bolen, degerlendirme, sayfaIsYuku }) => {
|
|
1401
|
+
const secenekler = isYukuDagilimlari(akts, tul, hafta, bolen, degerlendirme);
|
|
1402
|
+
const sayfa = sayfaIsYuku?.length
|
|
1403
|
+
? {
|
|
1404
|
+
satirlar: sayfaIsYuku.map((x) => ({ ...x, toplam: yuvarla(x.sayi * x.sure, 2), kaynak: "sayfa" })),
|
|
1405
|
+
toplam: yuvarla(sayfaIsYuku.reduce((t, x) => t + x.sayi * x.sure, 0), 2),
|
|
1406
|
+
}
|
|
1407
|
+
: null;
|
|
1191
1408
|
return {
|
|
1192
1409
|
sonuc: {
|
|
1193
|
-
hedef:
|
|
1410
|
+
hedef: bolen
|
|
1411
|
+
? `${akts} AKTS x ${bolen} (kurum değeri) = ${Math.round(akts * bolen)} saat`
|
|
1412
|
+
: `${akts} AKTS için ${Math.round(akts * 25)}-${Math.round(akts * 30)} saat (AKTS başına 25-30); öneriler ${Math.round(akts * 30)} saati hedefler`,
|
|
1413
|
+
sayfadanOkunan: sayfa,
|
|
1194
1414
|
secenekler,
|
|
1195
|
-
|
|
1415
|
+
...(degerlendirme?.length
|
|
1416
|
+
? {}
|
|
1417
|
+
: {
|
|
1418
|
+
eksik: "Değerlendirme kalemleri verilmedi: sınav hazırlığı, ödev ya da proje satırı eklenmedi. Sayfadan okuyun ya da hocaya sorun, sonra aracı yeniden çağırın.",
|
|
1419
|
+
}),
|
|
1420
|
+
not: "Önerilen dağılım ile sayfadan okunan değer ayrıdır. Oranlar Akredit önerisidir; hoca teyit eder.",
|
|
1196
1421
|
resmiDayanak: RESMI_DAYANAK,
|
|
1197
1422
|
},
|
|
1198
1423
|
kaynak: [KAYNAK_KURALLAR],
|
|
1199
1424
|
};
|
|
1200
1425
|
},
|
|
1201
1426
|
});
|
|
1427
|
+
// ---------------------------------------------------------------- obs_karsilastir
|
|
1428
|
+
const sadeMetin = (s) => s
|
|
1429
|
+
.replace(/<(script|style)\b[\s\S]*?<\/\1>/giu, " ")
|
|
1430
|
+
.replace(/<[^>]*>/gu, " ")
|
|
1431
|
+
.replace(/ /gu, " ")
|
|
1432
|
+
.toLocaleLowerCase("tr")
|
|
1433
|
+
.replace(/[\s ]+/gu, " ")
|
|
1434
|
+
.trim();
|
|
1435
|
+
/** Beklenen değeri satır satır karşılaştırılabilir parçalara böler (sekme boşluk sayılır; tablo satırı tek parça). */
|
|
1436
|
+
const parcalar = (deger) => deger
|
|
1437
|
+
.split(/\n+/u)
|
|
1438
|
+
.map(sadeMetin)
|
|
1439
|
+
.filter((x) => x.length >= 3);
|
|
1440
|
+
/**
|
|
1441
|
+
* OBS'ye yazılan alanları geri okunan sayfa metniyle karşılaştırır. Ders kodu ya da dönem farklıysa durur:
|
|
1442
|
+
* yanlış derse yazılmış olabilir.
|
|
1443
|
+
*/
|
|
1444
|
+
export function obsKarsilastir(g) {
|
|
1445
|
+
const sayfa = sadeMetin(g.geriOkunan.metin);
|
|
1446
|
+
const nedenler = [];
|
|
1447
|
+
const kod = sadeMetin(g.dersKodu);
|
|
1448
|
+
if (g.geriOkunan.dersKodu !== undefined && sadeMetin(g.geriOkunan.dersKodu) !== kod)
|
|
1449
|
+
nedenler.push(`Geri okunan sayfanın ders kodu (${g.geriOkunan.dersKodu}) beklenenden (${g.dersKodu}) farklı.`);
|
|
1450
|
+
else if (!sayfa.includes(kod))
|
|
1451
|
+
nedenler.push(`Geri okunan sayfada ders kodu (${g.dersKodu}) görünmüyor.`);
|
|
1452
|
+
if (g.donem) {
|
|
1453
|
+
const d = sadeMetin(g.donem);
|
|
1454
|
+
if (g.geriOkunan.donem !== undefined && sadeMetin(g.geriOkunan.donem) !== d)
|
|
1455
|
+
nedenler.push(`Geri okunan sayfanın dönemi (${g.geriOkunan.donem}) beklenenden (${g.donem}) farklı.`);
|
|
1456
|
+
else if (g.geriOkunan.donem === undefined && !sayfa.includes(d))
|
|
1457
|
+
nedenler.push(`Geri okunan sayfada dönem (${g.donem}) görünmüyor.`);
|
|
1458
|
+
}
|
|
1459
|
+
const alanlar = g.beklenen.map((b) => {
|
|
1460
|
+
const p = parcalar(b.deger);
|
|
1461
|
+
const eksik = p.filter((x) => !sayfa.includes(x));
|
|
1462
|
+
return {
|
|
1463
|
+
alan: b.alan,
|
|
1464
|
+
durum: p.length === 0 ? "bos" : eksik.length ? "farkli" : "eslesti",
|
|
1465
|
+
eksikParcalar: eksik.slice(0, 3).map((x) => (x.length > 120 ? `${x.slice(0, 117)}...` : x)),
|
|
1466
|
+
eksikParcaSayisi: eksik.length,
|
|
1467
|
+
};
|
|
1468
|
+
});
|
|
1469
|
+
return {
|
|
1470
|
+
dur: nedenler.length > 0,
|
|
1471
|
+
nedenler,
|
|
1472
|
+
eslesen: alanlar.filter((a) => a.durum === "eslesti").length,
|
|
1473
|
+
farkli: alanlar.filter((a) => a.durum === "farkli").length,
|
|
1474
|
+
alanlar,
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
const obsKarsilastirAraci = arac({
|
|
1478
|
+
ad: "obs_karsilastir",
|
|
1479
|
+
baslik: "OBS'ye yazılanı geri okuyup doğrula",
|
|
1480
|
+
aciklama: "OBS'ye kaydettikten sonra sayfa geri okunur; bu araç beklenen alanları (bologna_kopya_metni'nin obsKarsilastirGirdisi) geri okunan sayfa metniyle karşılaştırır ve farkları döndürür. Ders kodu ya da dönem değişmişse dur der. OBS'ye yazmaz.",
|
|
1481
|
+
sonrakiAdim: "dur true ise yazmayı hemen bırakın ve hocaya nedenini söyleyin. Farklı alan varsa hocaya alan alan gösterin; düzeltmeyi hoca onaylarsa yapın ve yeniden karşılaştırın. Hepsi eşleştiyse 'Kaydedildi ve geri okunarak doğrulandı' deyin.",
|
|
1482
|
+
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
1483
|
+
kunyeAlanlari: ["beklenen"],
|
|
1484
|
+
onAyikla: (g) => {
|
|
1485
|
+
const o = g;
|
|
1486
|
+
const m = o?.geriOkunan?.metin;
|
|
1487
|
+
if (typeof m !== "string")
|
|
1488
|
+
return { girdi: g, atilan: [] };
|
|
1489
|
+
const giris = girisBilgisiAyikla(m);
|
|
1490
|
+
// Görev satırları (Dersi Verenler, Koordinatör) karşılaştırılmaz; kişi verisi taşınmasın diye atılır.
|
|
1491
|
+
const satir = bolognaSatirAyikla(giris.metin);
|
|
1492
|
+
return {
|
|
1493
|
+
girdi: { ...g, geriOkunan: { ...o?.geriOkunan, metin: satir.html } },
|
|
1494
|
+
atilan: giris.atildi ? ["oturum ve parola alanları"] : [],
|
|
1495
|
+
};
|
|
1496
|
+
},
|
|
1497
|
+
girdi: z.object({
|
|
1498
|
+
dersKodu: z.string().trim().min(1).max(30),
|
|
1499
|
+
donem: z.string().trim().max(40).optional(),
|
|
1500
|
+
beklenen: z
|
|
1501
|
+
.array(z.object({ alan: z.string().max(120), deger: z.string().max(20_000) }))
|
|
1502
|
+
.min(1)
|
|
1503
|
+
.max(40),
|
|
1504
|
+
geriOkunan: z.object({
|
|
1505
|
+
metin: z.string().min(20).max(400_000).describe("Kayıttan sonra sayfadan okunan metin ya da HTML"),
|
|
1506
|
+
dersKodu: z.string().trim().max(30).optional().describe("Sayfanın başlığından okunan ders kodu"),
|
|
1507
|
+
donem: z.string().trim().max(40).optional().describe("Sayfadan okunan dönem"),
|
|
1508
|
+
}),
|
|
1509
|
+
}),
|
|
1510
|
+
calistir: (g) => {
|
|
1511
|
+
const sonuc = obsKarsilastir(g);
|
|
1512
|
+
return {
|
|
1513
|
+
sonuc,
|
|
1514
|
+
kaynak: [KOD_KAYNAGI("apps/acik-mcp/src/bologna.ts", "Akredit OBS geri okuma karşılaştırması")],
|
|
1515
|
+
...(sonuc.dur
|
|
1516
|
+
? {
|
|
1517
|
+
sonrakiAdim: "Yazmayı durdurun: ders kodu ya da dönem beklenenden farklı. Hocaya nedenini söyleyin, başka alana yazmayın.",
|
|
1518
|
+
}
|
|
1519
|
+
: {}),
|
|
1520
|
+
};
|
|
1521
|
+
},
|
|
1522
|
+
});
|
|
1202
1523
|
// ---------------------------------------------------------------- bologna_kopya_metni
|
|
1203
1524
|
const SEKME_SIRASI = [
|
|
1204
1525
|
"Ders Bilgileri",
|
|
@@ -1214,14 +1535,41 @@ const kopyaMetniAraci = arac({
|
|
|
1214
1535
|
aciklama: "Hocanın seçtiği alanları OBS sekmesi ve alan adıyla sıralı, alan alan kopyalanacak düz metin olarak verir. Tablolar sekmeyle ayrılır. Toplamları kendisi hesaplar ve kalan eksikleri yazar. OBS'ye yazmaz; kaydı hoca yapar.",
|
|
1215
1536
|
sonrakiAdim: "Metni olduğu gibi, kod bloğu içinde gösterin. Hocaya sorun: 1) OBS'ye yapıştırdım, yeniden denetle 2) bir alanı değiştir 3) bitir.",
|
|
1216
1537
|
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
1217
|
-
kunyeAlanlari:
|
|
1538
|
+
kunyeAlanlari: BOLOGNA_KUNYE_ALANLARI,
|
|
1218
1539
|
girdi: z.object({
|
|
1219
1540
|
ders: DersTemeli,
|
|
1220
1541
|
alanlar: TaslakAlanlari,
|
|
1221
1542
|
programCiktilari: z.array(z.string().max(10)).max(40).optional(),
|
|
1222
|
-
ingilizce: z.boolean().default(
|
|
1543
|
+
ingilizce: z.boolean().default(false).describe("bologna_ayristir'in ingilizce değeri"),
|
|
1544
|
+
oncekiDegerler: z
|
|
1545
|
+
.record(z.string().max(120), z.string().max(20_000))
|
|
1546
|
+
.optional()
|
|
1547
|
+
.describe("OBS'deki şimdiki değerler (alan adı, ör. 'Dersin Amacı (Türkçe)' → değer); önceki/yeni özeti için"),
|
|
1223
1548
|
}),
|
|
1224
|
-
calistir: ({ ders, alanlar, programCiktilari, ingilizce }) => {
|
|
1549
|
+
calistir: ({ ders, alanlar: gelen, programCiktilari, ingilizce, oncekiDegerler }) => {
|
|
1550
|
+
// Yazma paketi kapısı: doğrulanmamış kaynak ve teyitsiz değerlendirme yüzdesi pakete giremez (ilke 1-3).
|
|
1551
|
+
const yazilamayan = [];
|
|
1552
|
+
const kaynaklar = gelen.kaynaklar?.filter((k, i) => {
|
|
1553
|
+
if (kaynakDurumu(k) !== "dogrulanmamis")
|
|
1554
|
+
return true;
|
|
1555
|
+
yazilamayan.push({
|
|
1556
|
+
alan: "Kaynaklar",
|
|
1557
|
+
neden: `${i + 1}. kaynak doğrulanmamış (katalogda bulunmadı ya da hoca beyanı yok); OBS'ye yazılmaz.`,
|
|
1558
|
+
});
|
|
1559
|
+
return false;
|
|
1560
|
+
});
|
|
1561
|
+
const degerlendirmeTeyitli = gelen.degerlendirmeDayanagi === "sayfa" || gelen.degerlendirmeDayanagi === "hoca";
|
|
1562
|
+
if (gelen.degerlendirme && !degerlendirmeTeyitli)
|
|
1563
|
+
yazilamayan.push({
|
|
1564
|
+
alan: "Değerlendirme",
|
|
1565
|
+
neden: "Değerlendirme yöntem ve yüzdeleri teyitsiz (sayfadan okunmadı, hoca söylemedi); OBS'ye yazılmaz. Hocaya sorun.",
|
|
1566
|
+
});
|
|
1567
|
+
const { degerlendirme: _d, kaynaklar: _k, ...kalanAlanlar } = gelen;
|
|
1568
|
+
const alanlar = {
|
|
1569
|
+
...kalanAlanlar,
|
|
1570
|
+
...(gelen.degerlendirme && degerlendirmeTeyitli ? { degerlendirme: gelen.degerlendirme } : {}),
|
|
1571
|
+
...(kaynaklar ? { kaynaklar } : {}),
|
|
1572
|
+
};
|
|
1225
1573
|
const bolumler = [];
|
|
1226
1574
|
const ekle = (sekme, alan, deger) => bolumler.push({ sira: bolumler.length + 1, sekme, alan, deger });
|
|
1227
1575
|
const hesap = bolognaHesabi(ders, alanlar);
|
|
@@ -1269,8 +1617,8 @@ const kopyaMetniAraci = arac({
|
|
|
1269
1617
|
if (alanlar.ska)
|
|
1270
1618
|
ekle("Sür. Kalkınma Amaçları", "Seçilecek amaç numaraları", alanlar.ska.join(", "));
|
|
1271
1619
|
if (alanlar.kaynaklar?.length)
|
|
1272
|
-
ekle("Diğer Kaynaklar", "Kaynaklar (her satır bir künye)", alanlar.kaynaklar.map(
|
|
1273
|
-
if (!bolumler.length)
|
|
1620
|
+
ekle("Diğer Kaynaklar", "Kaynaklar (her satır bir künye)", alanlar.kaynaklar.map(kunyeMetni).join("\n"));
|
|
1621
|
+
if (!bolumler.length && !yazilamayan.length)
|
|
1274
1622
|
throw new AracHatasi("Kopyalanacak alan yok. En az bir alan gönderin.");
|
|
1275
1623
|
const b = alanBulgulari(ders, alanlar, { ingilizce, programCiktilari });
|
|
1276
1624
|
const kalanEksikler = [...b.entries()].flatMap(([alan, xs]) => xs.filter((x) => x.duzey === "hata").map((x) => ({ alan, neden: x.aciklama })));
|
|
@@ -1293,14 +1641,37 @@ const kopyaMetniAraci = arac({
|
|
|
1293
1641
|
...kalanEksikler.map((e) => `- ${ALANLAR[e.alan]?.ad ?? "Genel"}: ${e.neden}`),
|
|
1294
1642
|
]
|
|
1295
1643
|
: []),
|
|
1644
|
+
...(yazilamayan.length
|
|
1645
|
+
? ["", "Yazma paketine alınmayanlar:", ...yazilamayan.map((y) => `- ${y.alan}: ${y.neden}`)]
|
|
1646
|
+
: []),
|
|
1296
1647
|
].join("\n");
|
|
1648
|
+
const numarali = sirali.map((x, i) => ({ ...x, sira: i + 1 }));
|
|
1649
|
+
const normal = (v) => v.replace(/\s+/gu, " ").trim();
|
|
1650
|
+
const degisiklikOzeti = numarali.map((x) => {
|
|
1651
|
+
const onceki = oncekiDegerler?.[x.alan];
|
|
1652
|
+
return {
|
|
1653
|
+
no: x.sira,
|
|
1654
|
+
sekme: x.sekme,
|
|
1655
|
+
alan: x.alan,
|
|
1656
|
+
onceki: onceki === undefined ? null : onceki,
|
|
1657
|
+
yeni: x.deger,
|
|
1658
|
+
degisim: onceki === undefined ? "bilinmiyor" : normal(onceki) === normal(x.deger) ? "aynı" : "değişecek",
|
|
1659
|
+
};
|
|
1660
|
+
});
|
|
1297
1661
|
return {
|
|
1298
1662
|
sonuc: {
|
|
1299
1663
|
metin,
|
|
1300
|
-
bolumler:
|
|
1664
|
+
bolumler: numarali,
|
|
1665
|
+
degisiklikOzeti,
|
|
1666
|
+
yazilamayan,
|
|
1301
1667
|
hesap,
|
|
1302
1668
|
kalanEksikler,
|
|
1303
|
-
|
|
1669
|
+
obsKarsilastirGirdisi: {
|
|
1670
|
+
...(ders.kod ? { dersKodu: ders.kod } : {}),
|
|
1671
|
+
...(ders.donem ? { donem: ders.donem } : {}),
|
|
1672
|
+
beklenen: numarali.map((x) => ({ alan: x.alan, deger: x.deger })),
|
|
1673
|
+
},
|
|
1674
|
+
not: "Biçim denetiminden geçti; içerik teyidi: hoca. Yazmadan önce degisiklikOzeti'ni (önceki/yeni) hocaya alan alan gösterin; hoca bir kez 'kaydet' der. Kayıttan sonra sayfayı geri okuyup obs_karsilastir ile doğrulayın. Alan adları OBS sürümüne göre küçük farklar gösterebilir.",
|
|
1304
1675
|
},
|
|
1305
1676
|
kaynak: [KAYNAK_KURALLAR],
|
|
1306
1677
|
...(kalanEksikler.length
|
|
@@ -1318,4 +1689,5 @@ export const BOLOGNA_ARACLARI = [
|
|
|
1318
1689
|
isYukuOnerAraci,
|
|
1319
1690
|
durumAraci,
|
|
1320
1691
|
kopyaMetniAraci,
|
|
1692
|
+
obsKarsilastirAraci,
|
|
1321
1693
|
];
|