diff --git a/denis/final.py b/denis/final.py
index cbee80df..3728d311 100755
--- a/denis/final.py
+++ b/denis/final.py
@@ -11,4 +11,6 @@
 
 print(f'final subs for {assignment} released')
 
-utilities.update_tags(assignment, 'final')
+tags = utilities.update_tags(assignment, 'final')
+
+utilities.run_automated_checks(tags)
diff --git a/denis/initial.py b/denis/initial.py
index 5faa098c..71cf2d05 100755
--- a/denis/initial.py
+++ b/denis/initial.py
@@ -58,4 +58,6 @@
 
 print(f'initial subs for {assignment} released')
 
-utilities.update_tags(assignment, 'initial')
+tags = utilities.update_tags(assignment, 'initial')
+
+utilities.run_automated_checks(tags)
diff --git a/denis/utilities.py b/denis/utilities.py
index 0120a7e0..09e34f74 100644
--- a/denis/utilities.py
+++ b/denis/utilities.py
@@ -38,6 +38,12 @@ def release_subs(sub_ids):
                    input=journal_data, check=True)
 
 
+def configure_repo(repo):
+    with repo.config_writer() as config:
+        config.set_value('user', 'name', 'denis')
+        config.set_value('user', 'email', 'denis@denis')
+
+
 def update_tags(assignment, component):
     grd_tbl = mailman.db.Gradeable
     subs = (grd_tbl.select()
@@ -47,15 +53,15 @@ def update_tags(assignment, component):
     with tempfile.TemporaryDirectory() as repo_path:
         repo = git.Repo.clone_from(PULL_URL, repo_path)
         repo.create_remote(REMOTE_NAME, PUSH_URL)
-        repo.config_writer().set_value('user', 'name', 'denis').release()
-        (repo.config_writer().set_value('user', 'email', 'denis@denis')
-                             .release())
+        configure_repo(repo)
         if 'EMPTY' not in repo.tags:
             repo.git.commit('--allow-empty', '-m', 'No gradeable submission.')
             repo.create_tag('EMPTY')
 
+        updated_tags = []
         for user in orbit.db.User.select():
             new_tag_name = f'{assignment}_{component}_{user.username}'
+            updated_tags.append(new_tag_name)
             if new_tag_name in repo.tags:
                 print('Potential issue? Attempted to create duplicate tag '
                       f'{new_tag_name}')
@@ -70,3 +76,20 @@ def update_tags(assignment, component):
             repo.create_tag(new_tag_name, ref=to_promote.commit, message=msg)
 
         repo.git.push(REMOTE_NAME, tags=True)
+
+    return updated_tags
+
+
+def run_automated_checks(tags):
+    with tempfile.TemporaryDirectory() as repo_path:
+        repo = git.Repo.clone_from(PULL_URL, repo_path)
+
+        remote = repo.create_remote(REMOTE_NAME, PUSH_URL)
+        remote.fetch('refs/notes/*:refs/notes/*')
+        configure_repo(repo)
+
+        for tag in tags:
+            msg = 'Automated tests by denis'
+            repo.git.execute(['git', 'notes', '--ref=denis', 'add', tag, '-m', msg])
+
+        remote.push('refs/notes/*:refs/notes/*')
diff --git a/mailman/patchset.py b/mailman/patchset.py
index 7738f05d..44a3854f 100644
--- a/mailman/patchset.py
+++ b/mailman/patchset.py
@@ -19,10 +19,9 @@ def try_or_false(do, exc):
         return False
 
 
-def tag_and_push(repo_path, tag_name):
+def tag_and_push(repo, tag_name, msg=None):
     try:
-        repo = git.Repo(repo_path)
-        repo.create_tag(tag_name)
+        repo.create_tag(tag_name, message=msg)
         repo.create_remote('grading', REMOTE_PUSH_URL)
         repo.git.push('grading', tags=True)
         return True
@@ -31,14 +30,11 @@ def tag_and_push(repo_path, tag_name):
         return False
 
 
-author_args = ['-c', 'user.name=mailman', '-c',
-               'user.email=mailman@mailman']
 git_am_args = ['git', '-c', 'advice.mergeConflict=false',
-               *author_args, 'am', '--keep']
+               'am', '--keep']
 
 
-def do_check(repo_path, cover_letter, patches):
-    repo = git.Repo.init(repo_path)
+def do_check(repo, cover_letter, patches):
     whitespace_errors = []
 
     def am_cover_letter(keep_empty=True):
@@ -51,7 +47,7 @@ def am_cover_letter(keep_empty=True):
                     git.GitCommandError):
         return "missing cover letter!"
 
