Skip to content

feat: improve AI personal analytics queries - #1514

Merged
aleksdotbar merged 14 commits into
mainfrom
codex/ai-personal-gold
Jul 1, 2026
Merged

feat: improve AI personal analytics queries#1514
aleksdotbar merged 14 commits into
mainfrom
codex/ai-personal-gold

Conversation

@aleksdotbar

@aleksdotbar aleksdotbar commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Adds the improved AI personal analytics query set, catalog rows, and ClickHouse gold views.

Supports the new AI drilldown counters and charts with unified person-level AI activity data.

Summary by CodeRabbit

  • New Features
    • Added AI personal analytics metric setup, including catalog/threshold seeding and predefined metric query references.
    • Introduced updated analytics views for daily, period, trend, cost rollups, and unified per-person peer counter calculations.
  • Bug Fixes
    • Improved analytics generation by excluding records with missing or empty emails.
  • Chores
    • Expanded bronze placeholder schemas to support new provenance, action, and media fields required by the analytics pipeline.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds AI personal analytics ClickHouse views, bronze placeholder schema updates, two SeaORM migrations for metric queries and catalog seeds, and migrator registration.

Changes

AI Personal Analytics Migrations

Layer / File(s) Summary
Bronze placeholder schema updates
src/ingestion/scripts/create-bronze-placeholders.sh
Extends silver.class_ai_dev_usage with new identifier, metric, provenance, and timestamp columns, and adds reconciliation for missing columns in silver.class_ai_dev_usage and bronze_zoom.participants.
ClickHouse gold views
src/ingestion/scripts/migrations/20260623000000_ai_personal_gold_views.sql
Creates the daily, period, counter, cost, and trend views that aggregate AI dev and assistant usage in the insight schema.
Metric query rows migration
src/backend/services/analytics-api/src/migration/m20260623_000001_ai_personal_queries.rs
Defines fixed metric IDs and query builders, then upserts three metrics rows and removes them on rollback.
Metric catalog seed migration
src/backend/services/analytics-api/src/migration/m20260623_000002_seed_ai_personal_metric_catalog.rs
Defines the seed catalog, upserts metric catalog and threshold rows, links them to the query catalog, and keeps the migration forward-only.
Migrator registration
src/backend/services/analytics-api/src/migration/mod.rs
Adds the new migration modules and registers both migrations in Migrator::migrations().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • mitasovr
  • cyberantonz
  • ktursunov

Poem

🐇 Hop hop, the gold views gleam,
Seeds and queries join the stream.
Bronze wakes with columns new,
AI metrics bloom in view ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: improved AI personal analytics queries and supporting data views.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/ai-personal-gold

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

❤️ Share

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

@aleksdotbar
aleksdotbar marked this pull request as ready for review June 29, 2026 08:28
@aleksdotbar
aleksdotbar requested a review from a team as a code owner June 29, 2026 08:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In
`@src/backend/services/analytics-api/src/migration/m20260623_000002_seed_ai_personal_metric_catalog.rs`:
- Around line 128-133: The INSERT_LINK_SQL in
m20260623_000002_seed_ai_personal_metric_catalog.rs currently links every
metric_catalog row matching metric_key, which can include tenant-specific rows.
Update the SELECT used by the catalog link insert so it only targets the global
seed rows created by the migration (the ones with tenant_id set to NULL),
keeping the link scoped to the global catalog entry and avoiding accidental
links to tenant-specific catalog rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ffe0f64-b1d8-4270-ad68-8d147a1c7f93

📥 Commits

Reviewing files that changed from the base of the PR and between da10446 and a9dba614007102dc4a616fca3c4032777ca37627.

📒 Files selected for processing (4)
  • src/backend/services/analytics-api/src/migration/m20260623_000001_ai_personal_queries.rs
  • src/backend/services/analytics-api/src/migration/m20260623_000002_seed_ai_personal_metric_catalog.rs
  • src/backend/services/analytics-api/src/migration/mod.rs
  • src/ingestion/scripts/migrations/20260623000000_ai_personal_gold_views.sql

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
@aleksdotbar
aleksdotbar force-pushed the codex/ai-personal-gold branch from 5d6de19 to 2c3573e Compare June 29, 2026 21:49
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
src/ingestion/scripts/create-bronze-placeholders.sh (2)

942-960: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Upgrade existing bronze_zoom.participants placeholders too.

This table predated the PR, but the new columns are only present in the create path. On warm e2e/dev ClickHouse volumes, the old placeholder survives and dbt will still see the stale contract when it selects the newly added Zoom fields. Mirror the jira_issue pattern here with ADD COLUMN IF NOT EXISTS for the added columns.

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

In `@src/ingestion/scripts/create-bronze-placeholders.sh` around lines 942 - 960,
The bronze_zoom.participants placeholder is missing the newly added Zoom fields
on existing ClickHouse volumes, so the create script needs to backfill them for
warm environments. Update the create-bronze-placeholders.sh logic around the
bronze_zoom.participants definition to mirror the jira_issue pattern by issuing
ADD COLUMN IF NOT EXISTS statements for the new columns, using the
bronze_zoom.participants table name and the added field names from the diff so
stale contracts are upgraded in place.

192-227: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reconcile the expanded silver.class_ai_dev_usage schema on reruns.

CREATE TABLE IF NOT EXISTS will not add these new columns to an older placeholder that already exists on a reused cluster. That leaves the pre-PR schema in place, so the new AI personal gold-view migration can still fail before dbt has a chance to replace the table. Add an else branch here with ALTER TABLE ... ADD COLUMN IF NOT EXISTS ..., like the bronze_jira.jira_issue reconciliation below.

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

In `@src/ingestion/scripts/create-bronze-placeholders.sh` around lines 192 - 227,
The `silver.class_ai_dev_usage` placeholder creation only handles first-time
setup, so reruns on an existing cluster can leave the old schema unchanged. Add
schema reconciliation in `create-bronze-placeholders.sh` by extending the
existing `if ! ch_table_exists silver class_ai_dev_usage` block with an `else`
branch that uses `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` for the newly added
columns, matching the pattern used for `bronze_jira.jira_issue`. Make sure the
fix is applied around the `CREATE TABLE IF NOT EXISTS` logic for
`silver.class_ai_dev_usage` so older placeholders are upgraded in place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/ingestion/scripts/create-bronze-placeholders.sh`:
- Around line 942-960: The bronze_zoom.participants placeholder is missing the
newly added Zoom fields on existing ClickHouse volumes, so the create script
needs to backfill them for warm environments. Update the
create-bronze-placeholders.sh logic around the bronze_zoom.participants
definition to mirror the jira_issue pattern by issuing ADD COLUMN IF NOT EXISTS
statements for the new columns, using the bronze_zoom.participants table name
and the added field names from the diff so stale contracts are upgraded in
place.
- Around line 192-227: The `silver.class_ai_dev_usage` placeholder creation only
handles first-time setup, so reruns on an existing cluster can leave the old
schema unchanged. Add schema reconciliation in `create-bronze-placeholders.sh`
by extending the existing `if ! ch_table_exists silver class_ai_dev_usage` block
with an `else` branch that uses `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` for
the newly added columns, matching the pattern used for `bronze_jira.jira_issue`.
Make sure the fix is applied around the `CREATE TABLE IF NOT EXISTS` logic for
`silver.class_ai_dev_usage` so older placeholders are upgraded in place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6546120c-90c1-4e2a-93cc-5a4c6d4b397a

📥 Commits

Reviewing files that changed from the base of the PR and between a9dba614007102dc4a616fca3c4032777ca37627 and b9a0b7e.

📒 Files selected for processing (5)
  • src/backend/services/analytics-api/src/migration/m20260623_000001_ai_personal_queries.rs
  • src/backend/services/analytics-api/src/migration/m20260623_000002_seed_ai_personal_metric_catalog.rs
  • src/backend/services/analytics-api/src/migration/mod.rs
  • src/ingestion/scripts/create-bronze-placeholders.sh
  • src/ingestion/scripts/migrations/20260623000000_ai_personal_gold_views.sql
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/backend/services/analytics-api/src/migration/mod.rs
  • src/ingestion/scripts/migrations/20260623000000_ai_personal_gold_views.sql
  • src/backend/services/analytics-api/src/migration/m20260623_000001_ai_personal_queries.rs
  • src/backend/services/analytics-api/src/migration/m20260623_000002_seed_ai_personal_metric_catalog.rs

Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
@aleksdotbar
aleksdotbar force-pushed the codex/ai-personal-gold branch from e68af32 to 0adfda5 Compare June 29, 2026 23:04
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@src/ingestion/scripts/create-bronze-placeholders.sh`:
- Around line 229-243: The ALTER TABLE block in create-bronze-placeholders.sh
for silver.class_ai_dev_usage must only run for placeholder tables, not the real
dbt-backed model. Update the else branch that issues the schema reconciliation
so it first checks the table’s placeholder marker/comment
(INSIGHT_PLACEHOLDER_v1) before applying any ADD COLUMN statements. Keep the
guard close to the existing silver.class_ai_dev_usage reconciliation path so
reruns do not mutate a materialized production table.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f17162d-e400-42b4-8cfb-74ad598941db

📥 Commits

Reviewing files that changed from the base of the PR and between b9a0b7e and 03ee65a.

📒 Files selected for processing (3)
  • src/backend/services/analytics-api/src/migration/m20260623_000001_ai_personal_queries.rs
  • src/backend/services/analytics-api/src/migration/m20260623_000002_seed_ai_personal_metric_catalog.rs
  • src/ingestion/scripts/create-bronze-placeholders.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/backend/services/analytics-api/src/migration/m20260623_000002_seed_ai_personal_metric_catalog.rs
  • src/backend/services/analytics-api/src/migration/m20260623_000001_ai_personal_queries.rs

Comment thread src/ingestion/scripts/create-bronze-placeholders.sh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/ingestion/scripts/create-bronze-placeholders.sh (1)

241-252: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reconcile the full class_ai_dev_usage placeholder contract.

This ALTER TABLE block still leaves older placeholders missing several columns that the create branch now defines, including total_lines_added, accepted_lines_added, spec_lines, session_count, total_chat_messages, cost_cents, prs_with_cc_count, and prs_total_count. On a warm cluster, rerunning this script will therefore keep silver.class_ai_dev_usage out of sync with the new AI analytics schema instead of repairing it.

