Skip to content

feat: add analytics trends and budget forecast API endpoints#798

Merged
Aureliolo merged 10 commits intomainfrom
feat/771-analytics-trends
Mar 24, 2026
Merged

feat: add analytics trends and budget forecast API endpoints#798
Aureliolo merged 10 commits intomainfrom
feat/771-analytics-trends

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Add GET /api/v1/analytics/trends -- time-series sparkline data (tasks_completed, spend, active_agents, success_rate) with period param (7d/30d/90d), bucketed by day or hour
  • Add GET /api/v1/analytics/forecast -- budget spend projection with horizon_days param, daily projections array, days_until_exhausted (ceiling-rounded), confidence score
  • Extend GET /api/v1/analytics/overview -- add budget_remaining_usd, budget_used_percent, cost_7d_trend sparkline, active_agents_count, idle_agents_count
  • Pure trend bucketing and forecast functions in budget/trends.py (follows category_analytics.py pattern)
  • CostTracker gains public budget_config property
  • New analytics event constants in observability/events/analytics.py
  • 57 new unit tests (35 pure function + 22 endpoint)

Review coverage

Pre-reviewed by 7 specialized agents (code-reviewer, python-reviewer, test-analyzer, conventions-enforcer, issue-verifier, async-reviewer, docs-checker). 17 findings addressed:

  • Refactored 4 functions exceeding 50-line limit into extracted helpers
  • Eliminated duplicated _generate_bucket_starts (now public in trends.py)
  • Extracted shared _resolve_budget_context helper for overview + forecast
  • Added logging on all graceful degradation paths (ServiceUnavailableError, budget fallback)
  • Fixed days_until_exhausted to use math.ceil (budget survives N full days)
  • Added now param to project_daily_spend for deterministic testing
  • Used StrEnum types in response models instead of raw strings
  • Added max(0) guard on idle agent count for TOCTOU safety
  • Updated docs/design/operations.md (event count 52->53, expanded analytics API row)

Test plan

  • uv run python -m pytest tests/unit/budget/test_trends.py -m unit -n auto -- 35 pure function tests
  • uv run python -m pytest tests/unit/api/controllers/test_analytics.py -m unit -n auto -- 22 endpoint tests
  • uv run ruff check src/ tests/ -- clean
  • uv run mypy src/ tests/ -- clean
  • uv run pre-commit run --all-files -- all hooks pass
  • Full unit test suite (10327 pass)
  • OpenAPI schema exported

Closes #771

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 23, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 43bc6f9.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d18ac5b8-89db-480b-84b1-cc0d65ae75d3

📥 Commits

Reviewing files that changed from the base of the PR and between f4c3933 and 43bc6f9.

📒 Files selected for processing (1)
  • src/synthorg/api/controllers/analytics.py
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) for multiple exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14
All public functions and classes must have type hints; mypy strict mode is enforced
All public classes and functions must have Google-style docstrings; enforced by ruff D rules
Use immutability patterns: create new objects instead of mutating existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement. For dict/list fields in frozen Pydantic models, use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)
Use frozen Pydantic models (v2) for config/identity; use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 BaseModel, model_validator, computed_field, and ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task
Functions must be < 50 lines; files must be < 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries (user input, external APIs, config files)
Line length: 88 characters (enforced by ruff)

Files:

  • src/synthorg/api/controllers/analytics.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import logger: from synthorg.observability import get_logger then logger = get_logger(__name__); never use import logging or print() (exceptions: observability/setup.py and observability/sinks.py may use stdlib logging and print(..., file=sys.stderr) for bootstrap code)
Always use structured logging with logger name as logger (not _logger or log). Use event name constants from synthorg.observability.events.<domain> modules (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT. Always log as logger.info(EVENT, key=value) -- never logger.info("msg %s", val)
Log WARNING or ERROR on all error paths before raising exceptions. Log INFO on all state transitions. Log DEBUG for object creation, internal flow, and entry/exit of key functions
Never implement retry logic in provider driver subclasses or calling code -- it is handled automatically by BaseCompletionProvider base class. RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig. Retryable errors: RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately. RetryExhaustedError signals all retries failed -- catch at engine layer to trigger fallback chains. Rate limiter respects RateLimitError.retry_after

Files:

  • src/synthorg/api/controllers/analytics.py
🧠 Learnings (16)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for multiple exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/api/controllers/analytics.py
🔇 Additional comments (12)
src/synthorg/api/controllers/analytics.py (12)

192-206: The billing-period query is now correctly bounded.

The end=end parameter ensures future-dated records are excluded from the period cost calculation.

However, the fallback path using fallback_total_cost can still produce incorrect budget metrics when the period cost query fails, as this value may not be scoped to the current billing period.


332-338: ACTIVE_AGENTS fallback still uses zero.

The call at line 334 passes 0 as the fallback, which causes all buckets to report zero active agents when the registry is unavailable. The past review comment suggesting use of configured agent count has not been addressed.


527-534: horizon_days still accepts 1-90 instead of spec-defined values.

The linked issue specifies forecasts should support 7, 14, or 30 days only, but this parameter accepts any integer from 1 to 90. This widens the public API contract beyond the specification.


54-154: LGTM!

The response models are well-structured with proper validation:

  • Frozen configuration ensures immutability
  • allow_inf_nan=False prevents invalid numeric values in API responses
  • Field constraints (ge, le) provide appropriate boundary validation

160-165: LGTM!

Clean use of NamedTuple for internal budget state representation.


217-247: LGTM!

The graceful degradation pattern is well-implemented:

  • Logs warning with context before falling back
  • Returns sensible defaults (all agents active, none idle) when registry is unavailable

250-281: LGTM!

Clean dispatch helper for task metric bucketing.


303-330: Query bounds are correctly applied.

Both SPEND and task metrics queries now properly bound their time ranges with end=now and until=now respectively. The graceful degradation for unavailable performance tracker is well-handled with logging.


341-401: LGTM!

The overview assembly is well-implemented:

  • Sparkline start is correctly aligned to produce exactly 7 daily buckets
  • Agent count fallback uses len(agents) (not zero like the trends endpoint)
  • Structured logging with event constants follows guidelines

413-468: LGTM!

The overview endpoint is well-structured:

  • Parallel queries using asyncio.TaskGroup follow the structured concurrency guideline
  • 7-day records query is now correctly bounded with end=now
  • Error handling logs warning with context before raising ServiceUnavailableError

470-521: LGTM!

The trends endpoint is cleanly implemented with proper parameter typing and structured logging.


550-584: LGTM!

The forecast endpoint is correctly implemented:

  • Records query is properly bounded with end=now
  • project_daily_spend receives now parameter for deterministic results
  • Structured logging with event constants follows guidelines

Walkthrough

Adds time-series analytics and budget forecasting endpoints and related implementation. New guarded endpoints: GET /api/v1/analytics/trends and GET /api/v1/analytics/forecast. Extends GET /api/v1/analytics/overview with budget_remaining_usd, budget_used_percent, cost_7d_trend, active_agents_count, and idle_agents_count. Introduces src/synthorg/budget/trends.py (trend enums, bucketing, forecasting), exposes CostTracker.budget_config property, adds Pydantic response models and analytics observability event constants, updates docs, and expands unit tests and fixtures.

Suggested labels

autorelease: tagged

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.92% which is insufficient. The required threshold is 40.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 pull request title accurately summarizes the main change: adding analytics trends and budget forecast API endpoints, which is the primary objective of this changeset.
Description check ✅ Passed The pull request description is detailed and directly related to the changeset, covering the new endpoints, extended overview fields, implementation details, and test coverage.
Linked Issues check ✅ Passed The pull request comprehensively addresses all coding requirements from issue #771: implements GET /trends and /forecast endpoints with specified query parameters and response fields, extends /overview with budget and agent fields, provides trending/forecast functions in budget/trends.py, exposes CostTracker.budget_config, and includes 57 unit tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue #771: analytics endpoints, budget tracking enhancements, event constants for new endpoints, documentation updates reflecting the new functionality, and comprehensive test coverage. No extraneous modifications detected.

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


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

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the system's analytical capabilities by introducing dedicated API endpoints for time-series trend analysis and budget forecasting. It also enriches the existing analytics overview with critical budget and agent activity metrics. These changes provide users with more granular and predictive insights into operational performance and resource allocation, enabling proactive management and strategic planning.

Highlights

  • New Analytics Endpoints: Added GET /api/v1/analytics/trends for time-series sparkline data (tasks_completed, spend, active_agents, success_rate) with configurable periods (7d/30d/90d) and bucketing (day/hour). Also introduced GET /api/v1/analytics/forecast for budget spend projections, providing daily projections, days until budget exhaustion, and a confidence score.
  • Extended Analytics Overview: The GET /api/v1/analytics/overview endpoint was enhanced to include budget_remaining_usd, budget_used_percent, a cost_7d_trend sparkline, and counts for active_agents_count and idle_agents_count.
  • Core Logic for Trends and Forecasts: A new module, budget/trends.py, was introduced to house pure functions for bucketing cost records and task metrics into time-series data points, and for projecting future budget spend. This follows a pattern similar to category_analytics.py.
  • CostTracker Enhancement: The CostTracker now exposes its budget_config as a public property, facilitating easier access to budget-related settings.
  • New Observability Events: New analytics event constants (ANALYTICS_FORECAST_QUERIED, ANALYTICS_OVERVIEW_QUERIED, ANALYTICS_TRENDS_QUERIED) were added to observability/events/analytics.py for better tracking.
  • Extensive Testing: 57 new unit tests were added, covering both the pure functions in budget/trends.py (35 tests) and the new API endpoints in analytics.py (22 tests), ensuring robust functionality.
  • Code Quality and Refinements: Several code quality improvements were made, including refactoring large functions into smaller helpers, eliminating duplicated logic for bucket generation, adding logging for graceful degradation, fixing days_until_exhausted calculation, and using StrEnum types in response models.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces significant new functionality by adding API endpoints for analytics trends and budget forecasting, and extending the existing overview endpoint. The changes are well-structured, with pure functions for calculations in budget/trends.py and new controller logic in analytics.py. The addition of comprehensive unit tests is also a great inclusion. My review has identified a critical syntax issue that would break the application in a Python 3 environment, along with some medium-severity suggestions to improve maintainability and robustness by removing duplicated code, improving exception handling, and clarifying mathematical operations.

async def _resolve_agent_counts(
app_state: AppState,
config_agent_count: int,
) -> tuple[int, int]:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

This except syntax is for Python 2 and will raise a SyntaxError in Python 3. To catch multiple exceptions, you should enclose them in a tuple.

This same issue is present on lines 370 and 426.

            except (MemoryError, RecursionError):

Comment on lines +208 to +209

Uses AgentRegistryService when available, falls back to
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Catching a broad Exception can hide unexpected errors and make debugging difficult. Any failure in get_total_cost will silently cause period_cost to fall back to total_cost. The exception should be logged to provide visibility into what went wrong. Consider catching more specific exceptions if possible.

A similar issue exists on lines 372-373 where the fallback is to 0.0 and there is no logging.

            except Exception:
                logger.warning("Failed to get period cost for budget calculation, falling back to total cost", exc_info=True)
                period_cost = total_cost

Comment on lines +437 to +463
),
)

@get("/forecast", guards=[require_read_access])
async def get_forecast(
self,
state: State,
horizon_days: Annotated[
int,
Parameter(
ge=1,
le=90,
description="Projection horizon in days",
),
] = 14,
) -> ApiResponse[ForecastResponse]:
"""Return budget spend projection.

Uses average daily spend over the lookback period
(equal to horizon_days) to project future spend.

Args:
state: Application state.
horizon_days: Number of days to project forward.

Returns:
Forecast data envelope.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This function is a duplicate of _generate_bucket_starts in src/synthorg/budget/trends.py. The PR description states that this duplication was to be eliminated. To adhere to the DRY principle and improve maintainability, this function should be made public in trends.py (e.g., as generate_bucket_starts) and imported for use here, rather than being reimplemented. After that, this local implementation can be removed.

"""Project future budget spend using average daily spend.

Computes the average daily spend from the provided records,
then projects forward for ``horizon_days``. Confidence is
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The PR description mentions that days_until_exhausted was fixed to use math.ceil, but the implementation uses int(), which truncates the result. This is inconsistent and could be confusing for future maintainers.

If the goal is to calculate the number of full days the budget will last, using math.floor() is more explicit and clearer than int(). If the goal is to determine on which day the budget will be exhausted, then math.ceil() should be used. Given the tests expect truncation, math.floor() seems appropriate to clarify intent.

Suggested change
then projects forward for ``horizon_days``. Confidence is
days_until = max(math.floor(budget_remaining_usd / avg_daily), 0)

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 23, 2026

Codecov Report

❌ Patch coverage is 85.62874% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.29%. Comparing base (7433548) to head (43bc6f9).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/api/controllers/analytics.py 73.03% 22 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #798      +/-   ##
==========================================
- Coverage   92.33%   92.29%   -0.04%     
==========================================
  Files         574      576       +2     
  Lines       29798    29957     +159     
  Branches     2889     2896       +7     
