Phase (b) part 2 of docs/DATA-ANALYSIS.md (§3, §4.3, §8): the spec'd UI API, core pushdown, and the analyze permission through core.
UI API extension data-analysis 1.0.0 (/qqq/v1/qbit/data-analysis, the #1128 extension point). It is registered only when qqq-middleware-javalin is present.
Method
Path
GET
/tables/{table}/capabilities (200 with canAnalyze=false when not allowed, so the UI can hide the action)
POST
/tables/{table}/column-profile
POST
/tables/{table}/distinct-values
POST
/tables/{table}/table-profile
POST
/tables/{table}/duplicates
POST
/runs (start an exact run)
GET
/runs/{runId}
Schemas: 26 DataAnalysis…V1 schemas. Filters reuse core's filter schema, and every operation has an assistant-oriented summary and description.
Contract: the fragment is committed at src/main/resources/openapi/qqq-v1/data-analysis.yaml, and a test regenerates it and fails on drift (-Dopenapi.write=true rewrites it).
Errors: 403 for permission, 400 for a bad field or body, 404 for an unknown table.
Analyze permission:<table>.analyze is registered through core TableFeaturePermissionsMetaData (#1138) and checked with PermissionsHelper.hasTableFeaturePermission, so it shows in the authentik policy editor (qbit-authentik#19). The stand-in is gone.
CoreAnalysisBackend:
Pushes percentiles, stddev, null/blank counts, numeric and date buckets and lengths down through #1139. Patterns and length histograms stay streamed.
Backends without capabilities keep the streamed stand-in.
Scalable estimates:
Native backends: aggregates run with withSamplePercent, counts are scaled to the population ("≈"), ratios are unscaled, and the row count is the true total.
Stand-in: uses one primary-key <= bound for "first N rows by id".
No IN lists, so the Postgres bind limit is never hit.
Estimate drills cover the full population; exact results keep strict drill equivalence.
Requires QQQ 4.1.0-RC.6, with #1128, #1138, #1139 and #1145 (H2 default-mode grouping). The pom targets RC.6, so this stays a draft until RC.6 is published. Local builds use -Dqqq.snapshot.version=4.1.0-DATAANALYSIS-SNAPSHOT, which is develop plus #1145.
Test plan
./mvnw verify locally against the snapshot, with Docker: 123 tests, all passing, including 21 on Postgres 17. A 50,000-row estimate regression covers the bind limit. JaCoCo 98% instructions / 100% classes; checkstyle clean.
HTTP server tests:
every endpoint's happy path;
403 without .analyze, and canAnalyze=false;
400 for a field behind a field security lock;
a drill filter from a response, posted to core /qqq/v1/table/{t}/count, returns the same number;
an async run completes;
the operations appear in /qqq/v1/openapi.json and in UiApiInvoker.listOperations() (assistant tools).
The engine contract suite runs for both backends on memory, H2 (default and PostgreSQL mode) and Postgres
## Summary
Phase (b) part 2 of `docs/DATA-ANALYSIS.md` (§3, §4.3, §8): the spec'd UI API, core pushdown, and the analyze permission through core.
**UI API extension `data-analysis` 1.0.0** (`/qqq/v1/qbit/data-analysis`, the #1128 extension point). It is registered only when `qqq-middleware-javalin` is present.
| Method | Path |
|---|---|
| GET | `/tables/{table}/capabilities` (200 with `canAnalyze=false` when not allowed, so the UI can hide the action) |
| POST | `/tables/{table}/column-profile` |
| POST | `/tables/{table}/distinct-values` |
| POST | `/tables/{table}/table-profile` |
| POST | `/tables/{table}/duplicates` |
| POST | `/runs` (start an exact run) |
| GET | `/runs/{runId}` |
- **Schemas:** 26 `DataAnalysis…V1` schemas. Filters reuse core's filter schema, and every operation has an assistant-oriented summary and description.
- **Contract:** the fragment is committed at `src/main/resources/openapi/qqq-v1/data-analysis.yaml`, and a test regenerates it and fails on drift (`-Dopenapi.write=true` rewrites it).
- **Errors:** 403 for permission, 400 for a bad field or body, 404 for an unknown table.
- **Analyze permission:** `<table>.analyze` is registered through core `TableFeaturePermissionsMetaData` (#1138) and checked with `PermissionsHelper.hasTableFeaturePermission`, so it shows in the authentik policy editor (qbit-authentik#19). The stand-in is gone.
- **`CoreAnalysisBackend`:**
- Pushes percentiles, stddev, null/blank counts, numeric and date buckets and lengths down through #1139. Patterns and length histograms stay streamed.
- Backends without capabilities keep the streamed stand-in.
- **Scalable estimates:**
- Native backends: aggregates run with `withSamplePercent`, counts are scaled to the population ("≈"), ratios are unscaled, and the row count is the true total.
- Stand-in: uses one primary-key `<=` bound for "first N rows by id".
- No IN lists, so the Postgres bind limit is never hit.
- Estimate drills cover the full population; exact results keep strict drill equivalence.
**Requires QQQ 4.1.0-RC.6**, with #1128, #1138, #1139 and **#1145** (H2 default-mode grouping). The pom targets RC.6, so this stays a **draft until RC.6 is published**. Local builds use `-Dqqq.snapshot.version=4.1.0-DATAANALYSIS-SNAPSHOT`, which is develop plus #1145.
## Test plan
- [x] `./mvnw verify` locally against the snapshot, with Docker: **123 tests, all passing, including 21 on Postgres 17**. A 50,000-row estimate regression covers the bind limit. JaCoCo 98% instructions / 100% classes; checkstyle clean.
- [x] HTTP server tests:
- every endpoint's happy path;
- 403 without `.analyze`, and `canAnalyze=false`;
- 400 for a field behind a field security lock;
- a drill filter from a response, posted to core `/qqq/v1/table/{t}/count`, returns the same number;
- an async run completes;
- the operations appear in `/qqq/v1/openapi.json` and in `UiApiInvoker.listOperations()` (assistant tools).
- [x] The engine contract suite runs for both backends on memory, H2 (default and PostgreSQL mode) and Postgres
- [ ] CI after RC.6 is published
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Phase (b) part 2 of
docs/DATA-ANALYSIS.md(§3, §4.3, §8): the spec'd UI API, core pushdown, and the analyze permission through core.UI API extension
data-analysis1.0.0 (/qqq/v1/qbit/data-analysis, the #1128 extension point). It is registered only whenqqq-middleware-javalinis present./tables/{table}/capabilities(200 withcanAnalyze=falsewhen not allowed, so the UI can hide the action)/tables/{table}/column-profile/tables/{table}/distinct-values/tables/{table}/table-profile/tables/{table}/duplicates/runs(start an exact run)/runs/{runId}DataAnalysis…V1schemas. Filters reuse core's filter schema, and every operation has an assistant-oriented summary and description.src/main/resources/openapi/qqq-v1/data-analysis.yaml, and a test regenerates it and fails on drift (-Dopenapi.write=truerewrites it).<table>.analyzeis registered through coreTableFeaturePermissionsMetaData(#1138) and checked withPermissionsHelper.hasTableFeaturePermission, so it shows in the authentik policy editor (qbit-authentik#19). The stand-in is gone.CoreAnalysisBackend:withSamplePercent, counts are scaled to the population ("≈"), ratios are unscaled, and the row count is the true total.<=bound for "first N rows by id".Requires QQQ 4.1.0-RC.6, with #1128, #1138, #1139 and #1145 (H2 default-mode grouping). The pom targets RC.6, so this stays a draft until RC.6 is published. Local builds use
-Dqqq.snapshot.version=4.1.0-DATAANALYSIS-SNAPSHOT, which is develop plus #1145.Test plan
./mvnw verifylocally against the snapshot, with Docker: 123 tests, all passing, including 21 on Postgres 17. A 50,000-row estimate regression covers the bind limit. JaCoCo 98% instructions / 100% classes; checkstyle clean..analyze, andcanAnalyze=false;/qqq/v1/table/{t}/count, returns the same number;/qqq/v1/openapi.jsonand inUiApiInvoker.listOperations()(assistant tools).🤖 Generated with Claude Code