Μετάβαση στο περιεχόμενο

Δημιουργία περιεχομένου μαθημάτων

Αυτός ο οδηγός περιγράφει βήμα προς βήμα πώς να στήσεις ένα νέο σύνολο μαθημάτων για τον Content-Loader του Adaptive Learner. Όποιος θέλει να φτιάξει ένα γλωσσικό ή θεματικό σύνολο — για δική του χρήση ή ως συνεισφορά στη δημόσια δεξαμενή περιεχομένου — καλό είναι να τον διαβάσει μία φορά ολόκληρο πριν από το πρώτο μάθημα.

Τι είναι ένα Content-Set;

Ένα Content-Set είναι ένα εκδοσιοποιημένο πακέτο μαθημάτων, που ένας χρήστης μπορεί να κατεβάσει μέσω της σελίδας Set-Browser (/content). Το plugin Content-Loader (v1.27.0) αναλαμβάνει το discovery, τη λήψη, το caching και τη σύγκριση εκδόσεων και στους δύο τρόπους αποθήκευσης.

Ένα σύνολο έχει τρία επίπεδα:

  1. Root-Manifest (manifest.yaml) — παραθέτει κάθε σύνολο του repo. Διαβάζεται από τον Set Browser για τον κατάλογο προέλευσης.
  2. Set-Manifest (sets/{set-id}/manifest.yaml) — αδελφικό του Root-Manifest, παραθέτει τα αρχεία μαθημάτων του συγκεκριμένου συνόλου.
  3. Αρχεία μαθημάτων (sets/{set-id}/lessons/NN-slug.json) — ένα αρχείο JSON ανά μάθημα, επικυρωμένο σε κάθε λήψη έναντι του σχήματος v1.0.

Τα pilot-σύνολα που αποστέλλονται με τον Adaptive Learner βρίσκονται στο ξεχωριστό repo περιεχομένου astrapi69/adaptive-learner-content (ελεγμένα ως αδελφικό checkout ../adaptive-learner-content και ομαδοποιημένα από το build μέσω frontend/scripts/copy-bundled-content.mjs) και είναι κατάλληλα ως πρότυπο.

Ζεύγη γλωσσών (v1.44.0)

Κάθε Content-Set δηλώνει το ΖΕΥΓΟΣ γλωσσών που μεταδίδει:

  • target_language — αυτό που ΜΑΘΑΙΝΕΙ ο εκπαιδευόμενος (π.χ. fr).
  • source_language — αυτό που ΜΙΛΑΕΙ ήδη ο εκπαιδευόμενος, δηλαδή η γλώσσα στην οποία είναι γραμμένα τα πεδία back των καρτών, τα notes και το κείμενο θεωρίας (π.χ. de).

Ακριβώς αυτό κάνει τα «Γαλλικά για Αγγλόφωνους» ένα διαφορετικό σύνολο από τα «Γαλλικά για Γερμανόφωνους»: ίδιος στόχος (fr), διαφορετική γλώσσα αφετηρίας (en έναντι de), διαφορετική γλώσσα επεξήγησης. Ένας εκπαιδευόμενος βλέπει μόνο σύνολα των οποίων η source_language ταιριάζει με μια από τις γλώσσες που μιλάει (γλώσσα εφαρμογής συν προαιρετικές πρόσθετες γλώσσες στις Ρυθμίσεις → Μάθηση).

Τα Set-IDs κωδικοποιούν το ζεύγος ως {ziel}-{niveau}-from-{quelle} (π.χ. fr-a1-from-de), και κάθε σύνολο δηλώνει ένα path, που δείχνει στον κατάλογο γλώσσας αφετηρίας του (sets/de/fr-a1). Ένα σύνολο φέρει επιπλέον title (στη γλώσσα αφετηρίας, αυτό που διαβάζει ο εκπαιδευόμενος) και title_native (στη γλώσσα στόχο, ως δευτερεύων τίτλος).

Και οι δύο κωδικοί πρέπει να είναι ISO-639-1 (δύο γράμματα), και η source_language πρέπει να διαφέρει από την target_language. Σύνολα πριν την v1.2 χωρίς αυτά τα πεδία φορτώνουν ακόμη: το παλιό κλειδί language γίνεται δεκτό ως target_language, και η source_language πέφτει πίσω στο en.

Διάταξη καταλόγων

Το δέντρο είναι οργανωμένο κατά ΓΛΩΣΣΑ ΑΦΕΤΗΡΙΑΣ, μετά στόχο+επίπεδο:

mein-content-repo/
  manifest.yaml               # Root: listet jedes Set (mit path + Paar)
  sets/
    de/                       # Ausgangssprache: Deutsch
      fr-a1/                  # Ziel Französisch, Niveau A1  -> ID fr-a1-from-de
        manifest.yaml         # Set: listet die Lektionen
        lessons/
          01-begruessung.json
          ...
        assets/               # optionale Bilder / Audio
    en/                       # Ausgangssprache: Englisch
      fr-a1/                  # -> ID fr-a1-from-en
        ...

Μορφή Manifest

Και τα δύο αρχεία manifest (Root + Set) χρησιμοποιούν την ίδια μορφή με schema_version: '1.0'. Υποχρεωτικά πεδία:

schema_version: '1.0'
name: Mein Englisch-B1-Set
description: >-
  Optionale Langbeschreibung.
sets:
  - id: language-en-b1        # slug-sicher, eindeutig
    title: Englisch B1 (Fortgeschrittene)
    language: en              # BCP-47 (z.B. en, fr, zh-Hans)
    level: B1                 # CEFR für Sprachen, frei für andere Domänen
    version: '1.0.0'          # Semver — pro Set-Release erhöht
    lesson_count: 12
    domain: language          # 'language' / 'math' / 'programming' / ...
    description: >-
      Optionale Set-Beschreibung.
    tags:
      - intermediate
      - business
metadata:
  author: Dein Name
  license: CC-BY-SA-4.0       # oder die Lizenz deiner Wahl

Το Set-Manifest παραθέτει επιπλέον κάθε αρχείο μαθήματος:

metadata:
  lessons:
    - 01-intro.json
    - 02-articles.json
    - ...

Ο Content-Loader επαναλαμβάνει το metadata.lessons με τη δεδομένη σειρά· τα ονόματα αρχείων στον δίσκο είναι άσχετα — μόνο η σειρά του manifest μετρά.

Σχήμα μαθήματος (v1.0)

Κάθε μάθημα είναι ένα μεμονωμένο αρχείο JSON. Δομή Top-Level:

{
  "id": "01-greetings",
  "title": "Begrüßungen",
  "description": "Optionale 1-2-Satz-Zusammenfassung.",
  "estimated_minutes": 12,
  "cards": [ ... ],
  "steps": [ ... ]
}

Cards

Μια Card είναι η μικρότερη μαθήσιμη μονάδα — τυπικά ένας μεμονωμένος όρος ή μια έννοια. Κάθε Card έχει ένα σταθερό id (αναφερόμενο από ασκήσεις) και ένα ζεύγος front/back:

{
  "id": "art-le",
  "front": "le",
  "back": "der (männlich Singular)",
  "notes": "Vor konsonantenanfangenden männlichen Substantiven. **le chat**, **le livre**.",
  "tags": ["article", "definite"]
}

Το notes δέχεται Markdown. Χρησιμοποίησέ το για κανόνες προφοράς, προειδοποιήσεις για ψευδόφιλους, υποδείξεις εξαιρέσεων — οτιδήποτε βελτιώνει τη μακροχρόνια αποθήκευση. Τα tags ελέγχουν το φιλτράρισμα SRS.

Steps

Ένα μάθημα είναι μια ακολουθία βήμα προς βήμα, κάθε βήμα είτε THEORY (ένα μπλοκ Markdown) είτε EXERCISE (ένας από τους τέσσερις τύπους ασκήσεων):

{
  "id": "intro",
  "type": "theory",
  "title": "Warum Artikel wichtig sind",
  "body": "# Artikel im Französischen\n\nJedes französische Nomen hat ein Geschlecht..."
}

Ένα βήμα θεωρίας μπορεί προαιρετικά να φέρει έναν σύνδεσμο παραδείγματος (σχήμα v1.4, additive — τα υπάρχοντα μαθήματα παραμένουν έγκυρα χωρίς αυτόν). Αν υπάρχει, ο προβολέας αποδίδει από κάτω ένα κουμπί για το άνοιγμα του παραδείγματος:

{
  "id": "intro",
  "type": "theory",
  "body": "Die Korrelation misst den Zusammenhang...",
  "example_url": "https://example.com/correlation-visualizer",
  "example_label": "Interaktive Visualisierung"
}
  • example_url (προαιρετικό): πρέπει να είναι ένα URL http(s).
  • example_label (προαιρετικό): το κείμενο συνδέσμου· κενό γίνεται ένα τοπικοποιημένο «Προβολή παραδείγματος».

Ή μια άσκηση:

{
  "id": "ex-match-greetings",
  "type": "exercise",
  "title": "Begrüßungen zuordnen",
  "exercise": {
    "id": "ex-match-greetings",
    "type": "matching",
    "prompt": "Ordne jede Begrüßung ihrer Übersetzung zu.",
    "card_ids": ["bonjour", "salut"],
    "pairs": [
      {"left": "Bonjour", "right": "Hallo"},
      {"left": "Salut", "right": "Hi"}
    ]
  }
}

Αναφορά τύπων ασκήσεων

matching

Άσκηση drag-pair. Ο renderer ανακατεύει πριν την εμφάνιση.

{
  "id": "ex-id",
  "type": "matching",
  "prompt": "Ordne jedem französischen Nomen seinen Artikel zu.",
  "card_ids": ["noun-1", "noun-2"],
  "pairs": [
    {"left": "chat", "right": "le"},
    {"left": "chaise", "right": "la"}
  ]
}

Κάθε pair πρέπει να έχει ακριβώς δύο κλειδιά: left + right.

picture_choice

Multiple-Choice με εικόνες. ≥ 2 εικόνες, ακριβώς μία σημειωμένη ως σωστή.

{
  "id": "ex-id",
  "type": "picture_choice",
  "prompt": "Welche Begrüßung passt zum Abend?",
  "card_ids": ["card-1"],
  "images": [
    {"src": "assets/img/morning.png", "label": "Bonjour"},
    {"src": "assets/img/evening.png", "label": "Bonsoir", "is_correct": "true"}
  ],
  "hint": "Optionaler Markdown-Tipp auf Knopfdruck.",
  "distractors": ["Bonjour"]
}