==========================================
+ Hits        27514    27650     +136     
- Misses       1803     1824      +21     
- Partials      481      483       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/api/controllers/analytics.py`:
- Around line 286-291: Update the endpoint/docstring that returns trend data to
explicitly state that the ACTIVE_AGENTS metric is a flat-line over the returned
buckets (it uses the current count from _resolve_agent_counts and repeats that
value for each timestamp generated by generate_bucket_starts), so consumers know
TrendDataPoint values for ACTIVE_AGENTS are not historical but a repeated
snapshot; mention the behavior in the function's docstring and in any external
API documentation where ACTIVE_AGENTS is described, referencing the
ACTIVE_AGENTS identifier, the use of _resolve_agent_counts, TrendDataPoint, and
generate_bucket_starts.

In `@src/synthorg/budget/trends.py`:
- Around line 337-363: The function _compute_daily_spend currently computes
lookback_days using (timestamps[-1].date() - timestamps[0].date()).days which is
exclusive of the end date and therefore overstates avg_daily; change the
lookback_days calculation to use an inclusive range by adding 1 (e.g.,
lookback_days = max((timestamps[-1].date() - timestamps[0].date()).days + 1, 1))
so avg_daily, confidence, and days_until_exhausted use the correct inclusive day
count; preserve the rounding and existing uses of timestamps, total_cost,
avg_daily and confidence.
- Around line 181-202: generate_bucket_starts currently floors the incoming
start via _bucket_key which silently widens the window; change the function to
make alignment explicit by adding a boolean parameter (e.g., align_start: bool)
and only floor start when align_start is True, otherwise begin iteration at the
raw start value; update the docstring to document the new parameter and its
behavior, and keep using BucketSize and _bucket_key to compute the floored start
when requested so the loop that appends current buckets remains correct.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f0047b0a-04d5-4e37-a5f7-e342406ab05a

📥 Commits

Reviewing files that changed from the base of the PR and between 7433548 and b8a895b.

📒 Files selected for processing (9)
  • docs/design/operations.md
  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/budget/trends.py
  • src/synthorg/observability/events/analytics.py
  • tests/unit/api/conftest.py
  • tests/unit/api/controllers/test_analytics.py
  • tests/unit/budget/test_trends.py
  • tests/unit/observability/test_events.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
🧰 Additional context used
📓 Path-based instructions (7)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python files -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax without parentheses (PEP 758 except syntax) in exception handlers
All public functions and methods must have type hints; enforce strict mypy mode
Use Google-style docstrings on all public classes and functions; enforced by ruff D rules

Files:

  • tests/unit/observability/test_events.py
  • src/synthorg/budget/tracker.py
  • tests/unit/api/conftest.py
  • src/synthorg/observability/events/analytics.py
  • tests/unit/api/controllers/test_analytics.py
  • tests/unit/budget/test_trends.py
  • src/synthorg/budget/trends.py
  • src/synthorg/api/controllers/analytics.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow markers on all tests
Maintain 80% minimum code coverage (enforced in CI)
Use asyncio_mode = 'auto' -- no manual @pytest.mark.asyncio needed on async tests
Global test timeout is 30 seconds per test; do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed
ALWAYS run pytest with -n auto for parallelism via pytest-xdist; never run tests sequentially
Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use Hypothesis for Python property-based testing with @given + @settings decorators; use profiles: ci (50 examples, default) and dev (1000 examples) controlled via HYPOTHESIS_PROFILE env var
Run Hypothesis dev profile with: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number)
NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally

Files:

  • tests/unit/observability/test_events.py
  • tests/unit/api/conftest.py
  • tests/unit/api/controllers/test_analytics.py
  • tests/unit/budget/test_trends.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import and use from synthorg.observability import get_logger with variable name logger
Never use import logging, logging.getLogger(), or print() in application code except in observability/setup.py and observability/sinks.py
Use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api); import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Use structured logging with kwargs: logger.info(EVENT, key=value) -- never use old-style string formatting like logger.info('msg %s', val)
Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement
Use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence) for dict/list fields in frozen Pydantic models
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves (e.g., agent execution state, task progress)
Never mix static config fields with mutable runtime fields in one Pydantic model
Use @computed_field for derived values instead of storing + validating redundant fields (e.g., TokenUsage.total_tokens)
Use NotBlankStr from core.types for all identifier/name fields, including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants, instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) instead of bare create_task
Keep functions under 50 lines and files under 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries (user input, exter...

Files:

  • src/synthorg/budget/tracker.py
  • src/synthorg/observability/events/analytics.py
  • src/synthorg/budget/trends.py
  • src/synthorg/api/controllers/analytics.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Run uv run ruff check src/ tests/ for linting; uv run ruff check src/ tests/ --fix for auto-fix; uv run ruff format src/ tests/ for formatting
Run uv run mypy src/ tests/ for strict type-checking

Files:

  • src/synthorg/budget/tracker.py
  • src/synthorg/observability/events/analytics.py
  • src/synthorg/budget/trends.py
  • src/synthorg/api/controllers/analytics.py
docs/**

📄 CodeRabbit inference engine (CLAUDE.md)

Run uv run zensical build to build docs (output: _site/docs/); uv run zensical serve for local preview

Files:

  • docs/design/operations.md
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Use Markdown for documentation in docs/ directory, built with Zensical (config: mkdocs.yml)

Files:

  • docs/design/operations.md
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Design spec in docs/design/ (7 pages); use for all architecture, data models, and behavior decisions. Reference with format: docs/design/<page>

Files:

  • docs/design/operations.md
🧠 Learnings (30)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • tests/unit/observability/test_events.py
  • src/synthorg/observability/events/analytics.py
  • docs/design/operations.md
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • tests/unit/observability/test_events.py
  • src/synthorg/observability/events/analytics.py
  • docs/design/operations.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • tests/unit/observability/test_events.py
  • src/synthorg/observability/events/analytics.py
  • docs/design/operations.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • tests/unit/observability/test_events.py
  • src/synthorg/observability/events/analytics.py
  • docs/design/operations.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • tests/unit/observability/test_events.py
  • src/synthorg/observability/events/analytics.py
  • docs/design/operations.md
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • tests/unit/observability/test_events.py
  • src/synthorg/observability/events/analytics.py
  • docs/design/operations.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • tests/unit/observability/test_events.py
  • src/synthorg/observability/events/analytics.py
  • docs/design/operations.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to src/**/*.py : Use event name constants from domain-specific modules under ai_company.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.) — import directly

Applied to files:

  • tests/unit/observability/test_events.py
  • src/synthorg/observability/events/analytics.py
  • docs/design/operations.md
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • tests/unit/observability/test_events.py
  • src/synthorg/observability/events/analytics.py
  • docs/design/operations.md
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • tests/unit/observability/test_events.py
  • src/synthorg/observability/events/analytics.py
  • docs/design/operations.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/budget/tracker.py
  • tests/unit/budget/test_trends.py
  • src/synthorg/budget/trends.py
  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/budget/tracker.py
  • tests/unit/api/controllers/test_analytics.py
  • tests/unit/budget/test_trends.py
  • src/synthorg/budget/trends.py
  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • tests/unit/api/conftest.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • tests/unit/api/conftest.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • tests/unit/api/conftest.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/budget/trends.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).

Applied to files:

  • src/synthorg/budget/trends.py
  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to **/*.py : Use `except A, B:` syntax without parentheses (PEP 758 except syntax) in exception handlers

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
🧬 Code graph analysis (5)
src/synthorg/budget/tracker.py (1)
web/src/api/types.ts (1)
  • BudgetConfig (407-414)
tests/unit/api/conftest.py (3)
tests/unit/hr/promotion/conftest.py (1)
  • tracker (104-106)
src/synthorg/hr/performance/tracker.py (1)
  • PerformanceTracker (51-506)
src/synthorg/api/state.py (1)
  • performance_tracker (294-299)
tests/unit/budget/test_trends.py (2)
src/synthorg/budget/trends.py (9)
  • BucketSize (49-53)
  • TrendMetric (40-46)
  • TrendPeriod (32-37)
  • bucket_cost_records (208-247)
  • bucket_success_rate (286-331)
  • bucket_task_completions (250-283)
  • period_to_timedelta (151-160)
  • project_daily_spend (393-446)
  • resolve_bucket_size (137-148)
src/synthorg/hr/performance/models.py (1)
  • TaskMetricRecord (25-67)
src/synthorg/budget/trends.py (4)
web/src/api/types.ts (1)
  • CostRecord (382-392)
src/synthorg/hr/performance/models.py (1)
  • TaskMetricRecord (25-67)
src/synthorg/budget/tracker.py (1)
  • record (108-121)
src/synthorg/api/dto.py (1)
  • is_success (485-487)
src/synthorg/api/controllers/analytics.py (6)
src/synthorg/api/errors.py (1)
  • ServiceUnavailableError (222-231)
src/synthorg/api/state.py (2)
  • AppState (42-511)
  • has_agent_registry (307-309)
src/synthorg/budget/trends.py (9)
  • BucketSize (49-53)
  • TrendDataPoint (59-70)
  • TrendMetric (40-46)
  • TrendPeriod (32-37)
  • bucket_cost_records (208-247)
  • bucket_success_rate (286-331)
  • bucket_task_completions (250-283)
  • project_daily_spend (393-446)
  • resolve_bucket_size (137-148)
src/synthorg/budget/tracker.py (2)
  • get_total_cost (123-146)
  • get_records (194-234)
src/synthorg/hr/performance/tracker.py (1)
  • get_task_metrics (374-400)
src/synthorg/engine/agent_state.py (1)
  • idle (104-117)
🔇 Additional comments (7)
src/synthorg/api/controllers/analytics.py (7)

33-41: LGTM!

Correctly uses get_logger from observability and imports event constants from the domain-specific analytics events module, following project conventions.


47-147: LGTM!

Response models are well-designed: all frozen for immutability, fields have appropriate constraints (ge=0.0, le=1.0), and docstrings clearly document semantics including edge cases like budget_used_percent > 100 indicating overrun.


160-200: LGTM!

Excellent error handling pattern: critical errors (MemoryError, RecursionError) are re-raised to avoid masking resource exhaustion, while other failures degrade gracefully with logging. The division-by-zero is properly guarded by the early return on line 176.


203-233: LGTM!

Good defensive coding with max(total - len(active), 0) preventing negative idle counts if data is inconsistent. The service availability check via has_agent_registry properly guards the optional dependency.


322-345: LGTM!

Good use of asyncio.TaskGroup for parallel queries as recommended by coding guidelines. The ExceptionGroup handling logs context before re-raising the first exception, maintaining explicit error handling.


387-438: LGTM!

Clean endpoint implementation with proper parameter validation via Parameter constraints. The structured logging captures essential context for debugging without sensitive data.


440-498: LGTM!

The forecast endpoint correctly validates horizon_days (1-90) at the API layer, passes deterministic now to project_daily_spend for testability, and properly constructs the response from the BudgetForecast result.

Comment on lines +286 to +291
# ACTIVE_AGENTS: flat line at current count
active_count, _ = await _resolve_agent_counts(app_state, 0)
return tuple(
TrendDataPoint(timestamp=bs, value=float(active_count))
for bs in generate_bucket_starts(start, now, bucket_size)
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider documenting the flat-line behavior for API consumers.

The ACTIVE_AGENTS metric returns the current count repeated across all buckets rather than historical data. While the code comment explains this, consider documenting this limitation in the endpoint docstring or API documentation so consumers understand the sparkline isn't showing true historical trends for this metric.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/analytics.py` around lines 286 - 291, Update the
endpoint/docstring that returns trend data to explicitly state that the
ACTIVE_AGENTS metric is a flat-line over the returned buckets (it uses the
current count from _resolve_agent_counts and repeats that value for each
timestamp generated by generate_bucket_starts), so consumers know TrendDataPoint
values for ACTIVE_AGENTS are not historical but a repeated snapshot; mention the
behavior in the function's docstring and in any external API documentation where
ACTIVE_AGENTS is described, referencing the ACTIVE_AGENTS identifier, the use of
_resolve_agent_counts, TrendDataPoint, and generate_bucket_starts.

Comment on lines +181 to +202
def generate_bucket_starts(
start: datetime,
end: datetime,
bucket_size: BucketSize,
) -> list[datetime]:
"""Generate all bucket start times in [start, end).

Args:
start: Period start (inclusive).
end: Period end (exclusive).
bucket_size: Granularity.

Returns:
Sorted list of bucket start datetimes.
"""
step = timedelta(hours=1) if bucket_size == BucketSize.HOUR else timedelta(days=1)
current = _bucket_key(start, bucket_size)
buckets: list[datetime] = []
while current < end:
buckets.append(current)
current = current + step
return buckets
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

generate_bucket_starts() silently widens the requested window.

This helper says it returns bucket starts in [start, end), but it floors start before iterating. With unaligned inputs that emits a leading partial bucket before start, so a "7d" hourly range can come back with 169 points and a first timestamp that does not actually represent the requested interval. Please make alignment explicit instead of silently widening the range.

🛠️ One safe fix
 def generate_bucket_starts(
     start: datetime,
     end: datetime,
     bucket_size: BucketSize,
 ) -> list[datetime]:
     """Generate all bucket start times in [start, end).
@@
     Returns:
         Sorted list of bucket start datetimes.
+
+    Raises:
+        ValueError: If `start` or `end` is not aligned to `bucket_size`.
     """
     step = timedelta(hours=1) if bucket_size == BucketSize.HOUR else timedelta(days=1)
-    current = _bucket_key(start, bucket_size)
+    if start != _bucket_key(start, bucket_size) or end != _bucket_key(
+        end,
+        bucket_size,
+    ):
+        msg = "start and end must align to the selected bucket size"
+        raise ValueError(msg)
+    current = start
     buckets: list[datetime] = []
     while current < end:
         buckets.append(current)
         current = current + step
     return buckets
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/budget/trends.py` around lines 181 - 202, generate_bucket_starts
currently floors the incoming start via _bucket_key which silently widens the
window; change the function to make alignment explicit by adding a boolean
parameter (e.g., align_start: bool) and only floor start when align_start is
True, otherwise begin iteration at the raw start value; update the docstring to
document the new parameter and its behavior, and keep using BucketSize and
_bucket_key to compute the floored start when requested so the loop that appends
current buckets remains correct.

Comment on lines +337 to +363
def _compute_daily_spend(
records: Sequence[CostRecord],
) -> tuple[float, float, int]:
"""Compute average daily spend and confidence inputs.

Args:
records: Non-empty cost records.

Returns:
Tuple of (avg_daily_spend, confidence, lookback_days).
"""
timestamps = sorted(r.timestamp for r in records)
lookback_days = max(
(timestamps[-1].date() - timestamps[0].date()).days,
1,
)
total_cost = round(
math.fsum(r.cost_usd for r in records),
BUDGET_ROUNDING_PRECISION,
)
avg_daily = round(total_cost / lookback_days, BUDGET_ROUNDING_PRECISION)
days_with_data = len({r.timestamp.date() for r in records})
confidence = round(
min(days_with_data / lookback_days, 1.0),
BUDGET_ROUNDING_PRECISION,
)
return avg_daily, confidence, lookback_days
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

_compute_daily_spend() overestimates burn rate for multi-day samples.

(timestamps[-1].date() - timestamps[0].date()).days excludes one endpoint. For example, records on March 1, 2026 and March 2, 2026 yield lookback_days == 1, so $20 of spend is treated as $20/day instead of $10/day. That inflates avg_daily_spend_usd, days_until_exhausted, and confidence.

🛠️ Proposed fix
-    lookback_days = max(
-        (timestamps[-1].date() - timestamps[0].date()).days,
-        1,
-    )
+    lookback_days = max(
+        (timestamps[-1].date() - timestamps[0].date()).days + 1,
+        1,
+    )
📝 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
def _compute_daily_spend(
records: Sequence[CostRecord],
) -> tuple[float, float, int]:
"""Compute average daily spend and confidence inputs.
Args:
records: Non-empty cost records.
Returns:
Tuple of (avg_daily_spend, confidence, lookback_days).
"""
timestamps = sorted(r.timestamp for r in records)
lookback_days = max(
(timestamps[-1].date() - timestamps[0].date()).days,
1,
)
total_cost = round(
math.fsum(r.cost_usd for r in records),
BUDGET_ROUNDING_PRECISION,
)
avg_daily = round(total_cost / lookback_days, BUDGET_ROUNDING_PRECISION)
days_with_data = len({r.timestamp.date() for r in records})
confidence = round(
min(days_with_data / lookback_days, 1.0),
BUDGET_ROUNDING_PRECISION,
)
return avg_daily, confidence, lookback_days
def _compute_daily_spend(
records: Sequence[CostRecord],
) -> tuple[float, float, int]:
"""Compute average daily spend and confidence inputs.
Args:
records: Non-empty cost records.
Returns:
Tuple of (avg_daily_spend, confidence, lookback_days).
"""
timestamps = sorted(r.timestamp for r in records)
lookback_days = max(
(timestamps[-1].date() - timestamps[0].date()).days + 1,
1,
)
total_cost = round(
math.fsum(r.cost_usd for r in records),
BUDGET_ROUNDING_PRECISION,
)
avg_daily = round(total_cost / lookback_days, BUDGET_ROUNDING_PRECISION)
days_with_data = len({r.timestamp.date() for r in records})
confidence = round(
min(days_with_data / lookback_days, 1.0),
BUDGET_ROUNDING_PRECISION,
)
return avg_daily, confidence, lookback_days
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/budget/trends.py` around lines 337 - 363, The function
_compute_daily_spend currently computes lookback_days using
(timestamps[-1].date() - timestamps[0].date()).days which is exclusive of the
end date and therefore overstates avg_daily; change the lookback_days
calculation to use an inclusive range by adding 1 (e.g., lookback_days =
max((timestamps[-1].date() - timestamps[0].date()).days + 1, 1)) so avg_daily,
confidence, and days_until_exhausted use the correct inclusive day count;
preserve the rounding and existing uses of timestamps, total_cost, avg_daily and
confidence.

Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/budget/test_trends.py`:
- Around line 364-385: The test comment in test_known_daily_spend is misleading
about span counting; update the inline comment in the test (around the
test_known_daily_spend function) to explicitly state that project_daily_spend
uses an exclusive span calculation (timestamp delta = 2 days for Mar 1 -> Mar 3)
rather than saying "3-day span", e.g. note that the implementation computes a
2-day delta and clamps to 2 so average = $30/2 = $15/day; keep assertions
unchanged.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d372bc7b-9ffc-48e3-8a1f-849e70d95426

📥 Commits

Reviewing files that changed from the base of the PR and between b8a895b and 93f36c0.

📒 Files selected for processing (6)
  • CLAUDE.md
  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/trends.py
  • tests/unit/api/controllers/test_analytics.py
  • tests/unit/budget/test_trends.py
  • tests/unit/observability/test_events.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python files -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax without parentheses (PEP 758 except syntax) in exception handlers
All public functions and methods must have type hints; enforce strict mypy mode
Use Google-style docstrings on all public classes and functions; enforced by ruff D rules

Files:

  • tests/unit/observability/test_events.py
  • tests/unit/budget/test_trends.py
  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/trends.py
  • tests/unit/api/controllers/test_analytics.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow markers on all tests
Maintain 80% minimum code coverage (enforced in CI)
Use asyncio_mode = 'auto' -- no manual @pytest.mark.asyncio needed on async tests
Global test timeout is 30 seconds per test; do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed
ALWAYS run pytest with -n auto for parallelism via pytest-xdist; never run tests sequentially
Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use Hypothesis for Python property-based testing with @given + @settings decorators; use profiles: ci (50 examples, default) and dev (1000 examples) controlled via HYPOTHESIS_PROFILE env var
Run Hypothesis dev profile with: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number)
NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally

Files:

  • tests/unit/observability/test_events.py
  • tests/unit/budget/test_trends.py
  • tests/unit/api/controllers/test_analytics.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import and use from synthorg.observability import get_logger with variable name logger
Never use import logging, logging.getLogger(), or print() in application code except in observability/setup.py and observability/sinks.py
Use event name constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api); import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT
Use structured logging with kwargs: logger.info(EVENT, key=value) -- never use old-style string formatting like logger.info('msg %s', val)
Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement
Use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence) for dict/list fields in frozen Pydantic models
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves (e.g., agent execution state, task progress)
Never mix static config fields with mutable runtime fields in one Pydantic model
Use @computed_field for derived values instead of storing + validating redundant fields (e.g., TokenUsage.total_tokens)
Use NotBlankStr from core.types for all identifier/name fields, including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants, instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls) instead of bare create_task
Keep functions under 50 lines and files under 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries (user input, exter...

Files:

  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/trends.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Run uv run ruff check src/ tests/ for linting; uv run ruff check src/ tests/ --fix for auto-fix; uv run ruff format src/ tests/ for formatting
Run uv run mypy src/ tests/ for strict type-checking

Files:

  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/trends.py
🧠 Learnings (32)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • tests/unit/observability/test_events.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • tests/unit/observability/test_events.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • tests/unit/observability/test_events.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • tests/unit/observability/test_events.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • tests/unit/observability/test_events.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • tests/unit/observability/test_events.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • tests/unit/observability/test_events.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • tests/unit/observability/test_events.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to src/**/*.py : Use event name constants from domain-specific modules under ai_company.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.) — import directly

Applied to files:

  • tests/unit/observability/test_events.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • tests/unit/observability/test_events.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • CLAUDE.md
  • tests/unit/budget/test_trends.py
  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/trends.py
  • tests/unit/api/controllers/test_analytics.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • CLAUDE.md
  • tests/unit/budget/test_trends.py
  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/trends.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/api/**/*.py : REST API: Litestar framework, controllers with guards, channels for WebSocket, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint. RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/trends.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to **/*.py : Use `except A, B:` syntax without parentheses (PEP 758 except syntax) in exception handlers

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-23T06:50:44.558Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-23T06:50:44.558Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
🧬 Code graph analysis (4)
tests/unit/budget/test_trends.py (3)
src/synthorg/budget/trends.py (10)
  • BucketSize (45-49)
  • TrendMetric (36-42)
  • TrendPeriod (28-33)
  • bucket_cost_records (208-247)
  • bucket_success_rate (286-331)
  • bucket_task_completions (250-283)
  • generate_bucket_starts (178-202)
  • period_to_timedelta (148-157)
  • project_daily_spend (396-449)
  • resolve_bucket_size (134-145)
src/synthorg/hr/performance/models.py (1)
  • TaskMetricRecord (25-67)
src/synthorg/api/dto.py (1)
  • is_success (485-487)
src/synthorg/api/controllers/analytics.py (4)
src/synthorg/api/errors.py (1)
  • ServiceUnavailableError (222-231)
src/synthorg/api/state.py (2)
  • AppState (42-511)
  • has_agent_registry (307-309)
src/synthorg/budget/trends.py (12)
  • BucketSize (45-49)
  • ForecastPoint (69-83)
  • TrendDataPoint (55-66)
  • TrendMetric (36-42)
  • TrendPeriod (28-33)
  • bucket_cost_records (208-247)
  • bucket_success_rate (286-331)
  • bucket_task_completions (250-283)
  • generate_bucket_starts (178-202)
  • period_to_timedelta (148-157)
  • project_daily_spend (396-449)
  • resolve_bucket_size (134-145)
src/synthorg/budget/tracker.py (2)
  • get_total_cost (123-146)
  • get_records (194-234)
src/synthorg/budget/trends.py (2)
web/src/api/types.ts (1)
  • CostRecord (382-392)
src/synthorg/hr/performance/models.py (1)
  • TaskMetricRecord (25-67)
tests/unit/api/controllers/test_analytics.py (7)
src/synthorg/budget/tracker.py (2)
  • CostTracker (68-464)
  • record (108-121)
src/synthorg/hr/performance/models.py (1)
  • TaskMetricRecord (25-67)
src/synthorg/hr/performance/tracker.py (2)
  • PerformanceTracker (51-506)
  • record_task_metric (136-159)
tests/unit/api/conftest.py (7)
  • test_client (210-243)
  • cost_tracker (180-181)
  • performance_tracker (195-196)
  • fake_persistence (166-169)
  • fake_message_bus (173-176)
  • _seed_test_users (246-277)
  • auth_service (161-162)
tests/unit/api/fakes.py (1)
  • FakePersistenceBackend (416-519)
src/synthorg/api/app.py (1)
  • create_app (407-639)
src/synthorg/settings/registry.py (1)
  • get_registry (111-113)
🔇 Additional comments (16)
src/synthorg/budget/trends.py (2)

351-366: _compute_daily_spend() uses exclusive day counting, which may overestimate burn rate.

The calculation (timestamps[-1].date() - timestamps[0].date()).days counts the interval between dates, not the inclusive span. For records on March 1 and March 3, this yields 2 days, but the actual span covers 3 calendar days.

This affects avg_daily_spend_usd accuracy: $30 spread across a 3-day window should be ~$10/day, not $15/day.

If this is intentional (treating the span as "days of activity"), please add a comment clarifying the rationale. Otherwise, consider the inclusive fix:

🛠️ Proposed fix for inclusive day counting
     lookback_days = max(
-        (timestamps[-1].date() - timestamps[0].date()).days,
+        (timestamps[-1].date() - timestamps[0].date()).days + 1,
         1,
     )

1-24: LGTM! Well-structured pure functions module.

The module correctly follows the pure-function pattern for bucketing and forecasting. Models use frozen=True and allow_inf_nan=False as required by the coding guidelines.

tests/unit/observability/test_events.py (1)

181-181: LGTM!

The new "analytics" module is correctly added to the expected set, and the test for analytics event constants follows the established pattern used by other event tests in this file.

Also applies to: 238-247

CLAUDE.md (1)

106-106: LGTM!

The package structure documentation accurately reflects the new trend analysis and budget forecasting capabilities added by this PR.

tests/unit/api/controllers/test_analytics.py (4)

27-65: LGTM! Clean test helpers.

The _make_cost_record and _make_task_metric helpers follow the vendor-agnostic naming convention (test-provider, test-small-001) and provide sensible defaults for concise test setup.


150-275: LGTM! Comprehensive trends endpoint coverage.

The tests cover default parameters, period/metric variations, data aggregation, invalid inputs, and auth requirements. The async tests properly await tracker operations.


280-359: LGTM! Solid forecast endpoint coverage.

Tests verify empty state, data-driven projections, custom horizons, validation boundaries, and authentication. Edge cases for invalid horizon values (0, 100) are appropriately covered.


364-402: LGTM! Graceful degradation test correctly verifies fallback behavior.

The test properly constructs an app without a PerformanceTracker and verifies that the trends endpoint returns empty data_points instead of failing, matching the controller's error handling.

tests/unit/budget/test_trends.py (3)

1-65: LGTM! Well-organized test module with clear helpers.

The test helpers follow the vendor-agnostic naming guidelines and provide appropriate defaults. The file structure with section comments aids readability.


108-211: LGTM! Thorough bucketing tests.

Tests cover empty records, daily/hourly bucketing, out-of-range exclusion, single records, sorting, and immutability via ValidationError on frozen models. The pytest.approx usage for floating-point comparisons is appropriate.


352-479: LGTM! Comprehensive forecast projection tests.

Edge cases for empty records, zero spend, budget exhaustion, confidence scoring, and sequential date projections are well covered. The frozen model validation test at lines 464-467 verifies immutability constraints.

src/synthorg/api/controllers/analytics.py (5)

54-96: LGTM! Well-designed response model with appropriate constraints.

OverviewMetrics correctly uses allow_inf_nan=False, ge=0.0 constraints, and documents that budget_used_percent can exceed 100% for overruns. The frozen config ensures immutability.


168-208: LGTM! Robust budget context resolution with graceful fallback.

The helper correctly handles missing budget config, logs warnings on query failures, and uses the fallback total cost. The PEP 758 except MemoryError, RecursionError: syntax is correctly used to re-raise critical errors.


278-328: LGTM! Clean metric dispatch with graceful degradation.

The function handles each TrendMetric appropriately. The ServiceUnavailableError catch for performance_tracker (lines 306-313) ensures the endpoint degrades gracefully instead of failing.

The inline comment at lines 322-323 appropriately documents the flat-line behavior for ACTIVE_AGENTS.


452-503: LGTM! Trends endpoint with proper parameter validation.

The Annotated[TrendPeriod, Parameter(...)] pattern provides clear API documentation. The endpoint correctly converts period to bucket size and delegates to the pure bucketing functions.


505-565: LGTM! Forecast endpoint with bounded horizon.

The ge=1, le=90 constraint on horizon_days prevents unbounded projections. The lookback period matching the horizon is a sensible default for confidence calculation.

Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/budget/test_trends.py`:
- Around line 451-463: Update the misleading comment in the
test_confidence_partial_coverage test: change the opening comment that currently
reads “4-day span -> confidence 0.5” to correctly describe the exclusive span
and expected confidence (span is 3 days, data on 2 days -> confidence 2/3) so it
matches the assertion `assert result.confidence == pytest.approx(2.0 / 3.0)` and
clarifies the behavior of project_daily_spend used in this test.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1d8e7183-a8b1-4d71-9e47-29e35c0a5458