Suggested fix
 ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS lines_removed Nullable(Float64);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS total_lines_added Nullable(Float64);
 ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS total_lines_removed Nullable(Float64);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS accepted_lines_added Nullable(Float64);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS spec_lines Nullable(Float64);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS session_count Nullable(Float64);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS total_chat_messages Nullable(Float64);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS cost_cents Nullable(UInt32);
 ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS commits_count Nullable(UInt32);
 ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS pull_requests_count Nullable(UInt32);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS prs_with_cc_count Nullable(UInt32);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS prs_total_count Nullable(UInt32);
 ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS tool_action_breakdown_json Nullable(String);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/scripts/create-bronze-placeholders.sh` around lines 241 - 252,
The `ALTER TABLE` reconciliation for `silver.class_ai_dev_usage` is incomplete,
so `create-bronze-placeholders.sh` does not fully match the newer placeholder
contract. Update the `run_ch` SQL block in the `class_ai_dev_usage` section to
add the missing columns that the create path already defines, including
`total_lines_added`, `accepted_lines_added`, `spec_lines`, `session_count`,
`total_chat_messages`, `cost_cents`, `prs_with_cc_count`, and `prs_total_count`,
alongside the existing `ADD COLUMN IF NOT EXISTS` statements. Ensure the
warm-cluster repair path mirrors the current schema exactly so reruns bring
`silver.class_ai_dev_usage` back into sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ingestion/scripts/create-bronze-placeholders.sh`:
- Around line 1052-1090: The `create-bronze-placeholders.sh` branch for
`bronze_cursor.cursor_daily_usage` only creates the table when it does not
already exist, so stale warm-cluster placeholders never get upgraded to the new
schema. Add a reconciliation path in this section that detects an existing table
and aligns it with the current `CREATE TABLE` definition, either by adding
missing columns or recreating/migrating the placeholder safely. Use the
`ch_table_exists` check and the `run_ch` block around
`bronze_cursor.cursor_daily_usage` as the entry point for the fix.

---

Outside diff comments:
In `@src/ingestion/scripts/create-bronze-placeholders.sh`:
- Around line 241-252: The `ALTER TABLE` reconciliation for
`silver.class_ai_dev_usage` is incomplete, so `create-bronze-placeholders.sh`
does not fully match the newer placeholder contract. Update the `run_ch` SQL
block in the `class_ai_dev_usage` section to add the missing columns that the
create path already defines, including `total_lines_added`,
`accepted_lines_added`, `spec_lines`, `session_count`, `total_chat_messages`,
`cost_cents`, `prs_with_cc_count`, and `prs_total_count`, alongside the existing
`ADD COLUMN IF NOT EXISTS` statements. Ensure the warm-cluster repair path
mirrors the current schema exactly so reruns bring `silver.class_ai_dev_usage`
back into sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 325baf61-ab07-407b-9a2e-b7a8d86367c1

📥 Commits

Reviewing files that changed from the base of the PR and between 7afa51e and ffedf93.

📒 Files selected for processing (1)
  • src/ingestion/scripts/create-bronze-placeholders.sh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/ingestion/scripts/create-bronze-placeholders.sh (1)

241-252: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reconcile the full class_ai_dev_usage placeholder contract.

This ALTER TABLE block still leaves older placeholders missing several columns that the create branch now defines, including total_lines_added, accepted_lines_added, spec_lines, session_count, total_chat_messages, cost_cents, prs_with_cc_count, and prs_total_count. On a warm cluster, rerunning this script will therefore keep silver.class_ai_dev_usage out of sync with the new AI analytics schema instead of repairing it.

Suggested fix
 ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS lines_removed Nullable(Float64);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS total_lines_added Nullable(Float64);
 ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS total_lines_removed Nullable(Float64);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS accepted_lines_added Nullable(Float64);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS spec_lines Nullable(Float64);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS session_count Nullable(Float64);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS total_chat_messages Nullable(Float64);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS cost_cents Nullable(UInt32);
 ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS commits_count Nullable(UInt32);
 ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS pull_requests_count Nullable(UInt32);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS prs_with_cc_count Nullable(UInt32);
+ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS prs_total_count Nullable(UInt32);
 ALTER TABLE silver.class_ai_dev_usage ADD COLUMN IF NOT EXISTS tool_action_breakdown_json Nullable(String);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/scripts/create-bronze-placeholders.sh` around lines 241 - 252,
The `ALTER TABLE` reconciliation for `silver.class_ai_dev_usage` is incomplete,
so `create-bronze-placeholders.sh` does not fully match the newer placeholder
contract. Update the `run_ch` SQL block in the `class_ai_dev_usage` section to
add the missing columns that the create path already defines, including
`total_lines_added`, `accepted_lines_added`, `spec_lines`, `session_count`,
`total_chat_messages`, `cost_cents`, `prs_with_cc_count`, and `prs_total_count`,
alongside the existing `ADD COLUMN IF NOT EXISTS` statements. Ensure the
warm-cluster repair path mirrors the current schema exactly so reruns bring
`silver.class_ai_dev_usage` back into sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ingestion/scripts/create-bronze-placeholders.sh`:
- Around line 1052-1090: The `create-bronze-placeholders.sh` branch for
`bronze_cursor.cursor_daily_usage` only creates the table when it does not
already exist, so stale warm-cluster placeholders never get upgraded to the new
schema. Add a reconciliation path in this section that detects an existing table
and aligns it with the current `CREATE TABLE` definition, either by adding
missing columns or recreating/migrating the placeholder safely. Use the
`ch_table_exists` check and the `run_ch` block around
`bronze_cursor.cursor_daily_usage` as the entry point for the fix.

---

