Skip to content

feat(metrics): add metric evidence drilldowns - #2003

Closed
aleksdotbar wants to merge 11 commits into
mainfrom
feat/metric-drilldown
Closed

feat(metrics): add metric evidence drilldowns#2003
aleksdotbar wants to merge 11 commits into
mainfrom
feat/metric-drilldown

Conversation

@aleksdotbar

@aleksdotbar aleksdotbar commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add materialized evidence serving tables for Git, AI, collaboration, tasks, and wiki
  • expose capability-driven, paginated metric drilldowns through the unified metrics API
  • add human-facing evidence metadata, dimension filters, and CSV/XLSX exports
  • document the evidence contract and metric authoring workflow

Validation

  • cargo check -p analytics
  • dbt parse
  • dbt run --select tag:gold

Summary by CodeRabbit

  • New Features
    • Added metric drilldown endpoints for evidence-backed exploration (pagination with entity/dimension/period filtering).
    • Metric definitions now advertise drilldown availability, and metric results include a canonical selection plus optional drilldown details.
    • Added drilldown export as downloadable CSV and XLSX files.
    • Expanded evidence coverage across AI usage, collaboration, Git, tasks, and wiki metrics.
  • Documentation
    • Updated metrics concepts/design docs to cover the evidence contract, selection/drilldown rules, and export constraints.
  • Tests
    • Added integration and API coverage for drilldown validation and export behavior.

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>
Signed-off-by: Aleksandr Barkhatov <pm@aleks.bar>

# Conflicts:
#	src/ingestion/gold/collab_metric_observations.sql
#	src/ingestion/gold/git_metric_observations.sql
@aleksdotbar
aleksdotbar requested a review from a team as a code owner July 28, 2026 20:43
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Regenerate the connectors-ddl snapshot