📥 Commits

Reviewing files that changed from the base of the PR and between 93f36c0 and aef320d.

📒 Files selected for processing (1)
  • tests/unit/budget/test_trends.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python files -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax without parentheses (PEP 758 except syntax) in exception handlers
All public functions and methods must have type hints; enforce strict mypy mode
Use Google-style docstrings on all public classes and functions; enforced by ruff D rules

Files:

  • tests/unit/budget/test_trends.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow markers on all tests
Maintain 80% minimum code coverage (enforced in CI)
Use asyncio_mode = 'auto' -- no manual @pytest.mark.asyncio needed on async tests
Global test timeout is 30 seconds per test; do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed
ALWAYS run pytest with -n auto for parallelism via pytest-xdist; never run tests sequentially
Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use Hypothesis for Python property-based testing with @given + @settings decorators; use profiles: ci (50 examples, default) and dev (1000 examples) controlled via HYPOTHESIS_PROFILE env var
Run Hypothesis dev profile with: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number)
NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally

Files:

  • tests/unit/budget/test_trends.py
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • tests/unit/budget/test_trends.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • tests/unit/budget/test_trends.py

Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/budget/test_trends.py`:
- Around line 356-480: Add a deterministic assertion that when calling
project_daily_spend with an explicit now argument the projection start aligns
with that anchor: in a suitable test (e.g., extend
test_projections_have_sequential_dates or create a new test) call
project_daily_spend(records, horizon_days=..., now=<fixed datetime>) and assert
result.daily_projections[0].day == <fixed datetime>.date(); reference the
project_daily_spend function and the daily_projections list to locate where to
add the check so the projections' first day uses the provided now anchor.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ca8a024e-83e0-4ea5-b535-050cf4b3d9b7

📥 Commits

Reviewing files that changed from the base of the PR and between aef320d and 790e8db.

📒 Files selected for processing (1)
  • tests/unit/budget/test_trends.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations in Python files -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax without parentheses (PEP 758 except syntax) in exception handlers
All public functions and methods must have type hints; enforce strict mypy mode
Use Google-style docstrings on all public classes and functions; enforced by ruff D rules

Files:

  • tests/unit/budget/test_trends.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow markers on all tests
Maintain 80% minimum code coverage (enforced in CI)
Use asyncio_mode = 'auto' -- no manual @pytest.mark.asyncio needed on async tests
Global test timeout is 30 seconds per test; do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed
ALWAYS run pytest with -n auto for parallelism via pytest-xdist; never run tests sequentially
Prefer @pytest.mark.parametrize for testing similar cases
Tests must use test-provider, test-small-001, etc. instead of real vendor names
Use Hypothesis for Python property-based testing with @given + @settings decorators; use profiles: ci (50 examples, default) and dev (1000 examples) controlled via HYPOTHESIS_PROFILE env var
Run Hypothesis dev profile with: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number)
NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally

Files:

  • tests/unit/budget/test_trends.py
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • tests/unit/budget/test_trends.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • tests/unit/budget/test_trends.py
🧬 Code graph analysis (1)
tests/unit/budget/test_trends.py (2)
src/synthorg/budget/trends.py (10)
  • BucketSize (45-49)
  • TrendMetric (36-42)
  • TrendPeriod (28-33)
  • bucket_cost_records (208-247)
  • bucket_success_rate (286-331)
  • bucket_task_completions (250-283)
  • generate_bucket_starts (178-202)
  • period_to_timedelta (148-157)
  • project_daily_spend (396-449)
  • resolve_bucket_size (134-145)
src/synthorg/hr/performance/models.py (1)
  • TaskMetricRecord (25-67)
🔇 Additional comments (2)
tests/unit/budget/test_trends.py (2)

167-183: Good boundary coverage for [start, end) filtering.

These cases validate inclusive start/exclusive end semantics for both cost and task bucketing, which protects against off-by-one regressions.

Also applies to: 253-263


544-579: Enum serialization contract tests look solid.

These assertions provide stable API-wire guarantees for period/metric/bucket string values.

Aureliolo and others added 6 commits March 24, 2026 06:06
Add GET /api/v1/analytics/trends with period (7d/30d/90d) and metric
(tasks_completed, spend, active_agents, success_rate) query params,
returning bucketed time-series data points.

Add GET /api/v1/analytics/forecast with horizon_days param, returning
daily spend projections, days until budget exhaustion, and confidence.

Extend GET /api/v1/analytics/overview with budget_remaining_usd,
budget_used_percent, cost_7d_trend sparkline, active_agents_count,
and idle_agents_count.

Pure trend bucketing and forecast functions in budget/trends.py follow
the existing category_analytics.py pattern. CostTracker gains a public
budget_config property. PerformanceTracker wired into API test fixtures.

Closes #771

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract _resolve_budget_context, _fetch_trend_data_points helpers
  to bring all controller methods under 50-line limit
- Extract _compute_daily_spend, _build_projections from project_daily_spend
- Make generate_bucket_starts public in trends.py, remove duplicate from controller
- Add logging on ServiceUnavailableError catch and budget fallback paths
- Use math.ceil for days_until_exhausted (budget survives N full days)
- Add now param to project_daily_spend for deterministic testing
- Use StrEnum types in TrendsResponse instead of raw str
- Add max(0) guard on idle agent count for TOCTOU safety
- Add exc_info=True to agent registry warning log
- Hoist _PERIOD_TIMEDELTA to module constant
- Document budget_used_percent >100 means overrun
- Update operations.md: event module count 52->53, expand analytics API row

Pre-reviewed by 7 agents, 17 findings addressed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…l reviewers

- Add allow_inf_nan=False to OverviewMetrics, TrendsResponse, ForecastResponse
- Add ge=0 constraint to days_until_exhausted on both BudgetForecast and ForecastResponse
- Fix hardcoded rounding precision (2) to use BUDGET_ROUNDING_PRECISION constant
- Fix dead code guard: reorder budget_config None check for type narrowing
- Fix ExceptionGroup handler to raise controlled ServiceUnavailableError
- Extract _assemble_overview helper to bring get_overview under 50-line limit
- Extract _bucket_task_metric_data to bring _fetch_trend_data_points under 50 lines
- Fix semantically inverted docstring for days_until_exhausted
- Fix misleading "additional spend" wording in ForecastResponse
- Fix get_forecast docstring about lookback period averaging
- Fix generate_bucket_starts docstring to document start-flooring behavior
- Fix _compute_daily_spend docstring to document non-empty precondition
- Remove unused logger declaration from trends.py (pure functions only)
- Add comment explaining DAY bucket divergence in overview sparkline
- Expand active agents flat-line comment to explain why
- Update CLAUDE.md budget/ package description with trend analysis
- Refactor resolver and enum tests to use @pytest.mark.parametrize
- Add direct tests for generate_bucket_starts (5 tests)
- Add test for budget_remaining_usd=0 edge case (days_until_exhausted=0)
- Add test for ServiceUnavailableError graceful degradation in trends
- Add test_analytics_events_exist for event constant values

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… exclusive delta)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

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

♻️ Duplicate comments (2)
src/synthorg/budget/trends.py (2)

178-202: ⚠️ Potential issue | 🟠 Major

Still unresolved: generate_bucket_starts() widens [start, end).

Flooring start on Line 197 means an unaligned empty interval can still produce a bucket, and a 7-day hourly query can grow to 169 points with a first timestamp before the requested range. Make alignment explicit instead of silently widening the window here.

🛠️ One safe fix
 def generate_bucket_starts(
     start: datetime,
     end: datetime,
     bucket_size: BucketSize,
 ) -> list[datetime]:
@@
-    step = timedelta(hours=1) if bucket_size == BucketSize.HOUR else timedelta(days=1)
-    current = _bucket_key(start, bucket_size)
+    step = timedelta(hours=1) if bucket_size == BucketSize.HOUR else timedelta(days=1)
+    if start != _bucket_key(start, bucket_size) or end != _bucket_key(
+        end,
+        bucket_size,
+    ):
+        msg = "start and end must align to the selected bucket size"
+        raise ValueError(msg)
+    current = start
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/budget/trends.py` around lines 178 - 202, generate_bucket_starts
currently floors start via _bucket_key which can widen the requested [start,
end) by emitting a bucket that begins before start; change this so alignment is
explicit and never yields a bucket that starts before the original start.
Compute aligned = _bucket_key(start, bucket_size) and then set current = aligned
if aligned >= start else aligned + step (where step is the hourly/daily
timedelta already used); proceed with the same while loop using current so the
first yielded bucket is always >= the original start. Reference:
generate_bucket_starts, _bucket_key, current, start, step.

351-365: ⚠️ Potential issue | 🟠 Major

Still unresolved: burn-rate span is off by one day.

(timestamps[-1].date() - timestamps[0].date()).days treats March 1 → March 2 as a one-day sample. That doubles avg_daily_spend_usd and skews projected_total_usd, days_until_exhausted, and confidence.

🛠️ Minimal fix
     lookback_days = max(
-        (timestamps[-1].date() - timestamps[0].date()).days,
+        (timestamps[-1].date() - timestamps[0].date()).days + 1,
         1,
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/budget/trends.py` around lines 351 - 365, The lookback span is
off-by-one: compute lookback_days from timestamps inclusive (so that March
1→March 2 yields 2 days) by adding 1 to the date-difference calculation; update
the calculation that sets lookback_days (which currently uses
(timestamps[-1].date() - timestamps[0].date()).days) to use +1 and keep the
max(..., 1) guard, which will correct avg_daily, projected totals and confidence
that rely on lookback_days (see variables lookback_days, timestamps, avg_daily,
confidence, days_with_data).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/operations.md`:
- Line 1073: Update the `/api/v1/analytics` row to explicitly document the query
contract for the three sub-endpoints (`GET /overview`, `GET /trends`, `GET
/forecast`): add the allowed period enum `period=7d|30d|90d`, specify that
hourly bucketing is supported only when `period=7d` for `/trends`, and add the
forecast horizon enum `horizon_days=7|14|30` for `/forecast`; include brief
notes about default values and validation behavior so the design spec contains
the same constraints reflected in the controllers/tests.

In `@src/synthorg/api/controllers/analytics.py`:
- Around line 168-200: The fallback in _resolve_budget_context uses a
period_cost value (fallback_total_cost) that may not be scoped to the current
billing period (it can come from get_overview() or get_forecast()), which
produces incorrect remaining/percent/days calculations across resets; change
_resolve_budget_context to treat an out-of-period fallback as “unavailable”
(e.g., return _BudgetContext with used_percent/remaining set to a
sentinel/unavailable state) or require callers to pass a period-scoped fallback
(instead of generic fallback_total_cost) and validate it against
billing_period_start(budget_config.reset_day); update call sites that pass
get_overview() / get_forecast() to either provide a period-scoped total or
handle the unavailable response so budget_remaining_usd, budget_used_percent,
and days_until_exhausted are not computed from lifetime totals.
- Around line 509-516: The horizon_days parameter currently accepts any int
1..90; restrict it to the supported set {7,14,30} by changing the parameter
type/validation for horizon_days in the analytics controller: replace the
Annotated[int, Parameter(... ge=1, le=90 ...)] with a constrained type (either
typing.Literal[7,14,30] or a small Enum like ForecastHorizon with members
SEVEN=7, FOURTEEN=14, THIRTY=30) and keep the Parameter(...) description but
remove the ge/le bounds; update any usages of horizon_days in functions/methods
that expect an int to accept the Literal/Enum value (or cast to int if
necessary) so external input is validated to only 7, 14, or 30.
- Around line 536-538: The query fetching cost records uses only
start=lookback_start, so future-dated records may be included; update the call
to app_state.cost_tracker.get_records(...) to pass an explicit end parameter set
to the current time (e.g., a timezone-aware "now") so the lookup is bounded:
include end=now alongside start=lookback_start when calling get_records to
ensure averages and confidence calculations exclude future records.
- Around line 322-328: ACTIVE_AGENTS currently calls
_resolve_agent_counts(app_state, 0) which forces a zero fallback when the
registry is missing; change the second argument to the configured agent count
instead of 0 (e.g. _resolve_agent_counts(app_state,
app_state.config.agent_count)) so active_count reflects configured agents; keep
the rest of the logic that maps generate_bucket_starts(...) to
TrendDataPoint(timestamp=bs, value=float(active_count)).

In `@src/synthorg/budget/trends.py`:
- Around line 1-18: Add the standard module logger by importing get_logger from
synthorg.observability and defining logger = get_logger(__name__) near the top
of the module (e.g., after existing imports). Do not use import logging or
print(); ensure the symbol logger is available for subsequent functions/classes
in trends.py so future validation/error paths call
logger.debug/info/warning/error as needed.

In `@tests/unit/api/controllers/test_analytics.py`:
- Around line 242-251: Update test_trends_active_agents to seed at least one
active/configured agent before calling the trends endpoint, then assert the
actual numeric values in data["data_points"] rather than only the shape: create
an agent (via existing fixture or by POSTing through test_client), call GET
"/api/v1/analytics/trends" with params {"metric": "active_agents"}, parse
data["data_points"] and assert each point has a "value" field and that the
values equal the expected active-agent count (e.g., >= 1 or the exact seeded
count) across buckets (and that at least one value is > 0) to verify correct
behavior of test_trends_active_agents.

---

Duplicate comments:
In `@src/synthorg/budget/trends.py`:
- Around line 178-202: generate_bucket_starts currently floors start via
_bucket_key which can widen the requested [start, end) by emitting a bucket that
begins before start; change this so alignment is explicit and never yields a
bucket that starts before the original start. Compute aligned =
_bucket_key(start, bucket_size) and then set current = aligned if aligned >=
start else aligned + step (where step is the hourly/daily timedelta already
used); proceed with the same while loop using current so the first yielded
bucket is always >= the original start. Reference: generate_bucket_starts,
_bucket_key, current, start, step.
- Around line 351-365: The lookback span is off-by-one: compute lookback_days
from timestamps inclusive (so that March 1→March 2 yields 2 days) by adding 1 to
the date-difference calculation; update the calculation that sets lookback_days
(which currently uses (timestamps[-1].date() - timestamps[0].date()).days) to
use +1 and keep the max(..., 1) guard, which will correct avg_daily, projected
totals and confidence that rely on lookback_days (see variables lookback_days,
timestamps, avg_daily, confidence, days_with_data).
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5cfbe794-ce7b-4fbe-b98d-f82423a84c07

📥 Commits

Reviewing files that changed from the base of the PR and between 790e8db and 481b54c.

📒 Files selected for processing (10)
  • CLAUDE.md
  • docs/design/operations.md
  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/tracker.py
  • src/synthorg/budget/trends.py
  • src/synthorg/observability/events/analytics.py
  • tests/unit/api/conftest.py
  • tests/unit/api/controllers/test_analytics.py
  • tests/unit/budget/test_trends.py
  • tests/unit/observability/test_events.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Backend
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) for multiple exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14
All public functions and classes must have type hints; mypy strict mode is enforced
All public classes and functions must have Google-style docstrings; enforced by ruff D rules
Use immutability patterns: create new objects instead of mutating existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement. For dict/list fields in frozen Pydantic models, use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)
Use frozen Pydantic models (v2) for config/identity; use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 BaseModel, model_validator, computed_field, and ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task
Functions must be < 50 lines; files must be < 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries (user input, external APIs, config files)
Line length: 88 characters (enforced by ruff)