Outside diff comments:
In `@src/ingestion/scripts/create-bronze-placeholders.sh`:
- Around line 241-252: The `ALTER TABLE` reconciliation for
`silver.class_ai_dev_usage` is incomplete, so `create-bronze-placeholders.sh`
does not fully match the newer placeholder contract. Update the `run_ch` SQL
block in the `class_ai_dev_usage` section to add the missing columns that the
create path already defines, including `total_lines_added`,
`accepted_lines_added`, `spec_lines`, `session_count`, `total_chat_messages`,
`cost_cents`, `prs_with_cc_count`, and `prs_total_count`, alongside the existing
`ADD COLUMN IF NOT EXISTS` statements. Ensure the warm-cluster repair path
mirrors the current schema exactly so reruns bring `silver.class_ai_dev_usage`
back into sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 325baf61-ab07-407b-9a2e-b7a8d86367c1

📥 Commits

Reviewing files that changed from the base of the PR and between 7afa51e and ffedf93.

📒 Files selected for processing (1)
  • src/ingestion/scripts/create-bronze-placeholders.sh
🛑 Comments failed to post (1)
src/ingestion/scripts/create-bronze-placeholders.sh (1)

1052-1090: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Add a reconciliation path for pre-existing bronze_cursor.cursor_daily_usage.

This branch only fixes fresh clusters. If a warm environment already has the older 14-column placeholder, CREATE TABLE IF NOT EXISTS is skipped and the new dbt inputs (tenant_id, source_id, unique_key, userId, day, totalLinesAdded, etc.) never get added. That leaves reruns broken in exactly the stale-placeholder scenario this comment describes.

Suggested fix
 if ! ch_table_exists bronze_cursor cursor_daily_usage; then
   echo "  Creating placeholder: bronze_cursor.cursor_daily_usage"
   run_ch <<'SQL'
 CREATE TABLE IF NOT EXISTS bronze_cursor.cursor_daily_usage (
   ...
 ) ENGINE = ReplacingMergeTree(_airbyte_extracted_at) ORDER BY unique_key;
 SQL
+else
+  echo "  Reconciling placeholder schema: bronze_cursor.cursor_daily_usage"
+  run_ch <<'SQL'
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS tenant_id String;
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS source_id String;
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS unique_key String;
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS userId Nullable(String);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS day Nullable(String);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS date Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS isActive Nullable(UInt8);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS chatRequests Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS cmdkUsages Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS composerRequests Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS agentRequests Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS bugbotUsages Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalTabsShown Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalTabsAccepted Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalAccepts Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalApplies Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalRejects Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalLinesAdded Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalLinesDeleted Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS acceptedLinesAdded Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS acceptedLinesDeleted Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS mostUsedModel Nullable(String);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS tabMostUsedExtension Nullable(String);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS applyMostUsedExtension Nullable(String);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS clientVersion Nullable(String);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS subscriptionIncludedReqs Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS usageBasedReqs Nullable(Float64);
+ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS apiKeyReqs Nullable(Float64);
+SQL
 fi
📝 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.

if ! ch_table_exists bronze_cursor cursor_daily_usage; then
  echo "  Creating placeholder: bronze_cursor.cursor_daily_usage"
  run_ch <<'SQL'
CREATE TABLE IF NOT EXISTS bronze_cursor.cursor_daily_usage (
    tenant_id                String,
    source_id                String,
    unique_key               String,
    userId                   Nullable(String),
    email                    String,
    day                      Nullable(String),
    date                     Nullable(Float64),
    isActive                 Nullable(UInt8),
    chatRequests             Nullable(Float64),
    cmdkUsages               Nullable(Float64),
    composerRequests         Nullable(Float64),
    agentRequests            Nullable(Float64),
    bugbotUsages             Nullable(Float64),
    totalTabsShown           Nullable(Float64),
    totalTabsAccepted        Nullable(Float64),
    totalAccepts             Nullable(Float64),
    totalApplies             Nullable(Float64),
    totalRejects             Nullable(Float64),
    totalLinesAdded          Nullable(Float64),
    totalLinesDeleted        Nullable(Float64),
    acceptedLinesAdded       Nullable(Float64),
    acceptedLinesDeleted     Nullable(Float64),
    mostUsedModel            Nullable(String),
    tabMostUsedExtension     Nullable(String),
    applyMostUsedExtension   Nullable(String),
    clientVersion            Nullable(String),
    subscriptionIncludedReqs Nullable(Float64),
    usageBasedReqs           Nullable(Float64),
    apiKeyReqs               Nullable(Float64),
    _airbyte_raw_id        String        DEFAULT toString(generateUUIDv4()),
    _airbyte_extracted_at  DateTime64(3) DEFAULT now64(3),
    _airbyte_meta          String        DEFAULT '{}',
    _airbyte_generation_id UInt32        DEFAULT 0
) ENGINE = ReplacingMergeTree(_airbyte_extracted_at) ORDER BY unique_key;
SQL
else
  echo "  Reconciling placeholder schema: bronze_cursor.cursor_daily_usage"
  run_ch <<'SQL'
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS tenant_id String;
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS source_id String;
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS unique_key String;
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS userId Nullable(String);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS day Nullable(String);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS date Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS isActive Nullable(UInt8);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS chatRequests Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS cmdkUsages Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS composerRequests Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS agentRequests Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS bugbotUsages Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalTabsShown Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalTabsAccepted Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalAccepts Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalApplies Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalRejects Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalLinesAdded Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS totalLinesDeleted Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS acceptedLinesAdded Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS acceptedLinesDeleted Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS mostUsedModel Nullable(String);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS tabMostUsedExtension Nullable(String);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS applyMostUsedExtension Nullable(String);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS clientVersion Nullable(String);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS subscriptionIncludedReqs Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS usageBasedReqs Nullable(Float64);
ALTER TABLE bronze_cursor.cursor_daily_usage ADD COLUMN IF NOT EXISTS apiKeyReqs Nullable(Float64);
SQL
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ingestion/scripts/create-bronze-placeholders.sh` around lines 1052 -
1090, The `create-bronze-placeholders.sh` branch for
`bronze_cursor.cursor_daily_usage` only creates the table when it does not
already exist, so stale warm-cluster placeholders never get upgraded to the new
schema. Add a reconciliation path in this section that detects an existing table
and aligns it with the current `CREATE TABLE` definition, either by adding
missing columns or recreating/migrating the placeholder safely. Use the
`ch_table_exists` check and the `run_ch` block around
`bronze_cursor.cursor_daily_usage` as the entry point for the fix.

@aleksdotbar
aleksdotbar merged commit 5274e39 into main Jul 1, 2026
23 of 24 checks passed
ktursunov pushed a commit to ktursunov/insight that referenced this pull request Jul 2, 2026
… connector)

Upstream's AI-personal-gold work (constructorfabric#1514) added the ai_person_counter_daily
catalog vector — 10 per-person AI daily counters (accepted/removed lines, cost,
active days, assistant messages/actions, chat/dev conversations, tool
acceptance). Like ai_bullet_rows it needs a Cursor/Claude/ChatGPT connector to
have data, so add it to SKIP_TABLES. Metric-coverage gate back to 0 missing
(44/106).

Signed-off-by: Konstantin Tursunov <Konstantin.Tursunov@constructor.tech>
cyberantonz added a commit to cyberantonz/insight that referenced this pull request Jul 2, 2026
Migration 20260623_ai_personal_gold_views (from constructorfabric#1514 ai-personal-gold)
selects and GROUP BYs c.source / c.source_id from silver.class_ai_dev_usage,
but the seed's placeholder DDL was never updated to add those columns.
On a fresh ClickHouse the view creation fails with UNKNOWN_IDENTIFIER
("Identifier 'c.source' cannot be resolved"), breaking `seed silver`.

Add source/source_id (String) to the class_ai_dev_usage placeholder,
matching the real silver model — same placeholder-mirror gap the file
already documents for the post-20260601 columns. Not gear-related;
surfaced while validating the analytics-api gear on a clean k8s cluster.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>
cyberantonz added a commit that referenced this pull request Jul 2, 2026
* analytics-api: run as an idiomatic gears-rust gear

Migrate analytics-api from a self-hosted axum::serve service to a
gears-rust host on toolkit::bootstrap::run_server:

- main.rs: toolkit bootstrap (AppConfig::load_or_default → run_server /
  gear::run_migrate); links the api-gateway REST host + auth/authz/tenant
  system gears via inventory.
- gear.rs (new): AnalyticsApiGear #[toolkit::gear(capabilities=[rest])].
  Gear::init reconstructs the full former run_server startup (self-managed
  MariaDB pool + 45 migrations + CHECK/product-default probes, redis
  catalog cache, ClickHouse + Identity clients, schema validator,
  admin-CRUD service), stores AppState in a OnceLock, and tokio::spawns the
  one-shot validate_all() scan. RestApiCapability::register_rest wires the
  OperationBuilder routes into the host router + Extension(AppState).
- Auth: host runs auth_disabled (the platform api-gateway is the sole
  authenticator); it injects a SecurityContext with DEFAULT_TENANT_ID
  (== the single-tenant default the seeded data uses). The former
  hand-rolled tenant_middleware becomes a thin, stateless layer that reads
  toolkit_security::SecurityContext and overrides the tenant from
  X-Insight-Tenant-Id when present. The local SecurityContext struct is
  removed; handlers + domain services now use toolkit_security only.
- DB stays self-managed (no toolkit `db` capability; ClickHouse is not a
  toolkit-db backend anyway).
- config.rs: figment loader removed; GearConfig is deserialized from
  gears.analytics-api.config via ctx.config(). New config/insight.yaml in
  the gears layout (auth_disabled: true), modeled on api-gateway's
  no-auth.yaml.

Build, clippy (pedantic), and tests (325 pass) all green. Helm/compose
manifests still reference the old ANALYTICS__* env model and are updated
in a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* analytics-api: wire deploy for the gears-rust config model

The gear migration switched analytics-api from figment (`ANALYTICS__*`
env, no config file) to toolkit AppConfig (a gears-layout YAML at `-c`
plus `APP__*` env overrides). Update both deploy paths to match:

- Dockerfile: bake config/ into the image; launch
  `-c /app/config/insight.yaml run`.
- docker-compose.yml: bind-mount the config dir, add `-c … run`, and
  rename every `ANALYTICS__<x>` env var to
  `APP__gears__analytics-api__config__<x>`.
- Helm (services/analytics-api): new configmap.yaml (the gears structure
  with the auth-disabled api-gateway host); deployment mounts it at
  /app/config/analytics.yaml, launches `-c … run`, fixes the migrate
  initContainer likewise, and rolls on config change via checksum/config.
- Umbrella secret (charts/insight) + compose-app-secrets.sh: rename the
  generated `ANALYTICS__*` keys to `APP__gears__analytics-api__config__*`
  (hyphenated names delivered via envFrom, same as api-gateway's
  `APP__gears__oidc-authn-plugin__*`). Structural config now lives in the
  mounted ConfigMap; the Secret carries only the leaf overrides
  (DB/clickhouse/redis/identity URLs + tenant_default_id).
- Drop the now-host-owned `bind_addr` env var; refresh stale comments
  (the /health-400 CrashLoop rationale no longer applies — the
  auth-disabled host injects DEFAULT_TENANT_ID and /health is off the
  auth path).

Validated: `docker compose config -q`, `helm template` of the chart, and
the gears config YAML all parse cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* style(analytics-api): cargo fmt after the gear migration

Apply rustfmt to the files touched by the gears-rust migration — the
migration left several formatting violations (fmt is not covered by
clippy, so build/clippy/test were green while `cargo fmt --check` was
not). No behavioral change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* analytics-api: use identifier-safe env-override keys for the gear

Runtime fix: with the gears-rust host, config overrides arrive as
env vars under `gears.<name>.config.*`. The gear name is
`analytics-api` (kebab, macro-enforced), so the natural env key was
`APP__gears__analytics-api__config__*` — but hyphenated env-var names
are silently dropped by the compose `sh`/dash entrypoint (dash discards
env names that aren't valid identifiers) and skipped by Kubernetes
`envFrom`. The result: every override (DB URL, tenant, clickhouse,
redis, identity) was stranded at its YAML default, so the gear tried to
reach `localhost:3306` and failed init with "pool timed out". (The old
service worked because `ANALYTICS__*` had no hyphen.)

Fix, keeping the kebab gear name:
- Rename every override key to the identifier-safe alias
  `APP__gears__analytics_api__config__*` (compose env, umbrella Secret,
  gitops compose-app-secrets.sh) — survives dash and k8s envFrom.
- The toolkit does no kebab/snake normalization, so bridge it in
  main.rs: `fold_gear_env_alias` deep-merges the `analytics_api` gears
  alias into `analytics-api` right after config load (both `run` and
  `migrate` paths). Verified via `--print-config`: the underscore env
  keys now land in `gears.analytics-api.config`.

Build, clippy (-D warnings), fmt, and tests (325) all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* analytics-api: don't register /health (host gear owns it)

With the toolkit host, the api-gateway system gear's `rest_prepare`
already registers `/health` and `/healthz`. Our gear also registered
`/health`, so route composition panicked at boot with "Overlapping
method route. Handler for `GET /health` already exists".

Drop the gear's `/health` route (and the now-unused `handlers::health`).
Also build the gear's endpoints on a fresh sub-router so the
tenant-override middleware + `AppState` extension scope to analytics-api's
own routes and don't wrap the host's `/health`/`/healthz`/`/openapi.json`.

DB now connects (the earlier env-key fix landed); this was the next boot
panic. Build, clippy (-D warnings), fmt, tests (325) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* fix(gitops): drop backticks from compose-app-secrets heredoc comments

The analytics-api/identity config Secrets are emitted via an unquoted
`cat <<EOF` heredoc (unquoted so ${MDB_USER} etc. expand). Backticks in
the heredoc *body* comments were therefore run as commands, printing
`keep: command not found`, `APP__gears__analytics_api__config__*: command
not found`, `ANALYTICS__*: command not found` during `make deploy`. The
`keep` one was pre-existing; the APP__/ANALYTICS__ ones came in with the
env-key rename.

Remove the backticks from the heredoc comments (shell comments elsewhere
in the file are unaffected — bash doesn't substitute in `#` lines). Only
comment text was corrupted, not the Secret data, but the noise is gone
and the script is correct.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* fix(seed): mirror source/source_id in class_ai_dev_usage placeholder

Migration 20260623_ai_personal_gold_views (from #1514 ai-personal-gold)
selects and GROUP BYs c.source / c.source_id from silver.class_ai_dev_usage,
but the seed's placeholder DDL was never updated to add those columns.
On a fresh ClickHouse the view creation fails with UNKNOWN_IDENTIFIER
("Identifier 'c.source' cannot be resolved"), breaking `seed silver`.

Add source/source_id (String) to the class_ai_dev_usage placeholder,
matching the real silver model — same placeholder-mirror gap the file
already documents for the post-20260601 columns. Not gear-related;
surfaced while validating the analytics-api gear on a clean k8s cluster.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* analytics-api: address CodeRabbit review (PR #1546)

- config/insight.yaml: drop the committed `mysql://insight:insight@…`
  default; database_url/clickhouse_url are empty and injected per-deploy
  via APP__* env overrides (no credential in the checked-in runnable config).
- gear.rs: redact userinfo from redis_url before logging (redis:// URLs
  can embed credentials); add `check_config` that validates the gear
  config section + required URLs without touching any backend.
- main.rs: `check` subcommand now runs `gear::check_config` instead of a
  no-op success.
- charts secrets.yaml: percent-encode (urlquery) the MariaDB user/password
  in the analytics DSN, matching the identity DSN, so operator credentials
  with :/@/%/ chars produce a valid connection string.

Deferred (replied on PR): NetworkPolicy/trust-boundary + auth fail-closed
(the deliberate header-trust posture — infra follow-up), --print-config
redaction (operator debug flag, parity with api-gateway), bash DSN
url-encoding (pre-existing, also affects identity — needs shared helper),
Secret-change reload (Secret is externally managed in gitops).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* test(e2e): spawn analytics-api as a gears host

The gear migration changed how analytics-api boots — it's now a
toolkit::bootstrap host that needs `-c <gears-config> run` and reads
`APP__gears__analytics_api__config__*` (not the old `ANALYTICS__*`). The
e2e harness still launched the bare binary with `ANALYTICS__*` env, so the
new binary came up with empty config and the suite failed.

Update lib/analytics_api.py to spawn the way the deployment does:
- pass `-c <repo>/services/analytics-api/config/insight.yaml run`
- inject leaf overrides via APP__* (direct Popen execve preserves the
  hyphenated gear-name segments, unlike the compose sh entrypoint)
- override the REST-host bind on the api-gateway gear to loopback:port
- give grpc-hub a per-spawn UDS so parallel workers/re-runs don't collide
- widen the health-wait to 60s (host inits all system gears + migrations)
- /health is now host-served (public); refresh the stale tenant comments

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* test(analytics-api): unit-test the gear's unit-testable new code

Adds tests for the pure/new logic introduced by the gear migration and
refactors the route table to be testable without a DB:

- api/mod.rs: split the OperationBuilder chains into a stateless
  `build_operations(router, openapi)`; `register_routes` now just layers
  state/middleware over it and merges into the host router. A test drives
  `build_operations` so the full 20-endpoint route table + OpenAPI
  registration is exercised with no AppState/DB (0% → ~95%).
- main.rs: tests for `fold_gear_env_alias` + `deep_merge` (alias→canonical
  fold, object merge, leaf overwrite).
- gear.rs: tests for `redact_url` (strips userinfo) and `check_config`
  (missing section / empty required URLs).
- config.rs reaches 100% via the above.

Patch coverage for the crate rises ~40% → 60%. The remainder is
MariaDB/ClickHouse-bound boot + handler code (gear init, infra::db
connect, AdminThresholdService, REST handler bodies) that needs a live
DB — exercised by the e2e suite + the #[ignore]d live_tests, neither of
which runs in the unit-coverage job.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* ci(analytics-api): run DB-backed live_tests in the rust job (#1564)

Wire the existing `rust:` CI job to exercise analytics-api's `#[ignore]`d
live_tests against a real MariaDB — no new job, no ClickHouse (CH-gated
tests skip themselves).

CI (`.github/workflows/ci.yml`, `scripts/ci/*`):
- Add a `live_db` component flag (components.py) threaded into the matrix
  entry (changed.py); default false, true only for analytics-api.
- For live_db entries the rust job starts a MariaDB 11.4 container, applies
  migrations once up front via `analytics-api migrate` (a failed migration
  fails the job), then runs the live_tests in parallel with
  INTEGRATION_TESTS_MARIADB_URL set (`--include-ignored`). Both the migrate
  and the tests run under llvm-cov, so the DB paths now count toward coverage
  (patch coverage 60% -> 73%).

Parallel-safe live_tests (no DB reset):
- Migrations run once before the suite; tests never migrate or reset the DB.
- Isolation is per-tenant / per-metric-key, so the suite is parallel-safe:
  admin_threshold + catalog + schema_validator each own unique keys/tenants.
- Drop the per-test `reset_catalog` + `Migrator::up` blocks and the helper.
- Fix an admin_threshold key collision: use the full UUID (`metric_id.simple()`)
  instead of the v7 timestamp prefix `[..8]`, which collided in same-ms runs.
- Delete migration/live_tests.rs: obsolete now that the CI migrate step (plus
  the startup CHECK probe) covers "migrations apply on an empty DB".

Identity resolution already uses Testcontainers MariaDB (MariaDbFixture) in
the dotnet job — no CI change needed there.

Verified: 8/8 fresh-DB parallel runs green (362 tests, ~3s); no-DB run skips
all 24 live_tests cleanly; fmt + clippy -D warnings clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* test(analytics-api): HTTP-level live tests to close the handler coverage gap (#1564)

The service-layer live_tests exercise the domain logic directly but never
drive the axum handlers, leaving the extract -> delegate -> Result->Response
glue at 0% (new-code patch coverage stuck at 73%, under the 80% gate).

Add `api/http_live_tests.rs`: builds a full AppState against the live DB and
mounts the real route table via `register_routes`, then drives it with
`tower::oneshot`. ClickHouse/Identity clients point at an unreachable address
on purpose — DB-backed handlers return real 2xx; the CH/Identity ones exercise
their entry + error-mapping path. Covers the admin-threshold CRUD round-trip
(201/200/204 status mapping), the metric/column/catalog reads, and the
unknown-field rejection envelope. Same `#[ignore]` + `INTEGRATION_TESTS_MARIADB_URL`
gating as the domain live_tests; a fresh tenant per write keeps it parallel-safe.

Result: `admin/handlers.rs` and `catalog.rs` 100%, `api/mod.rs` (register_routes)
100%, patch coverage 73% -> 84% (clears the gate).

Also fix a latent bug the reset-removal exposed:
`response_link_map_omits_metrics_dropped_by_walk_all` DELETEs the GLOBAL
`product-default` seed threshold for `ic_kpis.tasks_closed` and never restored
it — `reset_catalog` used to rebuild the seed each run and hid this. Left as-is
it corrupts the shared seed (every subsequent run/reader sees the metric
permanently dropped) and fails on re-run. Fix: capture the row (as CHAR via CAST
— sqlx-mysql can't decode DECIMAL->f64), delete, resolve, then RESTORE it before
any assertion can unwind. The `fetch_links` surfaced-ids filter already keeps
metrics + links consistent during the delete window, so concurrent readers
never observe a phantom. Verified: full suite 3x on one DB, no reset, seed
intact (good/warn preserved), 372 passed each; no-DB run skips all 34
live_tests; fmt + clippy -D warnings clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* test(analytics-api): isolate the walk-drop test via tenant scoping, not shared mutation

Replace the capture->delete->restore approach (which mutated the GLOBAL
`product-default` seed row for `ic_kpis.tasks_closed`) with a fully isolated,
purely-additive fixture that proves the same ADR-003 `surfaced_ids` invariant
through tenant scoping alone — the model's own rule: a DEFAULT metric
(`tenant_id IS NULL`) is visible to everyone, and per-tenant threshold data
never intersects across tenants.

The test now inserts a GLOBAL catalog row with a UNIQUE key (catalog is
global-only in v1), links it into `metric_query_catalog`, and gives it a
threshold for ONE tenant. Resolving as that tenant surfaces it + its link;
resolving as any other (never-seen) tenant has no candidate, so `walk_all`
drops it and the surfaced-ids filter drops its link. No shared row is read or
mutated, nothing is deleted or restored.

Verified: shared seed untouched (tasks_closed product-default stays intact),
full suite 3x on one DB with no reset (372 passed each), no-DB run skips all
34 live_tests, fmt + clippy -D warnings clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* ci: scope a component's coverage report to its own code (fix dependency spillover)

`cargo llvm-cov --package X` reports every instrumented file, including
path-dependency crates compiled into X's test binary. So the analytics-api
report also carried `libs/insight-clickhouse` at ~15% (only `Client::new` is
hit from analytics-api) — and the gate buckets files into components via
`component_for`, so those lines land on the insight-clickhouse component.

Today this is masked: the gate merges all reports with max-hits, and this
branch also changes insight-clickhouse (its own job reports ~93%), so the union
wins. But on a PR that touches analytics-api and NOT insight-clickhouse, the
latter's job never runs — the only insight-clickhouse data is analytics-api's
15% spillover, which would fail the component's OVERALL_MIN (80%) gate. No
amount of insight-clickhouse tests fixes that; the spillover must not be
counted here in the first place.

Add a per-component `cover_ignore_regex` (threaded through changed.py) wired to
`cargo llvm-cov report --ignore-filename-regex`. Set `src/backend/libs/` on
analytics-api (the only rust service that links a shared lib). Each library
crate keeps self-reporting in its own job. Verified: the analytics-api report
no longer lists any insight-clickhouse file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

* test(insight-clickhouse): cover Client + Config Debug + Error Display (no ClickHouse)

Raise the crate's own coverage without a live ClickHouse by testing the pure,
connection-free surface that was untested: `Client::new` auth branches,
`query()` with/without a timeout, the `inner()`/`config()` accessors, and
`tenant_query` validation (lib.rs 46% -> 100% line); the password-redacting
`Config` Debug impl (config.rs -> 100%); and `Error` Display (error.rs ->
100%). Crate line coverage 88% -> 94%.

The remaining gap is `QueryBuilder::fetch_all` — query EXECUTION, which needs a
live ClickHouse and is out of scope here (cf/insight#1564 skips CH).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>

---------

Signed-off-by: Anton Zelenov <antonz@constructor.tech>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mozhaev-dev added a commit that referenced this pull request Jul 6, 2026
…#1527)

PR A of the #1527 split — the data-layer foundation. Establishes the shared
gold view the other collab modalities (#1528#1532) extend, and delivers the
Messaging modality's two counters.

- New gold view `insight.collab_person_counter_daily` (migration
  20260702000000): honest-NULL UNION ALL over the per-vendor silver chat
  classes (M365 Teams · Slack · Zulip), outer GROUP BY (person_id, metric_date)
  with the `if(countIf(x IS NOT NULL) > 0, sumIf …, NULL)` wrapper so a person
  with no source on a day is NULL, never a fake 0. LEFT JOIN insight.people for
  org_unit_id. Clone of #1514's ai_person_counter_daily shape.
    · messages_sent — Σ total_chat_messages across the three vendors.
    · channel_posts — Σ (channel_posts + channel_replies) across M365 · Slack
      for vendor comparability (Slack folds replies in already; M365 splits
      them); Zulip has no channel split → honest-NULL.
- dbt singular test `assert_collab_messaging_bounds.sql`: counters ≥ 0.
- Register the view in silver/_shared/gold_sources.yml.

Designed for extension: later modalities add columns + UNION branches, not new
vendor rows. Backend query_ref + catalog seed (PR B), e2e YAML (PR C) and FE
(insight-front) follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergei Mozhaev <mozhaev.dev@gmail.com>
cyberantonz pushed a commit to cyberantonz/insight that referenced this pull request Aug 5, 2026
…tructorfabric#1527)

PR D of the constructorfabric#1527 split — the frontend surface for the collaboration Messaging
counters (backend: gold view constructorfabric#1567 + query/seed). Mirrors the AI-adoption
peer-counters panel (constructorfabric#1514).

- metric-registry: add V2_IC_COLLAB_PEER_COUNTERS (…0053), the seeded collab
  Messaging peer-counter query.
- ic-extras: add useIcCollabPeerCounters (clone of useIcAiPeerCounters, same row
  shape + per-org_unit bands, different metric id).
- new CollabMessagingPanel: renders messages_sent + channel_posts vs the
  person's department under a "Messaging" card via the shared PeerStorySection.
  Fully catalog-driven — any counter row whose metric_key is in the catalog
  renders; unknown keys are dropped.
- mount it in the collaboration section drilldown (section-drilldown-sheet).
- catalog-snapshot mock: add the two collab_person_counter_daily.* metrics.
- vitest: renders both under "Messaging"; drops unseeded keys; renders nothing
  without person/range.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Sergey Mozhaev <Sergey.Mozhaev@constructor.tech>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants