Skip to content

Add live data to project summary#1296

Merged
mihow merged 28 commits into
mainfrom
feat/project-overview
May 14, 2026
Merged

Add live data to project summary#1296
mihow merged 28 commits into
mainfrom
feat/project-overview

Conversation

@annavik
Copy link
Copy Markdown
Member

@annavik annavik commented May 7, 2026

Summary

In this PR we include some live data in the project summary. This is to make the project dashboard feel a bit more alive and interactive. The information about top identifiers is new and not something we have been able to see before. Maybe this could be motivating for team members to see?

List of Changes

  • Setup new backend endpoint for top identifiers
  • Add overview section to project summary
  • Present latest occurrences in overview section
  • Present top identifiers in overview section
  • Present top taxa in overview section

Introduces an API pattern for adding stats & aggregate summary metrics around the app:

GET /api/v2/<entity>/stats/<kind>/?project_id=X[&limit=N&...]

In this example: GET /api/v2/occurrences/stats/top-identifiers/?project_id=18&limit=5

Other upcoming data that might use this:

  • /occurrences/stats/identifications-by-species/ — per-taxon identification counts
  • /occurrences/stats/human-model-agreement/ — model agreement / verification rate
  • /deployments/stats/processed-images/ — processed images per station or capture counts per processing state
  • /taxa/stats/model-accuracy/

How to Test the Changes

Backend extended past the original prototype — see "Backend follow-ups" below for the convention and verification notes.

Screenshots

Screenshot 2026-05-07 at 13 26 56

After removing emails:
image

Next Steps

Include a stats section in the occurrence view where we can see total number of identifications, percentage of occurrences verified and maybe even human-model agreement rate?


Backend follow-ups (Claude commits)

Anna's "could @mihow help review the backend" ask was picked up — the original prototype was extended into a reusable convention so future stats endpoints land in the same namespace.

Endpoint (legacy /users/identifications/top/ removed, no overlap period):

GET /occurrences/stats/top-identifiers/?project_id=X[&limit=N]{project_id, top_identifiers: [{id, name, image, identification_count}, ...]}

Convention: /api/v2/<entity>/stats/<kind>/ — full writeup at docs/claude/reference/api-stats-pattern.md. Each kind owns its response shape (scalar by default; opt into the DRF paginator envelope only when a kind genuinely needs ?limit / ?offset / ?ordering rails on hundreds of rows — deferred design at docs/claude/planning/stats-list-pattern.md).

Design notes:

  • OccurrenceStatsViewSet is a viewsets.GenericViewSet + ProjectMixin, one @action per stats kind.
  • Pure query lives in ami/main/models_future/occurrence.py::top_identifiers_for_project(). Bakes in identification_count >= 1 non-configurably — anon / empty calls never leak the project user list.
  • ?limit validated via SingleParamSerializer[int].clean() — 400 on out-of-range instead of silent clamp.
  • Draft project visibility gated inline (Project.objects.visible_for_user(request.user)); ProjectMixin enforces presence (400/404 on missing/unknown id) but not draft filtering.
  • Router order: r"occurrences/stats" MUST register before r"occurrences" — guarded by TestOccurrenceStatsViewSet.test_registration_order_preserves_occurrence_retrieve.

Verification:

  • python manage.py test ami.main.tests.TestOccurrenceStatsViewSet --keepdb → 6/6 pass
  • FE jest → 29/29 pass
  • CI green on the merge SHA

Convention reference: docs/claude/reference/api-stats-pattern.md.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added "Top Identifiers" leaderboard showing users ranked by identification count, with configurable result limits (1–50, default 5).
    • Added project summary section with three new widgets: Latest Occurrences, Most Identifications, and Most Observed Taxa.
    • Added "View All" links to expand dataset results.
  • Style

    • Updated error state component styling.
    • Refined sidebar layout and spacing.
  • Chores

    • Added new internationalization strings.

Review Change Stack

@annavik annavik requested a review from mihow May 7, 2026 11:35
@netlify
Copy link
Copy Markdown

netlify Bot commented May 7, 2026

Deploy Preview for antenna-preview canceled.

Name Link
🔨 Latest commit db0157a
🔍 Latest deploy log https://app.netlify.com/projects/antenna-preview/deploys/6a056b3e2494ae0008a184a8

@netlify
Copy link
Copy Markdown

netlify Bot commented May 7, 2026

Deploy Preview for antenna-ssec canceled.

Name Link
🔨 Latest commit db0157a
🔍 Latest deploy log https://app.netlify.com/projects/antenna-ssec/deploys/6a056b3e6c7c1100081fa4a3

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a complete top-identifiers stats endpoint with a project summary dashboard. The backend adds aggregation logic, serializers, and a DRF viewset action; the frontend implements data hooks and refactors the summary page with three new widgets; and supporting translations, styling updates, and comprehensive documentation complete the feature.

Changes

Project Summary Dashboard with Top Identifiers API