Files:

  • src/synthorg/budget/tracker.py
  • tests/unit/observability/test_events.py
  • tests/unit/api/conftest.py
  • src/synthorg/observability/events/analytics.py
  • tests/unit/budget/test_trends.py
  • tests/unit/api/controllers/test_analytics.py
  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/trends.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import logger: from synthorg.observability import get_logger then logger = get_logger(__name__); never use import logging or print() (exceptions: observability/setup.py and observability/sinks.py may use stdlib logging and print(..., file=sys.stderr) for bootstrap code)
Always use structured logging with logger name as logger (not _logger or log). Use event name constants from synthorg.observability.events.<domain> modules (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT. Always log as logger.info(EVENT, key=value) -- never logger.info("msg %s", val)
Log WARNING or ERROR on all error paths before raising exceptions. Log INFO on all state transitions. Log DEBUG for object creation, internal flow, and entry/exit of key functions
Never implement retry logic in provider driver subclasses or calling code -- it is handled automatically by BaseCompletionProvider base class. RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig. Retryable errors: RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately. RetryExhaustedError signals all retries failed -- catch at engine layer to trigger fallback chains. Rate limiter respects RateLimitError.retry_after

Files:

  • src/synthorg/budget/tracker.py
  • src/synthorg/observability/events/analytics.py
  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/trends.py
**.md

📄 CodeRabbit inference engine (CLAUDE.md)

Commit messages must follow format: <type>: <description> with types: feat, fix, refactor, docs, test, chore, perf, ci. Enforced by commitizen (commit-msg hook). Signed commits required on main via branch protection -- all commits must be GPG/SSH signed

Files:

  • CLAUDE.md
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow on all tests
Async tests use asyncio_mode = "auto" (configured in pyproject.toml) -- do not add manual @pytest.mark.asyncio markers. Tests have a global 30-second timeout (configured in pyproject.toml) -- do not add per-file @pytest.mark.timeout(30) markers; only add non-default overrides like @pytest.mark.timeout(60) when needed
Always run pytest with -n auto for parallelism (pytest-xdist); never run tests sequentially. Always include coverage target --cov=synthorg --cov-fail-under=80 when running full test suite
Prefer @pytest.mark.parametrize for testing similar cases; use vendor-agnostic provider names in all project-owned code (use test-provider, test-small-001, test-medium-001, test-large-001, or small/medium/large aliases). Vendor names may only appear in: (1) Operations design page, (2) .claude/ files, (3) third-party import paths. Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project code, docstrings, comments, or tests
Use Hypothesis for property-based testing with @given and @settings. Control Hypothesis profiles via HYPOTHESIS_PROFILE env var: ci (50 examples, default) or dev (1000 examples). Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties
Never skip, dismiss, or ignore flaky tests -- always fix them fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number)

Files:

  • tests/unit/observability/test_events.py
  • tests/unit/api/conftest.py
  • tests/unit/budget/test_trends.py
  • tests/unit/api/controllers/test_analytics.py
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

ALWAYS read the relevant docs/design/ page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file to 7 design pages. The design spec is the starting point for architecture, data models, and behavior. If implementation deviates from the spec, alert the user and explain why -- user decides whether to proceed or update the spec. When a spec topic is referenced, read the relevant page before coding. Update the relevant design page when approved deviations occur

Files:

  • docs/design/operations.md
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation is written in Markdown, built with Zensical (config: mkdocs.yml). Auto-generated reference docs use mkdocstrings + Griffe (AST-based). REST API reference in docs/rest-api.md and auto-generated docs/_generated/api-reference.html (generated via scripts/export_openapi.py)

Files:

  • docs/design/operations.md
🧠 Learnings (35)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/budget/tracker.py
  • CLAUDE.md
  • tests/unit/budget/test_trends.py
  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/trends.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/budget/tracker.py
  • CLAUDE.md
  • tests/unit/budget/test_trends.py
  • tests/unit/api/controllers/test_analytics.py
  • src/synthorg/api/controllers/analytics.py
  • src/synthorg/budget/trends.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/api/**/*.py : REST API: Litestar framework, controllers with guards, channels for WebSocket, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint. RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/communication/**/*.py : Communication package (communication/): message bus, dispatcher, messenger, channels, delegation, loop prevention, conflict resolution; meeting/ subpackage for meeting protocol (round-robin, position papers, structured phases), scheduler (frequency, participant resolver), orchestrator

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/api/**/*.py : Use Litestar for REST + WebSocket API. Controllers, guards, channels, JWT + API key + WS ticket auth, RFC 9457 structured errors.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/hr/**/*.py : HR package (hr/): hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)

Applied to files:

  • CLAUDE.md
  • tests/unit/api/conftest.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).

Applied to files:

  • CLAUDE.md
  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/engine/**/*.py : Engine package (engine/): agent orchestration, parallel execution, task decomposition, routing, TaskEngine (centralized single-writer), task lifecycle/recovery/shutdown, workspace isolation, coordination (4 dispatchers: SAS/centralized/decentralized/context-dependent, wave execution), approval gates (escalation detection, context parking/resume), stagnation detection (ToolRepetitionDetector, corrective prompt injection), AgentRuntimeState (execution status), context budget management, conversation compaction (oldest-turns summarizer)

Applied to files:

  • CLAUDE.md
  • tests/unit/api/conftest.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • tests/unit/observability/test_events.py
  • docs/design/operations.md
  • src/synthorg/observability/events/analytics.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • tests/unit/observability/test_events.py
  • docs/design/operations.md
  • src/synthorg/observability/events/analytics.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • tests/unit/observability/test_events.py
  • docs/design/operations.md
  • src/synthorg/observability/events/analytics.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • tests/unit/observability/test_events.py
  • docs/design/operations.md
  • src/synthorg/observability/events/analytics.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • tests/unit/observability/test_events.py
  • docs/design/operations.md
  • src/synthorg/observability/events/analytics.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • tests/unit/observability/test_events.py
  • docs/design/operations.md
  • src/synthorg/observability/events/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to src/**/*.py : Use event name constants from domain-specific modules under ai_company.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.) — import directly

Applied to files:

  • tests/unit/observability/test_events.py
  • docs/design/operations.md
  • src/synthorg/observability/events/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • tests/unit/observability/test_events.py
  • docs/design/operations.md
  • src/synthorg/observability/events/analytics.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • tests/unit/observability/test_events.py
  • docs/design/operations.md
  • src/synthorg/observability/events/analytics.py
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging with logger name as `logger` (not `_logger` or `log`). Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`. Always log as `logger.info(EVENT, key=value)` -- never `logger.info("msg %s", val)`

Applied to files:

  • tests/unit/observability/test_events.py
  • docs/design/operations.md
  • src/synthorg/observability/events/analytics.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/hr/**/*.py : HR engine must provide: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, trend detection), promotion/demotion

Applied to files:

  • tests/unit/api/conftest.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/budget/test_trends.py
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests -- always fix them fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins. For tasks that must block indefinitely until cancelled, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`

Applied to files:

  • tests/unit/budget/test_trends.py
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for multiple exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
🧬 Code graph analysis (4)
tests/unit/api/conftest.py (3)
tests/unit/hr/promotion/conftest.py (1)
  • tracker (104-106)
src/synthorg/hr/performance/tracker.py (1)
  • PerformanceTracker (51-506)
src/synthorg/api/state.py (1)
  • performance_tracker (294-299)
tests/unit/budget/test_trends.py (4)
src/synthorg/budget/trends.py (10)
  • BucketSize (45-49)
  • TrendMetric (36-42)
  • TrendPeriod (28-33)
  • bucket_cost_records (208-247)
  • bucket_success_rate (286-331)
  • bucket_task_completions (250-283)
  • generate_bucket_starts (178-202)
  • period_to_timedelta (148-157)
  • project_daily_spend (396-449)
  • resolve_bucket_size (134-145)
src/synthorg/core/enums.py (2)
  • Complexity (275-281)
  • TaskType (255-263)
src/synthorg/hr/performance/models.py (1)
  • TaskMetricRecord (25-67)
src/synthorg/api/dto.py (1)
  • is_success (485-487)
src/synthorg/api/controllers/analytics.py (5)
src/synthorg/api/errors.py (1)
  • ServiceUnavailableError (222-231)
src/synthorg/budget/trends.py (12)
  • BucketSize (45-49)
  • ForecastPoint (69-83)
  • TrendDataPoint (55-66)
  • TrendMetric (36-42)
  • TrendPeriod (28-33)
  • bucket_cost_records (208-247)
  • bucket_success_rate (286-331)
  • bucket_task_completions (250-283)
  • generate_bucket_starts (178-202)
  • period_to_timedelta (148-157)
  • project_daily_spend (396-449)
  • resolve_bucket_size (134-145)
src/synthorg/core/enums.py (1)
  • TaskStatus (226-252)
src/synthorg/budget/tracker.py (2)
  • get_total_cost (123-146)
  • get_records (194-234)
src/synthorg/hr/performance/tracker.py (1)
  • get_task_metrics (374-400)
src/synthorg/budget/trends.py (1)
src/synthorg/hr/performance/models.py (1)
  • TaskMetricRecord (25-67)

Comment on lines +168 to +200
async def _resolve_budget_context(
app_state: AppState,
fallback_total_cost: float = 0.0,
) -> _BudgetContext:
"""Compute budget remaining and usage percentage.

Args:
app_state: Application state.
fallback_total_cost: Total cost to use if period query fails.

Returns:
Budget context with monthly, remaining, and used_percent.
"""
budget_config = app_state.cost_tracker.budget_config
monthly = budget_config.total_monthly if budget_config else 0.0
if budget_config is None or monthly <= 0:
return _BudgetContext(monthly=0.0, remaining=0.0, used_percent=0.0)

period_start = billing_period_start(budget_config.reset_day)
try:
period_cost = await app_state.cost_tracker.get_total_cost(
start=period_start,
)
except MemoryError, RecursionError:
raise
except Exception:
logger.warning(
API_REQUEST_ERROR,
endpoint="analytics.budget_context",
error="period_cost_query_failed",
exc_info=True,
)
period_cost = fallback_total_cost
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The degraded budget fallback can cross billing-period boundaries.

period_cost here is supposed to mean spend since billing_period_start(reset_day), but this fallback is populated from get_overview()'s lifetime total_cost or get_forecast()'s default 0.0. On that path, budget_remaining_usd, budget_used_percent, and days_until_exhausted can all be materially wrong after a reset. Please return an explicit unavailable state or require a period-scoped fallback instead.
Based on learnings, budget tracking includes billing periods.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/analytics.py` around lines 168 - 200, The
fallback in _resolve_budget_context uses a period_cost value
(fallback_total_cost) that may not be scoped to the current billing period (it
can come from get_overview() or get_forecast()), which produces incorrect
remaining/percent/days calculations across resets; change
_resolve_budget_context to treat an out-of-period fallback as “unavailable”
(e.g., return _BudgetContext with used_percent/remaining set to a
sentinel/unavailable state) or require callers to pass a period-scoped fallback
(instead of generic fallback_total_cost) and validate it against
billing_period_start(budget_config.reset_day); update call sites that pass
get_overview() / get_forecast() to either provide a period-scoped total or
handle the unavailable response so budget_remaining_usd, budget_used_percent,
and days_until_exhausted are not computed from lifetime totals.

Comment on lines +322 to +328
# ACTIVE_AGENTS: flat line -- no historical agent counts are
# tracked, so report the current snapshot across all buckets
active_count, _ = await _resolve_agent_counts(app_state, 0)
return tuple(
TrendDataPoint(timestamp=bs, value=float(active_count))
for bs in generate_bucket_starts(start, now, bucket_size)
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

ACTIVE_AGENTS drops to zero when the registry is missing or degraded.

_resolve_agent_counts() is documented to fall back to the configured agent count, but this path passes 0. In deployments without AgentRegistryService—or when it throws—every bucket here becomes 0 even if agents are configured.

Suggested fix
-    active_count, _ = await _resolve_agent_counts(app_state, 0)
+    config_agents = await app_state.config_resolver.get_agents()
+    active_count, _ = await _resolve_agent_counts(
+        app_state,
+        len(config_agents),
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/analytics.py` around lines 322 - 328,
ACTIVE_AGENTS currently calls _resolve_agent_counts(app_state, 0) which forces a
zero fallback when the registry is missing; change the second argument to the
configured agent count instead of 0 (e.g. _resolve_agent_counts(app_state,
app_state.config.agent_count)) so active_count reflects configured agents; keep
the rest of the logic that maps generate_bucket_starts(...) to
TrendDataPoint(timestamp=bs, value=float(active_count)).

Comment on lines +509 to +516
horizon_days: Annotated[
int,
Parameter(
ge=1,
le=90,
description="Projection horizon in days",
),
] = 14,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Constrain horizon_days to the supported set.

The linked objective narrows forecasts to 7, 14, or 30, but this accepts any integer from 1 to 90. That widens the public API and leaves boundary validation weaker than the contract.
As per coding guidelines, validate at system boundaries (user input, external APIs, config files).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/analytics.py` around lines 509 - 516, The
horizon_days parameter currently accepts any int 1..90; restrict it to the
supported set {7,14,30} by changing the parameter type/validation for
horizon_days in the analytics controller: replace the Annotated[int,
Parameter(... ge=1, le=90 ...)] with a constrained type (either
typing.Literal[7,14,30] or a small Enum like ForecastHorizon with members
SEVEN=7, FOURTEEN=14, THIRTY=30) and keep the Parameter(...) description but
remove the ge/le bounds; update any usages of horizon_days in functions/methods
that expect an int to accept the Literal/Enum value (or cast to int if
necessary) so external input is validated to only 7, 14, or 30.

Comment on lines +1 to +18
"""Time-series trend bucketing and budget forecast projections.

Provides pure functions to bucket cost records and task metrics into
time-series data points, and to project future budget spend from
historical data. Follows the same pure-function pattern as
:mod:`~synthorg.budget.category_analytics`.
"""

import math
from collections import defaultdict
from datetime import UTC, date, datetime, timedelta
from enum import StrEnum
from typing import TYPE_CHECKING

from pydantic import AwareDatetime, BaseModel, ConfigDict, Field

from synthorg.constants import BUDGET_ROUNDING_PRECISION

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add the standard module logger.

src/synthorg/budget/trends.py is business logic under src/synthorg/, so it should follow the repo’s required get_logger(__name__) convention before more validation/error paths land here.

As per coding guidelines, "Every module with business logic must import logger: from synthorg.observability import get_logger then logger = get_logger(__name__); never use import logging or print()."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/budget/trends.py` around lines 1 - 18, Add the standard module
logger by importing get_logger from synthorg.observability and defining logger =
get_logger(__name__) near the top of the module (e.g., after existing imports).
Do not use import logging or print(); ensure the symbol logger is available for
subsequent functions/classes in trends.py so future validation/error paths call
logger.debug/info/warning/error as needed.

Comment on lines +242 to +251
def test_trends_active_agents(self, test_client: TestClient[Any]) -> None:
resp = test_client.get(
"/api/v1/analytics/trends",
params={"metric": "active_agents"},
headers=_HEADERS,
)
assert resp.status_code == 200
data = resp.json()["data"]
assert data["metric"] == "active_agents"
assert isinstance(data["data_points"], list)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Assert the active-agent values, not just the response shape.

This still passes if /analytics/trends?metric=active_agents returns the wrong flat-line count for every bucket. Seed at least one configured or active agent and assert the repeated value so this endpoint behavior is actually covered.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/api/controllers/test_analytics.py` around lines 242 - 251, Update
test_trends_active_agents to seed at least one active/configured agent before
calling the trends endpoint, then assert the actual numeric values in
data["data_points"] rather than only the shape: create an agent (via existing
fixture or by POSTing through test_client), call GET "/api/v1/analytics/trends"
with params {"metric": "active_agents"}, parse data["data_points"] and assert
each point has a "value" field and that the values equal the expected
active-agent count (e.g., >= 1 or the exact seeded count) across buckets (and
that at least one value is > 0) to verify correct behavior of
test_trends_active_agents.

- Expand operations.md analytics row with query params, defaults, validation
- Pass end=now to get_records in forecast to exclude future-dated records

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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

♻️ Duplicate comments (3)
src/synthorg/api/controllers/analytics.py (3)

509-516: ⚠️ Potential issue | 🟠 Major

Constrain horizon_days to the supported set.

The linked objective (#771) narrows forecasts to 7, 14, or 30 days, but this accepts any integer from 1 to 90. Consider using typing.Literal[7, 14, 30] or a dedicated enum to match the documented contract.

As per coding guidelines, validate at system boundaries (user input, external APIs, config files).

,

🔧 Suggested fix using Literal
+from typing import Literal
+
+ForecastHorizon = Literal[7, 14, 30]
+
     `@get`("/forecast", guards=[require_read_access])
     async def get_forecast(
         self,
         state: State,
         horizon_days: Annotated[
-            int,
+            ForecastHorizon,
             Parameter(
-                ge=1,
-                le=90,
                 description="Projection horizon in days (7, 14, or 30)",
             ),
         ] = 14,
     ) -> ApiResponse[ForecastResponse]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/analytics.py` around lines 509 - 516, The
parameter horizon_days currently accepts any int 1–90; restrict it to the
supported set (7, 14, 30) at the API boundary by changing the type of
horizon_days in analytics.py to a constrained type (e.g., typing.Literal[7, 14,
30] or a small Enum) instead of Annotated[int, Parameter(...)] and update any
Parameter/validation metadata accordingly so requests outside {7,14,30} are
rejected; ensure the handler/signature that references horizon_days (the
function parameter named horizon_days) and any downstream assumptions are
updated to use the new Literal/Enum type.

168-208: ⚠️ Potential issue | 🟠 Major

The degraded budget fallback can cross billing-period boundaries.

When get_total_cost(start=period_start) fails, the function falls back to fallback_total_cost, which at the /overview call site is total_cost (lifetime total from get_total_cost() with no bounds), and at /forecast is 0.0. Neither is scoped to the current billing period.

After a billing reset, budget_remaining_usd and budget_used_percent computed from this fallback will be materially incorrect.

Consider either returning an explicit "unavailable" state when the period query fails, or requiring callers to pass a period-scoped fallback.

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/analytics.py` around lines 168 - 208,
_resolve_budget_context currently uses fallback_total_cost when
get_total_cost(start=period_start) fails, but that fallback may be
lifetime-scoped (e.g., total_cost) and can cross billing periods; update
_resolve_budget_context to return an explicit "unavailable" budget context when
the period query fails (e.g., _BudgetContext with monthly set and
remaining/used_percent as None or a sentinel) OR change the function signature
to require a period-scoped fallback (rename fallback_total_cost to
period_fallback_total_cost) and use that value only; modify callers (e.g., the
/overview and /forecast code paths that call _resolve_budget_context) to either
handle the unavailable sentinel or pass a proper period-scoped fallback so
budget_remaining_usd and budget_used_percent are not computed from lifetime
totals. Ensure references to billing_period_start,
app_state.cost_tracker.get_total_cost, and _BudgetContext are updated
accordingly.

322-328: ⚠️ Potential issue | 🟠 Major

ACTIVE_AGENTS drops to zero when the registry is missing or degraded.

_resolve_agent_counts(app_state, 0) passes 0 as the fallback. When AgentRegistryService is unavailable or throws, every bucket becomes 0 even if agents are configured.

,

🔧 Suggested fix
     # ACTIVE_AGENTS: flat line -- no historical agent counts are
     # tracked, so report the current snapshot across all buckets
-    active_count, _ = await _resolve_agent_counts(app_state, 0)
+    agents = await app_state.config_resolver.get_agents()
+    active_count, _ = await _resolve_agent_counts(app_state, len(agents))
     return tuple(
         TrendDataPoint(timestamp=bs, value=float(active_count))
         for bs in generate_bucket_starts(start, now, bucket_size)
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/controllers/analytics.py` around lines 322 - 328, The
ACTIVE_AGENTS code is passing a literal 0 as the fallback to
_resolve_agent_counts which causes every bucket to be reported as 0 when
AgentRegistryService is missing; replace the hardcoded 0 with the
configured/current agent count from app_state (e.g.,
app_state.config.agent_count or app_state.get_configured_agent_count()) and pass
that value into _resolve_agent_counts(app_state, configured_count) so
TrendDataPoint generation via generate_bucket_starts/TrendDataPoint reflects the
configured snapshot instead of dropping to zero.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/operations.md`:
- Line 1073: Docs and implementation disagree on allowed forecast horizons: docs
show GET /forecast?horizon_days=1..90 while issue `#771` expects only 7, 14, or
30; fix by making the contract consistent—either restrict the implementation or
update the spec/docs. Update the GET /forecast handler and its validation
(horizon_days) to only accept 7, 14, or 30 and return 400 for others, and/or
change the `docs/design/operations.md` entry for `/api/v1/analytics` -> `GET
/forecast?horizon_days` to explicitly document the allowed values (7|14|30) and
default (14), ensuring the parameter name `horizon_days`, default, and error
behavior match the implementation and reference issue `#771`.

In `@src/synthorg/api/controllers/analytics.py`:
- Around line 297-299: The SPEND branch calls app_state.cost_tracker.get_records
without an end bound, which can include future-dated records; change the call in
the TrendMetric.SPEND handling to pass end=now (i.e.,
app_state.cost_tracker.get_records(start=start, end=now)) so bucket_cost_records
receives only records within [start, now] consistent with the forecast path that
uses end=now; update the invocation referenced in the SPEND conditional that
returns bucket_cost_records(records, start, now, bucket_size).

---

Duplicate comments:
In `@src/synthorg/api/controllers/analytics.py`:
- Around line 509-516: The parameter horizon_days currently accepts any int
1–90; restrict it to the supported set (7, 14, 30) at the API boundary by
changing the type of horizon_days in analytics.py to a constrained type (e.g.,
typing.Literal[7, 14, 30] or a small Enum) instead of Annotated[int,
Parameter(...)] and update any Parameter/validation metadata accordingly so
requests outside {7,14,30} are rejected; ensure the handler/signature that
references horizon_days (the function parameter named horizon_days) and any
downstream assumptions are updated to use the new Literal/Enum type.
- Around line 168-208: _resolve_budget_context currently uses
fallback_total_cost when get_total_cost(start=period_start) fails, but that
fallback may be lifetime-scoped (e.g., total_cost) and can cross billing
periods; update _resolve_budget_context to return an explicit "unavailable"
budget context when the period query fails (e.g., _BudgetContext with monthly
set and remaining/used_percent as None or a sentinel) OR change the function
signature to require a period-scoped fallback (rename fallback_total_cost to
period_fallback_total_cost) and use that value only; modify callers (e.g., the
/overview and /forecast code paths that call _resolve_budget_context) to either
handle the unavailable sentinel or pass a proper period-scoped fallback so
budget_remaining_usd and budget_used_percent are not computed from lifetime
totals. Ensure references to billing_period_start,
app_state.cost_tracker.get_total_cost, and _BudgetContext are updated
accordingly.
- Around line 322-328: The ACTIVE_AGENTS code is passing a literal 0 as the
fallback to _resolve_agent_counts which causes every bucket to be reported as 0
when AgentRegistryService is missing; replace the hardcoded 0 with the
configured/current agent count from app_state (e.g.,
app_state.config.agent_count or app_state.get_configured_agent_count()) and pass
that value into _resolve_agent_counts(app_state, configured_count) so
TrendDataPoint generation via generate_bucket_starts/TrendDataPoint reflects the
configured snapshot instead of dropping to zero.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: dc432f03-1975-412f-b8fa-45526cb129b4

📥 Commits

Reviewing files that changed from the base of the PR and between 481b54c and 622e2e9.

📒 Files selected for processing (2)
  • docs/design/operations.md
  • src/synthorg/api/controllers/analytics.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Backend
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

ALWAYS read the relevant docs/design/ page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file to 7 design pages. The design spec is the starting point for architecture, data models, and behavior. If implementation deviates from the spec, alert the user and explain why -- user decides whether to proceed or update the spec. When a spec topic is referenced, read the relevant page before coding. Update the relevant design page when approved deviations occur

Files:

  • docs/design/operations.md
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation is written in Markdown, built with Zensical (config: mkdocs.yml). Auto-generated reference docs use mkdocstrings + Griffe (AST-based). REST API reference in docs/rest-api.md and auto-generated docs/_generated/api-reference.html (generated via scripts/export_openapi.py)

Files:

  • docs/design/operations.md
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) for multiple exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14
All public functions and classes must have type hints; mypy strict mode is enforced
All public classes and functions must have Google-style docstrings; enforced by ruff D rules
Use immutability patterns: create new objects instead of mutating existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement. For dict/list fields in frozen Pydantic models, use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)
Use frozen Pydantic models (v2) for config/identity; use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 BaseModel, model_validator, computed_field, and ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task
Functions must be < 50 lines; files must be < 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries (user input, external APIs, config files)
Line length: 88 characters (enforced by ruff)

