ladok3 4.10__py3-none-any.whl → 5.4__py3-none-any.whl
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.
- doc/ltxobj/ladok3.pdf +0 -0
- ladok3/Makefile +6 -0
- ladok3/__init__.py +1511 -3386
- ladok3/api.nw +1653 -225
- ladok3/cli.nw +118 -53
- ladok3/cli.py +323 -252
- ladok3/data.nw +92 -15
- ladok3/data.py +79 -3
- ladok3/ladok.bash +35 -17
- ladok3/ladok3.nw +288 -16
- ladok3/report.nw +183 -117
- ladok3/report.py +135 -63
- ladok3/scripts.nw +244 -0
- ladok3/student.nw +69 -4
- ladok3/student.py +98 -42
- ladok3/undoc.nw +62 -3119
- {ladok3-4.10.dist-info → ladok3-5.4.dist-info}/LICENSE +1 -1
- {ladok3-4.10.dist-info → ladok3-5.4.dist-info}/METADATA +39 -17
- ladok3-5.4.dist-info/RECORD +21 -0
- {ladok3-4.10.dist-info → ladok3-5.4.dist-info}/WHEEL +1 -1
- ladok3/.gitignore +0 -10
- ladok3-4.10.dist-info/RECORD +0 -21
- {ladok3-4.10.dist-info → ladok3-5.4.dist-info}/entry_points.txt +0 -0
ladok3/data.nw
CHANGED
|
@@ -16,7 +16,7 @@ from LADOK (\cref{DataCommand}).
|
|
|
16
16
|
This is a subcommand for the [[ladok]] command-line interface.
|
|
17
17
|
It can be used like this:
|
|
18
18
|
\begin{minted}{bash}
|
|
19
|
-
ladok
|
|
19
|
+
ladok course DD1315 > DD1315.csv
|
|
20
20
|
\end{minted}
|
|
21
21
|
This program will produce CSV-formated data to answer the questions above.
|
|
22
22
|
The data is formated like this:
|
|
@@ -26,11 +26,11 @@ The data is formated like this:
|
|
|
26
26
|
\item component,
|
|
27
27
|
\item student (pseudonym),
|
|
28
28
|
\item grade,
|
|
29
|
-
\item normalized
|
|
29
|
+
\item either absolute date or normalized to the course start and finish.
|
|
30
30
|
\end{itemize}
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
\section{The [[
|
|
33
|
+
\section{The [[course]] subcommand}\label{DataCommand}
|
|
34
34
|
|
|
35
35
|
This is a subcommand run as part of the [[ladok3.cli]] module.
|
|
36
36
|
We provide a function [[add_command_options]] that adds the subcommand options
|
|
@@ -47,10 +47,24 @@ import sys
|
|
|
47
47
|
<<functions>>
|
|
48
48
|
|
|
49
49
|
def add_command_options(parser):
|
|
50
|
+
"""Add the 'course' subcommand options to the argument parser.
|
|
51
|
+
|
|
52
|
+
Creates a subparser for the course data extraction command with all
|
|
53
|
+
necessary arguments for filtering and output formatting.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
parser (ArgumentParser): The parent parser to add the subcommand to.
|
|
57
|
+
"""
|
|
50
58
|
<<add data parser to parser>>
|
|
51
59
|
<<add data command arguments to data parser>>
|
|
52
60
|
|
|
53
61
|
def command(ladok, args):
|
|
62
|
+
"""Execute the course data extraction command.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
ladok (LadokSession): The LADOK session for data access.
|
|
66
|
+
args: Parsed command line arguments containing course and filter options.
|
|
67
|
+
"""
|
|
54
68
|
<<produce data about course specified in args>>
|
|
55
69
|
@
|
|
56
70
|
|
|
@@ -59,7 +73,7 @@ def command(ladok, args):
|
|
|
59
73
|
We add a subparser.
|
|
60
74
|
We set it up to use the function [[command]].
|
|
61
75
|
<<add data parser to parser>>=
|
|
62
|
-
data_parser = parser.add_parser("
|
|
76
|
+
data_parser = parser.add_parser("course",
|
|
63
77
|
help="Returns course results data in CSV form",
|
|
64
78
|
description="""
|
|
65
79
|
Returns the results in CSV form for all first-time registered students.
|
|
@@ -76,12 +90,13 @@ This way the user can deal with how to store the data.
|
|
|
76
90
|
<<produce data about course specified in args>>=
|
|
77
91
|
data_writer = csv.writer(sys.stdout, delimiter=args.delimiter)
|
|
78
92
|
course_rounds = filter_rounds(
|
|
79
|
-
|
|
80
|
-
|
|
93
|
+
ladok.search_course_rounds(code=args.course_code),
|
|
94
|
+
args.rounds)
|
|
81
95
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
if args.header:
|
|
97
|
+
data_writer.writerow([
|
|
98
|
+
"Course", "Round", "Component", "Student", "Grade", "Time"
|
|
99
|
+
])
|
|
85
100
|
for course_round in course_rounds:
|
|
86
101
|
data = extract_data_for_round(ladok, course_round, args)
|
|
87
102
|
|
|
@@ -91,6 +106,7 @@ for course_round in course_rounds:
|
|
|
91
106
|
student, grade, time]
|
|
92
107
|
)
|
|
93
108
|
@ We must take a course code and a delimiter as arguments.
|
|
109
|
+
We also want to know if we want a header or not.
|
|
94
110
|
<<add data command arguments to data parser>>=
|
|
95
111
|
data_parser.add_argument("course_code",
|
|
96
112
|
help="The course code of the course for which to export data")
|
|
@@ -100,6 +116,9 @@ data_parser.add_argument("-d", "--delimiter",
|
|
|
100
116
|
help="The delimiter for the CSV output; "
|
|
101
117
|
"default is a tab character to be compatible with POSIX commands, "
|
|
102
118
|
"use `-d,` or `-d ,` to get comma-separated values.")
|
|
119
|
+
|
|
120
|
+
data_parser.add_argument("-H", "--header", action="store_true",
|
|
121
|
+
help="Print a header line with the column names.")
|
|
103
122
|
@
|
|
104
123
|
|
|
105
124
|
We filter the rounds.
|
|
@@ -129,24 +148,40 @@ We also need the course round through the [[course_round]] object of type
|
|
|
129
148
|
[[CourseRound]].
|
|
130
149
|
<<functions>>=
|
|
131
150
|
def extract_data_for_round(ladok, course_round, args):
|
|
151
|
+
"""Extract student result data for a specific course round.
|
|
152
|
+
|
|
153
|
+
Processes a course round to extract student results data in CSV format,
|
|
154
|
+
filtering by students and components as specified in args.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
ladok (LadokSession): The LADOK session for data access.
|
|
158
|
+
course_round: The course round object to extract data from.
|
|
159
|
+
args: Command line arguments containing filter options.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
list: List of result data dictionaries for CSV output.
|
|
163
|
+
"""
|
|
132
164
|
<<compute start and length of the course>>
|
|
133
165
|
<<get the results for the course round>>
|
|
134
166
|
|
|
135
|
-
students = filter_students(course_round.participants(),
|
|
167
|
+
students = filter_students(course_round.participants(),
|
|
168
|
+
args.students)
|
|
136
169
|
|
|
137
170
|
for student in students:
|
|
138
|
-
student_results = filter_student_results(student,
|
|
171
|
+
student_results = filter_student_results(student,
|
|
172
|
+
results)
|
|
139
173
|
|
|
140
174
|
<<determine if student should be included>>
|
|
141
175
|
|
|
142
|
-
components = filter_components(course_round.components(),
|
|
176
|
+
components = filter_components(course_round.components(),
|
|
177
|
+
args.components)
|
|
143
178
|
|
|
144
179
|
for component in components:
|
|
145
180
|
if len(student_results) < 1:
|
|
146
181
|
result_data = None
|
|
147
182
|
else:
|
|
148
|
-
result_data = filter_component_result(
|
|
149
|
-
|
|
183
|
+
result_data = filter_component_result(component,
|
|
184
|
+
student_results[0]["ResultatPaUtbildningar"])
|
|
150
185
|
|
|
151
186
|
if not result_data:
|
|
152
187
|
grade = "-"
|
|
@@ -154,7 +189,29 @@ def extract_data_for_round(ladok, course_round, args):
|
|
|
154
189
|
else:
|
|
155
190
|
<<extract grade and normalized date from result data>>
|
|
156
191
|
|
|
157
|
-
yield student, component, grade
|
|
192
|
+
<<yield [[student, component, grade]] and date>>
|
|
193
|
+
@
|
|
194
|
+
|
|
195
|
+
We want to yield the data in CSV form, so we simply yield a tuple.
|
|
196
|
+
The date is either the normalized date or the date from the result data.
|
|
197
|
+
The student's identifier will be either the LADOK ID or the student name and
|
|
198
|
+
personnummer, depending on the command line arguments.
|
|
199
|
+
<<yield [[student, component, grade]] and date>>=
|
|
200
|
+
yield student.ladok_id if args.ladok_id \
|
|
201
|
+
else student, \
|
|
202
|
+
component, \
|
|
203
|
+
grade, \
|
|
204
|
+
normalized_date if args.normalize_date \
|
|
205
|
+
else result_data["Examinationsdatum"] if result_data \
|
|
206
|
+
else None
|
|
207
|
+
<<add data command arguments to data parser>>=
|
|
208
|
+
data_parser.add_argument("-l", "--ladok-id", action="store_true",
|
|
209
|
+
help="Use the LADOK ID for the student, "
|
|
210
|
+
"otherwise the student name and personnummer "
|
|
211
|
+
"will be used.")
|
|
212
|
+
data_parser.add_argument("-n", "--normalize-date", action="store_true",
|
|
213
|
+
help="Normalize the date to the start of the course, "
|
|
214
|
+
"otherwise the date is printed as is.")
|
|
158
215
|
@
|
|
159
216
|
|
|
160
217
|
\subsection{Get round data}
|
|
@@ -185,6 +242,15 @@ Then we must search for a student's result in the batch of results we received
|
|
|
185
242
|
from LADOK.
|
|
186
243
|
<<functions>>=
|
|
187
244
|
def filter_student_results(student, results):
|
|
245
|
+
"""Filter results for a specific student.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
student: Student object with ladok_id attribute.
|
|
249
|
+
results (list): List of result dictionaries from LADOK.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
list: Filtered list containing only results for the specified student.
|
|
253
|
+
"""
|
|
188
254
|
return list(filter(
|
|
189
255
|
lambda x: x["Student"]["Uid"] == student.ladok_id,
|
|
190
256
|
results))
|
|
@@ -193,6 +259,17 @@ def filter_student_results(student, results):
|
|
|
193
259
|
Similarly, we want to find the result for a particular component.
|
|
194
260
|
<<functions>>=
|
|
195
261
|
def filter_component_result(component, results):
|
|
262
|
+
"""Find the result data for a specific course component.
|
|
263
|
+
|
|
264
|
+
Searches through results to find the entry matching the given component.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
component: Course component object to search for.
|
|
268
|
+
results (list): List of result dictionaries to search through.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
dict or None: Result data for the component, or None if not found.
|
|
272
|
+
"""
|
|
196
273
|
for component_result in results:
|
|
197
274
|
<<get the component result data>>
|
|
198
275
|
<<check component code in result data>>
|
ladok3/data.py
CHANGED
|
@@ -13,6 +13,19 @@ def filter_rounds(all_rounds, desired_rounds):
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def extract_data_for_round(ladok, course_round, args):
|
|
16
|
+
"""Extract student result data for a specific course round.
|
|
17
|
+
|
|
18
|
+
Processes a course round to extract student results data in CSV format,
|
|
19
|
+
filtering by students and components as specified in args.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
ladok (LadokSession): The LADOK session for data access.
|
|
23
|
+
course_round: The course round object to extract data from.
|
|
24
|
+
args: Command line arguments containing filter options.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
list: List of result data dictionaries for CSV output.
|
|
28
|
+
"""
|
|
16
29
|
course_start = course_round.start
|
|
17
30
|
course_length = course_round.end - course_start
|
|
18
31
|
component = course_round.components()[0]
|
|
@@ -60,14 +73,38 @@ def extract_data_for_round(ladok, course_round, args):
|
|
|
60
73
|
grade = "-"
|
|
61
74
|
normalized_date = None
|
|
62
75
|
|
|
63
|
-
yield student, component, grade,
|
|
76
|
+
yield student.ladok_id if args.ladok_id else student, component, grade, (
|
|
77
|
+
normalized_date
|
|
78
|
+
if args.normalize_date
|
|
79
|
+
else result_data["Examinationsdatum"] if result_data else None
|
|
80
|
+
)
|
|
64
81
|
|
|
65
82
|
|
|
66
83
|
def filter_student_results(student, results):
|
|
84
|
+
"""Filter results for a specific student.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
student: Student object with ladok_id attribute.
|
|
88
|
+
results (list): List of result dictionaries from LADOK.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
list: Filtered list containing only results for the specified student.
|
|
92
|
+
"""
|
|
67
93
|
return list(filter(lambda x: x["Student"]["Uid"] == student.ladok_id, results))
|
|
68
94
|
|
|
69
95
|
|
|
70
96
|
def filter_component_result(component, results):
|
|
97
|
+
"""Find the result data for a specific course component.
|
|
98
|
+
|
|
99
|
+
Searches through results to find the entry matching the given component.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
component: Course component object to search for.
|
|
103
|
+
results (list): List of result dictionaries to search through.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
dict or None: Result data for the component, or None if not found.
|
|
107
|
+
"""
|
|
71
108
|
for component_result in results:
|
|
72
109
|
if "Arbetsunderlag" in component_result:
|
|
73
110
|
result_data = component_result["Arbetsunderlag"]
|
|
@@ -131,8 +168,16 @@ def has_credit_transfer(results):
|
|
|
131
168
|
|
|
132
169
|
|
|
133
170
|
def add_command_options(parser):
|
|
171
|
+
"""Add the 'course' subcommand options to the argument parser.
|
|
172
|
+
|
|
173
|
+
Creates a subparser for the course data extraction command with all
|
|
174
|
+
necessary arguments for filtering and output formatting.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
parser (ArgumentParser): The parent parser to add the subcommand to.
|
|
178
|
+
"""
|
|
134
179
|
data_parser = parser.add_parser(
|
|
135
|
-
"
|
|
180
|
+
"course",
|
|
136
181
|
help="Returns course results data in CSV form",
|
|
137
182
|
description="""
|
|
138
183
|
Returns the results in CSV form for all first-time registered students.
|
|
@@ -151,6 +196,13 @@ def add_command_options(parser):
|
|
|
151
196
|
"default is a tab character to be compatible with POSIX commands, "
|
|
152
197
|
"use `-d,` or `-d ,` to get comma-separated values.",
|
|
153
198
|
)
|
|
199
|
+
|
|
200
|
+
data_parser.add_argument(
|
|
201
|
+
"-H",
|
|
202
|
+
"--header",
|
|
203
|
+
action="store_true",
|
|
204
|
+
help="Print a header line with the column names.",
|
|
205
|
+
)
|
|
154
206
|
data_parser.add_argument(
|
|
155
207
|
"-r",
|
|
156
208
|
"--rounds",
|
|
@@ -158,6 +210,21 @@ def add_command_options(parser):
|
|
|
158
210
|
help="The round codes for the rounds to include, "
|
|
159
211
|
"otherwise all rounds will be included.",
|
|
160
212
|
)
|
|
213
|
+
data_parser.add_argument(
|
|
214
|
+
"-l",
|
|
215
|
+
"--ladok-id",
|
|
216
|
+
action="store_true",
|
|
217
|
+
help="Use the LADOK ID for the student, "
|
|
218
|
+
"otherwise the student name and personnummer "
|
|
219
|
+
"will be used.",
|
|
220
|
+
)
|
|
221
|
+
data_parser.add_argument(
|
|
222
|
+
"-n",
|
|
223
|
+
"--normalize-date",
|
|
224
|
+
action="store_true",
|
|
225
|
+
help="Normalize the date to the start of the course, "
|
|
226
|
+
"otherwise the date is printed as is.",
|
|
227
|
+
)
|
|
161
228
|
data_parser.add_argument(
|
|
162
229
|
"-t",
|
|
163
230
|
"--time-limit",
|
|
@@ -183,12 +250,21 @@ def add_command_options(parser):
|
|
|
183
250
|
|
|
184
251
|
|
|
185
252
|
def command(ladok, args):
|
|
253
|
+
"""Execute the course data extraction command.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
ladok (LadokSession): The LADOK session for data access.
|
|
257
|
+
args: Parsed command line arguments containing course and filter options.
|
|
258
|
+
"""
|
|
186
259
|
data_writer = csv.writer(sys.stdout, delimiter=args.delimiter)
|
|
187
260
|
course_rounds = filter_rounds(
|
|
188
261
|
ladok.search_course_rounds(code=args.course_code), args.rounds
|
|
189
262
|
)
|
|
190
263
|
|
|
191
|
-
|
|
264
|
+
if args.header:
|
|
265
|
+
data_writer.writerow(
|
|
266
|
+
["Course", "Round", "Component", "Student", "Grade", "Time"]
|
|
267
|
+
)
|
|
192
268
|
for course_round in course_rounds:
|
|
193
269
|
data = extract_data_for_round(ladok, course_round, args)
|
|
194
270
|
|
ladok3/ladok.bash
CHANGED
|
@@ -25,22 +25,40 @@ __python_argcomplete_run_inner() {
|
|
|
25
25
|
|
|
26
26
|
_python_argcomplete() {
|
|
27
27
|
local IFS=$'\013'
|
|
28
|
-
local
|
|
29
|
-
if
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
28
|
+
local script=""
|
|
29
|
+
if [[ -n "${ZSH_VERSION-}" ]]; then
|
|
30
|
+
local completions
|
|
31
|
+
completions=($(IFS="$IFS" \
|
|
32
|
+
COMP_LINE="$BUFFER" \
|
|
33
|
+
COMP_POINT="$CURSOR" \
|
|
34
|
+
_ARGCOMPLETE=1 \
|
|
35
|
+
_ARGCOMPLETE_SHELL="zsh" \
|
|
36
|
+
_ARGCOMPLETE_SUPPRESS_SPACE=1 \
|
|
37
|
+
__python_argcomplete_run ${script:-${words[1]}}))
|
|
38
|
+
_describe "${words[1]}" completions -o nosort
|
|
39
|
+
else
|
|
40
|
+
local SUPPRESS_SPACE=0
|
|
41
|
+
if compopt +o nospace 2> /dev/null; then
|
|
42
|
+
SUPPRESS_SPACE=1
|
|
43
|
+
fi
|
|
44
|
+
COMPREPLY=($(IFS="$IFS" \
|
|
45
|
+
COMP_LINE="$COMP_LINE" \
|
|
46
|
+
COMP_POINT="$COMP_POINT" \
|
|
47
|
+
COMP_TYPE="$COMP_TYPE" \
|
|
48
|
+
_ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \
|
|
49
|
+
_ARGCOMPLETE=1 \
|
|
50
|
+
_ARGCOMPLETE_SHELL="bash" \
|
|
51
|
+
_ARGCOMPLETE_SUPPRESS_SPACE=$SUPPRESS_SPACE \
|
|
52
|
+
__python_argcomplete_run ${script:-$1}))
|
|
53
|
+
if [[ $? != 0 ]]; then
|
|
54
|
+
unset COMPREPLY
|
|
55
|
+
elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
|
|
56
|
+
compopt -o nospace
|
|
57
|
+
fi
|
|
44
58
|
fi
|
|
45
59
|
}
|
|
46
|
-
|
|
60
|
+
if [[ -z "${ZSH_VERSION-}" ]]; then
|
|
61
|
+
complete -o nospace -o default -o bashdefault -F _python_argcomplete ladok
|
|
62
|
+
else
|
|
63
|
+
compdef _python_argcomplete ladok
|
|
64
|
+
fi
|