diff --git a/api/gitlab_classes.py b/api/gitlab_classes.py
index 4e8a059252cb6eb7c7bea344a27236850cdb9405..4ca6b635c0480751823758717a33742521b4287b 100644
--- a/api/gitlab_classes.py
+++ b/api/gitlab_classes.py
@@ -215,6 +215,12 @@ class GitlabCommit:
     def is_merge(self) -> bool:
         return len(self.gitlab_commit.parent_ids) > 1
 
+    def get_id(self) -> str:
+        return self.gitlab_commit.id
+
+    def get_short_id(self) -> str:
+        return self.gitlab_commit.short_id
+
     # noinspection PyShadowingNames
     def get_diffs(self) -> List[Dict[str, Union[str, int]]]:
         diffs: List[Dict[str, Union[str, int]]] = []
@@ -234,6 +240,34 @@ class GitlabCommit:
                 deletions += diff['-']
         return max(insertions, deletions)
 
+    # noinspection PyShadowingNames
+    def get_diff_size_by_filetype(self) -> Dict[str, int]:
+        binary_like_text_files = {"css", "csv", "fxml", "html"}                             # TODO soft-code this
+        # noinspection SpellCheckingInspection
+        binary_files = {"docx", "gif", "jpg", "jpeg", "pdf", "png", "pptx", "svg", "xlsx"}  # TODO this, too
+        return_diff: Dict[str, int] = {}
+        insert_diff: Dict[str, int] = {}
+        delete_diff: Dict[str, int] = {}
+        if not self.is_merge():
+            for diff in self.get_diffs():
+                file = diff['file'].split('.')
+                if len(file) == 1:
+                    filetype = 'no type'
+                else:
+                    filetype = file[-1]
+                if filetype not in return_diff.keys():
+                    return_diff[filetype] = 0
+                    insert_diff[filetype] = 0
+                    delete_diff[filetype] = 0
+                if filetype in binary_files.union(binary_like_text_files):
+                    insert_diff[filetype] += 1
+                else:
+                    insert_diff[filetype] += diff['+']
+                    delete_diff[filetype] += diff['-']
+            for key in return_diff.keys():
+                return_diff[key] = max(insert_diff[key], delete_diff[key])
+        return return_diff
+
     @staticmethod
     def _number_of_lines_too_long(lines, subject_line_length, message_line_length):
         # noinspection PyUnusedLocal
diff --git a/grade_team_contribution.py b/grade_team_contribution.py
index 618369479afc9a7af577674afb65061f9caedf7a..29a13591c312b75f9ccedbe5559bbfb5eecff6c9 100644
--- a/grade_team_contribution.py
+++ b/grade_team_contribution.py
@@ -1,7 +1,6 @@
-import math
 import textwrap
 from datetime import date
-from math import ceil, log10
+from math import log10, floor
 from typing import Tuple
 
 from gitlab import GitlabError
@@ -94,8 +93,24 @@ def get_project_prefix(canvas_groups):
     return prefix
 
 
+def _combine_typed_contributions(typed_sizes: Tuple[Dict[str, int]]) -> Dict[str, int]:
+    combined_typed_size: Dict[str, int] = {}
+    keys: Set[str] = set()
+    for typed_size in typed_sizes:
+        keys = keys.union(typed_size.keys())
+    for key in keys:
+        combined_typed_size[key] = 0
+        for typed_size in typed_sizes:
+            if key in typed_size.keys():
+                combined_typed_size[key] += typed_size[key]
+    return combined_typed_size
+
+
 def display_git_contributions(project: GitlabProject):
     # TODO: recognize that this only works for projects in namespace; will need to ask whether to retrieve project.
+    binary_like_text_files = {"css", "csv", "fxml", "html"}                             # TODO soft-code this
+    # noinspection SpellCheckingInspection
+    binary_files = {"docx", "gif", "jpg", "jpeg", "pdf", "png", "pptx", "svg", "xlsx"}  # TODO this, too
     # noinspection PyUnusedLocal
     project_commits: List[GitlabCommit]
     if assignment_start_date == '':
@@ -103,6 +118,7 @@ def display_git_contributions(project: GitlabProject):
     else:
         project_commits = project.get_commits(after_date=assignment_start_date)
     contributions: Dict[str, int] = {}  # TODO: also broaden to multiple branches?
+    typed_contributions: Dict[str, Dict[str, int]] = {}
     timestamps: Dict[str, List[datetime]] = {}
     contributors: Set[Tuple[str, str]] = set()
     # noinspection PyShadowingNames
@@ -111,24 +127,33 @@ def display_git_contributions(project: GitlabProject):
             author = commit.get_author()
             contributors.add((author['name'], author['email']))
             email = author['email']  # TODO: manage aliases
-            size = commit.get_diff_size()  # TODO: distinguish between file types
+            size = commit.get_diff_size()
+            typed_size = commit.get_diff_size_by_filetype()
             if email != 'bohn@unl.edu':  # TODO: un-hard-code this -- may not be necessary with a starting date
                 if email not in contributions:
                     contributions[email] = 0
+                    typed_contributions[email] = {}
                     timestamps[email] = []
                 contributions[email] += size
+                typed_sizes: Tuple[Dict[str, int]] = (typed_contributions[email], typed_size)
+                typed_contributions[email] = _combine_typed_contributions(typed_sizes)
                 timestamps[email].append(commit.get_timestamp())
     print(f'Contributions by each partner to {project} :')
     for contribution in contributions:
         contributor = list(filter(lambda c: c[1] == contribution, contributors))[0]
         email = contributor[1]
+        typed_contribution = typed_contributions[email]
         timestamps[email].sort()
         number_of_commits = len(timestamps[email])
         print(f'\t{contributor}')
         print(
-            f'\t{str(contributions[contribution]).rjust(5)} line changes in {str(number_of_commits).rjust(3)} commits')
+            f'\t{str(contributions[contribution]).rjust(5)} total line changes in '
+            f'{str(number_of_commits).rjust(3)} commits')
+        for filetype in sorted(typed_contribution.keys()):
+            change_type = 'file' if filetype in binary_files.union(binary_like_text_files) else 'line'
+            print(f'\t\t\t{filetype} {str(typed_contribution[filetype]).rjust(10-len(filetype))} {change_type} changes')
         print(f'\t\tFirst commit:  {timestamps[email][0]}')
-        print(f'\t\tMedian commit: {timestamps[email][math.floor(number_of_commits/2)]}')
+        print(f'\t\tMedian commit: {timestamps[email][floor(number_of_commits/2)]}')
         print(f'\t\tLast commit:   {timestamps[email][-1]}')
 
 
@@ -171,8 +196,7 @@ if __name__ == '__main__':
     if option is options[1]:
         print('Which group?')
         student_groups = [select_from_list(student_groups, 'student group')]
-    zero_padding: int = ceil(log10(len(projects)))          # remove this after 24pair is graded
-    # zero_padding: int = floor(log10(len(projects))) + 1   # leaving this here until 24pair is graded
+    zero_padding: int = floor(log10(len(projects))) + 1
     assignment_start_date = get_assignment_start()  # TODO: only need this if grading git histories
     for student_group in student_groups:  # TODO: Skip past graded groups
         input(f'\n\nPress Enter to grade {student_group}')