Files:

  • src/synthorg/api/controllers/analytics.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import logger: from synthorg.observability import get_logger then logger = get_logger(__name__); never use import logging or print() (exceptions: observability/setup.py and observability/sinks.py may use stdlib logging and print(..., file=sys.stderr) for bootstrap code)
Always use structured logging with logger name as logger (not _logger or log). Use event name constants from synthorg.observability.events.<domain> modules (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT. Always log as logger.info(EVENT, key=value) -- never logger.info("msg %s", val)
Log WARNING or ERROR on all error paths before raising exceptions. Log INFO on all state transitions. Log DEBUG for object creation, internal flow, and entry/exit of key functions
Never implement retry logic in provider driver subclasses or calling code -- it is handled automatically by BaseCompletionProvider base class. RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig. Retryable errors: RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately. RetryExhaustedError signals all retries failed -- catch at engine layer to trigger fallback chains. Rate limiter respects RateLimitError.retry_after

Files:

  • src/synthorg/api/controllers/analytics.py
🧠 Learnings (32)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to docs/design/**/*.md : ALWAYS read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file to 7 design pages. The design spec is the starting point for architecture, data models, and behavior. If implementation deviates from the spec, alert the user and explain why -- user decides whether to proceed or update the spec. When a spec topic is referenced, read the relevant page before coding. Update the relevant design page when approved deviations occur

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue — DESIGN_SPEC.md is a pointer file linking to 7 design pages (Agents, Organization, Communication, Engine, Memory, Operations)

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use event name constants from domain-specific modules under `ai_company.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to src/**/*.py : Use event name constants from domain-specific modules under ai_company.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.) — import directly

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging with logger name as `logger` (not `_logger` or `log`). Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`. Always log as `logger.info(EVENT, key=value)` -- never `logger.info("msg %s", val)`

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for multiple exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/api/controllers/analytics.py
🧬 Code graph analysis (1)
src/synthorg/api/controllers/analytics.py (7)
src/synthorg/api/dto.py (1)
  • ApiResponse (130-158)
src/synthorg/api/errors.py (1)
  • ServiceUnavailableError (222-231)
src/synthorg/api/guards.py (1)
  • require_read_access (121-145)
src/synthorg/api/state.py (2)
  • AppState (42-511)
  • has_agent_registry (307-309)
src/synthorg/budget/trends.py (10)
  • BucketSize (45-49)
  • TrendDataPoint (55-66)
  • TrendMetric (36-42)
  • TrendPeriod (28-33)
  • bucket_cost_records (208-247)
  • bucket_success_rate (286-331)
  • bucket_task_completions (250-283)
  • generate_bucket_starts (178-202)
  • project_daily_spend (396-449)
  • resolve_bucket_size (134-145)
src/synthorg/budget/tracker.py (3)
  • budget_config (100-106)
  • get_total_cost (123-146)
  • get_records (194-234)
src/synthorg/hr/performance/tracker.py (1)
  • get_task_metrics (374-400)
🔇 Additional comments (9)
src/synthorg/api/controllers/analytics.py (8)

1-50: LGTM!

Imports are well-organized. Logger is correctly sourced from synthorg.observability, event constants are imported from the domain-specific events.analytics module, and TYPE_CHECKING is used appropriately for type-only imports.


54-155: LGTM!

Response models are well-structured with frozen configs, appropriate field constraints (ge=0, le=1), comprehensive docstrings, and allow_inf_nan=False to prevent serialization issues. The use of tuple[...] for collections aligns with immutability patterns.


211-242: LGTM!

The helper correctly abstracts agent count resolution with graceful fallback and proper error logging. The PEP 758 exception syntax is used correctly.


244-276: LGTM!

Clean dispatcher that routes task metric data to the appropriate bucketing function based on metric type.


331-385: LGTM!

The assembly helper is well-structured, correctly uses the configured agent count as fallback for _resolve_agent_counts, and properly computes the 7-day sparkline with daily buckets.


396-450: LGTM!

Good use of asyncio.TaskGroup for concurrent data fetching. Exception handling properly logs the error count and converts to ServiceUnavailableError with a clear message.


452-503: LGTM!

The trends endpoint correctly uses typed enums for period and metric parameters, leveraging Litestar's built-in validation. Debug logging includes appropriate structured fields.


536-539: Good: Lookback query now bounded at now.

The end=now parameter was added, addressing the previous concern about future-dated records skewing forecasts.

docs/design/operations.md (1)

1298-1301: LGTM!

Event taxonomy count correctly updated to 53 to reflect the new analytics event constants module.

| `/api/v1/budget` | Spending, limits, projections |
| `/api/v1/approvals` | Pending human approvals queue |
| `/api/v1/analytics` | Performance metrics, dashboards |
| `/api/v1/analytics` | `GET /overview` (metrics summary with budget status, 7d spend sparkline, agent counts), `GET /trends?period=7d\|30d\|90d&metric=spend\|tasks_completed\|active_agents\|success_rate` (time-series bucketed metrics; hourly buckets for 7d, daily for 30d/90d; defaults: `period=7d`, `metric=spend`), `GET /forecast?horizon_days=1..90` (budget spend projection with daily projections and exhaustion estimate; default 14; 400 on out-of-range) |
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Documentation specifies horizon_days=1..90 but issue #771 specifies 7/14/30.

The documentation now documents the analytics query contracts, which is good. However, horizon_days=1..90 matches the current implementation but diverges from the objective in issue #771 (which specifies only 7, 14, or 30). If the implementation is intentionally broader than the spec, update the issue/spec to reflect this decision; otherwise, align both doc and code to 7|14|30.

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/operations.md` at line 1073, Docs and implementation disagree on
allowed forecast horizons: docs show GET /forecast?horizon_days=1..90 while
issue `#771` expects only 7, 14, or 30; fix by making the contract
consistent—either restrict the implementation or update the spec/docs. Update
the GET /forecast handler and its validation (horizon_days) to only accept 7,
14, or 30 and return 400 for others, and/or change the
`docs/design/operations.md` entry for `/api/v1/analytics` -> `GET
/forecast?horizon_days` to explicitly document the allowed values (7|14|30) and
default (14), ensuring the parameter name `horizon_days`, default, and error
behavior match the implementation and reference issue `#771`.

…ords

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/api/controllers/analytics.py`:
- Around line 360-367: The 7-day sparkline is producing 8 buckets because
bucket_cost_records (via generate_bucket_starts) floors the start to midnight;
fix by passing an aligned start-of-day for 6 days ago instead of now -
timedelta(days=7). Update the call that builds cost_7d (references: cost_7d,
bucket_cost_records, BucketSize.DAY, records_7d, now) to compute start =
start_of_day(now - timedelta(days=6)) (i.e., shift to 6 days ago and floor to
midnight) before calling bucket_cost_records so the sparkline yields exactly 7
daily points.
- Around line 186-190: The billing-period cost query currently calls
app_state.cost_tracker.get_total_cost(start=period_start) which includes
future-dated records; update the flow to pass the request-scoped now into
_resolve_budget_context() and use it to bound the cost query by calling
get_total_cost(start=period_start, end=now). Specifically, modify the call sites
that build the budget context (functions get_overview and get_forecast) to pass
the same request-scoped now into _resolve_budget_context() and ensure the
resolved code uses that now when calling get_total_cost (identify
billing_period_start(budget_config.reset_day) and
app_state.cost_tracker.get_total_cost to locate the change).
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d1128141-5020-48ee-bdd5-2fb5b84026b6

📥 Commits

Reviewing files that changed from the base of the PR and between 622e2e9 and c77ce5f.

📒 Files selected for processing (1)
  • src/synthorg/api/controllers/analytics.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) for multiple exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14
All public functions and classes must have type hints; mypy strict mode is enforced
All public classes and functions must have Google-style docstrings; enforced by ruff D rules
Use immutability patterns: create new objects instead of mutating existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement. For dict/list fields in frozen Pydantic models, use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)
Use frozen Pydantic models (v2) for config/identity; use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 BaseModel, model_validator, computed_field, and ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task
Functions must be < 50 lines; files must be < 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries (user input, external APIs, config files)
Line length: 88 characters (enforced by ruff)

Files:

  • src/synthorg/api/controllers/analytics.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import logger: from synthorg.observability import get_logger then logger = get_logger(__name__); never use import logging or print() (exceptions: observability/setup.py and observability/sinks.py may use stdlib logging and print(..., file=sys.stderr) for bootstrap code)
Always use structured logging with logger name as logger (not _logger or log). Use event name constants from synthorg.observability.events.<domain> modules (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT. Always log as logger.info(EVENT, key=value) -- never logger.info("msg %s", val)
Log WARNING or ERROR on all error paths before raising exceptions. Log INFO on all state transitions. Log DEBUG for object creation, internal flow, and entry/exit of key functions
Never implement retry logic in provider driver subclasses or calling code -- it is handled automatically by BaseCompletionProvider base class. RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig. Retryable errors: RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately. RetryExhaustedError signals all retries failed -- catch at engine layer to trigger fallback chains. Rate limiter respects RateLimitError.retry_after

Files:

  • src/synthorg/api/controllers/analytics.py
🧠 Learnings (16)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for multiple exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/api/controllers/analytics.py
🧬 Code graph analysis (1)
src/synthorg/api/controllers/analytics.py (7)
src/synthorg/api/dto.py (1)
  • ApiResponse (130-158)
src/synthorg/api/errors.py (1)
  • ServiceUnavailableError (222-231)
src/synthorg/api/state.py (1)
  • AppState (42-511)
src/synthorg/budget/trends.py (11)
  • BucketSize (45-49)
  • TrendDataPoint (55-66)
  • TrendMetric (36-42)
  • TrendPeriod (28-33)
  • bucket_cost_records (208-247)
  • bucket_success_rate (286-331)
  • bucket_task_completions (250-283)
  • generate_bucket_starts (178-202)
  • period_to_timedelta (148-157)
  • project_daily_spend (396-449)
  • resolve_bucket_size (134-145)
src/synthorg/core/enums.py (1)
  • TaskStatus (226-252)
src/synthorg/budget/tracker.py (3)
  • budget_config (100-106)
  • get_total_cost (123-146)
  • get_records (194-234)
src/synthorg/hr/performance/tracker.py (1)
  • get_task_metrics (374-400)

- Align sparkline start to midnight 6 days ago for exactly 7 daily points
- Thread now into _resolve_budget_context to bound get_total_cost with end=now

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/api/controllers/analytics.py`:
- Around line 442-446: The 7-day records query created with tg.create_task
calling app_state.cost_tracker.get_records should be bounded with end=now
(matching the SPEND fix in _fetch_trend_data_points) to avoid fetching records
beyond the current window; update the call site where t_7d is created to pass
end=now alongside start=now - timedelta(days=7) so bucket_cost_records doesn't
end up filtering out extra records.
- Around line 310-314: The task metrics query for TrendMetric.TASKS_COMPLETED
and TrendMetric.SUCCESS_RATE currently calls
app_state.performance_tracker.get_task_metrics(since=start) without an upper
bound; modify the call to include until=now so it becomes
get_task_metrics(since=start, until=now) (using the existing now variable) to
prevent fetching future-dated records and match the SPEND fix 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a581f021-c34f-4710-92a5-dcdf3a98513b

📥 Commits

Reviewing files that changed from the base of the PR and between c77ce5f and f4c3933.

📒 Files selected for processing (1)
  • src/synthorg/api/controllers/analytics.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations
Use except A, B: syntax (no parentheses) for multiple exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14
All public functions and classes must have type hints; mypy strict mode is enforced
All public classes and functions must have Google-style docstrings; enforced by ruff D rules
Use immutability patterns: create new objects instead of mutating existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction and MappingProxyType wrapping for read-only enforcement. For dict/list fields in frozen Pydantic models, use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence)
Use frozen Pydantic models (v2) for config/identity; use separate mutable-via-copy models with model_copy(update=...) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 BaseModel, model_validator, computed_field, and ConfigDict. Use @computed_field for derived values instead of storing redundant fields. Use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task
Functions must be < 50 lines; files must be < 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries (user input, external APIs, config files)
Line length: 88 characters (enforced by ruff)

Files:

  • src/synthorg/api/controllers/analytics.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/synthorg/**/*.py: Every module with business logic must import logger: from synthorg.observability import get_logger then logger = get_logger(__name__); never use import logging or print() (exceptions: observability/setup.py and observability/sinks.py may use stdlib logging and print(..., file=sys.stderr) for bootstrap code)
