Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions backend/app/routers/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,27 +240,51 @@ def get_pages_per_day(
start_date_utc = start_date.astimezone(timezone.utc).replace(tzinfo=None)
end_date_utc = end_date.astimezone(timezone.utc).replace(tzinfo=None)

progress_entries = list(
# Books with at least one progress entry in the window — we need their full
# entry chains to compute correct daily averages.
book_ids_with_window_progress = set(
session.exec(
select(ReadingProgress)
select(ReadingProgress.book_id)
.where(
ReadingProgress.user_id == current_user.id,
ReadingProgress.created_at >= start_date_utc,
ReadingProgress.created_at <= end_date_utc,
)
.order_by(ReadingProgress.book_id, ReadingProgress.created_at)
.distinct()
).all()
)

# Load full entry chains for those books (including entries before the window
# so that the prev→curr delta and day_diff span are complete).
if book_ids_with_window_progress:
progress_entries = list(
session.exec(
select(ReadingProgress)
.where(
ReadingProgress.user_id == current_user.id,
ReadingProgress.book_id.in_(book_ids_with_window_progress),
)
.order_by(ReadingProgress.book_id, ReadingProgress.created_at)
).all()
)
else:
progress_entries = []

# All book_ids with *any* progress entry (used to exclude books from fallback).
all_book_ids_with_progress = set(
session.exec(
select(ReadingProgress.book_id)
.where(ReadingProgress.user_id == current_user.id)
.distinct()
).all()
)

books = list(
session.exec(select(Book).where(Book.user_id == current_user.id)).all()
)

books_with_progress = {e.book_id for e in progress_entries}

virtual_entries = []
for book in books:
if book.id not in books_with_progress or not book.date_started:
if book.id not in all_book_ids_with_progress or not book.date_started:
continue
# Finished books without date_finished have no bounded reading
# period; skip to avoid spreading pages from date_started to
Expand All @@ -281,11 +305,13 @@ def get_pages_per_day(
fallback_books = [
b
for b in books
if b.id not in books_with_progress
if b.id not in all_book_ids_with_progress
and b.reading_status == ReadingStatus.read
and b.date_started
and b.date_finished
and b.page_count
# Only include books whose reading period could overlap the window.
and _naive_utc(b.date_finished) >= start_date_utc
]
fallback_daily = _extract_book_level_daily_pages(fallback_books, tz, start_date_utc, end_date_utc)

Expand Down
27 changes: 0 additions & 27 deletions backend/tests/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,33 +315,6 @@ def test_data_import_validate_rejects_invalid_reading_status_enum(client: TestCl
assert any("reading_status" in error for error in payload["errors"])


def test_data_import_execute_deletes_temp_file_after_completion(client: TestClient, monkeypatch: MonkeyPatch, tmp_path: Path) -> None:
import_temp_dir = tmp_path / "import_temp"
monkeypatch.setattr(settings, "import_temp_dir", str(import_temp_dir))
csv_payload = "Title\nDune\n"
parse_resp = client.post(
"/api/data/import/parse",
files={"file": ("books.csv", csv_payload, "text/csv")},
)
assert parse_resp.status_code == 200
file_id = parse_resp.json()["file_id"]

temp_file = import_temp_dir / "1" / f"{file_id}.json"
assert temp_file.exists()

execute_resp = client.post(
"/api/data/import/execute",
json={
"file_id": file_id,
"mapping": {"title": {"source": "Title", "transform": None}},
"import_mode": "continue_on_error",
},
)
assert execute_resp.status_code == 200
events = _parse_sse(execute_resp.text)
assert any(event.get("event") == "complete" for event in events)
assert not temp_file.exists()


def test_data_import_execute_progress_uses_date_finished_for_read_books(
client: TestClient, monkeypatch: MonkeyPatch, tmp_path: Path
Expand Down