some initial testdata again

This commit is contained in:
Hartmut Seichter 2025-03-17 20:47:27 +01:00
parent c4ba0f5e75
commit 2e1490d98f
8 changed files with 448 additions and 96 deletions

View file

@ -2,29 +2,67 @@
# coursebuilder # coursebuilder
# #
from typing import Any
from argparse import ArgumentParser from argparse import ArgumentParser
from pathlib import Path from pathlib import Path
import yaml import yaml
from coursebuilder.schema import Schema
class LanguageSelector:
context_lang: str
class Course: class Course:
def i18n_name: str = "lang.{}.yaml"
mod_name: str = "mod.yaml"
def __init__(self, *, path: Path) -> None:
with open(path) as f:
# resolve rest of bundle
self.__data = yaml.load(f, Loader=yaml.Loader)
# load i18n overlays
self.__i18n = {
f"{str(p).split('.')[1]}": yaml.load(open(p), Loader=yaml.Loader)
for p in path.parent.glob(Course.i18n_name.format("*"))
}
def validate(self, *, schema: Schema, lang: str) -> None:
print(self.__data)
pass
def __getitem__(self, name: str, /) -> Any:
return (
self.__data[name]
if name in self.__data.keys()
else self.__i18n[LanguageSelector.context_lang][name]
)
def __getattr__(self, name: str, /) -> Any:
return self.__data[name]
def __str__(self):
return f"data:{self.__data}\ni18n:{self.__i18n}"
class StudyCourse: class StudyCourse:
def __init__(self, data: dict | None, path: Path | None): def __init__(self, *, path: Path) -> None:
self.path: Path | None = path self.path = path
self.data: dict | None = data
courses: list[Course] = []
@staticmethod
def load(*, path: str):
with open(path) as f: with open(path) as f:
data = yaml.load(f, Loader=yaml.Loader) self.__data = yaml.load(f, Loader=yaml.Loader)
return StudyCourse(data=data, path=Path(path))
self.courses: dict[str, Course] = {
f"{c}": Course(path=self.path.parent / c / Course.mod_name)
for c in self.__data["courses"]
}
def __getattr__(self, name: str, /) -> Any:
return self.__data[name] if self.__data else None
def __str__(self): def __str__(self):
return f"path: {self.path}\ndata: {self.data}" return f"path: {self.path}\ndata: {self.__data}"
def main(): def main():
@ -36,16 +74,31 @@ def main():
"-i", "-i",
"--input", "--input",
type=str, type=str,
help="folder with project data", help="course file with definition of the course",
)
parser.add_argument(
"-s",
"--schema",
type=str,
help="schema to validate against",
) )
# get arguments # get arguments
args = parser.parse_args() args = parser.parse_args()
# just input # just input
if args.input: if args.input and args.schema:
sc = StudyCourse.load(path=(Path(".") / args.input).absolute()) with open(args.schema) as f_schema:
print(sc) schema = Schema(schema=yaml.load(f_schema, Loader=yaml.Loader))
sc = StudyCourse(path=(Path(".") / args.input).absolute())
LanguageSelector.context_lang = "de"
for k in schema.keys():
print(k)
for shortcode, course in sc.courses.items():
print(course[k])
# run as main # run as main

View file