Always use structured logging with logger name as logger (not _logger or log). Use event name constants from synthorg.observability.events.<domain> modules (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT. Always log as logger.info(EVENT, key=value) -- never logger.info("msg %s", val)
Log WARNING or ERROR on all error paths before raising exceptions. Log INFO on all state transitions. Log DEBUG for object creation, internal flow, and entry/exit of key functions
Never implement retry logic in provider driver subclasses or calling code -- it is handled automatically by BaseCompletionProvider base class. RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig. Retryable errors: RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately. RetryExhaustedError signals all retries failed -- catch at engine layer to trigger fallback chains. Rate limiter respects RateLimitError.retry_after

Files:

  • src/synthorg/api/controllers/analytics.py
🧠 Learnings (16)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for multiple exception handling -- PEP 758 except syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-24T05:06:57.759Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-24T05:06:57.759Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T21:11:37.538Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T21:11:37.538Z
Learning: Applies to **/*.py : Errors: handle explicitly, never silently swallow.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/api/controllers/analytics.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files)

Applied to files:

  • src/synthorg/api/controllers/analytics.py
🧬 Code graph analysis (1)
src/synthorg/api/controllers/analytics.py (6)
src/synthorg/api/errors.py (1)
  • ServiceUnavailableError (222-231)
src/synthorg/api/state.py (3)
  • AppState (42-511)
  • has_agent_registry (307-309)
  • config_resolver (327-329)
src/synthorg/budget/trends.py (10)
  • BucketSize (45-49)
  • TrendDataPoint (55-66)
  • TrendMetric (36-42)
  • TrendPeriod (28-33)
  • bucket_cost_records (208-247)
  • bucket_success_rate (286-331)
  • bucket_task_completions (250-283)
  • generate_bucket_starts (178-202)
  • project_daily_spend (396-449)
  • resolve_bucket_size (134-145)
src/synthorg/core/enums.py (1)
  • TaskStatus (226-252)
src/synthorg/budget/tracker.py (3)
  • budget_config (100-106)
  • get_total_cost (123-146)
  • get_records (194-234)
src/synthorg/hr/performance/tracker.py (1)
  • get_task_metrics (374-400)
🔇 Additional comments (12)
src/synthorg/api/controllers/analytics.py (12)

331-333: ACTIVE_AGENTS fallback to 0 when registry is unavailable.

This concern was raised in a previous review. When AgentRegistryService is unavailable, passing 0 as the fallback causes all buckets to show zero agents, which is misleading. Consider fetching the configured agent count as the fallback.


525-532: horizon_days accepts broader range than specification.

This concern was raised in a previous review. The linked issue specifies horizon values of 7, 14, or 30 days, but this accepts any integer from 1 to 90. Consider constraining to Literal[7, 14, 30] or an enum. As per coding guidelines, validate at system boundaries.


1-48: LGTM!

Imports are well-organized with proper TYPE_CHECKING guards, structured logging setup, and analytics event constants. Compliant with Python 3.14 guidelines (no future annotations import).


54-96: LGTM!

Well-structured frozen Pydantic model with appropriate field constraints. The allow_inf_nan=False setting properly prevents invalid float values in API responses. Documentation correctly notes that budget_used_percent > 100 indicates overrun.


99-155: LGTM!

Both response models are properly configured as frozen with appropriate field constraints. The confidence field correctly bounds to [0.0, 1.0], and days_until_exhausted is appropriately nullable.


168-214: LGTM!

Good improvements: the now parameter enables deterministic time bounding, and get_total_cost now properly bounds with end=end. The PEP 758 exception syntax is correctly used for re-raising critical errors while gracefully degrading on other failures with appropriate warning logs.


217-248: LGTM!

Clean implementation with proper registry availability check via has_agent_registry. The graceful degradation to configured agent count is appropriate, and the PEP 758 exception syntax correctly re-raises critical errors.


250-282: LGTM!

Clean dispatch helper that appropriately delegates to the specialized bucketing functions from trends.py.


340-401: LGTM!

Well-structured assembly helper. The sparkline alignment fix (midnight 6 days ago) correctly produces 7 daily buckets. Budget context and agent counts are properly resolved with appropriate fallbacks.


447-466: LGTM!

Proper ExceptionGroup handling for TaskGroup failures with appropriate logging and conversion to ServiceUnavailableError (503).


468-519: LGTM!

Clean implementation using TrendPeriod and TrendMetric StrEnums for input validation. The delegation to _fetch_trend_data_points keeps the endpoint method focused.


548-582: LGTM!

Well-implemented forecast endpoint. The lookback query correctly bounds with end=now, and project_daily_spend receives deterministic now parameter for reproducible projections. Structured logging with ANALYTICS_FORECAST_QUERIED event provides good observability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 24, 2026 07:15 — with GitHub Actions Inactive
@Aureliolo Aureliolo merged commit 16b61f5 into main Mar 24, 2026
31 of 33 checks passed
@Aureliolo Aureliolo deleted the feat/771-analytics-trends branch March 24, 2026 07:27
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 24, 2026 07:27 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 30, 2026
🤖 I have created a release *beep* *boop*
---
#MAJOR CHANGES; We got a somewhat working webui :)

##
[0.5.0](v0.4.9...v0.5.0)
(2026-03-30)


### Features

* add analytics trends and budget forecast API endpoints
([#798](#798))
([16b61f5](16b61f5))
* add department policies to default templates
([#852](#852))
([7a41548](7a41548))
* add remaining activity event types (task_started, tool_used,
delegation, cost_incurred)
([#832](#832))
([4252fac](4252fac))
* agent performance, activity, and history API endpoints
([#811](#811))
([9b75c1d](9b75c1d))
* Agent Profiles and Detail pages (biography, career, performance)
([#874](#874))
([62d7880](62d7880))
* app shell, Storybook, and CI/CD pipeline
([#819](#819))
([d4dde90](d4dde90))
* Approvals page with risk grouping, urgency indicators, batch actions
([#889](#889))
([4e9673d](4e9673d))
* Budget Panel page (P&L dashboard, breakdown charts, forecast)
([#890](#890))
([b63b0f1](b63b0f1))
* build infrastructure layer (API client, auth, WebSocket)
([#815](#815))
([9f01d3e](9f01d3e))
* CLI global options infrastructure, UI modes, exit codes, env vars
([#891](#891))
([fef4fc5](fef4fc5))
* CodeMirror editor and theme preferences toggle
([#905](#905),
[#807](#807))
([#909](#909))
([41fbedc](41fbedc))
* Company page (department/agent management)
([#888](#888))
([cfb88b0](cfb88b0))
* comprehensive hint coverage across all CLI commands
([#900](#900))
([937974e](937974e))
* config system extensions, per-command flags for
init/start/stop/status/logs
([#895](#895))
([32f83fe](32f83fe))
* configurable currency system replacing hardcoded USD
([#854](#854))
([b372551](b372551))
* Dashboard page (metric cards, activity feed, budget burn)
([#861](#861))
([7d519d5](7d519d5))
* department health, provider status, and activity feed endpoints
([#818](#818))
([6d5f196](6d5f196))
* design tokens and core UI components
([#833](#833))
([ed887f2](ed887f2))
* extend approval, meeting, and budget API responses
([#834](#834))
([31472bf](31472bf))
* frontend polish -- real-time UX, accessibility, responsive,
performance ([#790](#790),
[#792](#792),
[#791](#791),
[#793](#793))
([#917](#917))
([f04a537](f04a537))
* implement human roles and access control levels
([#856](#856))
([d6d8a06](d6d8a06))
* implement semantic conflict detection in workspace merge
([#860](#860))
([d97283b](d97283b))
* interaction components and animation patterns
([#853](#853))
([82d4b01](82d4b01))
* Login page + first-run bootstrap + Company page
([#789](#789),
[#888](#888))
([#896](#896))
([8758e8d](8758e8d))
* Meetings page with timeline viz, token bars, contribution formatting
([#788](#788))
([#904](#904))
([b207f46](b207f46))
* Messages page with threading, channel badges, sender indicators
([#787](#787))
([#903](#903))
([28293ad](28293ad))
* Org Chart force-directed view and drag-drop reassignment
([#872](#872),
[#873](#873))
([#912](#912))
([a68a938](a68a938))
* Org Chart page (living nodes, status, CRUD, department health)
([#870](#870))
([0acbdae](0acbdae))
* per-command flags for remaining commands, auto-behavior wiring,
help/discoverability
([#897](#897))
([3f7afa2](3f7afa2))
* Providers page with backend rework -- health, CRUD, subscription auth
([#893](#893))
([9f8dd98](9f8dd98))
* scaffold React + Vite + TypeScript + Tailwind project
([#799](#799))
([bd151aa](bd151aa))
* Settings page with search, dependency indicators, grouped rendering
([#784](#784))
([#902](#902))
([a7b9870](a7b9870))
* Setup Wizard rebuild with template comparison, cost estimator, theme
customization ([#879](#879))
([ae8b50b](ae8b50b))
* setup wizard UX -- template filters, card metadata, provider form
reuse ([#910](#910))
([7f04676](7f04676))
* setup wizard UX overhaul -- mode choice, step reorder, provider fixes
([#907](#907))
([ee964c4](ee964c4))
* structured ModelRequirement in template agent configs
([#795](#795))
([7433548](7433548))
* Task Board page (rich Kanban, filtering, dependency viz)
([#871](#871))
([04a19b0](04a19b0))


### Bug Fixes

* align frontend types with backend and debounce WS refetches
([#916](#916))
([134c11b](134c11b))
* auto-cleanup targets newly pulled images instead of old ones
([#884](#884))
([50e6591](50e6591))
* correct wipe backup-skip flow and harden error handling
([#808](#808))
([c05860f](c05860f))
* improve provider setup in wizard, subscription auth, dashboard bugs
([#914](#914))
([87bf8e6](87bf8e6))
* improve update channel detection and add config get command
([#814](#814))
([6b137f0](6b137f0))
* resolve all ESLint warnings, add zero-warnings enforcement
([#899](#899))
([079b46a](079b46a))
* subscription auth uses api_key, base URL optional for cloud providers
([#915](#915))
([f0098dd](f0098dd))


### Refactoring

* semantic analyzer cleanup -- shared filtering, concurrency, extraction
([#908](#908))
([81372bf](81372bf))


### Documentation

* brand identity and UX design system from
[#765](#765) exploration
([#804](#804))
([389a9f4](389a9f4))
* page structure and information architecture for v0.5.0 dashboard
([#809](#809))
([f8d6d4a](f8d6d4a))
* write UX design guidelines with WCAG-verified color system
([#816](#816))
([4a4594e](4a4594e))


### Tests

* add unit tests for agent hooks and page components
([#875](#875))
([#901](#901))
([1d81546](1d81546))


### CI/CD

* bump actions/deploy-pages from 4.0.5 to 5.0.0 in the major group
([#831](#831))
([01c19de](01c19de))
* bump astral-sh/setup-uv from 7.6.0 to 8.0.0 in
/.github/actions/setup-python-uv in the all group
([#920](#920))
([5f6ba54](5f6ba54))
* bump codecov/codecov-action from 5.5.3 to 6.0.0 in the major group
([#868](#868))
([f22a181](f22a181))
* bump github/codeql-action from 4.34.1 to 4.35.0 in the all group
([#883](#883))
([87a4890](87a4890))
* bump sigstore/cosign-installer from 4.1.0 to 4.1.1 in the
minor-and-patch group
([#830](#830))
([7a69050](7a69050))
* bump the all group with 3 updates
([#923](#923))
([ff27c8e](ff27c8e))
* bump wrangler from 4.76.0 to 4.77.0 in /.github in the minor-and-patch
group ([#822](#822))
([07d43eb](07d43eb))
* bump wrangler from 4.77.0 to 4.78.0 in /.github in the all group
([#882](#882))
([f84118d](f84118d))


### Maintenance

* add design system enforcement hook and component inventory
([#846](#846))
([15abc43](15abc43))
* add dev-only auth bypass for frontend testing
([#885](#885))
([6cdcd8a](6cdcd8a))
* add pre-push rebase check hook
([#855](#855))
([b637a04](b637a04))
* backend hardening -- eviction/size-caps and model validation
([#911](#911))
([81253d9](81253d9))
* bump axios from 1.13.6 to 1.14.0 in /web in the all group across 1
directory ([#922](#922))
([b1b0232](b1b0232))
* bump brace-expansion from 5.0.4 to 5.0.5 in /web
([#862](#862))
([ba4a565](ba4a565))
* bump eslint-plugin-react-refresh from 0.4.26 to 0.5.2 in /web
([#801](#801))
([7574bb5](7574bb5))
* bump faker from 40.11.0 to 40.11.1 in the minor-and-patch group
([#803](#803))
([14d322e](14d322e))
* bump https://github.com/astral-sh/ruff-pre-commit from v0.15.7 to
0.15.8 ([#864](#864))
([f52901e](f52901e))
* bump nginxinc/nginx-unprivileged from `6582a34` to `f99cc61` in
/docker/web in the all group
([#919](#919))
([df85e4f](df85e4f))
* bump nginxinc/nginx-unprivileged from `ccbac1a` to `6582a34` in
/docker/web ([#800](#800))
([f4e9450](f4e9450))
* bump node from `44bcbf4` to `71be405` in /docker/sandbox
([#827](#827))
([91bec67](91bec67))
* bump node from `5209bca` to `cf38e1f` in /docker/web
([#863](#863))
([66d6043](66d6043))
* bump picomatch in /site
([#842](#842))
([5f20bcc](5f20bcc))
* bump recharts 2-&gt;3 and @types/node 22-&gt;25 in /web
([#802](#802))
([a908800](a908800))
* Bump requests from 2.32.5 to 2.33.0
([#843](#843))
([41daf69](41daf69))
* bump smol-toml from 1.6.0 to 1.6.1 in /site
([#826](#826))
([3e5dbe4](3e5dbe4))
* bump the all group with 3 updates
([#921](#921))
([7bace0b](7bace0b))
* bump the minor-and-patch group across 1 directory with 2 updates
([#829](#829))
([93e611f](93e611f))
* bump the minor-and-patch group across 1 directory with 3 updates
([#841](#841))
([7010c8e](7010c8e))
* bump the minor-and-patch group across 1 directory with 3 updates
([#869](#869))
([548cee5](548cee5))
* bump the minor-and-patch group in /site with 2 updates
([#865](#865))
([9558101](9558101))
* bump the minor-and-patch group with 2 updates
([#867](#867))
([4830706](4830706))
* consolidate Dependabot groups to 1 PR per ecosystem
([06d2556](06d2556))
* consolidate Dependabot groups to 1 PR per ecosystem
([#881](#881))
([06d2556](06d2556))
* improve worktree skill with full dep sync and status enhancements
([#906](#906))
([772c625](772c625))
* remove Vue remnants and document framework decision
([#851](#851))
([bf2adf6](bf2adf6))
* update web dependencies and fix brace-expansion CVE
([#880](#880))
([a7a0ed6](a7a0ed6))
* upgrade to Storybook 10 and TypeScript 6
([#845](#845))
([52d95f2](52d95f2))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

feat: analytics trends and budget forecast API endpoints

1 participant