Layer / File(s) Summary
Backend aggregation logic, serializers, and API contract
ami/main/models_future/occurrence.py, ami/main/api/serializers.py, ami/main/api/schemas.py
New top_identifiers_for_project() helper function returns users ranked by distinct occurrence identification counts. UserIdentificationCountSerializer and TopIdentifiersResponseSerializer define the stats response envelope with project_id and list of user identifiers. limit_doc_param documents the query parameter via OpenAPI.
Backend viewset action and route registration
ami/main/api/views.py, config/api_router.py
New OccurrenceStatsViewSet with top_identifiers GET action validates limit (1–50, default 5), enforces project visibility, and returns top users. Router registers occurrences/stats before the generic occurrences route to prevent URL collision.
Backend endpoint tests and routing regression
ami/main/tests.py
Test suite validates response shape, ordering by identification count, limit parameter truncation, distinct occurrence counting, zero-count user exclusion, missing/invalid query parameters, visibility gating for draft projects, and URL routing order.
Frontend data hooks
ui/src/data-services/hooks/occurrences/stats/useTopIdentifiers.ts, ui/src/data-services/hooks/occurrences/useLatestOccurrences.ts, ui/src/data-services/hooks/species/useTopSpecies.ts
Three new hooks (useTopIdentifiers, useLatestOccurrences, useTopSpecies) wrap base data-fetching hooks with fixed pagination, sorting, and optional project filtering for the summary dashboard widgets.
ListItem component for summary widgets
ui/src/pages/project/summary/list-item.tsx
Reusable ListItem component renders rows with variant-based user avatar or thumbnail, optional title/text, and right-aligned count; includes Image and UserImage helper components with icon fallbacks.
Summary page: overview widgets and charts refactor
ui/src/pages/project/summary/summary.tsx
Refactored to render "Overview" section with three widgets (Latest Occurrences, Most Identifications, Most Observed Taxa) and tabbed Charts section. New SummaryColumn helper standardizes loading/error/empty states. Grid spacing adjusted and chart empty behavior now displays translated "no results".
Translations, styling tweaks, and VIEW_ALL consolidation
ui/src/utils/language.ts, ui/src/pages/species-details/species-details.tsx, ui/src/pages/project/sidebar/sidebar.tsx, ui/src/components/error-state/error-state.tsx
STRING enum and ENGLISH_STRINGS extended with 15 new keys (VIEW_ALL, CHARTS, LATEST_OCCURRENCES, MOST_IDENTIFICATIONS, MOST_OBSERVED_TAXA, OVERVIEW, etc.). Species-details consolidates to translated VIEW_ALL. Sidebar CSS adjusted (h-min removed, shrink-0/p-0/rounded-md added); ErrorState compact title styling updated.
Stats endpoint pattern documentation and planning
docs/claude/archive/pr-1296-occurrence-stats-migration.md, docs/claude/reference/api-stats-pattern.md, docs/claude/planning/stats-list-pattern.md, docs/claude/sessions/pr-1296-anna-original-body.md
Planning doc with 4-commit implementation roadmap, routing/convention decisions, and resume instructions; reference doc defining stats endpoint pattern and conventions for future endpoints; deferred pagination pattern guidance; session notes preserving original PR context.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

backend

Suggested reviewers

  • mihow

🐰 A stats endpoint hops into view,
With aggregates counting who did what new,
The summary dashboard shows all the top names,
And translations now speak in locale-aware frames.
Distinct occurrences ranked by the wise,
Let the leaderboard shine before users' eyes! 🏆

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add live data to project summary' directly and clearly describes the main change—adding live, interactive data widgets to the project dashboard overview.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering summary, list of changes, related issues context, detailed design notes, testing guidance, screenshots, and deployment notes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/project-overview

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
ami/main/api/views.py (1)

1869-1898: ⚡ Quick win

Recommended: use Count(filter=Q(...)) and align serializer_class with the actual response shape.

Two adjacent improvements:

  • The aggregation works today because Django reuses the identifications join, but the canonical and safer pattern is Count("identifications", filter=Q(...)). It removes the asymmetry between the two branches (one uses distinct=True, the other doesn't), drops the redundant trailing .distinct(), and is robust against future query changes.
  • serializer_class = UserIdentificationCountSerializer is registered but the response is a hand-rolled {"project_id": ..., "top_identifiers": [...]} dict. drf-spectacular will document the inner item shape and miss the wrapper. Either build the response through a top-level serializer or declare it via @extend_schema(responses=...).
♻️ Proposed simplification of the queryset
-        # Start with user queryset
-        user_queryset = User.objects.all()
-
-        # Filter by project if provided, then annotate with count
-        if project:
-            user_queryset = (
-                user_queryset.filter(identifications__occurrence__project=project)
-                .annotate(identification_count=Count("identifications", distinct=True))
-                .distinct()
-            )
-        else:
-            user_queryset = user_queryset.annotate(identification_count=Count("identifications"))
-
-        # Get top 5 users, ordered by identification count (descending)
-        top_identifiers = user_queryset.filter(identification_count__gt=0).order_by("-identification_count")[:5]
+        count_filter = (
+            Q(identifications__occurrence__project=project) if project else Q()
+        )
+        top_identifiers = (
+            User.objects.annotate(
+                identification_count=Count("identifications", filter=count_filter, distinct=True)
+            )
+            .filter(identification_count__gt=0)
+            .order_by("-identification_count")[:5]
+        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ami/main/api/views.py` around lines 1869 - 1898, Refactor the aggregation in
the top user queryset to use Count with a filter argument
(Count("identifications", filter=Q(...))) for consistent and safer filtering
instead of the current conditional annotations and distinct calls. Additionally,
align the response with the declared serializer_class by either creating a
top-level serializer that reflects the full response shape including
"project_id" and "top_identifiers", or annotate the view with `@extend_schema` to
explicitly declare the custom response structure to keep documentation in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ami/main/api/serializers.py`:
- Around line 1709-1718: The UserIdentificationCountSerializer currently exposes
email (email = serializers.CharField()) which leaks PII; remove the email field
from UserIdentificationCountSerializer so it matches UserNestedSerializer (id,
name, image, details) and update any UI/data consumers to use name/image only,
or alternatively keep the field but gate its inclusion in the view logic (e.g.,
only add email to the serialized output when request.user has project manager
permissions) by returning a different serializer or conditionally adding the
email in the view that constructs the response.

In `@ami/main/api/views.py`:
- Around line 1855-1905: The UserIdentificationCountsView exposes user PII such
as emails to anonymous users due to the broad IsActiveStaffOrReadOnly permission
and also lacks the require_project=True guard, allowing costly global
aggregations without project filtering. To fix this, set require_project = True
on the view to enforce project filtering, adjust permission_classes to a
stricter class like IsAuthenticated or IsProjectMember to prevent anonymous
access, and remove the email field from the response or restrict it so only
staff users receive it. These changes should be applied within the
UserIdentificationCountsView class.

In `@ui/src/pages/project/summary/list-item.tsx`:
- Around line 36-54: The img elements in the Image and UserImage components
currently use className="object-cover" but lack sizing so object-fit has no
effect; update the <img> in both Image and UserImage (functions/components named
Image and UserImage) to include full-size classes (e.g., add w-full and h-full
alongside object-cover) so the image fills the 12x12 container and object-cover
works as intended.

In `@ui/src/pages/project/summary/summary.tsx`:
- Around line 107-136: The "Latest occurrences" links are using an ascending
ordering query param; update both Link usages that build URLs via
APP_ROUTES.OCCURRENCE_DETAILS (inside occurrences.map) and
APP_ROUTES.OCCURRENCES (the "View all" link) to request descending order by
prefixing the field with a minus (e.g. ordering=-first_appearance_timestamp) so
the pages show most-recent-first to match useLatestOccurrences and the section
title.
- Around line 179-180: The Link pointing to APP_ROUTES.OCCURRENCES({ projectId
}) incorrectly appends "?verified=-true" (a malformed query) — update that Link
to use a proper boolean query (e.g., "?verified=false") so the URL is correct
and not brittle; locate the Link component referencing APP_ROUTES.OCCURRENCES
and projectId in summary.tsx and replace the "?verified=-true" fragment with a
valid query string such as "?verified=false" (or generate the query via
URLSearchParams) to fix the bug.

---

Nitpick comments:
In `@ami/main/api/views.py`:
- Around line 1869-1898: Refactor the aggregation in the top user queryset to
use Count with a filter argument (Count("identifications", filter=Q(...))) for
consistent and safer filtering instead of the current conditional annotations
and distinct calls. Additionally, align the response with the declared
serializer_class by either creating a top-level serializer that reflects the
full response shape including "project_id" and "top_identifiers", or annotate
the view with `@extend_schema` to explicitly declare the custom response structure
to keep documentation in sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e06b3030-6423-436b-9cfb-81575abff194

📥 Commits

Reviewing files that changed from the base of the PR and between 183f487 and fe1862e.

📒 Files selected for processing (12)
  • ami/main/api/serializers.py
  • ami/main/api/views.py
  • config/api_router.py
  • ui/src/components/error-state/error-state.tsx
  • ui/src/data-services/hooks/identifications/useTopIdentifiers.ts
  • ui/src/data-services/hooks/occurrences/useLatestOccurrences.ts
  • ui/src/data-services/hooks/species/useTopSpecies.ts
  • ui/src/pages/project/sidebar/sidebar.tsx
  • ui/src/pages/project/summary/list-item.tsx
  • ui/src/pages/project/summary/summary.tsx
  • ui/src/pages/species-details/species-details.tsx
  • ui/src/utils/language.ts

Comment thread ami/main/api/serializers.py Outdated
Comment thread ami/main/api/views.py Outdated
Comment thread ui/src/pages/project/summary/list-item.tsx
Comment thread ui/src/pages/project/summary/summary.tsx Outdated
Comment thread ui/src/pages/project/summary/summary.tsx Outdated
Comment thread config/api_router.py Outdated
mihow and others added 5 commits May 13, 2026 16:03
- Drop `email` from UserIdentificationCountSerializer + view response.
  Existing UserNestedSerializer omits email for the same PII reasons;
  match that contract. UI now renders name only.
- Refactor UserIdentificationCountsView to use serializer_class
  + get_serializer(...) (was hand-rolling response dict).
- Set `require_project = True`. Avoids unbounded global aggregation
  on the Identification table (mirrors SummaryView's guard).
- Use `Count(filter=Q(...))` so the annotation only counts a user's
  identifications scoped to the requested project, not their global
  total (was returning the same number from the JOIN-then-count form).
- list-item.tsx: `text` becomes optional, `<img>` gets
  `w-full h-full object-cover` so cropping works inside the 12x12 box.

Co-Authored-By: Claude <noreply@anthropic.com>
UserIdentificationCountsView returned identifier names + profile photos
for any project_id, including draft projects. Pattern matches CodeRabbit's
concern about anonymous read access on this view.

Add a Project.visible_for_user(request.user) check that raises 404 when
the requested project is a draft and the caller is not a superuser,
owner, or member. Anonymous + non-member callers can no longer enumerate
contributors on unpublished projects.

Co-Authored-By: Claude <noreply@anthropic.com>
Previously `Count("identifications", distinct=True)` counted raw
Identification rows, so a user who corrected their own ID on the same
occurrence had their leaderboard number inflated by every revision.

Switch to `Count("identifications__occurrence", distinct=True)` so the
metric matches the panel's intent: number of distinct occurrences the
user has identified in the project.

Verified against local fixtures: counts for users with duplicate IDs on
the same occurrence drop to the deduplicated value (e.g. 7 → 5),
unaffected users stay flat.

Co-Authored-By: Claude <noreply@anthropic.com>
LatestOccurrences, MostIdentifications, and MostObservedTaxa shared a
near-identical loading/error/empty/list+ViewAll skeleton. Factor that
out into a SummaryColumn shell that takes the load state and a
viewAllHref, and have each column pass its rows as children. Adds a
single seam for the upcoming fourth stats tile rather than a fourth
copy.

Drop the dead `?ordering=-...` querystrings on per-row OCCURRENCE_DETAILS
and TAXON_DETAILS links: detail pages don't run `useSort`, and the list
context is re-derived from the list URL on back-navigation, so the
querystring was inert noise. ViewAll links keep `?ordering=` since those
target list pages that honor it.

Co-Authored-By: Claude <noreply@anthropic.com>
…ers migration

Captures the design decision (entity-rooted + stats namespace, scope via
query, M2 colocated module structure) and the 3-commit implementation
plan to migrate /users/identifications/top/ to
/occurrences/stats/top-identifiers/ before merging PR #1296.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ui/src/pages/project/summary/summary.tsx (1)

1-270: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix blocking formatting check in this file.

yarn format --check is failing on ui/src/pages/project/summary/summary.tsx, so this PR won’t pass frontend lint until this file is reformatted with the project’s pinned formatter.

Based on learnings: In the ui/ project (RolnickLab/antenna), formatting must be produced with the project-local Prettier pinned to 2.8.4 (use repo-local Prettier, not a global version).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/src/pages/project/summary/summary.tsx` around lines 1 - 270, The file
fails the repository's formatting check; reformat
ui/src/pages/project/summary/summary.tsx using the repo-local Prettier (pinned
to 2.8.4) so the frontend lint passes. Locate the Summary component and its
helper components (LatestOccurrences, MostIdentifications, MostObservedTaxa,
Charts, SummaryColumn) and run the project-local formatter (e.g. the repo's yarn
format script or npx prettier@./node_modules/prettier --write) to apply the
canonical formatting, then re-run yarn format --check before committing.
🧹 Nitpick comments (1)
docs/claude/planning/pr-1296-occurrence-stats-migration.md (1)

188-188: ⚡ Quick win

Avoid machine-specific absolute paths in committed runbooks.

/home/michael/... is not portable for teammates and leaks local workstation details. Prefer repo-relative references or a placeholder variable.

Suggested patch
-2. Stack still bind-mounted into main compose (`/home/michael/Projects/AMI/antenna/docker-compose.override.yml` overrides django+celeryworker+ui-dev to point at worktree)
+2. Stack still bind-mounted into main compose (use your local `docker-compose.override.yml`; it overrides django+celeryworker+ui-dev to point at the worktree)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/claude/planning/pr-1296-occurrence-stats-migration.md` at line 188,
Replace the machine-specific absolute path
"/home/michael/Projects/AMI/antenna/docker-compose.override.yml" in the runbook
with a portable reference: either a repo-relative path (e.g.,
./docker-compose.override.yml) or a placeholder variable (e.g.,
{WORKTREE_DOCKER_COMPOSE_OVERRIDE}) and include a short note explaining how to
resolve it locally; update the occurrence in the planning doc (the line that
currently mentions the absolute path) so teammates can use a repo-relative or
placeholder value instead of leaking a local workstation path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/claude/planning/pr-1296-occurrence-stats-migration.md`:
- Around line 22-29: The fenced code block containing the HTTP status lines (the
triple-backtick block that starts immediately before "/users/                   
HTTP 401   djoser owns this") needs a language label to satisfy MD040; update
the opening fence from ``` to ```text (or another appropriate language) so the
block becomes a labeled code fence and linting passes.
- Around line 105-106: The current code directly casts
request.query_params.get("limit", 5) to int which raises on invalid strings;
update the handler to validate the limit by attempting the int conversion in a
try/except (catch ValueError/TypeError) and return a 400 Bad Request with a
clear error message when conversion fails, otherwise proceed to clamp the parsed
integer (limit = max(1, min(limit, 50))). Locate uses of
request.query_params.get("limit", 5) and the variable limit to implement this
validation and error response.

---

Outside diff comments:
In `@ui/src/pages/project/summary/summary.tsx`:
- Around line 1-270: The file fails the repository's formatting check; reformat
ui/src/pages/project/summary/summary.tsx using the repo-local Prettier (pinned
to 2.8.4) so the frontend lint passes. Locate the Summary component and its
helper components (LatestOccurrences, MostIdentifications, MostObservedTaxa,
Charts, SummaryColumn) and run the project-local formatter (e.g. the repo's yarn
format script or npx prettier@./node_modules/prettier --write) to apply the
canonical formatting, then re-run yarn format --check before committing.

---

Nitpick comments:
In `@docs/claude/planning/pr-1296-occurrence-stats-migration.md`:
- Line 188: Replace the machine-specific absolute path
"/home/michael/Projects/AMI/antenna/docker-compose.override.yml" in the runbook
with a portable reference: either a repo-relative path (e.g.,
./docker-compose.override.yml) or a placeholder variable (e.g.,
{WORKTREE_DOCKER_COMPOSE_OVERRIDE}) and include a short note explaining how to
resolve it locally; update the occurrence in the planning doc (the line that
currently mentions the absolute path) so teammates can use a repo-relative or
placeholder value instead of leaking a local workstation path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 89457e85-a1c0-4f6b-ab8f-e302be4c9a8b

📥 Commits

Reviewing files that changed from the base of the PR and between 8494ad3 and 86d2f90.

📒 Files selected for processing (3)
  • ami/main/api/views.py
  • docs/claude/planning/pr-1296-occurrence-stats-migration.md
  • ui/src/pages/project/summary/summary.tsx

Comment thread docs/claude/archive/pr-1296-occurrence-stats-migration.md
Comment thread docs/claude/planning/pr-1296-occurrence-stats-migration.md Outdated
mihow and others added 7 commits May 13, 2026 17:45
Re-review pass on 2026-05-13 corrected three factual claims and locked open
design questions:

Corrections
- "djoser-squat" claim was wrong; explicit path() runs before router.urls so
  the current /users/identifications/top/ route works. Real motivation to
  migrate: bypasses router (no autodoc grouping) + pollutes /users/ hierarchy
- "/occurrences/stats/ clean greenfield" was wrong; falls into
  OccurrenceViewSet.retrieve(pk="stats"). Registration order is mandatory,
  not "verify at apply time"
- Tests promoted from "open question" to required scope in Commit 2

Locked decisions
- Nested r"occurrences/stats" over sibling r"occurrence-stats" (hierarchy)
- Noun = stats not metrics (avoid Prometheus/ops connotation)
- Response shapes vary per kind, always declared via DRF serializer; envelope
  kept for top-identifiers to match existing FE shape (no FE churn)
- limit param uses SingleParamSerializer with min_value=1/max_value=50,
  strict 400 on out-of-range (matches ami/jobs/views.py:321 precedent)
- FE hook moves to ui/src/data-services/hooks/occurrences/stats/
- get_active_project() discipline documented in viewset docstring
- Pydantic ruled out — DRF serializer integrates with drf-spectacular natively

Plan now has 4 commits (Commit 4 = convention reference doc at
docs/claude/reference/api-stats-pattern.md) so future stats endpoints have a
target to point at.

Co-Authored-By: Claude <noreply@anthropic.com>
Saves the original PR description verbatim so post-compact session can update
the PR body without clobbering Anna's writing. Anna remains the author; new
commits append a backend-follow-ups section, never replace existing content.

Co-Authored-By: Claude <noreply@anthropic.com>
No behavior change. UserIdentificationCountsView now calls
top_identifiers_for_project() from ami/main/models_future/occurrence.py.
Sets up Commit 2: a new /occurrences/stats/top-identifiers/ endpoint
calls the same function.

Co-Authored-By: Claude <noreply@anthropic.com>
Establishes the /<entity>/stats/<kind>/ pattern as the convention for
aggregate stats endpoints (see plan doc at
docs/claude/planning/pr-1296-occurrence-stats-migration.md; permanent
reference will land in Commit 4).

New endpoint mirrors the legacy /users/identifications/top/ envelope
exactly so the frontend swap in Commit 3 is just a URL change. The
legacy route stays live until Commit 3 deletes it.

- OccurrenceStatsViewSet on a ViewSet (not GenericViewSet) — actions are
  independent aggregations, no shared queryset
- TopIdentifiersResponseSerializer declares the envelope so spectacular
  can autodoc it (no raw Response({...}) shapes allowed in this viewset)
- limit param via SingleParamSerializer[int] with min/max validation —
  strict 400 on out-of-range, matches ami/jobs/views.py:321 precedent
- r"occurrences/stats" MUST register BEFORE r"occurrences"; the regex
  ^occurrences/(?P<pk>[^/.]+)/$ would otherwise capture stats/ first.
  Comment in api_router.py + regression test guard against accidental
  re-ordering.

Tests (8 in TestOccurrenceStatsTopIdentifiers):
- happy_path ranks users by distinct occurrences
- limit param caps results
- limit below min / above max → 400
- missing project_id → 400 (via ProjectMixin.require_project)
- draft project → 404 for anon (visible_for_user gate)
- distinct count: revising your own ID on the same occurrence = 1, not 2
- registration_order: /occurrences/<pk>/ retrieve still resolves

Co-Authored-By: Claude <noreply@anthropic.com>
Completes the migration started in Commit 2:
- Move the frontend hook to ui/src/data-services/hooks/occurrences/stats/
  (mirrors the backend URL hierarchy)
- Flip the hook URL to /occurrences/stats/top-identifiers/
- Delete UserIdentificationCountsView class + its path() registration
  at /users/identifications/top/

Response envelope is unchanged, so no UI logic changes — just the URL.

Co-Authored-By: Claude <noreply@anthropic.com>
Permanent reference doc for the pattern established by
OccurrenceStatsViewSet.top_identifiers. Documents the registration-order
trap, the stats-vs-metrics noun choice, response-shape rules,
SingleParamSerializer usage, the get_active_project() discipline, the
models_future split, FE hook layout, and the test floor for new
stats actions.

Co-Authored-By: Claude <noreply@anthropic.com>
…view

# Conflicts:
#	ami/main/models_future/occurrence.py
Comment thread ami/main/api/serializers.py
@mihow
Copy link
Copy Markdown
Collaborator

mihow commented May 14, 2026

Claude says: Establishing a new convention with this PR:

GET /api/v2/<entity>/stats/<kind>/?project_id=X[&limit=N&...]

Live example: GET /api/v2/occurrences/stats/top-identifiers/?project_id=18&limit=5

This is where future product-domain aggregates will land:

  • /occurrences/stats/top-identifiers/ ← shipped here
  • /occurrences/stats/by-algorithm/ — counts per detection algorithm
  • /occurrences/stats/identifications-by-species/ — per-taxon identification counts
  • /occurrences/stats/human-model-agreement/ — model agreement / verification rate
  • /occurrences/stats/timeline/ — Plotly-shaped time series
  • /deployments/stats/processed-images/ — processed images per station
  • /deployments/stats/by-status/ — capture counts per processing state
  • /taxa/stats/most-observed/ — top species

Conventions enforced for any new stats action:

  • ViewSet is a bare viewsets.ViewSet (not GenericViewSet — each kind queries a different model/shape)
  • One @action(url_path="<kind>") per stats kind
  • Response shape declared via a DRF serializer + @extend_schema(responses=...) — autodoc-able, no raw Response({...})
  • Query params validated via SingleParamSerializer[T].clean(...) — strict 400 on bad values, no silent clamping
  • Project gate via ProjectMixin.get_active_project() + Project.objects.visible_for_user() for draft visibility
  • Pure querysets live in ami/main/models_future/<entity>.py (reusable from anywhere, not view-layer-coupled)
  • FE hook mirrors the URL: ui/src/data-services/hooks/<entity>/stats/<kind>.ts
  • Registration order trap is real — r"<entity>/stats" MUST register before r"<entity>" in config/api_router.py, regression test included

Convention reference: docs/claude/reference/api-stats-pattern.md — copy that doc's "future stats actions" section when adding the next one.

metrics was deliberately not used (carries Prometheus / infra connotation). stats reads as product aggregates.

…ies)

Adds /deployments/stats/ examples (processed images per station, capture
status counts) and additional /occurrences/stats/ examples (per-species
identification counts, model agreement rate) to the convention doc, so
future PRs touching those use cases land on the established pattern.

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread ami/main/api/views.py
Comment thread ami/main/api/views.py Outdated
mihow and others added 2 commits May 13, 2026 19:01
Addresses mihow's review feedback on /occurrences/stats/top-identifiers/:
ride the standard list-endpoint rails instead of a hand-rolled envelope.
Also demonstrates the scalar/chart shape on the same viewset so the
convention covers both kinds.

Backend
- OccurrenceStatsViewSet now inherits viewsets.GenericViewSet with
  pagination_class=StatsPagination (default_limit=5, max_limit=50) and
  filter_backends=[NullsLastOrderingFilter] + ordering_fields=
  ["identification_count"]. Per-action: call paginate_queryset() +
  get_paginated_response() for list-shaped kinds; build dict +
  Response(serializer.data) for scalar/chart kinds.
- top_identifiers: drop inner [:limit], drop SingleParamSerializer for
  limit, drop {project_id, top_identifiers: [...]} envelope. Response
  is now DRF's {count, next, previous, results: [...]}.
  ?limit=N, ?offset=M, ?ordering=-identification_count all work.
- identifications_summary: new scalar placeholder action demonstrating
  pattern 2 (serializer-declared shape, no paginator). Returns
  {total_identifications, distinct_identifiers,
   distinct_identified_occurrences}. Real consumers (verification rate,
  model agreement, etc) follow the same shape.
- top_identifiers_for_project: drop limit param (paginator handles it).
  Keep .filter(identification_count__gt=0) as non-configurable — anon
  / empty calls must never leak the full project user list, regardless
  of how the caller slices.
- IdentificationsSummarySerializer added; TopIdentifiersResponseSerializer
  deleted (paginator wraps automatically).

Tests
- TestOccurrenceStatsTopIdentifiers → TestOccurrenceStatsViewSet
  (12 tests covering both patterns + shared regression).
- New: default_limit_is_five, ordering_param_overrides_default,
  excludes_users_with_zero_count, summary scalar happy path,
  summary missing project, summary draft 404.
- Dropped: limit_below_min/above_max 400 cases — paginator silently
  falls back to default on invalid values, doesn't 400.
- registration_order test now covers both stats subroutes.

Frontend
- useTopIdentifiers reads {count, results: [...]} and accepts an
  optional limit arg (defaults to 5, passed as ?limit=5 explicitly).
- MostIdentifications consumer reads data?.results.

Docs
- api-stats-pattern.md rewritten to cover both response shapes, the
  "don't leak entity lists" rule for list-kinds, and updated future
  examples split by pattern.

Co-Authored-By: Claude <noreply@anthropic.com>
Drop the placeholder `identifications_summary` action and the
paginator/filter-backend machinery added in 4c9acd5. The leaderboard
is intentionally short (default 5, max 50) — `{count, next, previous}`
fields per response add overhead with no consumer benefit, and the
placeholder existed only to demonstrate a scalar shape the leaderboard
itself wanted.

Changes:
- `OccurrenceStatsViewSet` returns `{project_id, top_identifiers: [...]}`
  via `TopIdentifiersResponseSerializer`. One action, scalar shape.
- `_gate_project` helper inlined (2 lines, single call site).
- `?limit` validated via `SingleParamSerializer[int].clean()` —
  strict 400 on out-of-range instead of silent paginator clamp.
- `identification_count >= 1` filter stays baked into
  `top_identifiers_for_project` (non-configurable).
- Tests trimmed 13 → 6 (drop pagination/ordering/summary tests, fold
  default_limit + distinct-count invariants into happy path).
- FE hook + `summary.tsx` read `data?.top_identifiers` instead of
  `data?.results`.
- Reference doc rewritten scalar-first.
- New planning doc captures the paginator design ready to revive when
  a stats kind genuinely needs `?limit / ?offset / ?ordering` rails
  on hundreds of rows.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
docs/claude/reference/api-stats-pattern.md (2)

77-77: 💤 Low value

Fix grammar: "needs paginated" → "needs to be paginated" or "needs pagination".

The phrase "needs paginated" is nonstandard in formal writing.

📝 Suggested fix
-When a kind genuinely needs paginated rails (e.g. a deployment
+When a kind genuinely needs to be paginated (e.g. a deployment

or

-When a kind genuinely needs paginated rails (e.g. a deployment
+When a kind genuinely needs pagination (e.g. a deployment
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/claude/reference/api-stats-pattern.md` at line 77, Replace the
nonstandard phrase "needs paginated" in the sentence fragment "When a kind
genuinely needs paginated rails (e.g. a deployment" with correct wording —
either "needs to be paginated" or "needs pagination" — so the sentence reads
e.g. "When a kind genuinely needs to be paginated (e.g. a deployment...)" or
"When a kind genuinely needs pagination (e.g. a deployment...)".

191-191: 💤 Low value

Add language identifier to fenced code block for linting compliance.

The code block showing file paths should have a language identifier to satisfy markdown linting rules.

📝 Suggested fix
-```
+```text
 ui/src/data-services/hooks/<entity>/stats/<kind>.ts
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/claude/reference/api-stats-pattern.md at line 191, The fenced code
block containing the path string
"ui/src/data-services/hooks//stats/.ts" is missing a language
identifier; update the opening fence from totext so the block is marked
as plain text (e.g., replace the opening triple backticks with ```text) to
satisfy markdown linting.


</details>

</blockquote></details>
<details>
<summary>docs/claude/planning/pr-1296-occurrence-stats-migration.md (1)</summary><blockquote>

`250-250`: _💤 Low value_

**Add period after "etc" for American English style.**

The abbreviation "etc" should be "etc." in American English conventions.




<details>
<summary>📝 Suggested fix</summary>

```diff
-2. Next stats endpoint that lands (species-accuracy, processing-progress, etc) confirms the
+2. Next stats endpoint that lands (species-accuracy, processing-progress, etc.) confirms the
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/claude/planning/pr-1296-occurrence-stats-migration.md` at line 250,
Update the sentence containing the phrase "species-accuracy,
processing-progress, etc" to use American English punctuation by adding a period
after "etc" (change "etc" to "etc."). Locate the exact occurrence of that phrase
in the document and replace it so the text reads "species-accuracy,
processing-progress, etc." ensuring only the punctuation is changed.
```

</details>

</blockquote></details>
<details>
<summary>ami/main/api/views.py (1)</summary><blockquote>

`1306-1309`: _💤 Low value_

**Prefer an explicit guard over `assert` for the project-presence invariant.**

`assert project is not None` is stripped under `python -O`. The comment "require_project=True guarantees this" treats it as documentation, but if a future change to `ProjectMixin` ever returns `None` here, the next-line `project.pk` access would raise `AttributeError` rather than a clean 400/404. Since you're also doing a visibility lookup right after, an explicit guard costs nothing.



<details>
<summary>🛡️ Proposed change</summary>

```diff
-        project = self.get_active_project()
-        assert project is not None  # require_project=True guarantees this
-        if not Project.objects.visible_for_user(request.user).filter(pk=project.pk).exists():
+        project = self.get_active_project()
+        if project is None or not Project.objects.visible_for_user(request.user).filter(pk=project.pk).exists():
             raise NotFound("Project not found.")
```

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ami/main/api/views.py` around lines 1306 - 1309, Replace the assert with an
explicit guard: check the value returned by get_active_project() (the local
variable project) and if it's None raise NotFound (or another appropriate HTTP
exception) before accessing project.pk; then proceed with the existing
Project.objects.visible_for_user(...) visibility check. This removes reliance on
assert and ensures a clean 404 if get_active_project() ever returns None.
```

</details>

</blockquote></details>
<details>
<summary>ami/main/api/schemas.py (1)</summary><blockquote>

`10-15`: _💤 Low value_

**Description hardcodes per-action bounds in a shared parameter.**

The description embeds `(1-50, default 5)`, but those constraints live in the `top_identifiers` action's `IntegerField(min_value=1, max_value=50, default=5)`. If this `OpenApiParameter` is intended to be reused across future stats actions (per the convention in `docs/claude/reference/api-stats-pattern.md`), other actions with different bounds will either silently mismatch or need their own `OpenApiParameter`. Consider keeping the description generic and declaring action-specific bounds inline in `@extend_schema` where they differ.



<details>
<summary>📝 Proposed change</summary>

```diff
 limit_doc_param = OpenApiParameter(
     name="limit",
-    description="Maximum number of items to return (1-50, default 5).",
+    description="Maximum number of items to return. Per-action bounds and default apply.",
     required=False,
     type=int,
 )
```

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ami/main/api/schemas.py` around lines 10 - 15, The shared OpenApiParameter
limit_doc_param currently hardcodes "(1-50, default 5)" which conflicts with
action-specific bounds like top_identifiers' IntegerField(min_value=1,
max_value=50, default=5); remove the per-action numeric bounds from
limit_doc_param's description and make it generic (e.g. "Maximum number of items
to return.") and then declare action-specific bounds and defaults where they
belong (for example in the top_identifiers view/serializer IntegerField or in
that action's `@extend_schema` parameter override) so each endpoint documents its
own min_value/max_value/default consistently.
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @ami/main/tests.py:

  • Around line 4697-4705: In setUp(), guard against a nullable self.taxon
    returned by Taxon.objects.filter(projects=project).first() by asserting it is
    not None (e.g., using assertIsNotNone or an explicit check) so test failures are
    immediate and descriptive; update the setUp() method where self.taxon is
    assigned and before returning to raise or assert with a clear message like "No
    Taxon found for project" so calls to _id(user, occurrence) and
    Identification.objects.create(...) won't fail with an obscure DB error.

Nitpick comments:
In @ami/main/api/schemas.py:

  • Around line 10-15: The shared OpenApiParameter limit_doc_param currently
    hardcodes "(1-50, default 5)" which conflicts with action-specific bounds like
    top_identifiers' IntegerField(min_value=1, max_value=50, default=5); remove the
    per-action numeric bounds from limit_doc_param's description and make it generic
    (e.g. "Maximum number of items to return.") and then declare action-specific
    bounds and defaults where they belong (for example in the top_identifiers
    view/serializer IntegerField or in that action's @extend_schema parameter
    override) so each endpoint documents its own min_value/max_value/default
    consistently.

In @ami/main/api/views.py:

  • Around line 1306-1309: Replace the assert with an explicit guard: check the
    value returned by get_active_project() (the local variable project) and if it's
    None raise NotFound (or another appropriate HTTP exception) before accessing
    project.pk; then proceed with the existing Project.objects.visible_for_user(...)
    visibility check. This removes reliance on assert and ensures a clean 404 if
    get_active_project() ever returns None.

In @docs/claude/planning/pr-1296-occurrence-stats-migration.md:

  • Line 250: Update the sentence containing the phrase "species-accuracy,
    processing-progress, etc" to use American English punctuation by adding a period
    after "etc" (change "etc" to "etc."). Locate the exact occurrence of that phrase
    in the document and replace it so the text reads "species-accuracy,
    processing-progress, etc." ensuring only the punctuation is changed.

In @docs/claude/reference/api-stats-pattern.md:

  • Line 77: Replace the nonstandard phrase "needs paginated" in the sentence
    fragment "When a kind genuinely needs paginated rails (e.g. a deployment" with
    correct wording — either "needs to be paginated" or "needs pagination" — so the
    sentence reads e.g. "When a kind genuinely needs to be paginated (e.g. a
    deployment...)" or "When a kind genuinely needs pagination (e.g. a
    deployment...)".
  • Line 191: The fenced code block containing the path string
    "ui/src/data-services/hooks//stats/.ts" is missing a language
    identifier; update the opening fence from totext so the block is marked
    as plain text (e.g., replace the opening triple backticks with ```text) to
    satisfy markdown linting.

</details>

<details>
<summary>🪄 Autofix (Beta)</summary>

Fix all unresolved CodeRabbit comments on this PR:

- [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended)
- [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `47dfdc57-35cc-4954-84fe-04825acf34dd`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 86d2f90117d1e2b7f2efb369f02d109fb1111796 and fbf43826bbb7295e0662580cf3287e7a273dc983.

</details>

<details>
<summary>📒 Files selected for processing (12)</summary>

* `ami/main/api/schemas.py`
* `ami/main/api/serializers.py`
* `ami/main/api/views.py`
* `ami/main/models_future/occurrence.py`
* `ami/main/tests.py`
* `config/api_router.py`
* `docs/claude/planning/pr-1296-occurrence-stats-migration.md`
* `docs/claude/planning/stats-list-pattern.md`
* `docs/claude/reference/api-stats-pattern.md`
* `docs/claude/sessions/pr-1296-anna-original-body.md`
* `ui/src/data-services/hooks/occurrences/stats/useTopIdentifiers.ts`
* `ui/src/pages/project/summary/summary.tsx`

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* ui/src/pages/project/summary/summary.tsx

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread ami/main/tests.py
The 3-line explanation duplicated docs/claude/reference/api-stats-pattern.md
and the regression test (test_registration_order_preserves_occurrence_retrieve)
already guards the invariant.

Co-Authored-By: Claude <noreply@anthropic.com>
@mihow
Copy link
Copy Markdown
Collaborator

mihow commented May 14, 2026

Claude says: Hi @annavik — quick summary of FE changes so you can review with focus.

Untouched: useLatestOccurrences.ts, useTopSpecies.ts, error-state.tsx, language.ts, sidebar.

useTopIdentifiers.ts — moved to hooks/occurrences/stats/ to mirror the new backend URL hierarchy, URL flipped to /occurrences/stats/top-identifiers/, dropped email from the response interface (matches our other user serializers), added optional limit arg (defaults to 5). Response shape ends up identical to what you wrote{project_id, top_identifiers: [...]} — so data?.top_identifiers works exactly as you had it.

summary.tsx — pulled the shared loading/error/empty/ViewAll skeleton out of the three overview columns into a SummaryColumn wrapper. Pure dedupe, no behavior change. Also dropped inert ?ordering= on per-row detail links (detail pages don't read them).

list-item.tsxtext made optional, <img> got w-full h-full object-cover so user avatars crop into the 12×12 box cleanly.

Backend grew (stats endpoint convention, models_future helper, reference doc) but your UI integration is unchanged in shape. Thanks for the clean FE work to build on.

Plan doc served its purpose — all four planned commits landed (plus a
paginator detour that was reverted). Live convention reference at
docs/claude/reference/api-stats-pattern.md is the canonical pointer for
future stats endpoints; archive preserves the design-decision audit
trail.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
docs/claude/archive/pr-1296-occurrence-stats-migration.md (1)

22-34: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language identifier to the fenced code block.

Line 22 opens an unlabeled fenced block, which still triggers MD040 markdown lint. Use ```text for this status-list block.

Suggested patch
-```
+```text
 /users/...                     handled by djoser router (registered as r"users")
 /users/identifications/top/    handled by explicit path() listed BEFORE router.urls →
                                works today because Django URL resolver matches in order;
                                not actually claimed by djoser, but bypasses the router
                                → pollutes /users/ hierarchy + no autodoc grouping
 /identifications/              our IdentificationViewSet
 /occurrences/                  our OccurrenceViewSet
 /occurrences/stats/            currently dispatches to OccurrenceViewSet.retrieve(pk="stats")
                                → 404 via Occurrence.objects.get(pk="stats") ValueError.
                                NOT clean greenfield — slot must be claimed by registering
                                r"occurrences/stats" BEFORE r"occurrences"
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/claude/archive/pr-1296-occurrence-stats-migration.md around lines 22 -
34, The fenced status-list block starting with must include a language identifier to satisfy MD040; change the opening fence from to text so the block begins with "text" and leave the rest of the list content unchanged
(the lines describing /users/..., /users/identifications/top/,
/identifications/, /occurrences/, /occurrences/stats/).


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In @docs/claude/archive/pr-1296-occurrence-stats-migration.md:

  • Around line 22-34: The fenced status-list block starting with must include a language identifier to satisfy MD040; change the opening fence from to
    text so the block begins with "text" and leave the rest of the list
    content unchanged (the lines describing /users/..., /users/identifications/top/,
    /identifications/, /occurrences/, /occurrences/stats/).

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `96e107c1-3662-4944-96c3-3dc4c88227c5`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between fbf43826bbb7295e0662580cf3287e7a273dc983 and 1da2943420d3c0b755cf00c73b5dfd282190627f.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `config/api_router.py`
* `docs/claude/archive/pr-1296-occurrence-stats-migration.md`

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* config/api_router.py

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

The previous archive-only commit used [no ci]; force a run so the
scalar-shape FE revert (fbf4382) gets verified on CI before merge.

Co-Authored-By: Claude <noreply@anthropic.com>
@mihow mihow merged commit 5d2145d into main May 14, 2026
5 checks passed
@mihow mihow deleted the feat/project-overview branch May 14, 2026 06:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants