diff --git a/2023/python/Day02.py b/2023/python/Day02.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1a7ba83dab9c25dafa5eb18b3106daebe2312df
--- /dev/null
+++ b/2023/python/Day02.py
@@ -0,0 +1,83 @@
+from enum import Enum
+from typing import List, Dict
+
+from ImportData import import_data
+
+day: int = 2
+
+
+class Color(Enum):
+    RED = 1
+    GREEN = 2
+    BLUE = 3
+
+
+sample_data: List[str] = '''
+Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
+Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
+Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
+Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
+Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
+'''.split('\n')[1:-1]
+
+data_structure: type = List[List[Dict[Color, int]]]
+
+
+def parse_data(data: List[str]) -> data_structure:
+    games: List[List[Dict[Color, int]]] = []
+    expected_game_number: int = 0
+    for game in data:
+        revelations: List[Dict[Color, int]] = []
+        game_identification, game_data = game.split(': ')
+        # validate my assumption that I can use a list without explicitly tracking the game number
+        expected_game_number += 1
+        actual_game_number = int(game_identification.split(' ')[1])
+        assert expected_game_number == actual_game_number
+        # and now on with the show
+        for reveal_data in game_data.split('; '):
+            revelation: Dict[Color, int] = {Color.RED: 0, Color.GREEN: 0, Color.BLUE: 0}
+            for reveal_datum in reveal_data.split(', '):
+                number_of_cubes = int(reveal_datum.split(' ')[0])
+                color = getattr(Color, reveal_datum.split(' ')[1].upper())
+                revelation[color] = number_of_cubes
+            revelations.append(revelation)
+        games.append(revelations)
+    return games
+
+
+def is_possible(game: List[Dict[Color, int]], cubes: Dict[Color, int]) -> bool:
+    game_is_possible: bool = True
+    for revelations in game:
+        for color in revelations:
+            if revelations[color] > cubes[color]:
+                game_is_possible = False
+    return game_is_possible
+
+
+def part1(data: data_structure) -> int:
+    score: int = 0
+    cubes: Dict[Color, int] = {Color.RED: 12, Color.GREEN: 13, Color.BLUE: 14}
+    for index, game in enumerate(data):
+        if is_possible(game, cubes):
+            score += index + 1
+    return score
+
+
+def fewest_cubes(game: List[Dict[Color, int]]) -> Dict[Color, int]:
+    reds: List[int] = [revelations[Color.RED] for revelations in game]
+    greens: List[int] = [revelations[Color.GREEN] for revelations in game]
+    blues: List[int] = [revelations[Color.BLUE] for revelations in game]
+    return {Color.RED: max(reds), Color.GREEN: max(greens), Color.BLUE: max(blues)}
+
+
+def part2(data: data_structure) -> int:
+    powers = [cubes[Color.RED] * cubes[Color.GREEN] * cubes[Color.BLUE] for cubes in
+              [fewest_cubes(game) for game in data]]
+    return sum(powers)
+
+
+if __name__ == '__main__':
+    production_ready = True
+    raw_data = import_data(day) if production_ready else sample_data
+    print(part1(parse_data(raw_data)))
+    print(part2(parse_data(raw_data)))