@akredit/acik-mcp 0.5.0 → 0.8.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 +41 -37
- package/dist/apps/acik-mcp/src/araclar.js +12 -7
- package/dist/apps/acik-mcp/src/bologna.d.ts +12 -0
- package/dist/apps/acik-mcp/src/bologna.js +498 -254
- package/dist/apps/acik-mcp/src/genel-ayristir.js +2 -2
- package/dist/apps/acik-mcp/src/http.js +2 -1
- package/dist/apps/acik-mcp/src/kaynak-dogrula.js +1 -1
- package/dist/apps/acik-mcp/src/kisisel-veri.d.ts +4 -3
- package/dist/apps/acik-mcp/src/kisisel-veri.js +5 -0
- package/dist/apps/acik-mcp/src/not-tablosu.d.ts +4 -1
- package/dist/apps/acik-mcp/src/not-tablosu.js +13 -7
- package/dist/apps/acik-mcp/src/promptlar.d.ts +28 -4
- package/dist/apps/acik-mcp/src/promptlar.js +145 -79
- package/dist/apps/acik-mcp/src/sunucu.d.ts +4 -1
- package/dist/apps/acik-mcp/src/sunucu.js +21 -9
- package/dist/apps/acik-mcp/src/temel.d.ts +8 -0
- package/dist/apps/acik-mcp/src/temel.js +32 -0
- package/dist/apps/acik-mcp/src/yardim.js +46 -12
- package/dist/packages/baglayicilar/src/bologna-proliz/ayristir.js +4 -2
- package/dist/packages/cekirdek/src/bologna/taslak.js +8 -2
- package/dist/packages/cekirdek/src/karar/katman.d.ts +2 -2
- package/dist/packages/cekirdek/src/karar/katman.js +2 -2
- package/dist/packages/cekirdek/src/turkce.d.ts +0 -2
- package/dist/packages/cekirdek/src/turkce.js +0 -4
- package/dist/packages/olcme/src/girdi.d.ts +3 -0
- package/dist/packages/olcme/src/girdi.js +55 -0
- package/dist/packages/olcme/src/index.d.ts +1 -0
- package/dist/packages/olcme/src/index.js +1 -0
- package/package.json +7 -1
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
// Akredit kurallarıyla denetler ve bütün sayıları (iş yükü, toplamlar) kendisi hesaplar (ilke 2).
|
|
6
6
|
// Kurallar @akredit/cekirdek taslak ve sayfa denetiminden gelir; resmî ölçüt değildir, öyle etiketlenir.
|
|
7
7
|
// Sunucu adres çekmez (SSRF yok): Bologna sayfası yapıştırılan HTML olarak gelir, ağsız ayrıştırılır.
|
|
8
|
-
// Yetkili
|
|
9
|
-
// yayımlanmış künyedir: yazar adı künye kuralıyla serbesttir
|
|
8
|
+
// Yetkili satırları (Dersi Verenler, Koordinatör) sayfada yayımlanmış görev bilgisidir: olduğu gibi okunur, taşınır
|
|
9
|
+
// ve korunur; araçlar onları değiştirmez. Kaynaklar yayımlanmış künyedir: yazar adı künye kuralıyla serbesttir.
|
|
10
|
+
// Öğrenci ve kimlik verisi (T.C. kimlik no, öğrenci numarası, IBAN) ile giriş bilgisi reddedilir.
|
|
10
11
|
import { dersSayfasiCoz, programCiktilari, tablolar } from "../../../packages/baglayicilar/src/bologna-proliz/ayristir.js";
|
|
11
12
|
import { bolognaDenetle, taslakDenetle, } from "../../../packages/cekirdek/src/index.js";
|
|
12
13
|
import { DOGRULANMADI as DOGRULANMADI_ISARETI } from "../../../packages/cerceveler/src/index.js";
|
|
@@ -14,7 +15,7 @@ import { z } from "zod";
|
|
|
14
15
|
import { GENEL_ALAN_ADLARI, genelAyikla, genelAyristir, ingilizceAlanVar, prolizMetniMi } from "./genel-ayristir.js";
|
|
15
16
|
import { kaynakImzasiDogru } from "./kaynak-dogrula.js";
|
|
16
17
|
import { girisBilgisiAyikla } from "./kisisel-veri.js";
|
|
17
|
-
import { AracHatasi, arac, KOD_KAYNAGI } from "./temel.js";
|
|
18
|
+
import { AracHatasi, arac, KOD_KAYNAGI, PESIN_ONAY_ACIKLAMASI, pesinOnayVar, TARAMA_SECENEKLERI, } from "./temel.js";
|
|
18
19
|
// ---------------------------------------------------------------- Alan modeli
|
|
19
20
|
export const ALAN_KODLARI = [
|
|
20
21
|
"amac",
|
|
@@ -40,7 +41,7 @@ export const ALANLAR = {
|
|
|
40
41
|
ad: "Dersin amacı",
|
|
41
42
|
sekme: DERS_BILGILERI,
|
|
42
43
|
kurallar: [
|
|
43
|
-
"
|
|
44
|
+
"Akredit önerisi, araç denetlemez (hoca bakar): ders adıyla tutarlı olsun; başka dersten kopya olmasın.",
|
|
44
45
|
"K2: 60-120 kelime. İngilizcesi tam çeviri olsun.",
|
|
45
46
|
],
|
|
46
47
|
sinir: { enAzKelime: 60, enCokKelime: 120 },
|
|
@@ -65,7 +66,10 @@ export const ALANLAR = {
|
|
|
65
66
|
yontem: {
|
|
66
67
|
ad: "Yöntem ve teknikler",
|
|
67
68
|
sekme: DERS_BILGILERI,
|
|
68
|
-
kurallar: [
|
|
69
|
+
kurallar: [
|
|
70
|
+
"K2: 40-90 kelime.",
|
|
71
|
+
"Akredit önerisi, araç denetlemez (hoca bakar): başka dersin yöntem metniyle birebir aynı olmasın.",
|
|
72
|
+
],
|
|
69
73
|
sinir: { enAzKelime: 40, enCokKelime: 90 },
|
|
70
74
|
kaliplar: ["Ders [anlatım, tartışma, uygulama, proje] ile yürütülür. [Haftalık etkinlik] konuyu pekiştirir."],
|
|
71
75
|
yasaklar: [ORTAK_YASAK],
|
|
@@ -94,6 +98,7 @@ export const ALANLAR = {
|
|
|
94
98
|
"K4: Her çıktı ölçülebilir bir eylem fiiliyle biter (açıklar, uygular, tasarlar). 'Bilgi sahibi olur', 'öğrenir', 'kavrar' olmaz.",
|
|
95
99
|
"K4: '-ebilme/-abilme' (tasarlayabilme) ve '-ebilir' kalıpları kabul edilir. 'Sahip olur', 'farkında olur', 'haberdar olur', 'vakıf olur' ölçülemez.",
|
|
96
100
|
"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.",
|
|
101
|
+
"K4 (uyarı): 'farkındalık' ifadesi hata sayılmaz ama ölçmesi zordur; gözlenebilir bir eylemle yazın.",
|
|
97
102
|
"İngilizcesi tam çeviri olsun.",
|
|
98
103
|
],
|
|
99
104
|
sinir: { enAzCikti: 4, enCokCikti: 10, onerilenEnAz: 5, onerilenEnCok: 8 },
|
|
@@ -102,7 +107,7 @@ export const ALANLAR = {
|
|
|
102
107
|
"[Yöntem] kullanarak [problem türünü] çözer. (Beceri)",
|
|
103
108
|
"[Ürün] tasarlar ve ekip içinde sunar. (Yetkinlik)",
|
|
104
109
|
],
|
|
105
|
-
yasaklar: ["Ölçülemeyen fiil (bilir, anlar, öğrenir,
|
|
110
|
+
yasaklar: ["Ölçülemeyen fiil (bilir, anlar, öğrenir, kavrar, bilgi sahibi olur)."],
|
|
106
111
|
mcpTasir: true,
|
|
107
112
|
},
|
|
108
113
|
degerlendirme: {
|
|
@@ -166,7 +171,7 @@ export const ALANLAR = {
|
|
|
166
171
|
kurallar: [
|
|
167
172
|
"K12: Her Ö satırı dolu; değerler 1-5 ya da boş.",
|
|
168
173
|
"Yalnız programın resmî P kodları kullanılır; P kodlarını Bologna sayfasından alın, ezberden yazmayın.",
|
|
169
|
-
"
|
|
174
|
+
"K12: En az 10 dolu hücrenin %60'tan fazlası 5 olmasın (şişirilmiş matris); ayrıştırın.",
|
|
170
175
|
"'Tüm' satırına dokunmayın.",
|
|
171
176
|
],
|
|
172
177
|
sinir: { enAz: 1, enCok: 5 },
|
|
@@ -207,7 +212,7 @@ export const ALANLAR = {
|
|
|
207
212
|
ad: "Dersin yetkilileri",
|
|
208
213
|
sekme: "Dersin Yetkilileri",
|
|
209
214
|
kurallar: [
|
|
210
|
-
"
|
|
215
|
+
"Araç denetlemez (kişisel veri taşınmaz; hoca bakar): koordinatör ve dersi veren hocanın kendisidir; başka hocanın adı yazılmaz.",
|
|
211
216
|
"Sayfada yayımlanmış görev bilgisi (Dersi Verenler, Yardımcılar, Koordinatör) olduğu gibi okunur ve korunur.",
|
|
212
217
|
],
|
|
213
218
|
kaliplar: [],
|
|
@@ -228,7 +233,7 @@ const RESMI_DAYANAK = {
|
|
|
228
233
|
durum: DOGRULANMADI_ISARETI,
|
|
229
234
|
not: "Bu kurallar Akredit'in iç kurallarıdır, resmî ölçüt değildir. Kurumunuzun Bologna ya da AKTS yönergesi farklı sınır koyabilir; o yönerge önceliklidir.",
|
|
230
235
|
};
|
|
231
|
-
const SECENEK_KURALI = "Sunucu metin üretmez.
|
|
236
|
+
const SECENEK_KURALI = "Sunucu metin üretmez. Taslağı asistan yazar: isterseniz 2-3 kısa, birbirinden gerçekten farklı aday (A, B, C) yazıp bologna_taslak_denetle ile denetleyin ve geçenlerden en iyisini kendiniz seçin; hocaya alan alan seçenek sormayın, seçileni son özette gösterin. Hocanın vermediği bilgi (kitap, araç, proje adı) uydurulmaz.";
|
|
232
237
|
const KAYNAK_KURALLAR = KOD_KAYNAGI("packages/cekirdek/src/bologna/taslak.ts", "Akredit Bologna taslak kuralları");
|
|
233
238
|
const KAYNAK_SAYFA = KOD_KAYNAGI("packages/cekirdek/src/bologna/denetim.ts", "Akredit Bologna sayfa denetim kuralları");
|
|
234
239
|
const KAYNAK_AYRISTIRICI = KOD_KAYNAGI("packages/baglayicilar/src/bologna-proliz/ayristir.ts", "Akredit Bologna (Proliz) sayfa ayrıştırıcısı");
|
|
@@ -377,6 +382,11 @@ const TaslakAlanlari = z.object({
|
|
|
377
382
|
.enum(["sayfa", "hoca", "oneri"])
|
|
378
383
|
.optional()
|
|
379
384
|
.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."),
|
|
385
|
+
degerlendirmeKaniti: z
|
|
386
|
+
.string()
|
|
387
|
+
.max(2000)
|
|
388
|
+
.optional()
|
|
389
|
+
.describe("'sayfa' ya da 'hoca' dayanağının kanıtı: sayfadaki değerlendirme tablosunun metni ya da hocanın sohbete yazdığı yanıt, olduğu gibi (ör. 'vize 40 final 60'). Her katkı yüzdesi bu metinde geçmelidir; geçmezse yüzde teyitsiz sayılır."),
|
|
380
390
|
kaynaklar: z
|
|
381
391
|
.array(KaynakOgesi)
|
|
382
392
|
.max(30)
|
|
@@ -470,13 +480,11 @@ export function alanBulgulari(ders, alanlar, s) {
|
|
|
470
480
|
aciklama: `${i + 1}. kaynakta yıl görünmüyor. Yılı hocaya sorun ya da kaynak_bicimle ile biçimlendirin.`,
|
|
471
481
|
});
|
|
472
482
|
}
|
|
473
|
-
if (verilen.has("degerlendirme") &&
|
|
474
|
-
alanlar.degerlendirmeDayanagi !== "sayfa" &&
|
|
475
|
-
alanlar.degerlendirmeDayanagi !== "hoca")
|
|
483
|
+
if (verilen.has("degerlendirme") && !degerlendirmeTeyitli(alanlar))
|
|
476
484
|
ekle("degerlendirme", {
|
|
477
485
|
duzey: "uyari",
|
|
478
486
|
kural: "Değerlendirme",
|
|
479
|
-
aciklama: "Değerlendirme yöntem ve yüzdeleri teyitsiz
|
|
487
|
+
aciklama: "Değerlendirme yöntem ve yüzdeleri teyitsiz: dayanak 'sayfa' ya da 'hoca' değil ya da yüzdeler degerlendirmeKaniti metninde geçmiyor. Hocaya tek mesajda sorun, yanıtını degerlendirmeKaniti'ne olduğu gibi yazın; teyitsiz yüzde OBS'ye yazılmaz.",
|
|
480
488
|
kuralTuru: "Akredit önerisi",
|
|
481
489
|
dayanak: "Değişmez ilke 2-3: değerlendirme oranları resmî taahhüttür, uydurulmaz; beyan hocadadır.",
|
|
482
490
|
});
|
|
@@ -734,110 +742,115 @@ function alanDurumlari(d, sayfaBulgulari) {
|
|
|
734
742
|
kural: ALANLAR[alan].kurallar.join(" "),
|
|
735
743
|
}));
|
|
736
744
|
}
|
|
745
|
+
const BolognaDenetleGirdisi = z.object({
|
|
746
|
+
ders: BolognaDersGirdisi,
|
|
747
|
+
programCiktilari: z
|
|
748
|
+
.array(z.object({ kod: z.string().max(20), metin: z.string().max(1000) }))
|
|
749
|
+
.max(40)
|
|
750
|
+
.optional()
|
|
751
|
+
.describe("Program çıktıları; verilirse program düzeyindeki kurallar da çalışır"),
|
|
752
|
+
matrisSatiri: z
|
|
753
|
+
.record(z.string().max(20), z.string().max(5))
|
|
754
|
+
.optional()
|
|
755
|
+
.describe("Program matrisinde bu dersin satırı: program çıktısı kodu ve değer"),
|
|
756
|
+
aktsSaat: z
|
|
757
|
+
.tuple([z.number().min(1), z.number().max(60)])
|
|
758
|
+
.optional()
|
|
759
|
+
.describe("AKTS başına saat aralığı; varsayılan [25, 30]"),
|
|
760
|
+
kurumBoleni: z
|
|
761
|
+
.number()
|
|
762
|
+
.int()
|
|
763
|
+
.min(20)
|
|
764
|
+
.max(40)
|
|
765
|
+
.optional()
|
|
766
|
+
.describe("Kurumun AKTS başına saat değeri; verilirse aralık yerine bu değer kullanılır (taslak denetimiyle aynı)"),
|
|
767
|
+
});
|
|
768
|
+
/** bologna_denetle hesabı. Ayrı işlevdir: bologna_ayristir aynı denetimi gömülü döndürür (tip dökümü gerekmez). */
|
|
769
|
+
const bolognaDenetimi = ({ ders, programCiktilari, matrisSatiri, aktsSaat: aralik, kurumBoleni }, b) => {
|
|
770
|
+
const aktsSaat = kurumBoleni ? [kurumBoleni, kurumBoleni] : aralik;
|
|
771
|
+
const { amac: _a, icerik: _i, yontem: _y, notlar: _n, kaynakSayisi: _k, kaynaklar: _kk, ...sayfaAlanlari } = ders;
|
|
772
|
+
const sayfa = {
|
|
773
|
+
...sayfaAlanlari,
|
|
774
|
+
ad: ders.ad ?? ders.kod,
|
|
775
|
+
bilgiler: {},
|
|
776
|
+
};
|
|
777
|
+
const pkodlar = Object.keys(matrisSatiri ?? {});
|
|
778
|
+
const veri = {
|
|
779
|
+
adres: "kullanici-girdisi",
|
|
780
|
+
okundu: b.simdi().toISOString(),
|
|
781
|
+
programCiktilari: programCiktilari ?? [],
|
|
782
|
+
matris: {
|
|
783
|
+
pkodlar,
|
|
784
|
+
dersler: matrisSatiri
|
|
785
|
+
? [
|
|
786
|
+
{
|
|
787
|
+
kod: ders.kod,
|
|
788
|
+
ad: ders.ad ?? ders.kod,
|
|
789
|
+
zorunlu: ders.zorunlu,
|
|
790
|
+
grup: null,
|
|
791
|
+
yariyil: ders.yariyil,
|
|
792
|
+
grupBasligi: false,
|
|
793
|
+
degerler: matrisSatiri,
|
|
794
|
+
},
|
|
795
|
+
]
|
|
796
|
+
: [],
|
|
797
|
+
},
|
|
798
|
+
tyycIsaret: null,
|
|
799
|
+
dersler: [{ ...sayfa, zorunlu: ders.zorunlu, bos: false, kaynak: "kullanici-girdisi" }],
|
|
800
|
+
};
|
|
801
|
+
let bulgular = bolognaDenetle(veri, { simdi: b.simdi().getTime(), ...(aktsSaat ? { aktsSaat } : {}) }).map((x) => ({
|
|
802
|
+
...x,
|
|
803
|
+
kuralTuru: x.kural === "akts-isyuku" && kurumBoleni ? "kurum" : "Akredit önerisi",
|
|
804
|
+
dayanak: x.kural === "akts-isyuku"
|
|
805
|
+
? kurumBoleni
|
|
806
|
+
? "Kurumun verdiği AKTS başına saat değeri."
|
|
807
|
+
: "AKTS başına 25-30 saat yaygın uygulamadır; resmî metinle DOĞRULANMADI."
|
|
808
|
+
: "Akredit iç kuralı (packages/cekirdek/src/bologna/denetim.ts); resmî dayanağı DOĞRULANMADI.",
|
|
809
|
+
}));
|
|
810
|
+
// Program düzeyi kuralları yalnız program çıktıları verildiyse anlamlıdır.
|
|
811
|
+
if (!programCiktilari)
|
|
812
|
+
bulgular = bulgular.filter((x) => x.konu !== "-");
|
|
813
|
+
const alanlar = alanDurumlari(ders, bulgular);
|
|
814
|
+
const doldurulacak = alanlar.filter((a) => a.durum === "bos" || a.durum === "zayif");
|
|
815
|
+
return {
|
|
816
|
+
sonuc: {
|
|
817
|
+
ders: ders.kod,
|
|
818
|
+
hataSayisi: bulgular.filter((x) => x.duzey === "hata").length,
|
|
819
|
+
uyariSayisi: bulgular.filter((x) => x.duzey === "uyari").length,
|
|
820
|
+
ozet: doldurulacak.length
|
|
821
|
+
? `${doldurulacak.length} alan doldurulmalı ya da iyileştirilmeli: ${doldurulacak.map((a) => `${a.no}) ${a.ad}`).join(", ")}.`
|
|
822
|
+
: "Gönderilen alanlarda boş ya da zayıf alan bulunmadı.",
|
|
823
|
+
alanlar,
|
|
824
|
+
doldurulacak: doldurulacak.map((a) => a.no),
|
|
825
|
+
kalanAlanSayisi: doldurulacak.length,
|
|
826
|
+
dokunulmayacak: ["Dersin Yetkilileri sayfadaki hâliyle korunur; değiştirilmez."],
|
|
827
|
+
bulgular,
|
|
828
|
+
not: "Kurallar Akredit'in iç kurallarıdır, resmî ölçüt değildir. Düzeltme kararını ders sorumlusu ve komisyon verir.",
|
|
829
|
+
},
|
|
830
|
+
kaynak: [KAYNAK_SAYFA, KAYNAK_KURALLAR],
|
|
831
|
+
...(doldurulacak.length
|
|
832
|
+
? {}
|
|
833
|
+
: {
|
|
834
|
+
sonrakiAdim: "Eksik görünmüyor. Bunu tek cümleyle söyleyin ve sormadan kaliteyi artıracak 2-4 somut öneri verin: zayıf ya da kısa alanlar, öğrenme çıktılarında Bloom düzeyi ve ölçülebilirlik, iş yükü ile AKTS uyumu, kaynakların güncelliği, İngilizce alanlar. Altına: 1) önerileri uygulayalım (önerilen) 2) sıradaki derse geç 3) bitir.",
|
|
835
|
+
}),
|
|
836
|
+
};
|
|
837
|
+
};
|
|
737
838
|
export const bolognaDenetleAraci = arac({
|
|
738
839
|
ad: "bologna_denetle",
|
|
739
840
|
baslik: "Bologna ders paketini denetle",
|
|
740
841
|
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.",
|
|
741
|
-
sonrakiAdim: "
|
|
842
|
+
sonrakiAdim: "Nasıl doldurulacağını hocaya sormayın; doğrudan akıllı doldurun. Sayfada değerlendirme yüzdeleri ya da kaynaklar yoksa yalnız bunları TEK mesajda sorun (yüzdeyi tahmin etmeyin). Gerisini sormadan bologna_alan_rehberi(alan='hepsi'), is_yuku_oner ve bologna_taslak_denetle ile doldurup denetleyin; sonunda bologna_kopya_metni özetini tek seferde gösterin.",
|
|
742
843
|
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
743
844
|
kunyeAlanlari: BOLOGNA_KUNYE_ALANLARI,
|
|
744
|
-
girdi:
|
|
745
|
-
|
|
746
|
-
programCiktilari: z
|
|
747
|
-
.array(z.object({ kod: z.string().max(20), metin: z.string().max(1000) }))
|
|
748
|
-
.max(40)
|
|
749
|
-
.optional()
|
|
750
|
-
.describe("Program çıktıları; verilirse program düzeyindeki kurallar da çalışır"),
|
|
751
|
-
matrisSatiri: z
|
|
752
|
-
.record(z.string().max(20), z.string().max(5))
|
|
753
|
-
.optional()
|
|
754
|
-
.describe("Program matrisinde bu dersin satırı: program çıktısı kodu ve değer"),
|
|
755
|
-
aktsSaat: z
|
|
756
|
-
.tuple([z.number().min(1), z.number().max(60)])
|
|
757
|
-
.optional()
|
|
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ı)"),
|
|
766
|
-
}),
|
|
767
|
-
calistir: ({ ders, programCiktilari, matrisSatiri, aktsSaat: aralik, kurumBoleni }, b) => {
|
|
768
|
-
const aktsSaat = kurumBoleni ? [kurumBoleni, kurumBoleni] : aralik;
|
|
769
|
-
const { amac: _a, icerik: _i, yontem: _y, notlar: _n, kaynakSayisi: _k, kaynaklar: _kk, ...sayfaAlanlari } = ders;
|
|
770
|
-
const sayfa = {
|
|
771
|
-
...sayfaAlanlari,
|
|
772
|
-
ad: ders.ad ?? ders.kod,
|
|
773
|
-
bilgiler: {},
|
|
774
|
-
};
|
|
775
|
-
const pkodlar = Object.keys(matrisSatiri ?? {});
|
|
776
|
-
const veri = {
|
|
777
|
-
adres: "kullanici-girdisi",
|
|
778
|
-
okundu: b.simdi().toISOString(),
|
|
779
|
-
programCiktilari: programCiktilari ?? [],
|
|
780
|
-
matris: {
|
|
781
|
-
pkodlar,
|
|
782
|
-
dersler: matrisSatiri
|
|
783
|
-
? [
|
|
784
|
-
{
|
|
785
|
-
kod: ders.kod,
|
|
786
|
-
ad: ders.ad ?? ders.kod,
|
|
787
|
-
zorunlu: ders.zorunlu,
|
|
788
|
-
grup: null,
|
|
789
|
-
yariyil: ders.yariyil,
|
|
790
|
-
grupBasligi: false,
|
|
791
|
-
degerler: matrisSatiri,
|
|
792
|
-
},
|
|
793
|
-
]
|
|
794
|
-
: [],
|
|
795
|
-
},
|
|
796
|
-
tyycIsaret: null,
|
|
797
|
-
dersler: [{ ...sayfa, zorunlu: ders.zorunlu, bos: false, kaynak: "kullanici-girdisi" }],
|
|
798
|
-
};
|
|
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
|
-
}));
|
|
808
|
-
// Program düzeyi kuralları yalnız program çıktıları verildiyse anlamlıdır.
|
|
809
|
-
if (!programCiktilari)
|
|
810
|
-
bulgular = bulgular.filter((x) => x.konu !== "-");
|
|
811
|
-
const alanlar = alanDurumlari(ders, bulgular);
|
|
812
|
-
const doldurulacak = alanlar.filter((a) => a.durum === "bos" || a.durum === "zayif");
|
|
813
|
-
return {
|
|
814
|
-
sonuc: {
|
|
815
|
-
ders: ders.kod,
|
|
816
|
-
hataSayisi: bulgular.filter((x) => x.duzey === "hata").length,
|
|
817
|
-
uyariSayisi: bulgular.filter((x) => x.duzey === "uyari").length,
|
|
818
|
-
ozet: doldurulacak.length
|
|
819
|
-
? `${doldurulacak.length} alan doldurulmalı ya da iyileştirilmeli: ${doldurulacak.map((a) => `${a.no}) ${a.ad}`).join(", ")}.`
|
|
820
|
-
: "Gönderilen alanlarda boş ya da zayıf alan bulunmadı.",
|
|
821
|
-
alanlar,
|
|
822
|
-
doldurulacak: doldurulacak.map((a) => a.no),
|
|
823
|
-
kalanAlanSayisi: doldurulacak.length,
|
|
824
|
-
dokunulmayacak: ["Dersin Yetkilileri kişi adı içerir; hoca OBS'de kendisi yazar."],
|
|
825
|
-
bulgular,
|
|
826
|
-
not: "Kurallar Akredit'in iç kurallarıdır, resmî ölçüt değildir. Düzeltme kararını ders sorumlusu ve komisyon verir.",
|
|
827
|
-
},
|
|
828
|
-
kaynak: [KAYNAK_SAYFA, KAYNAK_KURALLAR],
|
|
829
|
-
...(doldurulacak.length
|
|
830
|
-
? {}
|
|
831
|
-
: { sonrakiAdim: "Hocaya sorun: 1) başka bir ders 2) OBS'ye kopya metni (bologna_kopya_metni) 3) bitir." }),
|
|
832
|
-
};
|
|
833
|
-
},
|
|
845
|
+
girdi: BolognaDenetleGirdisi,
|
|
846
|
+
calistir: bolognaDenetimi,
|
|
834
847
|
});
|
|
835
848
|
// ---------------------------------------------------------------- bologna_alan_rehberi
|
|
836
849
|
const alanRehberi = arac({
|
|
837
850
|
ad: "bologna_alan_rehberi",
|
|
838
851
|
baslik: "Bologna alanı nasıl doldurulur",
|
|
839
852
|
aciklama: "Bir Bologna alanının (amaç, içerik, çıktılar, iş yükü, haftalar...) doldurma kurallarını, sınırlarını, örnek kalıplarını ve Bloom fiillerini verir. AKTS ve T+U+L verilirse iş yükü hedefini hesaplar. Metin üretmez.",
|
|
840
|
-
sonrakiAdim: "
|
|
853
|
+
sonrakiAdim: "Doldurulacak bütün alanların taslağını bu kurallarla tek seferde yazın ve bologna_taslak_denetle ile denetleyin. Ara adımda hocaya alan alan A/B/C sormayın; en iyi seçeneği kendiniz seçin.",
|
|
841
854
|
girdi: z.object({
|
|
842
855
|
alan: z.enum([...ALAN_KODLARI, "hepsi"]).describe("Alan kodu ya da 'hepsi'"),
|
|
843
856
|
akts: z.number().min(0).max(60).optional(),
|
|
@@ -899,7 +912,7 @@ const taslakDenetleAraci = arac({
|
|
|
899
912
|
ad: "bologna_taslak_denetle",
|
|
900
913
|
baslik: "Bologna taslağını denetle",
|
|
901
914
|
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: "
|
|
915
|
+
sonrakiAdim: "Geçmeyen alanı düzeltip yeniden denetleyin; hocaya göstermeyin. Hepsi geçince bologna_durum ve bologna_kopya_metni'ni çağırıp bütün alanları TEK özet tablosunda ('öneri' etiketiyle) gösterin; alan alan soru sormayın.",
|
|
903
916
|
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
904
917
|
kunyeAlanlari: BOLOGNA_KUNYE_ALANLARI,
|
|
905
918
|
girdi: z.object({
|
|
@@ -1048,11 +1061,45 @@ export function ayristirOnAyikla(g) {
|
|
|
1048
1061
|
...ek,
|
|
1049
1062
|
};
|
|
1050
1063
|
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Ayrıştırmanın hemen ardından denetim: tarama sırasında ders başına iki çağrı yerine bir çağrı yeter.
|
|
1066
|
+
* Girdi denetimin şemasından geçmezse (sayfa eksik okunduysa) denetim atlanır, asistan bologna_denetle'yi çağırır.
|
|
1067
|
+
*/
|
|
1068
|
+
function gomuluDenetim(ders, b) {
|
|
1069
|
+
const g = BolognaDenetleGirdisi.safeParse({ ders });
|
|
1070
|
+
if (!g.success)
|
|
1071
|
+
return undefined;
|
|
1072
|
+
const s = bolognaDenetimi(g.data, b).sonuc;
|
|
1073
|
+
const d = g.data.ders;
|
|
1074
|
+
return {
|
|
1075
|
+
ozet: s.ozet,
|
|
1076
|
+
kalanAlanSayisi: s.kalanAlanSayisi,
|
|
1077
|
+
doldurulacak: s.alanlar
|
|
1078
|
+
.filter((a) => s.doldurulacak.includes(a.no))
|
|
1079
|
+
.map((a) => ({ no: a.no, ad: a.ad, durum: a.durum })),
|
|
1080
|
+
// Yalnız hocanın bilebileceği resmî bilgi sorulur; sayfada varsa sorulmaz.
|
|
1081
|
+
teyitSorulacak: [
|
|
1082
|
+
...(d.degerlendirme.length ? [] : ["Değerlendirme yöntem ve yüzdeleri"]),
|
|
1083
|
+
...(d.kaynaklar?.length || d.kaynakSayisi ? [] : ["Kullanılan kaynaklar"]),
|
|
1084
|
+
],
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
/** Ders sayfası okunduktan sonra tarayıcıda yapılacaklar; model bu turlarda sunucuya uğramaz. */
|
|
1088
|
+
const AYRISTIRMA_SONRASI = [
|
|
1089
|
+
"ADIM 1: Hocaya soru sormayın. Tarama planında ders kaldıysa ders listesine dönüp sıradakini açın.",
|
|
1090
|
+
"ADIM 2: Plan bittiyse bütün derslerin denetim özetinden TEK rapor yazın (obs_ders_listesi'nin raporBicimi); sayfada olmayan değerlendirme yüzdeleri ve kaynakları aynı mesajda sorun.",
|
|
1091
|
+
"ADIM 3: Hoca seçince taslakları bologna_alan_rehberi ve bologna_taslak_denetle ile sormadan hazırlayın; sonra bologna_kopya_metni (birden çok derste bologna_toplu_ozet) ile tek özet ve tek kayıt onayı.",
|
|
1092
|
+
"Peşin onay varsa ADIM 1-3 yerine obs_ders_listesi'nin peşin onay adımlarını izleyin: rapor durağı ve teyit sorusu yoktur.",
|
|
1093
|
+
];
|
|
1094
|
+
const gomuluDenetimAlani = (ders, b) => {
|
|
1095
|
+
const d = gomuluDenetim(ders, b);
|
|
1096
|
+
return d ? { denetim: d } : {};
|
|
1097
|
+
};
|
|
1051
1098
|
const ayristirAraci = arac({
|
|
1052
1099
|
ad: "bologna_ayristir",
|
|
1053
1100
|
baslik: "Bologna sayfasını ayrıştır",
|
|
1054
1101
|
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.",
|
|
1055
|
-
sonrakiAdim: "
|
|
1102
|
+
sonrakiAdim: "Yanıttaki denetim alanı varsa bologna_denetle'yi ayrıca çağırmayın: eksikler (denetim.doldurulacak) ve teyit sorulacaklar (denetim.teyitSorulacak) hazırdır. Denetim alanı yoksa ders verisini bologna_denetle'ye verin (girdi hazır: bolognaDenetleGirdisi). Sonraki çağrılarda ingilizce değerini aynen verin. Sayfada değerlendirme yüzdeleri ya da kaynaklar yoksa yalnız bunları hocaya TEK mesajda sorun; yüzdeyi tahmin etmeyin. Uygulama/lab T+U+L'den okunur (U+L > 0 ise var), sorulmaz. Amaç ve içerik sayfada yoksa önden sorulmaz; 'öneri' olarak yazılıp son özette teyide sunulur.",
|
|
1056
1103
|
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
1057
1104
|
kunyeAlanlari: BOLOGNA_KUNYE_ALANLARI,
|
|
1058
1105
|
onAyikla: ayristirOnAyikla,
|
|
@@ -1072,7 +1119,7 @@ const ayristirAraci = arac({
|
|
|
1072
1119
|
.optional()
|
|
1073
1120
|
.describe("Sunucu doldurur; göndermeyin"),
|
|
1074
1121
|
}),
|
|
1075
|
-
calistir: ({ html, metin, kaynaklar = [], yetkililer = [], tur, sayfaIpucu }) => {
|
|
1122
|
+
calistir: ({ html, metin, kaynaklar = [], yetkililer = [], tur, sayfaIpucu }, b) => {
|
|
1076
1123
|
if (!html && !metin)
|
|
1077
1124
|
throw new AracHatasi("Sayfanın HTML'ini ya da metnini yapıştırın.");
|
|
1078
1125
|
const kaynakAlani = {
|
|
@@ -1128,10 +1175,12 @@ const ayristirAraci = arac({
|
|
|
1128
1175
|
guven: "yüksek",
|
|
1129
1176
|
ingilizce: sayfaIpucu?.ingilizce ?? ingilizceAlanVar(html),
|
|
1130
1177
|
bolognaDenetleGirdisi: { ders },
|
|
1178
|
+
...gomuluDenetimAlani(ders, b),
|
|
1179
|
+
tarayiciEylemPlani: AYRISTIRMA_SONRASI,
|
|
1131
1180
|
bulunamayan: bulunamayan.map((k) => GENEL_ALAN_ADLARI[k] ?? k),
|
|
1132
1181
|
okunamayan: bulunamayan.length
|
|
1133
1182
|
? [
|
|
1134
|
-
"Bu alanlar sayfada görünmedi.
|
|
1183
|
+
"Bu alanlar sayfada görünmedi. Tarayıcı aracınız varsa OBS düzenleme ekranından okuyun. Yoksa önden sormayın: kaynaklar teyit sorusuyla birlikte sorulur, öbürleri 'öneri' olarak yazılıp son özette teyide sunulur.",
|
|
1135
1184
|
]
|
|
1136
1185
|
: [],
|
|
1137
1186
|
},
|
|
@@ -1155,13 +1204,15 @@ const ayristirAraci = arac({
|
|
|
1155
1204
|
guven: g.guven,
|
|
1156
1205
|
ingilizce: sayfaIpucu?.ingilizce ?? ingilizceAlanVar(ham),
|
|
1157
1206
|
bolognaDenetleGirdisi: { ders },
|
|
1207
|
+
...gomuluDenetimAlani(ders, b),
|
|
1208
|
+
tarayiciEylemPlani: AYRISTIRMA_SONRASI,
|
|
1158
1209
|
bilgiler: g.bilgiler,
|
|
1159
1210
|
haftaKonulari,
|
|
1160
1211
|
bulunan: g.bulunan.map((k) => GENEL_ALAN_ADLARI[k] ?? k),
|
|
1161
1212
|
bulunamayan: g.bulunamayan.map((k) => GENEL_ALAN_ADLARI[k] ?? k),
|
|
1162
1213
|
not: g.guven === "yüksek"
|
|
1163
|
-
? "Genel ayrıştırıcı alanların çoğunu buldu.
|
|
1164
|
-
: "Genel ayrıştırıcı bazı alanları bulamadı. Bulunanları
|
|
1214
|
+
? "Genel ayrıştırıcı alanların çoğunu buldu. Okunan değerleri son özette 'sayfa' dayanağıyla gösterin; hoca orada doğrular."
|
|
1215
|
+
: "Genel ayrıştırıcı bazı alanları bulamadı. Bulunanları son özette gösterin; bulunamayanları tarayıcıyla OBS düzenleme ekranından okuyun ya da 'öneri' olarak yazıp son özette teyide sunun. Yalnız değerlendirme ve kaynaklar hocaya sorulur.",
|
|
1165
1216
|
},
|
|
1166
1217
|
kaynak: [KOD_KAYNAGI("apps/acik-mcp/src/genel-ayristir.ts", "Akredit genel Bologna ayrıştırıcısı")],
|
|
1167
1218
|
};
|
|
@@ -1183,8 +1234,8 @@ const ZORUNLU_ALANLAR = [
|
|
|
1183
1234
|
const durumAraci = arac({
|
|
1184
1235
|
ad: "bologna_durum",
|
|
1185
1236
|
baslik: "Bologna paketi ne kadar tamam",
|
|
1186
|
-
aciklama: "
|
|
1187
|
-
sonrakiAdim: "Kalan
|
|
1237
|
+
aciklama: "Hazırlanan taslak alanların bütününe bakar: hangi zorunlu alan tamam, hangisi eksik ya da kurala uymuyor, kaç alan kaldı. Eksik kalmadıysa biçim denetiminden geçti der. Bütün alanlar yazılınca, OBS'ye yazmadan önce çağrılır.",
|
|
1238
|
+
sonrakiAdim: "Kalan alanları hocaya sormadan tamamlayın ya da düzeltin (bologna_alan_rehberi, bologna_taslak_denetle) ve yeniden denetleyin; ara rapor vermeyin. Yalnız hocanın bilebileceği resmî bilgi (değerlendirme, kullanılan kaynak) eksikse onu TEK mesajda sorun. Kalan yoksa OBS'ye yazma adımına geçin.",
|
|
1188
1239
|
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
1189
1240
|
kunyeAlanlari: BOLOGNA_KUNYE_ALANLARI,
|
|
1190
1241
|
girdi: z.object({
|
|
@@ -1212,9 +1263,7 @@ const durumAraci = arac({
|
|
|
1212
1263
|
const kalan = satirlar.filter((s) => s.durum !== "tamam");
|
|
1213
1264
|
const tamamlandi = kalan.length === 0 && genel.length === 0;
|
|
1214
1265
|
const teyitBekleyen = [
|
|
1215
|
-
...(alanlar.degerlendirme && alanlar
|
|
1216
|
-
? ["Değerlendirme yöntem ve yüzdeleri"]
|
|
1217
|
-
: []),
|
|
1266
|
+
...(alanlar.degerlendirme && !degerlendirmeTeyitli(alanlar) ? ["Değerlendirme yöntem ve yüzdeleri"] : []),
|
|
1218
1267
|
];
|
|
1219
1268
|
return {
|
|
1220
1269
|
sonuc: {
|
|
@@ -1236,7 +1285,7 @@ const durumAraci = arac({
|
|
|
1236
1285
|
kaynak: [KAYNAK_KURALLAR],
|
|
1237
1286
|
...(tamamlandi
|
|
1238
1287
|
? {
|
|
1239
|
-
sonrakiAdim: "bologna_kopya_metni ile yazma paketini alın (sayfadaki önceki değerleri oncekiDegerler olarak verin).
|
|
1288
|
+
sonrakiAdim: "Hocaya ara özet gösterip 'devam' beklemeyin; hemen bologna_kopya_metni ile yazma paketini alın (sayfadaki önceki değerleri oncekiDegerler olarak verin). Tek mesajda önceki → yeni tablosunu gösterip sorun: 1) OBS'ye yaz ve kaydet (bu seçim 'kaydet' onayıdır) 2) formu doldur, Kaydet'e ben basayım 3) alan alan kopya metni ver. Değiştirmek istenen alan varsa numarası yazılır.",
|
|
1240
1289
|
}
|
|
1241
1290
|
: {}),
|
|
1242
1291
|
};
|
|
@@ -1369,7 +1418,7 @@ const isYukuOnerAraci = arac({
|
|
|
1369
1418
|
ad: "is_yuku_oner",
|
|
1370
1419
|
baslik: "AKTS iş yükü önerisi",
|
|
1371
1420
|
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: "
|
|
1421
|
+
sonrakiAdim: "Sayfadaki iş yükü AKTS hedef aralığındaysa onu koruyun. Değilse ilk öneriyi ('öneri' etiketiyle) doğrudan taslağa alıp bologna_taslak_denetle ile denetleyin; hocaya ayrı mesajda iş yükü seçeneği sormayın, son özette varsayımıyla gösterin. Değerlendirme kalemleri bilinmiyorsa uydurmayın.",
|
|
1373
1422
|
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
1374
1423
|
girdi: z.object({
|
|
1375
1424
|
akts: z.number().min(1).max(60),
|
|
@@ -1478,7 +1527,7 @@ const obsKarsilastirAraci = arac({
|
|
|
1478
1527
|
ad: "obs_karsilastir",
|
|
1479
1528
|
baslik: "OBS'ye yazılanı geri okuyup doğrula",
|
|
1480
1529
|
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
|
|
1530
|
+
sonrakiAdim: "dur true ise yazmayı hemen bırakın, toplu kayıttaysa sonraki derslere de geçmeyin ve hocaya nedenini söyleyin. Farklı alan varsa önce sormadan bir kez toparlayın: o sekmenin Kaydet'ine basılmamışsa basın ya da onaylanan AYNI değeri yeniden yazın, sonra geri okuyup yeniden karşılaştırın. Metni kısaltmak ya da değiştirmek gerekiyorsa bu yeni içeriktir: hocaya alan alan gösterip onay alın. Hepsi eşleştiyse 'Kaydedildi ve geri okunarak doğrulandı' deyin ve toplu kayıttaysa sormadan sonraki derse geçin. Onaya Gönder'e hoca basar.",
|
|
1482
1531
|
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
1483
1532
|
kunyeAlanlari: ["beklenen"],
|
|
1484
1533
|
onAyikla: (g) => {
|
|
@@ -1512,6 +1561,7 @@ const obsKarsilastirAraci = arac({
|
|
|
1512
1561
|
return {
|
|
1513
1562
|
sonuc,
|
|
1514
1563
|
kaynak: [KOD_KAYNAGI("apps/acik-mcp/src/bologna.ts", "Akredit OBS geri okuma karşılaştırması")],
|
|
1564
|
+
hocayaSimdiSor: sonuc.dur,
|
|
1515
1565
|
...(sonuc.dur
|
|
1516
1566
|
? {
|
|
1517
1567
|
sonrakiAdim: "Yazmayı durdurun: ders kodu ya da dönem beklenenden farklı. Hocaya nedenini söyleyin, başka alana yazmayın.",
|
|
@@ -1529,165 +1579,359 @@ const SEKME_SIRASI = [
|
|
|
1529
1579
|
"Sür. Kalkınma Amaçları",
|
|
1530
1580
|
"Diğer Kaynaklar",
|
|
1531
1581
|
];
|
|
1582
|
+
/**
|
|
1583
|
+
* Değerlendirme yüzdesi kod kapısı (ilke 2-3): dayanak 'sayfa' ya da 'hoca' olsa da her katkı yüzdesi verilen
|
|
1584
|
+
* kanıt metninde (sayfa tablosu ya da hocanın yanıtı) ayrı bir sayı olarak geçmelidir. Model beyanına güvenilmez.
|
|
1585
|
+
*/
|
|
1586
|
+
export function degerlendirmeTeyitli(a) {
|
|
1587
|
+
if (a.degerlendirmeDayanagi !== "sayfa" && a.degerlendirmeDayanagi !== "hoca")
|
|
1588
|
+
return false;
|
|
1589
|
+
if (!a.degerlendirme?.length)
|
|
1590
|
+
return true;
|
|
1591
|
+
const sayilar = new Set((a.degerlendirmeKaniti ?? "").match(/\d+(?:[.,]\d+)?/gu)?.map((x) => Number(x.replace(",", "."))));
|
|
1592
|
+
return a.degerlendirme.every((x) => sayilar.has(x.katki));
|
|
1593
|
+
}
|
|
1594
|
+
/** Peşin onay varken yazma adımı: durak yok, sonda tek rapor. */
|
|
1595
|
+
const PESIN_ONAY_YAZMA = "Peşin onay var (pesinOnayKaydi): hocaya sormadan yalnız bu paketteki alanları OBS'ye yazın, Kaydet'e basın, sayfayı geri okuyup obs_karsilastir ile doğrulayın. yazilamayan ve kalanEksikler'i uydurmayın, boş bırakın. Başka ders kaldıysa sormadan ona geçin. Hepsi bitince TEK rapor: ders ders yazılan alanlar, 'EKSİK' bırakılanlar (değerlendirme yüzdesi, kaynak), geri okuma sonucu, ve 'Onaya Gönder'e siz basarsınız; itirazınız olan alanı OBS'de düzeltin ya da bana numarasını yazın.' dur/fark çıkarsa o derste durun, rapora yazın.";
|
|
1596
|
+
const KopyaMetniGirdisi = z.object({
|
|
1597
|
+
ders: DersTemeli,
|
|
1598
|
+
alanlar: TaslakAlanlari,
|
|
1599
|
+
programCiktilari: z.array(z.string().max(10)).max(40).optional(),
|
|
1600
|
+
ingilizce: z.boolean().default(false).describe("bologna_ayristir'in ingilizce değeri"),
|
|
1601
|
+
oncekiDegerler: z
|
|
1602
|
+
.record(z.string().max(120), z.string().max(20_000))
|
|
1603
|
+
.optional()
|
|
1604
|
+
.describe("OBS'deki şimdiki değerler (alan adı, ör. 'Dersin Amacı (Türkçe)' → değer); önceki/yeni özeti için"),
|
|
1605
|
+
pesinOnay: z.string().max(500).optional().describe(PESIN_ONAY_ACIKLAMASI),
|
|
1606
|
+
});
|
|
1607
|
+
/** bologna_kopya_metni hesabı. Ayrı işlevdir: bologna_toplu_ozet her ders için aynı kapıları çalıştırır. */
|
|
1608
|
+
const kopyaPaketi = ({ ders, alanlar: gelen, programCiktilari, ingilizce, oncekiDegerler, pesinOnay, }) => {
|
|
1609
|
+
const pesin = pesinOnayVar(pesinOnay);
|
|
1610
|
+
// Yazma paketi kapısı: doğrulanmamış kaynak ve teyitsiz değerlendirme yüzdesi pakete giremez (ilke 1-3).
|
|
1611
|
+
const yazilamayan = [];
|
|
1612
|
+
const kaynaklar = gelen.kaynaklar?.filter((k, i) => {
|
|
1613
|
+
if (kaynakDurumu(k) !== "dogrulanmamis")
|
|
1614
|
+
return true;
|
|
1615
|
+
yazilamayan.push({
|
|
1616
|
+
alan: "Kaynaklar",
|
|
1617
|
+
neden: `${i + 1}. kaynak doğrulanmamış (katalogda bulunmadı ya da hoca beyanı yok); OBS'ye yazılmaz.`,
|
|
1618
|
+
});
|
|
1619
|
+
return false;
|
|
1620
|
+
});
|
|
1621
|
+
const teyitli = degerlendirmeTeyitli(gelen);
|
|
1622
|
+
if (gelen.degerlendirme && !teyitli)
|
|
1623
|
+
yazilamayan.push({
|
|
1624
|
+
alan: "Değerlendirme",
|
|
1625
|
+
neden: "Değerlendirme yöntem ve yüzdeleri teyitsiz (sayfadan okunmadı, hoca söylemedi ya da yüzdeler degerlendirmeKaniti metninde yok); OBS'ye yazılmaz. Hocaya sorun.",
|
|
1626
|
+
});
|
|
1627
|
+
const { degerlendirme: _d, kaynaklar: _k, ...kalanAlanlar } = gelen;
|
|
1628
|
+
const alanlar = {
|
|
1629
|
+
...kalanAlanlar,
|
|
1630
|
+
...(gelen.degerlendirme && teyitli ? { degerlendirme: gelen.degerlendirme } : {}),
|
|
1631
|
+
...(kaynaklar ? { kaynaklar } : {}),
|
|
1632
|
+
};
|
|
1633
|
+
const bolumler = [];
|
|
1634
|
+
const ekle = (sekme, alan, deger) => bolumler.push({ sira: bolumler.length + 1, sekme, alan, deger });
|
|
1635
|
+
const hesap = bolognaHesabi(ders, alanlar);
|
|
1636
|
+
const iki = (ad, m) => {
|
|
1637
|
+
if (!m)
|
|
1638
|
+
return;
|
|
1639
|
+
ekle(DERS_BILGILERI, `${ad} (Türkçe)`, m.tr.trim());
|
|
1640
|
+
if (m.en?.trim())
|
|
1641
|
+
ekle(DERS_BILGILERI, `${ad} (İngilizce)`, m.en.trim());
|
|
1642
|
+
};
|
|
1643
|
+
iki("Dersin Amacı", alanlar.amac);
|
|
1644
|
+
iki("Dersin İçeriği", alanlar.icerik);
|
|
1645
|
+
iki("Yöntem ve Teknikler", alanlar.yontem);
|
|
1646
|
+
iki("Ders Notları", alanlar.notlar);
|
|
1647
|
+
if (alanlar.onKosul !== undefined)
|
|
1648
|
+
ekle(DERS_BILGILERI, "Ön Koşul", alanlar.onKosul.trim());
|
|
1649
|
+
if (alanlar.degerlendirme)
|
|
1650
|
+
ekle(DERS_BILGILERI, "Değerlendirme (ad, sayı, katkı %)", [
|
|
1651
|
+
...alanlar.degerlendirme.map((x) => `${x.ad}\t${x.sayi}\t${x.katki}`),
|
|
1652
|
+
`Toplam\t\t${hesap.degerlendirmeToplami}`,
|
|
1653
|
+
].join("\n"));
|
|
1654
|
+
if (hesap.isyuku)
|
|
1655
|
+
ekle(DERS_BILGILERI, "AKTS İş Yükü (ad, sayı, süre, toplam saat)", [
|
|
1656
|
+
...hesap.isyuku.satirlar.map((x) => `${x.ad}\t${x.sayi}\t${x.sure}\t${x.toplam}`),
|
|
1657
|
+
`Toplam İş Yükü\t\t\t${hesap.isyuku.toplam}`,
|
|
1658
|
+
...(hesap.isyuku.hedef !== null ? [`AKTS\t\t\t${ders.akts}`] : []),
|
|
1659
|
+
].join("\n"));
|
|
1660
|
+
if (alanlar.dersYapisi)
|
|
1661
|
+
ekle(DERS_BILGILERI, "Ders Yapısı (alan, %)", alanlar.dersYapisi.map((x) => `${x.alan}\t${x.yuzde}`).join("\n"));
|
|
1662
|
+
if (alanlar.ciktilar)
|
|
1663
|
+
ekle("Öğrenme Çıktıları", "Öğrenme çıktıları (kod, tür, Türkçe, İngilizce)", alanlar.ciktilar.map((c) => `${c.kod}\t${c.tur}\t${c.tr.trim()}\t${c.en?.trim() ?? ""}`).join("\n"));
|
|
1664
|
+
if (alanlar.haftalar)
|
|
1665
|
+
ekle("Ders Akışı", "Haftalar (hafta, konu, İngilizce konu, ön hazırlık, dokümanlar)", alanlar.haftalar
|
|
1666
|
+
.map((h) => `${h.hafta}\t${h.konu.tr.trim()}\t${h.konu.en?.trim() ?? ""}\t${h.onHazirlik.trim()}\t${h.dokuman.trim()}`)
|
|
1667
|
+
.join("\n"));
|
|
1668
|
+
if (alanlar.katki) {
|
|
1669
|
+
const p = programCiktilari?.length
|
|
1670
|
+
? programCiktilari
|
|
1671
|
+
: [...new Set(Object.values(alanlar.katki).flatMap((s) => Object.keys(s)))];
|
|
1672
|
+
ekle("Prg.Çıktısına Katkısı", "Katkı tablosu ('Tüm' satırına dokunmayın)", [
|
|
1673
|
+
`\t${p.join("\t")}`,
|
|
1674
|
+
...Object.entries(alanlar.katki).map(([o, s]) => `${o}\t${p.map((k) => s[k] ?? "").join("\t")}`),
|
|
1675
|
+
].join("\n"));
|
|
1676
|
+
}
|
|
1677
|
+
if (alanlar.ska)
|
|
1678
|
+
ekle("Sür. Kalkınma Amaçları", "Seçilecek amaç numaraları", alanlar.ska.join(", "));
|
|
1679
|
+
if (alanlar.kaynaklar?.length)
|
|
1680
|
+
ekle("Diğer Kaynaklar", "Kaynaklar (her satır bir künye)", alanlar.kaynaklar.map(kunyeMetni).join("\n"));
|
|
1681
|
+
if (!bolumler.length && !yazilamayan.length)
|
|
1682
|
+
throw new AracHatasi("Kopyalanacak alan yok. En az bir alan gönderin.");
|
|
1683
|
+
const b = alanBulgulari(ders, alanlar, { ingilizce, programCiktilari });
|
|
1684
|
+
const kalanEksikler = [...b.entries()].flatMap(([alan, xs]) => xs.filter((x) => x.duzey === "hata").map((x) => ({ alan, neden: x.aciklama })));
|
|
1685
|
+
const baslik = [ders.kod, ders.ad].filter(Boolean).join(" ") || "Ders";
|
|
1686
|
+
const sirali = [...bolumler].sort((x, y) => SEKME_SIRASI.indexOf(x.sekme) - SEKME_SIRASI.indexOf(y.sekme) || x.sira - y.sira);
|
|
1687
|
+
const metin = [
|
|
1688
|
+
`${baslik}: OBS'ye kopyalanacak alanlar`,
|
|
1689
|
+
"",
|
|
1690
|
+
...sirali.flatMap((x, i) => [`${i + 1}. ${x.sekme} > ${x.alan}`, x.deger, ""]),
|
|
1691
|
+
...(ders.yetkililer?.length
|
|
1692
|
+
? [
|
|
1693
|
+
"Dersin Yetkilileri (sayfadaki hâliyle; değiştirmeyin, OBS'de zaten böyle kayıtlı):",
|
|
1694
|
+
...ders.yetkililer.map((y) => `${y.rol}: ${y.deger}`),
|
|
1695
|
+
]
|
|
1696
|
+
: ["Dersin Yetkilileri: bu metin onlara dokunmaz; OBS'deki hâliyle kalır."]),
|
|
1697
|
+
...(kalanEksikler.length
|
|
1698
|
+
? [
|
|
1699
|
+
"",
|
|
1700
|
+
"Kalan eksikler:",
|
|
1701
|
+
...kalanEksikler.map((e) => `- ${ALANLAR[e.alan]?.ad ?? "Genel"}: ${e.neden}`),
|
|
1702
|
+
]
|
|
1703
|
+
: []),
|
|
1704
|
+
...(yazilamayan.length
|
|
1705
|
+
? ["", "Yazma paketine alınmayanlar:", ...yazilamayan.map((y) => `- ${y.alan}: ${y.neden}`)]
|
|
1706
|
+
: []),
|
|
1707
|
+
].join("\n");
|
|
1708
|
+
const numarali = sirali.map((x, i) => ({ ...x, sira: i + 1 }));
|
|
1709
|
+
const normal = (v) => v.replace(/\s+/gu, " ").trim();
|
|
1710
|
+
const degisiklikOzeti = numarali.map((x) => {
|
|
1711
|
+
const onceki = oncekiDegerler?.[x.alan];
|
|
1712
|
+
return {
|
|
1713
|
+
no: x.sira,
|
|
1714
|
+
sekme: x.sekme,
|
|
1715
|
+
alan: x.alan,
|
|
1716
|
+
onceki: onceki === undefined ? null : onceki,
|
|
1717
|
+
yeni: x.deger,
|
|
1718
|
+
degisim: onceki === undefined ? "bilinmiyor" : normal(onceki) === normal(x.deger) ? "aynı" : "değişecek",
|
|
1719
|
+
};
|
|
1720
|
+
});
|
|
1721
|
+
return {
|
|
1722
|
+
sonuc: {
|
|
1723
|
+
metin,
|
|
1724
|
+
bolumler: numarali,
|
|
1725
|
+
degisiklikOzeti,
|
|
1726
|
+
yazilamayan,
|
|
1727
|
+
hesap,
|
|
1728
|
+
kalanEksikler,
|
|
1729
|
+
obsKarsilastirGirdisi: {
|
|
1730
|
+
...(ders.kod ? { dersKodu: ders.kod } : {}),
|
|
1731
|
+
...(ders.donem ? { donem: ders.donem } : {}),
|
|
1732
|
+
beklenen: numarali.map((x) => ({ alan: x.alan, deger: x.deger })),
|
|
1733
|
+
},
|
|
1734
|
+
...(pesin ? { pesinOnayKaydi: pesinOnay } : {}),
|
|
1735
|
+
not: `Biçim denetiminden geçti; içerik teyidi: hoca. ${pesin
|
|
1736
|
+
? "Peşin onay var: degisiklikOzeti'ni sondaki tek raporda gösterin."
|
|
1737
|
+
: "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.`,
|
|
1738
|
+
},
|
|
1739
|
+
kaynak: [KAYNAK_KURALLAR],
|
|
1740
|
+
...(pesin
|
|
1741
|
+
? {
|
|
1742
|
+
hocayaSimdiSor: false,
|
|
1743
|
+
sonrakiAdim: PESIN_ONAY_YAZMA,
|
|
1744
|
+
}
|
|
1745
|
+
: pesinOnay
|
|
1746
|
+
? {
|
|
1747
|
+
sonrakiAdim: "pesinOnay metninde kayıt sözü yok ya da söz olumsuz; peşin onay sayılmadı. degisiklikOzeti'ni gösterip kayıt onayını isteyin: 1) OBS'ye yaz ve kaydet 2) formu doldur, Kaydet'e ben basayım 3) kopya metni.",
|
|
1748
|
+
}
|
|
1749
|
+
: {}),
|
|
1750
|
+
...(kalanEksikler.length && !pesin
|
|
1751
|
+
? {
|
|
1752
|
+
sonrakiAdim: "Kalan eksikleri sormadan doldurmaya dönün (bologna_alan_rehberi, bologna_taslak_denetle); yalnız hocanın bilebileceği resmî bilgi (değerlendirme, kullanılan kaynak) eksikse onu TEK mesajda, numaralı sorun. Sonra degisiklikOzeti ile kayıt onayını aynı mesajda isteyin.",
|
|
1753
|
+
}
|
|
1754
|
+
: {}),
|
|
1755
|
+
};
|
|
1756
|
+
};
|
|
1532
1757
|
const kopyaMetniAraci = arac({
|
|
1533
1758
|
ad: "bologna_kopya_metni",
|
|
1534
1759
|
baslik: "OBS'ye kopyalanacak metin",
|
|
1535
|
-
|
|
1536
|
-
|
|
1760
|
+
hocayaSimdiSor: true,
|
|
1761
|
+
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. Doğrulanmamış kaynağı ve teyitsiz değerlendirme yüzdesini pakete almaz. Sunucu OBS'ye yazmaz.",
|
|
1762
|
+
sonrakiAdim: "Metnin tamamını basmayın. Yalnız degisiklikOzeti'ni tablo olarak gösterin (Alan | Önceki | Yeni, ilk cümle | Dayanak: sayfa/hoca/öneri) ve AYNI mesajda sorun: 1) OBS'ye yaz ve kaydet (önerilen; bu seçim özete 'kaydet' onayıdır) 2) formu doldur, Kaydet'e ben basayım 3) alan alan kopya metnini ver. Değiştirmek istediği alan varsa numarasını yazar. Tarayıcı aracınız yoksa yalnız 3'ü uygulayın ve metni kod bloğunda verin. Birden çok ders taranıyorsa tüm derslerin özetini tek tabloda gösterip tek onay alın.",
|
|
1537
1763
|
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
1538
1764
|
kunyeAlanlari: BOLOGNA_KUNYE_ALANLARI,
|
|
1765
|
+
girdi: KopyaMetniGirdisi,
|
|
1766
|
+
calistir: kopyaPaketi,
|
|
1767
|
+
});
|
|
1768
|
+
/** OBS'de ders listesi açılınca: hocaya "hangi ders?" sordurmaz, tarama planını ve rapor biçimini verir. */
|
|
1769
|
+
const dersListesiAraci = arac({
|
|
1770
|
+
ad: "obs_ders_listesi",
|
|
1771
|
+
baslik: "OBS ders listesini tara",
|
|
1772
|
+
aciklama: "OBS ya da Bologna sisteminde hocanın ders listesi açılınca, hocaya 'hangi ders?' diye SORMADAN İLK bu aracı çağır: listedeki ders kodlarını ve adlarını ver. Tarama sırasını, hocaya gösterilecek kısa bilgi cümlesini ve sonuç raporunun biçimini döndürür. OBS'ye yazmaz, adres açmaz.",
|
|
1773
|
+
sonrakiAdim: "Hocaya hocayaGoster cümlesini yazın ve hiçbir şey sormadan tarayiciEylemPlani'nı uygulayın: taramaPlani sırasıyla her dersin Bologna sayfasını açıp bologna_ayristir'a verin (yanıttaki denetim eksikleri sayar). Bitince raporBicimi'ne göre TEK mesaj yazın.",
|
|
1774
|
+
kisiselVeriKapsami: "tam",
|
|
1539
1775
|
girdi: z.object({
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
.
|
|
1547
|
-
.
|
|
1776
|
+
dersler: z
|
|
1777
|
+
.array(z.object({ kod: z.string().trim().min(1).max(30), ad: z.string().trim().max(160).optional() }))
|
|
1778
|
+
.min(1)
|
|
1779
|
+
.max(60)
|
|
1780
|
+
.describe("Listede görünen dersler: kod ve ad (her çağrıda listenin tamamı)"),
|
|
1781
|
+
baslangic: z
|
|
1782
|
+
.number()
|
|
1783
|
+
.int()
|
|
1784
|
+
.min(0)
|
|
1785
|
+
.max(59)
|
|
1786
|
+
.default(0)
|
|
1787
|
+
.describe("Taranacak dilimin ilk dersinin sırası (0'dan). İlk çağrıda 0; sonraki dilimde yanıttaki sonrakiBaslangic."),
|
|
1788
|
+
pesinOnay: z.string().max(500).optional().describe(PESIN_ONAY_ACIKLAMASI),
|
|
1548
1789
|
}),
|
|
1549
|
-
calistir: ({
|
|
1550
|
-
|
|
1551
|
-
const
|
|
1552
|
-
const
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1790
|
+
calistir: ({ dersler, baslangic, pesinOnay }) => {
|
|
1791
|
+
const pesin = pesinOnayVar(pesinOnay);
|
|
1792
|
+
const DILIM = 15;
|
|
1793
|
+
const plan = dersler.slice(baslangic, baslangic + DILIM);
|
|
1794
|
+
const sonraki = baslangic + plan.length;
|
|
1795
|
+
const kalan = dersler.length - sonraki;
|
|
1796
|
+
const hepsi = baslangic === 0 && kalan === 0;
|
|
1797
|
+
return {
|
|
1798
|
+
sonuc: {
|
|
1799
|
+
hocayaGoster: pesin
|
|
1800
|
+
? baslangic === 0
|
|
1801
|
+
? `${dersler.length} dersiniz var. Hepsini tarayıp eksikleri dolduracak ve OBS'ye kaydedeceğim; bilmediğim resmî bilgiyi boş bırakacağım. Bitince tek rapor vereceğim. Vazgeçmek için 'dur' yazın.`
|
|
1802
|
+
: ""
|
|
1803
|
+
: baslangic === 0
|
|
1804
|
+
? `${dersler.length} dersiniz var. ${hepsi ? "Hepsini" : `${DILIM}'erli dilimlerle hepsini`} tarıyorum; eksikleri ve önerilerimi birazdan tek listede göstereceğim.`
|
|
1805
|
+
: `Sıradaki ${plan.length} dersi tarıyorum (${baslangic + 1}-${sonraki} / ${dersler.length}).`,
|
|
1806
|
+
taramaPlani: plan.map((d, i) => ({ sira: baslangic + i + 1, kod: d.kod, ad: d.ad ?? "" })),
|
|
1807
|
+
// Model tarayıcı turlarında sunucuya uğramaz; adımlar emir kipinde verilir ki ara soru doğmasın.
|
|
1808
|
+
tarayiciEylemPlani: [
|
|
1809
|
+
`ADIM 1: Hocaya soru sormayın. Listede '${plan[0]?.kod ?? ""}' satırının düzenleme/ders bilgi paketi bağlantısına tıklayın.`,
|
|
1810
|
+
"ADIM 2: Açılan sayfanın tamamını (metin ya da HTML) okuyup bologna_ayristir'a verin; yanıttaki 'denetim' alanı eksikleri sayar, bologna_denetle'yi ayrıca çağırmayın.",
|
|
1811
|
+
"ADIM 3: Sayfa iframe içindeyse iframe'in src adresine gidin. Alanlar ayrı sekmelerdeyse (Ders Bilgileri, Öğrenme Çıktıları, Ders Akışı, Katkı) sırayla açıp metinleri birleştirin.",
|
|
1812
|
+
pesin
|
|
1813
|
+
? "ADIM 4: Bu dersin eksiklerini doldurup kaydedin (sonrakiAdim'daki sıra), sonra listeye dönüp sıradaki derse geçin; bütün plan bitene kadar hocaya hiçbir şey yazmayın."
|
|
1814
|
+
: "ADIM 4: Listeye dönüp sıradaki derse geçin; bütün plan bitene kadar hocaya hiçbir şey yazmayın.",
|
|
1815
|
+
"ADIM 5: Giriş ekranına düşerseniz durun: 'Oturum düştü; OBS sekmesinde yeniden girip girdim yazın' deyin. Parola ve doğrulama kodu istemeyin.",
|
|
1816
|
+
pesin
|
|
1817
|
+
? "ADIM 6: Plan bitince sonrakiAdim'daki TEK raporu yazın; ara rapor ve seçenek yok."
|
|
1818
|
+
: "ADIM 6: Plan bitince raporBicimi'ne göre TEK mesaj yazın.",
|
|
1819
|
+
],
|
|
1820
|
+
...(kalan > 0 ? { taranmayan: kalan, sonrakiBaslangic: sonraki } : {}),
|
|
1821
|
+
// Peşin onayda rapor durağı yoktur; rapor biçimi yalnız olağan akışta döner.
|
|
1822
|
+
...(pesin
|
|
1823
|
+
? {}
|
|
1824
|
+
: {
|
|
1825
|
+
raporBicimi: [
|
|
1826
|
+
"Başlık: 'Şu eksiklikleri tespit ettim, önerilerim şunlar.'",
|
|
1827
|
+
"Her ders bir satır, en eksikten en tamama: ders kodu, eksik ya da kurala uymayan alan sayısı, en önemli 1-2 eksik.",
|
|
1828
|
+
"Eksiği olmayan dersler için: 'eksik görünmüyor' ve kaliteyi artıracak 1-2 somut öneri (zayıf alan, Bloom düzeyi, iş yükü ile AKTS uyumu, kaynak güncelliği, İngilizce alanlar).",
|
|
1829
|
+
"Sayfada olmayan değerlendirme yüzdeleri ve kaynakları aynı mesajda sor (tahmin etme).",
|
|
1830
|
+
`Altına: ${TARAMA_SECENEKLERI.join(" ")}`,
|
|
1831
|
+
],
|
|
1832
|
+
}),
|
|
1833
|
+
},
|
|
1834
|
+
kaynak: [KAYNAK_KURALLAR],
|
|
1835
|
+
hocayaSimdiSor: false,
|
|
1836
|
+
...(pesin
|
|
1837
|
+
? {
|
|
1838
|
+
sonrakiAdim: `Peşin onay var: rapor durağı yok. Her ders için sırayla: bologna_ayristir → eksik alanları bologna_alan_rehberi ve bologna_taslak_denetle ile hazırla → bologna_kopya_metni'ni pesinOnay ile çağır → yaz, Kaydet, geri oku, obs_karsilastir. Sayfada olmayan değerlendirme yüzdesi ve kaynağı sorma, EKSİK bırak.${kalan > 0 ? ` Dilim bitince obs_ders_listesi'ni baslangic=${sonraki} ve aynı pesinOnay ile çağır.` : ""} Hepsi bitince TEK rapor: ders ders yazılanlar, EKSİK kalanlar, geri okuma sonucu; Onaya Gönder hocada.`,
|
|
1839
|
+
}
|
|
1840
|
+
: kalan > 0
|
|
1841
|
+
? {
|
|
1842
|
+
sonrakiAdim: `Bu dilimi sormadan tarayın ve raporunu verin. Hoca 1'i seçip bu dilim kapanınca 'devam edeyim mi' diye sormadan obs_ders_listesi'ni baslangic=${sonraki} ile yeniden çağırıp sonraki dilime geçin.`,
|
|
1843
|
+
}
|
|
1844
|
+
: {}),
|
|
1582
1845
|
};
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
if (alanlar.ska)
|
|
1618
|
-
ekle("Sür. Kalkınma Amaçları", "Seçilecek amaç numaraları", alanlar.ska.join(", "));
|
|
1619
|
-
if (alanlar.kaynaklar?.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)
|
|
1622
|
-
throw new AracHatasi("Kopyalanacak alan yok. En az bir alan gönderin.");
|
|
1623
|
-
const b = alanBulgulari(ders, alanlar, { ingilizce, programCiktilari });
|
|
1624
|
-
const kalanEksikler = [...b.entries()].flatMap(([alan, xs]) => xs.filter((x) => x.duzey === "hata").map((x) => ({ alan, neden: x.aciklama })));
|
|
1625
|
-
const baslik = [ders.kod, ders.ad].filter(Boolean).join(" ") || "Ders";
|
|
1626
|
-
const sirali = [...bolumler].sort((x, y) => SEKME_SIRASI.indexOf(x.sekme) - SEKME_SIRASI.indexOf(y.sekme) || x.sira - y.sira);
|
|
1627
|
-
const metin = [
|
|
1628
|
-
`${baslik}: OBS'ye kopyalanacak alanlar`,
|
|
1629
|
-
"",
|
|
1630
|
-
...sirali.flatMap((x, i) => [`${i + 1}. ${x.sekme} > ${x.alan}`, x.deger, ""]),
|
|
1631
|
-
...(ders.yetkililer?.length
|
|
1632
|
-
? [
|
|
1633
|
-
"Dersin Yetkilileri (sayfadaki hâliyle; değiştirmeyin, OBS'de zaten böyle kayıtlı):",
|
|
1634
|
-
...ders.yetkililer.map((y) => `${y.rol}: ${y.deger}`),
|
|
1635
|
-
]
|
|
1636
|
-
: ["Dersin Yetkilileri: bu metin onlara dokunmaz; OBS'deki hâliyle kalır."]),
|
|
1637
|
-
...(kalanEksikler.length
|
|
1638
|
-
? [
|
|
1639
|
-
"",
|
|
1640
|
-
"Kalan eksikler:",
|
|
1641
|
-
...kalanEksikler.map((e) => `- ${ALANLAR[e.alan]?.ad ?? "Genel"}: ${e.neden}`),
|
|
1642
|
-
]
|
|
1643
|
-
: []),
|
|
1644
|
-
...(yazilamayan.length
|
|
1645
|
-
? ["", "Yazma paketine alınmayanlar:", ...yazilamayan.map((y) => `- ${y.alan}: ${y.neden}`)]
|
|
1646
|
-
: []),
|
|
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];
|
|
1846
|
+
},
|
|
1847
|
+
});
|
|
1848
|
+
// ---------------------------------------------------------------- bologna_toplu_ozet
|
|
1849
|
+
/**
|
|
1850
|
+
* Toplu mod: birden çok dersin yazma paketi tek tabloda, tek onayla. Her ders için bologna_kopya_metni'nin kapıları
|
|
1851
|
+
* aynen çalışır (teyitsiz yüzde ve doğrulanmamış kaynak pakete girmez). Onay hakkı korunur: her satırda dayanak
|
|
1852
|
+
* etiketi görünür, hoca ders numarasıyla seçebilir, kayıt döngüsünde her dersten sonra obs_karsilastir çalışır.
|
|
1853
|
+
*/
|
|
1854
|
+
const topluOzetAraci = arac({
|
|
1855
|
+
ad: "bologna_toplu_ozet",
|
|
1856
|
+
baslik: "Birden çok dersin yazma özeti",
|
|
1857
|
+
aciklama: "Birden çok dersin (1-15) hazır taslaklarını tek çağrıda yazma paketine çevirir ve hocaya gösterilecek tek özet tablosunu üretir: ders, değişecek alanlar, değerlendirme dayanağı, iş yükü, kaynak sayısı, pakete alınmayanlar. Her ders için bologna_kopya_metni kuralları aynen uygulanır. OBS'ye yazmaz.",
|
|
1858
|
+
sonrakiAdim: "tablo'yu olduğu gibi ve hocayaGoster seçenekleriyle TEK mesajda gösterin; tek onay alın. Hoca 1 derse kayıt döngüsü: her dersi sırayla yazın, Kaydet'e basın, geri okuyup o dersin paketi (paketler[kod]) ile obs_karsilastir çağırın; dur true ya da ikinci farkta bütün döngüyü durdurup hocaya söyleyin. Onaya Gönder'e hiçbir derste basmayın.",
|
|
1859
|
+
hocayaSimdiSor: true,
|
|
1860
|
+
kisiselVeriKapsami: BOLOGNA_KAPSAMI,
|
|
1861
|
+
kunyeAlanlari: BOLOGNA_KUNYE_ALANLARI,
|
|
1862
|
+
girdi: z.object({
|
|
1863
|
+
dersler: z.array(KopyaMetniGirdisi).min(1).max(15).describe("Her öğe bologna_kopya_metni girdisiyle aynı"),
|
|
1864
|
+
pesinOnay: z.string().max(500).optional().describe(PESIN_ONAY_ACIKLAMASI),
|
|
1865
|
+
}),
|
|
1866
|
+
calistir: (g) => {
|
|
1867
|
+
const pesin = pesinOnayVar(g.pesinOnay);
|
|
1868
|
+
const satirlar = g.dersler.map((d, i) => {
|
|
1869
|
+
const s = kopyaPaketi(d).sonuc;
|
|
1870
|
+
const dayanak = d.alanlar.degerlendirmeDayanagi;
|
|
1871
|
+
const degerlendirme = d.alanlar.degerlendirme
|
|
1872
|
+
? degerlendirmeTeyitli(d.alanlar)
|
|
1873
|
+
? `${d.alanlar.degerlendirme.map((x) => `${x.ad} %${x.katki}`).join(", ")} (${dayanak})`
|
|
1874
|
+
: "teyitsiz, yazılmaz"
|
|
1875
|
+
: "sayfadaki korunur";
|
|
1876
|
+
const kaynaklar = d.alanlar.kaynaklar ?? [];
|
|
1877
|
+
const kaynakOzeti = kaynaklar.length
|
|
1878
|
+
? `${kaynaklar.length} (${kaynaklar.map((k) => kaynakDurumu(k)).join(", ")})`
|
|
1879
|
+
: "EKSİK";
|
|
1652
1880
|
return {
|
|
1653
|
-
no:
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1881
|
+
no: i + 1,
|
|
1882
|
+
kod: d.ders.kod,
|
|
1883
|
+
ad: d.ders.ad ?? "",
|
|
1884
|
+
degisecekAlanlar: s.degisiklikOzeti.filter((x) => x.degisim !== "aynı").map((x) => x.alan),
|
|
1885
|
+
degerlendirme,
|
|
1886
|
+
isYuku: s.hesap.isyuku
|
|
1887
|
+
? `${s.hesap.isyuku.toplam} saat${d.ders.akts ? ` / ${d.ders.akts} AKTS` : ""}`
|
|
1888
|
+
: "EKSİK",
|
|
1889
|
+
kaynaklar: kaynakOzeti,
|
|
1890
|
+
yazilamayan: s.yazilamayan.map((y) => y.alan),
|
|
1891
|
+
kalanEksik: s.kalanEksikler.length,
|
|
1892
|
+
paket: s.obsKarsilastirGirdisi,
|
|
1659
1893
|
};
|
|
1660
1894
|
});
|
|
1895
|
+
const paketler = Object.fromEntries(satirlar.map((s) => [s.kod, s.paket]));
|
|
1896
|
+
const tablo = satirlar.map(({ paket: _p, ...kalan }) => kalan);
|
|
1897
|
+
const sorunlu = satirlar.filter((s) => s.yazilamayan.length || s.kalanEksik);
|
|
1661
1898
|
return {
|
|
1662
1899
|
sonuc: {
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1900
|
+
tablo,
|
|
1901
|
+
paketler,
|
|
1902
|
+
hocayaGoster: [
|
|
1903
|
+
`${satirlar.length} dersin değişiklik özeti yukarıda. Dayanak: sayfa = OBS'den okundu, hoca = siz söylediniz, öneri = ders adından üretildi.`,
|
|
1904
|
+
...(sorunlu.length
|
|
1905
|
+
? [
|
|
1906
|
+
`Pakete alınmayan ya da eksik kalan alanı olan dersler: ${sorunlu.map((s) => `${s.no}) ${s.kod}`).join(", ")}.`,
|
|
1907
|
+
]
|
|
1908
|
+
: []),
|
|
1909
|
+
// Peşin onayda seçenek sorulmaz; özet sondaki raporda yer alır.
|
|
1910
|
+
...(pesin
|
|
1911
|
+
? []
|
|
1912
|
+
: [
|
|
1913
|
+
`1) ${satirlar.length} dersin tamamını OBS'ye yaz ve kaydet (önerilen; bu seçim 'kaydet' onayıdır)`,
|
|
1914
|
+
"2) Yalnız şu dersleri yaz: numaralarını yazın (ör. 1, 3)",
|
|
1915
|
+
"3) Bir dersin tam metnini göster: numarasını yazın (ör. detay 2)",
|
|
1916
|
+
]),
|
|
1917
|
+
"Onaya Gönder'e siz basarsınız.",
|
|
1918
|
+
].join("\n"),
|
|
1919
|
+
...(pesin ? { pesinOnayKaydi: g.pesinOnay } : {}),
|
|
1920
|
+
not: "Biçim denetiminden geçti; içerik teyidi: hoca. Her ders kaydedildikten sonra geri okunup obs_karsilastir ile doğrulanır.",
|
|
1675
1921
|
},
|
|
1676
1922
|
kaynak: [KAYNAK_KURALLAR],
|
|
1677
|
-
...(
|
|
1678
|
-
? {
|
|
1679
|
-
sonrakiAdim: "Kalan eksikleri hocaya numaralı gösterin ve sorun: 1) eksikleri dolduralım 2) böyle kalsın, metni kopyalayacağım.",
|
|
1680
|
-
}
|
|
1681
|
-
: {}),
|
|
1923
|
+
...(pesin ? { hocayaSimdiSor: false, sonrakiAdim: PESIN_ONAY_YAZMA } : {}),
|
|
1682
1924
|
};
|
|
1683
1925
|
},
|
|
1684
1926
|
});
|
|
1685
1927
|
export const BOLOGNA_ARACLARI = [
|
|
1928
|
+
dersListesiAraci,
|
|
1686
1929
|
ayristirAraci,
|
|
1687
1930
|
alanRehberi,
|
|
1688
1931
|
taslakDenetleAraci,
|
|
1689
1932
|
isYukuOnerAraci,
|
|
1690
1933
|
durumAraci,
|
|
1691
1934
|
kopyaMetniAraci,
|
|
1935
|
+
topluOzetAraci,
|
|
1692
1936
|
obsKarsilastirAraci,
|
|
1693
1937
|
];
|