This PR changes src/ingestion/**. If your change affects any
bronze / silver / gold schema, regenerate the committed DDL snapshot
and include it in this PR.

Prerequisites (details: src/ingestion/scripts/bootstrap-db/README.md):

  • docker + a fresh throwaway ClickHouse 25.7.5 (README "Local ClickHouse for testing")
  • .env from .env.bootstrap.example pointing at it; use the host LAN IP,
    reachable from both the host and connector containers
    (host.docker.internal does not resolve on the macOS host itself)
  • python3.12 or python3.11 on PATH (pinned dbt venv)
  • HubSpot + Salesforce credentials in .env — their discover calls the
    live APIs; without them, apply ../connectors-ddl/{hubspot,salesforce}.sql
    (relative to bootstrap-db/) to seed their bronze, then run the dbt step
cd src/ingestion/scripts/bootstrap-db
set -a; source pins.env; source .env; set +a
./bootstrap-db.sh connectors-config.yaml   # fresh ClickHouse 25.7.5
./dump-ddl.sh                              # writes scripts/connectors-ddl/*.sql

Commit the resulting scripts/connectors-ddl/*.sql diff. If nothing
changed, no snapshot update is needed. (Regeneration is manual for now.)

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@aleksdotbar, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 31 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8291da36-77ae-42bb-9812-6218e5c5fd59

📥 Commits

Reviewing files that changed from the base of the PR and between 9935e09 and 2b1677b.

⛔ Files ignored due to path filters (1)
  • src/backend/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • src/backend/Cargo.toml
  • src/backend/services/analytics/Cargo.toml
  • src/backend/services/analytics/src/domain/mod.rs
  • src/ingestion/gold/git_metric_evidence.sql
  • src/ingestion/gold/task_worklog_flow.sql
📝 Walkthrough

Walkthrough

Metric evidence is added across analytics storage, ClickHouse gold models, API contracts, metric-result responses, drilldown query/export handlers, validation, and documentation. The new endpoints return typed paginated evidence and bounded CSV/XLSX exports.

Changes

Metric evidence drilldown

Layer / File(s) Summary
Evidence contract and storage metadata
src/backend/services/analytics/src/domain/metric_definitions/*, src/backend/services/analytics/src/migration/*
Adds evidence relations, granularities, schema metadata, source seeding, revision-conditional updates, and evidence validation.
Evidence materialization models
src/ingestion/gold/*metric_evidence.sql, src/ingestion/gold/*metric_observations.sql, src/ingestion/gold/schema.yml
Adds standardized evidence tables for AI, Git, collaboration, task, and wiki sources, then derives observations from evidence records.
Drilldown validation and query domain
src/backend/services/analytics/src/domain/metric_drilldown/mod.rs
Adds DTOs, capability loading, request validation, evidence planning, query compilation, typed presentation, cursor pagination, and snapshot verification.
Drilldown endpoints and exports
src/backend/services/analytics/src/api/metric_drilldown.rs, src/backend/services/analytics/src/api/mod.rs
Adds query and export endpoints with bounded execution, concurrency limits, CSV/XLSX generation, and OpenAPI wiring.
Metric result and definition capability wiring
src/backend/services/analytics/src/api/metric_results.rs, src/backend/services/analytics/src/domain/metric_results/*, src/backend/services/analytics/src/domain/metric_definitions/listing.rs
Adds canonical selections and optional drilldown capabilities to metric results and definition views.
Public contracts, documentation, and validation
docs/components/backend/analytics/openapi.json, docs/domain/metrics/*, src/backend/services/analytics/src/**/*tests.rs, src/ingestion/tests/e2e/api/test_metric_drilldown.py, src/ingestion/dbt/tests/gold/*
Documents the evidence contract and adds live, end-to-end, fixture, and data-quality checks.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: cyberantonz

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.03% 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 clearly summarizes the main change: adding metric evidence drilldowns to the metrics API.
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 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/metric-drilldown
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/metric-drilldown

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.

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

🧹 Nitpick comments (16)
src/backend/services/analytics/src/api/metric_results.rs (1)

36-44: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Run capability loading concurrently with the result queries.

capabilities is a lazy future and is first awaited after the ranking/views streams finish, so the initial DB lookup is serialized. Start it with join!/a spawned task or await it before result queries, then remove the misleading tokio::pin!.

🤖 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/backend/services/analytics/src/api/metric_results.rs` around lines 36 -
44, The capability lookup created by load_capabilities must begin before the
ranking/views result queries execute, rather than remaining lazy behind
tokio::pin!. Update the surrounding metric-results flow to start capabilities
concurrently using join! or a spawned task, or await it before launching the
result queries, and remove the unnecessary tokio::pin!(capabilities).
src/backend/services/analytics/src/domain/metric_drilldown/mod.rs (1)

541-550: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Dimension names are string-interpolated into SQL.

filter.dimension is allowlisted via definition.allowed_dimension, so this is not currently exploitable, but the safety depends entirely on the definition catalog never containing a quote character. Binding the dimension name as a parameter (it is only compared, never used as an identifier) removes that coupling.

♻️ Suggested change
-        let _ = write!(
-            filter_sql,
-            " AND indexOf(evidence.dimensions.1, '{}') > 0 AND evidence.dimensions.2[indexOf(evidence.dimensions.1, '{}')] IN ({placeholders})",
-            filter.dimension, filter.dimension
-        );
+        let _ = write!(
+            filter_sql,
+            " AND indexOf(evidence.dimensions.1, ?) > 0 AND evidence.dimensions.2[indexOf(evidence.dimensions.1, ?)] IN ({placeholders})"
+        );
+        params.push(filter.dimension.clone());
+        params.push(filter.dimension.clone());
         params.extend(filter.values.iter().cloned());

Note the same pattern exists in compile_ratio_query (Lines 613-622).

🤖 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/backend/services/analytics/src/domain/metric_drilldown/mod.rs` around
lines 541 - 550, Update the filter SQL construction in the current drilldown
query and in compile_ratio_query so filter.dimension is passed through bound
query parameters rather than interpolated into the SQL string. Add placeholders
for both dimension comparisons, append the dimension value in matching parameter
order, and preserve the existing filter.values parameter ordering and query
behavior.
src/backend/services/analytics/src/api/metric_drilldown.rs (1)

353-374: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

export_values runs twice per row for CSV exports.

ensure_export_input_bound materializes every cell, then build_csv re-materializes them. At the 50k-row cap this doubles allocation/formatting work inside the blocking task while an export permit is held. Consider precomputing rows once (Vec<Vec<String>>) and passing that to both the bound check and the writers.

🤖 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/backend/services/analytics/src/api/metric_drilldown.rs` around lines 353
- 374, Update the export flow around ensure_export_input_bound and build_csv so
each row’s export_values result is computed once and reused for both byte-limit
validation and CSV writing. Precompute the formatted rows as Vec<Vec<String>>
within the existing blocking task, pass that collection to the bound-checking
logic and writers, and remove the duplicate per-row materialization while
preserving the current limit behavior.
src/backend/services/analytics/src/domain/metric_definitions/repository.rs (2)

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

Reuse the existing uuid_value helper.

source_evidence_granularities and update_definition_status in this same file bind UUIDs via uuid_value(...); this new writer hand-rolls Value::Bytes(Some(Box::new(source_id.as_bytes().to_vec()))).

♻️ Proposed cleanup
-            Value::Bytes(Some(Box::new(source_id.as_bytes().to_vec()))),
+            uuid_value(source_id),
🤖 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/backend/services/analytics/src/domain/metric_definitions/repository.rs`
around lines 613 - 621, Replace the hand-rolled source_id byte binding in the
new writer with the existing uuid_value helper, matching the usage in
source_evidence_granularities and update_definition_status. Preserve the
surrounding status, error_code, and config_revision bindings unchanged.

605-625: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Add a no-op trace for stale config pins.

metric_sources.updated_at is DATETIME(3), and the extra %f padding from DATE_FORMAT(updated_at, ...) %Y-%m-%d %H:%i:%s.%f matches the row value for non-edited rows. The stale-pin path still affects 0 rows and returns Ok(()), so log rows_affected == 0 alongside the source/config revision to make mid-sweep edits diagnosable.

🤖 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/backend/services/analytics/src/domain/metric_definitions/repository.rs`
around lines 605 - 625, After the metric_sources UPDATE in the repository
method, inspect the returned execution result’s rows_affected value and emit a
trace when it is zero, including source_id and config_revision. Preserve the
existing update, await/error propagation, and Ok(()) behavior while making stale
config-pin no-ops diagnosable.
src/backend/services/analytics/src/domain/metric_definitions/validator.rs (1)

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

has_exact_columns is not an exact-set check.

It verifies every expected column exists with the expected type, but a table with additional columns still returns Present. The name reads as "exact column set"; a one-line doc comment ("exact type match; extra columns tolerated") would prevent a future reader from relying on the stronger guarantee. It also duplicates has_columns almost entirely — the only difference is the predicate builder, which could be a parameter.

🤖 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/backend/services/analytics/src/domain/metric_definitions/validator.rs`
around lines 413 - 445, Clarify the contract of has_exact_columns with a doc
comment stating that expected columns must match their types while extra table
columns are tolerated; do not imply an exact-set guarantee. Avoid broader
refactoring of has_columns unless needed to preserve this documented behavior.
src/ingestion/gold/ai_metric_observations.sql (1)

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

Stale query_settings for a passthrough model.

The model is now a plain projection over ai_metric_evidence, yet it keeps join_algorithm: 'grace_hash,hash' and the 3 GiB max_memory_usage, while collab_metric_observations.sql and git_metric_observations.sql were trimmed to 1.5 GiB with no join setting. Aligning them keeps the gold models' resource budgets consistent.

♻️ Suggested alignment
     query_settings={
-        'max_memory_usage': 3221225472,
+        'max_memory_usage': 1610612736,
         'max_threads': 4,
         'max_bytes_before_external_group_by': 805306368,
-        'max_bytes_before_external_sort': 805306368,
-        'join_algorithm': 'grace_hash,hash'
+        'max_bytes_before_external_sort': 805306368
     }
🤖 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/gold/ai_metric_observations.sql` around lines 8 - 15, Update
the query_settings for the passthrough model to remove the obsolete
join_algorithm setting and reduce max_memory_usage from 3 GiB to 1.5 GiB,
matching the resource configuration used by collab_metric_observations.sql and
git_metric_observations.sql. Preserve the remaining settings.
src/backend/services/analytics/src/domain/metric_definitions/builtin.rs (1)

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

Granularity is now encoded twice: here and in the dbt granularity column.

This mapping duplicates the granularity expressions in src/ingestion/gold/*_metric_evidence.sql (e.g. ai_metric_evidence.sql line 131, collab_metric_evidence.sql lines 298-302, task_metric_evidence.sql lines 221-225). They agree today, but they can drift silently since the drilldown presentation reads the seeded DB value while the table stores its own. A registry test asserting each (source_key, measure_key) pair here matches the materialized granularity (or dropping one of the two representations) would pin the contract.

🤖 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/backend/services/analytics/src/domain/metric_definitions/builtin.rs`
around lines 73 - 98, Prevent granularity drift between
BuiltinSource::evidence_granularity and the dbt granularity expressions in the
metric evidence models. Prefer removing this duplicate mapping and reusing the
seeded/materialized granularity representation; if both representations must
remain, add a registry test covering every (source_key, measure_key) pair and
asserting it matches the materialized database value.
src/backend/services/analytics/src/domain/metric_definitions/definition.rs (1)

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

Consider sharing the relation-name parser with ObservationRelation.

EvidenceRelation::parse / table_ref / source_ref are a byte-for-byte copy of ObservationRelation (lines 244-276) apart from the suffix. A small private helper (e.g. fn parse_relation(value: &str, suffix: &str) -> Option<String>) keeps the two validators from drifting, since both feed directly into interpolated {database}.{table} SQL. The doc comments explaining the naming contract are also missing here.

♻️ Suggested shape
fn parse_relation(value: &str, suffix: &str) -> Option<String> {
    let family = value.strip_suffix(suffix)?;
    let mut chars = family.chars();
    let starts_alpha = chars.next().is_some_and(|c| c.is_ascii_lowercase());
    let rest_ok = family
        .chars()
        .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_');
    (starts_alpha && rest_ok).then(|| value.to_owned())
}
 impl EvidenceRelation {
     pub const DATABASE: &'static str = "insight";
 
+    /// Accepts exactly the managed-evidence naming shape: lowercase
+    /// `snake_case` ending in `_metric_evidence`, with a non-empty family
+    /// prefix. Anything else is a configuration error.
     pub fn parse(value: &str) -> Option<Self> {
-        let family = value.strip_suffix("_metric_evidence")?;
-        if family.is_empty() {
-            return None;
-        }
-        let starts_alpha = family
-            .chars()
-            .next()
-            .is_some_and(|c| c.is_ascii_lowercase());
-        let rest_ok = family
-            .chars()
-            .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_');
-        (starts_alpha && rest_ok).then(|| Self(value.to_owned()))
+        parse_relation(value, "_metric_evidence").map(Self)
     }

Note the empty-family check is already implied by starts_alpha in both implementations.

🤖 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/backend/services/analytics/src/domain/metric_definitions/definition.rs`
around lines 278 - 303, Extract the shared validation logic from
ObservationRelation::parse and EvidenceRelation::parse into a private
parse_relation helper accepting the value and suffix, and remove the redundant
empty-family check because starts_alpha rejects empty names. Update both parsers
to use the helper while preserving their suffixes and return types, and add the
missing naming-contract documentation to EvidenceRelation’s
parse/table_ref/source_ref methods consistent with ObservationRelation.
src/ingestion/gold/task_metric_evidence.sql (3)

157-169: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

today() makes this model's output build-time dependent.

stale_in_progress is stamped with the run date rather than an event date, so re-running on a different day produces different rows and backfills are not reproducible. Worth documenting, or deriving the date from last_status_event_at if the intent is a point-in-time snapshot.

🤖 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/gold/task_metric_evidence.sql` around lines 157 - 169, Update
the stale CTE’s metric_date assignment to derive a deterministic date from
s.last_status_event_at instead of today(), preserving reproducible results
across reruns and backfills; retain the existing stale filtering and grouping
behavior.

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

Dead granularity branch.

value_measures (Lines 170-204) emits none of dev_time_hours, resolution_days, pickup_days, so this arm always resolves to 'derived_population'. Same shape as src/ingestion/gold/git_metric_evidence.sql Lines 276-280; see the consolidated note 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/gold/task_metric_evidence.sql` around lines 205 - 234, The
granularity expression in the value_measures SELECT contains an unreachable
event branch. Update the measure_key-based granularity logic to emit only the
applicable 'derived_population' value, matching the corresponding
git_metric_evidence pattern while preserving the rest of the projected columns.

60-96: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add a uniqueness assertion for (insight_source_id, issue_id).

task_issue_state groups by issue at two subqueries, but the final select depends on class_task_statuses and task_users also being unique-per-user/source-status. Add a schema/assertion target for the resolved grain so duplicates don’t silently double-count evidence after ARRAY JOIN.

🤖 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/gold/task_metric_evidence.sql` around lines 60 - 96, Add a
schema/assertion target for issue_item_evidence that enforces uniqueness of the
resolved (insight_source_id, issue_id) grain after class_task_statuses and
task_users are applied. Ensure duplicate rows fail validation before ARRAY JOIN
evidence can be emitted and double-counted.
src/ingestion/gold/task_metric_observations.sql (1)

8-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale query_settings: this model no longer joins.

The git counterpart got the same rewrite and had join_algorithm dropped and max_memory_usage halved (src/ingestion/gold/git_metric_observations.sql Lines 9-13). This model is now a plain aggregate over task_metric_evidence, so the join hint is inert and the 3 GB budget is unnecessarily large.

♻️ Align with the git model
     query_settings={
-        'max_memory_usage': 3221225472,
+        'max_memory_usage': 1610612736,
         'max_threads': 4,
         'max_bytes_before_external_group_by': 805306368,
-        'max_bytes_before_external_sort': 805306368,
-        'join_algorithm': 'grace_hash,hash'
+        'max_bytes_before_external_sort': 805306368
     }
🤖 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/gold/task_metric_observations.sql` around lines 8 - 15, Update
the query_settings block in the task metric observations model to match the
rewritten git metric model: remove the inert join_algorithm setting and reduce
max_memory_usage from 3221225472 to the corresponding halved value, while
preserving the remaining settings.
src/ingestion/gold/git_metric_evidence.sql (3)

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

Dead branch: measure_observations never yields those measure keys.

measure_observations (Lines 245-259) only emits commit_day, code_lines_added, lines_added, lines_removed. The IN ('commit_change_size', 'pr_cycle_hours', 'pr_change_size') test is always false here, so this branch always resolves to 'source_summary'. Consider collapsing it to the literal to avoid implying event granularity is reachable in this arm.

♻️ Suggested simplification
-    if(
-        measure_key IN ('commit_change_size', 'pr_cycle_hours', 'pr_change_size'),
-        'event',
-        'source_summary'
-    ) AS granularity,
+    'source_summary' AS granularity,
🤖 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/gold/git_metric_evidence.sql` around lines 276 - 280, In the
granularity expression for the measure_observations output, replace the
unreachable measure_key conditional with the literal 'source_summary'. Keep the
existing measure_observations keys and surrounding query logic unchanged.

276-280: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Event measure-key lists are hardcoded in four places with no single source of truth. The set that decides granularity = 'event' in the evidence models is the same set that decides sum-vs-pass-through in the observations models. Adding an event measure to one side without the other silently changes aggregation semantics (an event measure left out of the observations IN list gets summed per day; one left out of the evidence granularity expression is labelled source_summary/derived_population and the drilldown presentation in src/backend/services/analytics/src/domain/metric_drilldown/mod.rs flips show_value). Extract each source's list into a dbt macro or var and reference it from all four sites.

  • src/ingestion/gold/git_metric_evidence.sql#L276-L280: this arm reads only measure_observations, which emits none of the three keys, so the conditional is dead — replace it with the macro/var call (or the 'source_summary' literal) so the list lives in one place.
  • src/ingestion/gold/git_metric_observations.sql#L28-L45: replace both the NOT IN and IN literals with the shared git event-measure list.
  • src/ingestion/gold/task_metric_evidence.sql#L205-L234: this arm reads only value_measures, which emits none of dev_time_hours/resolution_days/pickup_days, so the conditional is likewise dead — reference the shared task list instead.
  • src/ingestion/gold/task_metric_observations.sql#L34-L46: replace both the NOT IN and IN literals with the shared task event-measure list.
🤖 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/gold/git_metric_evidence.sql` around lines 276 - 280, Define
shared dbt macros or vars for the git and task event-measure lists, then reuse
them at all four affected sites:
src/ingestion/gold/git_metric_evidence.sql#L276-L280 must reference the git list
in its granularity condition;
src/ingestion/gold/git_metric_observations.sql#L28-L45 must use it for both NOT
IN and IN checks; src/ingestion/gold/task_metric_evidence.sql#L205-L234 must
reference the task list in its granularity condition; and
src/ingestion/gold/task_metric_observations.sql#L34-L46 must use it for both NOT
IN and IN checks. Keep each source’s event keys defined in only one place.

382-386: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Missing null guards on the PR branches.

The measure_observations and commits arms both end with WHERE tenant_id IS NOT NULL AND entity_id IS NOT NULL AND metric_date IS NOT NULL before assumeNotNull(...), but the two PR arms (Lines 352-357 and 382-386) rely only on the upstream CTE filters. entity_id/metric_date are covered there, tenant_id is not. Adding the same guard keeps assumeNotNull(tenant_id) safe and consistent across all four arms.

🤖 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/gold/git_metric_evidence.sql` around lines 382 - 386, Update
both PR arms, including the branch using pr_measure from prs_merged_source, to
add WHERE tenant_id IS NOT NULL alongside the existing entity_id and metric_date
guards before assumeNotNull(tenant_id). Keep the current upstream filters and PR
measurement logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/domain/metrics/README.md`:
- Line 74: Update the “Drilldown runtime and endpoints” documentation row to
also link the endpoint implementation at api/metric_drilldown.rs, or split the
runtime and endpoint references into separate rows while preserving the existing
domain-module link.

In `@src/backend/services/analytics/src/api/metric_drilldown.rs`:
- Around line 267-279: Update the Number branch in the metric drilldown
worksheet-writing match to handle values where as_f64() returns None by writing
the original value as text instead of propagating export_internal(). Preserve
numeric writing for parseable values and existing null handling.

In `@src/backend/services/analytics/src/api/metric_results.rs`:
- Around line 97-98: Only expose drilldown metadata when using the capability
that validates evidence-plan compatibility: update metric result population
around build_metric_result in
src/backend/services/analytics/src/api/metric_results.rs lines 97-98, apply the
same plan-compatible capability when populating listing metadata in
src/backend/services/analytics/src/domain/metric_definitions/listing.rs lines
107-110, and clarify inherited support in docs/domain/metrics/README.md lines
31-36 to require compatible inputs backed by the same evidence relation.

In `@src/backend/services/analytics/src/domain/metric_definitions/validator.rs`:
- Around line 464-497: Bound both evidence granularity probes in the validator
query flow around the evidence and observation SQL: reuse the drilldown query
settings for max execution time, memory, bytes read, and result bytes, and add a
recent metric_date predicate when the required window is available. Apply
identical safeguards to both queries while preserving their existing
source_key/measure_key filtering and result mapping.
- Around line 582-601: The EVIDENCE_COLUMN_TYPES definitions rely on brittle
byte-for-byte ClickHouse type strings, especially for dimensions and nullable
fields. Update has_exact_columns and its callers to validate evidence columns
using robust type semantics or a higher-level provenance/creation-point
invariant, rather than matching system.columns.type literals; preserve strict
validation where the invariant requires it.
- Around line 121-161: Update the evidence validation state flow so missing or
unparsable evidence_ref produces unchecked rather than Error(Unknown), and skip
evidence validation entirely for SourceKind::CustomObservationSql. In the
evidence_granularities_match Ok(false) branch, preserve the fail-closed error
state but emit a tracing::warn! containing the offending measure and granularity
details. Use the existing validation-state and source-kind symbols rather than
adding new status representations.

In `@src/backend/services/analytics/src/domain/metric_drilldown/mod.rs`:
- Around line 1005-1013: Update selection_fingerprint to exclude snapshot_id,
then validate the snapshot ID carried in the decoded cursor envelope against the
current snapshot before continuing pagination. Route mismatches through
verify_evidence_snapshot so snapshot rebuilds return EVIDENCE_SNAPSHOT_EXPIRED
instead of decode_cursor producing INVALID for cursor, while retaining
fingerprint validation for tenant and selection.

In
`@src/backend/services/analytics/src/migration/m20260727_000001_metric_evidence.rs`:
- Around line 11-46: Update migration m20260727_000001_metric_evidence to use
MySQL 8-compatible DDL: replace each ADD COLUMN IF NOT EXISTS with conditional
schema checks or plain ADD COLUMN statements appropriate for the migration
framework, and replace DROP CONSTRAINT IF EXISTS with MySQL-compatible DROP
CHECK using the named constraints. Preserve the existing column definitions,
ordering, and constraint expressions.

In `@src/ingestion/gold/git_metric_evidence.sql`:
- Around line 353-357: Update the pr_measure ARRAY JOIN expression to use a
null-safe change_size check, treating NULL as zero before evaluating whether it
is greater than 0, while preserving the existing metric tuple and empty-branch
behavior.

In `@src/ingestion/gold/schema.yml`:
- Around line 9-39: Add shared column tests to the evidence anchor
&metric_evidence_columns: apply not_null to the key columns, add accepted_values
for granularity with event, source_summary, and derived_population, and preserve
the existing accepted-value coverage for source_key, entity_type, and
measure_key. Add a build-integrity SQL test matching the pattern of
assert_metric_entity_cohorts_unique.sql to enforce uniqueness across tenant_id,
source_key, measure_key, entity_id, metric_date, and record_id.

In `@src/ingestion/gold/task_metric_evidence.sql`:
- Around line 238-257: The non-aggregated task evidence arms must exclude
unassigned entities before assumeNotNull is applied. In
src/ingestion/gold/task_metric_evidence.sql lines 238-257, add predicates for
tenant_id IS NOT NULL, entity_id IS NOT NULL, entity_id != '', and metric_date
IS NOT NULL after issue_item_evidence; apply the same guard in lines 261-285 for
the issue_facts arm, placing it so it filters rows produced by ARRAY JOIN.
- Around line 130-146: Update the close_reopen CTE’s GROUP BY to include
c.insight_source_id alongside the existing grouping keys, preserving the
composite (insight_source_id, issue_id) identity used by the closes/reopens
joins and preventing events from different sources from collapsing.

In `@src/ingestion/gold/task_worklog_flow.sql`:
- Around line 39-41: Update the day expansion around the ARRAY JOIN’s range()
expression to clamp the dateDiff result at zero before converting it to UInt32,
preventing inverted intervals from wrapping to a huge range. Also cap the
resulting day count to the established maximum span limit, if one exists, so
pathological live spans cannot generate unbounded rows.
- Around line 57-67: Update the FULL OUTER JOIN query using the visible
`ip`/`wl` join in `task_worklog_flow` to enable `join_use_nulls = 1` before the
`coalesce` expressions are evaluated. Preserve the existing coalesced key
selection and metric aggregation while ensuring unmatched sides remain NULL
rather than type defaults.

In `@src/ingestion/gold/wiki_metric_evidence.sql`:
- Around line 116-135: Update the page_creations event branch to exclude rows
with NULL tenant_id before the existing assumeNotNull(tenant_id) projection,
matching the summary branch’s guard behavior. Preserve the existing author_email
and created_at conditions and all other event-row mappings.

---

Nitpick comments:
In `@src/backend/services/analytics/src/api/metric_drilldown.rs`:
- Around line 353-374: Update the export flow around ensure_export_input_bound
and build_csv so each row’s export_values result is computed once and reused for
both byte-limit validation and CSV writing. Precompute the formatted rows as
Vec<Vec<String>> within the existing blocking task, pass that collection to the
bound-checking logic and writers, and remove the duplicate per-row
materialization while preserving the current limit behavior.

In `@src/backend/services/analytics/src/api/metric_results.rs`:
- Around line 36-44: The capability lookup created by load_capabilities must
begin before the ranking/views result queries execute, rather than remaining
lazy behind tokio::pin!. Update the surrounding metric-results flow to start
capabilities concurrently using join! or a spawned task, or await it before
launching the result queries, and remove the unnecessary
tokio::pin!(capabilities).

In `@src/backend/services/analytics/src/domain/metric_definitions/builtin.rs`:
- Around line 73-98: Prevent granularity drift between
BuiltinSource::evidence_granularity and the dbt granularity expressions in the
metric evidence models. Prefer removing this duplicate mapping and reusing the
seeded/materialized granularity representation; if both representations must
remain, add a registry test covering every (source_key, measure_key) pair and
asserting it matches the materialized database value.

In `@src/backend/services/analytics/src/domain/metric_definitions/definition.rs`:
- Around line 278-303: Extract the shared validation logic from
ObservationRelation::parse and EvidenceRelation::parse into a private
parse_relation helper accepting the value and suffix, and remove the redundant
empty-family check because starts_alpha rejects empty names. Update both parsers
to use the helper while preserving their suffixes and return types, and add the
missing naming-contract documentation to EvidenceRelation’s
parse/table_ref/source_ref methods consistent with ObservationRelation.

In `@src/backend/services/analytics/src/domain/metric_definitions/repository.rs`:
- Around line 613-621: Replace the hand-rolled source_id byte binding in the new
writer with the existing uuid_value helper, matching the usage in
source_evidence_granularities and update_definition_status. Preserve the
surrounding status, error_code, and config_revision bindings unchanged.
- Around line 605-625: After the metric_sources UPDATE in the repository method,
inspect the returned execution result’s rows_affected value and emit a trace
when it is zero, including source_id and config_revision. Preserve the existing
update, await/error propagation, and Ok(()) behavior while making stale
config-pin no-ops diagnosable.

In `@src/backend/services/analytics/src/domain/metric_definitions/validator.rs`:
- Around line 413-445: Clarify the contract of has_exact_columns with a doc
comment stating that expected columns must match their types while extra table
columns are tolerated; do not imply an exact-set guarantee. Avoid broader
refactoring of has_columns unless needed to preserve this documented behavior.

In `@src/backend/services/analytics/src/domain/metric_drilldown/mod.rs`:
- Around line 541-550: Update the filter SQL construction in the current
drilldown query and in compile_ratio_query so filter.dimension is passed through
bound query parameters rather than interpolated into the SQL string. Add
placeholders for both dimension comparisons, append the dimension value in
matching parameter order, and preserve the existing filter.values parameter
ordering and query behavior.

In `@src/ingestion/gold/ai_metric_observations.sql`:
- Around line 8-15: Update the query_settings for the passthrough model to
remove the obsolete join_algorithm setting and reduce max_memory_usage from 3
GiB to 1.5 GiB, matching the resource configuration used by
collab_metric_observations.sql and git_metric_observations.sql. Preserve the
remaining settings.

In `@src/ingestion/gold/git_metric_evidence.sql`:
- Around line 276-280: In the granularity expression for the
measure_observations output, replace the unreachable measure_key conditional
with the literal 'source_summary'. Keep the existing measure_observations keys
and surrounding query logic unchanged.
- Around line 276-280: Define shared dbt macros or vars for the git and task
event-measure lists, then reuse them at all four affected sites:
src/ingestion/gold/git_metric_evidence.sql#L276-L280 must reference the git list
in its granularity condition;
src/ingestion/gold/git_metric_observations.sql#L28-L45 must use it for both NOT
IN and IN checks; src/ingestion/gold/task_metric_evidence.sql#L205-L234 must
reference the task list in its granularity condition; and
src/ingestion/gold/task_metric_observations.sql#L34-L46 must use it for both NOT
IN and IN checks. Keep each source’s event keys defined in only one place.
- Around line 382-386: Update both PR arms, including the branch using
pr_measure from prs_merged_source, to add WHERE tenant_id IS NOT NULL alongside
the existing entity_id and metric_date guards before assumeNotNull(tenant_id).
Keep the current upstream filters and PR measurement logic unchanged.

In `@src/ingestion/gold/task_metric_evidence.sql`:
- Around line 157-169: Update the stale CTE’s metric_date assignment to derive a
deterministic date from s.last_status_event_at instead of today(), preserving
reproducible results across reruns and backfills; retain the existing stale
filtering and grouping behavior.
- Around line 205-234: The granularity expression in the value_measures SELECT
contains an unreachable event branch. Update the measure_key-based granularity
logic to emit only the applicable 'derived_population' value, matching the
corresponding git_metric_evidence pattern while preserving the rest of the
projected columns.
- Around line 60-96: Add a schema/assertion target for issue_item_evidence that
enforces uniqueness of the resolved (insight_source_id, issue_id) grain after
class_task_statuses and task_users are applied. Ensure duplicate rows fail
validation before ARRAY JOIN evidence can be emitted and double-counted.

In `@src/ingestion/gold/task_metric_observations.sql`:
- Around line 8-15: Update the query_settings block in the task metric
observations model to match the rewritten git metric model: remove the inert
join_algorithm setting and reduce max_memory_usage from 3221225472 to the
corresponding halved value, while preserving the remaining settings.
🪄 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 Plus

Run ID: a9d9b915-c002-4f75-b914-7b99a0e3ba6b

📥 Commits

Reviewing files that changed from the base of the PR and between d284207 and 3ca1015.

⛔ Files ignored due to path filters (1)
  • src/backend/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (35)
  • docs/components/backend/analytics/openapi.json
  • docs/domain/metrics/README.md
  • docs/domain/metrics/specs/DESIGN.md
  • src/backend/Cargo.toml
  • src/backend/services/analytics/Cargo.toml
  • src/backend/services/analytics/src/api/metric_drilldown.rs
  • src/backend/services/analytics/src/api/metric_results.rs
  • src/backend/services/analytics/src/api/mod.rs
  • src/backend/services/analytics/src/domain/metric_definitions/builtin.rs
  • src/backend/services/analytics/src/domain/metric_definitions/definition.rs
  • src/backend/services/analytics/src/domain/metric_definitions/listing.rs
  • src/backend/services/analytics/src/domain/metric_definitions/mod.rs
  • src/backend/services/analytics/src/domain/metric_definitions/repository.rs
  • src/backend/services/analytics/src/domain/metric_definitions/seeds.rs
  • src/backend/services/analytics/src/domain/metric_definitions/validator.rs
  • src/backend/services/analytics/src/domain/metric_drilldown/mod.rs
  • src/backend/services/analytics/src/domain/metric_results/builder.rs
  • src/backend/services/analytics/src/domain/metric_results/dto.rs
  • src/backend/services/analytics/src/domain/metric_results/mod.rs
  • src/backend/services/analytics/src/domain/metric_results/validation.rs
  • src/backend/services/analytics/src/domain/mod.rs
  • src/backend/services/analytics/src/migration/m20260727_000001_metric_evidence.rs
  • src/backend/services/analytics/src/migration/mod.rs
  • src/ingestion/gold/ai_metric_evidence.sql
  • src/ingestion/gold/ai_metric_observations.sql
  • src/ingestion/gold/collab_metric_evidence.sql
  • src/ingestion/gold/collab_metric_observations.sql
  • src/ingestion/gold/git_metric_evidence.sql
  • src/ingestion/gold/git_metric_observations.sql
  • src/ingestion/gold/schema.yml
  • src/ingestion/gold/task_metric_evidence.sql
  • src/ingestion/gold/task_metric_observations.sql
  • src/ingestion/gold/task_worklog_flow.sql
  • src/ingestion/gold/wiki_metric_evidence.sql
  • src/ingestion/gold/wiki_metric_observations.sql

Comment thread docs/domain/metrics/README.md Outdated
Comment thread src/backend/services/analytics/src/api/metric_drilldown.rs
Comment thread src/backend/services/analytics/src/api/metric_results.rs
Comment thread src/backend/services/analytics/src/domain/metric_definitions/validator.rs Outdated
Comment thread src/ingestion/gold/task_metric_evidence.sql
Comment thread src/ingestion/gold/task_metric_evidence.sql
Comment thread src/ingestion/gold/task_worklog_flow.sql Outdated
Comment thread src/ingestion/gold/task_worklog_flow.sql
Comment thread src/ingestion/gold/wiki_metric_evidence.sql
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: 3

Caution

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

⚠️ Outside diff range comments (1)
src/backend/services/analytics/src/migration/m20260727_000001_metric_evidence.rs (1)

108-120: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Invalidate both sources when source_id changes.

This trigger only checks evidence_granularity and updates NEW.source_id. Reassigning a measure from one source to another without changing its granularity leaves the old source’s evidence status stale and does not invalidate the new source. Include source_id in the change predicate and invalidate both OLD.source_id and NEW.source_id (or enforce source immutability explicitly).

Proposed fix
-                 IF NOT (OLD.evidence_granularity <=> NEW.evidence_granularity) THEN
+                 IF NOT (OLD.evidence_granularity <=> NEW.evidence_granularity)
+                    OR NOT (OLD.source_id <=> NEW.source_id) THEN
                      UPDATE metric_sources
                      SET evidence_schema_status = 'unchecked',
                          evidence_schema_checked_at = NULL,
                          evidence_schema_error_code = NULL
-                     WHERE id = NEW.source_id;
+                     WHERE id = OLD.source_id
+                        OR id = NEW.source_id;
                  END IF;
🤖 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/backend/services/analytics/src/migration/m20260727_000001_metric_evidence.rs`
around lines 108 - 120, Update the trg_metric_source_measures_evidence_update
trigger to detect changes to both evidence_granularity and source_id, then
invalidate metric_sources for both OLD.source_id and NEW.source_id when either
field changes. Preserve the existing reset values for evidence_schema_status,
evidence_schema_checked_at, and evidence_schema_error_code.
🤖 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/src/api/http_live_tests.rs`:
- Around line 162-170: The metadata helpers enable_drilldown_metadata in
src/backend/services/analytics/src/api/http_live_tests.rs:162-170 and the
corresponding logic in
src/backend/services/analytics/src/domain/metric_definitions/live_tests.rs:165-179
must stop updating every row; limit mutations to test-owned fixtures and restore
each fixture’s prior status values during cleanup, using the same isolated
fixture/cleanup strategy at both sites.

In `@src/backend/services/analytics/src/domain/metric_definitions/live_tests.rs`:
- Around line 237-241: Update the test invoking
MetricDefinitionValidator::new(db, ch).validate_all() to inspect its result
instead of discarding it. Assert the expected outcome for the unavailable
ClickHouse client, including any expected error or status update, so the test
fails when validation handles the outage unexpectedly.
- Around line 212-227: Update the test around the two update_evidence_status
calls to read and assert the stored status after the current-revision write,
then perform the distinct stale-revision update and read the row again. Assert
the stale write leaves the previously stored value unchanged, ensuring the
revision predicate is exercised.

---

Outside diff comments:
In
`@src/backend/services/analytics/src/migration/m20260727_000001_metric_evidence.rs`:
- Around line 108-120: Update the trg_metric_source_measures_evidence_update
trigger to detect changes to both evidence_granularity and source_id, then
invalidate metric_sources for both OLD.source_id and NEW.source_id when either
field changes. Preserve the existing reset values for evidence_schema_status,
evidence_schema_checked_at, and evidence_schema_error_code.
🪄 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 Plus

Run ID: 78494da4-0c0d-4d6c-ba56-495cd0e06d28

📥 Commits

Reviewing files that changed from the base of the PR and between 3ca1015 and 7043470.

📒 Files selected for processing (20)
  • docs/domain/metrics/README.md
  • src/backend/services/analytics/src/api/http_live_tests.rs
  • src/backend/services/analytics/src/api/metric_drilldown.rs
  • src/backend/services/analytics/src/api/metric_results.rs
  • src/backend/services/analytics/src/domain/metric_definitions/definition.rs
  • src/backend/services/analytics/src/domain/metric_definitions/live_tests.rs
  • src/backend/services/analytics/src/domain/metric_definitions/repository.rs
  • src/backend/services/analytics/src/domain/metric_definitions/validator.rs
  • src/backend/services/analytics/src/domain/metric_drilldown/mod.rs
  • src/backend/services/analytics/src/migration/m20260727_000001_metric_evidence.rs
  • src/ingestion/dbt/tests/gold/assert_metric_evidence_unique.sql
  • src/ingestion/dbt/tests/gold/assert_task_issue_state_unique.sql
  • src/ingestion/gold/ai_metric_observations.sql
  • src/ingestion/gold/git_metric_evidence.sql
  • src/ingestion/gold/schema.yml
  • src/ingestion/gold/task_metric_evidence.sql
  • src/ingestion/gold/task_metric_observations.sql
  • src/ingestion/gold/task_worklog_flow.sql
  • src/ingestion/gold/wiki_metric_evidence.sql
  • src/ingestion/tests/e2e/api/test_metric_drilldown.py
🚧 Files skipped from review as they are similar to previous changes (14)
  • src/ingestion/gold/task_worklog_flow.sql
  • src/ingestion/gold/ai_metric_observations.sql
  • src/ingestion/gold/task_metric_observations.sql
  • docs/domain/metrics/README.md
  • src/ingestion/gold/wiki_metric_evidence.sql
  • src/ingestion/gold/git_metric_evidence.sql
  • src/ingestion/gold/schema.yml
  • src/ingestion/gold/task_metric_evidence.sql
  • src/backend/services/analytics/src/domain/metric_definitions/definition.rs
  • src/backend/services/analytics/src/domain/metric_definitions/validator.rs
  • src/backend/services/analytics/src/api/metric_results.rs
  • src/backend/services/analytics/src/api/metric_drilldown.rs
  • src/backend/services/analytics/src/domain/metric_definitions/repository.rs
  • src/backend/services/analytics/src/domain/metric_drilldown/mod.rs

Comment thread src/backend/services/analytics/src/api/http_live_tests.rs Outdated
Comment thread src/backend/services/analytics/src/domain/metric_definitions/live_tests.rs Outdated
Comment thread src/backend/services/analytics/src/domain/metric_definitions/live_tests.rs Outdated
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

Copy link
Copy Markdown
Contributor Author

Split into a reviewable stack: #2071 (evidence tables) → #2072 (capability metadata) → #2073 (drilldown endpoint) → #2074 (export). Sub-issues under #1603: #2067#2070.

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.

1 participant