small update to get better query mode working

This commit is contained in:
Hartmut Seichter 2024-05-29 20:02:48 +02:00
parent ef011cda55
commit d41712e010
4 changed files with 164 additions and 234 deletions

View file

@ -1,11 +1,11 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
CourseBuilder CourseBuilder
Coursebuilder is a preprocessor tool for [pandoc](https://pandoc.org) Coursebuilder is a preprocessor tool for [pandoc](https://pandoc.org)
to generate multi-lingual curricula documentation tables from to generate multi-lingual curricula documentation tables from
structured representations as a flatfile database. Data scheme and structured representations as a flatfile database. Data scheme and
actual values are kept in YAML files in order to version them with git. actual values are kept in YAML files in order to version them with git.
""" """
@ -27,7 +27,7 @@ class CourseBuilder:
@staticmethod @staticmethod
def generate(args): def generate(args):
if args.schema and args.meta: if args.schema and args.meta:
# get actual fields # get actual fields
actual_fields = None actual_fields = None
@ -37,8 +37,8 @@ class CourseBuilder:
actual_fields = yaml.load(ff,Loader=yaml.Loader)['fields'] actual_fields = yaml.load(ff,Loader=yaml.Loader)['fields']
else: else:
# seem we have a list or None # seem we have a list or None
actual_fields = args.fields actual_fields = args.fields
# get schema # get schema
schema = None schema = None
with open(args.schema) as f: with open(args.schema) as f:
@ -66,12 +66,12 @@ class CourseBuilder:
title_template=args.title, title_template=args.title,
first_colwidth=args.leftcol) first_colwidth=args.leftcol)
elif args.query: elif args.query:
lot = schema.to_short_dict( lot = schema.to_short_dict(
meta=yaml.load(fm,Loader=yaml.Loader), meta=yaml.load(fm,Loader=yaml.Loader),
fields=actual_fields, fields=actual_fields,
lang=args.lang) lang=args.lang)
result_df.append(pd.DataFrame([lot])) result_df.append(pd.DataFrame([lot]))
else: else:
MarkdownGenerator.generate_table( MarkdownGenerator.generate_table(
@ -85,16 +85,17 @@ class CourseBuilder:
# query mode # query mode
if args.query and len(result_df): if args.query and len(result_df):
# got the list # got the list
df = pd.concat(result_df,ignore_index=True) df = pd.concat(result_df,ignore_index=True)
# generate a dataframe # generate a dataframe
df_q = df.query(args.query) df_q = df.query(args.query)
# generate a compound column --query-compound column:sum # generate a compound column --query-compound column:sum
if args.query_compound: if args.query_compound:
df_q.loc[:,'form-of-instruction.sum'] = df_q['form-of-instruction'].apply(lambda x: sum(list(x.values()))) print(args.query_compound)
df_q.loc[:,'form-of-instruction.sum'] = df_q['form-of-instruction'].apply(lambda x: sum(list(x.values())))
# --query-sort is parameterized as min:credits - hence direction:column # --query-sort is parameterized as min:credits - hence direction:column
if args.query_sort: if args.query_sort:
@ -111,7 +112,8 @@ class CourseBuilder:
# set value transforms # set value transforms
if args.query_template: if args.query_template:
# no idea yet how to parameterize this
ww = 'written' ww = 'written'
#df_q['form-of-exam'] = 'Schriftlich' if df_q.loc[:,'form-of-exam'] == 'written' else 'was anderes' #df_q['form-of-exam'] = 'Schriftlich' if df_q.loc[:,'form-of-exam'] == 'written' else 'was anderes'
# mm = Template("{'written':'S','oral':'mündlich'}[${v}]")? # mm = Template("{'written':'S','oral':'mündlich'}[${v}]")?
@ -121,8 +123,8 @@ class CourseBuilder:
df_summary = pd.DataFrame([{ df_summary = pd.DataFrame([{
'sum.credits': df_q['credits'].sum() 'sum.credits': df_q['credits'].sum()
}]) }])
# set labels # set labels directly!
if args.query_labels: if args.query_labels:
df_q.columns = args.query_labels df_q.columns = args.query_labels
@ -135,7 +137,7 @@ class CourseBuilder:
@staticmethod @staticmethod
def run(): def run():
# arguments # arguments
parser = ArgumentParser(description='versatile curricula generator') parser = ArgumentParser(description='versatile curricula generator')
@ -144,7 +146,7 @@ class CourseBuilder:
parser.add_argument('-l','--lang',help="Language to parse from meta file (use de or en)",default='de') parser.add_argument('-l','--lang',help="Language to parse from meta file (use de or en)",default='de')
parser.add_argument('-f','--fields',help="Fields to be used, the table will be build accordingly",action="extend", nargs="+", type=str) parser.add_argument('-f','--fields',help="Fields to be used, the table will be build accordingly",action="extend", nargs="+", type=str)
parser.add_argument('-s','--schema', help="using provided schema") parser.add_argument('-s','--schema', help="using provided schema")
# query mode # query mode
parser.add_argument('-q','--query', type=str, default=None, help="compound query to select items") parser.add_argument('-q','--query', type=str, default=None, help="compound query to select items")
parser.add_argument('-qs','--query-sort',type=str,default=None,help="sort query with a min/max over a column like min:credits") parser.add_argument('-qs','--query-sort',type=str,default=None,help="sort query with a min/max over a column like min:credits")
@ -152,8 +154,8 @@ class CourseBuilder:
parser.add_argument('-qf','--query-filter',type=str,default=[],action="extend", nargs="+",help="filter final list of columns for output") parser.add_argument('-qf','--query-filter',type=str,default=[],action="extend", nargs="+",help="filter final list of columns for output")
parser.add_argument('-ql','--query-labels',type=str,default=[],action="extend", nargs="+",help="new labels for query like") parser.add_argument('-ql','--query-labels',type=str,default=[],action="extend", nargs="+",help="new labels for query like")
parser.add_argument('-qt','--query-template',type=str,default=[],action="extend", nargs="+",help="templates for values in the form of {value}") parser.add_argument('-qt','--query-template',type=str,default=[],action="extend", nargs="+",help="templates for values in the form of {value}")
# create pagebreaks # create pagebreaks
parser.add_argument('-p','--pagebreak',action="store_true",help="add a pagebreak after each module") parser.add_argument('-p','--pagebreak',action="store_true",help="add a pagebreak after each module")
parser.add_argument('--title',type=str,default=None,help="template for title - use curly brackets (i.e. {}) to mark where the title string is inserted") parser.add_argument('--title',type=str,default=None,help="template for title - use curly brackets (i.e. {}) to mark where the title string is inserted")
@ -163,22 +165,22 @@ class CourseBuilder:
parser.add_argument('--template',type=str,default=None,help='defines a template to be used with fields') parser.add_argument('--template',type=str,default=None,help='defines a template to be used with fields')
parser.add_argument('-o','--out',type=str,default=None,help='set the output type') parser.add_argument('-o','--out',type=str,default=None,help='set the output type')
parser.add_argument('--legacy',action="store_true",help="use legacy generator mode for compatibility") parser.add_argument('--legacy',action="store_true",help="use legacy generator mode for compatibility")
parser.add_argument('--leftcol',type=int,default=35,help='maximum size of left column') parser.add_argument('--leftcol',type=int,default=35,help='maximum size of left column')
# get arguments # get arguments
args = parser.parse_args() args = parser.parse_args()
if args.table_gen: if args.table_gen:
tg = TableGenerator() tg = TableGenerator()
tg.generate_table(args.table_gen) tg.generate_table(args.table_gen)
return return
# book mode with predefined setting from a book file # book mode with predefined setting from a book file
if args.book and args.schema: if args.book and args.schema:
with open(args.book) as bf: with open(args.book) as bf:
actual_fields = [] actual_fields = []
@ -199,13 +201,13 @@ class CourseBuilder:
# gernerate section wise parts # gernerate section wise parts
if 'modules' in section: if 'modules' in section:
# override fields # override fields
args.fields = actual_fields args.fields = actual_fields
# expand filenames to be relative to the book # expand filenames to be relative to the book
args.meta = [os.path.join(os.path.dirname(book_path),mod_path) for mod_path in section['modules']] args.meta = [os.path.join(os.path.dirname(book_path),mod_path) for mod_path in section['modules']]
CourseBuilder.generate(args=args) CourseBuilder.generate(args=args)
# verbose command line mode # verbose command line mode
@ -217,6 +219,6 @@ class CourseBuilder:
# run as main # run as main
if __name__ == '__main__': if __name__ == '__main__':
# recommended setting for pandas # recommended setting for pandas
pd.options.mode.copy_on_write = True pd.options.mode.copy_on_write = True
# run # run
CourseBuilder.run() CourseBuilder.run()

View file

@ -1,23 +1,23 @@
# Concept # concept
The concept behind coursebuilder is to store curricula descriptions in `YAML` files that can be versioned in a git repository. Unlike classic databases an observable and well defined versioning is paramount in these descriptions as they are the legal foundation for study and exam regulations. The concept behind coursebuilder is to store curricula descriptions in `YAML` files that can be versioned in a git repository. Unlike classic databases, an observable and well defined versioning is paramount in these descriptions as they are the legal foundation for study and exam regulations.
The following pieces play together here: The following pieces play together here:
- `schema` files, usually a `schema.yaml` - `schema` files, usually a `schema.yaml`
- `mod` files, usually something along the lines of `mod.coursecode.yaml` - `mod` files, usually something along the lines of `mod.coursecode.yaml`
- `book` files describing a whole regulation set and course global details - `book` files describing a whole regulation set and course global details
- some sort of transformation with `coursebuilder` into Markdown that is piped through [pandoc](https://pandoc.org) in order to generate PDF, HTML and other representation from this code - some sort of transformation with `coursebuilder` into Markdown that is piped through [pandoc](https://pandoc.org) in order to generate PDF, HTML and other representation from this code
# schema files # schema files
Schema files are responsible to describe the used structures in a database. The following datatypes are supported: Schema files are responsible to describe the used structures in a database. The following datatypes are supported:
- `str` a simple string, can be accompanied with a `template` - `str` a simple string, can be accompanied with a `template`
- `enum` a classic enum datatype with a fixed set of values - `enum` a classic enum datatype with a fixed set of values
- `num` a numeric datatype - `num` a numeric datatype
- `multinum` an array type with the possibility to `spec` each value - `multinum` an array type with the possibility to `spec` each value
- `multikey` a key-value type with additional numeric data associated with each key instance - `multikey` a key-value type with additional numeric data associated with each key instance
# mod files (modules) # mod files (modules)
@ -31,11 +31,11 @@ Modules describe a course in detail and implement an instance of the schema file
```yaml ```yaml
# this would reside in a schema field on top level # this would reside in a schema field on top level
# a field of name 'id' # a field of name 'id'
id: # name of the field id: # name of the field
type: str # sets the datatype to str type: str # sets the datatype to str
translatable: false # enforces the value is not translatable (default is true) translatable: false # enforces the value is not translatable (default is true)
label: { # label describes the meaning of the datatype in regards of the schema label: { # label describes the meaning of the datatype in regards of the schema
de: "Kürzel", # translation of the label in German (de) de: "Kürzel", # translation of the label in German (de)
en: "code" # translation of the label in English (en) en: "code" # translation of the label in English (en)
} }
``` ```

View file

@ -1,4 +1,4 @@
# debug make file for testing
build_dir := build build_dir := build
target_en := ${build_dir}/table.en.pdf target_en := ${build_dir}/table.en.pdf
target_de := ${build_dir}/table.de.pdf target_de := ${build_dir}/table.de.pdf
@ -25,7 +25,7 @@ ${target_de}: mod.cg.yaml
${target_de_book}: *.yaml ${target_de_book}: *.yaml
python ${coursebuilder} -s schema.yaml -b book.yaml -p --title "### {}" -l de --leftcol 25 --legacy | pandoc ${target_flags} -V toc:true -V lang:de -o ${target_de_book} python ${coursebuilder} -s schema.yaml -b book.yaml -p --title "### {}" -l de --leftcol 25 --legacy | pandoc ${target_flags} -V toc:true -V lang:de -o ${target_de_book}
clean: clean:
rm -f ${targets} rm -f ${targets}
debug: debug:
@ -38,4 +38,4 @@ debug-query:
debug-query-book: debug-query-book:
python ${coursebuilder} -s schema.yaml -b book.yaml -q "kind=='compulsory'" -qs min:credits -qc form-of-instruction -qf name credits form-of-instruction -ql Modulname Kürzel Kreditpunkte python ${coursebuilder} -s schema.yaml -b book.yaml -q "kind=='compulsory'" -qs min:credits -qc form-of-instruction -qf name credits form-of-instruction -ql Modulname Kürzel Kreditpunkte
.PHONY: clean .PHONY: clean

View file

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