mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-07 07:55:13 +02:00
Merge pull request #22 from Kaelio/andreybavt/fix-metabase-readiness
fix(cli): report metabase ingest readiness
This commit is contained in:
commit
da108e556c
27 changed files with 238 additions and 62 deletions
89
README.md
89
README.md
|
|
@ -43,6 +43,7 @@ SQLite.
|
|||
Install the CLI and run the setup wizard:
|
||||
|
||||
```bash
|
||||
npm install @kaelio/ktx
|
||||
npm install -g @kaelio/ktx
|
||||
ktx setup
|
||||
```
|
||||
|
|
@ -70,6 +71,40 @@ KTX context built: yes
|
|||
Agent integration ready: yes (claude-code:project)
|
||||
```
|
||||
|
||||
Run the packaged demo without installing globally:
|
||||
|
||||
```bash
|
||||
npx @kaelio/ktx setup demo --no-input
|
||||
npx @kaelio/ktx setup demo inspect
|
||||
```
|
||||
|
||||
The default demo uses packaged sample data and prebuilt context. It does not
|
||||
require API keys, network access, or an LLM provider.
|
||||
|
||||
Generate SQL from a semantic-layer source:
|
||||
|
||||
```bash
|
||||
npx @kaelio/ktx sl query --project-dir "$PROJECT_DIR" \
|
||||
--connection-id warehouse \
|
||||
--measure accounts.account_count \
|
||||
--dimension accounts.segment \
|
||||
--format sql
|
||||
```
|
||||
|
||||
List and test a configured warehouse connection:
|
||||
|
||||
```bash
|
||||
ktx connection list --project-dir "$PROJECT_DIR"
|
||||
ktx connection test warehouse --project-dir "$PROJECT_DIR"
|
||||
```
|
||||
|
||||
The connection test prints the configured driver and discovered table count:
|
||||
|
||||
```text
|
||||
Driver: sqlite
|
||||
Tables: 1
|
||||
```
|
||||
|
||||
## What's in a project
|
||||
|
||||
```
|
||||
|
|
@ -97,6 +132,47 @@ Semantic sources and knowledge pages are committed to git. The `.ktx/` directory
|
|||
holds ephemeral state and is git-ignored — delete it and KTX rebuilds on the
|
||||
next run.
|
||||
|
||||
### Scan the demo warehouse
|
||||
|
||||
Scan artifacts are written under
|
||||
`raw-sources/warehouse/live-database/<syncId>/` in the project directory.
|
||||
|
||||
```bash
|
||||
SCAN_OUTPUT="$(ktx scan warehouse --project-dir "$PROJECT_DIR")"
|
||||
printf '%s\n' "$SCAN_OUTPUT"
|
||||
SCAN_RUN_ID="$(printf '%s\n' "$SCAN_OUTPUT" | awk '/^Run: / { print $2 }')"
|
||||
ktx scan status --project-dir "$PROJECT_DIR" "$SCAN_RUN_ID"
|
||||
ktx scan report --project-dir "$PROJECT_DIR" "$SCAN_RUN_ID"
|
||||
```
|
||||
|
||||
For non-SQLite drivers, prefer credential references such as `--url env:NAME`
|
||||
or `--url file:PATH` over literal credential URLs.
|
||||
|
||||
## Managed Python runtime
|
||||
|
||||
KTX installs its Python runtime only when a Python-backed command needs it.
|
||||
The runtime lives outside the npm cache, is versioned by the installed CLI
|
||||
version, and is managed by `ktx runtime` commands.
|
||||
|
||||
KTX requires `uv` on `PATH` to create the managed runtime. Install `uv` with
|
||||
your system package manager or the official installer before running Python-
|
||||
backed KTX commands. KTX doesn't download `uv` automatically; run
|
||||
`ktx runtime doctor` if runtime installation fails:
|
||||
|
||||
```bash
|
||||
ktx runtime install --yes
|
||||
ktx runtime status
|
||||
ktx runtime doctor
|
||||
ktx runtime start
|
||||
ktx runtime stop
|
||||
ktx runtime prune --dry-run
|
||||
ktx runtime prune --yes
|
||||
```
|
||||
|
||||
The release artifact manifest contains the public npm tarball and the bundled `kaelio-ktx`
|
||||
runtime wheel. The `python/ktx-sl` and `python/ktx-daemon` directories remain
|
||||
source packages for development, not public release artifacts.
|
||||
|
||||
## Serve agents
|
||||
|
||||
KTX integrates with coding agents through CLI skills, an MCP server, or both.
|
||||
|
|
@ -126,6 +202,11 @@ This exposes tools for connections, knowledge search, semantic-layer sources,
|
|||
validation, queries, ingestion, and replay. The `--semantic-compute` flag starts
|
||||
the managed Python runtime for query planning automatically.
|
||||
|
||||
The standalone MCP server exposes `connection_list`, `knowledge_search`,
|
||||
`knowledge_read`, `knowledge_write`, `sl_list_sources`, `sl_read_source`,
|
||||
`sl_write_source`, `sl_validate`, `sl_query`, `ingest_trigger`,
|
||||
`ingest_status`, `ingest_report`, and `ingest_replay`.
|
||||
|
||||
Supported agents: Claude Code, Codex, Cursor, OpenCode, and any agent that
|
||||
reads `.agents/` skills or MCP configuration.
|
||||
|
||||
|
|
@ -136,7 +217,13 @@ reads `.agents/` skills or MCP configuration.
|
|||
| `packages/cli` | CLI entry point |
|
||||
| `packages/context` | Core context engine |
|
||||
| `packages/llm` | LLM and embedding providers |
|
||||
| `packages/connector-*` | Database connectors (Postgres, Snowflake, BigQuery, ClickHouse, MySQL, SQL Server, SQLite) |
|
||||
| `packages/connector-bigquery` | BigQuery scan connector |
|
||||
| `packages/connector-clickhouse` | ClickHouse scan connector |
|
||||
| `packages/connector-mysql` | MySQL scan connector |
|
||||
| `packages/connector-postgres` | Postgres scan connector |
|
||||
| `packages/connector-snowflake` | Snowflake scan connector |
|
||||
| `packages/connector-sqlite` | SQLite scan connector |
|
||||
| `packages/connector-sqlserver` | SQL Server scan connector |
|
||||
| `python/ktx-sl` | Semantic-layer query planning |
|
||||
| `python/ktx-daemon` | Portable compute service |
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ refs:
|
|||
|
||||
## New Hire Week-One Onboarding Policy
|
||||
|
||||
**Source:** Notion — People & Operating Norms, last edited 2026-05-07
|
||||
**Source:** Notion — People & Operating Norms, last edited 2026-05-07
|
||||
**Owner:** Manager (not People Ops)
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ tables:
|
|||
|
||||
# Activation KPI Glossary
|
||||
|
||||
**Owner team:** Growth
|
||||
**Owner team:** Growth
|
||||
**Source:** Notion — Orbit Demo Home / Data Team - Onboarding / Activation KPI Glossary, last edited 2026-05-07
|
||||
|
||||
Use this when a question is about signup-to-habit behavior. Orbit uses activation language across Growth, Product, and CS conversations.
|
||||
|
|
@ -62,4 +62,3 @@ Growth conversations typically use D7 and D14 Activation Rate. Product and CS ma
|
|||
## Relationship to Account-Level Activation
|
||||
|
||||
This glossary defines **customer-level** activation (signup-to-habit). The **account-level** activation workflow (requester login → first approved purchase request → account activated) is a separate concept tracked in `mart_account_activity` and governed by the January 2026 policy change. See `orbit-activation-policy-change-jan-2026` for that definition.
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ sl_refs:
|
|||
|
||||
# Activation Policy Change — January 2026
|
||||
|
||||
**Governed metric key:** `activated_accounts`
|
||||
**Owner team:** growth
|
||||
**Notion:** `notion://notion_page_activation_policy_decision#policy-change`
|
||||
**Governed metric key:** `activated_accounts`
|
||||
**Owner team:** growth
|
||||
**Notion:** `notion://notion_page_activation_policy_decision#policy-change`
|
||||
**Sources:** `mart_account_activity`, `int_activation_policy_windows`, `stg_activation_events`
|
||||
|
||||
## Policy Boundary
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ sl_refs:
|
|||
|
||||
# ARR — Contract-First Definition
|
||||
|
||||
**Governed metric key:** `arr`
|
||||
**Owner team:** finance
|
||||
**Notion:** `notion://notion_page_arr_contract_reporting#arr-contract-first`
|
||||
**Governed metric key:** `arr`
|
||||
**Owner team:** finance
|
||||
**Notion:** `notion://notion_page_arr_contract_reporting#arr-contract-first`
|
||||
**Source:** `mart_arr_daily` (grain: `metric_date`)
|
||||
|
||||
## Rule
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ refs:
|
|||
|
||||
Orbit sells procurement workflow and spend-control software. The core value proposition: route purchase requests, collect approvals, onboard suppliers, and issue purchase orders without turning every exception into a status hunt.
|
||||
|
||||
**Primary buyers:** Finance, Procurement, Business Operations.
|
||||
**Primary buyers:** Finance, Procurement, Business Operations.
|
||||
**Daily users:** department admins, office managers, IT leads, legal ops partners — anyone who has to get a vendor through the building.
|
||||
|
||||
## Product Workflow
|
||||
|
|
@ -69,4 +69,3 @@ Orbit sells procurement workflow and spend-control software. The core value prop
|
|||
- "Supplier onboarding is split across three teams."
|
||||
- "Renewals are visible too late."
|
||||
- "People keep asking Finance for status because there is nowhere better to look."
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ sl_refs:
|
|||
|
||||
# Customer Health Risk Definition
|
||||
|
||||
**Governed metric key:** `active_customers`
|
||||
**Owner team:** customer_success
|
||||
**Notion:** `notion://notion_page_customer_health_playbook#risk-definition`
|
||||
**Governed metric key:** `active_customers`
|
||||
**Owner team:** customer_success
|
||||
**Notion:** `notion://notion_page_customer_health_playbook#risk-definition`
|
||||
**Sources:** `mart_customer_health`, `int_customer_health_signals`
|
||||
|
||||
## Risk Levels
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ tables:
|
|||
|
||||
# Orbit Customers Source
|
||||
|
||||
**Table:** `orbit_analytics.customer`
|
||||
**Grain:** one row per signed-up customer
|
||||
**Table:** `orbit_analytics.customer`
|
||||
**Grain:** one row per signed-up customer
|
||||
**Source:** Notion — Orbit Demo Home / Data Team - Onboarding / Orbit Customers Source, last edited 2026-05-07
|
||||
|
||||
Use this when a question needs customer identity, plan tier, signup timing, recent activity, or the standard customer joins.
|
||||
|
|
@ -58,4 +58,3 @@ Always join through `customer.id`. Do not join on `email`.
|
|||
- **Timezone:** `created_at` and `last_seen_at` are UTC. Confirm whether a question expects UTC or a local business day before filtering.
|
||||
- **Paying vs. all:** `free` customers must be excluded from paying-customer follow-ups. Use `paying_customer_count`, not `customer_count`.
|
||||
- **plan_tier values:** `free`, `pro`, `enterprise`. Note: `pro_plus` is a legacy alias for `growth` in the account/contract layer (see `orbit-plan-segment-normalization`), but `plan_tier` on this table uses `pro` not `pro_plus`.
|
||||
|
||||
|
|
|
|||
|
|
@ -42,4 +42,3 @@ Declared in `models/exposures.yml`. All exposures are type `dashboard` with matu
|
|||
- **Owner:** Growth (growth@orbit-demo.example.com)
|
||||
- **Depends on:** `mart_account_activity`
|
||||
- **Description:** Activation policy comparison around the January 2026 workflow update.
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ sl_refs:
|
|||
|
||||
# Orbit dbt Project Overview
|
||||
|
||||
**Project name:** `kaelio_demo`
|
||||
**dbt version:** 1.0.0
|
||||
**Profile target:** Postgres (`orbit_analytics` schema, `kaelio_demo` database)
|
||||
**Raw source schema:** `orbit_raw`
|
||||
**Project name:** `kaelio_demo`
|
||||
**dbt version:** 1.0.0
|
||||
**Profile target:** Postgres (`orbit_analytics` schema, `kaelio_demo` database)
|
||||
**Raw source schema:** `orbit_raw`
|
||||
**Analytics schema:** `orbit_analytics` (all models materialised as views by default)
|
||||
|
||||
## Model Layers
|
||||
|
|
@ -52,4 +52,3 @@ sl_refs:
|
|||
## Raw Source Tables (`orbit_raw` schema)
|
||||
|
||||
accounts, account_hierarchy, plans, contracts, subscriptions, contract_discount_terms, arr_movements, invoices, invoice_line_items, refunds, plan_segment_mapping, users, activation_events, sessions, purchase_requests, approval_events, suppliers, supplier_onboarding_events, purchase_orders, support_tickets, account_owners.
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ tables:
|
|||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/106.json -->
|
||||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/107.json -->
|
||||
|
||||
**Table:** `orbit_analytics.mart_account_activity`
|
||||
**Table:** `orbit_analytics.mart_account_activity`
|
||||
**Grain:** one row per `policy_change_date`
|
||||
|
||||
## Columns
|
||||
|
|
@ -47,4 +47,3 @@ tables:
|
|||
- The January 2026 activation policy change (`policy_change_date = 2026-01-15`) is the primary boundary. `policy_version` in upstream events splits into `pre_2026_01_15` and `post_2026_01_15` cohorts.
|
||||
- Rates are ratios (0–1); multiply by 100 for percentage display.
|
||||
- See [orbit-activation-policy-change-jan-2026](orbit-activation-policy-change-jan-2026) for full policy context.
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ tables:
|
|||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/69.json -->
|
||||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/100.json -->
|
||||
|
||||
**Table:** `orbit_analytics.mart_account_segments`
|
||||
**Table:** `orbit_analytics.mart_account_segments`
|
||||
**Grain:** one row per `account_id`
|
||||
|
||||
## Columns
|
||||
|
|
@ -53,4 +53,3 @@ tables:
|
|||
- `normalized_plan_code` maps `pro_plus` → `growth`. Always use `normalized_plan_code` for plan-based reporting. See [orbit-plan-segment-normalization](orbit-plan-segment-normalization).
|
||||
- `segment` is derived from `canonical_plan_code × size_band` via `stg_plan_segment_mapping`.
|
||||
- `contract_arr_cents` is the contract-first ARR value. See [orbit-arr-contract-first-definition](orbit-arr-contract-first-definition).
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ tables:
|
|||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/56.json -->
|
||||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/96.json -->
|
||||
|
||||
**Table:** `orbit_analytics.mart_arr_daily`
|
||||
**Table:** `orbit_analytics.mart_arr_daily`
|
||||
**Grain:** one row per `metric_date`
|
||||
|
||||
## Columns
|
||||
|
|
@ -44,4 +44,3 @@ tables:
|
|||
|
||||
- ARR is calculated contract-first: active contract ARR takes precedence over subscription ARR for any covered period. See [orbit-arr-contract-first-definition](orbit-arr-contract-first-definition).
|
||||
- `display` is a formatted label for UI rendering; use `arr_cents` for all arithmetic.
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ tables:
|
|||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/98.json -->
|
||||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/103.json -->
|
||||
|
||||
**Table:** `orbit_analytics.mart_nrr_quarterly`
|
||||
**Table:** `orbit_analytics.mart_nrr_quarterly`
|
||||
**Grain:** one row per `quarter_label` × `segment`
|
||||
|
||||
## Columns
|
||||
|
|
@ -53,4 +53,3 @@ tables:
|
|||
- `net_revenue_retention` is a ratio, not a percentage. Multiply by 100 for display.
|
||||
- Contraction includes discount expirations (classified as contraction, not churn). See [orbit-nrr-discount-expiration-treatment](orbit-nrr-discount-expiration-treatment).
|
||||
- Enterprise is the primary executive reporting segment.
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ tables:
|
|||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/88.json -->
|
||||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/108.json -->
|
||||
|
||||
**Table:** `orbit_analytics.mart_procurement_activity`
|
||||
**Table:** `orbit_analytics.mart_procurement_activity`
|
||||
**Grain:** one row per `week_start_date` × `contract_arr_threshold_cents`
|
||||
|
||||
## Columns
|
||||
|
|
@ -45,4 +45,3 @@ tables:
|
|||
- `active_requesters` counts non-internal, non-test requesters on large active contracts. See [orbit-procurement-qualifying-actions](orbit-procurement-qualifying-actions).
|
||||
- The standard threshold is `contract_arr_threshold_cents = 20000000` ($200k ARR).
|
||||
- Always filter by `contract_arr_threshold_cents` — the table contains rows for multiple threshold values.
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ tables:
|
|||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/105.json -->
|
||||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/115.json -->
|
||||
|
||||
**Table:** `orbit_analytics.mart_retention_movement_breakout`
|
||||
**Table:** `orbit_analytics.mart_retention_movement_breakout`
|
||||
**Grain:** one row per `quarter_label` × `segment` × `movement_type` × `movement_reason`
|
||||
|
||||
## Columns
|
||||
|
|
@ -53,4 +53,3 @@ tables:
|
|||
- Contraction includes discount expirations, classified as contraction (not churn), tracked via `movement_reason`. See [orbit-nrr-discount-expiration-treatment](orbit-nrr-discount-expiration-treatment).
|
||||
- This table is the row-level source for `mart_nrr_quarterly` aggregations.
|
||||
- Only one of `expansion_arr_cents`, `contraction_arr_cents`, `churned_arr_cents` is non-zero per row.
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ tables:
|
|||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/102.json -->
|
||||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/104.json -->
|
||||
|
||||
**Table:** `orbit_analytics.mart_revenue_daily`
|
||||
**Table:** `orbit_analytics.mart_revenue_daily`
|
||||
**Grain:** one row per `revenue_date`
|
||||
|
||||
## Columns
|
||||
|
|
@ -54,4 +54,3 @@ tables:
|
|||
- `reconciliation_check` must be `true` on every row. Any `false` row indicates a data quality issue.
|
||||
- Gross-to-net reconciliation: gross revenue − credits − refunds = net revenue. See [orbit-revenue-gross-to-net-reconciliation](orbit-revenue-gross-to-net-reconciliation).
|
||||
- All amounts are in cents; divide by 100 for USD, by 100,000,000 for $M.
|
||||
|
||||
|
|
|
|||
|
|
@ -69,4 +69,3 @@ Card 48 is the canonical reference; card 55 is a filtered variant for large-cont
|
|||
| 53 | Enterprise NRR quarter breakout | mart_nrr_quarterly | 0 |
|
||||
| 54 | February credits drilldown | mart_revenue_daily | 0 |
|
||||
| 55 | Large contract requesters | mart_account_segments | 0 |
|
||||
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ sl_refs:
|
|||
|
||||
# NRR — Discount Expiration Treatment
|
||||
|
||||
**Governed metric key:** `net_revenue_retention`
|
||||
**Owner team:** analytics
|
||||
**Notion:** `notion://notion_page_retention_policy_current#nrr-definition` and `#discount-expiration-treatment`
|
||||
**Governed metric key:** `net_revenue_retention`
|
||||
**Owner team:** analytics
|
||||
**Notion:** `notion://notion_page_retention_policy_current#nrr-definition` and `#discount-expiration-treatment`
|
||||
**Sources:** `mart_nrr_quarterly`, `mart_retention_movement_breakout`
|
||||
|
||||
## NRR Definition
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ sl_refs:
|
|||
|
||||
# Plan & Segment Normalization
|
||||
|
||||
**Governed metric key:** `segment`
|
||||
**Owner team:** sales_ops
|
||||
**Notion:** `notion://notion_page_sales_ops_segmentation#growth-plan-normalization`
|
||||
**Governed metric key:** `segment`
|
||||
**Owner team:** sales_ops
|
||||
**Notion:** `notion://notion_page_sales_ops_segmentation#growth-plan-normalization`
|
||||
**Sources:** `mart_account_segments`, `stg_plan_segment_mapping`, `stg_plans`
|
||||
|
||||
## Canonical Plan Codes
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ sl_refs:
|
|||
|
||||
# Procurement — Qualifying Actions & Weekly Active Requesters
|
||||
|
||||
**Governed metric key:** `weekly_active_requesters`
|
||||
**Owner team:** product
|
||||
**Notion:** `notion://notion_page_procurement_instrumentation#qualifying-procurement-actions`
|
||||
**Governed metric key:** `weekly_active_requesters`
|
||||
**Owner team:** product
|
||||
**Notion:** `notion://notion_page_procurement_instrumentation#qualifying-procurement-actions`
|
||||
**Sources:** `mart_procurement_activity`, `int_procurement_qualifying_actions`
|
||||
|
||||
## Qualifying Action Definition
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ sl_refs:
|
|||
|
||||
# Revenue — Gross-to-Net Reconciliation
|
||||
|
||||
**Governed metric key:** `net_revenue`
|
||||
**Owner team:** finance
|
||||
**Notion:** `notion://notion_page_revenue_reporting_policy#gross-to-net-reconciliation`
|
||||
**Governed metric key:** `net_revenue`
|
||||
**Owner team:** finance
|
||||
**Notion:** `notion://notion_page_revenue_reporting_policy#gross-to-net-reconciliation`
|
||||
**Source:** `mart_revenue_daily` (grain: `revenue_date`)
|
||||
|
||||
## Formula
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ refs:
|
|||
|
||||
## Sales Ops → Customer Success Implementation Handoff
|
||||
|
||||
**Source:** Notion — People & Operating Norms, last edited 2026-05-07
|
||||
**Source:** Notion — People & Operating Norms, last edited 2026-05-07
|
||||
**Owner:** Sales Ops (sender), Customer Success (receiver)
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import type { renderMemoryFlowTui } from './memory-flow-tui.js';
|
|||
import { KTX_NEXT_STEP_COMMANDS } from './next-steps.js';
|
||||
import { resetVizFallbackWarningsForTest } from './viz-fallback.js';
|
||||
|
||||
const SEEDED_DEMO_SEMANTIC_SOURCE_COUNT = 46;
|
||||
const SEEDED_DEMO_KNOWLEDGE_PAGE_COUNT = 28;
|
||||
|
||||
function makeIo(options: { isTTY?: boolean; columns?: number; rawMode?: boolean } = {}) {
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
|
@ -336,8 +339,14 @@ describe('runKtxDemo', () => {
|
|||
notion: { pageCount: 8 },
|
||||
},
|
||||
generatedOutputs: {
|
||||
semanticLayer: { manifestSourceCount: 46, fileCount: 46 },
|
||||
knowledge: { manifestPageCount: 28, fileCount: 28 },
|
||||
semanticLayer: {
|
||||
manifestSourceCount: SEEDED_DEMO_SEMANTIC_SOURCE_COUNT,
|
||||
fileCount: SEEDED_DEMO_SEMANTIC_SOURCE_COUNT,
|
||||
},
|
||||
knowledge: {
|
||||
manifestPageCount: SEEDED_DEMO_KNOWLEDGE_PAGE_COUNT,
|
||||
fileCount: SEEDED_DEMO_KNOWLEDGE_PAGE_COUNT,
|
||||
},
|
||||
links: { manifestLinkCount: 23, linkCount: 23 },
|
||||
reports: { primaryPath: 'reports/seeded-demo-report.json', fileCount: 1 },
|
||||
},
|
||||
|
|
@ -636,10 +645,16 @@ describe('runKtxDemo', () => {
|
|||
).resolves.toBe(0);
|
||||
|
||||
expect(seededIo.stdout()).toContain('Status: ready');
|
||||
expect(seededIo.stdout()).toContain('Semantic-layer sources: 46 manifest, 46 files');
|
||||
expect(seededIo.stdout()).toContain('Knowledge pages: 28 manifest, 28 files');
|
||||
expect(seededIo.stdout()).toContain(
|
||||
`Semantic-layer sources: ${SEEDED_DEMO_SEMANTIC_SOURCE_COUNT} manifest, ${SEEDED_DEMO_SEMANTIC_SOURCE_COUNT} files`,
|
||||
);
|
||||
expect(seededIo.stdout()).toContain(
|
||||
`Knowledge pages: ${SEEDED_DEMO_KNOWLEDGE_PAGE_COUNT} manifest, ${SEEDED_DEMO_KNOWLEDGE_PAGE_COUNT} files`,
|
||||
);
|
||||
expect(seededIo.stdout()).not.toContain('Status: corrupt');
|
||||
expect(seededIo.stdout()).not.toContain('Semantic-layer sources: 46 manifest, 0 files');
|
||||
expect(seededIo.stdout()).not.toContain(
|
||||
`Semantic-layer sources: ${SEEDED_DEMO_SEMANTIC_SOURCE_COUNT} manifest, 0 files`,
|
||||
);
|
||||
});
|
||||
|
||||
it('fails corrupted demo projects in no-input mode with reset guidance', async () => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { tmpdir } from 'node:os';
|
|||
import { join } from 'node:path';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { localFakeBundleReport, persistLocalBundleReport } from './ingest.test-utils.js';
|
||||
import { contextBuildCommands, writeKtxSetupContextState } from './setup-context.js';
|
||||
import { runDemoTour } from './setup-demo-tour.js';
|
||||
import { readKtxSetupStatus, runKtxSetup } from './setup.js';
|
||||
|
|
@ -311,6 +312,62 @@ describe('setup status', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('reports Vertex LLM and context ready after a successful Metabase ingest report', async () => {
|
||||
await writeFile(
|
||||
join(tempDir, 'ktx.yaml'),
|
||||
[
|
||||
'project: revenue',
|
||||
'setup:',
|
||||
' database_connection_ids:',
|
||||
' - warehouse',
|
||||
' completed_steps:',
|
||||
' - project',
|
||||
' - databases',
|
||||
' - sources',
|
||||
'connections:',
|
||||
' warehouse:',
|
||||
' driver: postgres',
|
||||
' url: env:DATABASE_URL',
|
||||
' metabase:',
|
||||
' driver: metabase',
|
||||
' url: env:METABASE_URL',
|
||||
' api_key_ref: env:METABASE_API_KEY',
|
||||
' warehouse_connection_id: warehouse',
|
||||
'llm:',
|
||||
' provider:',
|
||||
' backend: vertex',
|
||||
' vertex:',
|
||||
' project: kaelio-dev',
|
||||
' location: us-east5',
|
||||
' models:',
|
||||
' default: claude-sonnet-4-6',
|
||||
'ingest:',
|
||||
' embeddings:',
|
||||
' backend: deterministic',
|
||||
' model: deterministic',
|
||||
' dimensions: 8',
|
||||
'',
|
||||
].join('\n'),
|
||||
'utf-8',
|
||||
);
|
||||
await persistLocalBundleReport(
|
||||
tempDir,
|
||||
localFakeBundleReport('metabase-job-1', {
|
||||
connectionId: 'warehouse',
|
||||
sourceKey: 'metabase',
|
||||
}),
|
||||
);
|
||||
|
||||
const status = await readKtxSetupStatus(tempDir);
|
||||
const io = makeIo();
|
||||
await expect(runKtxSetup({ command: 'status', projectDir: tempDir, json: false }, io.io)).resolves.toBe(0);
|
||||
|
||||
expect(status.llm).toMatchObject({ backend: 'vertex', ready: true, model: 'claude-sonnet-4-6' });
|
||||
expect(status.context).toMatchObject({ ready: true, status: 'completed' });
|
||||
expect(io.stdout()).toContain('LLM ready: yes (claude-sonnet-4-6)');
|
||||
expect(io.stdout()).toContain('KTX context built: yes');
|
||||
});
|
||||
|
||||
it('prints plain and JSON setup status', async () => {
|
||||
const plainIo = makeIo();
|
||||
const jsonIo = makeIo();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { existsSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { cancel, isCancel, select } from '@clack/prompts';
|
||||
import { loadKtxProject } from '@ktx/context/project';
|
||||
import { getLatestLocalIngestStatus, savedMemoryCountsForReport } from '@ktx/context/ingest';
|
||||
import { ktxLocalStateDbPath, loadKtxProject, type KtxLocalProject } from '@ktx/context/project';
|
||||
import type { KtxCliIo } from './cli-runtime.js';
|
||||
import { formatSetupNextStepLines } from './next-steps.js';
|
||||
import { isKtxSetupExitError, withSetupInterruptConfirmation } from './setup-interrupt.js';
|
||||
|
|
@ -248,6 +249,31 @@ function sourceConnections(config: Awaited<ReturnType<typeof loadKtxProject>>['c
|
|||
.sort((left, right) => left.connectionId.localeCompare(right.connectionId));
|
||||
}
|
||||
|
||||
type LocalIngestStatusReport = NonNullable<Awaited<ReturnType<typeof getLatestLocalIngestStatus>>>;
|
||||
|
||||
function reportHasSavedContext(report: LocalIngestStatusReport): boolean {
|
||||
if (report.body.failedWorkUnits.length > 0) {
|
||||
return false;
|
||||
}
|
||||
const counts = savedMemoryCountsForReport(report);
|
||||
return counts.wikiCount > 0 || counts.slCount > 0;
|
||||
}
|
||||
|
||||
async function readIngestContextStatus(project: KtxLocalProject): Promise<KtxSetupContextStatusSummary | null> {
|
||||
if (!existsSync(ktxLocalStateDbPath(project))) {
|
||||
return null;
|
||||
}
|
||||
const report = await getLatestLocalIngestStatus(project);
|
||||
if (!report || !reportHasSavedContext(report)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
ready: true,
|
||||
status: 'completed',
|
||||
runId: report.runId,
|
||||
};
|
||||
}
|
||||
|
||||
export async function readKtxSetupStatus(projectDir: string): Promise<KtxSetupStatus> {
|
||||
const resolvedProjectDir = resolve(projectDir);
|
||||
if (!existsSync(join(resolvedProjectDir, 'ktx.yaml'))) {
|
||||
|
|
@ -279,6 +305,10 @@ export async function readKtxSetupStatus(projectDir: string): Promise<KtxSetupSt
|
|||
|
||||
const completedSteps = project.config.setup?.completed_steps ?? [];
|
||||
const contextState = await readKtxSetupContextState(resolvedProjectDir);
|
||||
const setupContextStatus = setupContextStatusFromState(contextState, {
|
||||
completedStep: completedSteps.includes('context'),
|
||||
});
|
||||
const ingestContextStatus = setupContextStatus.ready ? null : await readIngestContextStatus(project);
|
||||
const databaseIds = project.config.setup?.database_connection_ids ?? Object.keys(project.config.connections);
|
||||
const databasesComplete = completedSteps.includes('databases');
|
||||
const manifest = await readKtxAgentInstallManifest(resolvedProjectDir);
|
||||
|
|
@ -301,7 +331,7 @@ export async function readKtxSetupStatus(projectDir: string): Promise<KtxSetupSt
|
|||
...source,
|
||||
ready: completedSteps.includes('sources'),
|
||||
})),
|
||||
context: setupContextStatusFromState(contextState, { completedStep: completedSteps.includes('context') }),
|
||||
context: ingestContextStatus ?? setupContextStatus,
|
||||
agents,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ describe('standalone built ktx CLI smoke', () => {
|
|||
const knowledgeSearch = structuredContent<{
|
||||
results: Array<{ key: string; summary: string; score: number }>;
|
||||
totalFound: number;
|
||||
}>(await client.callTool({ name: 'knowledge_search', arguments: { query: 'ARR contract', limit: 5 } }));
|
||||
}>(await client.callTool({ name: 'knowledge_search', arguments: { query: 'ARR contract-first definition', limit: 10 } }));
|
||||
expect(knowledgeSearch.totalFound).toBeGreaterThan(0);
|
||||
expect(knowledgeSearch.results.map((result) => result.key)).toContain('orbit-arr-contract-first-definition');
|
||||
|
||||
|
|
@ -387,7 +387,7 @@ describe('standalone built ktx CLI smoke', () => {
|
|||
const slRead = structuredContent<{ sourceName: string; yaml: string }>(
|
||||
await client.callTool({
|
||||
name: 'sl_read_source',
|
||||
arguments: { connectionId: 'postgres-warehouse', sourceName: 'mart_arr_daily' },
|
||||
arguments: { connectionId: 'dbt-main', sourceName: 'mart_arr_daily' },
|
||||
}),
|
||||
);
|
||||
expect(slRead.sourceName).toBe('mart_arr_daily');
|
||||
|
|
@ -397,7 +397,7 @@ describe('standalone built ktx CLI smoke', () => {
|
|||
const slValidate = structuredContent<{ success: boolean; errors: string[]; warnings: string[] }>(
|
||||
await client.callTool({
|
||||
name: 'sl_validate',
|
||||
arguments: { connectionId: 'postgres-warehouse', names: ['mart_arr_daily'] },
|
||||
arguments: { connectionId: 'dbt-main', names: ['mart_arr_daily', 'stg_contracts'] },
|
||||
}),
|
||||
);
|
||||
expect(slValidate.success).toBe(true);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue