diff --git a/program/models.py b/program/models.py
index fe6ba333e0e139713af97af9ed23d17f8b4abf18..821cd67f7cc70068c02bbf2fdac1236e23332d20 100644
--- a/program/models.py
+++ b/program/models.py
@@ -17,6 +17,9 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
+import dataclasses
+import datetime
+
 import jsonschema
 from rest_framework.exceptions import ValidationError
 from versatileimagefield.fields import PPOIField, VersatileImageField
@@ -687,3 +690,12 @@ class RadioSettings(models.Model):
 
     def __str__(self):
         return self.station_name
+
+
+@dataclasses.dataclass()
+class ProgramEntry:
+    id: str
+    start: datetime.datetime
+    end: datetime.datetime
+    show: Show
+    timeslot: TimeSlot | None
diff --git a/program/services.py b/program/services.py
index 39efd7109233724fa693bdfb7ce47e0c52c33f2d..21b1baaf90656fef6e46c5817cf8dbf178505946 100644
--- a/program/services.py
+++ b/program/services.py
@@ -20,7 +20,6 @@
 import copy
 from collections.abc import Iterator
 from datetime import datetime, time, timedelta
-from typing import TypedDict
 
 from dateutil.relativedelta import relativedelta
 from dateutil.rrule import rrule
@@ -33,6 +32,7 @@ from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 from program.models import (
     Note,
+    ProgramEntry,
     RadioSettings,
     RRule,
     Schedule,
@@ -699,14 +699,6 @@ def generate_conflicts(timeslots: list[TimeSlot]) -> Conflicts:
     return conflicts
 
 
-class ProgramEntry(TypedDict):
-    id: str
-    start: datetime
-    end: datetime
-    show: Show
-    timeslot: TimeSlot | None
-
-
 def generate_program_entries(
     queryset: QuerySet[TimeSlot],
     *,