diff --git a/core/fixtures/testdata-core.json b/core/fixtures/testdata-core.json
index ff4385a6ca4c846b14debc333abfeef9452b277d..fa913e9d82a430eb60e4042eb63a026da7ed8115 100644
--- a/core/fixtures/testdata-core.json
+++ b/core/fixtures/testdata-core.json
@@ -1 +1,92 @@
-[{"model": "core.submissiontype", "pk": 1, "fields": {"name": "Aufgabe 01", "slug": "brezmaphgocfuikw", "full_score": 10, "task_description": "description", "possible_solution": "solution", "correction_guideline": "guideline"}}, {"model": "core.submissiontype", "pk": 2, "fields": {"name": "Aufgabe 02", "slug": "zbjfwldsuhqgxvmn", "full_score": 20, "task_description": "description", "possible_solution": "solution", "correction_guideline": "guideline"}}, {"model": "core.student", "pk": 1, "fields": {"matrikel_no": "12345678", "has_logged_in": false, "name": "Student 01 Vorname und Nachname", "user": 4}}, {"model": "core.student", "pk": 2, "fields": {"matrikel_no": "87654321", "has_logged_in": false, "name": "Student 02 Vorname und Nachname", "user": 5}}, {"model": "core.submission", "pk": 1, "fields": {"slug": "qgleatcwzfxsdnjr", "seen": false, "type": 1, "text": "function generate(timeout){\r\n\r\n\t$('#menu_button_img').attr('src', 'style/menu_blink.gif'); \r\n\r\n\tif(timeout == 0)\t\t\t\t\t\t\t\t\r\n\t\t$('#config_form').attr('action', $('#config_form').attr('action') + '#title'); \t\t\t\t// show directly the question\r\n\telse\r\n\t\ttimeout = 0;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// disable timeout\r\n\t\r\n\tsetTimeout(function(){ $('#config_form').submit(); }, timeout);\r\n\r\n}", "pre_corrections": "COMPILER", "student": 1}}, {"model": "core.submission", "pk": 2, "fields": {"slug": "mrthqgsloaydjfnc", "seen": false, "type": 2, "text": "function showTextEditor(){\r\n\r\n\t$('.ilc_question_Standard').hide('slow');\r\n\t$('.ilc_question_ml_Standard').hide('slow');\r\n\t$('.text_editor').show('slow');\r\n\t\r\n}\r\n\r\nfunction showConfig(){\r\n\r\n\t$('#config_wrapper').animate(\r\n\t\t{\r\n\t\t\tright: ($('#config_wrapper').css('right') == '0px' ? '-322px' : '0px')\r\n\t\t}, \r\n\t500);\r\n\r\n}", "pre_corrections": "LINKER ERROR", "student": 1}}, {"model": "core.submission", "pk": 3, "fields": {"slug": "hunkgevtcfdobyxw", "seen": false, "type": 2, "text": "$(document).keydown(function(evt){\r\n\r\n\tif(evt.which == 9){\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// #9 = TAB\r\n\t\tgenerate(0);\r\n\t\tevt.preventDefault();\r\n\t}\r\n\t\r\n});", "pre_corrections": "ALL GOOD", "student": 2}}, {"model": "core.submission", "pk": 4, "fields": {"slug": "gurvbyzxjfmhdiep", "seen": false, "type": 1, "text": "function showTextEditor(){\r\n\r\n\t$('.ilc_question_Standard').hide('slow');\r\n\t$('.ilc_question_ml_Standard').hide('slow');\r\n\t$('.text_editor').show('slow');\r\n\t\r\n}\r\n\r\nfunction showConfig(){\r\n\r\n\t$('#config_wrapper').animate(\r\n\t\t{\r\n\t\t\tright: ($('#config_wrapper').css('right') == '0px' ? '-322px' : '0px')\r\n\t\t}, \r\n\t500);\r\n\r\n}", "pre_corrections": "QUACK", "student": 2}}]
\ No newline at end of file
+[
+    {
+        "fields": {
+            "full_score": 10,
+            "name": "Aufgabe 01",
+            "possible_solution": "solution",
+            "slug": "brezmaphgocfuikw",
+            "task_description": "description"
+        },
+        "model": "core.submissiontype",
+        "pk": 1
+    },
+    {
+        "fields": {
+            "full_score": 20,
+            "name": "Aufgabe 02",
+            "possible_solution": "solution",
+            "slug": "zbjfwldsuhqgxvmn",
+            "task_description": "description"
+        },
+        "model": "core.submissiontype",
+        "pk": 2
+    },
+    {
+        "fields": {
+            "has_logged_in": false,
+            "matrikel_no": "12345678",
+            "name": "Student 01 Vorname und Nachname",
+            "user": 4
+        },
+        "model": "core.student",
+        "pk": 1
+    },
+    {
+        "fields": {
+            "has_logged_in": false,
+            "matrikel_no": "87654321",
+            "name": "Student 02 Vorname und Nachname",
+            "user": 5
+        },
+        "model": "core.student",
+        "pk": 2
+    },
+    {
+        "fields": {
+            "pre_corrections": "COMPILER",
+            "seen_by_student": false,
+            "slug": "qgleatcwzfxsdnjr",
+            "student": 1,
+            "text": "function generate(timeout){\r\n\r\n\t$('#menu_button_img').attr('src', 'style/menu_blink.gif'); \r\n\r\n\tif(timeout == 0)\t\t\t\t\t\t\t\t\r\n\t\t$('#config_form').attr('action', $('#config_form').attr('action') + '#title'); \t\t\t\t// show directly the question\r\n\telse\r\n\t\ttimeout = 0;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// disable timeout\r\n\t\r\n\tsetTimeout(function(){ $('#config_form').submit(); }, timeout);\r\n\r\n}",
+            "type": 1
+        },
+        "model": "core.submission",
+        "pk": 1
+    },
+    {
+        "fields": {
+            "pre_corrections": "LINKER ERROR",
+            "seen_by_student": false,
+            "slug": "mrthqgsloaydjfnc",
+            "student": 1,
+            "text": "function showTextEditor(){\r\n\r\n\t$('.ilc_question_Standard').hide('slow');\r\n\t$('.ilc_question_ml_Standard').hide('slow');\r\n\t$('.text_editor').show('slow');\r\n\t\r\n}\r\n\r\nfunction showConfig(){\r\n\r\n\t$('#config_wrapper').animate(\r\n\t\t{\r\n\t\t\tright: ($('#config_wrapper').css('right') == '0px' ? '-322px' : '0px')\r\n\t\t}, \r\n\t500);\r\n\r\n}",
+            "type": 2
+        },
+        "model": "core.submission",
+        "pk": 2
+    },
+    {
+        "fields": {
+            "pre_corrections": "ALL GOOD",
+            "seen_by_student": false,
+            "slug": "hunkgevtcfdobyxw",
+            "student": 2,
+            "text": "$(document).keydown(function(evt){\r\n\r\n\tif(evt.which == 9){\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// #9 = TAB\r\n\t\tgenerate(0);\r\n\t\tevt.preventDefault();\r\n\t}\r\n\t\r\n});",
+            "type": 2
+        },
+        "model": "core.submission",
+        "pk": 3
+    },
+    {
+        "fields": {
+            "pre_corrections": "QUACK",
+            "seen_by_student": false,
+            "slug": "gurvbyzxjfmhdiep",
+            "student": 2,
+            "text": "function showTextEditor(){\r\n\r\n\t$('.ilc_question_Standard').hide('slow');\r\n\t$('.ilc_question_ml_Standard').hide('slow');\r\n\t$('.text_editor').show('slow');\r\n\t\r\n}\r\n\r\nfunction showConfig(){\r\n\r\n\t$('#config_wrapper').animate(\r\n\t\t{\r\n\t\t\tright: ($('#config_wrapper').css('right') == '0px' ? '-322px' : '0px')\r\n\t\t}, \r\n\t500);\r\n\r\n}",
+            "type": 1
+        },
+        "model": "core.submission",
+        "pk": 4
+    }
+]
diff --git a/grrr.py b/grrr.py
index b73afb33cd6a6f84268f485851101591f983a943..4294b4fe1a6820e353225ffb831127a7b67eb127 100644
--- a/grrr.py
+++ b/grrr.py
@@ -9,28 +9,34 @@ import json
 import django
 django.setup()
 
