Archived
feat: US #32 Section-based Reading Recommendations
Backend: - Add ReadingSchedule and MeetingSection models for ebook reading schedules - Implement recommendation algorithm: greedy partition minimizing per-meeting time variance - Algorithm respects chapter boundaries, estimates time based on chapter weight - Add serializers for schedules (list/detail/generate/confirm/update) - Add ScheduleViewSet with endpoints: list, create (generate), retrieve, regenerate, confirm, update_meeting, reorder_meetings - Add URL routes for /api/books/schedules/ - Create migration 0004 for new models Frontend: - Add types: MeetingSection, ReadingSchedule, GenerateRecommendationsRequest - Add schedulesApi with methods: getSchedules, generateRecommendations, confirmSchedule, updateMeeting, reorderMeetings, deleteSchedule - Build RecommendationPage with 4-column meeting layout, drag-and-drop chapter reassignment, generate/regenerate/confirm flow - Add 'Schedule Reading' menu item to BookContextMenu - Add route: /books/:ebookId/schedule
This commit is contained in:
@@ -153,4 +153,47 @@ class ReadingProgress(models.Model):
|
||||
"current_position", "last_page",
|
||||
"device_id", "device_name", "version", "updated_at",
|
||||
])
|
||||
return self, True
|
||||
return self, True
|
||||
|
||||
|
||||
class ReadingSchedule(models.Model):
|
||||
"""A reading schedule that groups sections into weekly meetings for an ebook."""
|
||||
|
||||
ebook = models.ForeignKey(EBook, on_delete=models.CASCADE, related_name="schedules")
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="schedules")
|
||||
meeting_count = models.PositiveIntegerField(default=4)
|
||||
confirmed = models.BooleanField(default=False)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "books_reading_schedule"
|
||||
verbose_name = "Reading Schedule"
|
||||
verbose_name_plural = "Reading Schedules"
|
||||
ordering = ["-created_at"]
|
||||
unique_together = [("ebook", "user")]
|
||||
indexes = [models.Index(fields=["ebook", "user"])]
|
||||
|
||||
def __str__(self):
|
||||
return f"Schedule for {self.ebook.title} ({self.meeting_count} meetings)"
|
||||
|
||||
|
||||
class MeetingSection(models.Model):
|
||||
"""A single meeting within a reading schedule, containing assigned chapters."""
|
||||
|
||||
schedule = models.ForeignKey(ReadingSchedule, on_delete=models.CASCADE, related_name="meetings")
|
||||
meeting_index = models.PositiveIntegerField()
|
||||
title = models.CharField(max_length=256, default="Meeting")
|
||||
estimated_time_minutes = models.PositiveIntegerField(default=0)
|
||||
chapter_ids = models.JSONField(default=list)
|
||||
|
||||
class Meta:
|
||||
db_table = "books_meeting_section"
|
||||
verbose_name = "Meeting Section"
|
||||
verbose_name_plural = "Meeting Sections"
|
||||
ordering = ["meeting_index"]
|
||||
unique_together = [("schedule", "meeting_index")]
|
||||
indexes = [models.Index(fields=["schedule", "meeting_index"])]
|
||||
|
||||
def __str__(self):
|
||||
return f"Meeting {self.meeting_index + 1}: {self.title}"
|
||||
Reference in New Issue
Block a user