Skip to content

[US-9.1 / OBT-216] Include team_size in project data - #95

Open
levigtri wants to merge 6 commits into
mainfrom
levigft/obt-216-us-91-the-project-data-to-include-the-team-size
Open

[US-9.1 / OBT-216] Include team_size in project data#95
levigtri wants to merge 6 commits into
mainfrom
levigft/obt-216-us-91-the-project-data-to-include-the-team-size

Conversation

@levigtri

@levigtri levigtri commented Jul 4, 2026

Copy link
Copy Markdown
Member

Summary

Adds a team_size field to the project response so the project map popup can show how many people work on each project. The count reflects the distinct users with direct access to a project (project_user_access) and is computed with a single grouped query, so listing N projects does not trigger N extra lookups. No database migration is required — team_size is a computed field.

Changes

  1. count_project_team_sizes serviceapp/services/project/count_project_team_sizes.py: one grouped COUNT(DISTINCT user_id) query over project_user_access for a batch of project ids, resolving ids with no access rows to 0. Exported from app/services/project/__init__.py.
  2. team_size on the response schemaapp/models/project.py: ProjectResponse gains team_size: int = 0.
  3. Router populates the fieldapp/api/projects/core.py: a _serialize_projects helper batches the team-size lookup for the list endpoint and a single-project variant covers get/create/update/update-location, keeping the value consistent across every project response.
  4. Teststests/test_project_team_size.py: distinct-user counting, zero for projects without access, per-project isolation, and empty input.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation

Testing

  1. uv run ruff check . and uv run ruff format --check .
  2. uv run --group dev python -m pytest tests/test_project_team_size.py
  3. GET /api/projects and confirm each item includes team_size matching the number of distinct users granted access.

Summary by CodeRabbit

  • New Features

    • Project responses now include team_size, showing the number of distinct users with direct access to each project.
    • Team-size values are returned consistently across project list, create, view, update, and relocation.
  • Bug Fixes

    • Projects with no user access correctly report team_size as 0.
    • Team counts are distinct per user and correctly isolated across multiple projects.
  • Tests

    • Added coverage for team-size counting, including empty input and multi-project scenarios.

levigtri added 2 commits July 4, 2026 20:32
Add a computed team_size to ProjectResponse so the map popup can show how
many people work on each project. team_size counts distinct users with
direct access (project_user_access) via a single grouped query, avoiding
N+1 lookups. No migration needed.
Build the counts dict with explicit key/value indexing so mypy can infer
dict[str, int] from Row results while keeping ruff C416 happy.
@levigtri levigtri self-assigned this Jul 15, 2026
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Project responses now expose team_size, populated by a grouped distinct-user count service. Project creation, retrieval, listing, and update routes use shared serialization helpers to attach the computed value.

Changes

Project team-size enrichment

Layer / File(s) Summary
Team-size counting service
app/services/project/count_project_team_sizes.py, app/services/project/__init__.py, tests/test_project_team_size.py
Adds grouped distinct-user counting with zero defaults, exports the service helper, and tests empty, isolated, duplicate, and no-access cases.
Project response serialization
app/models/project.py, app/services/project/serialize_project_responses.py, app/api/projects/core.py, app/services/project/__init__.py
Adds ProjectResponse.team_size, centralizes project serialization with computed counts, exports the serialization helpers, and applies them across project list, create, get, and update responses.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding a team_size field to project responses.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch levigft/obt-216-us-91-the-project-data-to-include-the-team-size

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@app/api/projects/core.py`:
- Line 10: Move _serialize_projects and _serialize_project from the projects
router into a service-owned serializer, including team-size count-query
orchestration and ProjectResponse construction. Update the router handlers to
only validate input, delegate to services, and map HTTP responses; remove the
SQLAlchemy Project import and all direct model creation or orchestration from
app/api/projects/core.py.
🪄 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: Organization UI

Review profile: QUIET

Plan: Pro Plus

Run ID: 2f541764-0d57-4958-b309-c61f6ed0d4c1

📥 Commits

Reviewing files that changed from the base of the PR and between d669a56 and 7c1e04a.

📒 Files selected for processing (5)
  • app/api/projects/core.py
  • app/models/project.py
  • app/services/project/__init__.py
  • app/services/project/count_project_team_sizes.py
  • tests/test_project_team_size.py

Comment thread app/api/projects/core.py Outdated
levigtri and others added 2 commits July 15, 2026 22:00
@levigtri

Copy link
Copy Markdown
Member Author

@coderabbitai — decision on this review:

app/api/projects/core.py (move _serialize_projects / _serialize_project out of the router into a service-owned serializer):
Done. Added app/services/project/serialize_project_responses.py (serialize_projects / serialize_project), exported it from the project service, and updated the router to delegate. The SQLAlchemy Project import and the in-router serialization/orchestration are gone. This matches the existing enrich_* service pattern used elsewhere for response enrichment that needs an extra query (here, the team-size count).

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd . app/services/project | grep -i serialize
echo "---"
cat app/services/project/serialize_project_responses.py 2>/dev/null
echo "---init---"
cat app/services/project/__init__.py
echo "---core.py---"
cat app/api/projects/core.py

Length of output: 6696


@levigtri Confirmed — I checked app/services/project/serialize_project_responses.py, the updated __init__.py exports, and app/api/projects/core.py. The router now delegates cleanly to project_service.serialize_project/serialize_projects, the Project import and in-router orchestration are gone, and this lines up nicely with the existing enrich_* service pattern. Looks good to me.

(=^・ω・^=)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@app/services/project/serialize_project_responses.py`:
- Around line 8-18: Add concise docstrings to the public functions
serialize_projects and serialize_project describing that they serialize projects
into responses and enrich each response with the computed team_size.
🪄 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: Organization UI