-from django.contrib.auth.models import Group, User
-
-from core.models import Student
-
-
-
+from django.contrib.auth.models import User
 
+from core.models import Student, Submission
 
 
 def parseme():
     parser      = argparse.ArgumentParser()
     subparsers  = parser.add_subparsers(dest="command")
 
-    newstudentpasswordlist = subparsers.add_parser(
-        'newstudentpasswordlist',
+    parser.add_argument(
+        '-o', '--output',
+        help='Where the output goes (not info messages)',
+        default=sys.stdout,
+        type=argparse.FileType(mode='r'),
+    )
+
+    ### parser for printing out new passwordlists ###
+    passwordlist = subparsers.add_parser(
+        'passwordlist',
         help='all student passwords will be changed and a list of these password will be printed'
     )
-    newstudentpasswordlist.add_argument(
+    passwordlist.add_argument(
         'instance',
+        default='',
         help='name of the instance that generated the passwords'
     )
 
+    ### parser for replacing usernames ###
     replaceusernames = subparsers.add_parser(
         'replaceusernames',
         help='replaces all usernames based on a matrikel_no -> new_name dict (input should be JSON)'
@@ -38,9 +44,11 @@ def parseme():
     replaceusernames.add_argument(
         'matno2username_dict',
         help='the mapping as a JSON file',
+        default=sys.stdin,
         type=argparse.FileType('r')
     )
 
+    ### parser for enabling or disabling users ###
     enableusers = subparsers.add_parser(
         'enableusers',
         help='All user accounts will be disabled'
@@ -64,19 +72,16 @@ def parseme():
         nargs='+',
         default=())
 
-    parser.add_argument(
-        '-o', '--output',
-        help='Where the output goes (not info messages)',
-        default=sys.stdout,
-        type=argparse.FileType(mode='r'),
-    )
+    ### parser for extracting submissions ###
+    subparsers.add_parser('extractsubmissions')
 
     return parser.parse_args()
 
 
-def handle_newstudentpasswordlist(output=sys.stdout, instance=""):
+def handle_passwordlist(output=sys.stdout, instance="", **kwargs):
     with open('/usr/share/dict/words') as words:
-        choose_from = list({word.strip().lower() for word in words if 5 < len(word) < 8})
+        choose_from = list({word.strip().lower()
+                            for word in words if 5 < len(word) < 8})
 
     writer = csv.writer(output)
     writer.writerow(['Name', 'Matrikel', 'Username', 'password', 'instance'])
@@ -90,7 +95,8 @@ def handle_newstudentpasswordlist(output=sys.stdout, instance=""):
         writer.writerow([student.name, student.matrikel_no,
                          student.user.username, password, instance])
 
-def handle_enableusers(switch, exclude, include):
+
+def handle_enableusers(switch, exclude, include, **kwargs):
 
     if include:
         for user in User.objects.filter(username__in=include):
@@ -102,7 +108,8 @@ def handle_enableusers(switch, exclude, include):
             user.save()
 
 
-def handle_replace_usernames(matno2username):
+def handle_replaceusernames(matno2username_dict, **kwargs):
+    matno2username = json.JSONDecoder().decode(matno2username_dict.read())
     for student in Student.objects.all():
         if student.matrikel_no in matno2username:
             new_name = matno2username[student.matrikel_no]
@@ -110,15 +117,15 @@ def handle_replace_usernames(matno2username):
             student.user.save()
 
 
+def handle_extractsubmissions(output, **kwargs):
+    for submission in Submission.objects.filter(feedback__isnull=False).order_by('type'):
+        print(submission.feedback.score, repr(submission.text), file=open(str(submission.type).replace(' ', '_'), 'a'))
+
+
 def main():
     args = parseme()
-
-    if args.command == 'newstudentpasswordlist':
-        handle_newstudentpasswordlist(args.output, args.instance)
-    if args.command == 'enableusers':
-        handle_enableusers(args.switch, args.exclude, args.include)
-    if args.command == 'replaceusernames':
-        handle_replace_usernames(json.JSONDecoder().decode(args.matno2username_dict.read()))
+    if args.command:
+        globals()['handle_' + args.command](**vars(args))
 
 if __name__ == '__main__':
     main()
diff --git a/processing.py b/processing.py
deleted file mode 100644
index cc775307ae10dd49b1a100f363d4e29573af70ab..0000000000000000000000000000000000000000
--- a/processing.py
+++ /dev/null
@@ -1,155 +0,0 @@
-import abc
-import hashlib
-import json
-import tempfile
-import os
-
-TEMP_DIR = tempfile.mkdtemp()
-os.chdir(TEMP_DIR)
-
-before = {
-    "username": {
-        "name": "87654321",
-        "email": "username@example.org",
-        "matrikel_no": "87654321",
-        "submissions": [
-            {
-                "type": "[a01] Zeichen und Strings",
-                "code": "#include <ctype.h>\nint umschalt(char *s)\n\n{\n\tint i = 0, count = 0;\n\twhile(s[i] != '\\0')\n\t{\n\t\tif(islower(s[i]) != 0)\n\t\t{\n\t\t\ts[i] = toupper(s[i]);\n\t\t} else if(isupper(s[i]) != 0)\n\t\t{\n\t\t\ts[i] = tolower(s[i]);\n\t\t} else\n\t\t{\n\t\t\tcount++;\n\t\t}\n\t\ti++;\n\t}\n\treturn count;\n}",
-                "tests" : {}
-            },
-            {
-                "type": "[a02] Iteration",
-                "code": "#include <stdlib.h>\nlong iteP(unsigned int k)\n{\n\tif(k == 0)\n\t{\n\t\treturn 3;\n\t}\n\tif( k == 1)\n\t{\n\t\treturn 0;\n\t}\n\tif( k == 3)\n\t{\n\t\treturn 2;\n\t}\n\tint *p = malloc(k * sizeof(int));\n\tp[0] = 3;\n\tp[1] = 0;\n\tp[2] = 2;\n\tfor(int i = 3; i < k; i++)\n\t{\n\t\tp[i] = p[i-2] + p[i - 3];\n\t}\n\n\treturn p[k];\n}",
-                "tests" : {}
-            }
-        ]
-    }
-}
-
-after = {
-  "username": {
-    "name": "87654321",
-    "email": "username@example.org",
-    "matrikel_no": "87654321",
-    "submissions": [
-      {
-        "type": "[a01] Zeichen und Strings",
-        "code": "#include <ctype.h>\nint umschalt(char *s)\n\n{\n\tint i = 0, count = 0;\n\twhile(s[i] != '\\0')\n\t{\n\t\tif(islower(s[i]) != 0)\n\t\t{\n\t\t\ts[i] = toupper(s[i]);\n\t\t} else if(isupper(s[i]) != 0)\n\t\t{\n\t\t\ts[i] = tolower(s[i]);\n\t\t} else\n\t\t{\n\t\t\tcount++;\n\t\t}\n\t\ti++;\n\t}\n\treturn count;\n}",
-        "tests": {
-          "EmptyTest": {
-            "name": "EmptyTest",
-            "annotation": "",
-            "label": "NOT_EMPTY"
-          }
-        }
-      },
-      {
-        "type": "[a02] Iteration",
-        "code": "#include <stdlib.h>\nlong iteP(unsigned int k)\n{\n\tif(k == 0)\n\t{\n\t\treturn 3;\n\t}\n\tif( k == 1)\n\t{\n\t\treturn 0;\n\t}\n\tif( k == 3)\n\t{\n\t\treturn 2;\n\t}\n\tint *p = malloc(k * sizeof(int));\n\tp[0] = 3;\n\tp[1] = 0;\n\tp[2] = 2;\n\tfor(int i = 3; i < k; i++)\n\t{\n\t\tp[i] = p[i-2] + p[i - 3];\n\t}\n\n\treturn p[k];\n}",
-        "tests": {}
-      }
-    ]
-  }
-}
-
-
-def all_subclasses(cls):
-    return cls.__subclasses__() \
-        + [g for s in cls.__subclasses__() for g in all_subclasses(s)]
-
-def sha1_prefix(submission_obj):
-    return hashlib.sha1(
-        submission_obj['code'].encode()
-    ).hexdigest()[:12]
-
-class Test(metaclass=abc.ABCMeta):
-    """docstring for IliasQuestion"""
-
-    @classmethod
-    def available_tests(cls):
-        return {sub.__name__ : sub for sub in all_subclasses(cls)}
-
-    def __new__(cls, *args, **kwargs):
-        assert hasattr(cls, 'depends'),         "depends not defined"
-        assert hasattr(cls, 'label_success'),   "label_success not defined"
-        assert hasattr(cls, 'label_failure'),   "label_failure not defined"
-        return super().__new__(cls)
-
-    def __init__(self, submission_obj):
-
-        if not self.dependencies_satisfied(submission_obj):
-            self.result     = self.label_failure
-            self.annotation = "TEST DEPENDENCY NOT MET"
-            return
-
-        elif str(self) in submission_obj['tests']:
-            self.deserialize(submission_obj['tests'][str(self)])
-
-        else:
-            self.result, self.annotation = self.run_test(submission_obj)
-            self.serialize(submission_obj)
-
-    def __bool__(self):
-        return self.result
-
-    def __str__(self):
-        return self.__class__.__name__
-
-    def dependencies_satisfied(self, submission_obj):
-        return all(dep(submission_obj).result for dep in self.depends)
-
-    def deserialize(self, test):
-        self.result     = test['label'] == self.label_success
-        self.annotation = test['annotation']
-
-    def serialize(self, submission_obj):
-        as_dict = {
-            'name'       : str(self),
-            'annotation' : self.annotation
-        }
-
-        if self.result:
-            as_dict['label'] = self.label_success
-        else:
-            as_dict['label'] = self.label_failure
-
-        submission_obj['tests'][str(self)] = as_dict
-
-    @abc.abstractmethod
-    def run_test(self, submission_obj) -> (bool, str):
-        return NotImplemented
-
-
-class EmptyTest(Test):
-    """docstring for EmptyTest"""
-
-    depends         = ()
-    label_success   = 'NOT_EMPTY'
-    label_failure   = 'EMPTY'
-
-    def run_test(self, submission_obj):
-        return bool(submission_obj['code'].strip()), ""
-
-
-class CompileTest(Test):
-
-    depends         = (EmptyTest, )
-    label_success   = 'COMPILATION_SUCCESSFUL'
-    label_failure   = 'COMPILATION_FAILED'
-
-    def run_test(self, submission_obj):
-        prefix = sha1_prefix(submission_obj)
-        os.mkdir(prefix)
-
-        return False, "naa"
-
-
-
-
-
-d = CompileTest(before["username"]['submissions'][0])
-
-
-with open("/dev/null", "w") as out:
-    out.write(json.JSONEncoder().encode(after))
diff --git a/scripts/README.rst b/scripts/README.rst
index f07fc7c98533c47e545e02655b1c317d173e884e..46af791e4de48682ed54ba493832131d7dae2f81 100644
--- a/scripts/README.rst
+++ b/scripts/README.rst
@@ -1,7 +1,7 @@
 What is this directory about?
 =============================
 
-Well, it just servers as a collection of files that currently live in folders
+Well, it just serves as a collection of files that currently live in folders
 not part of the git repository, since they contain volatile or test data. I
 include them here for the sake of completeness, but they will be removed in
 later versions, since their work has to be encapsulated in the overall process.
diff --git a/scripts/convert.py b/scripts/convert.py
new file mode 100755
index 0000000000000000000000000000000000000000..62b10f89a6858dfa7658e99c238a35f3b8c22f90
--- /dev/null
+++ b/scripts/convert.py
@@ -0,0 +1,141 @@
+#!/usr/local/bin/python3
+""" a simple script that converts ilias exam output to readable json
+
+The json output will look like this:
+{
+    "max.mustermann": { <<--- OR all uppercase letter of the name + username/matrikel_no
+        "matrikel_no": "12345678",
+        "name": "Mustermann, Max",
+        "task_list": {
+            "[task_id_1]": "print Hello World!",
+            ....,
+            "[task_id_n]": "#include <stdio.h> etc."
+        }
+    },
+    ... ans so on
+}
+
+usage: convert.py [-h] [-n NUMBER_OF_TASKS] INFILE OUTFILE
+
+positional arguments:
+  INFILE                Ilias exam data
+  OUTFILE               Where to write the final file
+
+optional arguments:
+  -h, --help            show this help message and exit
+  -n NUMBER_OF_TASKS, --NUMBER_OF_TASKS NUMBER_OF_TASKS
+                        Where to write the final file
+
+
+Author: Jan Maximilian Michal
+Date: 30 March 2017
+"""
+
+import json
+import os
+import re
+import argparse
+import urllib.parse
+from collections import namedtuple, defaultdict
+
+from xlrd import open_workbook
+
+parser = argparse.ArgumentParser()
+parser.add_argument('INFILE', help='Ilias exam data')
+parser.add_argument('OUTFILE', help='Where to write the final file')
+parser.add_argument('-u', '--usernames', help='a json dict matno -> email')
+parser.add_argument(
+    '-n', '--NUMBER_OF_TASKS',
+    default=0, # don't check
+    metavar='NUMBER_OF_TASKS',
+    type=int,
+    help='Where to write the final file')
+args = parser.parse_args()
+
+# meta sheet contains ilias evaluation names usernames etc - data contains code
+meta, *data = open_workbook(args.INFILE, open(os.devnull, 'w')).sheets()
+
+# one user has one submission (code) per task
+# yes, I know it is possible to name match groups via (?P<name>) but
+# I like this solution better since it gets the job done nicely
+user_head = namedtuple('user_head', 'kohorte, name')
+user_head_re = re.compile(r'^Ergebnisse von Testdurchlauf (?P<kohorte>\d+) für (?P<name>[\w\s\.,-]+)$')
+
+# one task has a title and id and hpfly code
+task_head_re = re.compile(r'^Quellcode Frage(?P<title>.*) \d{8}$')
+
+# nor parsing the weird mat no
+matno_re = re.compile(r'^(?P<matrikel_no>\d{8})-(\d{3})-(\d{3})$')
+
+# Modify these iterators in order to change extraction behaviour
+
+
+def sheet_iter_meta(sheet):
+    """ yield first and second col entry as tuple of (name, matnr) """
+    for row in (sheet.row(i) for i in range(1, sheet.nrows)):
+        m = re.search(matno_re, row[1].value)
+        yield row[0].value, m.group('matrikel_no') if m else row[1].value
+
+
+def sheet_iter_data(sheet):
+    """ yields all rows that are not of empty type as one string """
+    for row in (sheet.row(i) for i in range(sheet.nrows)):
+        if any(map(lambda c: c.ctype, row)):
+            yield ''.join(c.value for c in row)
+
+# nice!
+name2mat = dict(sheet_iter_meta(meta))
+
+# from xls to lists and namedtuples
+# [ [user0, task0_h, code0, ..., taskn, coden ], ..., [...] ]
+root = []
+for sheet in data:
+    for row in sheet_iter_data(sheet):
+        user = re.search(user_head_re, row)
+        task = re.search(task_head_re, row)
+        if user:
+            root.append([user_head(*user.groups())])
+        elif task:
+            root[-1].append(task.group('title'))
+        else: # should be code
+            root[-1].append(urllib.parse.unquote(row).strip())
+
+if args.NUMBER_OF_TASKS:
+    for (user, *task_list) in sorted(root, key=lambda u: u[0].name):
+        assert len(task_list) == args.NUMBER_OF_TASKS * 2
+
+mat_to_email = defaultdict(str)
+if args.usernames:
+    with open(args.usernames) as data:
+        mat_to_email.update(json.JSONDecoder().decode(data.read()))
+
+def get_username(user):
+    if name2mat[user.name] in mat_to_email:
+        return mat_to_email[name2mat[user.name]].split('@')[0]
+    return ''.join(filter(str.isupper, user.name)) + name2mat[user.name]
+
+usernames = {user.name : get_username(user) for (user, *_) in root}
+
+# form list to json_like via comprehension
+# the format {userinitials + matrikel_no : {name:, matrikel_no:, tasklist: {id:, ..., id:}}}
+json_dict = {
+    usernames[user.name] : {
+        'name' : name2mat[user.name],
+        'email' : mat_to_email[name2mat[user.name]],
+        'matrikel_no' : name2mat[user.name],
+        'submissions' : [
+            {
+                "type" : task,
+                "code" : code,
+                "label" : [],
+                "annotations": {}
+            } for task, code in zip(task_list[::2], task_list[1::2])
+        ]
+    } for (user, *task_list) in sorted(root, key=lambda u: u[0].name)
+}
+
+# just encode python style
+with open(args.OUTFILE, "w") as out:
+    out.write(json.JSONEncoder().encode(json_dict))
+
+print(f"Wrote data to {args.OUTFILE}. Done.")
diff --git a/scripts/matrikel_to_email.py b/scripts/matrikel_to_email.py
deleted file mode 100644
index 920a278fbbf3c4adae90750c8936aedb414d1831..0000000000000000000000000000000000000000
--- a/scripts/matrikel_to_email.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from itertools import chain
-import re, json
-
-OUTFILE = 'matno2email.json'
-
-with \
-        open('binf1801-flexnow-20170329.csv') as inf, \
-        open('bphy1601-flexnow-20170328.csv') as phy, \
-        open(OUTFILE, "w") as out:
-    out.write(json.JSONEncoder().encode({matno : email for (matno, email) in (re.split(r'[\t;]', line.strip()) for line in chain(inf, phy) if line)})) # i just love one liners
diff --git a/util/__init__.py b/util/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/populatedb.py b/util/populatedb.py
similarity index 100%
rename from populatedb.py
rename to util/populatedb.py
diff --git a/util/processing.py b/util/processing.py
new file mode 100644
index 0000000000000000000000000000000000000000..997cf1b86f70c1b55fe3b7303234512fee8d345b
--- /dev/null
+++ b/util/processing.py
@@ -0,0 +1,224 @@
+import abc
+import hashlib
+import json
+import tempfile
+import os
+import shutil
+import subprocess
+
+before = {
+    "username": {
+        "name": "87654321",
+        "email": "username@example.org",
+        "matrikel_no": "87654321",
+        "submissions": [
+            {
+                "type": "[a01] Zeichen und Strings",
+                "code": "#include <ctype.h>\nint umschalt(char *s)\n\n{\n\t if (s == 0) return 0;int i = 0, count = 0;\n\twhile(s[i] != '\\0')\n\t{\n\t\tif(islower(s[i]) != 0)\n\t\t{\n\t\t\ts[i] = toupper(s[i]);\n\t\t} else if(isupper(s[i]) != 0)\n\t\t{\n\t\t\ts[i] = tolower(s[i]);\n\t\t} else\n\t\t{\n\t\t\tcount++;\n\t\t}\n\t\ti++;\n\t}\n\treturn count;\n}",
+                "tests" : {}
+            },
+            {
+                "type": "[a02] Iteration",
+                "code": "#include <stdlib.h>\nlong iteP(unsigned int k)\n{\n\tif(k == 0)\n\t{\n\t\treturn 3;\n\t}\n\tif( k == 1)\n\t{\n\t\treturn 0;\n\t}\n\tif( k == 3)\n\t{\n\t\treturn 2;\n\t}\n\tint *p = malloc(k * sizeof(int));\n\tp[0] = 3;\n\tp[1] = 0;\n\tp[2] = 2;\n\tfor(int i = 3; i < k; i++)\n\t{\n\t\tp[i] = p[i-2] + p[i - 3];\n\t}\n\n\treturn p[k];\n}",
+                "tests" : {}
+            }
+        ]
+    }
+}
+
+after = {
+    "username": {
+        "name": "87654321",
+        "email": "username@example.org",
+        "matrikel_no": "87654321",
+        "submissions": [
+            {
+                "type": "[a01] Zeichen und Strings",
+                "code": "#include <ctype.h>\nint umschalt(char *s)\n\n{\n\tint i = 0, count = 0;\n\twhile(s[i] != '\\0')\n\t{\n\t\tif(islower(s[i]) != 0)\n\t\t{\n\t\t\ts[i] = toupper(s[i]);\n\t\t} else if(isupper(s[i]) != 0)\n\t\t{\n\t\t\ts[i] = tolower(s[i]);\n\t\t} else\n\t\t{\n\t\t\tcount++;\n\t\t}\n\t\ti++;\n\t}\n\treturn count;\n}",
+                "tests": {
+                    "EmptyTest": {
+                        "name": "EmptyTest",
+                        "annotation": "",
+                        "label": "NOT_EMPTY"
+                    }
+                }
+            },
+            {
+                "type": "[a02] Iteration",
+                "code": "#include <stdlib.h>\nlong iteP(unsigned int k)\n{\n\tif(k == 0)\n\t{\n\t\treturn 3;\n\t}\n\tif( k == 1)\n\t{\n\t\treturn 0;\n\t}\n\tif( k == 3)\n\t{\n\t\treturn 2;\n\t}\n\tint *p = malloc(k * sizeof(int));\n\tp[0] = 3;\n\tp[1] = 0;\n\tp[2] = 2;\n\tfor(int i = 3; i < k; i++)\n\t{\n\t\tp[i] = p[i-2] + p[i - 3];\n\t}\n\n\treturn p[k];\n}",
+                "tests": {}
+            }
+        ]
+    }
+}
+
+
+def sha(s):
+    return hashlib.sha1(s.encode()).hexdigest()
+
+
+def run_cmd(cmd, stdin=None):
+    return subprocess.run(
+        cmd,
+        stderr=subprocess.PIPE,
+        stdout=subprocess.PIPE,
+        input=stdin,
+        shell=True,
+        encoding='utf-8',
+        timeout=0.5
+    )
+
+
+def testcase(i, args, stdout):
+    try:
+        ret = run_cmd("./code %s" % args)
+        assert ret.stdout == stdout
+    except AssertionError:
+        return f"Case #{i}: [ASSERT FAILED] ./umschalt {args} WAS '{ret.stdout}' SHOULD '{stdout}'"
+    except subprocess.CalledProcessError:
+        return f"Case #{i}: [FAILED] ./umschalt {args}"
+    except subprocess.TimeoutExpired:
+        return f"Case #{i}: [TIMEOUT] ./umschalt {args}"
+    else:
+        return f"Case #{i}: [SUCCESS] ./umschalt {args}"
+
+
+def all_subclasses(cls):
+    return cls.__subclasses__() \
+        + [g for s in cls.__subclasses__() for g in all_subclasses(s)]
+
+
+def sha1(submission_obj):
+    return hashlib.sha1(submission_obj['code'].encode()).hexdigest()
+
+
+class Test(metaclass=abc.ABCMeta):
+    """docstring for IliasQuestion"""
+
+    @classmethod
+    def available_tests(cls):
+        return {sub.__name__ : sub for sub in all_subclasses(cls)}
+
+    def __new__(cls, *args, **kwargs):
+        assert hasattr(cls, 'depends'),         "depends not defined"
+        assert hasattr(cls, 'label_success'),   "label_success not defined"
+        assert hasattr(cls, 'label_failure'),   "label_failure not defined"
+        return super().__new__(cls)
+
+    def __init__(self, submission_obj, **kwargs):
+
+        if not self.dependencies_satisfied(submission_obj):
+            self.result     = self.label_failure
+            self.annotation = "TEST DEPENDENCY NOT MET"
+            return
+
+        elif str(self) in submission_obj['tests']:
+            self.deserialize(submission_obj['tests'][str(self)])
+
+        else:
+            self.result, self.annotation = self.run_test(
+                submission_obj, **kwargs)
+            self.serialize(submission_obj)
+
+    def __bool__(self):
+        return self.result
+
+    def __str__(self):
+        return self.__class__.__name__
+
+    def dependencies_satisfied(self, submission_obj):
+        return all(dep(submission_obj).result for dep in self.depends)
+
+    def deserialize(self, test):
+        self.result     = test['label'] == self.label_success
+        self.annotation = test['annotation']
+
+    def serialize(self, submission_obj):
+        as_dict = {
+            'name'       : str(self),
+            'annotation' : self.annotation
+        }
+
+        if self.result:
+            as_dict['label'] = self.label_success
+        else:
+            as_dict['label'] = self.label_failure
+
+        submission_obj['tests'][str(self)] = as_dict
+
+    @abc.abstractmethod
+    def run_test(self, submission_obj) -> (bool, str):
+        return NotImplemented
+
+
+class EmptyTest(Test):
+    """docstring for EmptyTest"""
+
+    depends         = ()
+    label_success   = 'NOT_EMPTY'
+    label_failure   = 'EMPTY'
+
+    def run_test(self, submission_obj):
+        return bool(submission_obj['code'].strip()), ""
+
+
+class CompileTest(Test):
+
+    depends         = (EmptyTest, )
+    label_success   = 'COMPILATION_SUCCESSFUL'
+    label_failure   = 'COMPILATION_FAILED'
+
+    def run_test(self, submission_obj):
+
+        try:
+            ret = run_cmd("gcc-7 -c -xc -o code.o -", submission_obj['code'])
+        except subprocess.CalledProcessError as err:
+            print('[FATAL] The compiler failed.')
+
+        return not ret.returncode, ret.stderr
+
+
+class LinkTest(Test):
+
+    depends         = (CompileTest, )
+    label_success   = 'LINKING_SUCCESSFUL'
+    label_failure   = 'LINKING_FAILED'
+
+    def run_test(self, submission_obj):
+
+        ret = run_cmd("gcc-7 -o code a01-testing.o code.o")
+        return not ret.returncode, ret.stderr
+
+
+class UnitTestTest(Test):
+    """docstring for UnitTestTest"""
+
+    depends         = (LinkTest, )
+    label_success   = 'UNITTEST_SUCCSESSFUL'
+    label_failure   = 'UNITTEST_FAILED'
+
+    def run_test(self, submission_obj):
+
+        test_cases = (
+            ('', 'umschalt(NULL)\n== 0\n'),
+            ('...',  'input : "..."\numschalt("...")\n== 3\nresult: "..."\n'),
+            ('anV',  'input : "anV"\numschalt("anV")\n== 0\nresult: "ANv"\n'),
+        )
+
+        return 1, '\n'.join(testcase(i, *test) for i, test in enumerate(test_cases))
+
+
+path = tempfile.mkdtemp()
+run_cmd(f'cp ../data/testing_facility/klausur_tag_02/objects/* {path}')
+os.chdir(path)
+
+d = UnitTestTest(before["username"]['submissions'][0])
+
+# shutil.rmtree(path)
+
+print(path)
+print(d.annotation)
+
+
+with open("/dev/stdout", "w") as out:
+    out.write(json.JSONEncoder().encode(before))
diff --git a/util/testcases.py b/util/testcases.py
new file mode 100644
index 0000000000000000000000000000000000000000..87e24ed44e7cef2ea4f45315d21d2fcf230dfec2
--- /dev/null
+++ b/util/testcases.py
@@ -0,0 +1,78 @@
+r = '''-- [a01] Zeichen und Strings
+USAGE: ./bin/a01 <string> <character>
+-- [a02] Iteration
+USAGE: ./bin/a02 <unsigned integer>
+-- [a03] Rekursion
+USAGE: ./bin/a03 <integer> <integer> <integer> <integer>
+-- [a04] Strukturen I
+USAGE: ./bin/a04 <integer> <integer> ... <integer> <integer>
+-- [a05] Strukturen II
+NO EXECUTABLE
+-- [a06] Speicher auf dem Heap
+USAGE: ./bin/a06 <integer> <integer> ... <integer> <integer>
+'''
+
+import re
+import random
+from string import ascii_letters, digits
+
+types = ('integer', 'unsigned_integer', 'character', 'string')
+list_sep = '...'
+
+def call_function(name: str, *args, **kwargs):
+    return globals()[name](*args, **kwargs)
+
+def integer(bounds=1000):
+    return random.randint(-bounds, bounds)
+
+def unsigned_integer(bounds=1000):
+    return random.randint(0, bounds)
+
+def character():
+    return random.choice(5*ascii_letters + 2*digits + '%*+,-./:?@[]^_{}~')
+
+def string(lenght=31):
+    return ''.join(character() for i in range(unsigned_integer(lenght)))
+
+def type_list(_type):
+    def generic_list():
+        return ' '.join(str(call_function(_type)) for i in range(unsigned_integer(30)))
+    return generic_list
+
+def rubbish():
+    return ' '.join(string(4))
+
+for t in types:
+    globals()[t + '_list'] = type_list(t) # I fucking love it
+
+
+task = re.compile(r'^-- (?P<title>.*)\n(USAGE: (?P<cmd>[\./\w]+) (?P<syntax>.*)|NO EXECUTABLE)', re.MULTILINE)
+args = re.compile(rf"<({'|'.join(types)}|{'|'.join(t + '_list' for t in types)})>")
+
+
+def argument_generator(syntax):
+    syntax, _ = re.subn(r'<([\w\s]+)> <\1> \.\.\. <\1> <\1>', r'<\1_list>', syntax)
+    syntax, _ = re.subn(r'<(\w+)\s(\w+)>', r'<\1_\2>', syntax)
+
+    return ' '.join(str(call_function(arg)) for arg in re.findall(args, syntax))
+
+def testcases_generator(task, n=10):
+    syntax = task.group('syntax')
+
+    if not syntax:
+        return
+
+    yield ''
+    yield 0
+
+    for i in range(n//3):
+        yield rubbish()
+
+    for i in range(n):
+        yield argument_generator(syntax)
+
+for task in re.finditer(task, r):
+    for t in testcases_generator(task):
+        print(t)
+
+