-    repo.git.execute(["git", *author_args, "am", "--abort"])
+    repo.git.execute(["git", "am", "--abort"])
     if not try_or_false(lambda: am_cover_letter(keep_empty=True),
                         git.GitCommandError):
         return ("missing cover letter and "
@@ -69,7 +65,7 @@ def do_git_am(extra_args=[]):
                         git.GitCommandError):
             continue
 
-        repo.git.execute(["git", *author_args, "am", "--abort"])
+        repo.git.execute(["git", "am", "--abort"])
 
         # Try again, if we succeed, count this patch as a whitespace error
         if try_or_false(lambda: do_git_am(), git.GitCommandError):
@@ -80,7 +76,7 @@ def do_git_am(extra_args=[]):
         return f'patch {i+1} failed to apply!'
 
     if whitespace_errors:
-        return ('whitespace error patch(es) '
+        return (f'whitespace error patch{"es" if len(whitespace_errors) > 1 else ""} '
                 f'{",".join(whitespace_errors)}?')
     else:
         return 'patchset applies.'
@@ -88,13 +84,16 @@ def do_git_am(extra_args=[]):
 
 def check(cover_letter, patches, submission_id):
     with tempfile.TemporaryDirectory() as repo_path:
-        status = do_check(repo_path, cover_letter, patches)
+        repo = git.Repo.init(repo_path)
+        with repo.config_writer() as config:
+            config.set_value('user', 'name', 'mailman')
+            config.set_value('user', 'email', 'mailman@mailman')
+        status = do_check(repo, cover_letter, patches)
         if status[-1] == '!':
-            repo = git.Repo(repo_path)
             for patch in patches:
                 patch_abspath = str(maildir / patch.msg_id)
-                repo.git.execute(['git', *author_args, 'commit', '--allow-empty', '-F', patch_abspath])
-        tag_and_push(repo_path, submission_id)
+                repo.git.execute(['git', 'commit', '--allow-empty', '-F', patch_abspath])
+        tag_and_push(repo, submission_id, msg=status)
     return status
 
 
@@ -110,8 +109,11 @@ def apply_peer_review(email, submission_id, review_id):
                                        multi_options=[f'--branch={review_id}',
                                                       '--single-branch',
                                                       '--no-tags'])
+            with repo.config_writer() as config:
+                config.set_value('user', 'name', 'mailman')
+                config.set_value('user', 'email', 'mailman@mailman')
             repo.git.execute([*args, patch_abspath])
-            tag_and_push(repo_path, submission_id)
+            tag_and_push(repo, submission_id)
         except git.GitCommandError as e:
             print(e, file=sys.stderr)
             status = 'failed to apply peer review'
diff --git a/orbit/Containerfile b/orbit/Containerfile
index f083a0d7..9927359d 100644
--- a/orbit/Containerfile
+++ b/orbit/Containerfile
@@ -18,6 +18,7 @@ FROM alpine:3.20 AS orbit
 
 RUN apk update && apk upgrade && apk add \
 	py3-bcrypt \
+	py3-gitpython \
 	py3-peewee \
 	py3-markdown \
 	uwsgi-python3 \
diff --git a/orbit/radius.py b/orbit/radius.py
index ab26f8f0..6ca6a6fb 100644
--- a/orbit/radius.py
+++ b/orbit/radius.py
@@ -4,6 +4,7 @@
 
 import base64
 import bcrypt
+import git
 import html
 import markdown
 import os
@@ -425,7 +426,7 @@ class OopsStatus:
 
 class AsmtTable:
     def __init__(self, assignment, oopsieness, peer1, peer2, init,
-                 review1, review2, final):
+                 review1, review1_grade, review2, review2_grade, final, final_grade):
         self.assignment = assignment
         self.name = assignment.name
         self.oopsieness = oopsieness