Σημαντικό: το is_correct είναι ένα String "true", όχι Boolean JSON.

Αν η διαδρομή src δείχνει σε ένα μη υπάρχον αρχείο, ο renderer πέφτει πίσω στο label — άρα το picture_choice λειτουργεί και χωρίς assets εικονογράφησης.

free_text

Πληκτρολόγηση απάντησης. Ο renderer ταιριάζει πρώτα ακριβώς, μετά με ανοχή Levenshtein.

{
  "id": "ex-id",
  "type": "free_text",
  "prompt": "Wie sagt man 'Danke' auf Französisch?",
  "card_ids": ["card-merci"],
  "accept": ["Merci", "merci", "MERCI"],
  "hint": "Beginnt mit M.",
  "distractors": ["Bonjour", "Salut"]
}

Το accept[0] είναι η κανονική απάντηση, που εμφανίζεται σε μια λανθασμένη προσπάθεια. Παράθεσε ≥ 3 παραλλαγές, ώστε να καλύψεις πεζά/κεφαλαία + στίξη· ο renderer κανονικοποιεί τα κενά.

word_tiles

Τοποθέτηση πλακιδίων στη σωστή σειρά. Ο renderer ανακατεύει πριν την εμφάνιση.

{
  "id": "ex-id",
  "type": "word_tiles",
  "prompt": "Bring die Kacheln in die Reihenfolge: Ich sehe eine Katze.",
  "card_ids": ["card-1"],
  "tiles": ["Je", "vois", "un", "chat"],
  "hint": "Gleiche Wortreihenfolge wie im Deutschen."
}

Αν είναι σωστές πολλές σειρές λέξεων, πρόσθεσε accept_orderings:

{
  "tiles": ["Je", "vois", "un", "chat"],
  "accept_orderings": [
    [0, 1, 2, 3],
    [0, 1, 3, 2]
  ]
}

Κάθε σειρά είναι μια μετάθεση των δεικτών των tiles.

cloze (Φάση 52 / v1.35.0 — Σχήμα 1.1)

Συμπλήρωση κενού με ορατούς δείκτες ___ στην πρόταση. Κάθε ___ αντιστοιχεί σε μια καταχώρηση στο blanks[] (αντιστοίχιση από αριστερά προς τα δεξιά· ο loader ελέγχει sentence.count("___") == len(blanks)).

{
  "id": "ex-id",
  "type": "cloze",
  "prompt": "Setze den unbestimmten Artikel ein.",
  "card_ids": ["art-un", "noun-chat"],
  "sentence": "Je vois ___ chat dans le jardin.",
  "blanks": [
    {
      "accept": ["un"],
      "hint": "männlicher unbestimmter Artikel",
      "placeholder": "?"
    }
  ],
  "cloze_mode": "type",
  "distractors": ["le", "la", "les"],
  "hint": "*un* ist der männliche unbestimmte Artikel."
}

Τρόποι απόδοσης — οριζόμενοι ανά άσκηση μέσω cloze_mode:

  • "type" (προεπιλογή, αν δεν οριστεί): ένα <input> ανά κενό. Επικυρώνεται με τον ίδιο matcher NFC + Levenshtein-≤-1 όπως το free-text, ώστε οι συντάκτριες να χρειάζεται να παραθέσουν μόνο σημασιολογικές παραλλαγές (όχι τυπογραφικά λάθη).
  • "select": ένα <select> ανά κενό. Οι επιλογές προέρχονται από το accept[0] + τα distractors της άσκησης, ανακατεμένες ανά κενό με σταθερό seed. Απαιτεί μη κενά distractors — ο επικυρωτής σχήματος απορρίπτει το cloze_mode: "select" χωρίς αυτά.

Πολλαπλά κενά ανά cloze υποστηρίζονται: κάθε ___ στην πρόταση αντιστοιχίζεται με τη σειρά στην επόμενη καταχώρηση στο blanks. Κάθε κενό μπορεί να έχει δικό του Hint + Placeholder + λίστα Accept. Το SRS στοιχείων ξεδιπλώνει ανά κενό ένα ElementAttempt — όποιος συμπληρώνει άπταιστα το κενό A, αλλά αστοχεί συνεχώς στο κενό B, λαμβάνει παρακολούθηση κατάκτησης με κενο-γκρανουλάρ ανάλυση.

Ρόλοι token σε Cards (Φάση 52I / v1.35.0) — προαιρετικά μεταδεδομένα Card, με τα οποία η γεννήτρια cloze μπορεί στον χρόνο εκτέλεσης (συνεδρίες review + ο γύρος διόρθωσης στο τέλος του μαθήματος) να επιλέξει ένα σημασιολογικά σημαντικό κενό:

{
  "id": "art-un",
  "front": "un chat",
  "back": "eine Katze",
  "tags": ["article"],
  "token_roles": [
    {"token": "un", "role": "article"}
  ]
}

Κλειστή enum ρόλων: article / verb / noun / adjective / preposition / gender_marker / tense_marker. Η προσθήκη ενός ρόλου είναι μια αναβάθμιση δευτερεύουσας έκδοσης σχήματος — μην την επεκτείνεις inline.

Κατεύθυνση άσκησης (v1.46.0 / EXP-018)

Κάθε άσκηση δέχεται ένα προαιρετικό πεδίο direction, που δηλώνει προς ποια κατεύθυνση εξασκούν οι εκπαιδευόμενοι την κάρτα:

  • target_to_source (προεπιλογή) — ΑΝΤΙΛΗΠΤΙΚΑ: εμφανίζεται η γλώσσα στόχος, αναγνωρίζεται η γλώσσα αφετηρίας (ευκολότερο).
  • source_to_target — ΠΑΡΑΓΩΓΙΚΑ: εμφανίζεται η γλώσσα αφετηρίας, παράγεται η γλώσσα στόχος (δυσκολότερο).
  • both / random — αφήνει στον renderer / στην προσαρμοστική γεννήτρια την επιλογή μιας συγκεκριμένης κατεύθυνσης ανά προσπάθεια.
{
  "type": "matching",
  "direction": "source_to_target",
  "card_ids": ["bonjour"],
  "pairs": [{ "left": "Bonjour", "right": "Guten Tag" }]
}

Το πεδίο είναι additive — το σχήμα παραμένει στην έκδοση 1.2, και μαθήματα χωρίς direction συμπεριφέρονται ακριβώς όπως πριν (αντιληπτικά). Το SRS παρακολουθεί την κατάκτηση ανά κατεύθυνση: μια αντιληπτικά κατακτημένη κάρτα δεν είναι ακόμη παραγωγικά κατακτημένη. Οι ασκήσεις cloze είναι συμφραζομενικά δεσμευμένες και αγνοούν το direction. Για μια προοδευτική δυσκολία κρατάς τα πρώιμα μαθήματα αντιληπτικά και εισάγεις το source_to_target σε μεταγενέστερα μαθήματα (ακριβώς αυτό κάνει το ενσωματωμένο pilot-περιεχόμενο).

Σχολιασμοί για την προσαρμοστική γεννήτρια μαθημάτων (v1.36.0+)

Η προσαρμοστική γεννήτρια μαθημάτων από τη Φάση 53 (/adaptive-lesson/:setId, F-114) συνδυάζει εκ νέου τις υπάρχουσες ασκήσεις, ώστε να αντιμετωπίσει στοχευμένα τις συγκεκριμένες αδυναμίες των εκπαιδευομένων. Η γεννήτρια λειτουργεί χωρίς πρόσθετους σχολιασμούς, δύο πεδία όμως την κάνουν σημαντικά εξυπνότερη:

  1. Ευρύτερη κάλυψη token_roles σε κάρτες. Η γεννήτρια χρησιμοποιεί τα token_roles για να:
  2. Επιλέγει σημασιολογικά λογικά κενά, όταν δημιουργούνται παραλλαγές cloze από σφάλματα (ήδη στην v1.35.0)
  3. Ταξινομεί σφάλματα ως article_gender / verb_conjugation, για τα chips «εστίαση άσκησης» στο Dashboard (53E)
  4. Βρίσκει ΕΝΑΛΛΑΚΤΙΚΕΣ ασκήσεις, που ελέγχουν το ίδιο στοιχείο, όταν η αρχική άσκηση ήταν λάθος (53D λογική παραλλαγών — βρίσκει υποψήφιους, των οποίων η κάρτα έχει μια κατάλληλη καταχώρηση token_roles)

Πρόσθεσε σε ΚΑΘΕ κάρτα που διδάσκει μια δική της γραμματική μονάδα (άρθρα, κλιμένες μορφές ρημάτων, ουσιαστικά με γένος) μια καταχώρηση token_roles. Κόστος: μια πρόσθετη καταχώρηση JSON ανά κάρτα· όφελος: σημαντικά πλουσιότερη προσαρμοστική δημιουργία.

  1. Tags καρτών όπως tags: ["article", "masculine"] διαβάζονται από τον ταξινομητή σφαλμάτων ως fallback, όταν λείπουν τα token_roles. Δεν αντικαθιστούν τα token_roles — είναι ένας φθηνός μεσοβέζικος σχολιασμός.

Τι δεν χρειαζόμαστε ΑΚΟΜΗ (μετατεθειμένο σε μελλοντική αναβάθμιση σχήματος):

  • Διασταυρούμενες αναφορές related_cards ανάμεσα σε κάρτες από διαφορετικά μαθήματα
  • Αξιολογήσεις δυσκολίας ανά άσκηση (η γεννήτρια εκτιμά τη δυσκολία προς το παρόν από το exercise.type)
  • Παραδειγματικές προτάσεις ανά κάρτα στο notes, αναλύσιμες ως εναλλακτικά συμφραζόμενα cloze (η γεννήτρια cloze χρησιμοποιεί αποκλειστικά το front)

Εμπειρικός κανόνας: πρόσθεσε token_roles σε κάθε κάρτα που διδάσκει ένα γραμματικό token. Αυτή είναι μακράν η πιο αποτελεσματική συνήθεια συντάκτη για το προσαρμοστικό σύστημα.

Assets (εικόνες που φέρνει ένα σύνολο) — v1.37.0+

Οι ασκήσεις Picture-Choice και οι εικόνες εξωφύλλου καρτών προέρχονται από δύο πηγές: 1. Αρχεία asset συντάκτη, δηλωμένα στο Set-Manifest και αποστελλόμενα δίπλα στο JSON μαθήματος 2. Placeholder-SVGs, παραγόμενα από το Runtime, όταν δεν υπάρχει asset (χρωματικοί πίνακες για λέξεις χρωμάτων, μεγάλα ψηφία για αριθμούς, στυλ avatar για οτιδήποτε άλλο)

Αν δημοσιεύσεις ένα σύνολο χωρίς assets, το Picture-Choice λειτουργεί παρ' όλα αυτά — η γεννήτρια Placeholder-SVG καλύπτει χρώματα + αριθμούς αυτόματα και πέφτει πίσω για οτιδήποτε άλλο σε έναν ντετερμινιστικό avatar.

Διάταξη καταλόγων

Μέσα στον κατάλογο του συνόλου, τα assets βρίσκονται κάτω από assets/:

sets/
  language-fr-a1/
    manifest.yaml
    lessons/
      01-greetings.json
      02-numbers.json
      ...
    assets/
      img/
        chat.png
        chien.png
        oiseau.png

Δήλωση Manifest

Κάθε asset πρέπει να δηλωθεί στο Set-Manifest, ώστε ο downloader να ξέρει τι να φέρει:

sets:
  - id: language-fr-a1
    title: French A1
    language: fr
    level: A1
    version: '1.0.0'
    lesson_count: 10
    assets:
      - path: img/chat.png
        size_kb: 45
      - path: img/chien.png
        size_kb: 38

Το path είναι σχετικό προς τον κατάλογο assets/ του συνόλου (ΟΧΙ προς το JSON μαθήματος). Στο JSON μαθήματος οι ασκήσεις Picture-Choice αναφέρουν assets ΜΕ το πρόθεμα assets/:

{
  "type": "picture_choice",
  "prompt": "Welches ist 'chat'?",
  "images": [
    {"src": "assets/img/chat.png", "label": "Katze", "is_correct": "true"},
    {"src": "assets/img/chien.png", "label": "Hund"}
  ]
}

Το frontend αφαιρεί το πρόθεμα assets/ αυτόματα κατά την κλήση του asset-resolver, ώστε το JSON μαθήματος να παραμένει στη διαισθητική για τους συντάκτες μορφή.

Όρια μεγέθους + μορφής

  • Όριο ανά asset: 500 KiB. Ο επικυρωτής manifest απορρίπτει assets των οποίων το δηλωμένο size_kb υπερβαίνει αυτό το όριο. Ο downloader απορρίπτει επίσης assets των οποίων το πραγματικό μέγεθος bytes υπερβαίνει τη δήλωση κατά περισσότερο από 10% — κρατά το manifest ειλικρινές.
  • Soft-Limit ανά σύνολο: 10 MiB συνολικό μέγεθος. Ο επικυρωτής προειδοποιεί, αλλά δεν απορρίπτει.
  • Αποδεκτές μορφές: .png / .jpg / .jpeg / .webp / .svg. Κανένα GIF (το κινούμενο περιεχόμενο αποσπά την προσοχή), κανένα BMP (καμία συμπίεση). Για φωτογραφίες προτίμησε WebP — σημαντικά μικρότερο από PNG με συγκρίσιμη ποιότητα. Για εικονίδια + διαγράμματα προτίμησε SVG — κλιμακώνεται καθαρά + ελάχιστο μέγεθος αρχείου.

Συστάσεις μεγέθους

Τα πλακίδια Picture-Choice αποδίδονται έως το πολύ 150x150 px στον desktop και 100x100 px στο κινητό (object-fit: contain). Πηγαίες εικόνες με 300x300 px δίνουν στις οθόνες Retina το καλύτερο αποτέλεσμα χωρίς περιττή απαίτηση δεδομένων. PNGs πάνω από 150 KiB σπάνια δείχνουν καλύτερα από ένα καλά συμπιεσμένο WebP μισού μεγέθους.

Πότε αρκεί το Runtime-Placeholder

Τρία είδη μαθημάτων, όπου το Runtime-Placeholder είναι τόσο καλό, που οι εικόνες συντάκτη δεν φέρνουν μαθησιακό όφελος:

  • Μαθήματα χρωμάτων (rouge / rojo / rot / red): η γεννήτρια placeholder δημιουργεί ένα χρωματιστό πλακίδιο hex που ταιριάζει στο όνομα του χρώματος. Τα πλακίδια συντάκτη είναι περιττά.
  • Μαθήματα αριθμών (7 / 42 / 1492): το placeholder αποδίδει τα ψηφία μεγάλα + κεντραρισμένα. Οι εικόνες συντάκτη θα είχαν νόημα μόνο σε μη αραβικά αριθμητικά συστήματα.
  • Αφηρημένες έννοιες χωρίς προφανή οπτική αναπαράσταση (patience, liberté): το placeholder avatar παρέχει μια σαφή οπτική άγκυρα, χωρίς να επιβάλλει μια αμφιλεγόμενη επιλογή εικονιδίου.

Για οτιδήποτε άλλο (ζώα, αντικείμενα, φαγητό, τόπους, μέλη του σώματος) οι εικόνες συντάκτη βοηθούν μετρήσιμα στην αναγνώριση + ανάκληση.

Λίστα ελέγχου ποιότητας

Πριν το PR για ένα νέο μάθημα έλεγξε:

  • [ ] 3-5 βήματα θεωρίας + 8-12 ασκήσεις ανά μάθημα
  • [ ] Τουλάχιστον 3 τύποι ασκήσεων εκπροσωπούνται (matching, picture-choice, free-text, word-tiles ή cloze — cloze από v1.35.0)
  • [ ] Βήματα θεωρίας ≤ 200 λέξεις ανά βήμα
  • [ ] Ασκήσεις Free-Text: ≥ 3 παραλλαγές Accept + ≥ 3 Distraktoren
  • [ ] Word-Tiles: ≥ 3 πλακίδια ανά άσκηση
  • [ ] estimated_minutes: 10-15 (ρεαλιστικά, όχι εξιδανικευμένα)
  • [ ] Οι Distraktoren είναι λάθος-αλλά-εύλογοι — σημασιολογικά συγγενείς, ποτέ τυχαίοι
  • [ ] Card-Notes προσφέρουν πραγματική προστιθέμενη αξία (προφορά, ψευδόφιλοι, σημαία εξαίρεσης)
  • [ ] Προοδευτική δομή: μεταγενέστερες έννοιες χτίζουν πάνω σε προηγούμενες στο ίδιο σύνολο
  • [ ] Πολιτισμική ακρίβεια: πραγματική χρήση γλώσσας, όχι μόνο σχολικές φράσεις
  • [ ] Επικύρωση σχήματος: το μάθημα φορτώνει καθαρά μέσω dict_to_lesson() (δες Τοπικός έλεγχος)
  • [ ] Ακεραιότητα Card-ID: κάθε exercise.card_ids[i] υπάρχει στο cards[] του μαθήματος
  • [ ] Ζεύγος γλωσσών: target_language + source_language ορισμένα (ISO 639-1, διαφορετικά), title_native παρόν

Επικύρωση (δύο επίπεδα, v1.44.0)

Το περιεχόμενο διασφαλίζεται μέσω δύο επιπέδων επικύρωσης με τους ΙΔΙΟΥΣ ελέγχους:

  1. Στην εφαρμογή, πριν την κοινοποίηση. Κατά την κοινοποίηση μέσω Τα μαθήματά μου → Διάθεση στην κοινότητα εκτελείται πρώτα ένας έλεγχος βασισμένος σε κανόνες (πάντα, χωρίς AI). Επιβάλλει τα ελάχιστα παρακάτω· ένα σύνολο κάτω από αυτά δεν μπορεί να κοινοποιηθεί. Αν περάσει και είναι ρυθμισμένο ένα κλειδί AI, ο εκπαιδευόμενος μπορεί ΠΡΟΑΙΡΕΤΙΚΑ να ξεκινήσει έναν συμπληρωματικό έλεγχο AI (ακρίβεια μετάφρασης, ευλογοφάνεια distraktoren, γραμματική, επίπεδο, πολιτισμική ευαισθησία, φυσικότητα). Το βήμα AI δεν είναι ποτέ αυτόματο, απαιτεί ρητή συγκατάθεση (το περιεχόμενο μαθήματος αποστέλλεται στον ρυθμισμένο πάροχο) και δεν μπλοκάρει ποτέ την κοινοποίηση — ο έλεγχος βασισμένος σε κανόνες είναι η πύλη.
  2. Στο CI του repo περιεχομένου. Ένα Pull Request στο astrapi69/adaptive-learner-content εκτελεί scripts/validate_content.py (καθρεφτισμένο κάτω από docs/ci/adaptive-learner-content/) και ελέγχει κάθε σύνολο με τους ίδιους κανόνες, ώστε ένα χειροκίνητο PR να μην παρακάμπτει την πύλη.

Ελάχιστα ποιότητας (σκληρή πύλη): ≥ 5 ασκήσεις ανά μάθημα, ≥ 2 τύποι ασκήσεων, ≥ 1 βήμα θεωρίας, Free-Text ≥ 2 αποδεκτές απαντήσεις + distraktoren, Matching ≥ 3 ζεύγη, Picture-Choice με distraktoren, καμία κενή μπροστινή/πίσω όψη καρτών και (σε μη λατινικές γραφές αφετηρίας) πίσω όψεις καρτών στη γραφή αφετηρίας. Αυτά είναι ελάχιστα, όχι στόχοι — η λίστα ελέγχου παραπάνω απαιτεί περισσότερα.

Τοπικός έλεγχος

Ο επικυρωτής σχήματος του Content-Loader εκτελείται στο πλαίσιο του make test. Επικύρωσε ένα μεμονωμένο μάθημα χειροκίνητα:

cd plugins/adaptive-learner-plugin-content-loader
poetry run python -c "
import json, sys
from adaptive_learner_content_loader.schema import dict_to_lesson
path = '../adaptive-learner-content/sets/en/fr-a1/lessons/01-greetings.json'
with open(path) as f:
    lesson = dict_to_lesson(json.load(f))
print(f'OK: {lesson.id} — {len(lesson.cards)} Cards, {len(lesson.steps)} Steps')
"

Επικύρωσε όλα τα μαθήματα ενός repo περιεχομένου με τη μία — με τον επικυρωτή του repo περιεχομένου (το ίδιο script που εκτελεί το CI του σε κάθε PR):

cd ../adaptive-learner-content
python3 scripts/validate_content.py

Βρίσκει κάθε σύνολο κάτω από sets/{source}/{target-level}/ και ελέγχει το σχήμα συν τα ελάχιστα ποιότητας (≥5 ασκήσεις, ≥2 τύποι ασκήσεων, ≥1 βήμα θεωρίας, Accepts free-text + distraktoren, ζεύγη matching, καμία κενή κάρτα, ακεραιότητα Card-ID). Τα νέα μαθήματα αναγνωρίζονται αυτόματα — καμία αλλαγή test δεν χρειάζεται.

Ροή εργασίας PR

Μόλις το σύνολό σου είναι έτοιμο:

  1. Άνοιξε ένα PR έναντι του κύριου repo (για σύνολα που πρόκειται να αποσταλούν με την εφαρμογή), Ή
  2. Δημιούργησε ένα δικό σου repo περιεχομένου κάτω από τον λογαριασμό σου στο GitHub και ρύθμισε τον Content-Loader μέσω backend/config/plugins/content-loader.yaml (κάτω από default_sources).

Ο Content-Loader υποστηρίζει οποιοδήποτε δημόσιο GitHub-Repo ως πηγή. Τα ιδιωτικά repos απαιτούν ένα Personal Access Token, που ορίζεται μέσω της διαχείρισης κλειδιού τριών επιπέδων (~/.config/adaptive_learner/secrets.yaml).

Συχνές παγίδες

Αναφορές Card-ID: Κάθε καταχώρηση card_ids σε μια άσκηση πρέπει να υπάρχει στο cards[] του μαθήματος. Αν αντιγράψεις μια άσκηση μεταξύ μαθημάτων και ξεχάσεις να πάρεις μαζί τη σχετική Card, η επικύρωση αποτυγχάνει.

IDs ασφαλή για slug: Όλα τα IDs (Lesson, Card, Step, Exercise) πρέπει να ταιριάζουν με ^[a-z0-9]+(-[a-z0-9]+)*$. Καμία κάτω παύλα, καμία απόστροφος, κανένα κεφαλαίο γράμμα, καμία προηγούμενη/τελική παύλα.

is_correct: "true": Είναι ένα String, όχι Boolean JSON. Το σχήμα απαιτεί ρητά "true", επειδή τα πεδία picture_choice μοντελοποιούνται εσωτερικά ως dict[str, str].

Πρόσθετα πεδία: Κάθε μοντέλο έχει extra="forbid". Ένα μη τεκμηριωμένο πεδίο οδηγεί στην απόρριψη ολόκληρου του μαθήματος. Μείνε στα τεκμηριωμένα πεδία.

Theory-Body: Τα Theory-Steps χρειάζονται ένα μη κενό πεδίο body (Markdown). Τα Exercise-Steps δεν επιτρέπεται να φέρουν body — χρησιμοποίησε αντ' αυτού το prompt της άσκησης.

Αναφορά: τα pilot-σύνολα

Τα δύο σύνολα που αποστέλλονται με τον Adaptive Learner είναι οι κανονικές αναφορές:

  • sets/en/fr-a1/ — Γαλλικά A1 για Αγγλόφωνους (10 μαθήματα, ~2 ώρες)· το sets/de/fr-a1/ είναι το γερμανόφωνο pilot-σύνολο.
  • sets/en/es-a1/ + sets/de/es-a1/ — Ισπανικά A1 (15 μαθήματα ανά γλώσσα αφετηρίας), στο repo adaptive-learner-content.

Και τα δύο ακολουθούν τις συμβάσεις που περιγράφονται σε αυτόν τον οδηγό. Το να διαβάσεις ένα ολόκληρο μάθημα είναι ο γρηγορότερος τρόπος να αφομοιώσεις τη δομή.


Δρόμος προς τη συμμετοχή της κοινότητας (v1.42.0)

Δεν χρειάζεται να δημιουργείς μαθήματα από το μηδέν χειροκίνητα. Ο γρηγορότερος τρόπος να συνεισφέρεις είναι να δημιουργήσεις και να μοιραστείς ένα μάθημα στην εφαρμογή:

  1. Εισήγαγε ένα chat και ανάλυσέ το, μετά Αποθήκευση ως μάθημα Offline (ή ολοκλήρωσε ένα προσαρμοστικό μάθημα και Αποθήκευση αυτού του μαθήματος;). Το μάθημα εμφανίζεται κάτω από Τα μαθήματά μου στον Set-Browser.
  2. Κάνε κλικ στα «Τα μαθήματά μου» στο Εξαγωγή ως Content-Set, για να κατεβάσεις ένα Content-Set ως .zip (Manifest + μαθήματα). Οι εξαγωγές περιέχουν μόνο το περιεχόμενο μαθήματος — καμία πρόοδο, καμία ιστορία σφαλμάτων, τίποτα προσωπικό.
  3. Κάνε κλικ στο Διάθεση στην κοινότητα, για να ανοίξεις ένα προσυμπληρωμένο Pull Request στο repository περιεχομένου — το JSON μαθήματος γίνεται commit στη σωστή διαδρομή του δέντρου, χωρίς ανάγκη επισύναψης .zip.
  4. Το CI του repo επικυρώνει το PR αυτόματα· ένας maintainer ελέγχει το μάθημα, εναρμονίζει το manifest (id, title, language, level, tags) με τις παραπάνω συμβάσεις και το συγχωνεύει κάτω από sets/. Μετά τη συγχώνευση όλοι μπορούν να το κατεβάσουν από τον Set-Browser.