@ -1,8 +1,5 @@
import string
class Schema: class Schema:
def __init__(self, *, schema: dict) -> None:
def __init__(self,schema) -> None:
self.__schema = schema self.__schema = schema
def __getitem__(self, field): def __getitem__(self, field):
@ -11,57 +8,82 @@ class Schema:
def keys(self): def keys(self):
return self.__schema.keys() return self.__schema.keys()
def is_translatable(self,field): def is_translatable(self, field):
if 'translatable' in self.__schema[field]: if "translatable" in self.__schema[field]:
return self.__schema[field]['translatable'] return self.__schema[field]["translatable"]
else: else:
return True return True
def needs_spec(self,field): def needs_spec(self, field):
if 'spec' in self.__schema[field]: if "spec" in self.__schema[field]:
return self.__schema[field] return self.__schema[field]
else: else:
return False return False
def get_value(self,meta,field,lang): def get_value(self, meta: dict, field: str, lang: str):
""" """
treats receiving the value like a variant, treats receiving the value like a variant,
returns values with their language specific representations returns values with their language specific representations
""" """
match self.__schema[field]['type']: match self.__schema[field]["type"]:
case 'str': return meta[field][lang] if self.is_translatable(field) else meta[field]['value'] case "str":
case 'enum' | 'int' | 'num' | 'multikey' : return meta[field]['value'] return (
case 'multinum': return meta[field]['value'] if hasattr(meta[field]['value'],'__iter__') else [meta[field]['value'],] # force list! meta[field][lang]
if self.is_translatable(field)
else meta[field]["value"]
)
case "enum" | "int" | "num" | "multikey":
return meta[field]["value"]
case "multinum":
return (
meta[field]["value"]
if hasattr(meta[field]["value"], "__iter__")
else [
meta[field]["value"],
]
) # force list!
def to_list_of_dict(self,meta,fields,lang): def to_list_of_dict(self, meta, fields, lang):
""" """
generates a list of dict which can easily be converted generates a list of dict which can easily be converted
to a pandas dataframe to a pandas dataframe
""" """
# list comprehension for rows # list comprehension for rows
return [{'field' : field, # field name return [
'lang' : lang, # language shortcode {
'type' : self.__schema[field]['type'], # datatype "field": field, # field name
'label' : self.__schema[field]['label'][lang], # label "lang": lang, # language shortcode
'value' : self.get_value(meta,field,lang), # actual value "type": self.__schema[field]["type"], # datatype
'template' : self.__schema[field]['template'][lang] if 'template' in self.__schema[field] else None, "label": self.__schema[field]["label"][lang], # label
# getting crazy with nested dict comprehension "value": self.get_value(meta, field, lang), # actual value
'enum_values' : { k:v[lang] for (k,v) in self.__schema[field]['values'].items()} if 'enum' in self.__schema[field]['type'] else None, "template": self.__schema[field]["template"][lang]
'key_values' : { k:v[lang] for (k,v) in self.__schema[field]['keys'].items()} if 'multikey' in self.__schema[field]['type'] else None, if "template" in self.__schema[field]
'spec' : meta[field]['spec'][lang] if 'spec' in meta[field] else None else None,
} # getting crazy with nested dict comprehension
for field in fields] "enum_values": {
k: v[lang] for (k, v) in self.__schema[field]["values"].items()
}
if "enum" in self.__schema[field]["type"]
else None,
"key_values": {
k: v[lang] for (k, v) in self.__schema[field]["keys"].items()
}
if "multikey" in self.__schema[field]["type"]
else None,
"spec": meta[field]["spec"][lang] if "spec" in meta[field] else None,
}
for field in fields
]
def to_short_dict(self, meta, fields, lang):
def to_short_dict(self,meta,fields,lang):
""" """
generates a short version of dict which can easily be converted generates a short version of dict which can easily be converted
to a pandas dataframe to a pandas dataframe
""" """
# dict comprehension for whole meta part # dict comprehension for whole meta part
return { field : self.get_value(meta,field,lang) for field in fields } return {field: self.get_value(meta, field, lang) for field in fields}
def to_list_of_tuple(self,meta,fields,lang): def to_list_of_tuple(self, meta, fields, lang):
""" """
generates a list of tuples with a label and value (text) generates a list of tuples with a label and value (text)
this is usually consumed by a Markdown generator this is usually consumed by a Markdown generator
@ -69,19 +91,52 @@ class Schema:
todo: needs deuglyfication of free standing loop, templates are possible for all todo: needs deuglyfication of free standing loop, templates are possible for all
""" """
list = [] list = []
for r in self.to_list_of_dict(meta,fields,lang): for r in self.to_list_of_dict(meta, fields, lang):
match r['type']: match r["type"]:
case 'str' : case "str":
list.append( (r['label'],r['value']) ) list.append((r["label"], r["value"]))
case 'int' | 'num' : case "int" | "num":
list.append( ( r['label'], r['template'].format(value=r['value'],spec=r['spec']) if r['template'] else r['value']) ) list.append(
case 'enum' : (
list.append( ( r['label'], r['template'].format(value=r['enum_values'][r['value']],spec=r['spec']) r["label"],
if r['template'] else r['enum_values'][r['value']] ) ) r["template"].format(value=r["value"], spec=r["spec"])
case 'multikey' : if r["template"]
list.append( ( r['label'], ', '.join( [r['template'].format(key=r['key_values'][k],value=v) for k,v in r['value'].items()] ) ) ) else r["value"],
case 'multinum' : )
list.append( (r['label'], ', '.join( r['template'].format(value=v) for v in r['value'])) ) )
case "enum":
list.append(
(
r["label"],
r["template"].format(
value=r["enum_values"][r["value"]], spec=r["spec"]
)
if r["template"]
else r["enum_values"][r["value"]],
)
)
case "multikey":
list.append(
(
r["label"],
", ".join(
[
r["template"].format(
key=r["key_values"][k], value=v
)
for k, v in r["value"].items()
]
),
)
)
case "multinum":
list.append(
(
r["label"],
", ".join(
r["template"].format(value=v) for v in r["value"]
),
)
)
return list return list

