Skip to content

Discord: Automate Stat Messages - #1012

Open
Prajna1999 wants to merge 18 commits into
mainfrom
feat/automate-stats-messages-basic-queries
Open

Discord: Automate Stat Messages#1012
Prajna1999 wants to merge 18 commits into
mainfrom
feat/automate-stats-messages-basic-queries

Conversation

@Prajna1999

@Prajna1999 Prajna1999 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Issue

Closes #825

Summary

Automates a daily platform stats digest to a Discord channel via webhook. Adds /cron/daily-stats that runs a set of per-organization SQL rollups (LLM call counts, total tokens by model, modality mix, job types, evaluation runs, STT/TTS results, assessments), formats them as compact plain-text tables, chunks past Discord's 2000-char limit, and posts fire-and-forget so a webhook outage never breaks the cron.

Query/format/post logic lives in
app/services/stats.py; the route is a thin orchestrator. Window size is hardcoded to 7day and 24 hours. Every morning 9 AM, the cron job is triggered that executes the count queries

The daily message in discord would look like this
`
Daily Stats · last 24h and 7d (UTC)

LLM Calls

organization      project             calls_24h  calls_7d
Project Tech4dev  Glific                      0       387
Project Tech4dev  Kaapi                       0        18
vignesh-devtest   langfuse                    0         7
Manav Vikas       Glific AI Acceler…          0         2
ProjectTech4Dev   Judge Metrics               0         2

LLM Tokens

organization      project             model               tokens_24h  tokens_7d
Project Tech4dev  Glific              gpt-4.1                      0     89,271
ProjectTech4Dev   Judge Metrics       gpt-4o-mini                  0     17,075
Project Tech4dev  Glific              gemini-3.1-flash-…           0     12,248
Project Tech4dev  Glific              gemini-2.5-flash-…           0      9,685
Project Tech4dev  Glific              gpt-4o                       0      2,120
Project Tech4dev  Kaapi               gpt-4o-mini                  0      1,739
vignesh-devtest   langfuse            gemini-3.1-flash-…           0        380
Manav Vikas       Glific AI Acceler…  gpt-4o                       0        350
Project Tech4dev  Glific              gpt-5                        0        137

LLM Modality

organization      project             modality  calls_24h  calls_7d
Manav Vikas       Glific AI Acceler…  TEXT              0         2
Project Tech4dev  Glific              TEXT              0        31
Project Tech4dev  Glific              TTS               0       356
Project Tech4dev  Kaapi               TEXT              0        18
ProjectTech4Dev   Judge Metrics       TEXT              0         2
vignesh-devtest   langfuse            TTS               0         7

Jobs by Type

organization      project             job_type            jobs_24h  jobs_7d
Manav Vikas       Glific AI Acceler…  LLM_API                    0        2
Project Tech4dev  Glific              LLM_API                    0      389
Project Tech4dev  Kaapi               LLM_API                    0       18
ProjectTech4Dev   Judge Metrics       LLM_API                    0        2
ProjectTech4Dev   Judge Metrics       PROMPT_IMPROVEMENT         0        2
vignesh-devtest   langfuse            LLM_API                    0        7

Evaluation Runs

organization     project        count_24h  count_7d
ProjectTech4Dev  Judge Metrics          0        19

STT Results
no data

TTS Results
no data

Assessments
no data
`

Checklist

Before submitting a pull request, please ensure that you mark these task.

  • Ran fastapi run --reload app/main.py or docker compose up in the repository root and test.
  • If you've fixed a bug or added code that is tested and has test cases.

Summary by CodeRabbit

New Features

  • Added automated daily statistics covering usage, jobs, evaluations, speech, and assessments.
  • Added a protected endpoint for generating daily statistics reports.
  • Added optional Discord delivery with formatted tables, message chunking, and graceful failure handling.
  • Added scheduled daily execution at 09:00 UTC.

Tests

  • Added coverage for report formatting, empty results, message limits, missing webhook configuration, and delivery errors.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • ready-for-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 84a5d2c7-4920-43df-bb5d-8ad77d2739ff

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

Adds daily statistics aggregation, Discord formatting and delivery, webhook configuration, a monitored protected cron endpoint, periodic invoker wiring, and tests.

Changes

Daily statistics pipeline

Layer / File(s) Summary
Statistics aggregation
backend/app/crud/stats.py, backend/app/tests/crud/test_stats.py
Adds eight SQL aggregation queries for LLM, token, modality, job, evaluation, STT, TTS, and assessment statistics across 24-hour and 7-day windows. Tests validate query execution and returned sections.
Stats rendering and Discord delivery
backend/app/services/stats.py, backend/app/core/config.py, backend/app/tests/services/test_stats.py
Adds optional webhook configuration, bounded Markdown table formatting, Discord message chunking, failure handling, and service tests.
Scheduled cron endpoint
backend/app/api/routes/cron.py, scripts/python/invoke-cron.py, backend/app/tests/api/routes/test_cron.py, .claude/agents/senior-engineer.md
Adds a monitored SUPERUSER-protected daily statistics route, periodic cron invocation, endpoint tests, and loop-style guidance.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CronInvoker
  participant daily_stats_cron_job
  participant get_daily_stats
  participant format_sections
  participant post_to_discord
  participant Discord
  CronInvoker->>daily_stats_cron_job: GET /cron/daily-stats
  daily_stats_cron_job->>get_daily_stats: retrieve statistics
  get_daily_stats-->>daily_stats_cron_job: categorized statistics
  daily_stats_cron_job->>format_sections: format sections
  format_sections-->>daily_stats_cron_job: Markdown sections
  daily_stats_cron_job->>post_to_discord: post sections
  post_to_discord->>Discord: POST webhook chunks