Αυτός είναι ο κοινωνικός δρόμος: Ο έλεγχος είναι χειροκίνητος (ένας maintainer επιμελείται κάθε προσθήκη — τίποτα δεν δημοσιεύεται αυτόματα), και όλη η ροή χρειάζεται μόνο το GitHub. Τα δημιουργημένα μαθήματα επικυρώνονται ήδη έναντι του σχήματος, ώστε ένα μάθημα που συνεισφέρεται συνήθως χρειάζεται μόνο λίγη λείανση manifest.

Οδηγός κοινοποίησης, παραλλαγές και Credit συντάκτη (Φάση 64)

Η κοινοποίηση ενός μαθήματος από τα Τα μαθήματά μου ανοίγει έναν οδηγό τεσσάρων σταδίων, αντί να μεταβαίνει απευθείας στο GitHub:

  1. Προεπισκόπηση + τοποθέτηση. Η εφαρμογή υπολογίζει ακριβώς πού προσγειώνεται το μάθημα στο δέντρο (sets/{quelle}/{ziel}-{niveau}/) και ένα αυτόματα αριθμημένο όνομα αρχείου ({nn}-{slug}.json, ο επόμενος αριθμός μετά τα υπάρχοντα μαθήματα). Ένα εντελώς νέο ζεύγος + επίπεδο δείχνει «Νέο σύνολο! Είσαι ο πρώτος.»
  2. Έλεγχος διπλότυπων. Το μάθημα συγκρίνεται με τα ήδη υπάρχοντα μαθήματα σε αυτή τη διαδρομή (επικάλυψη καρτών και ασκήσεων — συμβουλευτική, ποτέ αποκλειστική). Αν υπάρχει κάτι παρόμοιο, μπορείς να:
  3. Κοινοποιήσεις ως παραλλαγή — το μάθημα σημειώνεται με variation_of: "{original_id}" συν ένα προαιρετικό variation_note («Σε τι διαφέρει η εκδοχή σου;»).
  4. Προτείνεις μόνο τις νέες ασκήσεις (σε σχεδόν διπλότυπα) — ο οδηγός εξάγει ακριβώς τις ασκήσεις που λείπουν από το πρωτότυπο, μαζί με τις σχετικές κάρτες, ως παραλλαγή συμπλήρωσης.
  5. Σύνοψη ποιότητας. Τα ευρήματα του επικυρωτή βασισμένου σε κανόνες (συν ο προαιρετικός έλεγχος AI)· οι προειδοποιήσεις εμφανίζονται, αλλά δεν μπλοκάρουν ποτέ.
  6. Κοινοποίηση + γιορτή. Ένα κλικ ανοίγει το GitHub-Pull-Request (επεξεργαστής αρχείου σε μικρά μαθήματα, σελίδα Upload σε μεγάλα), και η εφαρμογή σε ευχαριστεί με μια μικρή γιορτή.

Πεδία παραλλαγών και Credit (Σχήμα 1.3, όλα προαιρετικά)

{
  "variation_of": "10-passe-compose",
  "variation_note": "Mehr Übungen zur Angleichung",
  "contributed_by": "Maria S.",
  "contributed_at": "2026-06-01T14:30:00Z"
}

Και τα τέσσερα είναι additive και προαιρετικά· μαθήματα χωρίς αυτά συμπεριφέρονται ακριβώς όπως πριν. Το contributed_by ορίζεται, όταν ο συντάκτης ενεργοποιεί το Credit κατά την κοινοποίηση (ένα πεδίο «Το όνομά σου (προαιρετικό)», που απομνημονεύεται τοπικά για την επόμενη φορά). Αν υπάρχει, ο προβολέας εμφανίζει μια διακριτική γραμμή «Διατέθηκε από {name}» κάτω από τον τίτλο, και το κείμενο του Pull-Request αναφέρει τον συντάκτη στον πίνακα μεταδεδομένων του.

Ιστορικό συνεισφορών και κενά

Τα κοινοποιημένα μαθήματα απομνημονεύονται τοπικά (κανένας λογαριασμός δεν χρειάζεται) κάτω από Οι συνεισφορές μου με έναν μετρητή και μια διάκριση Συνεισφέρων κοινότητας από πέντε κοινοποιημένα μαθήματα και άνω. Ο Set-Browser εμφανίζει επιπλέον Μαθήματα που λείπουν — ενθαρρυντικές προτάσεις για το επόμενο επίπεδο CEFR ενός υπάρχοντος ζεύγους ή μια γλώσσα στόχο, που υπάρχει για μια γλώσσα αφετηρίας, αλλά λείπει για μια άλλη («Μπορείς να βοηθήσεις;»).


Σχετικές σελίδες