View file

@ -1,19 +1,19 @@
fields: field-names:
- name - name
- instructor - instructor
- id - id
- goal - goal
- content - content
- form-of-instruction - form-of-instruction
- prerequisites - prerequisites
- teaching-material - teaching-material
- author-of-indenture - author-of-indenture
- used-in - used-in
- workload - workload
- credits - credits
- form-of-exam - form-of-exam
- term - term
- frequency - frequency
- duration - duration
- kind - kind
- remarks - remarks

View file

@ -34,8 +34,7 @@ teaching-material: |
prerequisites: | prerequisites: |
Formale Voraussetzung bestehen nicht. Für eine erfolgreiche Teilnahme sollte das Modul „Grundlagen der Computergrafik“ im Vorfeld belegt werden. Formale Voraussetzung bestehen nicht. Für eine erfolgreiche Teilnahme sollte das Modul „Grundlagen der Computergrafik“ im Vorfeld belegt werden.
author-of-indenture: form-of-exam:
spec: abzugebende Projektarbeit (70%) und mündliche Prüfung (30% ~20min)
workload: "150h Insgesamt bestehend aus 60 Stunden Präsenzzeit, 60 Stunden Selbststudium, 30h Prüfung und Prüfungsvorbereitung" workload: "150h Insgesamt bestehend aus 60 Stunden Präsenzzeit, 60 Stunden Selbststudium, 30h Prüfung und Prüfungsvorbereitung"
remarks:

View file

@ -29,5 +29,3 @@ workload: "overall 150h comprising of 60h in-person training, 60h of self-study
form-of-exam: form-of-exam:
spec: submitted project (70%) and oral exam (30% ~20min) spec: submitted project (70%) and oral exam (30% ~20min)
remarks:

View file

@ -1,9 +1,9 @@
id: 3DCC
name: 3D Content Creation name: 3D Content Creation
instructor: Prof. Hartmut Seichter, PhD instructor: Prof. Hartmut Seichter, PhD
id: 3DCC
form-of-instruction: form-of-instruction:
- seminar: 2 - seminar: 2
- exersise: 2 - exersise: 2
@ -12,7 +12,12 @@ credits: 5
form-of-exam: form-of-exam:
type: alternative type: alternative
spec: abzugebende Projektarbeit (70%) und mündliche Prüfung (30% ~20min)
duration: duration:
value: 1 value: 1
author-of-indenture:
kind: elective
remarks:

View file

@ -1,4 +1,15 @@
# shortcode
shortcode: maacs shortcode: maacs
# name
name: Applied Computer Science (Master of Science) name: Applied Computer Science (Master of Science)
# validation schema
schema: schema.yaml
# languages (oder defines the order of overlay)
languages: [de, en]
# courses
courses: courses:
- 3dcc - 3dcc

231
test/maacs/v2/schema.yaml Normal file
View file

