zam-core 0.6.2 → 0.6.3

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/dist/cli/index.js CHANGED
@@ -7386,7 +7386,7 @@ async function readWebLink(url) {
7386
7386
  redirect: "manual",
7387
7387
  signal: controller.signal,
7388
7388
  headers: {
7389
- "User-Agent": "ZAM-Content-Studio/0.6.1"
7389
+ "User-Agent": "ZAM-Content-Studio/0.6.3"
7390
7390
  }
7391
7391
  });
7392
7392
  if (res.status >= 300 && res.status < 400) {
@@ -7464,23 +7464,184 @@ async function setLastCurriculumSelection(db, breadcrumb) {
7464
7464
  await setSetting(db, LAST_SELECTION_KEY, JSON.stringify(breadcrumb));
7465
7465
  }
7466
7466
 
7467
+ // src/cli/curriculum/providers/bildungsplan-bremen/manifest.ts
7468
+ var BILDUNGSPLAN_BREMEN_MANIFEST = {
7469
+ schoolYear: "2025/2026",
7470
+ capturedOn: "2026-07-02",
7471
+ sourceRevision: "Bildungspl\xE4ne Bremen",
7472
+ schoolTypes: [
7473
+ { id: "oberschule", label: "Oberschule" },
7474
+ { id: "gymnasium", label: "Gymnasium" }
7475
+ ],
7476
+ grades: {
7477
+ oberschule: ["7", "8", "9", "10"],
7478
+ gymnasium: ["7", "8", "9", "10"]
7479
+ },
7480
+ subjects: {
7481
+ oberschule: [
7482
+ { id: "mathematik", label: "Mathematik" },
7483
+ { id: "informatik", label: "Informatik" },
7484
+ { id: "physik", label: "Physik" },
7485
+ { id: "chemie", label: "Chemie" },
7486
+ { id: "biologie", label: "Biologie" }
7487
+ ],
7488
+ gymnasium: [
7489
+ { id: "mathematik", label: "Mathematik" },
7490
+ { id: "informatik", label: "Informatik" },
7491
+ { id: "physik", label: "Physik" },
7492
+ { id: "chemie", label: "Chemie" },
7493
+ { id: "biologie", label: "Biologie" }
7494
+ ]
7495
+ },
7496
+ tracks: {},
7497
+ topics: {
7498
+ "oberschule|10|mathematik": [
7499
+ { id: "funktionen", label: "Funktionen" },
7500
+ { id: "geometrie", label: "Geometrie" }
7501
+ ],
7502
+ "gymnasium|10|mathematik": [{ id: "analysis", label: "Analysis" }],
7503
+ "oberschule|9|informatik": [{ id: "algorithmen", label: "Algorithmen" }],
7504
+ "oberschule|9|physik": [{ id: "mechanik", label: "Mechanik" }],
7505
+ "oberschule|9|chemie": [
7506
+ { id: "reaktionen", label: "Chemische Reaktionen" }
7507
+ ],
7508
+ "oberschule|9|biologie": [{ id: "zellen", label: "Zellen" }],
7509
+ "gymnasium|9|physik": [{ id: "elektrizitaet", label: "Elektrizit\xE4t" }],
7510
+ "gymnasium|9|chemie": [{ id: "bindungen", label: "Chemische Bindungen" }],
7511
+ "gymnasium|9|biologie": [{ id: "genetik", label: "Genetik" }]
7512
+ },
7513
+ contentUrls: {
7514
+ "oberschule|10|mathematik": "https://www.lis.bremen.de/schulqualitaet/bildungsplaene-21942",
7515
+ "gymnasium|10|mathematik": "https://www.lis.bremen.de/schulqualitaet/bildungsplaene-21942",
7516
+ "oberschule|9|informatik": "https://www.lis.bremen.de/schulqualitaet/bildungsplaene-21942",
7517
+ "oberschule|9|physik": "https://www.lis.bremen.de/schulqualitaet/bildungsplaene-21942",
7518
+ "oberschule|9|chemie": "https://www.lis.bremen.de/schulqualitaet/bildungsplaene-21942",
7519
+ "oberschule|9|biologie": "https://www.lis.bremen.de/schulqualitaet/bildungsplaene-21942",
7520
+ "gymnasium|9|physik": "https://www.lis.bremen.de/schulqualitaet/bildungsplaene-21942",
7521
+ "gymnasium|9|chemie": "https://www.lis.bremen.de/schulqualitaet/bildungsplaene-21942",
7522
+ "gymnasium|9|biologie": "https://www.lis.bremen.de/schulqualitaet/bildungsplaene-21942"
7523
+ }
7524
+ };
7525
+
7526
+ // src/cli/curriculum/providers/bildungsplan-bremen/index.ts
7527
+ var bildungsplanBremenProvider = {
7528
+ id: "bildungsplan-bremen",
7529
+ country: "DE",
7530
+ countryLabel: "Deutschland",
7531
+ region: "HB",
7532
+ regionLabel: "Bremen",
7533
+ label: "Bildungsplan (Bremen)",
7534
+ listSchoolTypes() {
7535
+ return BILDUNGSPLAN_BREMEN_MANIFEST.schoolTypes;
7536
+ },
7537
+ listGrades(schoolType) {
7538
+ return (BILDUNGSPLAN_BREMEN_MANIFEST.grades[schoolType] || []).map((id) => ({
7539
+ id,
7540
+ label: `Klasse ${id}`
7541
+ }));
7542
+ },
7543
+ listSubjects(schoolType, _grade) {
7544
+ return BILDUNGSPLAN_BREMEN_MANIFEST.subjects[schoolType] || [];
7545
+ },
7546
+ listTracks(schoolType, grade, subject) {
7547
+ const key = `${schoolType}|${grade}|${subject}`;
7548
+ return BILDUNGSPLAN_BREMEN_MANIFEST.tracks[key] || [];
7549
+ },
7550
+ listTopics(selection) {
7551
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
7552
+ const list = BILDUNGSPLAN_BREMEN_MANIFEST.topics[key] || [];
7553
+ return list.map((t2) => ({
7554
+ ...t2,
7555
+ sourceRef: key
7556
+ }));
7557
+ },
7558
+ resolveTopic(topic) {
7559
+ const uri = BILDUNGSPLAN_BREMEN_MANIFEST.contentUrls[topic.sourceRef];
7560
+ if (!uri) {
7561
+ throw new Error(
7562
+ `Bildungsplan Bremen: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
7563
+ );
7564
+ }
7565
+ return {
7566
+ provider: "bildungsplan-bremen",
7567
+ topicId: `${topic.sourceRef}#${topic.id}`,
7568
+ uri
7569
+ };
7570
+ },
7571
+ extractTopics(html, topicIds) {
7572
+ const results = {};
7573
+ const headingMatches = Array.from(
7574
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
7575
+ );
7576
+ const sections = [];
7577
+ for (const m of headingMatches) {
7578
+ const headerText = cleanHtmlText(m[1]).trim();
7579
+ const start = m.index + m[0].length;
7580
+ const nextHeading = html.indexOf("<h", start);
7581
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 1500);
7582
+ const content = cleanHtmlText(rawContent).slice(0, 1500);
7583
+ if (headerText) sections.push({ header: headerText, content });
7584
+ }
7585
+ for (const topicId of topicIds) {
7586
+ const hashIdx = topicId.indexOf("#");
7587
+ if (hashIdx === -1) continue;
7588
+ const key = topicId.substring(0, hashIdx);
7589
+ const shortId = topicId.substring(hashIdx + 1);
7590
+ const list = BILDUNGSPLAN_BREMEN_MANIFEST.topics[key] ?? [];
7591
+ const match = list.find((t2) => t2.id === shortId);
7592
+ if (!match) continue;
7593
+ const label = match.label;
7594
+ const normalizedLabel = normalizeForComparison(label);
7595
+ let section = sections.find(
7596
+ (s) => normalizeForComparison(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
7597
+ );
7598
+ if (!section && sections.length > 0) {
7599
+ section = sections.find((s) => s.content.length > 80) || sections[0];
7600
+ }
7601
+ if (section) {
7602
+ results[topicId] = `${label}
7603
+
7604
+ ${section.content}`.trim();
7605
+ } else {
7606
+ results[topicId] = label;
7607
+ }
7608
+ }
7609
+ return results;
7610
+ }
7611
+ };
7612
+ function cleanHtmlText(html) {
7613
+ let text = html.replace(
7614
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
7615
+ " "
7616
+ );
7617
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
7618
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
7619
+ text = text.replace(/<p[^>]*>/gi, "\n");
7620
+ text = text.replace(/<br\s*\/?>/gi, "\n");
7621
+ text = text.replace(/<[^>]+>/g, " ");
7622
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
7623
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
7624
+ }
7625
+ function normalizeForComparison(str) {
7626
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
7627
+ }
7628
+
7467
7629
  // src/cli/curriculum/providers/bildungsplan-bw/manifest.ts
7468
7630
  var BILDUNGSPLAN_BW_MANIFEST = {
7469
7631
  schoolYear: "2026/2027",
7470
7632
  capturedOn: "2026-07-02",
7471
7633
  sourceRevision: "Bildungsplan Baden-W\xFCrttemberg Gymnasium 2016",
7472
- schoolTypes: [
7473
- { id: "gymnasium", label: "Gymnasium" },
7474
- { id: "realschule", label: "Realschule" }
7475
- ],
7634
+ schoolTypes: [{ id: "gymnasium", label: "Gymnasium" }],
7476
7635
  grades: {
7477
7636
  gymnasium: ["9", "10", "11", "12"]
7478
7637
  },
7479
7638
  subjects: {
7480
7639
  gymnasium: [
7481
7640
  { id: "mathematik", label: "Mathematik" },
7641
+ { id: "informatik", label: "Informatik" },
7482
7642
  { id: "physik", label: "Physik" },
7483
- { id: "englisch", label: "Englisch" }
7643
+ { id: "chemie", label: "Chemie" },
7644
+ { id: "biologie", label: "Biologie" }
7484
7645
  ]
7485
7646
  },
7486
7647
  tracks: {},
@@ -7491,10 +7652,44 @@ var BILDUNGSPLAN_BW_MANIFEST = {
7491
7652
  { id: "leitidee-raum", label: "Leitidee Raum und Form" },
7492
7653
  { id: "leitidee-funktion", label: "Leitidee Funktionaler Zusammenhang" },
7493
7654
  { id: "leitidee-daten", label: "Leitidee Daten und Zufall" }
7655
+ ],
7656
+ "gymnasium|10|informatik": [
7657
+ { id: "ik-daten-codierung", label: "Daten und Codierung" },
7658
+ { id: "ik-algorithmen", label: "Algorithmen" },
7659
+ { id: "ik-rechner-netze", label: "Rechner und Netze" },
7660
+ {
7661
+ id: "ik-informationsgesellschaft",
7662
+ label: "Informationsgesellschaft und Datensicherheit"
7663
+ }
7664
+ ],
7665
+ "gymnasium|10|physik": [
7666
+ { id: "optik-akustik", label: "Optik und Akustik" },
7667
+ { id: "mechanik", label: "Mechanik" },
7668
+ {
7669
+ id: "elektrizitaet-magnetismus",
7670
+ label: "Elektrizit\xE4t und Magnetismus"
7671
+ },
7672
+ { id: "energie-waerme", label: "Energie und W\xE4rmelehre" }
7673
+ ],
7674
+ "gymnasium|10|chemie": [
7675
+ { id: "stoffe-reaktionen", label: "Stoffe und chemische Reaktionen" },
7676
+ { id: "atome-molekuele", label: "Atome und Molek\xFCle" },
7677
+ { id: "saeuren-basen", label: "S\xE4uren, Basen und Salze" },
7678
+ { id: "organische-chemie", label: "Organische Chemie" }
7679
+ ],
7680
+ "gymnasium|10|biologie": [
7681
+ { id: "zellen", label: "Zellen und Gewebe" },
7682
+ { id: "genetik", label: "Genetik und Vererbung" },
7683
+ { id: "oekologie", label: "\xD6kologie und Umwelt" },
7684
+ { id: "evolution", label: "Evolution und Vielfalt" }
7494
7685
  ]
7495
7686
  },
7496
7687
  contentUrls: {
7497
- "gymnasium|10|mathematik": "http://www.bildungsplaene-bw.de/,Lde/LS/BP2016BW/ALLG/GYM/M/bp/klasse-10"
7688
+ "gymnasium|10|mathematik": "http://www.bildungsplaene-bw.de/,Lde/LS/BP2016BW/ALLG/GYM/M/bp/klasse-10",
7689
+ "gymnasium|10|informatik": "https://www.bildungsplaene-bw.de/,Lde/BP2016BW_ALLG_SEK1_INFWF",
7690
+ "gymnasium|10|physik": "https://www.bildungsplaene-bw.de/,Lde/BP2016BW_ALLG_GYM_PH",
7691
+ "gymnasium|10|chemie": "https://www.bildungsplaene-bw.de/,Lde/BP2016BW_ALLG_GYM_CH",
7692
+ "gymnasium|10|biologie": "https://www.bildungsplaene-bw.de/,Lde/BP2016BW_ALLG_GYM_BIO"
7498
7693
  }
7499
7694
  };
7500
7695
 
@@ -7555,7 +7750,7 @@ var bildungsplanBwProvider = {
7555
7750
  const headerMatch = contentHtml.match(
7556
7751
  /<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/i
7557
7752
  );
7558
- const headerText = headerMatch ? cleanHtmlText(headerMatch[1]).trim() : "";
7753
+ const headerText = headerMatch ? cleanHtmlText2(headerMatch[1]).trim() : "";
7559
7754
  sections.push({
7560
7755
  id,
7561
7756
  headerText,
@@ -7572,20 +7767,20 @@ var bildungsplanBwProvider = {
7572
7767
  const match = list.find((t2) => t2.id === shortId);
7573
7768
  if (!match) continue;
7574
7769
  const label = match.label;
7575
- const normalizedLabel = normalizeForComparison(label);
7770
+ const normalizedLabel = normalizeForComparison2(label);
7576
7771
  const sectionIndex = sections.findIndex((s) => {
7577
- const normalizedHeader = normalizeForComparison(s.headerText);
7772
+ const normalizedHeader = normalizeForComparison2(s.headerText);
7578
7773
  return normalizedHeader.includes(normalizedLabel) || s.id === shortId;
7579
7774
  });
7580
7775
  if (sectionIndex === -1) {
7581
7776
  continue;
7582
7777
  }
7583
- results[topicId] = cleanHtmlText(sections[sectionIndex].contentHtml);
7778
+ results[topicId] = cleanHtmlText2(sections[sectionIndex].contentHtml);
7584
7779
  }
7585
7780
  return results;
7586
7781
  }
7587
7782
  };
7588
- function cleanHtmlText(html) {
7783
+ function cleanHtmlText2(html) {
7589
7784
  let text = html.replace(
7590
7785
  /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
7591
7786
  " "
@@ -7598,181 +7793,1731 @@ function cleanHtmlText(html) {
7598
7793
  text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&ndash;/g, "\u2013").replace(/&mdash;/g, "\u2014").replace(/&middot;/g, "\xB7");
7599
7794
  return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
7600
7795
  }
7601
- function normalizeForComparison(str) {
7796
+ function normalizeForComparison2(str) {
7602
7797
  return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
7603
7798
  }
7604
7799
 
7605
- // src/cli/curriculum/providers/lehrplanplus-bayern/manifest.ts
7606
- var LEHRPLANPLUS_BAYERN_MANIFEST = {
7607
- schoolYear: "2026/2027",
7800
+ // src/cli/curriculum/providers/bildungsplan-hamburg/manifest.ts
7801
+ var BILDUNGSPLAN_HAMBURG_MANIFEST = {
7802
+ schoolYear: "2025/2026",
7608
7803
  capturedOn: "2026-07-02",
7609
- sourceRevision: "LehrplanPLUS Realschule \u2013 Oktober 2023",
7804
+ sourceRevision: "Bildungspl\xE4ne Hamburg",
7610
7805
  schoolTypes: [
7611
- { id: "grundschule", label: "Grundschule" },
7612
- { id: "mittelschule", label: "Mittelschule" },
7613
- { id: "foerderschule", label: "F\xF6rderschule" },
7614
- { id: "realschule", label: "Realschule" },
7615
- { id: "gymnasium", label: "Gymnasium" },
7616
- { id: "wirtschaftsschule", label: "Wirtschaftsschule" },
7617
- { id: "fos", label: "Fachoberschule" },
7618
- { id: "bos", label: "Berufsoberschule" }
7806
+ { id: "stadtteilschule", label: "Stadtteilschule (Realschule)" },
7807
+ { id: "gymnasium", label: "Gymnasium" }
7619
7808
  ],
7620
7809
  grades: {
7621
- realschule: ["5", "6", "7", "8", "9", "10"]
7810
+ stadtteilschule: ["7", "8", "9", "10"],
7811
+ gymnasium: ["7", "8", "9", "10"]
7622
7812
  },
7623
7813
  subjects: {
7624
- realschule: [
7625
- {
7626
- id: "bwl-rechnungswesen",
7627
- label: "Betriebswirtschaftslehre / Rechnungswesen"
7628
- },
7629
- { id: "biologie", label: "Biologie" },
7814
+ stadtteilschule: [
7815
+ { id: "mathematik", label: "Mathematik" },
7816
+ { id: "informatik", label: "Informatik" },
7817
+ { id: "physik", label: "Physik" },
7630
7818
  { id: "chemie", label: "Chemie" },
7631
- { id: "deutsch", label: "Deutsch" },
7632
- { id: "englisch", label: "Englisch" },
7633
- { id: "ernaehrung_und_gesundheit", label: "Ern\xE4hrung und Gesundheit" },
7634
- { id: "ethik", label: "Ethik" },
7635
- {
7636
- id: "evangelische-religionslehre",
7637
- label: "Evangelische Religionslehre"
7638
- },
7639
- { id: "franzoesisch", label: "Franz\xF6sisch" },
7640
- { id: "geographie", label: "Geographie" },
7641
- { id: "geschichte", label: "Geschichte" },
7642
- { id: "it", label: "Informationstechnologie" },
7643
- { id: "iu", label: "Islamischer Unterricht" },
7644
- { id: "ir", label: "Israelitische Religionslehre" },
7645
- {
7646
- id: "katholische-religionslehre",
7647
- label: "Katholische Religionslehre"
7648
- },
7649
- { id: "kunst", label: "Kunst" },
7819
+ { id: "biologie", label: "Biologie" }
7820
+ ],
7821
+ gymnasium: [
7650
7822
  { id: "mathematik", label: "Mathematik" },
7651
- { id: "musik", label: "Musik" },
7652
- { id: "or", label: "Orthodoxe Religionslehre" },
7823
+ { id: "informatik", label: "Informatik" },
7653
7824
  { id: "physik", label: "Physik" },
7654
- { id: "pug", label: "Politik und Gesellschaft" },
7655
- { id: "soziallehre", label: "Soziallehre" },
7656
- { id: "sozialwesen", label: "Sozialwesen" },
7657
- { id: "spanisch", label: "Spanisch" },
7658
- { id: "sport", label: "Sport" },
7659
- { id: "textiles-gestalten", label: "Textiles Gestalten" },
7660
- { id: "werken", label: "Werken" },
7661
- { id: "wirtschaft-und-recht", label: "Wirtschaft und Recht" }
7662
- ]
7663
- },
7664
- tracks: {
7665
- "realschule|9|mathematik": [
7666
- { id: "wpfg1", label: "Mathematik 9 (I)" },
7667
- { id: "wpfg2-3", label: "Mathematik 9 (II/III)" }
7825
+ { id: "chemie", label: "Chemie" },
7826
+ { id: "biologie", label: "Biologie" }
7668
7827
  ]
7669
7828
  },
7829
+ tracks: {},
7670
7830
  topics: {
7671
- "realschule|9|mathematik|wpfg1": [
7672
- { id: "lb1", label: "Reelle Zahlen", hours: 10 },
7673
- { id: "lb2", label: "Zentrische Streckung", hours: 17 },
7674
- { id: "lb3", label: "Rechtwinklige Dreiecke", hours: 20 },
7675
- { id: "lb4", label: "Kreis", hours: 10 },
7676
- { id: "lb5", label: "Raumgeometrie", hours: 20 },
7677
- { id: "lb6", label: "Systeme linearer Gleichungen", hours: 12 },
7678
- {
7679
- id: "lb7",
7680
- label: "Quadratische Funktionen und quadratische Gleichungen",
7681
- hours: 42
7682
- },
7683
- { id: "lb8", label: "Daten und Zufall", hours: 9 }
7684
- ],
7685
- "realschule|9|mathematik|wpfg2-3": [
7686
- { id: "lb1", label: "Reelle Zahlen", hours: 7 },
7687
- { id: "lb2", label: "Zentrische Streckung", hours: 13 },
7688
- { id: "lb3", label: "Rechtwinklige Dreiecke", hours: 20 },
7689
- { id: "lb4", label: "Kreis", hours: 10 },
7690
- { id: "lb5", label: "Lineare Funktionen", hours: 15 },
7691
- { id: "lb6", label: "Systeme linearer Gleichungen", hours: 10 },
7692
- { id: "lb7", label: "Daten und Zufall", hours: 9 }
7693
- ],
7694
- "realschule|9|deutsch": [
7695
- { id: "lb1", label: "Sprechen und Zuh\xF6ren" },
7696
- { id: "lb2", label: "Lesen \u2013 mit Texten und weiteren Medien umgehen" },
7697
- { id: "lb3", label: "Schreiben" },
7698
- {
7699
- id: "lb4",
7700
- label: "Sprachgebrauch und Sprache untersuchen und reflektieren"
7701
- }
7831
+ "stadtteilschule|10|mathematik": [
7832
+ { id: "funktionen", label: "Funktionen" },
7833
+ { id: "geometrie", label: "Geometrie" }
7702
7834
  ],
7703
- "realschule|9|englisch": [
7704
- { id: "lb1", label: "Kommunikative Kompetenzen" },
7705
- { id: "lb2", label: "Interkulturelle Kompetenzen" },
7706
- { id: "lb3", label: "Text- und Medienkompetenzen" },
7707
- { id: "lb4", label: "Methodische Kompetenzen" },
7708
- { id: "lb5", label: "Themengebiete" }
7709
- ]
7835
+ "gymnasium|10|mathematik": [{ id: "analysis", label: "Analysis" }],
7836
+ "stadtteilschule|9|physik": [{ id: "mechanik", label: "Mechanik" }],
7837
+ "stadtteilschule|9|chemie": [{ id: "reaktionen", label: "Reaktionen" }],
7838
+ "stadtteilschule|9|biologie": [{ id: "zellen", label: "Zellen" }],
7839
+ "gymnasium|9|physik": [{ id: "elektrizitaet", label: "Elektrizit\xE4t" }],
7840
+ "gymnasium|9|chemie": [{ id: "bindungen", label: "Bindungen" }],
7841
+ "gymnasium|9|biologie": [{ id: "genetik", label: "Genetik" }]
7710
7842
  },
7711
7843
  contentUrls: {
7712
- "realschule|9|mathematik|wpfg1": "https://www.lehrplanplus.bayern.de/schulart/realschule/jgs/9/fach/mathematik/inhalt/fachlehrplaene?w_schulart=realschule&wt_1=schulart&w_fach=mathematik&wt_2=fach&w_jgs=9&wt_3=jgs&w_auspraegung=wpfg1",
7713
- "realschule|9|mathematik|wpfg2-3": "https://www.lehrplanplus.bayern.de/schulart/realschule/jgs/9/fach/mathematik/inhalt/fachlehrplaene?w_schulart=realschule&wt_1=schulart&w_fach=mathematik&wt_2=fach&w_jgs=9&wt_3=jgs&w_auspraegung=wpfg2-3",
7714
- "realschule|9|deutsch": "https://www.lehrplanplus.bayern.de/schulart/realschule/jgs/9/fach/deutsch/inhalt/fachlehrplaene",
7715
- "realschule|9|englisch": "https://www.lehrplanplus.bayern.de/schulart/realschule/jgs/9/fach/englisch/inhalt/fachlehrplaene"
7844
+ "stadtteilschule|10|mathematik": "https://www.hamburg.de/bildungsplaene",
7845
+ "gymnasium|10|mathematik": "https://www.hamburg.de/bildungsplaene",
7846
+ "stadtteilschule|9|physik": "https://www.hamburg.de/bildungsplaene",
7847
+ "stadtteilschule|9|chemie": "https://www.hamburg.de/bildungsplaene",
7848
+ "stadtteilschule|9|biologie": "https://www.hamburg.de/bildungsplaene",
7849
+ "gymnasium|9|physik": "https://www.hamburg.de/bildungsplaene",
7850
+ "gymnasium|9|chemie": "https://www.hamburg.de/bildungsplaene",
7851
+ "gymnasium|9|biologie": "https://www.hamburg.de/bildungsplaene"
7716
7852
  }
7717
7853
  };
7718
7854
 
7719
- // src/cli/curriculum/providers/lehrplanplus-bayern/index.ts
7720
- function levelKey(schoolType, grade, subject, track) {
7721
- return track ? `${schoolType}|${grade}|${subject}|${track}` : `${schoolType}|${grade}|${subject}`;
7722
- }
7723
- var lehrplanplusBayernProvider = {
7724
- id: "lehrplanplus-bayern",
7855
+ // src/cli/curriculum/providers/bildungsplan-hamburg/index.ts
7856
+ var bildungsplanHamburgProvider = {
7857
+ id: "bildungsplan-hamburg",
7725
7858
  country: "DE",
7726
7859
  countryLabel: "Deutschland",
7727
- region: "BY",
7728
- regionLabel: "Bayern",
7729
- label: "LehrplanPLUS (Bayern)",
7860
+ region: "HH",
7861
+ regionLabel: "Hamburg",
7862
+ label: "Bildungsplan (Hamburg)",
7730
7863
  listSchoolTypes() {
7731
- return LEHRPLANPLUS_BAYERN_MANIFEST.schoolTypes;
7864
+ return BILDUNGSPLAN_HAMBURG_MANIFEST.schoolTypes;
7732
7865
  },
7733
7866
  listGrades(schoolType) {
7734
- return (LEHRPLANPLUS_BAYERN_MANIFEST.grades[schoolType] ?? []).map((grade) => ({
7735
- id: grade,
7736
- label: grade
7867
+ return (BILDUNGSPLAN_HAMBURG_MANIFEST.grades[schoolType] || []).map((id) => ({
7868
+ id,
7869
+ label: `Klasse ${id}`
7737
7870
  }));
7738
7871
  },
7739
7872
  listSubjects(schoolType, _grade) {
7740
- return LEHRPLANPLUS_BAYERN_MANIFEST.subjects[schoolType] ?? [];
7873
+ return BILDUNGSPLAN_HAMBURG_MANIFEST.subjects[schoolType] || [];
7741
7874
  },
7742
7875
  listTracks(schoolType, grade, subject) {
7743
- return LEHRPLANPLUS_BAYERN_MANIFEST.tracks[levelKey(schoolType, grade, subject)] ?? [];
7876
+ const key = `${schoolType}|${grade}|${subject}`;
7877
+ return BILDUNGSPLAN_HAMBURG_MANIFEST.tracks[key] || [];
7744
7878
  },
7745
7879
  listTopics(selection) {
7746
- const { schoolType, grade, subject, track } = selection;
7747
- if (!schoolType || !grade || !subject) return [];
7748
- const key = levelKey(schoolType, grade, subject, track);
7749
- return (LEHRPLANPLUS_BAYERN_MANIFEST.topics[key] ?? []).map((topic) => ({
7750
- ...topic,
7880
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
7881
+ const list = BILDUNGSPLAN_HAMBURG_MANIFEST.topics[key] || [];
7882
+ return list.map((t2) => ({
7883
+ ...t2,
7751
7884
  sourceRef: key
7752
7885
  }));
7753
7886
  },
7754
7887
  resolveTopic(topic) {
7755
- const uri = LEHRPLANPLUS_BAYERN_MANIFEST.contentUrls[topic.sourceRef];
7888
+ const uri = BILDUNGSPLAN_HAMBURG_MANIFEST.contentUrls[topic.sourceRef];
7756
7889
  if (!uri) {
7757
7890
  throw new Error(
7758
- `LehrplanPLUS Bayern: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}). The manifest only covers the combinations curated as of ${LEHRPLANPLUS_BAYERN_MANIFEST.capturedOn}.`
7891
+ `Bildungsplan Hamburg: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
7759
7892
  );
7760
7893
  }
7761
7894
  return {
7762
- provider: "lehrplanplus-bayern",
7895
+ provider: "bildungsplan-hamburg",
7763
7896
  topicId: `${topic.sourceRef}#${topic.id}`,
7764
7897
  uri
7765
7898
  };
7766
7899
  },
7767
7900
  extractTopics(html, topicIds) {
7768
- const chunks = html.split('<div id="thema_');
7901
+ const results = {};
7902
+ const headingMatches = Array.from(
7903
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
7904
+ );
7769
7905
  const sections = [];
7770
- for (let i = 1; i < chunks.length; i++) {
7771
- const chunk = chunks[i];
7772
- const quoteIdx = chunk.indexOf('"');
7773
- if (quoteIdx === -1) continue;
7774
- const id = chunk.slice(0, quoteIdx);
7775
- const classStart = chunk.indexOf('class="');
7906
+ for (const m of headingMatches) {
7907
+ const headerText = cleanHtmlText3(m[1]).trim();
7908
+ const start = m.index + m[0].length;
7909
+ const nextHeading = html.indexOf("<h", start);
7910
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 1500);
7911
+ const content = cleanHtmlText3(rawContent).slice(0, 1500);
7912
+ if (headerText) sections.push({ header: headerText, content });
7913
+ }
7914
+ for (const topicId of topicIds) {
7915
+ const hashIdx = topicId.indexOf("#");
7916
+ if (hashIdx === -1) continue;
7917
+ const key = topicId.substring(0, hashIdx);
7918
+ const shortId = topicId.substring(hashIdx + 1);
7919
+ const list = BILDUNGSPLAN_HAMBURG_MANIFEST.topics[key] ?? [];
7920
+ const match = list.find((t2) => t2.id === shortId);
7921
+ if (!match) continue;
7922
+ const label = match.label;
7923
+ const normalizedLabel = normalizeForComparison3(label);
7924
+ let section = sections.find(
7925
+ (s) => normalizeForComparison3(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
7926
+ );
7927
+ if (!section && sections.length > 0) {
7928
+ section = sections.find((s) => s.content.length > 80) || sections[0];
7929
+ }
7930
+ if (section) {
7931
+ results[topicId] = `${label}
7932
+
7933
+ ${section.content}`.trim();
7934
+ } else {
7935
+ results[topicId] = label;
7936
+ }
7937
+ }
7938
+ return results;
7939
+ }
7940
+ };
7941
+ function cleanHtmlText3(html) {
7942
+ let text = html.replace(
7943
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
7944
+ " "
7945
+ );
7946
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
7947
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
7948
+ text = text.replace(/<p[^>]*>/gi, "\n");
7949
+ text = text.replace(/<br\s*\/?>/gi, "\n");
7950
+ text = text.replace(/<[^>]+>/g, " ");
7951
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
7952
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
7953
+ }
7954
+ function normalizeForComparison3(str) {
7955
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
7956
+ }
7957
+
7958
+ // src/cli/curriculum/providers/fachanforderungen-sh/manifest.ts
7959
+ var FACHANFORDERUNGEN_SH_MANIFEST = {
7960
+ schoolYear: "2025/2026",
7961
+ capturedOn: "2026-07-02",
7962
+ sourceRevision: "Fachanforderungen Schleswig-Holstein",
7963
+ schoolTypes: [
7964
+ { id: "gemeinschaftsschule", label: "Gemeinschaftsschule" },
7965
+ { id: "gymnasium", label: "Gymnasium" }
7966
+ ],
7967
+ grades: {
7968
+ gemeinschaftsschule: ["7", "8", "9", "10"],
7969
+ gymnasium: ["7", "8", "9", "10"]
7970
+ },
7971
+ subjects: {
7972
+ gemeinschaftsschule: [
7973
+ { id: "mathematik", label: "Mathematik" },
7974
+ { id: "informatik", label: "Informatik" },
7975
+ { id: "physik", label: "Physik" },
7976
+ { id: "chemie", label: "Chemie" },
7977
+ { id: "biologie", label: "Biologie" }
7978
+ ],
7979
+ gymnasium: [
7980
+ { id: "mathematik", label: "Mathematik" },
7981
+ { id: "informatik", label: "Informatik" },
7982
+ { id: "physik", label: "Physik" },
7983
+ { id: "chemie", label: "Chemie" },
7984
+ { id: "biologie", label: "Biologie" }
7985
+ ]
7986
+ },
7987
+ tracks: {},
7988
+ topics: {
7989
+ "gemeinschaftsschule|10|mathematik": [
7990
+ { id: "funktionen", label: "Funktionen" }
7991
+ ],
7992
+ "gymnasium|10|mathematik": [{ id: "analysis", label: "Analysis" }],
7993
+ "gemeinschaftsschule|9|physik": [{ id: "mechanik", label: "Mechanik" }],
7994
+ "gemeinschaftsschule|9|chemie": [{ id: "reaktionen", label: "Reaktionen" }],
7995
+ "gemeinschaftsschule|9|biologie": [{ id: "zellen", label: "Zellen" }],
7996
+ "gymnasium|9|physik": [{ id: "elektrizitaet", label: "Elektrizit\xE4t" }],
7997
+ "gymnasium|9|chemie": [{ id: "bindungen", label: "Bindungen" }],
7998
+ "gymnasium|9|biologie": [{ id: "genetik", label: "Genetik" }]
7999
+ },
8000
+ contentUrls: {
8001
+ "gemeinschaftsschule|10|mathematik": "https://fachportal.lernnetz.de/sh/fachanforderungen.html",
8002
+ "gymnasium|10|mathematik": "https://fachportal.lernnetz.de/sh/fachanforderungen.html",
8003
+ "gemeinschaftsschule|9|physik": "https://fachportal.lernnetz.de/sh/fachanforderungen.html",
8004
+ "gemeinschaftsschule|9|chemie": "https://fachportal.lernnetz.de/sh/fachanforderungen.html",
8005
+ "gemeinschaftsschule|9|biologie": "https://fachportal.lernnetz.de/sh/fachanforderungen.html",
8006
+ "gymnasium|9|physik": "https://fachportal.lernnetz.de/sh/fachanforderungen.html",
8007
+ "gymnasium|9|chemie": "https://fachportal.lernnetz.de/sh/fachanforderungen.html",
8008
+ "gymnasium|9|biologie": "https://fachportal.lernnetz.de/sh/fachanforderungen.html"
8009
+ }
8010
+ };
8011
+
8012
+ // src/cli/curriculum/providers/fachanforderungen-sh/index.ts
8013
+ var fachanforderungenShProvider = {
8014
+ id: "fachanforderungen-sh",
8015
+ country: "DE",
8016
+ countryLabel: "Deutschland",
8017
+ region: "SH",
8018
+ regionLabel: "Schleswig-Holstein",
8019
+ label: "Fachanforderungen (Schleswig-Holstein)",
8020
+ listSchoolTypes() {
8021
+ return FACHANFORDERUNGEN_SH_MANIFEST.schoolTypes;
8022
+ },
8023
+ listGrades(schoolType) {
8024
+ return (FACHANFORDERUNGEN_SH_MANIFEST.grades[schoolType] || []).map((id) => ({
8025
+ id,
8026
+ label: `Klasse ${id}`
8027
+ }));
8028
+ },
8029
+ listSubjects(schoolType, _grade) {
8030
+ return FACHANFORDERUNGEN_SH_MANIFEST.subjects[schoolType] || [];
8031
+ },
8032
+ listTracks(schoolType, grade, subject) {
8033
+ const key = `${schoolType}|${grade}|${subject}`;
8034
+ return FACHANFORDERUNGEN_SH_MANIFEST.tracks[key] || [];
8035
+ },
8036
+ listTopics(selection) {
8037
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
8038
+ const list = FACHANFORDERUNGEN_SH_MANIFEST.topics[key] || [];
8039
+ return list.map((t2) => ({
8040
+ ...t2,
8041
+ sourceRef: key
8042
+ }));
8043
+ },
8044
+ resolveTopic(topic) {
8045
+ const uri = FACHANFORDERUNGEN_SH_MANIFEST.contentUrls[topic.sourceRef];
8046
+ if (!uri) {
8047
+ throw new Error(
8048
+ `Fachanforderungen SH: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
8049
+ );
8050
+ }
8051
+ return {
8052
+ provider: "fachanforderungen-sh",
8053
+ topicId: `${topic.sourceRef}#${topic.id}`,
8054
+ uri
8055
+ };
8056
+ },
8057
+ extractTopics(html, topicIds) {
8058
+ const results = {};
8059
+ const headingMatches = Array.from(
8060
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
8061
+ );
8062
+ const sections = [];
8063
+ for (const m of headingMatches) {
8064
+ const headerText = cleanHtmlText4(m[1]).trim();
8065
+ const start = m.index + m[0].length;
8066
+ const nextHeading = html.indexOf("<h", start);
8067
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 1500);
8068
+ const content = cleanHtmlText4(rawContent).slice(0, 1500);
8069
+ if (headerText) sections.push({ header: headerText, content });
8070
+ }
8071
+ for (const topicId of topicIds) {
8072
+ const hashIdx = topicId.indexOf("#");
8073
+ if (hashIdx === -1) continue;
8074
+ const key = topicId.substring(0, hashIdx);
8075
+ const shortId = topicId.substring(hashIdx + 1);
8076
+ const list = FACHANFORDERUNGEN_SH_MANIFEST.topics[key] ?? [];
8077
+ const match = list.find((t2) => t2.id === shortId);
8078
+ if (!match) continue;
8079
+ const label = match.label;
8080
+ const normalizedLabel = normalizeForComparison4(label);
8081
+ let section = sections.find(
8082
+ (s) => normalizeForComparison4(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
8083
+ );
8084
+ if (!section && sections.length > 0) {
8085
+ section = sections.find((s) => s.content.length > 80) || sections[0];
8086
+ }
8087
+ if (section) {
8088
+ results[topicId] = `${label}
8089
+
8090
+ ${section.content}`.trim();
8091
+ } else {
8092
+ results[topicId] = label;
8093
+ }
8094
+ }
8095
+ return results;
8096
+ }
8097
+ };
8098
+ function cleanHtmlText4(html) {
8099
+ let text = html.replace(
8100
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
8101
+ " "
8102
+ );
8103
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
8104
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
8105
+ text = text.replace(/<p[^>]*>/gi, "\n");
8106
+ text = text.replace(/<br\s*\/?>/gi, "\n");
8107
+ text = text.replace(/<[^>]+>/g, " ");
8108
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
8109
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
8110
+ }
8111
+ function normalizeForComparison4(str) {
8112
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
8113
+ }
8114
+
8115
+ // src/cli/curriculum/providers/kerncurriculum-hessen/manifest.ts
8116
+ var KERNCURRICULUM_HESSEN_MANIFEST = {
8117
+ schoolYear: "2025/2026",
8118
+ capturedOn: "2026-07-02",
8119
+ sourceRevision: "Kerncurricula Hessen (Sekundarstufe I)",
8120
+ schoolTypes: [
8121
+ { id: "realschule", label: "Realschule" },
8122
+ { id: "gymnasium", label: "Gymnasium" }
8123
+ ],
8124
+ grades: {
8125
+ realschule: ["7", "8", "9", "10"],
8126
+ gymnasium: ["7", "8", "9", "10"]
8127
+ },
8128
+ subjects: {
8129
+ realschule: [
8130
+ { id: "mathematik", label: "Mathematik" },
8131
+ { id: "informatik", label: "Informatik" },
8132
+ { id: "physik", label: "Physik" },
8133
+ { id: "chemie", label: "Chemie" },
8134
+ { id: "biologie", label: "Biologie" }
8135
+ ],
8136
+ gymnasium: [
8137
+ { id: "mathematik", label: "Mathematik" },
8138
+ { id: "informatik", label: "Informatik" },
8139
+ { id: "physik", label: "Physik" },
8140
+ { id: "chemie", label: "Chemie" },
8141
+ { id: "biologie", label: "Biologie" }
8142
+ ]
8143
+ },
8144
+ tracks: {},
8145
+ topics: {
8146
+ // Minimal for Hessen Realschule / Gym - based on Kerncurricula Inhaltsfelder
8147
+ "realschule|10|mathematik": [
8148
+ { id: "funktionen", label: "Funktionen" },
8149
+ { id: "geometrie", label: "Geometrie" },
8150
+ { id: "wahrscheinlichkeit", label: "Wahrscheinlichkeit und Statistik" }
8151
+ ],
8152
+ "gymnasium|10|mathematik": [
8153
+ { id: "analysis", label: "Analysis" },
8154
+ { id: "vektoren", label: "Vektoren und Geometrie" },
8155
+ { id: "stochastik", label: "Stochastik" }
8156
+ ],
8157
+ "realschule|9|informatik": [
8158
+ { id: "daten", label: "Daten und Information" },
8159
+ { id: "algorithmen", label: "Algorithmen und Programmierung" },
8160
+ { id: "netze", label: "Netze und Sicherheit" }
8161
+ ],
8162
+ "realschule|9|physik": [
8163
+ { id: "mechanik", label: "Mechanik" },
8164
+ { id: "energie", label: "Energie" },
8165
+ { id: "elektrizitaet", label: "Elektrizit\xE4t" }
8166
+ ],
8167
+ "realschule|9|chemie": [
8168
+ { id: "stoffe", label: "Stoffe und Reaktionen" },
8169
+ { id: "atome", label: "Atome und Periodensystem" },
8170
+ { id: "organisch", label: "Organische Verbindungen" }
8171
+ ],
8172
+ "realschule|9|biologie": [
8173
+ { id: "zelle", label: "Zelle und Stoffwechsel" },
8174
+ { id: "vererbung", label: "Vererbung" },
8175
+ { id: "oekologie", label: "\xD6kologie" }
8176
+ ],
8177
+ "gymnasium|9|physik": [
8178
+ { id: "optik", label: "Optik" },
8179
+ { id: "mechanik-dynamik", label: "Mechanik und Dynamik" },
8180
+ { id: "felder", label: "Elektrische und magnetische Felder" }
8181
+ ],
8182
+ "gymnasium|9|chemie": [
8183
+ { id: "bindung", label: "Chemische Bindung" },
8184
+ { id: "reaktionsgeschwindigkeit", label: "Reaktionsgeschwindigkeit" },
8185
+ { id: "saeure-base", label: "S\xE4ure-Base-Reaktionen" }
8186
+ ],
8187
+ "gymnasium|9|biologie": [
8188
+ { id: "genetik", label: "Genetik" },
8189
+ { id: "evolution", label: "Evolution" },
8190
+ { id: "neurobiologie", label: "Neurobiologie" }
8191
+ ]
8192
+ },
8193
+ contentUrls: {
8194
+ "realschule|10|mathematik": "https://kultusministerium.hessen.de/Unterricht/Kerncurricula-und-Lehrplaene/Kerncurricula/sekundarstufe-i-kerncurricula",
8195
+ "gymnasium|10|mathematik": "https://kultusministerium.hessen.de/Unterricht/Kerncurricula-und-Lehrplaene/Kerncurricula/sekundarstufe-i-kerncurricula",
8196
+ "realschule|9|informatik": "https://kultusministerium.hessen.de/Unterricht/Kerncurricula-und-Lehrplaene/Kerncurricula/sekundarstufe-i-kerncurricula",
8197
+ "realschule|9|physik": "https://kultusministerium.hessen.de/Unterricht/Kerncurricula-und-Lehrplaene/Kerncurricula/sekundarstufe-i-kerncurricula",
8198
+ "realschule|9|chemie": "https://kultusministerium.hessen.de/Unterricht/Kerncurricula-und-Lehrplaene/Kerncurricula/sekundarstufe-i-kerncurricula",
8199
+ "realschule|9|biologie": "https://kultusministerium.hessen.de/Unterricht/Kerncurricula-und-Lehrplaene/Kerncurricula/sekundarstufe-i-kerncurricula",
8200
+ "gymnasium|9|physik": "https://kultusministerium.hessen.de/Unterricht/Kerncurricula-und-Lehrplaene/Kerncurricula/sekundarstufe-i-kerncurricula",
8201
+ "gymnasium|9|chemie": "https://kultusministerium.hessen.de/Unterricht/Kerncurricula-und-Lehrplaene/Kerncurricula/sekundarstufe-i-kerncurricula",
8202
+ "gymnasium|9|biologie": "https://kultusministerium.hessen.de/Unterricht/Kerncurricula-und-Lehrplaene/Kerncurricula/sekundarstufe-i-kerncurricula"
8203
+ }
8204
+ };
8205
+
8206
+ // src/cli/curriculum/providers/kerncurriculum-hessen/index.ts
8207
+ var kerncurriculumHessenProvider = {
8208
+ id: "kerncurriculum-hessen",
8209
+ country: "DE",
8210
+ countryLabel: "Deutschland",
8211
+ region: "HE",
8212
+ regionLabel: "Hessen",
8213
+ label: "Kerncurriculum (Hessen)",
8214
+ listSchoolTypes() {
8215
+ return KERNCURRICULUM_HESSEN_MANIFEST.schoolTypes;
8216
+ },
8217
+ listGrades(schoolType) {
8218
+ return (KERNCURRICULUM_HESSEN_MANIFEST.grades[schoolType] || []).map((id) => ({
8219
+ id,
8220
+ label: `Klasse ${id}`
8221
+ }));
8222
+ },
8223
+ listSubjects(schoolType, _grade) {
8224
+ return KERNCURRICULUM_HESSEN_MANIFEST.subjects[schoolType] || [];
8225
+ },
8226
+ listTracks(schoolType, grade, subject) {
8227
+ const key = `${schoolType}|${grade}|${subject}`;
8228
+ return KERNCURRICULUM_HESSEN_MANIFEST.tracks[key] || [];
8229
+ },
8230
+ listTopics(selection) {
8231
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
8232
+ const list = KERNCURRICULUM_HESSEN_MANIFEST.topics[key] || [];
8233
+ return list.map((t2) => ({
8234
+ ...t2,
8235
+ sourceRef: key
8236
+ }));
8237
+ },
8238
+ resolveTopic(topic) {
8239
+ const uri = KERNCURRICULUM_HESSEN_MANIFEST.contentUrls[topic.sourceRef];
8240
+ if (!uri) {
8241
+ throw new Error(
8242
+ `Kerncurriculum Hessen: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
8243
+ );
8244
+ }
8245
+ return {
8246
+ provider: "kerncurriculum-hessen",
8247
+ topicId: `${topic.sourceRef}#${topic.id}`,
8248
+ uri
8249
+ };
8250
+ },
8251
+ extractTopics(html, topicIds) {
8252
+ const results = {};
8253
+ const headingMatches = Array.from(
8254
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
8255
+ );
8256
+ const sections = [];
8257
+ for (const m of headingMatches) {
8258
+ const headerText = cleanHtmlText5(m[1]).trim();
8259
+ const start = m.index + m[0].length;
8260
+ const nextHeading = html.indexOf("<h", start);
8261
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 1500);
8262
+ const content = cleanHtmlText5(rawContent).slice(0, 1500);
8263
+ if (headerText) sections.push({ header: headerText, content });
8264
+ }
8265
+ for (const topicId of topicIds) {
8266
+ const hashIdx = topicId.indexOf("#");
8267
+ if (hashIdx === -1) continue;
8268
+ const key = topicId.substring(0, hashIdx);
8269
+ const shortId = topicId.substring(hashIdx + 1);
8270
+ const list = KERNCURRICULUM_HESSEN_MANIFEST.topics[key] ?? [];
8271
+ const match = list.find((t2) => t2.id === shortId);
8272
+ if (!match) continue;
8273
+ const label = match.label;
8274
+ const normalizedLabel = normalizeForComparison5(label);
8275
+ let section = sections.find(
8276
+ (s) => normalizeForComparison5(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
8277
+ );
8278
+ if (!section && sections.length > 0) {
8279
+ section = sections.find((s) => s.content.length > 80) || sections[0];
8280
+ }
8281
+ if (section) {
8282
+ results[topicId] = `${label}
8283
+
8284
+ ${section.content}`.trim();
8285
+ } else {
8286
+ results[topicId] = label;
8287
+ }
8288
+ }
8289
+ return results;
8290
+ }
8291
+ };
8292
+ function cleanHtmlText5(html) {
8293
+ let text = html.replace(
8294
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
8295
+ " "
8296
+ );
8297
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
8298
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
8299
+ text = text.replace(/<p[^>]*>/gi, "\n");
8300
+ text = text.replace(/<br\s*\/?>/gi, "\n");
8301
+ text = text.replace(/<[^>]+>/g, " ");
8302
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
8303
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
8304
+ }
8305
+ function normalizeForComparison5(str) {
8306
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
8307
+ }
8308
+
8309
+ // src/cli/curriculum/providers/kerncurriculum-niedersachsen/manifest.ts
8310
+ var KERNCURRICULUM_NIEDERSACHSEN_MANIFEST = {
8311
+ schoolYear: "2025/2026",
8312
+ capturedOn: "2026-07-02",
8313
+ sourceRevision: "Kerncurricula Niedersachsen (CuVo)",
8314
+ schoolTypes: [
8315
+ { id: "realschule", label: "Realschule" },
8316
+ { id: "gymnasium", label: "Gymnasium" }
8317
+ ],
8318
+ grades: {
8319
+ realschule: ["7", "8", "9", "10"],
8320
+ gymnasium: ["7", "8", "9", "10"]
8321
+ },
8322
+ subjects: {
8323
+ realschule: [
8324
+ { id: "mathematik", label: "Mathematik" },
8325
+ { id: "informatik", label: "Informatik" },
8326
+ { id: "physik", label: "Physik" },
8327
+ { id: "chemie", label: "Chemie" },
8328
+ { id: "biologie", label: "Biologie" }
8329
+ ],
8330
+ gymnasium: [
8331
+ { id: "mathematik", label: "Mathematik" },
8332
+ { id: "informatik", label: "Informatik" },
8333
+ { id: "physik", label: "Physik" },
8334
+ { id: "chemie", label: "Chemie" },
8335
+ { id: "biologie", label: "Biologie" }
8336
+ ]
8337
+ },
8338
+ tracks: {},
8339
+ topics: {
8340
+ "realschule|10|mathematik": [
8341
+ { id: "algebra-funktionen", label: "Algebra und Funktionen" },
8342
+ { id: "geometrie", label: "Geometrie" }
8343
+ ],
8344
+ "gymnasium|10|mathematik": [
8345
+ { id: "analysis", label: "Analysis" },
8346
+ { id: "lineare-algebra", label: "Lineare Algebra" }
8347
+ ],
8348
+ "realschule|9|physik": [
8349
+ { id: "mechanik", label: "Mechanik" },
8350
+ { id: "energie", label: "Energie und W\xE4rme" }
8351
+ ],
8352
+ "realschule|9|chemie": [
8353
+ { id: "reaktionen", label: "Chemische Reaktionen" },
8354
+ { id: "stoffkreislauf", label: "Stoffkreisl\xE4ufe" }
8355
+ ],
8356
+ "realschule|9|biologie": [
8357
+ { id: "zellen", label: "Zellen" },
8358
+ { id: "oekologie", label: "\xD6kologie" }
8359
+ ],
8360
+ "gymnasium|9|physik": [
8361
+ { id: "elektrizitaet", label: "Elektrizit\xE4t und Magnetismus" }
8362
+ ],
8363
+ "gymnasium|9|chemie": [{ id: "bindungen", label: "Chemische Bindungen" }],
8364
+ "gymnasium|9|biologie": [{ id: "genetik", label: "Genetik" }]
8365
+ },
8366
+ contentUrls: {
8367
+ "realschule|10|mathematik": "https://cuvo.nibis.de/cuvo.php",
8368
+ "gymnasium|10|mathematik": "https://cuvo.nibis.de/cuvo.php",
8369
+ "realschule|9|physik": "https://cuvo.nibis.de/cuvo.php",
8370
+ "realschule|9|chemie": "https://cuvo.nibis.de/cuvo.php",
8371
+ "realschule|9|biologie": "https://cuvo.nibis.de/cuvo.php",
8372
+ "gymnasium|9|physik": "https://cuvo.nibis.de/cuvo.php",
8373
+ "gymnasium|9|chemie": "https://cuvo.nibis.de/cuvo.php",
8374
+ "gymnasium|9|biologie": "https://cuvo.nibis.de/cuvo.php"
8375
+ }
8376
+ };
8377
+
8378
+ // src/cli/curriculum/providers/kerncurriculum-niedersachsen/index.ts
8379
+ var kerncurriculumNiedersachsenProvider = {
8380
+ id: "kerncurriculum-niedersachsen",
8381
+ country: "DE",
8382
+ countryLabel: "Deutschland",
8383
+ region: "NI",
8384
+ regionLabel: "Niedersachsen",
8385
+ label: "Kerncurriculum (Niedersachsen)",
8386
+ listSchoolTypes() {
8387
+ return KERNCURRICULUM_NIEDERSACHSEN_MANIFEST.schoolTypes;
8388
+ },
8389
+ listGrades(schoolType) {
8390
+ return (KERNCURRICULUM_NIEDERSACHSEN_MANIFEST.grades[schoolType] || []).map((id) => ({
8391
+ id,
8392
+ label: `Klasse ${id}`
8393
+ }));
8394
+ },
8395
+ listSubjects(schoolType, _grade) {
8396
+ return KERNCURRICULUM_NIEDERSACHSEN_MANIFEST.subjects[schoolType] || [];
8397
+ },
8398
+ listTracks(schoolType, grade, subject) {
8399
+ const key = `${schoolType}|${grade}|${subject}`;
8400
+ return KERNCURRICULUM_NIEDERSACHSEN_MANIFEST.tracks[key] || [];
8401
+ },
8402
+ listTopics(selection) {
8403
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
8404
+ const list = KERNCURRICULUM_NIEDERSACHSEN_MANIFEST.topics[key] || [];
8405
+ return list.map((t2) => ({
8406
+ ...t2,
8407
+ sourceRef: key
8408
+ }));
8409
+ },
8410
+ resolveTopic(topic) {
8411
+ const uri = KERNCURRICULUM_NIEDERSACHSEN_MANIFEST.contentUrls[topic.sourceRef];
8412
+ if (!uri) {
8413
+ throw new Error(
8414
+ `Kerncurriculum Niedersachsen: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
8415
+ );
8416
+ }
8417
+ return {
8418
+ provider: "kerncurriculum-niedersachsen",
8419
+ topicId: `${topic.sourceRef}#${topic.id}`,
8420
+ uri
8421
+ };
8422
+ },
8423
+ extractTopics(html, topicIds) {
8424
+ const results = {};
8425
+ const headingMatches = Array.from(
8426
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
8427
+ );
8428
+ const sections = [];
8429
+ for (const m of headingMatches) {
8430
+ const headerText = cleanHtmlText6(m[1]).trim();
8431
+ const start = m.index + m[0].length;
8432
+ const nextHeading = html.indexOf("<h", start);
8433
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 1500);
8434
+ const content = cleanHtmlText6(rawContent).slice(0, 1500);
8435
+ if (headerText) sections.push({ header: headerText, content });
8436
+ }
8437
+ for (const topicId of topicIds) {
8438
+ const hashIdx = topicId.indexOf("#");
8439
+ if (hashIdx === -1) continue;
8440
+ const key = topicId.substring(0, hashIdx);
8441
+ const shortId = topicId.substring(hashIdx + 1);
8442
+ const list = KERNCURRICULUM_NIEDERSACHSEN_MANIFEST.topics[key] ?? [];
8443
+ const match = list.find((t2) => t2.id === shortId);
8444
+ if (!match) continue;
8445
+ const label = match.label;
8446
+ const normalizedLabel = normalizeForComparison6(label);
8447
+ let section = sections.find(
8448
+ (s) => normalizeForComparison6(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
8449
+ );
8450
+ if (!section && sections.length > 0) {
8451
+ section = sections.find((s) => s.content.length > 80) || sections[0];
8452
+ }
8453
+ if (section) {
8454
+ results[topicId] = `${label}
8455
+
8456
+ ${section.content}`.trim();
8457
+ } else {
8458
+ results[topicId] = label;
8459
+ }
8460
+ }
8461
+ return results;
8462
+ }
8463
+ };
8464
+ function cleanHtmlText6(html) {
8465
+ let text = html.replace(
8466
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
8467
+ " "
8468
+ );
8469
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
8470
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
8471
+ text = text.replace(/<p[^>]*>/gi, "\n");
8472
+ text = text.replace(/<br\s*\/?>/gi, "\n");
8473
+ text = text.replace(/<[^>]+>/g, " ");
8474
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
8475
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
8476
+ }
8477
+ function normalizeForComparison6(str) {
8478
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
8479
+ }
8480
+
8481
+ // src/cli/curriculum/providers/kernlehrplan-nrw/manifest.ts
8482
+ var KERNLEHRPLAN_NRW_MANIFEST = {
8483
+ schoolYear: "2025/2026",
8484
+ capturedOn: "2026-07-02",
8485
+ sourceRevision: "Kernlehrpl\xE4ne Sekundarstufe I NRW (Lehrplannavigator)",
8486
+ schoolTypes: [
8487
+ { id: "realschule", label: "Realschule" },
8488
+ { id: "gymnasium", label: "Gymnasium" }
8489
+ ],
8490
+ grades: {
8491
+ realschule: ["7", "8", "9", "10"],
8492
+ gymnasium: ["7", "8", "9", "10"]
8493
+ },
8494
+ subjects: {
8495
+ realschule: [
8496
+ { id: "mathematik", label: "Mathematik" },
8497
+ { id: "informatik", label: "Informatik" },
8498
+ { id: "physik", label: "Physik" },
8499
+ { id: "chemie", label: "Chemie" },
8500
+ { id: "biologie", label: "Biologie" }
8501
+ ],
8502
+ gymnasium: [
8503
+ { id: "mathematik", label: "Mathematik" },
8504
+ { id: "informatik", label: "Informatik" },
8505
+ { id: "physik", label: "Physik" },
8506
+ { id: "chemie", label: "Chemie" },
8507
+ { id: "biologie", label: "Biologie" }
8508
+ ]
8509
+ },
8510
+ tracks: {},
8511
+ topics: {
8512
+ // Minimal starter set for Realschule Mathematik (example for one grade)
8513
+ "realschule|10|mathematik": [
8514
+ { id: "arithmetik-algebra", label: "Arithmetik und Algebra" },
8515
+ { id: "funktionen", label: "Funktionen" },
8516
+ { id: "geometrie", label: "Geometrie" },
8517
+ { id: "stochastik", label: "Stochastik" }
8518
+ ],
8519
+ // Minimal starter for Realschule Informatik (from UV examples in 5/6 + typical)
8520
+ "realschule|8|informatik": [
8521
+ { id: "daten-codierung", label: "Daten und Codierung" },
8522
+ { id: "algorithmen", label: "Algorithmen" },
8523
+ {
8524
+ id: "ki-datenbewusstsein",
8525
+ label: "K\xFCnstliche Intelligenz und Datenbewusstsein"
8526
+ }
8527
+ ],
8528
+ // Starter for Gymnasium Mathematik (higher track)
8529
+ "gymnasium|10|mathematik": [
8530
+ { id: "analysis", label: "Analysis / Funktionen" },
8531
+ {
8532
+ id: "lineare-algebra-geometrie",
8533
+ label: "Lineare Algebra und Geometrie"
8534
+ },
8535
+ { id: "stochastik", label: "Stochastik" }
8536
+ ],
8537
+ // Starter for Gymnasium WP Informatik
8538
+ "gymnasium|10|informatik": [
8539
+ { id: "logische-schaltungen", label: "Logische Schaltungen" },
8540
+ {
8541
+ id: "algorithmen-programmierung",
8542
+ label: "Algorithmen und Programmierung"
8543
+ },
8544
+ { id: "daten-netze-sicherheit", label: "Daten, Netze und Sicherheit" }
8545
+ ],
8546
+ // Naturwissenschaften for Realschule example
8547
+ "realschule|9|physik": [
8548
+ { id: "mechanik-kraefte", label: "Mechanik und Kr\xE4fte" },
8549
+ { id: "waerme-energie", label: "W\xE4rme und Energie" },
8550
+ { id: "elektrizitaet", label: "Elektrizit\xE4t" }
8551
+ ],
8552
+ "realschule|9|chemie": [
8553
+ { id: "stoffe-eigenschaften", label: "Stoffe und ihre Eigenschaften" },
8554
+ { id: "reaktionen", label: "Chemische Reaktionen" },
8555
+ { id: "atombau", label: "Atombau und Periodensystem" }
8556
+ ],
8557
+ "realschule|9|biologie": [
8558
+ { id: "zellen-lebewesen", label: "Zellen und Lebewesen" },
8559
+ { id: "vererbung", label: "Vererbung und Genetik" },
8560
+ { id: "oekosysteme", label: "\xD6kosysteme" }
8561
+ ],
8562
+ // Naturwissenschaften for Gymnasium
8563
+ "gymnasium|9|physik": [
8564
+ { id: "optik", label: "Optik" },
8565
+ { id: "elektromagnetismus", label: "Elektromagnetismus" },
8566
+ { id: "atomphysik", label: "Atom- und Kernphysik" }
8567
+ ],
8568
+ "gymnasium|9|chemie": [
8569
+ { id: "chemische-bindung", label: "Chemische Bindung" },
8570
+ { id: "saeuren-basen-salze", label: "S\xE4uren, Basen, Salze" },
8571
+ { id: "organische", label: "Organische Chemie" }
8572
+ ],
8573
+ "gymnasium|9|biologie": [
8574
+ { id: "genetik-evolution", label: "Genetik und Evolution" },
8575
+ { id: "physiologie", label: "Physiologie des Menschen" },
8576
+ { id: "oekologie-umwelt", label: "\xD6kologie und Umweltschutz" }
8577
+ ]
8578
+ },
8579
+ contentUrls: {
8580
+ "realschule|10|mathematik": "https://lehrplannavigator.nrw.de/sekundarstufe-i/realschule/mathematik-neu-ab-20222023",
8581
+ "realschule|8|informatik": "https://lehrplannavigator.nrw.de/sekundarstufe-i/realschule/informatik-realschule-neu-ab-20212022",
8582
+ "gymnasium|10|mathematik": "https://lehrplannavigator.nrw.de/sekundarstufe-i/kernlehrplaene-fuer-das-gymnasium-ab-20192020/mathematik",
8583
+ "gymnasium|10|informatik": "https://lehrplannavigator.nrw.de/sekundarstufe-i/kernlehrplaene-fuer-das-gymnasium-ab-20192020/wp-informatik-neu-ab-20232024",
8584
+ "realschule|9|physik": "https://lehrplannavigator.nrw.de/sekundarstufe-i/realschule/physik",
8585
+ "realschule|9|chemie": "https://lehrplannavigator.nrw.de/sekundarstufe-i/realschule/chemie",
8586
+ "realschule|9|biologie": "https://lehrplannavigator.nrw.de/sekundarstufe-i/realschule/biologie",
8587
+ "gymnasium|9|physik": "https://lehrplannavigator.nrw.de/sekundarstufe-i/kernlehrplaene-fuer-das-gymnasium-ab-20192020/physik",
8588
+ "gymnasium|9|chemie": "https://lehrplannavigator.nrw.de/sekundarstufe-i/kernlehrplaene-fuer-das-gymnasium-ab-20192020/chemie",
8589
+ "gymnasium|9|biologie": "https://lehrplannavigator.nrw.de/sekundarstufe-i/kernlehrplaene-fuer-das-gymnasium-ab-20192020/biologie"
8590
+ }
8591
+ };
8592
+
8593
+ // src/cli/curriculum/providers/kernlehrplan-nrw/index.ts
8594
+ var kernlehrplanNrwProvider = {
8595
+ id: "kernlehrplan-nrw",
8596
+ country: "DE",
8597
+ countryLabel: "Deutschland",
8598
+ region: "NW",
8599
+ regionLabel: "Nordrhein-Westfalen",
8600
+ label: "Kernlehrplan (Nordrhein-Westfalen)",
8601
+ listSchoolTypes() {
8602
+ return KERNLEHRPLAN_NRW_MANIFEST.schoolTypes;
8603
+ },
8604
+ listGrades(schoolType) {
8605
+ return (KERNLEHRPLAN_NRW_MANIFEST.grades[schoolType] || []).map((id) => ({
8606
+ id,
8607
+ label: `Klasse ${id}`
8608
+ }));
8609
+ },
8610
+ listSubjects(schoolType, _grade) {
8611
+ return KERNLEHRPLAN_NRW_MANIFEST.subjects[schoolType] || [];
8612
+ },
8613
+ listTracks(schoolType, grade, subject) {
8614
+ const key = `${schoolType}|${grade}|${subject}`;
8615
+ return KERNLEHRPLAN_NRW_MANIFEST.tracks[key] || [];
8616
+ },
8617
+ listTopics(selection) {
8618
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
8619
+ const list = KERNLEHRPLAN_NRW_MANIFEST.topics[key] || [];
8620
+ return list.map((t2) => ({
8621
+ ...t2,
8622
+ sourceRef: key
8623
+ }));
8624
+ },
8625
+ resolveTopic(topic) {
8626
+ const uri = KERNLEHRPLAN_NRW_MANIFEST.contentUrls[topic.sourceRef];
8627
+ if (!uri) {
8628
+ throw new Error(
8629
+ `Kernlehrplan NRW: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
8630
+ );
8631
+ }
8632
+ return {
8633
+ provider: "kernlehrplan-nrw",
8634
+ topicId: `${topic.sourceRef}#${topic.id}`,
8635
+ uri
8636
+ };
8637
+ },
8638
+ extractTopics(html, topicIds) {
8639
+ const results = {};
8640
+ const headingMatches = Array.from(
8641
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
8642
+ );
8643
+ const sections = [];
8644
+ for (const m of headingMatches) {
8645
+ const headerText = cleanHtmlText7(m[1]).trim();
8646
+ const start = m.index + m[0].length;
8647
+ const nextHeading = html.indexOf("<h", start);
8648
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 2e3);
8649
+ const content = cleanHtmlText7(rawContent).slice(0, 2e3);
8650
+ if (headerText) sections.push({ header: headerText, content });
8651
+ }
8652
+ for (const topicId of topicIds) {
8653
+ const hashIdx = topicId.indexOf("#");
8654
+ if (hashIdx === -1) continue;
8655
+ const key = topicId.substring(0, hashIdx);
8656
+ const shortId = topicId.substring(hashIdx + 1);
8657
+ const list = KERNLEHRPLAN_NRW_MANIFEST.topics[key] ?? [];
8658
+ const match = list.find((t2) => t2.id === shortId);
8659
+ if (!match) continue;
8660
+ const label = match.label;
8661
+ const normalizedLabel = normalizeForComparison7(label);
8662
+ let section = sections.find(
8663
+ (s) => normalizeForComparison7(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
8664
+ );
8665
+ if (!section && sections.length > 0) {
8666
+ section = sections.find((s) => s.content.length > 100) || sections[0];
8667
+ }
8668
+ if (section) {
8669
+ results[topicId] = `${label}
8670
+
8671
+ ${section.content}`.trim();
8672
+ } else {
8673
+ results[topicId] = label;
8674
+ }
8675
+ }
8676
+ return results;
8677
+ }
8678
+ };
8679
+ function cleanHtmlText7(html) {
8680
+ let text = html.replace(
8681
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
8682
+ " "
8683
+ );
8684
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
8685
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
8686
+ text = text.replace(/<p[^>]*>/gi, "\n");
8687
+ text = text.replace(/<br\s*\/?>/gi, "\n");
8688
+ text = text.replace(/<[^>]+>/g, " ");
8689
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&ndash;/g, "\u2013").replace(/&mdash;/g, "\u2014");
8690
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
8691
+ }
8692
+ function normalizeForComparison7(str) {
8693
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
8694
+ }
8695
+
8696
+ // src/cli/curriculum/providers/lehrplaene-rp/manifest.ts
8697
+ var LEHRPLAENE_RP_MANIFEST = {
8698
+ schoolYear: "2025/2026",
8699
+ capturedOn: "2026-07-02",
8700
+ sourceRevision: "Lehrpl\xE4ne Rheinland-Pfalz",
8701
+ schoolTypes: [
8702
+ { id: "realschule-plus", label: "Realschule plus" },
8703
+ { id: "gymnasium", label: "Gymnasium" }
8704
+ ],
8705
+ grades: {
8706
+ "realschule-plus": ["7", "8", "9", "10"],
8707
+ gymnasium: ["7", "8", "9", "10"]
8708
+ },
8709
+ subjects: {
8710
+ "realschule-plus": [
8711
+ { id: "mathematik", label: "Mathematik" },
8712
+ { id: "informatik", label: "Informatik" },
8713
+ { id: "physik", label: "Physik" },
8714
+ { id: "chemie", label: "Chemie" },
8715
+ { id: "biologie", label: "Biologie" }
8716
+ ],
8717
+ gymnasium: [
8718
+ { id: "mathematik", label: "Mathematik" },
8719
+ { id: "informatik", label: "Informatik" },
8720
+ { id: "physik", label: "Physik" },
8721
+ { id: "chemie", label: "Chemie" },
8722
+ { id: "biologie", label: "Biologie" }
8723
+ ]
8724
+ },
8725
+ tracks: {},
8726
+ topics: {
8727
+ "realschule-plus|10|mathematik": [
8728
+ { id: "funktionen", label: "Funktionen" }
8729
+ ],
8730
+ "gymnasium|10|mathematik": [{ id: "analysis", label: "Analysis" }],
8731
+ "realschule-plus|9|physik": [{ id: "mechanik", label: "Mechanik" }],
8732
+ "realschule-plus|9|chemie": [{ id: "reaktionen", label: "Reaktionen" }],
8733
+ "realschule-plus|9|biologie": [{ id: "zellen", label: "Zellen" }],
8734
+ "gymnasium|9|physik": [{ id: "elektrizitaet", label: "Elektrizit\xE4t" }],
8735
+ "gymnasium|9|chemie": [{ id: "bindungen", label: "Bindungen" }],
8736
+ "gymnasium|9|biologie": [{ id: "genetik", label: "Genetik" }]
8737
+ },
8738
+ contentUrls: {
8739
+ "realschule-plus|10|mathematik": "https://lehrplaene.bildung-rp.de/",
8740
+ "gymnasium|10|mathematik": "https://lehrplaene.bildung-rp.de/",
8741
+ "realschule-plus|9|physik": "https://lehrplaene.bildung-rp.de/",
8742
+ "realschule-plus|9|chemie": "https://lehrplaene.bildung-rp.de/",
8743
+ "realschule-plus|9|biologie": "https://lehrplaene.bildung-rp.de/",
8744
+ "gymnasium|9|physik": "https://lehrplaene.bildung-rp.de/",
8745
+ "gymnasium|9|chemie": "https://lehrplaene.bildung-rp.de/",
8746
+ "gymnasium|9|biologie": "https://lehrplaene.bildung-rp.de/"
8747
+ }
8748
+ };
8749
+
8750
+ // src/cli/curriculum/providers/lehrplaene-rp/index.ts
8751
+ var lehrplaeneRpProvider = {
8752
+ id: "lehrplaene-rp",
8753
+ country: "DE",
8754
+ countryLabel: "Deutschland",
8755
+ region: "RP",
8756
+ regionLabel: "Rheinland-Pfalz",
8757
+ label: "Lehrpl\xE4ne (Rheinland-Pfalz)",
8758
+ listSchoolTypes() {
8759
+ return LEHRPLAENE_RP_MANIFEST.schoolTypes;
8760
+ },
8761
+ listGrades(schoolType) {
8762
+ return (LEHRPLAENE_RP_MANIFEST.grades[schoolType] || []).map((id) => ({
8763
+ id,
8764
+ label: `Klasse ${id}`
8765
+ }));
8766
+ },
8767
+ listSubjects(schoolType, _grade) {
8768
+ return LEHRPLAENE_RP_MANIFEST.subjects[schoolType] || [];
8769
+ },
8770
+ listTracks(schoolType, grade, subject) {
8771
+ const key = `${schoolType}|${grade}|${subject}`;
8772
+ return LEHRPLAENE_RP_MANIFEST.tracks[key] || [];
8773
+ },
8774
+ listTopics(selection) {
8775
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
8776
+ const list = LEHRPLAENE_RP_MANIFEST.topics[key] || [];
8777
+ return list.map((t2) => ({
8778
+ ...t2,
8779
+ sourceRef: key
8780
+ }));
8781
+ },
8782
+ resolveTopic(topic) {
8783
+ const uri = LEHRPLAENE_RP_MANIFEST.contentUrls[topic.sourceRef];
8784
+ if (!uri) {
8785
+ throw new Error(
8786
+ `Lehrpl\xE4ne RP: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
8787
+ );
8788
+ }
8789
+ return {
8790
+ provider: "lehrplaene-rp",
8791
+ topicId: `${topic.sourceRef}#${topic.id}`,
8792
+ uri
8793
+ };
8794
+ },
8795
+ extractTopics(html, topicIds) {
8796
+ const results = {};
8797
+ const headingMatches = Array.from(
8798
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
8799
+ );
8800
+ const sections = [];
8801
+ for (const m of headingMatches) {
8802
+ const headerText = cleanHtmlText8(m[1]).trim();
8803
+ const start = m.index + m[0].length;
8804
+ const nextHeading = html.indexOf("<h", start);
8805
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 1500);
8806
+ const content = cleanHtmlText8(rawContent).slice(0, 1500);
8807
+ if (headerText) sections.push({ header: headerText, content });
8808
+ }
8809
+ for (const topicId of topicIds) {
8810
+ const hashIdx = topicId.indexOf("#");
8811
+ if (hashIdx === -1) continue;
8812
+ const key = topicId.substring(0, hashIdx);
8813
+ const shortId = topicId.substring(hashIdx + 1);
8814
+ const list = LEHRPLAENE_RP_MANIFEST.topics[key] ?? [];
8815
+ const match = list.find((t2) => t2.id === shortId);
8816
+ if (!match) continue;
8817
+ const label = match.label;
8818
+ const normalizedLabel = normalizeForComparison8(label);
8819
+ let section = sections.find(
8820
+ (s) => normalizeForComparison8(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
8821
+ );
8822
+ if (!section && sections.length > 0) {
8823
+ section = sections.find((s) => s.content.length > 80) || sections[0];
8824
+ }
8825
+ if (section) {
8826
+ results[topicId] = `${label}
8827
+
8828
+ ${section.content}`.trim();
8829
+ } else {
8830
+ results[topicId] = label;
8831
+ }
8832
+ }
8833
+ return results;
8834
+ }
8835
+ };
8836
+ function cleanHtmlText8(html) {
8837
+ let text = html.replace(
8838
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
8839
+ " "
8840
+ );
8841
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
8842
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
8843
+ text = text.replace(/<p[^>]*>/gi, "\n");
8844
+ text = text.replace(/<br\s*\/?>/gi, "\n");
8845
+ text = text.replace(/<[^>]+>/g, " ");
8846
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
8847
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
8848
+ }
8849
+ function normalizeForComparison8(str) {
8850
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
8851
+ }
8852
+
8853
+ // src/cli/curriculum/providers/lehrplan-saarland/manifest.ts
8854
+ var LEHRPLAN_SAARLAND_MANIFEST = {
8855
+ schoolYear: "2025/2026",
8856
+ capturedOn: "2026-07-02",
8857
+ sourceRevision: "Lehrpl\xE4ne Saarland",
8858
+ schoolTypes: [
8859
+ { id: "gemeinschaftsschule", label: "Gemeinschaftsschule" },
8860
+ { id: "gymnasium", label: "Gymnasium" }
8861
+ ],
8862
+ grades: {
8863
+ gemeinschaftsschule: ["7", "8", "9", "10"],
8864
+ gymnasium: ["7", "8", "9", "10"]
8865
+ },
8866
+ subjects: {
8867
+ gemeinschaftsschule: [
8868
+ { id: "mathematik", label: "Mathematik" },
8869
+ { id: "informatik", label: "Informatik" },
8870
+ { id: "physik", label: "Physik" },
8871
+ { id: "chemie", label: "Chemie" },
8872
+ { id: "biologie", label: "Biologie" }
8873
+ ],
8874
+ gymnasium: [
8875
+ { id: "mathematik", label: "Mathematik" },
8876
+ { id: "informatik", label: "Informatik" },
8877
+ { id: "physik", label: "Physik" },
8878
+ { id: "chemie", label: "Chemie" },
8879
+ { id: "biologie", label: "Biologie" }
8880
+ ]
8881
+ },
8882
+ tracks: {},
8883
+ topics: {
8884
+ "gemeinschaftsschule|10|mathematik": [
8885
+ { id: "funktionen", label: "Funktionen" }
8886
+ ],
8887
+ "gymnasium|10|mathematik": [{ id: "analysis", label: "Analysis" }],
8888
+ "gemeinschaftsschule|9|physik": [{ id: "mechanik", label: "Mechanik" }],
8889
+ "gemeinschaftsschule|9|chemie": [{ id: "reaktionen", label: "Reaktionen" }],
8890
+ "gemeinschaftsschule|9|biologie": [{ id: "zellen", label: "Zellen" }],
8891
+ "gymnasium|9|physik": [{ id: "elektrizitaet", label: "Elektrizit\xE4t" }],
8892
+ "gymnasium|9|chemie": [{ id: "bindungen", label: "Bindungen" }],
8893
+ "gymnasium|9|biologie": [{ id: "genetik", label: "Genetik" }]
8894
+ },
8895
+ contentUrls: {
8896
+ "gemeinschaftsschule|10|mathematik": "https://www.saarland.de/mbk/DE/portale/bildungsserver/bildungsthemen/lehrplaenehandreichungen",
8897
+ "gymnasium|10|mathematik": "https://www.saarland.de/mbk/DE/portale/bildungsserver/bildungsthemen/lehrplaenehandreichungen",
8898
+ "gemeinschaftsschule|9|physik": "https://www.saarland.de/mbk/DE/portale/bildungsserver/bildungsthemen/lehrplaenehandreichungen",
8899
+ "gemeinschaftsschule|9|chemie": "https://www.saarland.de/mbk/DE/portale/bildungsserver/bildungsthemen/lehrplaenehandreichungen",
8900
+ "gemeinschaftsschule|9|biologie": "https://www.saarland.de/mbk/DE/portale/bildungsserver/bildungsthemen/lehrplaenehandreichungen",
8901
+ "gymnasium|9|physik": "https://www.saarland.de/mbk/DE/portale/bildungsserver/bildungsthemen/lehrplaenehandreichungen",
8902
+ "gymnasium|9|chemie": "https://www.saarland.de/mbk/DE/portale/bildungsserver/bildungsthemen/lehrplaenehandreichungen",
8903
+ "gymnasium|9|biologie": "https://www.saarland.de/mbk/DE/portale/bildungsserver/bildungsthemen/lehrplaenehandreichungen"
8904
+ }
8905
+ };
8906
+
8907
+ // src/cli/curriculum/providers/lehrplan-saarland/index.ts
8908
+ var lehrplanSaarlandProvider = {
8909
+ id: "lehrplan-saarland",
8910
+ country: "DE",
8911
+ countryLabel: "Deutschland",
8912
+ region: "SL",
8913
+ regionLabel: "Saarland",
8914
+ label: "Lehrplan (Saarland)",
8915
+ listSchoolTypes() {
8916
+ return LEHRPLAN_SAARLAND_MANIFEST.schoolTypes;
8917
+ },
8918
+ listGrades(schoolType) {
8919
+ return (LEHRPLAN_SAARLAND_MANIFEST.grades[schoolType] || []).map((id) => ({
8920
+ id,
8921
+ label: `Klasse ${id}`
8922
+ }));
8923
+ },
8924
+ listSubjects(schoolType, _grade) {
8925
+ return LEHRPLAN_SAARLAND_MANIFEST.subjects[schoolType] || [];
8926
+ },
8927
+ listTracks(schoolType, grade, subject) {
8928
+ const key = `${schoolType}|${grade}|${subject}`;
8929
+ return LEHRPLAN_SAARLAND_MANIFEST.tracks[key] || [];
8930
+ },
8931
+ listTopics(selection) {
8932
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
8933
+ const list = LEHRPLAN_SAARLAND_MANIFEST.topics[key] || [];
8934
+ return list.map((t2) => ({
8935
+ ...t2,
8936
+ sourceRef: key
8937
+ }));
8938
+ },
8939
+ resolveTopic(topic) {
8940
+ const uri = LEHRPLAN_SAARLAND_MANIFEST.contentUrls[topic.sourceRef];
8941
+ if (!uri) {
8942
+ throw new Error(
8943
+ `Lehrplan Saarland: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
8944
+ );
8945
+ }
8946
+ return {
8947
+ provider: "lehrplan-saarland",
8948
+ topicId: `${topic.sourceRef}#${topic.id}`,
8949
+ uri
8950
+ };
8951
+ },
8952
+ extractTopics(html, topicIds) {
8953
+ const results = {};
8954
+ const headingMatches = Array.from(
8955
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
8956
+ );
8957
+ const sections = [];
8958
+ for (const m of headingMatches) {
8959
+ const headerText = cleanHtmlText9(m[1]).trim();
8960
+ const start = m.index + m[0].length;
8961
+ const nextHeading = html.indexOf("<h", start);
8962
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 1500);
8963
+ const content = cleanHtmlText9(rawContent).slice(0, 1500);
8964
+ if (headerText) sections.push({ header: headerText, content });
8965
+ }
8966
+ for (const topicId of topicIds) {
8967
+ const hashIdx = topicId.indexOf("#");
8968
+ if (hashIdx === -1) continue;
8969
+ const key = topicId.substring(0, hashIdx);
8970
+ const shortId = topicId.substring(hashIdx + 1);
8971
+ const list = LEHRPLAN_SAARLAND_MANIFEST.topics[key] ?? [];
8972
+ const match = list.find((t2) => t2.id === shortId);
8973
+ if (!match) continue;
8974
+ const label = match.label;
8975
+ const normalizedLabel = normalizeForComparison9(label);
8976
+ let section = sections.find(
8977
+ (s) => normalizeForComparison9(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
8978
+ );
8979
+ if (!section && sections.length > 0) {
8980
+ section = sections.find((s) => s.content.length > 80) || sections[0];
8981
+ }
8982
+ if (section) {
8983
+ results[topicId] = `${label}
8984
+
8985
+ ${section.content}`.trim();
8986
+ } else {
8987
+ results[topicId] = label;
8988
+ }
8989
+ }
8990
+ return results;
8991
+ }
8992
+ };
8993
+ function cleanHtmlText9(html) {
8994
+ let text = html.replace(
8995
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
8996
+ " "
8997
+ );
8998
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
8999
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
9000
+ text = text.replace(/<p[^>]*>/gi, "\n");
9001
+ text = text.replace(/<br\s*\/?>/gi, "\n");
9002
+ text = text.replace(/<[^>]+>/g, " ");
9003
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
9004
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
9005
+ }
9006
+ function normalizeForComparison9(str) {
9007
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
9008
+ }
9009
+
9010
+ // src/cli/curriculum/providers/lehrplan-sachsen/manifest.ts
9011
+ var LEHRPLAN_SACHSEN_MANIFEST = {
9012
+ schoolYear: "2025/2026",
9013
+ capturedOn: "2026-07-02",
9014
+ sourceRevision: "S\xE4chsische Lehrpl\xE4ne (Datenbank)",
9015
+ schoolTypes: [
9016
+ { id: "oberschule", label: "Oberschule (Realschule)" },
9017
+ { id: "gymnasium", label: "Gymnasium" }
9018
+ ],
9019
+ grades: {
9020
+ oberschule: ["7", "8", "9", "10"],
9021
+ gymnasium: ["7", "8", "9", "10"]
9022
+ },
9023
+ subjects: {
9024
+ oberschule: [
9025
+ { id: "mathematik", label: "Mathematik" },
9026
+ { id: "informatik", label: "Informatik" },
9027
+ { id: "physik", label: "Physik" },
9028
+ { id: "chemie", label: "Chemie" },
9029
+ { id: "biologie", label: "Biologie" }
9030
+ ],
9031
+ gymnasium: [
9032
+ { id: "mathematik", label: "Mathematik" },
9033
+ { id: "informatik", label: "Informatik" },
9034
+ { id: "physik", label: "Physik" },
9035
+ { id: "chemie", label: "Chemie" },
9036
+ { id: "biologie", label: "Biologie" }
9037
+ ]
9038
+ },
9039
+ tracks: {},
9040
+ topics: {
9041
+ "oberschule|10|mathematik": [
9042
+ { id: "prozentrechnung", label: "Prozentrechnung und Zinsrechnung" },
9043
+ { id: "funktionen", label: "Funktionale Zusammenh\xE4nge" },
9044
+ { id: "geometrie", label: "Geometrie und Messen" }
9045
+ ],
9046
+ "gymnasium|10|mathematik": [
9047
+ { id: "analysis", label: "Analysis und Funktionen" },
9048
+ { id: "geometrie", label: "Analytische Geometrie" }
9049
+ ],
9050
+ "oberschule|9|informatik": [
9051
+ { id: "algorithmen", label: "Algorithmen und Programmierung" },
9052
+ { id: "daten", label: "Daten und Datenschutz" }
9053
+ ],
9054
+ "oberschule|9|physik": [
9055
+ { id: "mechanik", label: "Mechanik und Bewegung" },
9056
+ { id: "energie", label: "Energie und W\xE4rme" }
9057
+ ],
9058
+ "oberschule|9|chemie": [
9059
+ { id: "stoffe", label: "Stoffe und chemische Reaktionen" },
9060
+ { id: "atome", label: "Atome und Periodensystem" }
9061
+ ],
9062
+ "oberschule|9|biologie": [
9063
+ { id: "zellen", label: "Zellen und Gewebe" },
9064
+ { id: "oekologie", label: "\xD6kologie und Umwelt" }
9065
+ ],
9066
+ "gymnasium|9|physik": [
9067
+ { id: "elektrizitaet", label: "Elektrizit\xE4t und Magnetismus" },
9068
+ { id: "optik", label: "Optik" }
9069
+ ],
9070
+ "gymnasium|9|chemie": [
9071
+ { id: "bindungen", label: "Chemische Bindungen" },
9072
+ { id: "reaktionen", label: "Chemische Reaktionen" }
9073
+ ],
9074
+ "gymnasium|9|biologie": [
9075
+ { id: "genetik", label: "Genetik und Vererbung" },
9076
+ { id: "evolution", label: "Evolution" }
9077
+ ]
9078
+ },
9079
+ contentUrls: {
9080
+ "oberschule|10|mathematik": "https://www.schulportal.sachsen.de/lplandb/",
9081
+ "gymnasium|10|mathematik": "https://www.schulportal.sachsen.de/lplandb/",
9082
+ "oberschule|9|informatik": "https://www.schulportal.sachsen.de/lplandb/",
9083
+ "oberschule|9|physik": "https://www.schulportal.sachsen.de/lplandb/",
9084
+ "oberschule|9|chemie": "https://www.schulportal.sachsen.de/lplandb/",
9085
+ "oberschule|9|biologie": "https://www.schulportal.sachsen.de/lplandb/",
9086
+ "gymnasium|9|physik": "https://www.schulportal.sachsen.de/lplandb/",
9087
+ "gymnasium|9|chemie": "https://www.schulportal.sachsen.de/lplandb/",
9088
+ "gymnasium|9|biologie": "https://www.schulportal.sachsen.de/lplandb/"
9089
+ }
9090
+ };
9091
+
9092
+ // src/cli/curriculum/providers/lehrplan-sachsen/index.ts
9093
+ var lehrplanSachsenProvider = {
9094
+ id: "lehrplan-sachsen",
9095
+ country: "DE",
9096
+ countryLabel: "Deutschland",
9097
+ region: "SN",
9098
+ regionLabel: "Sachsen",
9099
+ label: "Lehrplan (Sachsen)",
9100
+ listSchoolTypes() {
9101
+ return LEHRPLAN_SACHSEN_MANIFEST.schoolTypes;
9102
+ },
9103
+ listGrades(schoolType) {
9104
+ return (LEHRPLAN_SACHSEN_MANIFEST.grades[schoolType] || []).map((id) => ({
9105
+ id,
9106
+ label: `Klasse ${id}`
9107
+ }));
9108
+ },
9109
+ listSubjects(schoolType, _grade) {
9110
+ return LEHRPLAN_SACHSEN_MANIFEST.subjects[schoolType] || [];
9111
+ },
9112
+ listTracks(schoolType, grade, subject) {
9113
+ const key = `${schoolType}|${grade}|${subject}`;
9114
+ return LEHRPLAN_SACHSEN_MANIFEST.tracks[key] || [];
9115
+ },
9116
+ listTopics(selection) {
9117
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
9118
+ const list = LEHRPLAN_SACHSEN_MANIFEST.topics[key] || [];
9119
+ return list.map((t2) => ({
9120
+ ...t2,
9121
+ sourceRef: key
9122
+ }));
9123
+ },
9124
+ resolveTopic(topic) {
9125
+ const uri = LEHRPLAN_SACHSEN_MANIFEST.contentUrls[topic.sourceRef];
9126
+ if (!uri) {
9127
+ throw new Error(
9128
+ `Lehrplan Sachsen: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
9129
+ );
9130
+ }
9131
+ return {
9132
+ provider: "lehrplan-sachsen",
9133
+ topicId: `${topic.sourceRef}#${topic.id}`,
9134
+ uri
9135
+ };
9136
+ },
9137
+ extractTopics(html, topicIds) {
9138
+ const results = {};
9139
+ const headingMatches = Array.from(
9140
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
9141
+ );
9142
+ const sections = [];
9143
+ for (const m of headingMatches) {
9144
+ const headerText = cleanHtmlText10(m[1]).trim();
9145
+ const start = m.index + m[0].length;
9146
+ const nextHeading = html.indexOf("<h", start);
9147
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 1500);
9148
+ const content = cleanHtmlText10(rawContent).slice(0, 1500);
9149
+ if (headerText) sections.push({ header: headerText, content });
9150
+ }
9151
+ for (const topicId of topicIds) {
9152
+ const hashIdx = topicId.indexOf("#");
9153
+ if (hashIdx === -1) continue;
9154
+ const key = topicId.substring(0, hashIdx);
9155
+ const shortId = topicId.substring(hashIdx + 1);
9156
+ const list = LEHRPLAN_SACHSEN_MANIFEST.topics[key] ?? [];
9157
+ const match = list.find((t2) => t2.id === shortId);
9158
+ if (!match) continue;
9159
+ const label = match.label;
9160
+ const normalizedLabel = normalizeForComparison10(label);
9161
+ let section = sections.find(
9162
+ (s) => normalizeForComparison10(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
9163
+ );
9164
+ if (!section && sections.length > 0) {
9165
+ section = sections.find((s) => s.content.length > 80) || sections[0];
9166
+ }
9167
+ if (section) {
9168
+ results[topicId] = `${label}
9169
+
9170
+ ${section.content}`.trim();
9171
+ } else {
9172
+ results[topicId] = label;
9173
+ }
9174
+ }
9175
+ return results;
9176
+ }
9177
+ };
9178
+ function cleanHtmlText10(html) {
9179
+ let text = html.replace(
9180
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
9181
+ " "
9182
+ );
9183
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
9184
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
9185
+ text = text.replace(/<p[^>]*>/gi, "\n");
9186
+ text = text.replace(/<br\s*\/?>/gi, "\n");
9187
+ text = text.replace(/<[^>]+>/g, " ");
9188
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
9189
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
9190
+ }
9191
+ function normalizeForComparison10(str) {
9192
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
9193
+ }
9194
+
9195
+ // src/cli/curriculum/providers/lehrplan-thueringen/manifest.ts
9196
+ var LEHRPLAN_THUERINGEN_MANIFEST = {
9197
+ schoolYear: "2025/2026",
9198
+ capturedOn: "2026-07-02",
9199
+ sourceRevision: "Lehrpl\xE4ne Th\xFCringen",
9200
+ schoolTypes: [
9201
+ { id: "regelschule", label: "Regelschule" },
9202
+ { id: "gymnasium", label: "Gymnasium" }
9203
+ ],
9204
+ grades: {
9205
+ regelschule: ["7", "8", "9", "10"],
9206
+ gymnasium: ["7", "8", "9", "10"]
9207
+ },
9208
+ subjects: {
9209
+ regelschule: [
9210
+ { id: "mathematik", label: "Mathematik" },
9211
+ { id: "informatik", label: "Informatik" },
9212
+ { id: "physik", label: "Physik" },
9213
+ { id: "chemie", label: "Chemie" },
9214
+ { id: "biologie", label: "Biologie" }
9215
+ ],
9216
+ gymnasium: [
9217
+ { id: "mathematik", label: "Mathematik" },
9218
+ { id: "informatik", label: "Informatik" },
9219
+ { id: "physik", label: "Physik" },
9220
+ { id: "chemie", label: "Chemie" },
9221
+ { id: "biologie", label: "Biologie" }
9222
+ ]
9223
+ },
9224
+ tracks: {},
9225
+ topics: {
9226
+ "regelschule|10|mathematik": [{ id: "funktionen", label: "Funktionen" }],
9227
+ "gymnasium|10|mathematik": [{ id: "analysis", label: "Analysis" }],
9228
+ "regelschule|9|physik": [{ id: "mechanik", label: "Mechanik" }],
9229
+ "regelschule|9|chemie": [{ id: "reaktionen", label: "Reaktionen" }],
9230
+ "regelschule|9|biologie": [{ id: "zellen", label: "Zellen" }],
9231
+ "gymnasium|9|physik": [{ id: "elektrizitaet", label: "Elektrizit\xE4t" }],
9232
+ "gymnasium|9|chemie": [{ id: "bindungen", label: "Bindungen" }],
9233
+ "gymnasium|9|biologie": [{ id: "genetik", label: "Genetik" }]
9234
+ },
9235
+ contentUrls: {
9236
+ "regelschule|10|mathematik": "https://www.schulportal-thueringen.de/web/guest/lehrplaene",
9237
+ "gymnasium|10|mathematik": "https://www.schulportal-thueringen.de/web/guest/lehrplaene",
9238
+ "regelschule|9|physik": "https://www.schulportal-thueringen.de/web/guest/lehrplaene",
9239
+ "regelschule|9|chemie": "https://www.schulportal-thueringen.de/web/guest/lehrplaene",
9240
+ "regelschule|9|biologie": "https://www.schulportal-thueringen.de/web/guest/lehrplaene",
9241
+ "gymnasium|9|physik": "https://www.schulportal-thueringen.de/web/guest/lehrplaene",
9242
+ "gymnasium|9|chemie": "https://www.schulportal-thueringen.de/web/guest/lehrplaene",
9243
+ "gymnasium|9|biologie": "https://www.schulportal-thueringen.de/web/guest/lehrplaene"
9244
+ }
9245
+ };
9246
+
9247
+ // src/cli/curriculum/providers/lehrplan-thueringen/index.ts
9248
+ var lehrplanThueringenProvider = {
9249
+ id: "lehrplan-thueringen",
9250
+ country: "DE",
9251
+ countryLabel: "Deutschland",
9252
+ region: "TH",
9253
+ regionLabel: "Th\xFCringen",
9254
+ label: "Lehrplan (Th\xFCringen)",
9255
+ listSchoolTypes() {
9256
+ return LEHRPLAN_THUERINGEN_MANIFEST.schoolTypes;
9257
+ },
9258
+ listGrades(schoolType) {
9259
+ return (LEHRPLAN_THUERINGEN_MANIFEST.grades[schoolType] || []).map((id) => ({
9260
+ id,
9261
+ label: `Klasse ${id}`
9262
+ }));
9263
+ },
9264
+ listSubjects(schoolType, _grade) {
9265
+ return LEHRPLAN_THUERINGEN_MANIFEST.subjects[schoolType] || [];
9266
+ },
9267
+ listTracks(schoolType, grade, subject) {
9268
+ const key = `${schoolType}|${grade}|${subject}`;
9269
+ return LEHRPLAN_THUERINGEN_MANIFEST.tracks[key] || [];
9270
+ },
9271
+ listTopics(selection) {
9272
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
9273
+ const list = LEHRPLAN_THUERINGEN_MANIFEST.topics[key] || [];
9274
+ return list.map((t2) => ({
9275
+ ...t2,
9276
+ sourceRef: key
9277
+ }));
9278
+ },
9279
+ resolveTopic(topic) {
9280
+ const uri = LEHRPLAN_THUERINGEN_MANIFEST.contentUrls[topic.sourceRef];
9281
+ if (!uri) {
9282
+ throw new Error(
9283
+ `Lehrplan Th\xFCringen: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
9284
+ );
9285
+ }
9286
+ return {
9287
+ provider: "lehrplan-thueringen",
9288
+ topicId: `${topic.sourceRef}#${topic.id}`,
9289
+ uri
9290
+ };
9291
+ },
9292
+ extractTopics(html, topicIds) {
9293
+ const results = {};
9294
+ const headingMatches = Array.from(
9295
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
9296
+ );
9297
+ const sections = [];
9298
+ for (const m of headingMatches) {
9299
+ const headerText = cleanHtmlText11(m[1]).trim();
9300
+ const start = m.index + m[0].length;
9301
+ const nextHeading = html.indexOf("<h", start);
9302
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 1500);
9303
+ const content = cleanHtmlText11(rawContent).slice(0, 1500);
9304
+ if (headerText) sections.push({ header: headerText, content });
9305
+ }
9306
+ for (const topicId of topicIds) {
9307
+ const hashIdx = topicId.indexOf("#");
9308
+ if (hashIdx === -1) continue;
9309
+ const key = topicId.substring(0, hashIdx);
9310
+ const shortId = topicId.substring(hashIdx + 1);
9311
+ const list = LEHRPLAN_THUERINGEN_MANIFEST.topics[key] ?? [];
9312
+ const match = list.find((t2) => t2.id === shortId);
9313
+ if (!match) continue;
9314
+ const label = match.label;
9315
+ const normalizedLabel = normalizeForComparison11(label);
9316
+ let section = sections.find(
9317
+ (s) => normalizeForComparison11(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
9318
+ );
9319
+ if (!section && sections.length > 0) {
9320
+ section = sections.find((s) => s.content.length > 80) || sections[0];
9321
+ }
9322
+ if (section) {
9323
+ results[topicId] = `${label}
9324
+
9325
+ ${section.content}`.trim();
9326
+ } else {
9327
+ results[topicId] = label;
9328
+ }
9329
+ }
9330
+ return results;
9331
+ }
9332
+ };
9333
+ function cleanHtmlText11(html) {
9334
+ let text = html.replace(
9335
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
9336
+ " "
9337
+ );
9338
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
9339
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
9340
+ text = text.replace(/<p[^>]*>/gi, "\n");
9341
+ text = text.replace(/<br\s*\/?>/gi, "\n");
9342
+ text = text.replace(/<[^>]+>/g, " ");
9343
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
9344
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
9345
+ }
9346
+ function normalizeForComparison11(str) {
9347
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
9348
+ }
9349
+
9350
+ // src/cli/curriculum/providers/lehrplanplus-bayern/manifest.ts
9351
+ var LEHRPLANPLUS_BAYERN_MANIFEST = {
9352
+ schoolYear: "2026/2027",
9353
+ capturedOn: "2026-07-02",
9354
+ sourceRevision: "LehrplanPLUS Realschule \u2013 Oktober 2023",
9355
+ schoolTypes: [
9356
+ { id: "grundschule", label: "Grundschule" },
9357
+ { id: "mittelschule", label: "Mittelschule" },
9358
+ { id: "foerderschule", label: "F\xF6rderschule" },
9359
+ { id: "realschule", label: "Realschule" },
9360
+ { id: "gymnasium", label: "Gymnasium" },
9361
+ { id: "wirtschaftsschule", label: "Wirtschaftsschule" },
9362
+ { id: "fos", label: "Fachoberschule" },
9363
+ { id: "bos", label: "Berufsoberschule" }
9364
+ ],
9365
+ grades: {
9366
+ realschule: ["5", "6", "7", "8", "9", "10"]
9367
+ },
9368
+ subjects: {
9369
+ realschule: [
9370
+ {
9371
+ id: "bwl-rechnungswesen",
9372
+ label: "Betriebswirtschaftslehre / Rechnungswesen"
9373
+ },
9374
+ { id: "biologie", label: "Biologie" },
9375
+ { id: "chemie", label: "Chemie" },
9376
+ { id: "deutsch", label: "Deutsch" },
9377
+ { id: "englisch", label: "Englisch" },
9378
+ { id: "ernaehrung_und_gesundheit", label: "Ern\xE4hrung und Gesundheit" },
9379
+ { id: "ethik", label: "Ethik" },
9380
+ {
9381
+ id: "evangelische-religionslehre",
9382
+ label: "Evangelische Religionslehre"
9383
+ },
9384
+ { id: "franzoesisch", label: "Franz\xF6sisch" },
9385
+ { id: "geographie", label: "Geographie" },
9386
+ { id: "geschichte", label: "Geschichte" },
9387
+ { id: "it", label: "Informationstechnologie" },
9388
+ { id: "iu", label: "Islamischer Unterricht" },
9389
+ { id: "ir", label: "Israelitische Religionslehre" },
9390
+ {
9391
+ id: "katholische-religionslehre",
9392
+ label: "Katholische Religionslehre"
9393
+ },
9394
+ { id: "kunst", label: "Kunst" },
9395
+ { id: "mathematik", label: "Mathematik" },
9396
+ { id: "musik", label: "Musik" },
9397
+ { id: "or", label: "Orthodoxe Religionslehre" },
9398
+ { id: "physik", label: "Physik" },
9399
+ { id: "pug", label: "Politik und Gesellschaft" },
9400
+ { id: "soziallehre", label: "Soziallehre" },
9401
+ { id: "sozialwesen", label: "Sozialwesen" },
9402
+ { id: "spanisch", label: "Spanisch" },
9403
+ { id: "sport", label: "Sport" },
9404
+ { id: "textiles-gestalten", label: "Textiles Gestalten" },
9405
+ { id: "werken", label: "Werken" },
9406
+ { id: "wirtschaft-und-recht", label: "Wirtschaft und Recht" }
9407
+ ]
9408
+ },
9409
+ tracks: {
9410
+ "realschule|9|mathematik": [
9411
+ { id: "wpfg1", label: "Mathematik 9 (I)" },
9412
+ { id: "wpfg2-3", label: "Mathematik 9 (II/III)" }
9413
+ ]
9414
+ },
9415
+ topics: {
9416
+ "realschule|9|mathematik|wpfg1": [
9417
+ { id: "lb1", label: "Reelle Zahlen", hours: 10 },
9418
+ { id: "lb2", label: "Zentrische Streckung", hours: 17 },
9419
+ { id: "lb3", label: "Rechtwinklige Dreiecke", hours: 20 },
9420
+ { id: "lb4", label: "Kreis", hours: 10 },
9421
+ { id: "lb5", label: "Raumgeometrie", hours: 20 },
9422
+ { id: "lb6", label: "Systeme linearer Gleichungen", hours: 12 },
9423
+ {
9424
+ id: "lb7",
9425
+ label: "Quadratische Funktionen und quadratische Gleichungen",
9426
+ hours: 42
9427
+ },
9428
+ { id: "lb8", label: "Daten und Zufall", hours: 9 }
9429
+ ],
9430
+ "realschule|9|mathematik|wpfg2-3": [
9431
+ { id: "lb1", label: "Reelle Zahlen", hours: 7 },
9432
+ { id: "lb2", label: "Zentrische Streckung", hours: 13 },
9433
+ { id: "lb3", label: "Rechtwinklige Dreiecke", hours: 20 },
9434
+ { id: "lb4", label: "Kreis", hours: 10 },
9435
+ { id: "lb5", label: "Lineare Funktionen", hours: 15 },
9436
+ { id: "lb6", label: "Systeme linearer Gleichungen", hours: 10 },
9437
+ { id: "lb7", label: "Daten und Zufall", hours: 9 }
9438
+ ],
9439
+ "realschule|9|deutsch": [
9440
+ { id: "lb1", label: "Sprechen und Zuh\xF6ren" },
9441
+ { id: "lb2", label: "Lesen \u2013 mit Texten und weiteren Medien umgehen" },
9442
+ { id: "lb3", label: "Schreiben" },
9443
+ {
9444
+ id: "lb4",
9445
+ label: "Sprachgebrauch und Sprache untersuchen und reflektieren"
9446
+ }
9447
+ ],
9448
+ "realschule|9|englisch": [
9449
+ { id: "lb1", label: "Kommunikative Kompetenzen" },
9450
+ { id: "lb2", label: "Interkulturelle Kompetenzen" },
9451
+ { id: "lb3", label: "Text- und Medienkompetenzen" },
9452
+ { id: "lb4", label: "Methodische Kompetenzen" },
9453
+ { id: "lb5", label: "Themengebiete" }
9454
+ ]
9455
+ },
9456
+ contentUrls: {
9457
+ "realschule|9|mathematik|wpfg1": "https://www.lehrplanplus.bayern.de/schulart/realschule/jgs/9/fach/mathematik/inhalt/fachlehrplaene?w_schulart=realschule&wt_1=schulart&w_fach=mathematik&wt_2=fach&w_jgs=9&wt_3=jgs&w_auspraegung=wpfg1",
9458
+ "realschule|9|mathematik|wpfg2-3": "https://www.lehrplanplus.bayern.de/schulart/realschule/jgs/9/fach/mathematik/inhalt/fachlehrplaene?w_schulart=realschule&wt_1=schulart&w_fach=mathematik&wt_2=fach&w_jgs=9&wt_3=jgs&w_auspraegung=wpfg2-3",
9459
+ "realschule|9|deutsch": "https://www.lehrplanplus.bayern.de/schulart/realschule/jgs/9/fach/deutsch/inhalt/fachlehrplaene",
9460
+ "realschule|9|englisch": "https://www.lehrplanplus.bayern.de/schulart/realschule/jgs/9/fach/englisch/inhalt/fachlehrplaene"
9461
+ }
9462
+ };
9463
+
9464
+ // src/cli/curriculum/providers/lehrplanplus-bayern/index.ts
9465
+ function levelKey(schoolType, grade, subject, track) {
9466
+ return track ? `${schoolType}|${grade}|${subject}|${track}` : `${schoolType}|${grade}|${subject}`;
9467
+ }
9468
+ var lehrplanplusBayernProvider = {
9469
+ id: "lehrplanplus-bayern",
9470
+ country: "DE",
9471
+ countryLabel: "Deutschland",
9472
+ region: "BY",
9473
+ regionLabel: "Bayern",
9474
+ label: "LehrplanPLUS (Bayern)",
9475
+ listSchoolTypes() {
9476
+ return LEHRPLANPLUS_BAYERN_MANIFEST.schoolTypes;
9477
+ },
9478
+ listGrades(schoolType) {
9479
+ return (LEHRPLANPLUS_BAYERN_MANIFEST.grades[schoolType] ?? []).map((grade) => ({
9480
+ id: grade,
9481
+ label: grade
9482
+ }));
9483
+ },
9484
+ listSubjects(schoolType, _grade) {
9485
+ return LEHRPLANPLUS_BAYERN_MANIFEST.subjects[schoolType] ?? [];
9486
+ },
9487
+ listTracks(schoolType, grade, subject) {
9488
+ return LEHRPLANPLUS_BAYERN_MANIFEST.tracks[levelKey(schoolType, grade, subject)] ?? [];
9489
+ },
9490
+ listTopics(selection) {
9491
+ const { schoolType, grade, subject, track } = selection;
9492
+ if (!schoolType || !grade || !subject) return [];
9493
+ const key = levelKey(schoolType, grade, subject, track);
9494
+ return (LEHRPLANPLUS_BAYERN_MANIFEST.topics[key] ?? []).map((topic) => ({
9495
+ ...topic,
9496
+ sourceRef: key
9497
+ }));
9498
+ },
9499
+ resolveTopic(topic) {
9500
+ const uri = LEHRPLANPLUS_BAYERN_MANIFEST.contentUrls[topic.sourceRef];
9501
+ if (!uri) {
9502
+ throw new Error(
9503
+ `LehrplanPLUS Bayern: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}). The manifest only covers the combinations curated as of ${LEHRPLANPLUS_BAYERN_MANIFEST.capturedOn}.`
9504
+ );
9505
+ }
9506
+ return {
9507
+ provider: "lehrplanplus-bayern",
9508
+ topicId: `${topic.sourceRef}#${topic.id}`,
9509
+ uri
9510
+ };
9511
+ },
9512
+ extractTopics(html, topicIds) {
9513
+ const chunks = html.split('<div id="thema_');
9514
+ const sections = [];
9515
+ for (let i = 1; i < chunks.length; i++) {
9516
+ const chunk = chunks[i];
9517
+ const quoteIdx = chunk.indexOf('"');
9518
+ if (quoteIdx === -1) continue;
9519
+ const id = chunk.slice(0, quoteIdx);
9520
+ const classStart = chunk.indexOf('class="');
7776
9521
  if (classStart === -1) continue;
7777
9522
  const classEnd = chunk.indexOf('"', classStart + 7);
7778
9523
  const classContent = chunk.slice(classStart + 7, classEnd);
@@ -7783,53 +9528,395 @@ var lehrplanplusBayernProvider = {
7783
9528
  const headerMatch = contentHtml.match(
7784
9529
  /<a[^>]*class="paragraph_toggle"[^>]*>([\s\S]*?)<\/a>/i
7785
9530
  );
7786
- const headerText = headerMatch ? cleanHtmlText2(headerMatch[1]).trim() : "";
7787
- sections.push({
7788
- id,
7789
- level,
7790
- headerText,
7791
- contentHtml
7792
- });
9531
+ const headerText = headerMatch ? cleanHtmlText12(headerMatch[1]).trim() : "";
9532
+ sections.push({
9533
+ id,
9534
+ level,
9535
+ headerText,
9536
+ contentHtml
9537
+ });
9538
+ }
9539
+ const results = {};
9540
+ for (const topicId of topicIds) {
9541
+ let label = "";
9542
+ for (const key of Object.keys(LEHRPLANPLUS_BAYERN_MANIFEST.topics)) {
9543
+ const list = LEHRPLANPLUS_BAYERN_MANIFEST.topics[key];
9544
+ const match = list.find(
9545
+ (t2) => t2.id === topicId || `${key}#${t2.id}` === topicId
9546
+ );
9547
+ if (match) {
9548
+ label = match.label;
9549
+ break;
9550
+ }
9551
+ }
9552
+ if (!label) {
9553
+ continue;
9554
+ }
9555
+ const normalizedLabel = normalizeForComparison12(label);
9556
+ const lvl1Index = sections.findIndex((s) => {
9557
+ if (s.level !== 1) return false;
9558
+ const normalizedHeader = normalizeForComparison12(s.headerText);
9559
+ return normalizedHeader.includes(normalizedLabel);
9560
+ });
9561
+ if (lvl1Index === -1) {
9562
+ continue;
9563
+ }
9564
+ const collectedSections = [sections[lvl1Index]];
9565
+ for (let i = lvl1Index + 1; i < sections.length; i++) {
9566
+ if (sections[i].level === 1) {
9567
+ break;
9568
+ }
9569
+ collectedSections.push(sections[i]);
9570
+ }
9571
+ const fullHtml = collectedSections.map((s) => s.contentHtml).join("\n");
9572
+ results[topicId] = cleanHtmlText12(fullHtml);
9573
+ }
9574
+ return results;
9575
+ }
9576
+ };
9577
+ function cleanHtmlText12(html) {
9578
+ let text = html.replace(
9579
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
9580
+ " "
9581
+ );
9582
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
9583
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
9584
+ text = text.replace(/<p[^>]*>/gi, "\n");
9585
+ text = text.replace(/<br\s*\/?>/gi, "\n");
9586
+ text = text.replace(/<[^>]+>/g, " ");
9587
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&ndash;/g, "\u2013").replace(/&mdash;/g, "\u2014").replace(/&middot;/g, "\xB7");
9588
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
9589
+ }
9590
+ function normalizeForComparison12(str) {
9591
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
9592
+ }
9593
+
9594
+ // src/cli/curriculum/providers/rahmenlehrplan-berlin-brandenburg/manifest.ts
9595
+ var RAHMENLEHRPLAN_BERLIN_BRANDENBURG_MANIFEST = {
9596
+ schoolYear: "2025/2026",
9597
+ capturedOn: "2026-07-02",
9598
+ sourceRevision: "Gemeinsamer Rahmenlehrplan Berlin-Brandenburg",
9599
+ schoolTypes: [
9600
+ { id: "realschule", label: "Realschule" },
9601
+ { id: "gymnasium", label: "Gymnasium" }
9602
+ ],
9603
+ grades: {
9604
+ realschule: ["7", "8", "9", "10"],
9605
+ gymnasium: ["7", "8", "9", "10"]
9606
+ },
9607
+ subjects: {
9608
+ realschule: [
9609
+ { id: "mathematik", label: "Mathematik" },
9610
+ { id: "informatik", label: "Informatik" },
9611
+ { id: "physik", label: "Physik" },
9612
+ { id: "chemie", label: "Chemie" },
9613
+ { id: "biologie", label: "Biologie" }
9614
+ ],
9615
+ gymnasium: [
9616
+ { id: "mathematik", label: "Mathematik" },
9617
+ { id: "informatik", label: "Informatik" },
9618
+ { id: "physik", label: "Physik" },
9619
+ { id: "chemie", label: "Chemie" },
9620
+ { id: "biologie", label: "Biologie" }
9621
+ ]
9622
+ },
9623
+ tracks: {},
9624
+ topics: {
9625
+ "realschule|10|mathematik": [
9626
+ { id: "funktionen", label: "Funktionen" },
9627
+ { id: "geometrie", label: "Geometrie" },
9628
+ { id: "stochastik", label: "Stochastik" }
9629
+ ],
9630
+ "gymnasium|10|mathematik": [
9631
+ { id: "analysis", label: "Analysis" },
9632
+ { id: "vektoren", label: "Vektoren" }
9633
+ ],
9634
+ "realschule|9|informatik": [
9635
+ { id: "algorithmen", label: "Algorithmen" },
9636
+ { id: "daten", label: "Daten und Information" }
9637
+ ],
9638
+ "realschule|9|physik": [
9639
+ { id: "mechanik", label: "Mechanik" },
9640
+ { id: "elektrizitaet", label: "Elektrizit\xE4t" }
9641
+ ],
9642
+ "realschule|9|chemie": [
9643
+ { id: "reaktionen", label: "Chemische Reaktionen" },
9644
+ { id: "stoffe", label: "Stoffe" }
9645
+ ],
9646
+ "realschule|9|biologie": [
9647
+ { id: "zellen", label: "Zellen" },
9648
+ { id: "oekologie", label: "\xD6kologie" }
9649
+ ],
9650
+ "gymnasium|9|physik": [
9651
+ { id: "optik", label: "Optik" },
9652
+ { id: "felder", label: "Felder" }
9653
+ ],
9654
+ "gymnasium|9|chemie": [
9655
+ { id: "bindungen", label: "Bindungen" },
9656
+ { id: "saeuren", label: "S\xE4uren und Basen" }
9657
+ ],
9658
+ "gymnasium|9|biologie": [
9659
+ { id: "genetik", label: "Genetik" },
9660
+ { id: "evolution", label: "Evolution" }
9661
+ ]
9662
+ },
9663
+ contentUrls: {
9664
+ "realschule|10|mathematik": "https://bildungsserver.berlin-brandenburg.de/rlp-online/c-faecher/mathematik",
9665
+ "gymnasium|10|mathematik": "https://bildungsserver.berlin-brandenburg.de/rlp-online/c-faecher/mathematik",
9666
+ "realschule|9|informatik": "https://bildungsserver.berlin-brandenburg.de/rlp-online/c-faecher/informatik",
9667
+ "realschule|9|physik": "https://bildungsserver.berlin-brandenburg.de/rlp-online/c-faecher/physik",
9668
+ "realschule|9|chemie": "https://bildungsserver.berlin-brandenburg.de/rlp-online/c-faecher/chemie",
9669
+ "realschule|9|biologie": "https://bildungsserver.berlin-brandenburg.de/rlp-online/c-faecher/biologie",
9670
+ "gymnasium|9|physik": "https://bildungsserver.berlin-brandenburg.de/rlp-online/c-faecher/physik",
9671
+ "gymnasium|9|chemie": "https://bildungsserver.berlin-brandenburg.de/rlp-online/c-faecher/chemie",
9672
+ "gymnasium|9|biologie": "https://bildungsserver.berlin-brandenburg.de/rlp-online/c-faecher/biologie"
9673
+ }
9674
+ };
9675
+
9676
+ // src/cli/curriculum/providers/rahmenlehrplan-berlin-brandenburg/index.ts
9677
+ var rahmenlehrplanBerlinBrandenburgProvider = {
9678
+ id: "rahmenlehrplan-berlin-brandenburg",
9679
+ country: "DE",
9680
+ countryLabel: "Deutschland",
9681
+ region: "BE-BB",
9682
+ regionLabel: "Berlin / Brandenburg",
9683
+ label: "Rahmenlehrplan (Berlin-Brandenburg)",
9684
+ listSchoolTypes() {
9685
+ return RAHMENLEHRPLAN_BERLIN_BRANDENBURG_MANIFEST.schoolTypes;
9686
+ },
9687
+ listGrades(schoolType) {
9688
+ return (RAHMENLEHRPLAN_BERLIN_BRANDENBURG_MANIFEST.grades[schoolType] || []).map((id) => ({
9689
+ id,
9690
+ label: `Klasse ${id}`
9691
+ }));
9692
+ },
9693
+ listSubjects(schoolType, _grade) {
9694
+ return RAHMENLEHRPLAN_BERLIN_BRANDENBURG_MANIFEST.subjects[schoolType] || [];
9695
+ },
9696
+ listTracks(schoolType, grade, subject) {
9697
+ const key = `${schoolType}|${grade}|${subject}`;
9698
+ return RAHMENLEHRPLAN_BERLIN_BRANDENBURG_MANIFEST.tracks[key] || [];
9699
+ },
9700
+ listTopics(selection) {
9701
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
9702
+ const list = RAHMENLEHRPLAN_BERLIN_BRANDENBURG_MANIFEST.topics[key] || [];
9703
+ return list.map((t2) => ({
9704
+ ...t2,
9705
+ sourceRef: key
9706
+ }));
9707
+ },
9708
+ resolveTopic(topic) {
9709
+ const uri = RAHMENLEHRPLAN_BERLIN_BRANDENBURG_MANIFEST.contentUrls[topic.sourceRef];
9710
+ if (!uri) {
9711
+ throw new Error(
9712
+ `Rahmenlehrplan Berlin-Brandenburg: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
9713
+ );
7793
9714
  }
9715
+ return {
9716
+ provider: "rahmenlehrplan-berlin-brandenburg",
9717
+ topicId: `${topic.sourceRef}#${topic.id}`,
9718
+ uri
9719
+ };
9720
+ },
9721
+ extractTopics(html, topicIds) {
7794
9722
  const results = {};
9723
+ const headingMatches = Array.from(
9724
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
9725
+ );
9726
+ const sections = [];
9727
+ for (const m of headingMatches) {
9728
+ const headerText = cleanHtmlText13(m[1]).trim();
9729
+ const start = m.index + m[0].length;
9730
+ const nextHeading = html.indexOf("<h", start);
9731
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 1500);
9732
+ const content = cleanHtmlText13(rawContent).slice(0, 1500);
9733
+ if (headerText) sections.push({ header: headerText, content });
9734
+ }
7795
9735
  for (const topicId of topicIds) {
7796
- let label = "";
7797
- for (const key of Object.keys(LEHRPLANPLUS_BAYERN_MANIFEST.topics)) {
7798
- const list = LEHRPLANPLUS_BAYERN_MANIFEST.topics[key];
7799
- const match = list.find(
7800
- (t2) => t2.id === topicId || `${key}#${t2.id}` === topicId
7801
- );
7802
- if (match) {
7803
- label = match.label;
7804
- break;
7805
- }
9736
+ const hashIdx = topicId.indexOf("#");
9737
+ if (hashIdx === -1) continue;
9738
+ const key = topicId.substring(0, hashIdx);
9739
+ const shortId = topicId.substring(hashIdx + 1);
9740
+ const list = RAHMENLEHRPLAN_BERLIN_BRANDENBURG_MANIFEST.topics[key] ?? [];
9741
+ const match = list.find((t2) => t2.id === shortId);
9742
+ if (!match) continue;
9743
+ const label = match.label;
9744
+ const normalizedLabel = normalizeForComparison13(label);
9745
+ let section = sections.find(
9746
+ (s) => normalizeForComparison13(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
9747
+ );
9748
+ if (!section && sections.length > 0) {
9749
+ section = sections.find((s) => s.content.length > 80) || sections[0];
7806
9750
  }
7807
- if (!label) {
7808
- continue;
9751
+ if (section) {
9752
+ results[topicId] = `${label}
9753
+
9754
+ ${section.content}`.trim();
9755
+ } else {
9756
+ results[topicId] = label;
7809
9757
  }
7810
- const normalizedLabel = normalizeForComparison2(label);
7811
- const lvl1Index = sections.findIndex((s) => {
7812
- if (s.level !== 1) return false;
7813
- const normalizedHeader = normalizeForComparison2(s.headerText);
7814
- return normalizedHeader.includes(normalizedLabel);
7815
- });
7816
- if (lvl1Index === -1) {
7817
- continue;
9758
+ }
9759
+ return results;
9760
+ }
9761
+ };
9762
+ function cleanHtmlText13(html) {
9763
+ let text = html.replace(
9764
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
9765
+ " "
9766
+ );
9767
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
9768
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
9769
+ text = text.replace(/<p[^>]*>/gi, "\n");
9770
+ text = text.replace(/<br\s*\/?>/gi, "\n");
9771
+ text = text.replace(/<[^>]+>/g, " ");
9772
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
9773
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
9774
+ }
9775
+ function normalizeForComparison13(str) {
9776
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
9777
+ }
9778
+
9779
+ // src/cli/curriculum/providers/rahmenplan-mv/manifest.ts
9780
+ var RAHMENPLAN_MV_MANIFEST = {
9781
+ schoolYear: "2025/2026",
9782
+ capturedOn: "2026-07-02",
9783
+ sourceRevision: "Rahmenpl\xE4ne Mecklenburg-Vorpommern",
9784
+ schoolTypes: [
9785
+ { id: "regionale-schule", label: "Regionale Schule" },
9786
+ { id: "gymnasium", label: "Gymnasium" }
9787
+ ],
9788
+ grades: {
9789
+ "regionale-schule": ["7", "8", "9", "10"],
9790
+ gymnasium: ["7", "8", "9", "10"]
9791
+ },
9792
+ subjects: {
9793
+ "regionale-schule": [
9794
+ { id: "mathematik", label: "Mathematik" },
9795
+ { id: "informatik", label: "Informatik" },
9796
+ { id: "physik", label: "Physik" },
9797
+ { id: "chemie", label: "Chemie" },
9798
+ { id: "biologie", label: "Biologie" }
9799
+ ],
9800
+ gymnasium: [
9801
+ { id: "mathematik", label: "Mathematik" },
9802
+ { id: "informatik", label: "Informatik" },
9803
+ { id: "physik", label: "Physik" },
9804
+ { id: "chemie", label: "Chemie" },
9805
+ { id: "biologie", label: "Biologie" }
9806
+ ]
9807
+ },
9808
+ tracks: {},
9809
+ topics: {
9810
+ "regionale-schule|10|mathematik": [
9811
+ { id: "funktionen", label: "Funktionen" }
9812
+ ],
9813
+ "gymnasium|10|mathematik": [{ id: "analysis", label: "Analysis" }],
9814
+ "regionale-schule|9|physik": [{ id: "mechanik", label: "Mechanik" }],
9815
+ "regionale-schule|9|chemie": [{ id: "reaktionen", label: "Reaktionen" }],
9816
+ "regionale-schule|9|biologie": [{ id: "zellen", label: "Zellen" }],
9817
+ "gymnasium|9|physik": [{ id: "elektrizitaet", label: "Elektrizit\xE4t" }],
9818
+ "gymnasium|9|chemie": [{ id: "bindungen", label: "Bindungen" }],
9819
+ "gymnasium|9|biologie": [{ id: "genetik", label: "Genetik" }]
9820
+ },
9821
+ contentUrls: {
9822
+ "regionale-schule|10|mathematik": "https://www.bildung-mv.de/schueler/schule-und-unterricht/faecher-und-rahmenplaene/rahmenplaene-an-allgemeinbildenden-schulen/",
9823
+ "gymnasium|10|mathematik": "https://www.bildung-mv.de/schueler/schule-und-unterricht/faecher-und-rahmenplaene/rahmenplaene-an-allgemeinbildenden-schulen/",
9824
+ "regionale-schule|9|physik": "https://www.bildung-mv.de/schueler/schule-und-unterricht/faecher-und-rahmenplaene/rahmenplaene-an-allgemeinbildenden-schulen/",
9825
+ "regionale-schule|9|chemie": "https://www.bildung-mv.de/schueler/schule-und-unterricht/faecher-und-rahmenplaene/rahmenplaene-an-allgemeinbildenden-schulen/",
9826
+ "regionale-schule|9|biologie": "https://www.bildung-mv.de/schueler/schule-und-unterricht/faecher-und-rahmenplaene/rahmenplaene-an-allgemeinbildenden-schulen/",
9827
+ "gymnasium|9|physik": "https://www.bildung-mv.de/schueler/schule-und-unterricht/faecher-und-rahmenplaene/rahmenplaene-an-allgemeinbildenden-schulen/",
9828
+ "gymnasium|9|chemie": "https://www.bildung-mv.de/schueler/schule-und-unterricht/faecher-und-rahmenplaene/rahmenplaene-an-allgemeinbildenden-schulen/",
9829
+ "gymnasium|9|biologie": "https://www.bildung-mv.de/schueler/schule-und-unterricht/faecher-und-rahmenplaene/rahmenplaene-an-allgemeinbildenden-schulen/"
9830
+ }
9831
+ };
9832
+
9833
+ // src/cli/curriculum/providers/rahmenplan-mv/index.ts
9834
+ var rahmenplanMvProvider = {
9835
+ id: "rahmenplan-mv",
9836
+ country: "DE",
9837
+ countryLabel: "Deutschland",
9838
+ region: "MV",
9839
+ regionLabel: "Mecklenburg-Vorpommern",
9840
+ label: "Rahmenplan (Mecklenburg-Vorpommern)",
9841
+ listSchoolTypes() {
9842
+ return RAHMENPLAN_MV_MANIFEST.schoolTypes;
9843
+ },
9844
+ listGrades(schoolType) {
9845
+ return (RAHMENPLAN_MV_MANIFEST.grades[schoolType] || []).map((id) => ({
9846
+ id,
9847
+ label: `Klasse ${id}`
9848
+ }));
9849
+ },
9850
+ listSubjects(schoolType, _grade) {
9851
+ return RAHMENPLAN_MV_MANIFEST.subjects[schoolType] || [];
9852
+ },
9853
+ listTracks(schoolType, grade, subject) {
9854
+ const key = `${schoolType}|${grade}|${subject}`;
9855
+ return RAHMENPLAN_MV_MANIFEST.tracks[key] || [];
9856
+ },
9857
+ listTopics(selection) {
9858
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
9859
+ const list = RAHMENPLAN_MV_MANIFEST.topics[key] || [];
9860
+ return list.map((t2) => ({
9861
+ ...t2,
9862
+ sourceRef: key
9863
+ }));
9864
+ },
9865
+ resolveTopic(topic) {
9866
+ const uri = RAHMENPLAN_MV_MANIFEST.contentUrls[topic.sourceRef];
9867
+ if (!uri) {
9868
+ throw new Error(
9869
+ `Rahmenplan MV: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
9870
+ );
9871
+ }
9872
+ return {
9873
+ provider: "rahmenplan-mv",
9874
+ topicId: `${topic.sourceRef}#${topic.id}`,
9875
+ uri
9876
+ };
9877
+ },
9878
+ extractTopics(html, topicIds) {
9879
+ const results = {};
9880
+ const headingMatches = Array.from(
9881
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
9882
+ );
9883
+ const sections = [];
9884
+ for (const m of headingMatches) {
9885
+ const headerText = cleanHtmlText14(m[1]).trim();
9886
+ const start = m.index + m[0].length;
9887
+ const nextHeading = html.indexOf("<h", start);
9888
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 1500);
9889
+ const content = cleanHtmlText14(rawContent).slice(0, 1500);
9890
+ if (headerText) sections.push({ header: headerText, content });
9891
+ }
9892
+ for (const topicId of topicIds) {
9893
+ const hashIdx = topicId.indexOf("#");
9894
+ if (hashIdx === -1) continue;
9895
+ const key = topicId.substring(0, hashIdx);
9896
+ const shortId = topicId.substring(hashIdx + 1);
9897
+ const list = RAHMENPLAN_MV_MANIFEST.topics[key] ?? [];
9898
+ const match = list.find((t2) => t2.id === shortId);
9899
+ if (!match) continue;
9900
+ const label = match.label;
9901
+ const normalizedLabel = normalizeForComparison14(label);
9902
+ let section = sections.find(
9903
+ (s) => normalizeForComparison14(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
9904
+ );
9905
+ if (!section && sections.length > 0) {
9906
+ section = sections.find((s) => s.content.length > 80) || sections[0];
7818
9907
  }
7819
- const collectedSections = [sections[lvl1Index]];
7820
- for (let i = lvl1Index + 1; i < sections.length; i++) {
7821
- if (sections[i].level === 1) {
7822
- break;
7823
- }
7824
- collectedSections.push(sections[i]);
9908
+ if (section) {
9909
+ results[topicId] = `${label}
9910
+
9911
+ ${section.content}`.trim();
9912
+ } else {
9913
+ results[topicId] = label;
7825
9914
  }
7826
- const fullHtml = collectedSections.map((s) => s.contentHtml).join("\n");
7827
- results[topicId] = cleanHtmlText2(fullHtml);
7828
9915
  }
7829
9916
  return results;
7830
9917
  }
7831
9918
  };
7832
- function cleanHtmlText2(html) {
9919
+ function cleanHtmlText14(html) {
7833
9920
  let text = html.replace(
7834
9921
  /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
7835
9922
  " "
@@ -7839,17 +9926,185 @@ function cleanHtmlText2(html) {
7839
9926
  text = text.replace(/<p[^>]*>/gi, "\n");
7840
9927
  text = text.replace(/<br\s*\/?>/gi, "\n");
7841
9928
  text = text.replace(/<[^>]+>/g, " ");
7842
- text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&ndash;/g, "\u2013").replace(/&mdash;/g, "\u2014").replace(/&middot;/g, "\xB7");
9929
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
7843
9930
  return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
7844
9931
  }
7845
- function normalizeForComparison2(str) {
9932
+ function normalizeForComparison14(str) {
9933
+ return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
9934
+ }
9935
+
9936
+ // src/cli/curriculum/providers/rahmenrichtlinien-st/manifest.ts
9937
+ var RAHMENRICHTLINIEN_ST_MANIFEST = {
9938
+ schoolYear: "2025/2026",
9939
+ capturedOn: "2026-07-02",
9940
+ sourceRevision: "Rahmenrichtlinien Sachsen-Anhalt",
9941
+ schoolTypes: [
9942
+ { id: "sekundarschule", label: "Sekundarschule" },
9943
+ { id: "gymnasium", label: "Gymnasium" }
9944
+ ],
9945
+ grades: {
9946
+ sekundarschule: ["7", "8", "9", "10"],
9947
+ gymnasium: ["7", "8", "9", "10"]
9948
+ },
9949
+ subjects: {
9950
+ sekundarschule: [
9951
+ { id: "mathematik", label: "Mathematik" },
9952
+ { id: "informatik", label: "Informatik" },
9953
+ { id: "physik", label: "Physik" },
9954
+ { id: "chemie", label: "Chemie" },
9955
+ { id: "biologie", label: "Biologie" }
9956
+ ],
9957
+ gymnasium: [
9958
+ { id: "mathematik", label: "Mathematik" },
9959
+ { id: "informatik", label: "Informatik" },
9960
+ { id: "physik", label: "Physik" },
9961
+ { id: "chemie", label: "Chemie" },
9962
+ { id: "biologie", label: "Biologie" }
9963
+ ]
9964
+ },
9965
+ tracks: {},
9966
+ topics: {
9967
+ "sekundarschule|10|mathematik": [{ id: "funktionen", label: "Funktionen" }],
9968
+ "gymnasium|10|mathematik": [{ id: "analysis", label: "Analysis" }],
9969
+ "sekundarschule|9|physik": [{ id: "mechanik", label: "Mechanik" }],
9970
+ "sekundarschule|9|chemie": [{ id: "reaktionen", label: "Reaktionen" }],
9971
+ "sekundarschule|9|biologie": [{ id: "zellen", label: "Zellen" }],
9972
+ "gymnasium|9|physik": [{ id: "elektrizitaet", label: "Elektrizit\xE4t" }],
9973
+ "gymnasium|9|chemie": [{ id: "bindungen", label: "Bindungen" }],
9974
+ "gymnasium|9|biologie": [{ id: "genetik", label: "Genetik" }]
9975
+ },
9976
+ contentUrls: {
9977
+ "sekundarschule|10|mathematik": "https://lisa.sachsen-anhalt.de/schulqualitaet/lehrplaene-rahmenrichtlinien",
9978
+ "gymnasium|10|mathematik": "https://lisa.sachsen-anhalt.de/schulqualitaet/lehrplaene-rahmenrichtlinien",
9979
+ "sekundarschule|9|physik": "https://lisa.sachsen-anhalt.de/schulqualitaet/lehrplaene-rahmenrichtlinien",
9980
+ "sekundarschule|9|chemie": "https://lisa.sachsen-anhalt.de/schulqualitaet/lehrplaene-rahmenrichtlinien",
9981
+ "sekundarschule|9|biologie": "https://lisa.sachsen-anhalt.de/schulqualitaet/lehrplaene-rahmenrichtlinien",
9982
+ "gymnasium|9|physik": "https://lisa.sachsen-anhalt.de/schulqualitaet/lehrplaene-rahmenrichtlinien",
9983
+ "gymnasium|9|chemie": "https://lisa.sachsen-anhalt.de/schulqualitaet/lehrplaene-rahmenrichtlinien",
9984
+ "gymnasium|9|biologie": "https://lisa.sachsen-anhalt.de/schulqualitaet/lehrplaene-rahmenrichtlinien"
9985
+ }
9986
+ };
9987
+
9988
+ // src/cli/curriculum/providers/rahmenrichtlinien-st/index.ts
9989
+ var rahmenrichtlinienStProvider = {
9990
+ id: "rahmenrichtlinien-st",
9991
+ country: "DE",
9992
+ countryLabel: "Deutschland",
9993
+ region: "ST",
9994
+ regionLabel: "Sachsen-Anhalt",
9995
+ label: "Rahmenrichtlinien (Sachsen-Anhalt)",
9996
+ listSchoolTypes() {
9997
+ return RAHMENRICHTLINIEN_ST_MANIFEST.schoolTypes;
9998
+ },
9999
+ listGrades(schoolType) {
10000
+ return (RAHMENRICHTLINIEN_ST_MANIFEST.grades[schoolType] || []).map((id) => ({
10001
+ id,
10002
+ label: `Klasse ${id}`
10003
+ }));
10004
+ },
10005
+ listSubjects(schoolType, _grade) {
10006
+ return RAHMENRICHTLINIEN_ST_MANIFEST.subjects[schoolType] || [];
10007
+ },
10008
+ listTracks(schoolType, grade, subject) {
10009
+ const key = `${schoolType}|${grade}|${subject}`;
10010
+ return RAHMENRICHTLINIEN_ST_MANIFEST.tracks[key] || [];
10011
+ },
10012
+ listTopics(selection) {
10013
+ const key = selection.track ? `${selection.schoolType}|${selection.grade}|${selection.subject}|${selection.track}` : `${selection.schoolType}|${selection.grade}|${selection.subject}`;
10014
+ const list = RAHMENRICHTLINIEN_ST_MANIFEST.topics[key] || [];
10015
+ return list.map((t2) => ({
10016
+ ...t2,
10017
+ sourceRef: key
10018
+ }));
10019
+ },
10020
+ resolveTopic(topic) {
10021
+ const uri = RAHMENRICHTLINIEN_ST_MANIFEST.contentUrls[topic.sourceRef];
10022
+ if (!uri) {
10023
+ throw new Error(
10024
+ `Rahmenrichtlinien ST: no resolvable source URL for topic "${topic.id}" (${topic.sourceRef}).`
10025
+ );
10026
+ }
10027
+ return {
10028
+ provider: "rahmenrichtlinien-st",
10029
+ topicId: `${topic.sourceRef}#${topic.id}`,
10030
+ uri
10031
+ };
10032
+ },
10033
+ extractTopics(html, topicIds) {
10034
+ const results = {};
10035
+ const headingMatches = Array.from(
10036
+ html.matchAll(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi)
10037
+ );
10038
+ const sections = [];
10039
+ for (const m of headingMatches) {
10040
+ const headerText = cleanHtmlText15(m[1]).trim();
10041
+ const start = m.index + m[0].length;
10042
+ const nextHeading = html.indexOf("<h", start);
10043
+ const rawContent = nextHeading > -1 ? html.slice(start, nextHeading) : html.slice(start, start + 1500);
10044
+ const content = cleanHtmlText15(rawContent).slice(0, 1500);
10045
+ if (headerText) sections.push({ header: headerText, content });
10046
+ }
10047
+ for (const topicId of topicIds) {
10048
+ const hashIdx = topicId.indexOf("#");
10049
+ if (hashIdx === -1) continue;
10050
+ const key = topicId.substring(0, hashIdx);
10051
+ const shortId = topicId.substring(hashIdx + 1);
10052
+ const list = RAHMENRICHTLINIEN_ST_MANIFEST.topics[key] ?? [];
10053
+ const match = list.find((t2) => t2.id === shortId);
10054
+ if (!match) continue;
10055
+ const label = match.label;
10056
+ const normalizedLabel = normalizeForComparison15(label);
10057
+ let section = sections.find(
10058
+ (s) => normalizeForComparison15(s.header).includes(normalizedLabel) || s.header.toLowerCase().includes(shortId.toLowerCase())
10059
+ );
10060
+ if (!section && sections.length > 0) {
10061
+ section = sections.find((s) => s.content.length > 80) || sections[0];
10062
+ }
10063
+ if (section) {
10064
+ results[topicId] = `${label}
10065
+
10066
+ ${section.content}`.trim();
10067
+ } else {
10068
+ results[topicId] = label;
10069
+ }
10070
+ }
10071
+ return results;
10072
+ }
10073
+ };
10074
+ function cleanHtmlText15(html) {
10075
+ let text = html.replace(
10076
+ /<(head|script|style|svg)[^>]*>[\s\S]*?<\/\1>/gi,
10077
+ " "
10078
+ );
10079
+ text = text.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, "\n\n$1\n\n");
10080
+ text = text.replace(/<li[^>]*>/gi, "\n- ");
10081
+ text = text.replace(/<p[^>]*>/gi, "\n");
10082
+ text = text.replace(/<br\s*\/?>/gi, "\n");
10083
+ text = text.replace(/<[^>]+>/g, " ");
10084
+ text = text.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
10085
+ return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
10086
+ }
10087
+ function normalizeForComparison15(str) {
7846
10088
  return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
7847
10089
  }
7848
10090
 
7849
10091
  // src/cli/curriculum/registry.ts
7850
10092
  var CURRICULUM_PROVIDERS = [
7851
10093
  lehrplanplusBayernProvider,
7852
- bildungsplanBwProvider
10094
+ bildungsplanBwProvider,
10095
+ kernlehrplanNrwProvider,
10096
+ kerncurriculumHessenProvider,
10097
+ kerncurriculumNiedersachsenProvider,
10098
+ lehrplanSachsenProvider,
10099
+ rahmenlehrplanBerlinBrandenburgProvider,
10100
+ bildungsplanHamburgProvider,
10101
+ bildungsplanBremenProvider,
10102
+ rahmenplanMvProvider,
10103
+ lehrplaeneRpProvider,
10104
+ lehrplanSaarlandProvider,
10105
+ rahmenrichtlinienStProvider,
10106
+ fachanforderungenShProvider,
10107
+ lehrplanThueringenProvider
7853
10108
  ];
7854
10109
  function getCurriculumProvider(id) {
7855
10110
  return CURRICULUM_PROVIDERS.find((provider) => provider.id === id);
@@ -11302,7 +13557,7 @@ async function fetchRawHtml(url) {
11302
13557
  const res = await fetch(url, {
11303
13558
  signal: controller.signal,
11304
13559
  headers: {
11305
- "User-Agent": "ZAM-Content-Studio/0.6.1"
13560
+ "User-Agent": "ZAM-Content-Studio/0.6.3"
11306
13561
  }
11307
13562
  });
11308
13563
  if (!res.ok) {