@@ -433,8 +434,11 @@ def __init__(self, assignment, oopsieness, peer1, peer2, init,
         self.peer2 = peer2
         self.init = init
         self.review1 = review1
+        self.review1_grade = review1_grade
         self.review2 = review2
+        self.review2_grade = review2_grade
         self.final = final
+        self.final_grade = final_grade
 
     def oops_button_hover(self):
         match self.oopsieness:
@@ -448,6 +452,48 @@ def oops_button_hover(self):
             case OopsStatus.UNAVAILABLE:
                 return "You have already used your oopsie"
 
+    def get_automated_feedback(self, attr):
+        if attr not in ['init', 'final'] or (gbl := getattr(self, attr)) is None:
+            return 'No submission'
+
+        match attr:
+            case 'init':
+                due_date = int(self.assignment.initial_due_date)
+            case 'final':
+                due_date = int(self.assignment.final_due_date)
+
+        if due_date < int(datetime.now().timestamp()):
+            return gbl.status
+
+        match gbl.status[-1]:
+            case '.':
+                return 'Submission accepted'
+            case '?':
+                return 'Issues detected in patchset'
+            case '!':
+                return 'Submission rejected'
+            case _:
+                return '---'
+
+    def get_total_score(self):
+        weighted_sum = 0
+        sum_of_weights = 0
+        try:
+            weighted_sum += 0.8 * int(self.final_grade)
+            sum_of_weights += 0.8
+            if self.peer1 is not None:
+                weighted_sum += 0.1 * int(self.review1_grade)
+                sum_of_weights += 0.1
+            if self.peer2 is not None:
+                weighted_sum += 0.1 * int(self.review2_grade)
+                sum_of_weights += 0.1
+        # if any of the grades are None, attempting to cast to int throws a TypeError
+        except TypeError:
+            return '-'
+        except ValueError:
+            return '???'
+        return f'{weighted_sum/sum_of_weights:.1f}'
+
     def gradeable_row(self, item_name, gradeable, rightmost_col):
         return f"""
         <tr>
@@ -480,8 +526,8 @@ def body(self):
             return f"""
               {self.gradeable_row('Final Submission', self.final, self.oopsie_button())}
               <tr>
-                <th>Comments</th>
-                <td colspan="3">-</td>
+                <th>Automated Feedback</th>
+                <td colspan="3">{self.get_automated_feedback('final')}</td>
               </tr>
             """
         if (not self.init or
@@ -491,14 +537,14 @@ def body(self):
               {self.gradeable_row('Initial Submission', self.init, self.oopsie_button())}
               <tr>
                 <th>Automated Feedback</th>
-                <td colspan="3">-</td>
+                <td colspan="3">{self.get_automated_feedback('init')}</td>
               </tr>
             """
         return f"""
           {self.gradeable_row('Initial Submission', self.init, self.oopsie_button())}
           <tr>
             <th>Automated Feedback</th>
-            <td colspan="3">-</td>
+            <td colspan="3">{self.get_automated_feedback('init')}</td>
           </tr>
           <tr>
             <th></th>
@@ -506,12 +552,12 @@ def body(self):
             <th>Submission ID</th>
             <th>Score</th>
           </tr>
-          {self.gradeable_row(self.peer1 + ' Peer Review', self.review1, '-') if self.peer1 else ''}
-          {self.gradeable_row(self.peer2 + ' Peer Review', self.review2, '-') if self.peer2 else ''}
-          {self.gradeable_row('Final Submission', self.final, '-')}
+          {self.gradeable_row(self.peer1 + ' Peer Review', self.review1, self.review1_grade if self.review1_grade else '-') if self.peer1 else ''}
+          {self.gradeable_row(self.peer2 + ' Peer Review', self.review2, self.review2_grade if self.review2_grade else '-') if self.peer2 else ''}
+          {self.gradeable_row('Final Submission', self.final, self.final_grade if self.final_grade else '-')}
           <tr>
-            <th>Comments</th>
-            <td colspan="3">-</td>
+            <th>Automated Feedback</th>
+            <td colspan="3">{self.get_automated_feedback('final')}</td>
           </tr>
         """
 
@@ -520,7 +566,7 @@ def __str__(self):
         <table>
           <caption><h3>{self.name}</h3></caption>
           <tr>
-            <th>Total Score: -</th>
+            <th>Total Score: {self.get_total_score()}</th>
             <th>Timestamp</th>
             <th>Submission ID</th>
             <th>Request an 'Oopsie'</th>
@@ -593,8 +639,18 @@ def handle_dashboard(rocket):
         rev1 = asn_gradeables.where(grd_tbl.component == 'review1').first()
         rev2 = asn_gradeables.where(grd_tbl.component == 'review2').first()
         final = asn_gradeables.where(grd_tbl.component == 'final').first()
-        ret += str(AsmtTable(assignment, oopsieness, peer1, peer2, init, rev1,
-                             rev2, final))
+
+        repo = git.Repo('/var/lib/git/grading.git')
+        grades = {}
+        for component in ['review1', 'review2', 'final']:
+            tag = f'{assignment.name}_{component}_{rocket.session.username}'
+            try:
+                grades[component] = repo.git.execute(['git', 'notes', '--ref=grade', 'show', tag])
+            except git.GitCommandError:
+                grades[component] = None
+
+        ret += str(AsmtTable(assignment, oopsieness, peer1, peer2, init,
+                             rev1, grades['review1'], rev2, grades['review2'], final, grades['final']))
     return rocket.respond(ret + '</form>', 'Dashboard')
 
 