@ -0,0 +1,231 @@
# fields in curricular description
# leaning on methods in OpenAPI 3.0
#
# Modulname
#
name:
type: str
label:
de: "Modulname"
en: "name of course"
#
# Modulverantwortliche:r
#
instructor:
type: str
label:
de: "Modulverantwortlicher / Modulverantwortliche"
en: "module instructor"
#
# Kürzel / ID
#
id:
type: str
translatable: false
label: { de: "Kürzel", en: "code" }
#
# Qualifikationsziele
#
# Welche fachbezogenen, methodischen, fachübergreifende Kompetenzen,
# Schlüsselqualifikationen - werden erzielt (erworben)? Diese sind
# an der zu definierenden Gesamtqualifikation (angestrebter Abschluss) auszurichten.
#
# Lernergebnisse sind Aussagen darüber, was ein Studierender nach Abschluss des Moduls weiß,
# versteht und in der Lage ist zu tun. Die Formulierung sollte sich am Qualifikationsrahmen
# für Deutsche Hochschulabschlüsse orientieren und Inhaltswiederholungen vermeiden.
#
# Des Weiteren finden Sie im QM-Portal die „Handreichung zur Beschreibung von Lernzielen“
# als Formulierungshilfe.
goal:
type: str
label: { de: "Qualifikationsziele", en: "educational goal" }
#
# Modulinhalte
#
# Welche fachlichen, methodischen, fachpraktischen und fächerübergreifenden
# Inhalte sollen vermittelt werden?
#
# Es ist ein stichpunktartiges Inhaltsverzeichnis zu erstellen.
content:
type: str
label: { de: "Modulinhalte", en: "content" }
#
# Lehrform
#
#
# Welche Lehr- und Lernformen werden angewendet?
# (Vorlesungen, Übungen, Seminare, Praktika,
# Projektarbeit, Selbststudium)
#
# Es sind nur Werte aus der Prüfungsordung zugelassen
#
form-of-instruction:
type: multikey
label: { de: "Lehrform(en)", en: "form of instruction" }
keys:
{
"lecture": { de: "Vorlesung", en: "lecture" },
"lecture_seminar":
{ de: "Seminaristische Vorlesung", en: "lecture and seminar" },
"seminar": { de: "Seminar", en: "seminar" },
"exersise": { de: "Übung", en: "lab exersise" },
"pc_lab": { de: "Rechnergestütztes Praktikum", en: "PC exersise" },
"project": { de: "Project", en: "project" },
}
template:
de: "{key} ({value}SWS)"
en: "{key} ({value}SWS)"
#
# Voraussetzungen für die Teilnahme
#
# Für jedes Modul sind die Voraussetzungen für die Teilnahme zu beschreiben.
# Welche Kenntnisse, Fähigkeiten und Fertigkeiten sind für eine
# erfolgreiche Teilnahme vorauszusetzen?
#
# Alternativ können die Module benannt werden welche für die erfolgreiche
# Teilnahme im Vorfeld zu belegen sind.
prerequisites:
type: str
label: { de: "Voraussetzungen für die Teilnahme", en: "prerequisites" }
#
# Literatur und multimediale Lehr- und Lernprogramme
#
#
# Wie können die Studierenden sich auf die Teilnahme an diesem Modul vorbereiten?
#
teaching-material:
type: str
label:
{
de: "Literatur und multimediale Lehr- und Lernprogramme",
en: "media of instruction",
}
#
# Lehrbriefautor
#
author-of-indenture:
type: str
label: { de: "Lehrbriefautor", en: "author of indenture" }
#
# Verwendung in (Studienprogramm)
#
# used-in:
# type: str
# label: { de: "Verwendung", en: "used in study programs" }
#
# Arbeitsaufwand
#
workload:
type: str
label: { de: "Arbeitsaufwand / Gesamtworkload", en: "workload" }
#
# credits/ECTS
#
credits:
type: num
unit: ECTS
label:
{
en: "credits and weight of mark",
de: "Kreditpunkte und Gewichtung der Note in der Gesamtnote",
}
template:
de: "{value}CP, Gewichtung: {value}CP von 120CP "
en: "{value}CP, weight: {value} / 120 "
#
# Leistungsnachweis
#
form-of-exam:
type: enum
label: { de: "Leistungsnachweis", en: "form of examination" }
values:
{
"written": { de: "Schriftliche Prüfung", en: "written exam" },
"oral": { de: "Mündliche Prüfung", en: "oral exam" },
"alternative":
{
de: "Alternative Prüfungunsleistung",
en: "alternative examination",
},
}
spec: true
template:
de: "{value} ({spec})"
en: "{value} ({spec})"
#
# Semester
#
# term:
# type: multinum
# label: { de: "Semester", en: "term" }
# template:
# de: "{value}\\. Semester"
# en: "{value}\\. semester"
#
# Häufigkeit des Angebots
#
# frequency:
# type: enum
# label: { de: "Häufigkeit des Angebots", en: "frequency of Offer" }
# values:
# {
# "once_per_term": { de: "jedes Semester", en: "every semester" },
# "once_per_year":
# { de: "einmal im Studienjahr", en: "once per study year" },
# }
#
# Dauer des Angebots
#
# duration:
# type: int
# label:
# de: Dauer
# en: duration
# template:
# de: "{value} Semester"
# en: "{value} term(s)"
#
# Art der Veranstaltung
#
kind:
type: enum
label:
{
de: "Art der Veranstaltung (Pflicht, Wahl, etc.)",
en: "kind of module (compulsory, elective)",
}
values:
{
"compulsory": { de: "Pflicht", en: "compulsory" },
"elective": { de: "Wahl/Wahlpflicht", en: "elective" },
}
#
# Freiform Bemerkungen
#
remarks:
type: str
label: { de: "Besonderes", en: "remarks" }