Loading

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: akhileshnegi

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The feature changes are in scope, but the added agent style guidance is unrelated to issue [#825]. Remove the unrelated .claude/agents/senior-engineer.md guidance change or move it to a separate pull request.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: automated Discord statistics messages.
Linked Issues check ✅ Passed The changes implement scheduled Discord statistics messages with traffic, performance, success, and failure data for issue [#825].
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 feat/automate-stats-messages-basic-queries

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.

@github-actions github-actions Bot changed the title feat: basic stats queries feat(stats): Implement basic stats queries Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

OpenAPI changes   ⚪ No API surface changes

Note

This PR does not modify the API contract.

main314a5d9d · generated by oasdiff

@Prajna1999 Prajna1999 self-assigned this Jul 7, 2026
@Prajna1999 Prajna1999 added the enhancement New feature or request label Jul 7, 2026
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Prajna1999 Prajna1999 linked an issue Jul 10, 2026 that may be closed by this pull request
@Prajna1999 Prajna1999 changed the title feat(stats): Implement basic stats queries Discord: Automate Stat Messages Jul 10, 2026

@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: 5

🧹 Nitpick comments (3)
backend/app/tests/services/test_stats.py (1)

134-134: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silence Ruff unused-argument warnings in mock helpers.

Ruff flags unused url, timeout (and json in flaky_post) in the mock callables. Use *_args, **_kwargs to match the requests.post signature without naming unused parameters.

♻️ Proposed fix
-    def fake_post(url, json, timeout):
+    def fake_post(*_args, **kwargs):
-        posted.append(json["content"])
+        posted.append(kwargs["json"]["content"])
-    def flaky_post(url, json, timeout):
+    def flaky_post(*_args, **_kwargs):
         calls["n"] += 1
         raise requests.ConnectionError("nope")

Also applies to: 161-161

🤖 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 `@backend/app/tests/services/test_stats.py` at line 134, Update the mock
helpers fake_post and flaky_post in the stats tests to accept unused positional
and keyword arguments via *_args and **_kwargs instead of naming unused request
parameters, while preserving each helper’s existing behavior.

Source: Linters/SAST tools

backend/app/crud/stats.py (1)

76-88: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

SQL identifier interpolation via f-string is a fragile pattern.

_org_count_sql interpolates table directly into the SQL string. All current callers pass hardcoded literals, so there is no immediate injection risk, but this pattern will silently allow SQL injection if a future caller passes dynamic input. Consider adding a whitelist assertion or a comment documenting the constraint.

🛡️ Proposed guard
 def _org_count_sql(table: str) -> TextClause:
+    # table must be a hardcoded literal — never user input
+    assert table.isidentifier(), f"Invalid table name: {table}"
     return text(
         f"""
🤖 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 `@backend/app/crud/stats.py` around lines 76 - 88, Protect the table identifier
interpolated by `_org_count_sql` from future dynamic input. Add an explicit
whitelist validation for the supported table names before constructing the SQL,
or document and enforce that callers may only provide trusted constants; raise
an appropriate error for unsupported values.
backend/app/api/routes/cron.py (1)

151-163: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use consistent logging format.

Line 151 and 162 use f-strings while line 156 uses %-style interpolation. Prefer %-style for logging to enable lazy evaluation, or at minimum be consistent within the same function.

♻️ Suggested consistency fix (f-string → %-style)
 def daily_stats_cron_job(session: SessionDep, hours: int | None = None) -> dict[str, Any]:
-    logger.info(f"[daily_stats_cron_job] Cron job invoked | hours={hours}")
+    logger.info("[daily_stats_cron_job] Cron job invoked | hours=%s", hours)
     try:
         result = collect_daily_stats(session=session, window_hours=hours)
         post_daily_stats_to_discord(format_daily_stats_message(result))
         logger.info(
             "[daily_stats_cron_job] Completed | window: %s",
             result["window"],
         )
         return result
     except Exception as e:
         logger.error(
-            f"[daily_stats_cron_job] Error executing cron job: {e}",
+            "[daily_stats_cron_job] Error executing cron job: %s",
+            e,
             exc_info=True,
         )
🤖 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 `@backend/app/api/routes/cron.py` around lines 151 - 163, Use consistent lazy
%-style logging in the daily_stats_cron_job function: replace the f-string
messages in the invocation and exception logger calls with format strings and
separate arguments, matching the existing completion log.
🤖 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 `@backend/app/api/routes/cron.py`:
- Line 150: Update the return annotation of daily_stats_cron_job from dict to
dict[str, Any], matching collect_daily_stats and the project’s requirement for
parameterized, specific return types; ensure Any is imported if needed.

In `@backend/app/crud/stats.py`:
- Line 76: Replace the Any annotations in _org_count_sql and _rows with
sqlalchemy.TextClause, importing TextClause as needed; keep the existing text()
SQL construction unchanged.

In `@backend/app/services/stats.py`:
- Around line 22-25: In the window calculation within the stats function,
replace the truthiness check on window_hours with an explicit None check so a
value of 0 produces a zero-duration window while only omitted values use
DAILY_WINDOW.
- Around line 120-131: Update _chunk_message to split any individual section
that exceeds _DISCORD_CHUNK_LIMIT into smaller newline-based chunks before or
during normal paragraph chunking, ensuring every returned chunk stays within the
limit. Preserve section content and ordering so post_daily_stats_to_discord can
send the complete digest without oversized Discord messages.

In `@scripts/python/invoke-cron.py`:
- Line 21: The ENDPOINTS list in invoke-cron.py incorrectly includes
/api/v1/cron/daily-stats in the shared five-minute loop, causing repeated daily
digests. Remove it from the shared list and invoke it through a separate daily
scheduler or guard it with a daily-only condition consistent with the configured
0 0 * * * schedule.

---

Nitpick comments:
In `@backend/app/api/routes/cron.py`:
- Around line 151-163: Use consistent lazy %-style logging in the
daily_stats_cron_job function: replace the f-string messages in the invocation
and exception logger calls with format strings and separate arguments, matching
the existing completion log.

In `@backend/app/crud/stats.py`:
- Around line 76-88: Protect the table identifier interpolated by
`_org_count_sql` from future dynamic input. Add an explicit whitelist validation
for the supported table names before constructing the SQL, or document and
enforce that callers may only provide trusted constants; raise an appropriate
error for unsupported values.

In `@backend/app/tests/services/test_stats.py`:
- Line 134: Update the mock helpers fake_post and flaky_post in the stats tests
to accept unused positional and keyword arguments via *_args and **_kwargs
instead of naming unused request parameters, while preserving each helper’s
existing behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1695d50d-6e21-4a4d-848b-95ea5e43a0ae

📥 Commits

Reviewing files that changed from the base of the PR and between 102b61c and bcc57ea.

📒 Files selected for processing (6)
  • backend/app/api/routes/cron.py
  • backend/app/core/config.py
  • backend/app/crud/stats.py
  • backend/app/services/stats.py
  • backend/app/tests/services/test_stats.py
  • scripts/python/invoke-cron.py

Comment thread backend/app/api/routes/cron.py Outdated
Comment thread backend/app/crud/stats.py Outdated
)


def _org_count_sql(table: str) -> Any:

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 | 🟡 Minor | ⚡ Quick win

Narrow Any type hints to TextClause.

_org_count_sql returns -> Any and _rows accepts stmt: Any, but both can be narrowed to sqlalchemy.TextClause since text() always returns that type. As per coding guidelines, -> Any is not acceptable unless the type cannot be narrowed.

♻️ Proposed fix
-from sqlalchemy import text
+from sqlalchemy import TextClause, text

-def _org_count_sql(table: str) -> Any:
+def _org_count_sql(table: str) -> TextClause:

-def _rows(session: Session, stmt: Any, params: dict[str, Any]) -> list[dict[str, Any]]:
+def _rows(session: Session, stmt: TextClause, params: dict[str, Any]) -> list[dict[str, Any]]:

Also applies to: 97-97

🤖 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 `@backend/app/crud/stats.py` at line 76, Replace the Any annotations in
_org_count_sql and _rows with sqlalchemy.TextClause, importing TextClause as
needed; keep the existing text() SQL construction unchanged.

Source: Coding guidelines

Comment thread backend/app/services/stats.py Outdated
Comment thread backend/app/services/stats.py Outdated
Comment thread scripts/python/invoke-cron.py
@AkhileshNegi
AkhileshNegi requested review from Ayush8923 and removed request for AkhileshNegi and vprashrex July 14, 2026 07:30
@Prajna1999
Prajna1999 requested a review from AkhileshNegi July 16, 2026 05:45
Comment thread backend/app/crud/stats.py Outdated
)


def _org_count_sql(table: str) -> Any:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we can avoid the any type here. This is good suggestion given by coderabbit https://github.com/ProjectTech4DevAI/kaapi-backend/pull/1012/changes#r3558059640. Please handle it.

Comment thread backend/app/services/stats.py Outdated
Comment on lines +36 to +41
def section_counts(result: dict[str, Any]) -> dict[str, int]:
return {
section: len(rows)
for section, rows in result["stats"].items()
if isinstance(rows, list)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this function is not used anywhere, if this not needed please remove this dead code.

Comment thread backend/app/services/stats.py Outdated

logger = logging.getLogger(__name__)

DAILY_WINDOW = timedelta(hours=168)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this timedelta(hours=168) is 7 days, not daily. as I can see this cron(here) run daily but posts a rolling 7-day window every run → ~85% of each day's report overlaps yesterday's. Intended? If yes, rename to DEFAULT_STATS_WINDOW / WEEKLY_WINDOW so the daily name doesn't mislead. If the intent was true daily deltas, then I think window should be 24h.

Comment thread backend/app/services/stats.py Outdated
@Ayush8923 Ayush8923 added reviewed and removed enhancement New feature or request ready-for-review labels Jul 23, 2026

@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: 3

🤖 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 `@backend/app/api/routes/cron.py`:
- Around line 148-151: Restore the optional hours override across the
daily-stats flow: in backend/app/api/routes/cron.py lines 148-151, validate the
hours query parameter and pass it through daily_stats_cron_job to
get_daily_stats and the CRUD boundary; in backend/app/crud/stats.py lines
10-132, use named window parameters while preserving the default 168-hour
behavior and omit *_7d fields for other windows; in
backend/app/services/stats.py lines 67-74, format labels using the selected
window, defaulting to 7d.

In `@backend/app/services/stats.py`:
- Around line 77-81: Update _post to call raise_for_status() on the
requests.post response, catch failures, and log with the [_post] prefix while
recording only the exception type rather than interpolating the exception value.
Update the successful fake response mock to implement raise_for_status(), and
add coverage confirming non-success responses are handled as failures.

In `@backend/app/tests/services/test_stats.py`:
- Around line 9-67: Update _sample_stats to return a concrete mapping type
matching the fixture structure, and add explicit parameter and return
annotations to each test function in this diff. Annotate fake_post’s parameters
and return value as well, using leading-underscore names for arguments whose
values are intentionally ignored.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d9913fd-78fe-4981-b9fe-7468ece8d36c

📥 Commits

Reviewing files that changed from the base of the PR and between ff8dd33 and ac11c46.

📒 Files selected for processing (5)
  • backend/app/api/routes/cron.py
  • backend/app/core/config.py
  • backend/app/crud/stats.py
  • backend/app/services/stats.py
  • backend/app/tests/services/test_stats.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/core/config.py

Comment thread backend/app/api/routes/cron.py
Comment thread backend/app/services/stats.py Outdated
Comment on lines +9 to +67
def _sample_stats() -> dict:
return {
"LLM Calls": [
{
"organization": "Acme",
"project": "Alpha",
"calls_24h": 3,
"calls_7d": 15,
},
],
"STT Results": [],
}


def test_format_sections_renders_bold_title_and_aligned_table():
sections = format_sections(_sample_stats())
llm_section = next(s for s in sections if s.startswith("**LLM Calls**"))
assert "organization project calls_24h calls_7d" in llm_section
assert "Acme Alpha 3 15" in llm_section
assert llm_section.count("```") == 2 # wrapped in one code block


def test_format_sections_marks_empty_sections():
sections = format_sections(_sample_stats())
stt_section = next(s for s in sections if s.startswith("**STT Results**"))
assert stt_section == "**STT Results**\n_no data_"


def test_post_to_discord_noop_when_webhook_unset():
with patch.object(stats_mod.settings, "DISCORD_STATS_WEBHOOK_URL", None), patch(
"app.services.stats.requests.post"
) as mock_post:
post_to_discord(["anything"])
mock_post.assert_not_called()


def test_post_to_discord_packs_sections_under_size_limit():
posted: list[str] = []

def fake_post(url, json, timeout):
posted.append(json["content"])

big_sections = ["x" * 1000 for _ in range(4)]
with patch.object(
stats_mod.settings, "DISCORD_STATS_WEBHOOK_URL", "https://x/hook"
), patch("app.services.stats.requests.post", side_effect=fake_post):
post_to_discord(big_sections)
assert len(posted) >= 2 # split into multiple messages
assert all(len(content) <= 2000 for content in posted)


def test_post_to_discord_swallows_request_exception():
with patch.object(
stats_mod.settings, "DISCORD_STATS_WEBHOOK_URL", "https://x/hook"
), patch(
"app.services.stats.requests.post",
side_effect=requests.ConnectionError("boom"),
):
post_to_discord(["hello"]) # must not raise

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n '^\s*def ' backend/app/tests/services/test_stats.py

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 538


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import ast

path = Path("backend/app/tests/services/test_stats.py")
src = path.read_text()
tree = ast.parse(src)

issues = []
for i, node in enumerate(tree.body, 1):
    if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
        if node.name == "fake_post":
            return_annot = ast.unparse(node.returns) if node.returns else "<missing>"
            args = [(arg.arg, ast.unparse(arg.annotation) if arg.annotation else "<missing>") for arg in node.args.args]
            issues.append(f"fake_post: return={return_annot}, args={args}")
        elif node.name == "_sample_stats":
            if node.returns and isinstance(node.returns, ast.Name) and node.returns.id == "dict":
                issues.append(f"{node.name}: generic return value dict instead of concrete type")
            else:
                issues.append(f"{node.name}: no return-annotation issue")
        else:
            if not node.returns:
                issues.append(f"{node.name}: no return annotation")

print("\n".join(issues))
PY

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 591


Add narrow annotations to the test helper and test functions.

_sample_stats uses the generic dict return type. All test functions and the nested fake_post stub lack parameter and return annotations. Use a concrete fixture mapping type and annotate ignored stub arguments with leading underscores.

🧰 Tools
🪛 ast-grep (0.45.0)

[info] 37-39: no timeout was given on call to external resource
Context: patch(
"app.services.stats.requests.post"
)
Note: [CWE-1088] Synchronous Access of Remote Resource without Timeout.

(requests-timeout)


[info] 53-53: no timeout was given on call to external resource
Context: patch("app.services.stats.requests.post", side_effect=fake_post)
Note: [CWE-1088] Synchronous Access of Remote Resource without Timeout.

(requests-timeout)


[info] 62-65: no timeout was given on call to external resource
Context: patch(
"app.services.stats.requests.post",
side_effect=requests.ConnectionError("boom"),
)
Note: [CWE-1088] Synchronous Access of Remote Resource without Timeout.

(requests-timeout)

🪛 Ruff (0.16.0)

[warning] 48-48: Unused function argument: url

(ARG001)


[warning] 48-48: Unused function argument: timeout

(ARG001)

🤖 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 `@backend/app/tests/services/test_stats.py` around lines 9 - 67, Update
_sample_stats to return a concrete mapping type matching the fixture structure,
and add explicit parameter and return annotations to each test function in this
diff. Annotate fake_post’s parameters and return value as well, using
leading-underscore names for arguments whose values are intentionally ignored.

Sources: Coding guidelines, Linters/SAST tools

@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: 2

🧹 Nitpick comments (2)
.claude/agents/senior-engineer.md (1)

49-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not require loop unrolling.

Loop unrolling duplicates statements and fixes behavior to a specific item count. It reduces maintainability when the item count changes.

Replace “unroll loops” with “prefer explicit multi-line loops when they improve clarity.” Also change “loops that involves” to “loops that involve.”

🤖 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 @.claude/agents/senior-engineer.md around lines 49 - 50, Update the Coding
Style guidance in the senior-engineer instructions to remove the requirement to
unroll loops and instead prefer explicit multi-line loops only when they improve
clarity; also correct “loops that involves” to “loops that involve,” while
preserving the existing guidance against nested or complex one-liner loops.
backend/app/tests/services/test_stats.py (1)

68-74: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert that packing retains every section.

The test checks message count and size only. An implementation that drops a section can still pass. Use unique section values and assert that every input section appears in the combined posted content.

Proposed test change
-big_sections = ["x" * 1000 for _ in range(4)]
+big_sections = [f"section-{section_index}: " + "x" * 990 for section_index in range(4)]
 ...
 assert len(posted) >= 2
 assert all(len(content) <= 2000 for content in posted)
+combined_content = "\n".join(posted)
+assert all(section in combined_content for section in big_sections)
🤖 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 `@backend/app/tests/services/test_stats.py` around lines 68 - 74, Update the
test around post_to_discord to use distinct section values and verify that the
combined content of all entries in posted contains every input section, while
retaining the existing message-count and 2000-character size assertions.
🤖 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 `@backend/app/tests/api/routes/test_cron.py`:
- Around line 277-298: Add an optional, validated hours query parameter to
daily_stats_cron_job, propagate it through the daily-stats service into
get_daily_stats and the CRUD layer, and update those contracts to apply the
requested reporting window while preserving the default behavior. Extend
test_daily_stats_cron_job_success with a /cron/daily-stats?hours=48 request and
assert the value is forwarded through the call chain.

In `@backend/app/tests/crud/test_stats.py`:
- Line 17: Add the narrow return annotation -> None to the
test_get_daily_stats_runs_every_section_and_maps_rows function definition,
leaving its existing test behavior unchanged.

---

Nitpick comments:
In @.claude/agents/senior-engineer.md:
- Around line 49-50: Update the Coding Style guidance in the senior-engineer
instructions to remove the requirement to unroll loops and instead prefer
explicit multi-line loops only when they improve clarity; also correct “loops
that involves” to “loops that involve,” while preserving the existing guidance
against nested or complex one-liner loops.

In `@backend/app/tests/services/test_stats.py`:
- Around line 68-74: Update the test around post_to_discord to use distinct
section values and verify that the combined content of all entries in posted
contains every input section, while retaining the existing message-count and
2000-character size assertions.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c73471ae-3954-4aaf-b5ad-83be1e5ff44a

📥 Commits

Reviewing files that changed from the base of the PR and between ac11c46 and 24407b9.

📒 Files selected for processing (5)
  • .claude/agents/senior-engineer.md
  • backend/app/services/stats.py
  • backend/app/tests/api/routes/test_cron.py
  • backend/app/tests/crud/test_stats.py
  • backend/app/tests/services/test_stats.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/services/stats.py

Comment thread backend/app/tests/api/routes/test_cron.py
Comment thread backend/app/tests/crud/test_stats.py
@Ayush8923

Copy link
Copy Markdown
Collaborator

@Prajna1999 what does the UI look like for the message being sent to Disocrd? Is this latest UI?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please check this comment: #1012 (comment)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Discord: Automate stats messages

2 participants