Review profile: QUIET

Plan: Pro Plus

Run ID: c0ccf943-c74d-46f6-adcb-ef22bbb621ee

📥 Commits

Reviewing files that changed from the base of the PR and between 7c1e04a and 46221ce.

📒 Files selected for processing (4)
  • app/api/projects/core.py
  • app/services/project/__init__.py
  • app/services/project/count_project_team_sizes.py
  • app/services/project/serialize_project_responses.py
💤 Files with no reviewable changes (1)
  • app/services/project/count_project_team_sizes.py

Comment on lines +8 to +18
async def serialize_projects(db: AsyncSession, projects: list[Project]) -> list[ProjectResponse]:
team_sizes = await count_project_team_sizes(db, [p.id for p in projects])
return [
ProjectResponse.model_validate(p).model_copy(update={"team_size": team_sizes.get(p.id, 0)})
for p in projects
]


async def serialize_project(db: AsyncSession, project: Project) -> ProjectResponse:
(response,) = await serialize_projects(db, [project])
return response

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add concise docstrings to the exported service functions.

serialize_projects and serialize_project are public service functions and should document the response-enrichment contract, including the computed team_size.

As per coding guidelines, app/services/**/*.py requires concise docstrings on public service functions.

Suggested documentation
 async def serialize_projects(db: AsyncSession, projects: list[Project]) -> list[ProjectResponse]:
+    """Serialize projects and attach distinct direct-access team sizes."""
     team_sizes = await count_project_team_sizes(db, [p.id for p in projects])

 async def serialize_project(db: AsyncSession, project: Project) -> ProjectResponse:
+    """Serialize one project and attach its distinct direct-access team size."""
     (response,) = await serialize_projects(db, [project])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def serialize_projects(db: AsyncSession, projects: list[Project]) -> list[ProjectResponse]:
team_sizes = await count_project_team_sizes(db, [p.id for p in projects])
return [
ProjectResponse.model_validate(p).model_copy(update={"team_size": team_sizes.get(p.id, 0)})
for p in projects
]
async def serialize_project(db: AsyncSession, project: Project) -> ProjectResponse:
(response,) = await serialize_projects(db, [project])
return response
async def serialize_projects(db: AsyncSession, projects: list[Project]) -> list[ProjectResponse]:
"""Serialize projects and attach distinct direct-access team sizes."""
team_sizes = await count_project_team_sizes(db, [p.id for p in projects])
return [
ProjectResponse.model_validate(p).model_copy(update={"team_size": team_sizes.get(p.id, 0)})
for p in projects
]
async def serialize_project(db: AsyncSession, project: Project) -> ProjectResponse:
"""Serialize one project and attach its distinct direct-access team size."""
(response,) = await serialize_projects(db, [project])
return response
🤖 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 `@app/services/project/serialize_project_responses.py` around lines 8 - 18, Add
concise docstrings to the public functions serialize_projects and
serialize_project describing that they serialize projects into responses and
enrich each response with the computed team_size.

Source: Coding guidelines

@levigtri
levigtri requested review from joaocarvoli and removed request for joaocarvoli July 16, 2026 01:05

@joaocarvoli joaocarvoli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

some comments

from app.services.project.count_project_team_sizes import count_project_team_sizes


async def serialize_projects(db: AsyncSession, projects: list[Project]) -> list[ProjectResponse]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Every other file here has one public function named after the file (create_project.py, count_project_team_sizes.py, and so on). This one has two and the name matches neither. Please verify that on the folder and if it is really the majority, split it and adapt the __init__.

async def serialize_projects(db: AsyncSession, projects: list[Project]) -> list[ProjectResponse]:
team_sizes = await count_project_team_sizes(db, [p.id for p in projects])
return [
ProjectResponse.model_validate(p).model_copy(update={"team_size": team_sizes.get(p.id, 0)})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

⚠️ setting the field with a string key. model_copy(update=...) does not validate, and team_size has = 0 as default on the schema, so a rename/typo on this key returns 0 silently instead of failing. For me it is relevant to keep this typed — could you explore the feasibility of it?


@pytest.mark.asyncio
async def test_count_team_sizes_empty_input(db_session) -> None:
assert await project_service.count_project_team_sizes(db_session, []) == {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

All the tests are on count_project_team_sizes, but what the 5 endpoints call now is serialize_projects, and the team_size merge itself has no test. Could you add one here? @levigtri

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