mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-07 07:55:13 +02:00
Merge pull request #26 from Kaelio/luca-martial/demo-data-wizard-ux
feat(cli): add demo guided tour with real Orbit project data
This commit is contained in:
commit
950fa151ce
104 changed files with 6779 additions and 468 deletions
813
docs/superpowers/plans/2026-05-11-demo-guided-tour.md
Normal file
813
docs/superpowers/plans/2026-05-11-demo-guided-tour.md
Normal file
|
|
@ -0,0 +1,813 @@
|
|||
# Demo Guided Tour Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Replace the disconnected "Try KTX with packaged demo data" flow with a guided tour that walks users through the same setup wizard steps using pre-filled, read-only selections, then connects their agent to the populated demo project.
|
||||
|
||||
**Architecture:** A new `setup-demo-tour.ts` module owns the demo tour flow. It renders read-only cards (database, sources), a simulated context build replay using the existing `renderContextBuildView` + `createRepainter` pipeline from `context-build-view.ts`, then hands off to the real `runKtxSetupAgentsStep`. The entry point in `setup.ts` (`runKtxSetupDemoFromEntryMenu`) is rewired to call this new module instead of `runKtxDemo`.
|
||||
|
||||
**Tech Stack:** TypeScript (ESM), Node.js raw stdin for keypress handling, existing `@clack/prompts` visual patterns, vitest for tests.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Create `setup-demo-tour.ts` with keypress utility and banner
|
||||
|
||||
**Files:**
|
||||
- Create: `packages/cli/src/setup-demo-tour.ts`
|
||||
- Test: `packages/cli/src/setup-demo-tour.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing test for `renderDemoBanner`**
|
||||
|
||||
```typescript
|
||||
// packages/cli/src/setup-demo-tour.test.ts
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { renderDemoBanner } from './setup-demo-tour.js';
|
||||
|
||||
describe('renderDemoBanner', () => {
|
||||
it('includes demo mode explanation', () => {
|
||||
const output = renderDemoBanner();
|
||||
expect(output).toContain('Demo mode');
|
||||
expect(output).toContain('pre-processed');
|
||||
expect(output).toContain('read-only');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the test to verify it fails**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour`
|
||||
Expected: FAIL — module not found
|
||||
|
||||
- [ ] **Step 3: Implement `renderDemoBanner` and `waitForDemoNavigation`**
|
||||
|
||||
```typescript
|
||||
// packages/cli/src/setup-demo-tour.ts
|
||||
import type { KtxCliIo } from './cli-runtime.js';
|
||||
import { KtxSetupExitError } from './setup-interrupt.js';
|
||||
|
||||
const ESC = String.fromCharCode(0x1b);
|
||||
|
||||
function cyan(text: string): string {
|
||||
return `${ESC}[36m${text}${ESC}[39m`;
|
||||
}
|
||||
|
||||
function dim(text: string): string {
|
||||
return `${ESC}[2m${text}${ESC}[22m`;
|
||||
}
|
||||
|
||||
export function renderDemoBanner(): string {
|
||||
const lines = [
|
||||
'',
|
||||
`┌ ${cyan('Demo mode')} — data has been pre-processed and KTX context is already built.`,
|
||||
`│ This walkthrough illustrates the setup steps. Selections are pre-filled and read-only.`,
|
||||
'',
|
||||
];
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export async function waitForDemoNavigation(
|
||||
stdin: NodeJS.ReadStream = process.stdin,
|
||||
): Promise<'forward' | 'back'> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const wasRaw = stdin.isRaw;
|
||||
if (stdin.setRawMode) stdin.setRawMode(true);
|
||||
stdin.resume();
|
||||
|
||||
const onData = (data: Buffer) => {
|
||||
const key = data.toString();
|
||||
if (key === '\r' || key === '\n') {
|
||||
cleanup();
|
||||
resolve('forward');
|
||||
} else if (key === '\x1b') {
|
||||
cleanup();
|
||||
resolve('back');
|
||||
} else if (key === '\x03') {
|
||||
cleanup();
|
||||
reject(new KtxSetupExitError());
|
||||
}
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
stdin.off('data', onData);
|
||||
if (stdin.setRawMode) stdin.setRawMode(wasRaw ?? false);
|
||||
};
|
||||
|
||||
stdin.on('data', onData);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the test to verify it passes**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add packages/cli/src/setup-demo-tour.ts packages/cli/src/setup-demo-tour.test.ts
|
||||
git commit -m "feat(cli): add demo tour banner and keypress navigation utility"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Add `renderDemoCard` function
|
||||
|
||||
**Files:**
|
||||
- Modify: `packages/cli/src/setup-demo-tour.ts`
|
||||
- Modify: `packages/cli/src/setup-demo-tour.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing test for `renderDemoCard`**
|
||||
|
||||
Append to the test file:
|
||||
|
||||
```typescript
|
||||
import { renderDemoCardContent } from './setup-demo-tour.js';
|
||||
|
||||
describe('renderDemoCardContent', () => {
|
||||
it('renders a card with title and selections', () => {
|
||||
const output = renderDemoCardContent('Database connection', ['PostgreSQL (demo warehouse)']);
|
||||
expect(output).toContain('Database connection');
|
||||
expect(output).toContain('PostgreSQL (demo warehouse)');
|
||||
expect(output).toContain('Press Enter to continue');
|
||||
expect(output).toContain('Escape to go back');
|
||||
});
|
||||
|
||||
it('renders multiple selections', () => {
|
||||
const output = renderDemoCardContent('Context sources', ['dbt', 'Metabase', 'Notion']);
|
||||
expect(output).toContain('dbt');
|
||||
expect(output).toContain('Metabase');
|
||||
expect(output).toContain('Notion');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the test to verify it fails**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour`
|
||||
Expected: FAIL — `renderDemoCardContent` not exported
|
||||
|
||||
- [ ] **Step 3: Implement `renderDemoCardContent` and `renderDemoCard`**
|
||||
|
||||
Add to `setup-demo-tour.ts`:
|
||||
|
||||
```typescript
|
||||
export function renderDemoCardContent(title: string, selections: string[]): string {
|
||||
const lines = [
|
||||
`┌ ${title}`,
|
||||
'│',
|
||||
...selections.map((s) => `│ ${cyan('▸')} ${s}`),
|
||||
'│',
|
||||
`│ ${dim('Press Enter to continue, Escape to go back')}`,
|
||||
'└',
|
||||
'',
|
||||
];
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export async function renderDemoCard(
|
||||
title: string,
|
||||
selections: string[],
|
||||
io: KtxCliIo,
|
||||
stdin?: NodeJS.ReadStream,
|
||||
waitNav?: (stdin?: NodeJS.ReadStream) => Promise<'forward' | 'back'>,
|
||||
): Promise<'forward' | 'back'> {
|
||||
io.stdout.write(renderDemoBanner());
|
||||
io.stdout.write(renderDemoCardContent(title, selections));
|
||||
const nav = waitNav ?? waitForDemoNavigation;
|
||||
return nav(stdin);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the test to verify it passes**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add packages/cli/src/setup-demo-tour.ts packages/cli/src/setup-demo-tour.test.ts
|
||||
git commit -m "feat(cli): add demo tour read-only card rendering"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Add demo context build replay animation
|
||||
|
||||
**Files:**
|
||||
- Modify: `packages/cli/src/setup-demo-tour.ts`
|
||||
- Modify: `packages/cli/src/setup-demo-tour.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing test for demo replay event sequence**
|
||||
|
||||
Append to the test file:
|
||||
|
||||
```typescript
|
||||
import { buildDemoReplayTimeline, DEMO_REPLAY_TARGETS } from './setup-demo-tour.js';
|
||||
|
||||
describe('buildDemoReplayTimeline', () => {
|
||||
it('produces events for all four demo targets', () => {
|
||||
const events = buildDemoReplayTimeline();
|
||||
const connectionIds = new Set(events.map((e) => e.connectionId));
|
||||
expect(connectionIds).toEqual(new Set(['demo-warehouse', 'dbt', 'metabase', 'notion']));
|
||||
});
|
||||
|
||||
it('ends with all targets done', () => {
|
||||
const events = buildDemoReplayTimeline();
|
||||
const lastByConnection = new Map<string, string>();
|
||||
for (const e of events) {
|
||||
lastByConnection.set(e.connectionId, e.status);
|
||||
}
|
||||
for (const status of lastByConnection.values()) {
|
||||
expect(status).toBe('done');
|
||||
}
|
||||
});
|
||||
|
||||
it('events are sorted by delayMs', () => {
|
||||
const events = buildDemoReplayTimeline();
|
||||
for (let i = 1; i < events.length; i++) {
|
||||
expect(events[i]!.delayMs).toBeGreaterThanOrEqual(events[i - 1]!.delayMs);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('DEMO_REPLAY_TARGETS', () => {
|
||||
it('has one primary source and three context sources', () => {
|
||||
expect(DEMO_REPLAY_TARGETS.primarySources).toHaveLength(1);
|
||||
expect(DEMO_REPLAY_TARGETS.contextSources).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the test to verify it fails**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour`
|
||||
Expected: FAIL — exports not found
|
||||
|
||||
- [ ] **Step 3: Implement replay timeline and target definitions**
|
||||
|
||||
Add to `setup-demo-tour.ts`:
|
||||
|
||||
```typescript
|
||||
import type { KtxPublicIngestPlanTarget } from './public-ingest.js';
|
||||
import type { ContextBuildTargetState, ContextBuildViewState } from './context-build-view.js';
|
||||
|
||||
export interface DemoReplayEvent {
|
||||
delayMs: number;
|
||||
connectionId: string;
|
||||
status: 'running' | 'done';
|
||||
detailLine: string | null;
|
||||
summaryText: string | null;
|
||||
}
|
||||
|
||||
function createDemoTarget(connectionId: string, operation: 'scan' | 'source-ingest', driver: string): KtxPublicIngestPlanTarget {
|
||||
return {
|
||||
connectionId,
|
||||
driver,
|
||||
operation,
|
||||
debugCommand: `ktx ${operation === 'scan' ? 'scan' : 'ingest'} ${connectionId}`,
|
||||
steps: operation === 'scan' ? ['scan'] : ['source-ingest'],
|
||||
};
|
||||
}
|
||||
|
||||
const primaryTarget = createDemoTarget('demo-warehouse', 'scan', 'postgres');
|
||||
const dbtTarget = createDemoTarget('dbt', 'source-ingest', 'dbt');
|
||||
const metabaseTarget = createDemoTarget('metabase', 'source-ingest', 'metabase');
|
||||
const notionTarget = createDemoTarget('notion', 'source-ingest', 'notion');
|
||||
|
||||
function createTargetState(target: KtxPublicIngestPlanTarget): ContextBuildTargetState {
|
||||
return {
|
||||
target,
|
||||
status: 'queued',
|
||||
detailLine: null,
|
||||
summaryText: null,
|
||||
startedAt: null,
|
||||
elapsedMs: 0,
|
||||
};
|
||||
}
|
||||
|
||||
export const DEMO_REPLAY_TARGETS = {
|
||||
primarySources: [primaryTarget],
|
||||
contextSources: [dbtTarget, metabaseTarget, notionTarget],
|
||||
};
|
||||
|
||||
export function buildDemoReplayTimeline(): DemoReplayEvent[] {
|
||||
return [
|
||||
{ delayMs: 0, connectionId: 'demo-warehouse', status: 'running', detailLine: 'scanning...', summaryText: null },
|
||||
{ delayMs: 600, connectionId: 'demo-warehouse', status: 'running', detailLine: '[50%] scanning...', summaryText: null },
|
||||
{ delayMs: 1200, connectionId: 'demo-warehouse', status: 'done', detailLine: null, summaryText: 'completed' },
|
||||
{ delayMs: 1200, connectionId: 'dbt', status: 'running', detailLine: 'ingesting...', summaryText: null },
|
||||
{ delayMs: 1800, connectionId: 'dbt', status: 'running', detailLine: '[60%] ingesting...', summaryText: null },
|
||||
{ delayMs: 2200, connectionId: 'dbt', status: 'done', detailLine: null, summaryText: 'completed' },
|
||||
{ delayMs: 2200, connectionId: 'metabase', status: 'running', detailLine: 'ingesting...', summaryText: null },
|
||||
{ delayMs: 2800, connectionId: 'metabase', status: 'done', detailLine: null, summaryText: 'completed' },
|
||||
{ delayMs: 2800, connectionId: 'notion', status: 'running', detailLine: 'ingesting...', summaryText: null },
|
||||
{ delayMs: 3400, connectionId: 'notion', status: 'done', detailLine: null, summaryText: 'completed' },
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the test to verify it passes**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Implement `runDemoContextReplay` animation driver**
|
||||
|
||||
Add to `setup-demo-tour.ts`:
|
||||
|
||||
```typescript
|
||||
import { renderContextBuildView, createRepainter } from './context-build-view.js';
|
||||
|
||||
export async function runDemoContextReplay(
|
||||
io: KtxCliIo,
|
||||
stdin?: NodeJS.ReadStream,
|
||||
): Promise<'forward' | 'back'> {
|
||||
const repainter = createRepainter(io);
|
||||
const timeline = buildDemoReplayTimeline();
|
||||
|
||||
const state: ContextBuildViewState = {
|
||||
primarySources: DEMO_REPLAY_TARGETS.primarySources.map((t) => createTargetState(t)),
|
||||
contextSources: DEMO_REPLAY_TARGETS.contextSources.map((t) => createTargetState(t)),
|
||||
frame: 0,
|
||||
startedAt: Date.now(),
|
||||
totalElapsedMs: 0,
|
||||
};
|
||||
|
||||
const allTargets = [...state.primarySources, ...state.contextSources];
|
||||
const targetMap = new Map(allTargets.map((t) => [t.target.connectionId, t]));
|
||||
let eventIndex = 0;
|
||||
const startTime = Date.now();
|
||||
const FRAME_MS = 120;
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
state.frame += 1;
|
||||
state.totalElapsedMs = elapsed;
|
||||
|
||||
while (eventIndex < timeline.length && timeline[eventIndex]!.delayMs <= elapsed) {
|
||||
const event = timeline[eventIndex]!;
|
||||
const target = targetMap.get(event.connectionId);
|
||||
if (target) {
|
||||
target.status = event.status;
|
||||
target.detailLine = event.detailLine;
|
||||
target.summaryText = event.summaryText;
|
||||
if (event.status === 'running' && target.startedAt === null) {
|
||||
target.startedAt = Date.now();
|
||||
}
|
||||
if (event.status === 'done') {
|
||||
target.elapsedMs = target.startedAt ? Date.now() - target.startedAt : 0;
|
||||
}
|
||||
}
|
||||
eventIndex += 1;
|
||||
}
|
||||
|
||||
for (const t of allTargets) {
|
||||
if (t.status === 'running' && t.startedAt !== null) {
|
||||
t.elapsedMs = Date.now() - t.startedAt;
|
||||
}
|
||||
}
|
||||
|
||||
repainter.paint(renderContextBuildView(state, { styled: io.stdout.isTTY ?? false, showHint: false }));
|
||||
|
||||
if (eventIndex >= timeline.length && allTargets.every((t) => t.status === 'done')) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, FRAME_MS);
|
||||
});
|
||||
|
||||
io.stdout.write(renderDemoContextCompletionSummary());
|
||||
return waitForDemoNavigation(stdin);
|
||||
}
|
||||
|
||||
function renderDemoContextCompletionSummary(): string {
|
||||
const lines = [
|
||||
'',
|
||||
`${cyan('★')} KTX finished ingesting demo data`,
|
||||
'',
|
||||
' Placeholder — final counts will come from pre-packaged demo results.',
|
||||
'',
|
||||
` ${dim('Press Enter to continue, Escape to go back')}`,
|
||||
'',
|
||||
];
|
||||
return lines.join('\n');
|
||||
}
|
||||
```
|
||||
|
||||
Note: `renderDemoContextCompletionSummary` is a placeholder that will be updated when
|
||||
the user provides the real pre-packaged demo data. The summary counts (business areas,
|
||||
query definitions, knowledge pages) will be populated from those assets.
|
||||
|
||||
- [ ] **Step 6: Run tests and type-check**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run type-check && pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add packages/cli/src/setup-demo-tour.ts packages/cli/src/setup-demo-tour.test.ts
|
||||
git commit -m "feat(cli): add demo context build replay animation"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Add transition message and completion summary
|
||||
|
||||
**Files:**
|
||||
- Modify: `packages/cli/src/setup-demo-tour.ts`
|
||||
- Modify: `packages/cli/src/setup-demo-tour.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing tests**
|
||||
|
||||
Append to test file:
|
||||
|
||||
```typescript
|
||||
import { renderDemoAgentTransition, renderDemoCompletionSummary } from './setup-demo-tour.js';
|
||||
|
||||
describe('renderDemoAgentTransition', () => {
|
||||
it('includes transition message about connecting agent', () => {
|
||||
const output = renderDemoAgentTransition();
|
||||
expect(output).toContain('Demo project is ready');
|
||||
expect(output).toContain('connect your agent');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderDemoCompletionSummary', () => {
|
||||
it('includes project path and temp warning', () => {
|
||||
const output = renderDemoCompletionSummary('/tmp/ktx-demo-abc123', true);
|
||||
expect(output).toContain('/tmp/ktx-demo-abc123');
|
||||
expect(output).toContain('temporary');
|
||||
expect(output).toContain('ktx setup');
|
||||
});
|
||||
|
||||
it('shows manual agent instructions when agent not installed', () => {
|
||||
const output = renderDemoCompletionSummary('/tmp/ktx-demo-abc123', false);
|
||||
expect(output).toContain('ktx setup --agents');
|
||||
});
|
||||
|
||||
it('shows success message when agent installed', () => {
|
||||
const output = renderDemoCompletionSummary('/tmp/ktx-demo-abc123', true);
|
||||
expect(output).toContain('agent is connected');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the test to verify it fails**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour`
|
||||
Expected: FAIL — exports not found
|
||||
|
||||
- [ ] **Step 3: Implement transition and completion rendering**
|
||||
|
||||
Add to `setup-demo-tour.ts`:
|
||||
|
||||
```typescript
|
||||
export function renderDemoAgentTransition(): string {
|
||||
const lines = [
|
||||
'',
|
||||
`┌ Demo project is ready — let's connect your agent`,
|
||||
'│',
|
||||
'│ Your KTX context has been built with demo data.',
|
||||
'│ Select an agent to start using it.',
|
||||
'└',
|
||||
'',
|
||||
];
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function renderDemoCompletionSummary(projectDir: string, agentInstalled: boolean): string {
|
||||
const lines = [
|
||||
'',
|
||||
`${cyan('★')} KTX demo is ready`,
|
||||
'',
|
||||
];
|
||||
|
||||
if (agentInstalled) {
|
||||
lines.push(' Your agent is connected to a demo KTX project.');
|
||||
} else {
|
||||
lines.push(' Demo project created. Connect an agent to start using it:');
|
||||
lines.push(` $ ktx setup --agents --project-dir ${projectDir}`);
|
||||
}
|
||||
|
||||
lines.push(
|
||||
'',
|
||||
` ${dim('⚠')} This project is in a temporary directory and will be`,
|
||||
` cleaned up by your system. To set up KTX with your own`,
|
||||
' data, run: ktx setup',
|
||||
'',
|
||||
` Project: ${projectDir}`,
|
||||
'',
|
||||
);
|
||||
return lines.join('\n');
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the test to verify it passes**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add packages/cli/src/setup-demo-tour.ts packages/cli/src/setup-demo-tour.test.ts
|
||||
git commit -m "feat(cli): add demo tour transition and completion summary"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Implement `runDemoTour` orchestrator
|
||||
|
||||
**Files:**
|
||||
- Modify: `packages/cli/src/setup-demo-tour.ts`
|
||||
- Modify: `packages/cli/src/setup-demo-tour.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write the failing test for the orchestrator**
|
||||
|
||||
Append to test file:
|
||||
|
||||
```typescript
|
||||
import { vi } from 'vitest';
|
||||
import type { KtxSetupAgentsResult } from './setup-agents.js';
|
||||
import { runDemoTour } from './setup-demo-tour.js';
|
||||
|
||||
describe('runDemoTour', () => {
|
||||
function createMockIo() {
|
||||
const chunks: string[] = [];
|
||||
return {
|
||||
io: {
|
||||
stdout: { isTTY: true, columns: 80, write: (chunk: string) => { chunks.push(chunk); } },
|
||||
stderr: { write: () => {} },
|
||||
},
|
||||
chunks,
|
||||
};
|
||||
}
|
||||
|
||||
it('returns 0 on successful tour with agent installed', async () => {
|
||||
const { io } = createMockIo();
|
||||
const mockAgents = vi.fn<() => Promise<KtxSetupAgentsResult>>().mockResolvedValue({
|
||||
status: 'ready',
|
||||
projectDir: '/tmp/test',
|
||||
installs: [{ target: 'claude-code' as const, scope: 'project' as const, mode: 'both' as const }],
|
||||
});
|
||||
|
||||
const navigation = vi.fn<() => Promise<'forward' | 'back'>>().mockResolvedValue('forward');
|
||||
|
||||
const result = await runDemoTour(
|
||||
{ inputMode: 'auto' },
|
||||
io,
|
||||
{ agents: mockAgents, waitForNavigation: navigation, skipReplayAnimation: true },
|
||||
);
|
||||
expect(result).toBe(0);
|
||||
expect(mockAgents).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('handles back navigation from first step', async () => {
|
||||
const { io } = createMockIo();
|
||||
const navigation = vi.fn<() => Promise<'forward' | 'back'>>().mockResolvedValue('back');
|
||||
|
||||
const result = await runDemoTour(
|
||||
{ inputMode: 'auto' },
|
||||
io,
|
||||
{ waitForNavigation: navigation, skipReplayAnimation: true },
|
||||
);
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the test to verify it fails**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour`
|
||||
Expected: FAIL — `runDemoTour` not exported or wrong signature
|
||||
|
||||
- [ ] **Step 3: Implement `runDemoTour`**
|
||||
|
||||
Add to `setup-demo-tour.ts`:
|
||||
|
||||
```typescript
|
||||
import { defaultDemoProjectDir, ensureSeededDemoProject } from './demo-assets.js';
|
||||
import type { KtxSetupAgentsResult } from './setup-agents.js';
|
||||
import { runKtxSetupAgentsStep } from './setup-agents.js';
|
||||
|
||||
type DemoStep = 'databases' | 'sources' | 'context' | 'agents';
|
||||
|
||||
const DEMO_STEPS: DemoStep[] = ['databases', 'sources', 'context', 'agents'];
|
||||
|
||||
export interface DemoTourDeps {
|
||||
agents?: (args: Parameters<typeof runKtxSetupAgentsStep>[0], io: KtxCliIo) => Promise<KtxSetupAgentsResult>;
|
||||
waitForNavigation?: (stdin?: NodeJS.ReadStream) => Promise<'forward' | 'back'>;
|
||||
ensureProject?: typeof ensureSeededDemoProject;
|
||||
skipReplayAnimation?: boolean;
|
||||
}
|
||||
|
||||
export async function runDemoTour(
|
||||
args: { inputMode: 'auto' | 'disabled' },
|
||||
io: KtxCliIo,
|
||||
deps: DemoTourDeps = {},
|
||||
): Promise<number> {
|
||||
const waitNav = deps.waitForNavigation ?? waitForDemoNavigation;
|
||||
const ensureProject = deps.ensureProject ?? ensureSeededDemoProject;
|
||||
|
||||
const projectDir = defaultDemoProjectDir();
|
||||
await ensureProject({ projectDir });
|
||||
|
||||
let stepIndex = 0;
|
||||
|
||||
while (stepIndex < DEMO_STEPS.length) {
|
||||
const step = DEMO_STEPS[stepIndex]!;
|
||||
let direction: 'forward' | 'back';
|
||||
|
||||
if (step === 'databases') {
|
||||
direction = await renderDemoCard('Database connection', ['PostgreSQL (demo warehouse)'], io, undefined, waitNav);
|
||||
} else if (step === 'sources') {
|
||||
direction = await renderDemoCard('Context sources', ['dbt', 'Metabase', 'Notion'], io, undefined, waitNav);
|
||||
} else if (step === 'context') {
|
||||
io.stdout.write(renderDemoBanner());
|
||||
if (deps.skipReplayAnimation) {
|
||||
direction = await waitNav();
|
||||
} else {
|
||||
direction = await runDemoContextReplay(io);
|
||||
}
|
||||
} else {
|
||||
io.stdout.write(renderDemoAgentTransition());
|
||||
const agentsRunner = deps.agents ?? runKtxSetupAgentsStep;
|
||||
const agentsResult = await agentsRunner(
|
||||
{
|
||||
projectDir,
|
||||
inputMode: args.inputMode,
|
||||
yes: false,
|
||||
agents: true,
|
||||
scope: 'project',
|
||||
mode: 'both',
|
||||
skipAgents: false,
|
||||
},
|
||||
io,
|
||||
);
|
||||
const agentInstalled = agentsResult.status === 'ready';
|
||||
if (agentsResult.status === 'back') {
|
||||
direction = 'back';
|
||||
} else {
|
||||
io.stdout.write(renderDemoCompletionSummary(projectDir, agentInstalled));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (direction === 'back') {
|
||||
if (stepIndex === 0) return 0;
|
||||
stepIndex -= 1;
|
||||
} else {
|
||||
stepIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the test to verify it passes**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run test -- --testPathPattern setup-demo-tour`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Run type-check**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run type-check`
|
||||
Expected: PASS — all types align with existing interfaces
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add packages/cli/src/setup-demo-tour.ts packages/cli/src/setup-demo-tour.test.ts
|
||||
git commit -m "feat(cli): add runDemoTour orchestrator with step navigation"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Wire up in `setup.ts`
|
||||
|
||||
**Files:**
|
||||
- Modify: `packages/cli/src/setup.ts`
|
||||
|
||||
- [ ] **Step 1: Read the current `runKtxSetupDemoFromEntryMenu` function**
|
||||
|
||||
Read `packages/cli/src/setup.ts` and locate `runKtxSetupDemoFromEntryMenu` (around lines 218-233).
|
||||
|
||||
Current implementation:
|
||||
```typescript
|
||||
async function runKtxSetupDemoFromEntryMenu(
|
||||
args: Extract<KtxSetupArgs, { command: 'run' }>,
|
||||
io: KtxCliIo,
|
||||
deps: KtxSetupDeps,
|
||||
): Promise<number> {
|
||||
const runner = deps.demo ?? (await import('./demo.js')).runKtxDemo;
|
||||
return await runner(
|
||||
{
|
||||
command: 'seeded',
|
||||
projectDir: defaultDemoProjectDir(),
|
||||
outputMode: 'viz',
|
||||
inputMode: args.inputMode,
|
||||
},
|
||||
io,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Replace with demo tour call**
|
||||
|
||||
Replace the function body to call `runDemoTour`:
|
||||
|
||||
```typescript
|
||||
async function runKtxSetupDemoFromEntryMenu(
|
||||
args: Extract<KtxSetupArgs, { command: 'run' }>,
|
||||
io: KtxCliIo,
|
||||
deps: KtxSetupDeps,
|
||||
): Promise<number> {
|
||||
const { runDemoTour } = await import('./setup-demo-tour.js');
|
||||
return await runDemoTour(
|
||||
{ inputMode: args.inputMode },
|
||||
io,
|
||||
{ agents: deps.agents },
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Update imports — remove unused `defaultDemoProjectDir` import if no longer needed elsewhere in setup.ts**
|
||||
|
||||
Check if `defaultDemoProjectDir` is used elsewhere in `setup.ts`. If it's only used
|
||||
in `runKtxSetupDemoFromEntryMenu`, remove the import. If used elsewhere, keep it.
|
||||
|
||||
Also check if the `KtxDemoArgs` import is still needed. If `runKtxSetupDemoFromEntryMenu`
|
||||
was the only consumer of `deps.demo` with that type, it may now be unused. Keep the
|
||||
`demo` slot in `KtxSetupDeps` for backwards compatibility but it will no longer be
|
||||
called from the entry menu path.
|
||||
|
||||
- [ ] **Step 4: Run type-check and tests**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run type-check && pnpm --filter @ktx/cli run test`
|
||||
Expected: PASS — existing tests continue to work, demo tour is now wired in
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add packages/cli/src/setup.ts
|
||||
git commit -m "feat(cli): wire demo tour into setup entry menu"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: End-to-end verification
|
||||
|
||||
**Files:**
|
||||
- None (verification only)
|
||||
|
||||
- [ ] **Step 1: Run full test suite**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run test 2>&1 | tee /tmp/ktx-demo-tour-test.log`
|
||||
Expected: All tests pass. Check the output for any regressions.
|
||||
|
||||
- [ ] **Step 2: Run type-check across workspace**
|
||||
|
||||
Run: `pnpm run type-check`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 3: Run pre-commit checks if available**
|
||||
|
||||
Run: `pnpm run check` (if configured)
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 4: Manual smoke test (if TTY available)**
|
||||
|
||||
Run: `pnpm --filter @ktx/cli run build && node packages/cli/dist/cli.js setup`
|
||||
|
||||
1. Select "Try KTX with packaged demo data"
|
||||
2. Verify demo banner appears with full explanation text
|
||||
3. Verify "Database connection" card shows with "PostgreSQL (demo warehouse)"
|
||||
4. Press Enter → verify "Context sources" card shows with dbt, Metabase, Notion
|
||||
5. Press Escape → verify you go back to database card
|
||||
6. Press Enter twice → verify context build replay animation runs
|
||||
7. Verify completion summary appears after replay
|
||||
8. Press Enter → verify agents step prompt appears (interactive)
|
||||
9. Press Escape all the way back → verify you return to entry menu
|
||||
|
||||
- [ ] **Step 5: Final commit if any adjustments needed**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix(cli): demo tour adjustments from smoke test"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Open Seams for Demo Data
|
||||
|
||||
When the user provides the real pre-packaged demo results, update these locations:
|
||||
|
||||
1. **`renderDemoContextCompletionSummary()`** in `setup-demo-tour.ts` — replace placeholder text with actual counts (business areas, query definitions, knowledge pages) from the demo data
|
||||
2. **`buildDemoReplayTimeline()`** in `setup-demo-tour.ts` — adjust timing and progress details to match the real ingestion profile
|
||||
3. **`demo-assets.ts`** — update `REQUIRED_SEEDED_ASSET_PATHS` and `demoConfig()` if the demo dataset changes from SQLite/Orbit to Postgres/dbt/Metabase/Notion
|
||||
4. **Pre-packaged asset files** in `packages/cli/assets/demo/` — replace with the new demo dataset
|
||||
252
docs/superpowers/specs/2026-05-11-demo-guided-tour-design.md
Normal file
252
docs/superpowers/specs/2026-05-11-demo-guided-tour-design.md
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
# Demo Guided Tour — Design Spec
|
||||
|
||||
## Problem
|
||||
|
||||
The "Try KTX with packaged demo data" option in `ktx setup` is completely
|
||||
disconnected from the real setup wizard. It bypasses all wizard steps, plays
|
||||
an animated replay in a temp directory, and exits with no bridge to actually
|
||||
using KTX. Users don't learn the real setup flow and hit a dead end.
|
||||
|
||||
## Solution
|
||||
|
||||
Redesign the demo option as a **guided tour** that walks the user through the
|
||||
same setup wizard steps with pre-filled, read-only selections. The tour ends
|
||||
with a real interactive agents step so the user can immediately use the demo
|
||||
project with their coding agent.
|
||||
|
||||
## Design Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|----------|--------|-----------|
|
||||
| Implementation strategy | Demo mode flag on existing wizard steps | Maximum code reuse; wizard changes automatically apply to demo |
|
||||
| LLM/embeddings steps | Skipped | Not relevant to pre-packaged demo data |
|
||||
| Database selection | PostgreSQL (read-only card) | Pre-filled, matches demo dataset |
|
||||
| Context sources | dbt, Metabase, Notion (read-only card) | Pre-filled, matches demo dataset |
|
||||
| Context build | Replay through real progress visualization | Same spinners, progress bars, status icons as real build |
|
||||
| Agents step | Real interactive step | User actually connects their agent |
|
||||
| Project location | Temp directory (`/tmp/ktx-demo-{hex}`) | Frictionless, no directory prompt |
|
||||
| Navigation | Enter to advance, Escape to go back | Consistent with rest of wizard |
|
||||
|
||||
## Flow
|
||||
|
||||
```
|
||||
Entry menu: "Try KTX with packaged demo data"
|
||||
│
|
||||
▼
|
||||
Create demo project in /tmp/ktx-demo-{hex}
|
||||
Copy pre-packaged assets (demo DB, replay, context artifacts)
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ Demo banner (persistent, shown on every step) │
|
||||
│ │
|
||||
│ Demo mode — data has been pre-processed and KTX context is │
|
||||
│ already built. This walkthrough illustrates the setup steps. │
|
||||
│ Selections are pre-filled and read-only. │
|
||||
└────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
Read-only card: Database connection
|
||||
▸ PostgreSQL (demo warehouse)
|
||||
[Enter → next, Escape → back to entry menu]
|
||||
│
|
||||
▼
|
||||
Read-only card: Context sources
|
||||
▸ dbt
|
||||
▸ Metabase
|
||||
▸ Notion
|
||||
[Enter → next, Escape → back to database card]
|
||||
│
|
||||
▼
|
||||
Context build replay
|
||||
Same renderContextBuildView() / repainter as real wizard
|
||||
Sources: demo-warehouse, dbt, metabase, notion
|
||||
Replay at slightly faster-than-real pace
|
||||
Completion summary: business areas, query definitions, knowledge pages
|
||||
[Enter → next, Escape → back to sources card]
|
||||
│
|
||||
▼
|
||||
Transition message:
|
||||
"Demo project is ready — let's connect your agent"
|
||||
│
|
||||
▼
|
||||
Interactive agents step (real runKtxSetupAgentsStep())
|
||||
User selects agent target, scope, install mode
|
||||
[Normal interactive navigation; Escape goes back to replay summary]
|
||||
│
|
||||
▼
|
||||
Final summary:
|
||||
★ KTX demo is ready
|
||||
Agent connected, project path shown
|
||||
⚠ Temp directory warning
|
||||
Pointer to `ktx setup` for real data
|
||||
```
|
||||
|
||||
## Step Details
|
||||
|
||||
### Demo Banner
|
||||
|
||||
Shown at the top of every read-only step. Uses clack box-drawing style:
|
||||
|
||||
```
|
||||
┌ Demo mode — data has been pre-processed and KTX context is already built.
|
||||
│ This walkthrough illustrates the setup steps. Selections are pre-filled and read-only.
|
||||
```
|
||||
|
||||
### Read-Only Step Cards
|
||||
|
||||
Rendered by a shared `renderDemoCard()` helper:
|
||||
|
||||
```typescript
|
||||
async function renderDemoCard(
|
||||
title: string,
|
||||
selections: string[],
|
||||
io: KtxCliIo,
|
||||
): Promise<'forward' | 'back'>
|
||||
```
|
||||
|
||||
- Renders a clack-style box with title, bullet list of pre-filled selections,
|
||||
and navigation hint ("Press Enter to continue, Escape to go back")
|
||||
- Listens for raw keypresses: Enter → `'forward'`, Escape → `'back'`
|
||||
- Uses same box-drawing characters and colors as `@clack/prompts`
|
||||
|
||||
Card format:
|
||||
|
||||
```
|
||||
┌ {title}
|
||||
│
|
||||
│ ▸ {selection 1}
|
||||
│ ▸ {selection 2}
|
||||
│ ...
|
||||
│
|
||||
│ Press Enter to continue, Escape to go back
|
||||
└
|
||||
```
|
||||
|
||||
### Demo Step Sequence
|
||||
|
||||
The demo reuses the main wizard's step loop with these steps:
|
||||
|
||||
```typescript
|
||||
const demoSteps = ['databases', 'sources', 'context', 'agents'];
|
||||
```
|
||||
|
||||
Steps `databases` and `sources` dispatch to `renderDemoCard()` instead of
|
||||
their real interactive functions when demo mode is active. Step `context`
|
||||
dispatches to the replay visualization. Step `agents` runs the real
|
||||
`runKtxSetupAgentsStep()`.
|
||||
|
||||
Back navigation reuses `previousNavigableStepIndex()`. Escaping from the
|
||||
first step (databases) returns to the entry menu.
|
||||
|
||||
### Context Build Replay
|
||||
|
||||
Uses the same rendering pipeline as the real context build:
|
||||
|
||||
- `renderContextBuildView()` for the progress display
|
||||
- `createRepainter()` for terminal repainting
|
||||
- Same spinner frames, progress bars (`████░░░░`), status icons (`✓`, `⠹`, `○`)
|
||||
- Same source grouping (Primary sources / Context sources)
|
||||
|
||||
Sources shown:
|
||||
|
||||
```
|
||||
Primary sources:
|
||||
✓ demo-warehouse completed · Xs
|
||||
|
||||
Context sources:
|
||||
✓ dbt completed · Xs
|
||||
✓ metabase completed · Xs
|
||||
✓ notion completed · Xs
|
||||
```
|
||||
|
||||
Replay timing: events from the pre-packaged replay file are played back at
|
||||
a slightly faster pace than real-time (compressed to feel brisk but not
|
||||
instant).
|
||||
|
||||
Completion summary uses the existing format:
|
||||
|
||||
```
|
||||
★ KTX finished ingesting your data
|
||||
|
||||
✓ Analyzed X business areas
|
||||
✓ Reconciled — 0 conflicts
|
||||
|
||||
KTX created:
|
||||
📊 X query definitions
|
||||
📝 X knowledge pages
|
||||
|
||||
Press Enter to continue, Escape to go back
|
||||
```
|
||||
|
||||
The exact counts and artifact names come from the pre-packaged demo results
|
||||
(to be provided by the user as improved demo data).
|
||||
|
||||
### Agents Step Transition
|
||||
|
||||
A brief message bridges from the read-only tour to the interactive step:
|
||||
|
||||
```
|
||||
┌ Demo project is ready — let's connect your agent
|
||||
│
|
||||
│ Your KTX context has been built with demo data.
|
||||
│ Select an agent to start using it.
|
||||
└
|
||||
```
|
||||
|
||||
Then `runKtxSetupAgentsStep()` runs with the demo project directory,
|
||||
normal interactive prompts enabled.
|
||||
|
||||
### Final Summary
|
||||
|
||||
```
|
||||
★ KTX demo is ready
|
||||
|
||||
Your agent is connected to a demo KTX project.
|
||||
|
||||
⚠ This project is in a temporary directory and will be
|
||||
cleaned up by your system. To set up KTX with your own
|
||||
data, run: ktx setup
|
||||
|
||||
Project: /tmp/ktx-demo-a1b2c3
|
||||
```
|
||||
|
||||
If the user skips the agents step, replace the first line with manual
|
||||
agent connection instructions (`ktx setup --agents --project-dir /tmp/...`).
|
||||
|
||||
## Implementation Approach
|
||||
|
||||
Thread a `demoMode` flag through the main setup loop in `setup.ts`. When
|
||||
active:
|
||||
|
||||
1. Skip `models` and `embeddings` steps entirely
|
||||
2. Replace `databases` and `sources` step dispatch with `renderDemoCard()`
|
||||
3. Replace `context` step dispatch with replay visualization
|
||||
4. Run `agents` step normally
|
||||
5. Show demo-specific completion summary instead of ready menu
|
||||
|
||||
The `renderDemoCard()` helper is a new function in a new file
|
||||
(e.g. `setup-demo-cards.ts`) that handles read-only card rendering and
|
||||
keypress listening.
|
||||
|
||||
The context build replay reuses existing `renderContextBuildView()` and
|
||||
`createRepainter()` from `context-build-view.ts`, fed with events from
|
||||
the pre-packaged replay file at an accelerated playback rate.
|
||||
|
||||
## Files Changed
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `packages/cli/src/setup.ts` | Add `demoMode` flag to setup loop; skip models/embeddings; dispatch to demo cards for databases/sources; show demo banner; demo completion summary |
|
||||
| `packages/cli/src/setup-demo-cards.ts` | New file: `renderDemoCard()` helper, demo banner renderer, demo step definitions |
|
||||
| `packages/cli/src/setup-context.ts` | Support replay mode for demo: feed pre-packaged events at accelerated pace through existing progress view |
|
||||
| `packages/cli/src/demo.ts` | Remove or simplify `runKtxSetupDemoFromEntryMenu()` — now dispatches to the main setup loop with `demoMode: true` |
|
||||
| `packages/cli/src/demo-assets.ts` | Update asset list if new demo data is provided; ensure demo project setup writes valid `ktx.yaml` for agent use |
|
||||
|
||||
## Open Items
|
||||
|
||||
- **Demo data**: User will provide improved pre-packaged results (Postgres,
|
||||
dbt, Metabase, Notion). Current demo assets may need updating.
|
||||
- **Replay speed**: Exact acceleration factor TBD — should feel brisk but
|
||||
give users time to read source names and status transitions. Start with
|
||||
~2x real-time and adjust.
|
||||
0
packages/cli/assets/demo/orbit/knowledge/global/.gitkeep
Normal file
0
packages/cli/assets/demo/orbit/knowledge/global/.gitkeep
Normal file
|
|
@ -1,18 +0,0 @@
|
|||
---
|
||||
summary: Account activation policy changed on January 15, 2026.
|
||||
tags:
|
||||
- growth
|
||||
- activation
|
||||
- policy
|
||||
refs: []
|
||||
sl_refs:
|
||||
- orbit_demo.accounts
|
||||
- orbit_demo.purchase_requests
|
||||
usage_mode: auto
|
||||
---
|
||||
|
||||
Before January 15, 2026, activation meant first requester login.
|
||||
|
||||
On and after January 15, 2026, activation requires an approved purchase request and at least three activated requesters.
|
||||
|
||||
Always separate pre-policy and post-policy cohorts when comparing activation rates.
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
---
|
||||
summary: ARR uses contract-first precedence before subscription-derived revenue.
|
||||
tags:
|
||||
- finance
|
||||
- arr
|
||||
- revenue
|
||||
refs: []
|
||||
sl_refs:
|
||||
- orbit_demo.contracts
|
||||
- orbit_demo.arr_movements
|
||||
usage_mode: auto
|
||||
---
|
||||
|
||||
ARR is calculated from active recurring contract ARR before falling back to subscription-derived revenue.
|
||||
|
||||
Do not double-count subscription MRR when an active contract row covers the same account and period.
|
||||
|
||||
Exclude cancelled contracts ending before the metric date, future-starting contracts, internal accounts, and test accounts.
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
summary: "Required elements for valid customer updates: what happened, what is being done, who owns next step, and when customer will hear back. Vague status phrases are not acceptable."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- policy
|
||||
- customer-success
|
||||
refs:
|
||||
- orbit-how-we-work
|
||||
- sales-ops-cs-handoff-process
|
||||
---
|
||||
|
||||
## Customer Update Communication Standard
|
||||
|
||||
**Source:** Notion — People & Operating Norms, last edited 2026-05-07
|
||||
|
||||
---
|
||||
|
||||
## Policy
|
||||
|
||||
Every customer update must contain four elements. An update that omits any of these is incomplete and must not be sent.
|
||||
|
||||
| # | Required Element | Example |
|
||||
|---|---|---|
|
||||
| 1 | **What happened** | "The approval routing failed for the renewal PO because the department budget split was not configured." |
|
||||
| 2 | **What is being done** | "We are reconfiguring the budget split and re-routing the approval to the correct approver." |
|
||||
| 3 | **Who owns the next step** | "[Name] on our CS team owns this and is working it now." |
|
||||
| 4 | **When the customer will hear back** | "You will have an update by 3pm today." |
|
||||
|
||||
## Named Anti-Pattern
|
||||
|
||||
- **Do not send:** "We are looking into it."
|
||||
- This phrase is only acceptable when the actual blocker is genuinely unknown. If the blocker is known, name it.
|
||||
- Vague status phrases without a named owner and a time commitment are not acceptable customer updates.
|
||||
|
||||
## When This Applies
|
||||
|
||||
- Any written update to a customer during an active issue, escalation, or implementation delay.
|
||||
- Applies to email, Slack, and any other written channel.
|
||||
- Verbal updates in calls should follow the same structure; a written summary must follow the call.
|
||||
|
||||
---
|
||||
|
||||
See also: [[orbit-how-we-work]], [[sales-ops-cs-handoff-process]]
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
summary: Customer health combines support severity and procurement activity.
|
||||
tags:
|
||||
- customer-success
|
||||
- health
|
||||
- churn-risk
|
||||
refs:
|
||||
- nrr-retention
|
||||
sl_refs:
|
||||
- orbit_demo.support_tickets
|
||||
- orbit_demo.purchase_requests
|
||||
- orbit_demo.accounts
|
||||
usage_mode: auto
|
||||
---
|
||||
|
||||
High-risk accounts have multiple recent high-severity tickets or no recent procurement activity on growth and enterprise plans.
|
||||
|
||||
Medium risk captures partial support pressure or a material month-over-month decline in procurement activity.
|
||||
|
||||
Internal and test accounts are excluded from customer health scoring.
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
---
|
||||
summary: Discount expirations are tracked separately from organic contraction.
|
||||
tags:
|
||||
- finance
|
||||
- retention
|
||||
refs:
|
||||
- arr-contract-first
|
||||
- nrr-retention
|
||||
sl_refs:
|
||||
- orbit_demo.contracts
|
||||
- orbit_demo.arr_movements
|
||||
usage_mode: auto
|
||||
---
|
||||
|
||||
Discount expiration events identify pricing changes when negotiated discounts end.
|
||||
|
||||
Track these separately from organic contraction so board reporting can split pricing-driven and usage-driven changes.
|
||||
|
||||
Use movement_reason on arr_movements when separating discount expiration from churn or seat-reduction events.
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
---
|
||||
summary: Canonical metrics exclude internal and test accounts and users.
|
||||
tags:
|
||||
- data-quality
|
||||
- governance
|
||||
refs: []
|
||||
sl_refs:
|
||||
- orbit_demo.accounts
|
||||
usage_mode: auto
|
||||
---
|
||||
|
||||
All canonical customer metrics exclude rows marked as internal or test fixtures.
|
||||
|
||||
This exclusion applies at both account and user grain when joining procurement, support, and revenue activity.
|
||||
|
||||
If a metric unexpectedly increases, check whether new internal or test accounts were created without proper flags.
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
summary: "New hire week-one knowledge requirements: four things every new hire must understand by end of week one, with manager as responsible owner."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- orbit
|
||||
- policy
|
||||
refs:
|
||||
- orbit-company-overview
|
||||
- orbit-how-we-work
|
||||
---
|
||||
|
||||
## New Hire Week-One Onboarding Policy
|
||||
|
||||
**Source:** Notion — People & Operating Norms, last edited 2026-05-07
|
||||
**Owner:** Manager (not People Ops)
|
||||
|
||||
---
|
||||
|
||||
## Policy
|
||||
|
||||
Every new hire must understand **four things by end of week one**. The manager — not People Ops — is responsible for supplying this context.
|
||||
|
||||
## Required Week-One Knowledge
|
||||
|
||||
| # | What the new hire must understand |
|
||||
|---|---|
|
||||
| 1 | **What Orbit sells** — the core procurement workflow product and value proposition |
|
||||
| 2 | **Why procurement workflow gets messy inside a customer** — the pain points that make Orbit necessary |
|
||||
| 3 | **Which team handles which part of the customer lifecycle** — team lanes and ownership boundaries |
|
||||
| 4 | **What their first useful project is** — a concrete, scoped piece of work they can contribute to immediately |
|
||||
|
||||
## Ownership
|
||||
|
||||
- The **manager** is responsible for delivering this context, not People Ops or a generic onboarding doc.
|
||||
- If the manager cannot supply item 4 (first useful project) by day one, they should have it ready by end of day two at the latest.
|
||||
- Items 1–3 can be covered via existing documentation; the manager should point to the right pages rather than re-explaining from scratch.
|
||||
|
||||
## Suggested Reading for Items 1–3
|
||||
|
||||
- Item 1 & 2: [[orbit-company-overview]]
|
||||
- Item 3: [[orbit-company-overview]] (Team Lanes section)
|
||||
- Operating norms and how decisions are made: [[orbit-how-we-work]]
|
||||
|
||||
---
|
||||
|
||||
See also: [[orbit-company-overview]], [[orbit-how-we-work]]
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
---
|
||||
summary: NRR is calculated at parent-account grain by calendar quarter.
|
||||
tags:
|
||||
- analytics
|
||||
- retention
|
||||
- nrr
|
||||
refs:
|
||||
- arr-contract-first
|
||||
sl_refs:
|
||||
- orbit_demo.arr_movements
|
||||
- orbit_demo.accounts
|
||||
usage_mode: auto
|
||||
---
|
||||
|
||||
Net Revenue Retention uses parent-account rollups by calendar quarter.
|
||||
|
||||
The formula is starting ARR plus expansion minus contraction and churn, divided by starting ARR.
|
||||
|
||||
Exclude parent accounts with zero starting ARR, new business, reactivations, and internal/test accounts from the denominator.
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
summary: "Customer activation: email verified + first project + team invite within 14 days of signup. D7/D14 activation rates and Time-to-Activate formulas. Source tables: customer, project, invite."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- activation
|
||||
- kpi
|
||||
- growth
|
||||
- funnel
|
||||
- metrics
|
||||
refs:
|
||||
- orbit-customers-source
|
||||
- orbit-activation-policy-change-jan-2026
|
||||
- orbit-mart-account-activity
|
||||
tables:
|
||||
- orbit_analytics.customer
|
||||
- orbit_analytics.project
|
||||
- orbit_analytics.invite
|
||||
---
|
||||
|
||||
# Activation KPI Glossary
|
||||
|
||||
**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.
|
||||
|
||||
## Activation Definition
|
||||
|
||||
A customer is **activated** when **all three** of the following happen **within 14 days of signup**:
|
||||
|
||||
1. Email is verified
|
||||
2. First project is created
|
||||
3. At least one teammate is invited
|
||||
|
||||
## Funnel Stages
|
||||
|
||||
| Stage | Signal | Data source |
|
||||
|---|---|---|
|
||||
| 1. Signup | Customer row created | `orbit_analytics.customer` |
|
||||
| 2. Email Verified | `customer.email_verified_at` is not null | `orbit_analytics.customer` |
|
||||
| 3. First Project | At least one row in `orbit_analytics.project` for the customer | `orbit_analytics.project` |
|
||||
| 4. Team Invite | At least one row in `orbit_analytics.invite` for the customer | `orbit_analytics.invite` |
|
||||
| 5. Activated | All of (2), (3), and (4) within 14 days of (1) | — |
|
||||
|
||||
## Conversion-Rate KPIs
|
||||
|
||||
| KPI | Formula |
|
||||
|---|---|
|
||||
| **D7 Activation Rate** | `activated_customers_within_7_days / signups_in_cohort` |
|
||||
| **D14 Activation Rate** | `activated_customers_within_14_days / signups_in_cohort` |
|
||||
| **Time-to-Activate** | `median(activated_at - created_at)` in hours |
|
||||
|
||||
Growth conversations typically use D7 and D14 Activation Rate. Product and CS may ask about individual funnel steps — confirm whether they mean the full activation definition or only one stage.
|
||||
|
||||
## Source Notes
|
||||
|
||||
- Use `orbit_analytics.customer` for `created_at` and `email_verified_at`.
|
||||
- For project or invite timing, check `orbit_analytics.project` and `orbit_analytics.invite` before changing the activation definition.
|
||||
- `created_at` is UTC; confirm timezone expectations before cohort filtering.
|
||||
|
||||
## 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.
|
||||
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
summary: "January 2026 activation policy change: policy_version splits events into pre_2026_01_15 and post_2026_01_15 cohorts. mart_account_activity compares activation counts across the boundary."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- activation
|
||||
- growth
|
||||
- policy
|
||||
- governed-metric
|
||||
- procurement
|
||||
sl_refs:
|
||||
- mart_account_activity
|
||||
---
|
||||
|
||||
# Activation Policy Change — January 2026
|
||||
|
||||
**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
|
||||
|
||||
The activation workflow changed on **2026-01-15**. All activation events are tagged with `policy_version`:
|
||||
|
||||
- `pre_2026_01_15` — events before the workflow update
|
||||
- `post_2026_01_15` — events after the workflow update
|
||||
|
||||
## Activation Event Types
|
||||
|
||||
`first_requester_login`, `requester_activated`, `first_approved_purchase_request`, `account_activated`
|
||||
|
||||
## Account Activation Sequence
|
||||
|
||||
1. First requester login → `first_requester_login`
|
||||
2. Requester activated → `requester_activated`
|
||||
3. First approved purchase request → `first_approved_purchase_request`
|
||||
4. Account activated → `account_activated`
|
||||
|
||||
## Exclusions
|
||||
|
||||
Internal and test accounts (lifecycle_status = `internal` or `test` on `stg_accounts`) are excluded from activation counts. Sessions (`stg_sessions`) are used for pre-policy activation and activity exclusions.
|
||||
|
||||
## Dashboard
|
||||
|
||||
Exposed via the **Growth Activation Dashboard** (`https://orbit-demo.example.com/dashboards/activation`), which depends on `mart_account_activity`.
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
summary: "ARR is calculated contract-first: active contract ARR takes precedence over subscription ARR for any covered period."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- arr
|
||||
- governed-metric
|
||||
- finance
|
||||
- contracts
|
||||
- subscriptions
|
||||
sl_refs:
|
||||
- mart_arr_daily
|
||||
- mart_account_segments
|
||||
---
|
||||
|
||||
# ARR — Contract-First Definition
|
||||
|
||||
**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
|
||||
|
||||
ARR is calculated **contract-first**: when an active contract exists for an account and period, `int_active_contract_arr` is used. Subscription ARR (`stg_subscriptions`) is only used when no active contract covers the period.
|
||||
|
||||
## Known Assertion
|
||||
|
||||
The dbt test on `mart_arr_daily.arr_cents` asserts the value equals **1,874,200,000 cents ($18,742,000)** as of `metric_date = 2026-03-31`.
|
||||
|
||||
## Intermediate model
|
||||
|
||||
`int_active_contract_arr` — active contract ARR as of 2026-03-31 (grain: `contract_id`).
|
||||
|
||||
## Related
|
||||
|
||||
- `stg_contracts` — contract records (status: draft, active, cancelled, expired)
|
||||
- `stg_subscriptions` — fallback ARR source (status: active, cancelled, past_due, trialing)
|
||||
- `mart_arr_daily` — board-prep daily ARR mart
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
summary: "Orbit: procurement workflow software (requests → approvals → supplier onboarding → POs). Plans: Starter, Growth, Enterprise. Team lanes and open operating questions as of May 2026."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- company-context
|
||||
- product
|
||||
- plans
|
||||
- team-lanes
|
||||
- procurement
|
||||
refs:
|
||||
- orbit-plan-segment-normalization
|
||||
- orbit-procurement-qualifying-actions
|
||||
---
|
||||
|
||||
# Orbit Company Overview
|
||||
|
||||
**Source:** Notion — Orbit Demo Home / Company Overview + Orbit Demo Home (root), last edited 2026-05-07
|
||||
|
||||
## What Orbit Sells
|
||||
|
||||
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.
|
||||
**Daily users:** department admins, office managers, IT leads, legal ops partners — anyone who has to get a vendor through the building.
|
||||
|
||||
## Product Workflow
|
||||
|
||||
1. Requester submits a purchase request
|
||||
2. Approval routing collects the right decision
|
||||
3. Supplier invite and onboarding happen before work starts
|
||||
4. Purchase order is created from the approved request
|
||||
5. Renewal handoff keeps the relationship from drifting
|
||||
|
||||
## Plans
|
||||
|
||||
| Plan | Target customer |
|
||||
|---|---|
|
||||
| **Starter** | Teams moving out of spreadsheet tracking |
|
||||
| **Growth** | Default mid-market plan |
|
||||
| **Enterprise** | Multiple approval policies, parent/child account structures, heavier renewal coordination |
|
||||
|
||||
**Legacy alias:** `pro_plus` in older notes means Growth. Treat as Growth unless Sales Ops says otherwise. See `orbit-plan-segment-normalization` for the data-layer normalization rule.
|
||||
|
||||
## Team Lanes
|
||||
|
||||
| Team | Responsibilities |
|
||||
|---|---|
|
||||
| Product | Requester onboarding, supplier onboarding, approval routing, PO workflow quality |
|
||||
| Growth | Activation, self-serve conversion |
|
||||
| Sales Ops | Account segmentation, plan mapping, contracts, handoff hygiene |
|
||||
| Customer Success | Implementation, support escalations, account health, renewal risk |
|
||||
| Finance | Billing, close, board prep |
|
||||
| Data | Cross-functional support for all departments |
|
||||
| Executive | Company priorities, weekly operating review |
|
||||
|
||||
## Open / Unsettled Questions (as of May 2026)
|
||||
|
||||
- Whether supplier onboarding stays fully inside Product or splits more work with CS for larger accounts.
|
||||
- Whether Growth is still the right default-plan language in sales materials.
|
||||
- How renewal handoff works when Sales Ops updates account segment late in-quarter.
|
||||
- Implementation handoff template decision still pending.
|
||||
- Renewal risk review agenda should not live only in meeting notes.
|
||||
|
||||
## Common Customer Pain Points (Pre-Sale)
|
||||
|
||||
- "We have too many request paths."
|
||||
- "Approvals happen, but no one can explain the state of the request."
|
||||
- "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."
|
||||
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
summary: "Customer health risk definition: risk_level (low/medium/high) derived from open critical support tickets and recent procurement activity. Mart: mart_customer_health, as of 2026-03-31."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- customer-health
|
||||
- risk
|
||||
- customer-success
|
||||
- governed-metric
|
||||
- support
|
||||
sl_refs:
|
||||
- mart_customer_health
|
||||
---
|
||||
|
||||
# Customer Health 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
|
||||
|
||||
`low`, `medium`, `high` — derived from two signal types:
|
||||
|
||||
1. **Support ticket signals** (`stg_support_tickets`): open or pending tickets with severity `high` or `critical` increase risk.
|
||||
2. **Procurement activity signals** (`stg_purchase_requests`, `stg_purchase_orders`): recent qualifying procurement actions reduce risk.
|
||||
|
||||
## Intermediate Model
|
||||
|
||||
`int_customer_health_signals` — combines open critical ticket count and recent procurement action count per account.
|
||||
|
||||
## Mart
|
||||
|
||||
`mart_customer_health` — account-grain risk mart as of **2026-03-31**.
|
||||
|
||||
- `account_id`: dbt not_null, unique
|
||||
- `risk_level`: dbt accepted_values [low, medium, high]
|
||||
|
||||
## Support Ticket Severities
|
||||
|
||||
`low`, `medium`, `high`, `critical`
|
||||
|
||||
## Account Ownership Context
|
||||
|
||||
`stg_account_owners` provides effective-dated ownership (owner_team: sales_ops, customer_success, finance) for escalation routing.
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
summary: "Recurring customer stakeholder needs by role: Finance, Department leaders, Procurement, Legal, and Customer Success each have distinct priorities that should inform product and positioning decisions."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- product
|
||||
- customer-success
|
||||
- orbit
|
||||
refs:
|
||||
- orbit-company-overview
|
||||
- orbit-product-review-checklist
|
||||
---
|
||||
|
||||
## Customer Stakeholder Needs by Role
|
||||
|
||||
**Source:** Notion — Product & Customers, last edited 2026-05-07
|
||||
|
||||
---
|
||||
|
||||
## Policy
|
||||
|
||||
These are recurring, role-specific customer needs observed across accounts. Use them to inform product prioritization, positioning, and CS engagement strategies.
|
||||
|
||||
## Stakeholder Map
|
||||
|
||||
| Role | Primary Need | Implication |
|
||||
|---|---|---|
|
||||
| **Finance** | Committed spend visibility earlier in the procurement cycle | Surface budget commitments at request approval, not at PO creation |
|
||||
| **Department leaders** | Request speed — faster time from request to approval | Reduce approval routing friction; minimize back-and-forth |
|
||||
| **Procurement** | Supplier file complete before the first invoice | Supplier onboarding must be finished before PO is issued, not after |
|
||||
| **Legal** | Fewer emergency reviews | Route contracts with legal implications earlier; avoid last-minute escalations |
|
||||
| **Customer Success (internal)** | Renewal risk visible before the account is already annoyed | CS needs leading indicators of dissatisfaction, not lagging ones |
|
||||
|
||||
## Usage Notes
|
||||
|
||||
- These needs are recurring patterns, not one-off requests. They should be treated as standing assumptions until explicitly updated.
|
||||
- When prioritizing roadmap items, map each item to the stakeholder(s) it serves and verify the need is still active.
|
||||
- When positioning Orbit to a new prospect, use this map to tailor the value proposition to the roles present in the buying committee.
|
||||
|
||||
---
|
||||
|
||||
See also: [[orbit-company-overview]], [[orbit-product-review-checklist]], [[orbit-known-product-gaps]]
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
summary: "orbit_analytics.customer: one row per customer. Columns, joins to account/subscription_event, measures (customer_count, paying_customer_count, mrr), and watch-outs."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- data-source
|
||||
- customers
|
||||
- orbit-analytics
|
||||
- measures
|
||||
refs:
|
||||
- orbit-plan-segment-normalization
|
||||
- orbit-activation-kpi-glossary
|
||||
tables:
|
||||
- orbit_analytics.customer
|
||||
- orbit_analytics.account
|
||||
- orbit_analytics.subscription_event
|
||||
---
|
||||
|
||||
# Orbit Customers Source
|
||||
|
||||
**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.
|
||||
|
||||
## Columns
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| `id` | number | Primary key, surrogate key |
|
||||
| `email` | string | Login email, unique — **do not use as join key** |
|
||||
| `name` | string | Display name |
|
||||
| `country` | string | ISO 3166-1 alpha-2 code |
|
||||
| `plan_tier` | string | One of `free`, `pro`, `enterprise` |
|
||||
| `created_at` | time | UTC signup timestamp |
|
||||
| `last_seen_at` | time | UTC most recent app activity |
|
||||
| `email_verified_at` | time | UTC email verification timestamp (used in activation funnel) |
|
||||
|
||||
## Joins
|
||||
|
||||
- **one-to-many** → `orbit_analytics.account` on `customer.id = account.customer_id`
|
||||
- **one-to-many** → `orbit_analytics.subscription_event` on `customer.id = subscription_event.customer_id`
|
||||
|
||||
Always join through `customer.id`. Do not join on `email`.
|
||||
|
||||
## Standard Measures
|
||||
|
||||
| Measure | Formula |
|
||||
|---|---|
|
||||
| `customer_count` | `count(distinct id)` |
|
||||
| `paying_customer_count` | `count(distinct id) where plan_tier in ('pro', 'enterprise')` |
|
||||
| `mrr` | `sum(subscription_event.amount) where event_type = 'renewed'` |
|
||||
|
||||
## Watch-outs
|
||||
|
||||
- **Join key:** Always use `customer.id`, never `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`.
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
summary: "dbt exposures declared in models/exposures.yml: three dashboards (Retention Executive, Executive Revenue, Growth Activation) with their upstream mart dependencies and owners."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- dbt
|
||||
- exposures
|
||||
- dashboards
|
||||
- orbit
|
||||
sl_refs:
|
||||
- mart_nrr_quarterly
|
||||
- mart_retention_movement_breakout
|
||||
- mart_arr_daily
|
||||
- mart_revenue_daily
|
||||
- mart_account_activity
|
||||
---
|
||||
|
||||
# Orbit dbt Exposures
|
||||
|
||||
Declared in `models/exposures.yml`. All exposures are type `dashboard` with maturity `high` or `medium`.
|
||||
|
||||
## Retention Executive Dashboard
|
||||
|
||||
- **URL:** https://orbit-demo.example.com/dashboards/retention
|
||||
- **Maturity:** high
|
||||
- **Owner:** Analytics (analytics@orbit-demo.example.com)
|
||||
- **Depends on:** `mart_nrr_quarterly`, `mart_retention_movement_breakout`
|
||||
- **Description:** Executive retention view covering NRR and movement breakout.
|
||||
|
||||
## Executive Revenue Dashboard
|
||||
|
||||
- **URL:** https://orbit-demo.example.com/dashboards/revenue
|
||||
- **Maturity:** high
|
||||
- **Owner:** Finance (finance@orbit-demo.example.com)
|
||||
- **Depends on:** `mart_arr_daily`, `mart_revenue_daily`
|
||||
- **Description:** Board reporting view for ARR and gross-to-net revenue.
|
||||
|
||||
## Growth Activation Dashboard
|
||||
|
||||
- **URL:** https://orbit-demo.example.com/dashboards/activation
|
||||
- **Maturity:** medium
|
||||
- **Owner:** Growth (growth@orbit-demo.example.com)
|
||||
- **Depends on:** `mart_account_activity`
|
||||
- **Description:** Activation policy comparison around the January 2026 workflow update.
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
summary: "Overview of the kaelio_demo dbt project: connection, schema layout, model layers, and governed metrics."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- dbt
|
||||
- orbit
|
||||
- data-model
|
||||
- governed-metrics
|
||||
sl_refs:
|
||||
- stg_accounts
|
||||
- stg_contracts
|
||||
- stg_arr_movements
|
||||
- mart_arr_daily
|
||||
- mart_nrr_quarterly
|
||||
- mart_revenue_daily
|
||||
- mart_account_activity
|
||||
- mart_procurement_activity
|
||||
- mart_customer_health
|
||||
- mart_account_segments
|
||||
---
|
||||
|
||||
# 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`
|
||||
**Analytics schema:** `orbit_analytics` (all models materialised as views by default)
|
||||
|
||||
## Model Layers
|
||||
|
||||
| Layer | Prefix | Purpose |
|
||||
|---|---|---|
|
||||
| Staging | `stg_` | 1-to-1 with `orbit_raw` tables; adds type-casting, column tests, enum constraints |
|
||||
| Intermediate | `int_` | Business-logic joins and rollups; not exposed to BI directly |
|
||||
| Mart | `mart_` | Board/dashboard-ready aggregates; each has a `governed_metric_key` and `owner_team` |
|
||||
|
||||
## Governed Metrics (mart layer)
|
||||
|
||||
| Mart | `governed_metric_key` | Owner | Notion |
|
||||
|---|---|---|---|
|
||||
| `mart_arr_daily` | `arr` | finance | `notion_page_arr_contract_reporting` |
|
||||
| `mart_nrr_quarterly` | `net_revenue_retention` | analytics | `notion_page_retention_policy_current` |
|
||||
| `mart_retention_movement_breakout` | `net_revenue_retention` | analytics | `notion_page_retention_policy_current` |
|
||||
| `mart_revenue_daily` | `net_revenue` | finance | `notion_page_revenue_reporting_policy` |
|
||||
| `mart_account_activity` | `activated_accounts` | growth | `notion_page_activation_policy_decision` |
|
||||
| `mart_procurement_activity` | `weekly_active_requesters` | product | `notion_page_procurement_instrumentation` |
|
||||
| `mart_customer_health` | `active_customers` | customer_success | `notion_page_customer_health_playbook` |
|
||||
| `mart_account_segments` | `segment` | sales_ops | `notion_page_sales_ops_segmentation` |
|
||||
|
||||
## 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.
|
||||
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
summary: "Orbit operating model: remote-first, written-first, weekly rhythm, decision process, escalation policy, and standing operating norms."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- policy
|
||||
- orbit
|
||||
refs:
|
||||
- orbit-company-overview
|
||||
- customer-communication-policy
|
||||
---
|
||||
|
||||
## How We Work
|
||||
|
||||
**Source:** Notion — Orbit Demo Home / How We Work, last edited 2026-05-07
|
||||
|
||||
---
|
||||
|
||||
## Operating Model
|
||||
|
||||
- Orbit is a **mostly remote, mostly written** company.
|
||||
- Meetings must serve a specific purpose: making a decision, unblocking a handoff, or building shared context that writing alone would be slower to achieve.
|
||||
- If a meeting does not meet one of those three purposes, default to async written communication.
|
||||
|
||||
---
|
||||
|
||||
## Weekly Rhythm
|
||||
|
||||
| Day(s) | Focus |
|
||||
|---|---|
|
||||
| **Monday** | Commitments and dependency checks |
|
||||
| **Tuesday – Thursday** | Customer calls, product work, implementation, and building |
|
||||
| **Friday** | Closing loops — review what shipped, what slipped, and write down any decisions |
|
||||
|
||||
Use this rhythm when scheduling work, meetings, or reviews. Do not schedule decision-making meetings on Fridays; use Friday to record decisions already made.
|
||||
|
||||
---
|
||||
|
||||
## Decision-Making Process
|
||||
|
||||
1. **The person closest to the work writes the recommendation.**
|
||||
2. **Stakeholders who will live with the decision get to push back.**
|
||||
3. **The accountable lead makes the call** when a real tradeoff exists.
|
||||
4. **The result is written where the work is happening.** Decisions that exist only in Slack or a meeting are not considered durable.
|
||||
|
||||
> A decision that isn't written down didn't happen.
|
||||
|
||||
---
|
||||
|
||||
## Standing Operating Norms
|
||||
|
||||
These are explicitly codified rules Orbit has identified as recurring failure modes:
|
||||
|
||||
- **Name the accountable person before work begins.** If no one is named, no one is accountable.
|
||||
- **Never let a quick sync be the only source of truth.** Write it down after.
|
||||
- **Bring a customer example when proposing product changes.** Abstract proposals without customer grounding are harder to evaluate.
|
||||
- **Involve affected teams before a plan is finalized.** Surprises in execution are more expensive than slower planning.
|
||||
- **Prefer a rough written decision today over a perfect recap that never gets written.** Done and documented beats polished and lost.
|
||||
|
||||
---
|
||||
|
||||
## Escalation Policy
|
||||
|
||||
- **Escalations are coordination tools, not indicators of individual failure.** Escalating is the correct behavior when a problem exceeds the current team's ability to resolve it alone.
|
||||
- When escalating, the person escalating must:
|
||||
1. Bring in the right people (those with authority or context to unblock).
|
||||
2. Summarize current state clearly — what has been tried, what is blocked, and why.
|
||||
3. Name the customer impact explicitly.
|
||||
4. Keep updates moving until the risk is resolved or a workaround is established.
|
||||
- Escalations that stall because no one owns the next update are a process failure, not a customer failure.
|
||||
- An escalation is closed when the risk is resolved or a documented workaround is in place — not when the immediate noise stops.
|
||||
|
||||
---
|
||||
|
||||
See also: [[orbit-company-overview]], [[orbit-team-lanes-detail]], [[customer-communication-policy]]
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
summary: "Known Orbit product friction: approval routing for non-standard cases (weird supplier setups, split budgets, renewal changes) causes teams to fall back to side channels outside Orbit."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- product
|
||||
- orbit
|
||||
- customer-success
|
||||
refs:
|
||||
- orbit-customer-stakeholder-needs
|
||||
- orbit-product-review-checklist
|
||||
- orbit-company-overview
|
||||
---
|
||||
|
||||
## Known Product Gaps and Friction Points
|
||||
|
||||
**Source:** Notion — Product & Customers (Notes from Recent Customer Calls), last edited 2026-05-07
|
||||
|
||||
---
|
||||
|
||||
## Primary Friction: Approval Routing for Exceptions
|
||||
|
||||
The primary source of customer friction is **approval routing around non-standard cases**. When a procurement request does not fit the standard routing rules, teams fall back to side channels (email, Slack, spreadsheets) outside Orbit.
|
||||
|
||||
### Specific Triggers
|
||||
|
||||
| Trigger | Why It Causes Fallback |
|
||||
|---|---|
|
||||
| **Weird supplier setups** | Non-standard supplier configurations don't fit the default approval chain |
|
||||
| **Split department budgets** | Requests that span multiple budget owners require manual coordination not supported in the routing UI |
|
||||
| **Renewal changes** | Mid-term contract changes (scope, price, term) don't map cleanly to the new-request flow |
|
||||
|
||||
## Impact
|
||||
|
||||
- Teams that fall back to side channels for exceptions create a split record: part of the procurement history is in Orbit, part is not.
|
||||
- This undermines the supplier file completeness that Procurement requires (see [[orbit-customer-stakeholder-needs]]).
|
||||
- It also creates renewal risk because CS cannot see the full picture of what was agreed.
|
||||
|
||||
## Status
|
||||
|
||||
- This is a known, unresolved gap as of May 2026.
|
||||
- Treat as a standing assumption in roadmap and analysis decisions until a fix is shipped and validated.
|
||||
- Do not design analyses or reports that assume all procurement activity flows through Orbit for accounts with known exception patterns.
|
||||
|
||||
---
|
||||
|
||||
See also: [[orbit-customer-stakeholder-needs]], [[orbit-product-review-checklist]], [[orbit-company-overview]]
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
summary: "mart_account_activity: pre/post policy 30-day activation rates per policy_change_date. policy_change_date = 2026-01-15 is the Jan 2026 boundary. Rates are 0–1 ratios."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- activation
|
||||
- policy
|
||||
- mart
|
||||
- orbit-analytics
|
||||
sl_refs:
|
||||
- mart_account_activity
|
||||
tables:
|
||||
- orbit_analytics.mart_account_activity
|
||||
---
|
||||
|
||||
# mart_account_activity
|
||||
|
||||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/63.json -->
|
||||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/101.json -->
|
||||
<!-- 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`
|
||||
**Grain:** one row per `policy_change_date`
|
||||
|
||||
## Columns
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| `policy_change_date` | date | The policy boundary date (primary value: `2026-01-15`) |
|
||||
| `pre_policy_30_day_activation_rate` | decimal | 30-day activation rate before the policy change (0–1 ratio) |
|
||||
| `post_policy_30_day_activation_rate` | decimal | 30-day activation rate after the policy change (0–1 ratio) |
|
||||
|
||||
## Key measures (SL: `mart_account_activity`)
|
||||
|
||||
- `avg_pre_policy_activation_rate` — `avg(pre_policy_30_day_activation_rate)`
|
||||
- `avg_post_policy_activation_rate` — `avg(post_policy_30_day_activation_rate)`
|
||||
|
||||
## Common query patterns
|
||||
|
||||
- **Policy comparison:** `WHERE policy_change_date = date '2026-01-15'`
|
||||
- **As percent:** `round(pre_policy_30_day_activation_rate * 100, 1)`
|
||||
- **Side-by-side:** UNION of pre and post rows with a `policy_window` label column
|
||||
|
||||
## Business rules
|
||||
|
||||
- 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.
|
||||
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
summary: "mart_account_segments: account segmentation with contract ARR, plan codes, size_band, segment (self_serve/commercial/enterprise), and contract_status. One row per account_id."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- arr
|
||||
- segmentation
|
||||
- accounts
|
||||
- mart
|
||||
- orbit-analytics
|
||||
sl_refs:
|
||||
- mart_account_segments
|
||||
tables:
|
||||
- orbit_analytics.mart_account_segments
|
||||
---
|
||||
|
||||
# mart_account_segments
|
||||
|
||||
<!-- 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`
|
||||
**Grain:** one row per `account_id`
|
||||
|
||||
## Columns
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| `account_id` | text | Primary key |
|
||||
| `parent_account_id` | text | Parent account for hierarchy rollups |
|
||||
| `current_plan_code` | text | Raw plan code from billing system |
|
||||
| `normalized_plan_code` | text | Canonical plan code (`pro_plus` → `growth`) |
|
||||
| `size_band` | text | Company size band |
|
||||
| `segment` | text | Reporting segment: `self_serve`, `commercial`, `enterprise` |
|
||||
| `contract_arr_cents` | bigint | Contract ARR in cents |
|
||||
| `contract_status` | text | `active`, `churned`, etc. |
|
||||
|
||||
## Key measures (SL: `mart_account_segments`)
|
||||
|
||||
- `account_count` — `count(*)`
|
||||
- `total_contract_arr_cents` — `sum(contract_arr_cents)`
|
||||
- `active_contract_arr_cents` — `sum(contract_arr_cents)` where `contract_status = 'active'`
|
||||
- `active_contract_arr_millions` — active ARR in $M
|
||||
|
||||
## Common query patterns
|
||||
|
||||
- **ARR by segment:** `GROUP BY segment WHERE contract_status = 'active'`
|
||||
- **Top accounts:** `ORDER BY contract_arr_cents DESC` with `is_internal = false AND is_test = false` (join to `orbit_raw.accounts`)
|
||||
- **Unmapped segment:** `COALESCE(segment, 'unmapped')`
|
||||
|
||||
## Business rules
|
||||
|
||||
- `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).
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
summary: "mart_arr_daily: daily ARR snapshot with contract-first valuation, arr_cents and display columns, used for ARR trend and EoQ reporting."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- arr
|
||||
- revenue
|
||||
- mart
|
||||
- orbit-analytics
|
||||
sl_refs:
|
||||
- mart_arr_daily
|
||||
tables:
|
||||
- orbit_analytics.mart_arr_daily
|
||||
---
|
||||
|
||||
# mart_arr_daily
|
||||
|
||||
<!-- 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`
|
||||
**Grain:** one row per `metric_date`
|
||||
|
||||
## Columns
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| `metric_date` | date | Snapshot date |
|
||||
| `arr_cents` | bigint | ARR in cents (contract-first: active contract ARR takes precedence over subscription ARR) |
|
||||
| `display` | text | Human-readable ARR label (e.g. formatted dollar string) |
|
||||
|
||||
## Key measures (SL: `mart_arr_daily`)
|
||||
|
||||
- `total_arr_cents` — `sum(arr_cents)`
|
||||
- `arr_millions` — `round(sum(arr_cents) / 100000000.0, 3)` — ARR in $M
|
||||
|
||||
## Common query patterns
|
||||
|
||||
- **Current ARR:** filter `metric_date = current_date` (or latest available date)
|
||||
- **EoQ ARR:** filter `metric_date = date '2026-03-31'`
|
||||
- **ARR trend:** group by `metric_date`, plot `arr_cents`
|
||||
|
||||
## Business rules
|
||||
|
||||
- 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.
|
||||
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
summary: "mart_nrr_quarterly: quarterly NRR by segment with net_revenue_retention ratio, expansion/contraction/churn ARR cents, and quarter_label. Enterprise is the primary reporting segment."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- nrr
|
||||
- retention
|
||||
- revenue
|
||||
- mart
|
||||
- orbit-analytics
|
||||
sl_refs:
|
||||
- mart_nrr_quarterly
|
||||
tables:
|
||||
- orbit_analytics.mart_nrr_quarterly
|
||||
---
|
||||
|
||||
# mart_nrr_quarterly
|
||||
|
||||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/58.json -->
|
||||
<!-- 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`
|
||||
**Grain:** one row per `quarter_label` × `segment`
|
||||
|
||||
## Columns
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| `quarter_start_date` | date | First day of the quarter |
|
||||
| `quarter_label` | text | Quarter identifier, e.g. `'2026-Q1'` |
|
||||
| `segment` | text | Customer segment: `enterprise`, `commercial`, `self_serve` |
|
||||
| `starting_arr_cents` | bigint | ARR at start of quarter in cents |
|
||||
| `expansion_arr_cents` | bigint | ARR added from expansions |
|
||||
| `contraction_arr_cents` | bigint | ARR lost from contractions (includes discount expirations) |
|
||||
| `churned_arr_cents` | bigint | ARR lost from churn |
|
||||
| `net_revenue_retention` | decimal | NRR ratio (e.g. `1.12` = 112%) |
|
||||
|
||||
## Key measures (SL: `mart_nrr_quarterly`)
|
||||
|
||||
- `avg_nrr` — `avg(net_revenue_retention)` across all rows
|
||||
- `avg_nrr_enterprise` — `avg(net_revenue_retention)` filtered to `segment = 'enterprise'`
|
||||
- `total_expansion_arr_cents`, `total_contraction_arr_cents`, `total_churned_arr_cents`
|
||||
|
||||
## Common query patterns
|
||||
|
||||
- **Q1 enterprise NRR:** `WHERE quarter_label = '2026-Q1' AND segment = 'enterprise'`
|
||||
- **NRR as percent:** `round(net_revenue_retention * 100, 1)`
|
||||
- **Trend by quarter:** `ORDER BY quarter_start_date`
|
||||
|
||||
## Business rules
|
||||
|
||||
- `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.
|
||||
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
summary: "mart_procurement_activity: weekly active requester counts by contract_arr_threshold_cents. Standard threshold is 20000000 cents ($200k ARR). Used for golden-week procurement metrics."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- procurement
|
||||
- mart
|
||||
- orbit-analytics
|
||||
- active-requesters
|
||||
sl_refs:
|
||||
- mart_procurement_activity
|
||||
tables:
|
||||
- orbit_analytics.mart_procurement_activity
|
||||
---
|
||||
|
||||
# mart_procurement_activity
|
||||
|
||||
<!-- 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`
|
||||
**Grain:** one row per `week_start_date` × `contract_arr_threshold_cents`
|
||||
|
||||
## Columns
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| `week_start_date` | date | Monday of the reporting week |
|
||||
| `week_end_date` | date | Sunday of the reporting week |
|
||||
| `contract_arr_threshold_cents` | bigint | ARR threshold filter applied (e.g. `20000000` = $200k) |
|
||||
| `active_requesters` | bigint | Count of qualifying active requesters for the week |
|
||||
|
||||
## Key measures (SL: `mart_procurement_activity`)
|
||||
|
||||
- `total_active_requesters` — `sum(active_requesters)`
|
||||
- `active_requesters_200k_threshold` — `sum(active_requesters)` where `contract_arr_threshold_cents = 20000000`
|
||||
|
||||
## Common query patterns
|
||||
|
||||
- **Golden week (week of 2026-03-23):** `WHERE week_start_date = date '2026-03-23' AND contract_arr_threshold_cents = 20000000`
|
||||
- **Weekly trend at $200k threshold:** `WHERE contract_arr_threshold_cents = 20000000 ORDER BY week_start_date`
|
||||
|
||||
## Business rules
|
||||
|
||||
- `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.
|
||||
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
summary: "mart_retention_movement_breakout: quarterly ARR movement by segment, movement_type, and movement_reason. NRR waterfall source. Contraction includes discount expirations."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- nrr
|
||||
- retention
|
||||
- arr
|
||||
- mart
|
||||
- orbit-analytics
|
||||
sl_refs:
|
||||
- mart_retention_movement_breakout
|
||||
tables:
|
||||
- orbit_analytics.mart_retention_movement_breakout
|
||||
---
|
||||
|
||||
# mart_retention_movement_breakout
|
||||
|
||||
<!-- 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`
|
||||
**Grain:** one row per `quarter_label` × `segment` × `movement_type` × `movement_reason`
|
||||
|
||||
## Columns
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| `quarter_start_date` | date | First day of the quarter |
|
||||
| `quarter_label` | text | Quarter identifier, e.g. `'2026-Q1'` |
|
||||
| `segment` | text | Customer segment: `enterprise`, `commercial`, `self_serve` |
|
||||
| `movement_type` | text | `expansion`, `contraction`, or `churn` |
|
||||
| `movement_reason` | text | Specific reason (e.g. `discount_expiration`) |
|
||||
| `parent_account_count` | bigint | Number of parent accounts in this bucket |
|
||||
| `expansion_arr_cents` | bigint | Expansion ARR in cents |
|
||||
| `contraction_arr_cents` | bigint | Contraction ARR in cents |
|
||||
| `churned_arr_cents` | bigint | Churned ARR in cents |
|
||||
|
||||
## Key measures (SL: `mart_retention_movement_breakout`)
|
||||
|
||||
- `total_expansion_arr_cents`, `total_contraction_arr_cents`, `total_churned_arr_cents`
|
||||
- `expansion_arr_millions`, `contraction_arr_millions`, `churned_arr_millions`
|
||||
- `parent_account_count`
|
||||
|
||||
## Common query patterns
|
||||
|
||||
- **Q1 enterprise waterfall:** `WHERE quarter_label = '2026-Q1' AND segment = 'enterprise'`
|
||||
- **Movement summary:** `GROUP BY movement_type ORDER BY movement_type`
|
||||
- **Discount expiration contraction:** `WHERE movement_reason = 'discount_expiration'`
|
||||
|
||||
## Business rules
|
||||
|
||||
- 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.
|
||||
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
summary: "mart_revenue_daily: daily gross-to-net revenue reconciliation with gross_revenue_cents, credits_cents, refunds_cents, net_revenue_cents, and reconciliation_check."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- revenue
|
||||
- reconciliation
|
||||
- mart
|
||||
- orbit-analytics
|
||||
sl_refs:
|
||||
- mart_revenue_daily
|
||||
tables:
|
||||
- orbit_analytics.mart_revenue_daily
|
||||
---
|
||||
|
||||
# mart_revenue_daily
|
||||
|
||||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/57.json -->
|
||||
<!-- from: raw-sources/postgres-warehouse/metabase/2026-05-12-035303-local-metabase-3-114d957b-f564-4f46-8d4c-2770720a95be/cards/97.json -->
|
||||
<!-- 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`
|
||||
**Grain:** one row per `revenue_date`
|
||||
|
||||
## Columns
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| `revenue_date` | date | Revenue recognition date |
|
||||
| `gross_revenue_cents` | bigint | Gross invoice revenue in cents |
|
||||
| `credits_cents` | bigint | Credits applied in cents |
|
||||
| `refunds_cents` | bigint | Refunds issued in cents |
|
||||
| `net_revenue_cents` | bigint | Net revenue = gross − credits − refunds |
|
||||
| `reconciliation_check` | boolean | Must be `true` on every row; flags rows where net ≠ gross − credits − refunds |
|
||||
|
||||
## Key measures (SL: `mart_revenue_daily`)
|
||||
|
||||
- `total_gross_revenue_cents` — `sum(gross_revenue_cents)`
|
||||
- `total_credits_cents` — `sum(credits_cents)`
|
||||
- `total_refunds_cents` — `sum(refunds_cents)`
|
||||
- `total_net_revenue_cents` — `sum(net_revenue_cents)`
|
||||
- `net_revenue_millions` — `round(sum(net_revenue_cents) / 100000000.0, 3)`
|
||||
- `gross_revenue_millions` — `round(sum(gross_revenue_cents) / 100000000.0, 3)`
|
||||
|
||||
## Common query patterns
|
||||
|
||||
- **Q1 net revenue:** `WHERE revenue_date BETWEEN '2026-01-01' AND '2026-03-31'`
|
||||
- **February reconciliation:** `WHERE revenue_date BETWEEN '2026-02-01' AND '2026-02-28'`
|
||||
- **Monthly trend:** `GROUP BY date_trunc('month', revenue_date)`
|
||||
|
||||
## Business rules
|
||||
|
||||
- `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.
|
||||
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
summary: "Metabase SQL Library collection (collection 7): reusable query patterns, the account_join snippet, and field-filter conventions used across Orbit Showcase cards."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- metabase
|
||||
- sql-patterns
|
||||
- orbit-showcase
|
||||
sl_refs:
|
||||
- mart_account_segments
|
||||
- mart_procurement_activity
|
||||
- mart_customer_health
|
||||
- mart_retention_movement_breakout
|
||||
- mart_revenue_daily
|
||||
- mart_nrr_quarterly
|
||||
---
|
||||
|
||||
# Orbit Metabase SQL Library — Patterns & Conventions
|
||||
|
||||
Collection **7 "SQL Library"** (parent: Orbit Showcase, collection 5) contains reference queries that demonstrate how to write Metabase native SQL against the Orbit analytics marts. Cards here are intentionally illustrative; several have `dashboardCount: 0` and are not embedded in live dashboards.
|
||||
|
||||
## Reusable snippet: `account_join`
|
||||
|
||||
Card 55 ("Large contract requesters") references `{{snippet: account_join}}`. The resolved SQL shows the canonical pattern for joining `orbit_analytics.mart_account_segments` to `orbit_raw.accounts`:
|
||||
|
||||
```sql
|
||||
FROM orbit_analytics.mart_account_segments mart
|
||||
LEFT JOIN orbit_raw.accounts a
|
||||
ON a.account_id = mart.account_id
|
||||
AND a.is_internal = false
|
||||
AND a.is_test = false
|
||||
```
|
||||
|
||||
Key points:
|
||||
- The `is_internal = false AND is_test = false` guard is applied **in the JOIN condition**, not the WHERE clause, so it does not drop rows from `mart_account_segments` that have no matching account row.
|
||||
- The alias `mart` is used for `mart_account_segments` throughout the snippet.
|
||||
- This pattern is equivalent to the filter used in card 48 ("Top accounts by contract ARR"), which applies the same guards in the WHERE clause instead.
|
||||
|
||||
## Field-filter conventions
|
||||
|
||||
Cards in this collection use Metabase dimension field filters (`type: dimension`) for optional narrowing:
|
||||
- `segment` filter → maps to `mart_account_segments.segment` or `mart_retention_movement_breakout.segment`.
|
||||
- `date_range` filter → maps to `mart_procurement_activity.week_start_date`.
|
||||
- `quarter` filter → maps to `mart_nrr_quarterly.quarter_label`.
|
||||
|
||||
These filters are **optional** (`[[ ... ]]` blocks in raw SQL); the resolved SQL drops them, leaving the unfiltered dataset. SL sources derived from these cards should not bake in the filter.
|
||||
|
||||
## Hard-coded date anti-pattern
|
||||
|
||||
Card 54 ("February credits drilldown") is explicitly documented as a **counter-example**: it hard-codes `revenue_date BETWEEN DATE '2026-02-01' AND DATE '2026-02-28'`. This card is not embedded in any dashboard and should not be used as a template. Use `mart_revenue_daily` directly with a runtime date filter instead.
|
||||
|
||||
## Near-duplicate pair: cards 48 and 55
|
||||
|
||||
Both cards query `mart_account_segments` + `orbit_raw.accounts` and project `account_name`, `contract_arr`, `segment`, `size_band`. They differ only in:
|
||||
- Card 48: no ARR floor filter, LIMIT 20, on 1 dashboard.
|
||||
- Card 55: `contract_arr_cents >= 20,000,000` ($200k floor), LIMIT 25, no dashboard.
|
||||
|
||||
Card 48 is the canonical reference; card 55 is a filtered variant for large-contract analysis.
|
||||
|
||||
## Cards and their mart sources
|
||||
|
||||
| Card | Name | Mart | Dashboard count |
|
||||
|------|------|------|----------------|
|
||||
| 48 | Top accounts by contract ARR | mart_account_segments | 1 |
|
||||
| 49 | Procurement actions by week | mart_procurement_activity | 1 |
|
||||
| 50 | Accounts at risk | mart_customer_health | 1 |
|
||||
| 51 | ARR movement breakout | mart_retention_movement_breakout | 1 |
|
||||
| 52 | Revenue refund audit | mart_revenue_daily | 0 |
|
||||
| 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 |
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
summary: "NRR definition and the Q1 2026 discount-expiration contraction treatment: discount expirations are classified as contraction, not churn, and tracked separately via is_discount_expiration_contraction."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- nrr
|
||||
- retention
|
||||
- governed-metric
|
||||
- analytics
|
||||
- discount
|
||||
- contraction
|
||||
sl_refs:
|
||||
- mart_nrr_quarterly
|
||||
---
|
||||
|
||||
# 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`
|
||||
**Sources:** `mart_nrr_quarterly`, `mart_retention_movement_breakout`
|
||||
|
||||
## NRR Definition
|
||||
|
||||
Net Revenue Retention (NRR) is calculated quarterly at the **parent-account** grain using `int_parent_account_arr_movements`. The enterprise segment is the primary reporting cut.
|
||||
|
||||
**Known assertions:**
|
||||
- Enterprise NRR **2026-Q1 = 1.018** (101.8%)
|
||||
- Enterprise NRR **2025-Q4 = 1.064** (106.4%)
|
||||
|
||||
## Discount Expiration Treatment
|
||||
|
||||
Contraction ARR arising from the expiry of launch/renewal/migration/goodwill discounts is **not classified as churn**. It is tracked via the boolean flag `is_discount_expiration_contraction` on `int_parent_account_arr_movements` and surfaced as `movement_reason = 'discount_expiration'` in `mart_retention_movement_breakout`.
|
||||
|
||||
**Known assertion:** 11 parent accounts had `movement_type = 'contraction'` and `movement_reason = 'discount_expiration'` in Q1 2026.
|
||||
|
||||
## Discount Types (from `stg_contract_discount_terms`)
|
||||
|
||||
`launch`, `renewal`, `migration`, `goodwill`
|
||||
|
||||
## Movement Types
|
||||
|
||||
`new`, `expansion`, `contraction`, `churn`, `reactivation`
|
||||
|
||||
## Why This Matters
|
||||
|
||||
Without the discount-expiration carve-out, Q1 2026 enterprise NRR would appear lower than it is. The Q4 → Q1 drop (1.064 → 1.018) is partly explained by discount expirations, not organic churn.
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
summary: "Plan code normalization rules: pro_plus maps to growth. Reporting segments (self_serve, commercial, enterprise) are derived from canonical_plan_code × size_band via stg_plan_segment_mapping."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- segmentation
|
||||
- plans
|
||||
- sales-ops
|
||||
- governed-metric
|
||||
- normalization
|
||||
sl_refs:
|
||||
- mart_account_segments
|
||||
---
|
||||
|
||||
# Plan & Segment 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
|
||||
|
||||
| Raw / Legacy Code | Canonical Code |
|
||||
|---|---|
|
||||
| `starter` | `starter` |
|
||||
| `growth` | `growth` |
|
||||
| `pro_plus` | **`growth`** (normalized) |
|
||||
| `enterprise` | `enterprise` |
|
||||
|
||||
The normalization is applied via `stg_plans.canonical_plan_code`. `mart_account_segments.normalized_plan_code` reflects the post-normalization value.
|
||||
|
||||
## Reporting Segments
|
||||
|
||||
Segments are derived from `canonical_plan_code` × `size_band` using the effective-dated lookup `stg_plan_segment_mapping`:
|
||||
|
||||
| Segment | Typical plan + size band |
|
||||
|---|---|
|
||||
| `self_serve` | starter / smb |
|
||||
| `commercial` | growth / mid_market |
|
||||
| `enterprise` | enterprise / enterprise |
|
||||
|
||||
## Size Bands
|
||||
|
||||
`smb`, `mid_market`, `enterprise`
|
||||
|
||||
## Effective Dating
|
||||
|
||||
`stg_plan_segment_mapping` has `effective_from` / `effective_to` columns, allowing segment rules to change over time without rewriting history.
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
summary: "Qualifying procurement actions for weekly active requester counts: non-internal, non-test requesters on large active contracts. Covers golden week metric and supplier onboarding."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- procurement
|
||||
- product
|
||||
- governed-metric
|
||||
- weekly-active-requesters
|
||||
- suppliers
|
||||
sl_refs:
|
||||
- mart_procurement_activity
|
||||
---
|
||||
|
||||
# Procurement — Qualifying Actions & Weekly Active Requesters
|
||||
|
||||
**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
|
||||
|
||||
A qualifying procurement action is any activity by a **non-internal, non-test** requester on a **large active contract** within the measurement week. Captured in `int_procurement_qualifying_actions`.
|
||||
|
||||
Qualifying action types include:
|
||||
- Submitting a purchase request (`stg_purchase_requests`, status: submitted/approved)
|
||||
- Supplier onboarding milestones (`stg_supplier_onboarding_events`, event_type: profile_completed, approved)
|
||||
- Purchase order creation (`stg_purchase_orders`)
|
||||
|
||||
## Exclusions
|
||||
|
||||
- Accounts with `lifecycle_status IN ('internal', 'test')` on `stg_accounts`
|
||||
- Requesters without an approved purchase request in the window
|
||||
|
||||
## Supplier Onboarding Milestones
|
||||
|
||||
`invited` → `profile_started` → `profile_completed` → `approved`
|
||||
|
||||
## Approval Decisions (`stg_approval_events`)
|
||||
|
||||
`approved`, `rejected`, `returned`
|
||||
|
||||
## Dashboard
|
||||
|
||||
Exposed via the **Growth Activation Dashboard** (`https://orbit-demo.example.com/dashboards/activation`), which depends on `mart_account_activity`.
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
summary: "Orbit product design policy: new features must make requester or approver experience clearer; complexity for its own sake is not built."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- product
|
||||
- policy
|
||||
- orbit
|
||||
refs:
|
||||
- orbit-product-review-checklist
|
||||
- orbit-company-overview
|
||||
---
|
||||
|
||||
## Orbit Product Design Principles
|
||||
|
||||
**Source:** Notion — Product & Customers, last edited 2026-05-07
|
||||
|
||||
---
|
||||
|
||||
## Core Policy
|
||||
|
||||
Orbit does not build complexity for its own sake.
|
||||
|
||||
## Feature Complexity Rule
|
||||
|
||||
- When a new feature adds multiple configuration choices, it **must** be evaluated on whether it makes the requester or approver experience clearer.
|
||||
- If the added configuration does not make the requester or approver experience clearer, the feature should not be built as designed.
|
||||
- The test: can a first-time requester or approver use the new feature without needing to understand the configuration choices behind it?
|
||||
|
||||
## Design Heuristics
|
||||
|
||||
- **Default to simpler.** If two designs achieve the same outcome, prefer the one with fewer choices exposed to the end user.
|
||||
- **Configuration is a last resort.** Expose configuration only when different customers have legitimately incompatible needs that cannot be resolved by a sensible default.
|
||||
- **Requester and approver clarity are the primary UX metrics.** Speed, completeness, and confidence for those two roles are the measures of a good Orbit feature.
|
||||
|
||||
## What This Is Not
|
||||
|
||||
- This principle does not prohibit powerful or flexible features.
|
||||
- It prohibits features where the complexity is internal to Orbit's implementation but leaks into the requester or approver experience without benefit.
|
||||
|
||||
---
|
||||
|
||||
See also: [[orbit-product-review-checklist]], [[orbit-company-overview]]
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
summary: "Five-question checklist to evaluate every Orbit product change: requester clarity, approver context, supplier onboarding ownership, PO accuracy, and CS rollout visibility."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- product
|
||||
- policy
|
||||
- orbit
|
||||
refs:
|
||||
- orbit-company-overview
|
||||
- sales-ops-cs-handoff-process
|
||||
---
|
||||
|
||||
## Product Review Checklist
|
||||
|
||||
**Source:** Notion — Product & Customers, last edited 2026-05-07
|
||||
|
||||
---
|
||||
|
||||
## Policy
|
||||
|
||||
Every product change must be evaluated against all five questions before shipping. A "no" on any question is a blocker unless explicitly waived by the accountable lead with a written rationale.
|
||||
|
||||
## The Five Questions
|
||||
|
||||
| # | Question | What a "no" means |
|
||||
|---|---|---|
|
||||
| 1 | **Does a first-time requester know what to do next?** | The requester experience is unclear or requires prior knowledge not surfaced in the UI. |
|
||||
| 2 | **Can an approver make a decision without missing context?** | The approver is missing information needed to approve or reject confidently. |
|
||||
| 3 | **Is supplier onboarding assigned to a named person, not a queue?** | Supplier onboarding has no clear owner and will stall. |
|
||||
| 4 | **Does the PO reflect the approved request?** | There is a mismatch between what was approved and what the PO captures. |
|
||||
| 5 | **Can Customer Success detect a stuck rollout after week two?** | CS has no signal to identify customers who are not progressing past initial setup. |
|
||||
|
||||
## Usage
|
||||
|
||||
- Use this checklist in product reviews, design critiques, and pre-launch readiness checks.
|
||||
- Questions 1–2 are requester/approver experience checks.
|
||||
- Question 3 is a supplier onboarding ownership check.
|
||||
- Question 4 is a PO accuracy check.
|
||||
- Question 5 is a post-launch CS visibility check.
|
||||
|
||||
---
|
||||
|
||||
See also: [[orbit-company-overview]], [[orbit-product-design-principles]], [[sales-ops-cs-handoff-process]]
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
summary: "Gross-to-net revenue reconciliation: mart_revenue_daily reconciles gross invoice revenue, credits, and refunds to net revenue daily. reconciliation_check must be true on every row."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- revenue
|
||||
- net-revenue
|
||||
- governed-metric
|
||||
- finance
|
||||
- reconciliation
|
||||
sl_refs:
|
||||
- mart_revenue_daily
|
||||
---
|
||||
|
||||
# Revenue — 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
|
||||
|
||||
```
|
||||
net_revenue = gross_revenue - credits - refunds
|
||||
```
|
||||
|
||||
All amounts are in **cents** (USD only — `stg_invoices.currency` is asserted to be `USD`).
|
||||
|
||||
## Components
|
||||
|
||||
| Column | Source | Description |
|
||||
|---|---|---|
|
||||
| `gross_revenue_cents` | `stg_invoices` / `stg_invoice_line_items` | Billed amounts before adjustments |
|
||||
| `credit_cents` | `stg_invoice_line_items` (type=credit) | Credits applied to invoices |
|
||||
| `refund_cents` | `stg_refunds` | Refunds reduce net revenue in the refund month |
|
||||
| `net_revenue_cents` | Derived | gross − credits − refunds |
|
||||
|
||||
## Intermediate model
|
||||
|
||||
`int_revenue_components` — daily gross, credit, refund, and net revenue components.
|
||||
|
||||
## Quality Gates
|
||||
|
||||
- `reconciliation_check` must be `true` on every row of `mart_revenue_daily`.
|
||||
- `assert_february_2026_net_revenue` — a dbt singular test covering February 2026 net revenue total.
|
||||
|
||||
## Line Item Types (`stg_invoice_line_items`)
|
||||
|
||||
`subscription`, `seat`, `usage`, `addon`, `credit`
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
---
|
||||
summary: Procurement workflow activity measures active requesters and qualifying actions.
|
||||
tags:
|
||||
- product
|
||||
- procurement
|
||||
refs:
|
||||
- activation-policy
|
||||
sl_refs:
|
||||
- orbit_demo.purchase_requests
|
||||
usage_mode: auto
|
||||
---
|
||||
|
||||
Weekly active requesters counts distinct non-internal requesters with a qualifying procurement action in the calendar week.
|
||||
|
||||
Qualifying actions include purchase request creation, approval decisions, supplier invites, and purchase-order creation.
|
||||
|
||||
Purchase-request comments and short sessions are excluded from the canonical requester activity metric.
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
---
|
||||
summary: Gross-to-net revenue reconciles paid invoices, credits, and refunds.
|
||||
tags:
|
||||
- finance
|
||||
- revenue
|
||||
refs:
|
||||
- arr-contract-first
|
||||
sl_refs:
|
||||
- orbit_demo.invoices
|
||||
usage_mode: auto
|
||||
---
|
||||
|
||||
Gross revenue starts from paid invoice activity. Net revenue subtracts credits and successful refunds in the month they are recorded.
|
||||
|
||||
Exclude unpaid, void, draft, failed, internal, and test-account invoice activity from canonical revenue reporting.
|
||||
|
||||
February 2026 has an elevated refund event captured in the source notes and revenue dashboard.
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
summary: "Sales Ops → Customer Success implementation handoff: required fields, ownership, enterprise account risk, and policy that CS must not rediscover sales-stage details."
|
||||
usage_mode: auto
|
||||
sort_order: 0
|
||||
tags:
|
||||
- policy
|
||||
- sales-ops
|
||||
- customer-success
|
||||
refs:
|
||||
- orbit-company-overview
|
||||
- orbit-how-we-work
|
||||
- orbit-plan-segment-normalization
|
||||
---
|
||||
|
||||
## Sales Ops → Customer Success Implementation Handoff
|
||||
|
||||
**Source:** Notion — People & Operating Norms, last edited 2026-05-07
|
||||
**Owner:** Sales Ops (sender), Customer Success (receiver)
|
||||
|
||||
---
|
||||
|
||||
## Policy
|
||||
|
||||
Sales Ops must complete the handoff **before the first implementation call**. Customer Success should not need to rediscover any of the following details.
|
||||
|
||||
## Required Handoff Fields
|
||||
|
||||
| Field | Notes |
|
||||
|---|---|
|
||||
| Current plan | Starter / Growth / Enterprise — use canonical plan name, not legacy aliases |
|
||||
| Account segment | self_serve / commercial / enterprise (see `orbit-plan-segment-normalization`) |
|
||||
| Contract shape | Term, ARR, any discounts or custom terms |
|
||||
| Renewal contact | Named person on the customer side responsible for renewal |
|
||||
| Unusual approval requirements | Any non-standard approval routing the customer has configured or requested |
|
||||
| Unusual supplier requirements | Any supplier onboarding exceptions or pre-approved vendor lists |
|
||||
|
||||
## Ownership
|
||||
|
||||
- **Sales Ops** is responsible for populating and delivering the handoff before the first implementation call.
|
||||
- **Customer Success** is responsible for flagging missing fields to Sales Ops before the call, not during or after.
|
||||
- If a field is unknown at handoff time, Sales Ops must note it explicitly as "unknown — to be resolved by [date]" rather than leaving it blank.
|
||||
|
||||
## Common Failure Mode
|
||||
|
||||
Handoffs that omit contract shape or renewal contact force CS to re-engage Sales Ops mid-implementation, which delays time-to-value and creates duplicate discovery work. This is the primary failure mode this process is designed to prevent.
|
||||
|
||||
---
|
||||
|
||||
## Enterprise Account Risk: Parent/Child Complexity
|
||||
|
||||
- Enterprise accounts with parent/child account structures require extra care during handoff.
|
||||
- Small assumptions made during handoff in these accounts tend to produce large downstream problems (billing mismatches, approval routing failures, supplier onboarding gaps).
|
||||
- When the account has parent/child complexity, Sales Ops must explicitly flag it in the handoff and document the account hierarchy before the first implementation call.
|
||||
- CS should treat any undocumented parent/child relationship as a blocker — do not proceed with implementation setup until the structure is confirmed.
|
||||
|
||||
---
|
||||
|
||||
See also: [[orbit-company-overview]], [[orbit-how-we-work]], [[orbit-plan-segment-normalization]]
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
---
|
||||
summary: Account segments derive from plan normalization and effective-dated mapping.
|
||||
tags:
|
||||
- sales-ops
|
||||
- segmentation
|
||||
refs: []
|
||||
sl_refs:
|
||||
- orbit_demo.accounts
|
||||
- orbit_demo.contracts
|
||||
usage_mode: auto
|
||||
---
|
||||
|
||||
Account segment labels combine plan_code, canonical_plan_code, and size_band fields.
|
||||
|
||||
Historical plan code pro_plus maps to growth for current segment analysis.
|
||||
|
||||
Use the mapping active at the metric date when segment definitions change over time.
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
---
|
||||
summary: Support escalation tiers map ticket severity to SLA targets.
|
||||
tags:
|
||||
- support
|
||||
- sla
|
||||
refs:
|
||||
- customer-health-scoring
|
||||
sl_refs:
|
||||
- orbit_demo.support_tickets
|
||||
usage_mode: auto
|
||||
---
|
||||
|
||||
Critical support tickets require immediate response and on-call escalation.
|
||||
|
||||
High severity tickets should receive first response within four business hours.
|
||||
|
||||
Resolution time is measured from created_at to resolved_at and only applies to resolved tickets.
|
||||
|
|
@ -43,12 +43,12 @@
|
|||
},
|
||||
"generated": {
|
||||
"semanticLayer": {
|
||||
"path": "semantic-layer/orbit_demo",
|
||||
"sourceCount": 6
|
||||
"path": "semantic-layer",
|
||||
"sourceCount": 46
|
||||
},
|
||||
"knowledge": {
|
||||
"path": "knowledge/global",
|
||||
"pageCount": 10
|
||||
"pageCount": 28
|
||||
},
|
||||
"links": {
|
||||
"path": "links",
|
||||
|
|
|
|||
0
packages/cli/assets/demo/orbit/semantic-layer/.gitkeep
Normal file
0
packages/cli/assets/demo/orbit/semantic-layer/.gitkeep
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
name: int_activation_policy_windows
|
||||
table: orbit_analytics.int_activation_policy_windows
|
||||
grain:
|
||||
- policy_version
|
||||
columns:
|
||||
- name: policy_version
|
||||
type: string
|
||||
descriptions:
|
||||
user: pre_2026_01_15 or post_2026_01_15
|
||||
- name: activated_account_count
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column activated account count from int_activation_policy_windows.
|
||||
- name: window_start
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Column window start from int_activation_policy_windows.
|
||||
- name: window_end
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Column window end from int_activation_policy_windows.
|
||||
joins: []
|
||||
measures:
|
||||
- name: total_activated_accounts
|
||||
expr: sum(activated_account_count)
|
||||
descriptions:
|
||||
user: Activation cohort counts around the January 2026 policy change.
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
name: int_active_contract_arr
|
||||
table: orbit_analytics.int_active_contract_arr
|
||||
grain:
|
||||
- contract_id
|
||||
columns:
|
||||
- name: contract_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on int_active_contract_arr.
|
||||
- name: arr_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column arr cents from int_active_contract_arr.
|
||||
joins: []
|
||||
measures:
|
||||
- name: total_arr_cents
|
||||
expr: sum(arr_cents)
|
||||
description: Total active contract ARR in cents as of 2026-03-31.
|
||||
descriptions:
|
||||
user: Active contract ARR as of 2026-03-31.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
name: int_customer_health_signals
|
||||
table: orbit_analytics.int_customer_health_signals
|
||||
grain:
|
||||
- account_id
|
||||
columns:
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on int_customer_health_signals.
|
||||
- name: open_critical_ticket_count
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column open critical ticket count from int_customer_health_signals.
|
||||
- name: recent_procurement_action_count
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column recent procurement action count from int_customer_health_signals.
|
||||
- name: risk_level
|
||||
type: string
|
||||
descriptions:
|
||||
user: "Derived risk level: low, medium, high"
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Support-ticket and recent-procurement signals for customer health risk.
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
name: int_parent_account_arr_movements
|
||||
table: orbit_analytics.int_parent_account_arr_movements
|
||||
grain:
|
||||
- arr_movement_id
|
||||
columns:
|
||||
- name: arr_movement_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: parent_account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related parent account on int_parent_account_arr_movements.
|
||||
- name: movement_type
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [new, expansion, contraction, churn, reactivation]"
|
||||
- name: is_discount_expiration_contraction
|
||||
type: boolean
|
||||
descriptions:
|
||||
user: Discount expiration contraction flag used to keep discount movement separate from churn.
|
||||
- name: movement_date
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Date or time value for movement date on int_parent_account_arr_movements.
|
||||
- name: arr_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column arr cents from int_parent_account_arr_movements.
|
||||
joins: []
|
||||
measures:
|
||||
- name: expansion_arr_cents
|
||||
expr: sum(arr_cents)
|
||||
filter: movement_type = 'expansion'
|
||||
description: Sum of expansion ARR movements in cents.
|
||||
- name: contraction_arr_cents
|
||||
expr: sum(arr_cents)
|
||||
filter: movement_type = 'contraction'
|
||||
description: Sum of contraction ARR movements in cents.
|
||||
- name: churn_arr_cents
|
||||
expr: sum(arr_cents)
|
||||
filter: movement_type = 'churn'
|
||||
description: Sum of churn ARR movements in cents.
|
||||
- name: discount_expiration_contraction_arr_cents
|
||||
expr: sum(arr_cents)
|
||||
filter: is_discount_expiration_contraction = true
|
||||
description: Contraction ARR from discount expirations — kept separate from churn in NRR calculation.
|
||||
descriptions:
|
||||
user: Parent-account movement rollups for retention metrics.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
name: int_procurement_qualifying_actions
|
||||
table: orbit_analytics.int_procurement_qualifying_actions
|
||||
grain:
|
||||
- purchase_request_id
|
||||
columns:
|
||||
- name: purchase_request_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related purchase request on int_procurement_qualifying_actions.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on int_procurement_qualifying_actions.
|
||||
- name: requester_user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related requester user on int_procurement_qualifying_actions.
|
||||
- name: action_week
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Column action week from int_procurement_qualifying_actions.
|
||||
joins: []
|
||||
measures:
|
||||
- name: qualifying_action_count
|
||||
expr: count(purchase_request_id)
|
||||
descriptions:
|
||||
user: Non-internal, non-test requester activity for large active contracts in the golden week.
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
name: int_revenue_components
|
||||
table: orbit_analytics.int_revenue_components
|
||||
grain:
|
||||
- revenue_date
|
||||
columns:
|
||||
- name: revenue_date
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Date or time value for revenue date on int_revenue_components.
|
||||
- name: gross_revenue_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column gross revenue cents from int_revenue_components.
|
||||
- name: credit_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column credit cents from int_revenue_components.
|
||||
- name: refund_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column refund cents from int_revenue_components.
|
||||
- name: net_revenue_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column net revenue cents from int_revenue_components.
|
||||
joins: []
|
||||
measures:
|
||||
- name: total_gross_revenue_cents
|
||||
expr: sum(gross_revenue_cents)
|
||||
- name: total_credit_cents
|
||||
expr: sum(credit_cents)
|
||||
- name: total_refund_cents
|
||||
expr: sum(refund_cents)
|
||||
- name: total_net_revenue_cents
|
||||
expr: sum(net_revenue_cents)
|
||||
descriptions:
|
||||
user: Daily gross, credit, refund, and net revenue components.
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
name: mart_account_activity
|
||||
table: orbit_analytics.mart_account_activity
|
||||
grain:
|
||||
- policy_version
|
||||
columns:
|
||||
- name: policy_version
|
||||
type: string
|
||||
descriptions:
|
||||
user: pre_2026_01_15 or post_2026_01_15
|
||||
- name: activated_account_count
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column activated account count from mart_account_activity.
|
||||
- name: window_label
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Column window label from mart_account_activity.
|
||||
joins: []
|
||||
measures:
|
||||
- name: total_activated_accounts
|
||||
expr: sum(activated_account_count)
|
||||
descriptions:
|
||||
user: "Activation policy comparison values. Governed metric: activated_accounts. Owner: growth. See notion://notion_page_activation_policy_decision#policy-change."
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
name: mart_account_segments
|
||||
table: orbit_analytics.mart_account_segments
|
||||
grain:
|
||||
- account_id
|
||||
columns:
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: normalized_plan_code
|
||||
type: string
|
||||
descriptions:
|
||||
user: pro_plus is normalized to growth through plans.canonical_plan_code.
|
||||
- name: size_band
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Column size band from mart_account_segments.
|
||||
- name: segment
|
||||
type: string
|
||||
descriptions:
|
||||
user: "Reporting segment: self_serve, commercial, enterprise"
|
||||
joins: []
|
||||
measures:
|
||||
- name: account_count
|
||||
expr: count(account_id)
|
||||
descriptions:
|
||||
user: "Current plan, size band, and reporting segment for accounts. Governed metric: segment. Owner: sales_ops. See notion://notion_page_sales_ops_segmentation#growth-plan-normalization."
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
name: mart_arr_daily
|
||||
table: orbit_analytics.mart_arr_daily
|
||||
grain:
|
||||
- metric_date
|
||||
columns:
|
||||
- name: metric_date
|
||||
type: time
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: arr_cents
|
||||
type: number
|
||||
descriptions:
|
||||
user: "ARR in cents. dbt assertion: expected value 1874200000 (i.e. $18,742,000) as of 2026-03-31."
|
||||
joins: []
|
||||
measures:
|
||||
- name: arr_cents
|
||||
expr: sum(arr_cents)
|
||||
description: Total ARR in cents across metric dates.
|
||||
descriptions:
|
||||
user: "Board-prep ARR as of the metric date. Governed metric: arr. Owner: finance. Contract-first ARR calculation — see notion://notion_page_arr_contract_reporting#arr-contract-first."
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
name: mart_customer_health
|
||||
table: orbit_analytics.mart_customer_health
|
||||
grain:
|
||||
- account_id
|
||||
columns:
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: risk_level
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [low, medium, high]"
|
||||
- name: open_critical_ticket_count
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column open critical ticket count from mart_customer_health.
|
||||
- name: recent_procurement_action_count
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column recent procurement action count from mart_customer_health.
|
||||
joins: []
|
||||
measures:
|
||||
- name: account_count
|
||||
expr: count(account_id)
|
||||
- name: high_risk_account_count
|
||||
expr: count(account_id)
|
||||
filter: risk_level = 'high'
|
||||
descriptions:
|
||||
user: "Customer-health risk mart as of 2026-03-31. Governed metric: active_customers. Owner: customer_success. See notion://notion_page_customer_health_playbook#risk-definition."
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
name: mart_nrr_quarterly
|
||||
table: orbit_analytics.mart_nrr_quarterly
|
||||
grain:
|
||||
- quarter_label
|
||||
- segment
|
||||
columns:
|
||||
- name: quarter_label
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null. Format: YYYY-QN (e.g. 2026-Q1)."
|
||||
- name: segment
|
||||
type: string
|
||||
descriptions:
|
||||
user: Reporting segment (self_serve, commercial, enterprise).
|
||||
- name: net_revenue_retention
|
||||
type: number
|
||||
descriptions:
|
||||
user: "NRR ratio. dbt assertions: enterprise 2026-Q1 = 1.018; enterprise 2025-Q4 = 1.064."
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: "Enterprise quarterly net revenue retention. Governed metric: net_revenue_retention. Owner: analytics. See notion://notion_page_retention_policy_current#nrr-definition."
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
name: mart_procurement_activity
|
||||
table: orbit_analytics.mart_procurement_activity
|
||||
grain:
|
||||
- week_start
|
||||
- account_id
|
||||
columns:
|
||||
- name: week_start
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Date or time value for week start on mart_procurement_activity.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on mart_procurement_activity.
|
||||
- name: active_requesters
|
||||
type: number
|
||||
descriptions:
|
||||
user: Weekly active requesters for large active contracts.
|
||||
joins: []
|
||||
measures:
|
||||
- name: total_active_requesters
|
||||
expr: sum(active_requesters)
|
||||
descriptions:
|
||||
user: "Weekly active requester counts for large active contracts. Governed metric: weekly_active_requesters. Owner: product. See notion://notion_page_procurement_instrumentation#qualifying-procurement-actions."
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
name: mart_retention_movement_breakout
|
||||
table: orbit_analytics.mart_retention_movement_breakout
|
||||
grain:
|
||||
- movement_type
|
||||
- movement_reason
|
||||
columns:
|
||||
- name: movement_type
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [expansion, contraction, churn]"
|
||||
- name: movement_reason
|
||||
type: string
|
||||
descriptions:
|
||||
user: Includes discount_expiration contraction, which is not churn.
|
||||
- name: parent_account_count
|
||||
type: number
|
||||
descriptions:
|
||||
user: "dbt assertion: 11 parent accounts where movement_type='contraction' and movement_reason='discount_expiration'."
|
||||
- name: expansion_arr_cents
|
||||
type: number
|
||||
descriptions:
|
||||
user: Expansion ARR cents for Q1 enterprise movement rows.
|
||||
joins: []
|
||||
measures:
|
||||
- name: total_parent_account_count
|
||||
expr: sum(parent_account_count)
|
||||
- name: total_expansion_arr_cents
|
||||
expr: sum(expansion_arr_cents)
|
||||
filter: movement_type = 'expansion'
|
||||
descriptions:
|
||||
user: "Q1 2026 enterprise retention movement breakout. Governed metric: net_revenue_retention. Owner: analytics. See notion://notion_page_retention_policy_current#discount-expiration-treatment."
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
name: mart_revenue_daily
|
||||
table: orbit_analytics.mart_revenue_daily
|
||||
grain:
|
||||
- revenue_date
|
||||
columns:
|
||||
- name: revenue_date
|
||||
type: time
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: reconciliation_check
|
||||
type: boolean
|
||||
descriptions:
|
||||
user: "dbt assertion: must be true on every row."
|
||||
- name: net_revenue_cents
|
||||
type: number
|
||||
descriptions:
|
||||
user: Daily net revenue in cents. February 2026 total covered by assert_february_2026_net_revenue.
|
||||
- name: gross_revenue_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column gross revenue cents from mart_revenue_daily.
|
||||
- name: credit_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column credit cents from mart_revenue_daily.
|
||||
- name: refund_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column refund cents from mart_revenue_daily.
|
||||
joins: []
|
||||
measures:
|
||||
- name: total_net_revenue_cents
|
||||
expr: sum(net_revenue_cents)
|
||||
- name: total_gross_revenue_cents
|
||||
expr: sum(gross_revenue_cents)
|
||||
descriptions:
|
||||
user: "Daily revenue mart reconciling gross, credits, refunds, and net revenue. Governed metric: net_revenue. Owner: finance. See notion://notion_page_revenue_reporting_policy#gross-to-net-reconciliation."
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
name: stg_account_hierarchy
|
||||
table: orbit_analytics.stg_account_hierarchy
|
||||
grain:
|
||||
- account_hierarchy_id
|
||||
columns:
|
||||
- name: account_hierarchy_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: parent_account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related parent account on stg_account_hierarchy.
|
||||
- name: child_account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related child account on stg_account_hierarchy.
|
||||
- name: relationship_type
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [subsidiary, division, billing_group]"
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Parent-child account relationships used for enterprise retention grain.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
name: stg_account_owners
|
||||
table: orbit_analytics.stg_account_owners
|
||||
grain:
|
||||
- account_owner_id
|
||||
columns:
|
||||
- name: account_owner_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on stg_account_owners.
|
||||
- name: owner_user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related owner user on stg_account_owners.
|
||||
- name: owner_team
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [sales_ops, customer_success, finance]"
|
||||
- name: effective_from
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Column effective from from stg_account_owners.
|
||||
- name: effective_to
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Column effective to from stg_account_owners.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Effective-dated ownership assignments for account health, renewals, and escalation context.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
name: stg_accounts
|
||||
table: orbit_analytics.stg_accounts
|
||||
grain:
|
||||
- account_id
|
||||
columns:
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: sales_region
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [na, emea, apac]"
|
||||
- name: size_band
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [smb, mid_market, enterprise]"
|
||||
- name: lifecycle_status
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [prospect, active, churned, internal, test]"
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Customer and internal/test account records for Orbit.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
name: stg_activation_events
|
||||
table: orbit_analytics.stg_activation_events
|
||||
grain:
|
||||
- activation_event_id
|
||||
columns:
|
||||
- name: activation_event_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on stg_activation_events.
|
||||
- name: user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related user on stg_activation_events.
|
||||
- name: event_type
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [first_requester_login, requester_activated, first_approved_purchase_request, account_activated]"
|
||||
- name: policy_version
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [pre_2026_01_15, post_2026_01_15]"
|
||||
- name: event_at
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Column event at from stg_activation_events.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Account and requester activation events across the January policy change.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
name: stg_approval_events
|
||||
table: orbit_analytics.stg_approval_events
|
||||
grain:
|
||||
- approval_event_id
|
||||
columns:
|
||||
- name: approval_event_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: purchase_request_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related purchase request on stg_approval_events.
|
||||
- name: decision
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [approved, rejected, returned]"
|
||||
- name: decided_at
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Column decided at from stg_approval_events.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Approval decisions tied to procurement requests.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
name: stg_arr_movements
|
||||
table: orbit_analytics.stg_arr_movements
|
||||
grain:
|
||||
- arr_movement_id
|
||||
columns:
|
||||
- name: arr_movement_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on stg_arr_movements.
|
||||
- name: movement_type
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [new, expansion, contraction, churn, reactivation]"
|
||||
- name: movement_date
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Date or time value for movement date on stg_arr_movements.
|
||||
- name: arr_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column arr cents from stg_arr_movements.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: ARR movement ledger used by retention and expansion marts.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
name: stg_contract_discount_terms
|
||||
table: orbit_analytics.stg_contract_discount_terms
|
||||
grain:
|
||||
- discount_term_id
|
||||
columns:
|
||||
- name: discount_term_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: contract_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related contract on stg_contract_discount_terms.
|
||||
- name: discount_type
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [launch, renewal, migration, goodwill]"
|
||||
- name: expiry_date
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Date or time value for expiry date on stg_contract_discount_terms.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Contract discount terms that explain Q1 2026 enterprise contraction movement.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
name: stg_contracts
|
||||
table: orbit_analytics.stg_contracts
|
||||
grain:
|
||||
- contract_id
|
||||
columns:
|
||||
- name: contract_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on stg_contracts.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [draft, active, cancelled, expired]"
|
||||
- name: renewal_type
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [new, renewal, expansion, downgrade]"
|
||||
- name: arr_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column arr cents from stg_contracts.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Contract records that provide contract-first ARR for active accounts.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
name: stg_invoice_line_items
|
||||
table: orbit_analytics.stg_invoice_line_items
|
||||
grain:
|
||||
- invoice_line_item_id
|
||||
columns:
|
||||
- name: invoice_line_item_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: invoice_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related invoice on stg_invoice_line_items.
|
||||
- name: line_item_type
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [subscription, seat, usage, addon, credit]"
|
||||
- name: amount_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column amount cents from stg_invoice_line_items.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Invoice line items used to split gross revenue, credits, seats, usage, and addons.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
name: stg_invoices
|
||||
table: orbit_analytics.stg_invoices
|
||||
grain:
|
||||
- invoice_id
|
||||
columns:
|
||||
- name: invoice_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on stg_invoices.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [draft, open, paid, void, failed]"
|
||||
- name: currency
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [USD] — USD only"
|
||||
- name: invoice_date
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Date or time value for invoice date on stg_invoices.
|
||||
- name: gross_amount_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column gross amount cents from stg_invoices.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Billing invoices that anchor gross revenue recognition dates.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
name: stg_plan_segment_mapping
|
||||
table: orbit_analytics.stg_plan_segment_mapping
|
||||
grain:
|
||||
- plan_segment_mapping_id
|
||||
columns:
|
||||
- name: plan_segment_mapping_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: canonical_plan_code
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [starter, growth, enterprise]"
|
||||
- name: size_band
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [smb, mid_market, enterprise]"
|
||||
- name: segment
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [self_serve, commercial, enterprise]"
|
||||
- name: effective_from
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Column effective from from stg_plan_segment_mapping.
|
||||
- name: effective_to
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Column effective to from stg_plan_segment_mapping.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Effective-dated mapping from canonical plans and size bands to reporting segments.
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
name: stg_plans
|
||||
table: orbit_analytics.stg_plans
|
||||
grain:
|
||||
- plan_id
|
||||
columns:
|
||||
- name: plan_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: canonical_plan_code
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [starter, growth, enterprise]. Note: pro_plus is normalized to growth."
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Canonical and historical Orbit pricing plans.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
name: stg_purchase_orders
|
||||
table: orbit_analytics.stg_purchase_orders
|
||||
grain:
|
||||
- purchase_order_id
|
||||
columns:
|
||||
- name: purchase_order_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: purchase_request_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related purchase request on stg_purchase_orders.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on stg_purchase_orders.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [created, sent, fulfilled, cancelled]"
|
||||
- name: created_at
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Date or time value for created at on stg_purchase_orders.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Purchase orders generated from approved procurement requests.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
name: stg_purchase_requests
|
||||
table: orbit_analytics.stg_purchase_requests
|
||||
grain:
|
||||
- purchase_request_id
|
||||
columns:
|
||||
- name: purchase_request_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on stg_purchase_requests.
|
||||
- name: requester_user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related requester user on stg_purchase_requests.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [draft, submitted, approved, rejected, cancelled]"
|
||||
- name: submitted_at
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Column submitted at from stg_purchase_requests.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Procurement request records used for activation, requester activity, and health signals.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
name: stg_refunds
|
||||
table: orbit_analytics.stg_refunds
|
||||
grain:
|
||||
- refund_id
|
||||
columns:
|
||||
- name: refund_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: invoice_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related invoice on stg_refunds.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [pending, succeeded, failed, cancelled]"
|
||||
- name: refund_date
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Date or time value for refund date on stg_refunds.
|
||||
- name: amount_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column amount cents from stg_refunds.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Refund events that reduce net revenue in the refund month.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
name: stg_sessions
|
||||
table: orbit_analytics.stg_sessions
|
||||
grain:
|
||||
- session_id
|
||||
columns:
|
||||
- name: session_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on stg_sessions.
|
||||
- name: user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related user on stg_sessions.
|
||||
- name: started_at
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Column started at from stg_sessions.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Product sessions used for pre-policy activation and activity exclusions.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
name: stg_subscriptions
|
||||
table: orbit_analytics.stg_subscriptions
|
||||
grain:
|
||||
- subscription_id
|
||||
columns:
|
||||
- name: subscription_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on stg_subscriptions.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [active, cancelled, past_due, trialing]"
|
||||
- name: arr_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ktx: Column arr cents from stg_subscriptions.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Subscription rows used when active contract ARR is not present for a covered period.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
name: stg_supplier_onboarding_events
|
||||
table: orbit_analytics.stg_supplier_onboarding_events
|
||||
grain:
|
||||
- supplier_onboarding_event_id
|
||||
columns:
|
||||
- name: supplier_onboarding_event_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: supplier_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related supplier on stg_supplier_onboarding_events.
|
||||
- name: event_type
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [invited, profile_started, profile_completed, approved]"
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [pending, completed, blocked]"
|
||||
- name: occurred_at
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Column occurred at from stg_supplier_onboarding_events.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Supplier onboarding milestones that qualify as procurement workflow activity.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
name: stg_suppliers
|
||||
table: orbit_analytics.stg_suppliers
|
||||
grain:
|
||||
- supplier_id
|
||||
columns:
|
||||
- name: supplier_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [invited, onboarding, active, inactive]"
|
||||
- name: name
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Column name from stg_suppliers.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Supplier directory records associated with procurement workflow events.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
name: stg_support_tickets
|
||||
table: orbit_analytics.stg_support_tickets
|
||||
grain:
|
||||
- support_ticket_id
|
||||
columns:
|
||||
- name: support_ticket_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on stg_support_tickets.
|
||||
- name: severity
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [low, medium, high, critical]"
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: accepted_values [open, pending, solved, closed]"
|
||||
- name: created_at
|
||||
type: time
|
||||
descriptions:
|
||||
ktx: Date or time value for created at on stg_support_tickets.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Customer support tickets that inform account health and risk.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
name: stg_users
|
||||
table: orbit_analytics.stg_users
|
||||
grain:
|
||||
- user_id
|
||||
columns:
|
||||
- name: user_id
|
||||
type: string
|
||||
descriptions:
|
||||
user: "dbt: not_null, unique"
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Identifier for the related account on stg_users.
|
||||
- name: email
|
||||
type: string
|
||||
descriptions:
|
||||
ktx: Column email from stg_users.
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Orbit user identities shared across warehouse, Slack, Looker, Notion, and Drive artifacts.
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
name: accounts
|
||||
table: accounts
|
||||
description: Customer accounts with industry, region, lifecycle, and internal/test flags.
|
||||
grain:
|
||||
- account_id
|
||||
columns:
|
||||
- name: account_id
|
||||
type: string
|
||||
- name: parent_account_id
|
||||
type: string
|
||||
- name: account_name
|
||||
type: string
|
||||
- name: domain
|
||||
type: string
|
||||
- name: industry
|
||||
type: string
|
||||
- name: sales_region
|
||||
type: string
|
||||
- name: size_band
|
||||
type: string
|
||||
- name: lifecycle_status
|
||||
type: string
|
||||
- name: is_internal
|
||||
type: boolean
|
||||
- name: is_test
|
||||
type: boolean
|
||||
- name: created_at
|
||||
type: time
|
||||
joins:
|
||||
- to: contracts
|
||||
"on": "account_id = contracts.account_id"
|
||||
relationship: one_to_many
|
||||
- to: purchase_requests
|
||||
"on": "account_id = purchase_requests.account_id"
|
||||
relationship: one_to_many
|
||||
measures:
|
||||
- name: account_count
|
||||
expr: "count(distinct account_id)"
|
||||
- name: enterprise_count
|
||||
expr: "count(distinct account_id)"
|
||||
filter: "size_band = 'enterprise'"
|
||||
segments:
|
||||
- name: external_only
|
||||
expr: "coalesce(is_internal, 0) = 0 AND coalesce(is_test, 0) = 0"
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
name: arr_movements
|
||||
table: arr_movements
|
||||
description: ARR movement ledger for expansion, contraction, churn, and reactivation analysis.
|
||||
grain:
|
||||
- arr_movement_id
|
||||
columns:
|
||||
- name: arr_movement_id
|
||||
type: string
|
||||
- name: account_id
|
||||
type: string
|
||||
- name: parent_account_id
|
||||
type: string
|
||||
- name: contract_id
|
||||
type: string
|
||||
- name: movement_date
|
||||
type: time
|
||||
- name: movement_type
|
||||
type: string
|
||||
- name: movement_reason
|
||||
type: string
|
||||
- name: arr_delta_cents
|
||||
type: number
|
||||
- name: starting_arr_cents
|
||||
type: number
|
||||
- name: ending_arr_cents
|
||||
type: number
|
||||
joins:
|
||||
- to: accounts
|
||||
"on": "account_id = accounts.account_id"
|
||||
relationship: many_to_one
|
||||
measures:
|
||||
- name: movement_count
|
||||
expr: "count(*)"
|
||||
- name: net_arr_delta
|
||||
expr: "sum(arr_delta_cents) / 100.0"
|
||||
segments:
|
||||
- name: external_only
|
||||
expr: "coalesce(is_internal, 0) = 0 AND coalesce(is_test, 0) = 0"
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
name: contracts
|
||||
table: contracts
|
||||
description: Subscription contracts with ARR, plan, renewal, and status details.
|
||||
grain:
|
||||
- contract_id
|
||||
columns:
|
||||
- name: contract_id
|
||||
type: string
|
||||
- name: account_id
|
||||
type: string
|
||||
- name: parent_account_id
|
||||
type: string
|
||||
- name: plan_id
|
||||
type: string
|
||||
- name: contract_arr_cents
|
||||
type: number
|
||||
- name: booked_arr_cents
|
||||
type: number
|
||||
- name: start_date
|
||||
type: time
|
||||
- name: end_date
|
||||
type: time
|
||||
- name: status
|
||||
type: string
|
||||
- name: renewal_type
|
||||
type: string
|
||||
joins:
|
||||
- to: accounts
|
||||
"on": "account_id = accounts.account_id"
|
||||
relationship: many_to_one
|
||||
measures:
|
||||
- name: contract_count
|
||||
expr: "count(distinct contract_id)"
|
||||
- name: total_arr
|
||||
expr: "sum(contract_arr_cents) / 100.0"
|
||||
filter: "status = 'active'"
|
||||
segments:
|
||||
- name: external_only
|
||||
expr: "coalesce(is_internal, 0) = 0 AND coalesce(is_test, 0) = 0"
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
name: invoices
|
||||
table: invoices
|
||||
description: Billing invoices with payment status and revenue-recognition dates.
|
||||
grain:
|
||||
- invoice_id
|
||||
columns:
|
||||
- name: invoice_id
|
||||
type: string
|
||||
- name: account_id
|
||||
type: string
|
||||
- name: subscription_id
|
||||
type: string
|
||||
- name: invoice_date
|
||||
type: time
|
||||
- name: paid_at
|
||||
type: time
|
||||
- name: status
|
||||
type: string
|
||||
- name: currency
|
||||
type: string
|
||||
joins:
|
||||
- to: accounts
|
||||
"on": "account_id = accounts.account_id"
|
||||
relationship: many_to_one
|
||||
measures:
|
||||
- name: invoice_count
|
||||
expr: "count(*)"
|
||||
- name: paid_invoice_count
|
||||
expr: "count(*)"
|
||||
filter: "status = 'paid'"
|
||||
segments:
|
||||
- name: external_only
|
||||
expr: "coalesce(is_internal, 0) = 0 AND coalesce(is_test, 0) = 0"
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
name: purchase_requests
|
||||
table: purchase_requests
|
||||
description: Procurement workflow requests with requester, status, supplier, and spend fields.
|
||||
grain:
|
||||
- purchase_request_id
|
||||
columns:
|
||||
- name: purchase_request_id
|
||||
type: string
|
||||
- name: account_id
|
||||
type: string
|
||||
- name: requester_user_id
|
||||
type: string
|
||||
- name: created_at
|
||||
type: time
|
||||
- name: status
|
||||
type: string
|
||||
- name: amount_cents
|
||||
type: number
|
||||
- name: supplier_id
|
||||
type: string
|
||||
joins:
|
||||
- to: accounts
|
||||
"on": "account_id = accounts.account_id"
|
||||
relationship: many_to_one
|
||||
measures:
|
||||
- name: request_count
|
||||
expr: "count(*)"
|
||||
- name: approved_spend
|
||||
expr: "sum(amount_cents) / 100.0"
|
||||
filter: "status = 'approved'"
|
||||
segments:
|
||||
- name: external_only
|
||||
expr: "coalesce(is_internal, 0) = 0 AND coalesce(is_test, 0) = 0"
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
name: support_tickets
|
||||
table: support_tickets
|
||||
description: Customer support tickets with severity, category, status, and resolution tracking.
|
||||
grain:
|
||||
- support_ticket_id
|
||||
columns:
|
||||
- name: support_ticket_id
|
||||
type: string
|
||||
- name: account_id
|
||||
type: string
|
||||
- name: requester_user_id
|
||||
type: string
|
||||
- name: severity
|
||||
type: string
|
||||
- name: category
|
||||
type: string
|
||||
- name: status
|
||||
type: string
|
||||
- name: created_at
|
||||
type: time
|
||||
- name: resolved_at
|
||||
type: time
|
||||
- name: owner_user_id
|
||||
type: string
|
||||
joins:
|
||||
- to: accounts
|
||||
"on": "account_id = accounts.account_id"
|
||||
relationship: many_to_one
|
||||
measures:
|
||||
- name: ticket_count
|
||||
expr: "count(*)"
|
||||
- name: open_ticket_count
|
||||
expr: "count(*)"
|
||||
filter: "status != 'resolved'"
|
||||
segments:
|
||||
- name: external_only
|
||||
expr: "coalesce(is_internal, 0) = 0 AND coalesce(is_test, 0) = 0"
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,989 @@
|
|||
tables:
|
||||
account_hierarchy:
|
||||
table: orbit_raw.account_hierarchy
|
||||
columns:
|
||||
- name: account_hierarchy_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for account parent-child relationship records within an organizational hierarchy.
|
||||
- name: parent_account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Identifiers for the higher-level accounts in a parent-child account relationship hierarchy.
|
||||
- name: child_account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for subordinate accounts nested within a parent-child account relationship structure.
|
||||
- name: relationship_type
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Categorizes the structural or financial relationship between parent and child accounts in a hierarchy.
|
||||
- name: effective_start_date
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Start date when a parent-child account relationship becomes active and valid.
|
||||
- name: effective_end_date
|
||||
type: time
|
||||
descriptions:
|
||||
ai: End date when the parent-child account relationship expires or becomes inactive.
|
||||
descriptions:
|
||||
ai: Defines parent-child relationships between accounts, supporting organizational structures like subsidiaries, divisions, and billing groups with time-bound validity periods.
|
||||
joins:
|
||||
- to: stg_account_hierarchy
|
||||
"on": account_hierarchy.account_hierarchy_id = stg_account_hierarchy.account_hierarchy_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
account_owners:
|
||||
table: orbit_raw.account_owners
|
||||
columns:
|
||||
- name: account_owner_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for account ownership records, linking accounts to their designated owners.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers linking ownership records to specific customer or business accounts.
|
||||
- name: owner_user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for individual users assigned as owners of specific accounts.
|
||||
- name: owner_team
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Organizational team responsible for managing or owning the account, such as sales or finance.
|
||||
- name: role
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Business function or responsibility of the owner associated with the account relationship.
|
||||
- name: effective_start_date
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Start date when an account owner's role or assignment becomes active.
|
||||
- name: effective_end_date
|
||||
type: time
|
||||
descriptions:
|
||||
ai: End date when an account owner's assigned role or responsibility expires or becomes inactive.
|
||||
descriptions:
|
||||
ai: Tracks ownership assignments of accounts to users and teams, defining roles and time-bound responsibilities for managing customer relationships.
|
||||
joins:
|
||||
- to: stg_account_owners
|
||||
"on": account_owners.account_owner_id = stg_account_owners.account_owner_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: accounts
|
||||
"on": account_owners.account_id = accounts.account_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
accounts:
|
||||
table: orbit_raw.accounts
|
||||
columns:
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for customer or business accounts, prefixed with "acct_" followed by a numeric sequence.
|
||||
- name: parent_account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Identifier linking a child account to its parent in a hierarchical account structure.
|
||||
- name: account_name
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Display name or label assigned to each customer or business account.
|
||||
- name: domain
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique web domain associated with each customer account for identification and access purposes.
|
||||
- name: industry
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Business sector or vertical market classification of the account organization.
|
||||
- name: sales_region
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Geographic sales territory classification (e.g., North America, EMEA, Asia-Pacific) for account segmentation.
|
||||
- name: size_band
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Categorical classification of account size, segmenting customers by organizational scale (e.g., enterprise, SMB).
|
||||
- name: lifecycle_status
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Current stage of an account in the business relationship lifecycle (e.g., active, churned).
|
||||
- name: is_internal
|
||||
type: boolean
|
||||
descriptions:
|
||||
ai: Boolean flag identifying whether the account belongs to the company internally, rather than an external customer.
|
||||
- name: is_test
|
||||
type: boolean
|
||||
descriptions:
|
||||
ai: Boolean flag identifying whether an account is a test/sandbox entry, excluded from production reporting.
|
||||
- name: created_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp recording when each account record was first created in the system.
|
||||
descriptions:
|
||||
ai: Customer account records for a SaaS platform, tracking organizational clients across industries, regions, and lifecycle stages for account management purposes.
|
||||
joins:
|
||||
- to: int_active_contract_arr
|
||||
"on": accounts.account_id = int_active_contract_arr.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: int_customer_health_signals
|
||||
"on": accounts.account_id = int_customer_health_signals.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: mart_account_segments
|
||||
"on": accounts.account_id = mart_account_segments.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: mart_customer_health
|
||||
"on": accounts.account_id = mart_customer_health.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_account_owners
|
||||
"on": accounts.account_id = stg_account_owners.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_accounts
|
||||
"on": accounts.account_id = stg_accounts.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_contracts
|
||||
"on": accounts.account_id = stg_contracts.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_invoices
|
||||
"on": accounts.account_id = stg_invoices.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_refunds
|
||||
"on": accounts.account_id = stg_refunds.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: account_owners
|
||||
"on": accounts.account_id = account_owners.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: contracts
|
||||
"on": accounts.account_id = contracts.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: invoices
|
||||
"on": accounts.account_id = invoices.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: refunds
|
||||
"on": accounts.account_id = refunds.account_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
activation_events:
|
||||
table: orbit_raw.activation_events
|
||||
columns:
|
||||
- name: activation_event_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique sequential identifiers for individual activation events, formatted with a prefixed numeric string.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for customer or business accounts associated with activation events.
|
||||
- name: user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for users associated with activation events, formatted as sequential user codes.
|
||||
- name: event_type
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Categorizes key activation milestones, such as a user's first login as a requester.
|
||||
- name: event_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp recording when an activation event occurred, stored in UTC with timezone offset.
|
||||
- name: policy_version
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Version identifier of the policy in effect at the time of the activation event.
|
||||
descriptions:
|
||||
ai: Tracks milestone activation events when users first access accounts, capturing policy version context for compliance and onboarding analytics.
|
||||
joins:
|
||||
- to: stg_activation_events
|
||||
"on": activation_events.activation_event_id = stg_activation_events.activation_event_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
approval_events:
|
||||
table: orbit_raw.approval_events
|
||||
columns:
|
||||
- name: approval_event_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for individual approval events, formatted with a sequential numeric suffix.
|
||||
- name: purchase_request_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers linking approval events to their associated purchase requests.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for accounts associated with purchase request approval events.
|
||||
- name: approver_user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for users who reviewed and made approval decisions on purchase requests.
|
||||
- name: decision
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Outcome of an approval action on a purchase request, such as "approved" or "rejected."
|
||||
- name: decided_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp recording when an approver made their decision on a purchase request.
|
||||
descriptions:
|
||||
ai: Records of approval decisions made by designated approvers on purchase requests, tracking who approved what and when for procurement governance.
|
||||
joins:
|
||||
- to: stg_approval_events
|
||||
"on": approval_events.approval_event_id = stg_approval_events.approval_event_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: purchase_requests
|
||||
"on": approval_events.purchase_request_id = purchase_requests.purchase_request_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
arr_movements:
|
||||
table: orbit_raw.arr_movements
|
||||
columns:
|
||||
- name: arr_movement_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for individual ARR movement records, using a sequential prefixed format.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier for the customer account associated with each ARR movement record.
|
||||
- name: parent_account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Identifier linking a child account to its parent in a hierarchical account structure.
|
||||
- name: contract_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier linking ARR movements to their associated customer contracts.
|
||||
- name: movement_date
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Date when an ARR movement or change event occurred, used for revenue tracking.
|
||||
- name: movement_type
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Categorical classification of ARR change events, such as expansion, contraction, churn, or new business.
|
||||
- name: movement_reason
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Categorizes the specific cause driving an ARR change, such as seat expansion or contraction.
|
||||
- name: arr_delta_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Monetary change in Annual Recurring Revenue for a given movement, stored in cents.
|
||||
- name: starting_arr_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Annual Recurring Revenue (ARR) value in cents before the movement was applied.
|
||||
- name: ending_arr_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Annual Recurring Revenue (ARR) balance in cents after applying the recorded movement.
|
||||
descriptions:
|
||||
ai: Tracks changes in Annual Recurring Revenue (ARR) for customer accounts, capturing expansion, contraction, and churn movements to monitor subscription revenue growth.
|
||||
joins:
|
||||
- to: int_parent_account_arr_movements
|
||||
"on": arr_movements.arr_movement_id = int_parent_account_arr_movements.arr_movement_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_arr_movements
|
||||
"on": arr_movements.arr_movement_id = stg_arr_movements.arr_movement_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
contract_discount_terms:
|
||||
table: orbit_raw.contract_discount_terms
|
||||
columns:
|
||||
- name: discount_term_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for individual discount terms associated with contracts, formatted as sequential codes.
|
||||
- name: contract_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier linking discount terms to their associated contracts in the system.
|
||||
- name: discount_type
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Categorizes the business reason or occasion for applying a contract discount.
|
||||
- name: discount_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Fixed discount amount in cents applied to a contract, representing a flat monetary reduction.
|
||||
- name: discount_percent
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Percentage-based discount rate applied to a contract, expressed as a decimal (e.g., 0.10 = 10%).
|
||||
- name: starts_on
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Effective start date for when a contract discount term becomes active.
|
||||
- name: expires_on
|
||||
type: time
|
||||
descriptions:
|
||||
ai: End date when a contract discount term becomes inactive or no longer valid.
|
||||
- name: reason
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Categorical label indicating the rationale or trigger for a contract discount, such as expiration events.
|
||||
descriptions:
|
||||
ai: Tracks discount terms applied to contracts, capturing promotional pricing details including discount amounts, types, validity periods, and expiration reasons for contract management.
|
||||
contracts:
|
||||
table: orbit_raw.contracts
|
||||
columns:
|
||||
- name: contract_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for individual contracts, formatted as sequential alphanumeric codes.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier linking each contract to a specific customer account.
|
||||
- name: parent_account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Identifier linking contracts to a parent/umbrella account, supporting hierarchical account structures.
|
||||
- name: plan_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Identifier referencing the subscription or pricing plan associated with each contract.
|
||||
- name: contract_arr_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Annual Recurring Revenue (ARR) value of the contract, stored in cents.
|
||||
- name: booked_arr_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Committed annual recurring revenue at time of booking, stored in cents.
|
||||
- name: start_date
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Date when a contract becomes active and billing or service obligations begin.
|
||||
- name: end_date
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Expiration date marking when a contract term concludes, used for renewal tracking.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Current lifecycle state of a contract, such as active, expired, or cancelled.
|
||||
- name: renewal_type
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Classification of contract motion type, indicating whether it's new business, a renewal, expansion, or downgrade.
|
||||
descriptions:
|
||||
ai: Tracks customer subscription contracts, capturing revenue commitments, contract terms, and lifecycle stages (new, renewal, expansion) for recurring revenue management.
|
||||
joins:
|
||||
- to: int_active_contract_arr
|
||||
"on": contracts.contract_id = int_active_contract_arr.contract_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_contracts
|
||||
"on": contracts.contract_id = stg_contracts.contract_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: accounts
|
||||
"on": contracts.account_id = accounts.account_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
- to: plans
|
||||
"on": contracts.plan_id = plans.plan_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
invoice_line_items:
|
||||
table: orbit_raw.invoice_line_items
|
||||
columns:
|
||||
- name: invoice_line_item_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for individual line items within invoices, formatted as sequential alphanumeric codes.
|
||||
- name: invoice_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Foreign key referencing the parent invoice, linking line items to their corresponding invoice record.
|
||||
- name: line_item_type
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Categorizes invoice charges by billing type, such as subscriptions, seats, usage, or add-ons.
|
||||
- name: amount_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Monetary value of individual invoice line items, stored in cents (e.g., $20,000).
|
||||
- name: recognized_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp indicating when a line item's revenue was formally recognized for accounting purposes.
|
||||
descriptions:
|
||||
ai: Individual line items within invoices, capturing billing details for subscriptions, seat licenses, and usage-based charges with revenue recognition timestamps.
|
||||
joins:
|
||||
- to: stg_invoice_line_items
|
||||
"on": invoice_line_items.invoice_line_item_id = stg_invoice_line_items.invoice_line_item_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
invoices:
|
||||
table: orbit_raw.invoices
|
||||
columns:
|
||||
- name: invoice_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique sequential identifiers for individual invoice records, prefixed with "inv_".
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers linking invoices to specific customer accounts in the billing system.
|
||||
- name: subscription_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers linking invoices to their associated subscription plans or agreements.
|
||||
- name: invoice_date
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp indicating when an invoice was issued to the account or customer.
|
||||
- name: paid_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp recording when an invoice was successfully paid, stored in UTC.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Current payment state of an invoice, such as paid, pending, or overdue.
|
||||
- name: currency
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Three-letter ISO currency code used for billing and payment processing on invoices.
|
||||
descriptions:
|
||||
ai: Billing records tracking subscription invoices, payment timestamps, and statuses for customer accounts in a subscription-based revenue management system.
|
||||
joins:
|
||||
- to: stg_invoices
|
||||
"on": invoices.invoice_id = stg_invoices.invoice_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_refunds
|
||||
"on": invoices.invoice_id = stg_refunds.invoice_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: accounts
|
||||
"on": invoices.account_id = accounts.account_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
- to: stg_subscriptions
|
||||
"on": invoices.subscription_id = stg_subscriptions.subscription_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
- to: refunds
|
||||
"on": invoices.invoice_id = refunds.invoice_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
plan_segment_mapping:
|
||||
table: orbit_raw.plan_segment_mapping
|
||||
columns:
|
||||
- name: plan_segment_mapping_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for plan-to-segment mapping records, linking insurance plans to specific customer segments.
|
||||
- name: canonical_plan_code
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Standardized plan tier identifiers (e.g., "starter," "growth") used to categorize subscription or pricing plans.
|
||||
- name: size_band
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Business size classification of customers, categorizing them as SMB, mid-market, or enterprise segments.
|
||||
- name: segment
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Business or market segment classification (e.g., self-serve, commercial, enterprise) for plan targeting.
|
||||
- name: effective_start_date
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Start date when a plan-segment mapping record becomes active and valid for use.
|
||||
- name: effective_end_date
|
||||
type: time
|
||||
descriptions:
|
||||
ai: End date when a plan-segment mapping record expires or becomes inactive.
|
||||
descriptions:
|
||||
ai: Maps subscription plans to customer segments and size bands with time-bound effective dates, enabling segment-specific plan routing and pricing logic.
|
||||
joins:
|
||||
- to: stg_plan_segment_mapping
|
||||
"on": plan_segment_mapping.plan_segment_mapping_id = stg_plan_segment_mapping.plan_segment_mapping_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: plans
|
||||
"on": plan_segment_mapping.canonical_plan_code = plans.plan_code
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
plans:
|
||||
table: orbit_raw.plans
|
||||
columns:
|
||||
- name: plan_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for subscription or service plans within the system.
|
||||
- name: plan_code
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Short identifier codes for subscription tiers, used to programmatically reference available plans.
|
||||
- name: plan_name
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Human-readable label for a subscription tier offered to customers.
|
||||
- name: canonical_plan_code
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Standardized plan tier identifiers grouping related plans into core business categories (starter, growth, enterprise).
|
||||
- name: is_retired
|
||||
type: boolean
|
||||
descriptions:
|
||||
ai: Boolean flag indicating whether a plan has been decommissioned or is no longer active.
|
||||
- name: retired_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp indicating when a plan was or will be retired/deactivated.
|
||||
descriptions:
|
||||
ai: Subscription tier configurations defining available service plans (Starter, Growth, Enterprise) for customer pricing and product packaging decisions.
|
||||
joins:
|
||||
- to: int_active_contract_arr
|
||||
"on": plans.plan_id = int_active_contract_arr.plan_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: mart_account_segments
|
||||
"on": plans.plan_code = mart_account_segments.current_plan_code
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: mart_account_segments
|
||||
"on": plans.plan_code = mart_account_segments.normalized_plan_code
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_contracts
|
||||
"on": plans.plan_id = stg_contracts.plan_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_plan_segment_mapping
|
||||
"on": plans.plan_code = stg_plan_segment_mapping.canonical_plan_code
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_plans
|
||||
"on": plans.plan_code = stg_plans.canonical_plan_code
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_plans
|
||||
"on": plans.plan_code = stg_plans.plan_code
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_plans
|
||||
"on": plans.plan_id = stg_plans.plan_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: contracts
|
||||
"on": plans.plan_id = contracts.plan_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: plan_segment_mapping
|
||||
"on": plans.plan_code = plan_segment_mapping.canonical_plan_code
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: plans
|
||||
"on": plans.canonical_plan_code = plans.plan_code
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
- to: plans
|
||||
"on": plans.plan_code = plans.canonical_plan_code
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
purchase_orders:
|
||||
table: orbit_raw.purchase_orders
|
||||
columns:
|
||||
- name: purchase_order_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for purchase orders, formatted with a "po_" prefix and sequential numbering.
|
||||
- name: purchase_request_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier linking a purchase order to its originating purchase request.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier for the account associated with each purchase order transaction.
|
||||
- name: supplier_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers referencing the supplier fulfilling each purchase order.
|
||||
- name: created_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp recording when each purchase order was created or submitted in the system.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Current state of a purchase order in its fulfillment lifecycle (e.g., sent, fulfilled).
|
||||
- name: amount_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Monetary value of purchase orders stored in cents (e.g., 25100 = $251.00).
|
||||
descriptions:
|
||||
ai: Records of formal purchase orders issued to suppliers, tracking procurement transactions from approved purchase requests through fulfillment for financial and supply chain management.
|
||||
joins:
|
||||
- to: stg_purchase_orders
|
||||
"on": purchase_orders.purchase_order_id = stg_purchase_orders.purchase_order_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: purchase_requests
|
||||
"on": purchase_orders.purchase_request_id = purchase_requests.purchase_request_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
- to: stg_suppliers
|
||||
"on": purchase_orders.supplier_id = stg_suppliers.supplier_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
purchase_requests:
|
||||
table: orbit_raw.purchase_requests
|
||||
columns:
|
||||
- name: purchase_request_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for purchase requests, formatted with a "pr_" prefix and sequential numbering.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier for the account associated with each purchase request.
|
||||
- name: requester_user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier of the user who submitted the purchase request.
|
||||
- name: created_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp recording when each purchase request was submitted, stored in UTC with timezone offset.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Current approval state of a purchase request, such as "submitted" or "approved."
|
||||
- name: amount_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Monetary value of purchase requests, stored in cents (e.g., $251–$255).
|
||||
- name: supplier_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier referencing the vendor or supplier associated with a purchase request.
|
||||
descriptions:
|
||||
ai: Tracks employee or user-submitted purchase requests, capturing approval status, requested amounts, and associated suppliers for procurement workflow management.
|
||||
joins:
|
||||
- to: stg_approval_events
|
||||
"on": purchase_requests.purchase_request_id = stg_approval_events.purchase_request_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_purchase_orders
|
||||
"on": purchase_requests.purchase_request_id = stg_purchase_orders.purchase_request_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_purchase_requests
|
||||
"on": purchase_requests.purchase_request_id = stg_purchase_requests.purchase_request_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: approval_events
|
||||
"on": purchase_requests.purchase_request_id = approval_events.purchase_request_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: purchase_orders
|
||||
"on": purchase_requests.purchase_request_id = purchase_orders.purchase_request_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_users
|
||||
"on": purchase_requests.requester_user_id = stg_users.user_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
- to: stg_suppliers
|
||||
"on": purchase_requests.supplier_id = stg_suppliers.supplier_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
refunds:
|
||||
table: orbit_raw.refunds
|
||||
columns:
|
||||
- name: refund_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for individual refund transactions, formatted with a sequential numeric suffix.
|
||||
- name: invoice_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier linking each refund to its originating invoice record.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier linking refunds to the associated customer or business account.
|
||||
- name: amount_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Monetary value of refunds in cents; all samples equal $10,000.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Current state of the refund process, indicating whether the refund was successfully completed.
|
||||
- name: refunded_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp recording when a refund was officially processed and completed.
|
||||
- name: reason
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Categorical explanation for why a refund was issued, such as board reconciliation credits.
|
||||
descriptions:
|
||||
ai: Records of processed financial refunds issued to accounts against invoices, tracking amounts, outcomes, and reasons for reconciliation purposes.
|
||||
joins:
|
||||
- to: stg_refunds
|
||||
"on": refunds.refund_id = stg_refunds.refund_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: accounts
|
||||
"on": refunds.account_id = accounts.account_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
- to: invoices
|
||||
"on": refunds.invoice_id = invoices.invoice_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
sessions:
|
||||
table: orbit_raw.sessions
|
||||
columns:
|
||||
- name: session_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique sequential identifiers for individual user sessions, formatted with zero-padded numeric suffixes.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for customer or business accounts associated with each session.
|
||||
- name: user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for individual users associated with each session record.
|
||||
- name: started_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp marking when a user session began, stored in Pacific Time.
|
||||
- name: duration_seconds
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Length of a user session measured in seconds, used for engagement analysis.
|
||||
- name: is_internal
|
||||
type: boolean
|
||||
descriptions:
|
||||
ai: Boolean flag indicating whether the session originated from internal company users or staff.
|
||||
- name: is_test
|
||||
type: boolean
|
||||
descriptions:
|
||||
ai: Boolean flag identifying whether the session was generated for testing purposes.
|
||||
descriptions:
|
||||
ai: Tracks user session activity across accounts, capturing login events, session duration, and timestamps for monitoring platform engagement and usage patterns.
|
||||
joins:
|
||||
- to: stg_sessions
|
||||
"on": sessions.session_id = stg_sessions.session_id
|
||||
relationship: one_to_many
|
||||
source: inferred
|
||||
- to: stg_users
|
||||
"on": sessions.user_id = stg_users.user_id
|
||||
relationship: many_to_one
|
||||
source: inferred
|
||||
subscriptions:
|
||||
table: orbit_raw.subscriptions
|
||||
columns:
|
||||
- name: subscription_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for individual subscription records, prefixed with "sub_" followed by a sequential number.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers linking subscriptions to their associated customer accounts.
|
||||
- name: contract_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier linking subscriptions to their associated contractual agreements with customers.
|
||||
- name: plan_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Identifier referencing the pricing plan associated with a subscription, linking to a plans table.
|
||||
- name: mrr_cents
|
||||
type: number
|
||||
descriptions:
|
||||
ai: Monthly recurring revenue in cents for each subscription, used for billing and revenue tracking.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Current state of a subscription, such as active, cancelled, or expired.
|
||||
- name: started_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp marking when a subscription became active or billing period began.
|
||||
- name: ended_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp marking when a subscription period expires or terminates, often a future contract end date.
|
||||
- name: cancelled_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp marking when a subscription was cancelled; a far-future date (2099) indicates active, non-cancelled subscriptions.
|
||||
descriptions:
|
||||
ai: Tracks customer subscription lifecycle data, linking accounts to contracts and pricing plans, with billing amounts and active/inactive status periods.
|
||||
supplier_onboarding_events:
|
||||
table: orbit_raw.supplier_onboarding_events
|
||||
columns:
|
||||
- name: supplier_onboarding_event_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for individual supplier onboarding events, formatted with sequential numeric suffixes.
|
||||
- name: supplier_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for suppliers being tracked through the onboarding process.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for accounts associated with supplier onboarding events, formatted as "acct_XXXX".
|
||||
- name: event_type
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Stages in the supplier onboarding lifecycle, tracking progression from invitation to approval.
|
||||
- name: event_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamps recording when supplier onboarding events occurred, stored in Pacific Time zone.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Current state of a supplier onboarding event, such as pending, completed, or blocked.
|
||||
descriptions:
|
||||
ai: Tracks milestone events during supplier onboarding workflows, capturing progression stages, statuses, and timestamps for managing supplier account activation processes.
|
||||
suppliers:
|
||||
table: orbit_raw.suppliers
|
||||
columns:
|
||||
- name: supplier_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for suppliers, formatted with a sequential numeric suffix.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique account identifiers linking suppliers to their associated accounts, formatted with an "acct_" prefix.
|
||||
- name: supplier_name
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Official business name or label assigned to identify each supplier entity.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Current lifecycle stage of a supplier relationship, tracking progression from invitation to active or inactive.
|
||||
- name: created_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamps recording when supplier records were created, stored in Pacific Time zone.
|
||||
descriptions:
|
||||
ai: Tracks vendor/supplier onboarding lifecycle, managing their registration status and account associations from initial invitation through active engagement.
|
||||
support_tickets:
|
||||
table: orbit_raw.support_tickets
|
||||
columns:
|
||||
- name: support_ticket_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for customer support tickets, formatted sequentially with a "ticket_" prefix.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier linking each support ticket to a specific customer account.
|
||||
- name: requester_user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers of users who submitted or initiated the support ticket requests.
|
||||
- name: severity
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Priority level of a support ticket, indicating urgency (e.g., critical, high).
|
||||
- name: category
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Classification of the support issue type, such as approval routing workflows or processes.
|
||||
- name: status
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Current state of a support ticket in its resolution workflow (e.g., open, closed).
|
||||
- name: created_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp recording when a support ticket was initially submitted or opened.
|
||||
- name: resolved_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp indicating when a ticket was resolved; future sentinel date (2099) likely represents unresolved tickets.
|
||||
- name: owner_user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier of the support agent or staff member assigned to handle the ticket.
|
||||
descriptions:
|
||||
ai: Tracks customer support requests, their severity, status, and ownership for managing issue resolution workflows across accounts.
|
||||
users:
|
||||
table: orbit_raw.users
|
||||
columns:
|
||||
- name: user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers for individual user records, formatted with a sequential numeric suffix.
|
||||
- name: account_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifier linking users to their associated organizational accounts, formatted with an "acct_" prefix.
|
||||
- name: email
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Email addresses of users, formatted with user and customer identifiers for multi-tenant accounts.
|
||||
- name: role
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Functional access level or permission type assigned to each user within the system.
|
||||
- name: is_requester
|
||||
type: boolean
|
||||
descriptions:
|
||||
ai: Boolean flag indicating whether the user has the role of a requester within the system.
|
||||
- name: is_internal
|
||||
type: boolean
|
||||
descriptions:
|
||||
ai: Boolean flag indicating whether a user belongs to the internal organization or team.
|
||||
- name: is_test
|
||||
type: boolean
|
||||
descriptions:
|
||||
ai: Boolean flag identifying whether the user account is a test or dummy entry.
|
||||
- name: created_at
|
||||
type: time
|
||||
descriptions:
|
||||
ai: Timestamp recording when a user account was first created in the system.
|
||||
- name: slack_user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers linking users to their corresponding Slack workspace accounts for integration purposes.
|
||||
- name: looker_user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers linking users to their corresponding accounts in the Looker analytics platform.
|
||||
- name: notion_user_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers linking users to their corresponding Notion workspace accounts.
|
||||
- name: drive_owner_id
|
||||
type: string
|
||||
descriptions:
|
||||
ai: Unique identifiers linking users to their associated Google Drive owner accounts.
|
||||
descriptions:
|
||||
ai: External customer user accounts with role-based access controls, integrated across Slack and Looker platforms for workflow and analytics management.
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
name: large_contract_requesters
|
||||
source_type: sql
|
||||
sql: |-
|
||||
select account.account_name,
|
||||
requester.email as requester_email,
|
||||
activity.action_type,
|
||||
activity.action_date,
|
||||
round(segment.contract_arr_cents / 100.0, 0) as contract_arr_usd
|
||||
from orbit_analytics.int_procurement_qualifying_actions activity
|
||||
join orbit_raw.accounts account on account.account_id = activity.account_id
|
||||
join orbit_raw.users requester on requester.user_id = activity.user_id
|
||||
left join orbit_analytics.mart_account_segments segment on segment.account_id = activity.account_id
|
||||
order by activity.action_date desc, segment.contract_arr_cents desc nulls last
|
||||
limit 25
|
||||
grain:
|
||||
- action_date
|
||||
- requester_email
|
||||
- action_type
|
||||
columns:
|
||||
- name: account_name
|
||||
type: string
|
||||
descriptions:
|
||||
user: Name of the account
|
||||
- name: requester_email
|
||||
type: string
|
||||
descriptions:
|
||||
user: Email of the requester
|
||||
- name: action_type
|
||||
type: string
|
||||
descriptions:
|
||||
user: Type of qualifying procurement action
|
||||
- name: action_date
|
||||
type: time
|
||||
role: time
|
||||
descriptions:
|
||||
user: Date the action occurred
|
||||
- name: contract_arr_usd
|
||||
type: number
|
||||
descriptions:
|
||||
user: Contract ARR in USD
|
||||
joins: []
|
||||
measures: []
|
||||
descriptions:
|
||||
user: Recent procurement actions by requesters on large contracts. Joins qualifying procurement actions to account names, requester emails, and contract ARR. Used to identify active requesters on high-value accounts.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
name: mart_account_activity
|
||||
measures:
|
||||
- name: avg_pre_policy_activation_rate
|
||||
expr: avg(pre_policy_30_day_activation_rate)
|
||||
description: Average 30-day activation rate for the pre-policy cohort (before 2026-01-15).
|
||||
- name: avg_post_policy_activation_rate
|
||||
expr: avg(post_policy_30_day_activation_rate)
|
||||
description: Average 30-day activation rate for the post-policy cohort (on or after 2026-01-15).
|
||||
descriptions:
|
||||
user: "Pre/post-policy 30-day activation rates. Source: dbt mart_account_activity. Compares activation counts across the Jan 2026 policy boundary."
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
name: mart_account_segments
|
||||
measures:
|
||||
- name: total_contract_arr_cents
|
||||
expr: sum(contract_arr_cents)
|
||||
description: Total active-contract ARR in cents across all accounts.
|
||||
- name: active_contract_arr_cents
|
||||
expr: sum(contract_arr_cents)
|
||||
filter: contract_status = 'active'
|
||||
description: Active-contract ARR in cents, filtered to accounts with an active contract status.
|
||||
- name: count_accounts
|
||||
expr: count(distinct account_id)
|
||||
description: Distinct count of accounts in the segment.
|
||||
- name: count_active_contract_accounts
|
||||
expr: count(distinct account_id)
|
||||
filter: contract_status = 'active'
|
||||
description: Distinct count of accounts with an active contract.
|
||||
descriptions:
|
||||
user: "Per-account segment and active-contract ARR. Source: dbt mart_account_segments."
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
name: mart_arr_daily
|
||||
measures:
|
||||
- name: total_arr_cents
|
||||
expr: sum(arr_cents)
|
||||
description: Sum of ARR in cents across all snapshot dates (use with a date filter to get point-in-time ARR).
|
||||
- name: latest_arr_cents
|
||||
expr: max(arr_cents)
|
||||
description: Most recent ARR value in cents (max across dates in the filtered window).
|
||||
descriptions:
|
||||
user: "Daily ARR snapshot. Source: dbt mart_arr_daily. One row per metric_date with global ARR in cents."
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
name: mart_customer_health
|
||||
measures:
|
||||
- name: count_accounts
|
||||
expr: count(distinct account_id)
|
||||
description: Total distinct accounts in the health snapshot.
|
||||
- name: count_active_customers
|
||||
expr: count(distinct account_id)
|
||||
filter: is_active_customer = true
|
||||
description: Distinct count of accounts flagged as active customers.
|
||||
- name: count_high_risk_accounts
|
||||
expr: count(distinct account_id)
|
||||
filter: risk_level = 'high'
|
||||
description: Distinct count of accounts with high risk level (open critical tickets and/or recent procurement activity).
|
||||
- name: count_medium_risk_accounts
|
||||
expr: count(distinct account_id)
|
||||
filter: risk_level = 'medium'
|
||||
description: Distinct count of accounts with medium risk level.
|
||||
- name: count_accounts_with_high_ticket
|
||||
expr: count(distinct account_id)
|
||||
filter: has_unresolved_high_ticket = true
|
||||
description: Distinct count of accounts with at least one unresolved high-severity support ticket.
|
||||
- name: count_accounts_with_recent_procurement
|
||||
expr: count(distinct account_id)
|
||||
filter: has_recent_procurement_activity = true
|
||||
description: Distinct count of accounts with recent procurement activity.
|
||||
descriptions:
|
||||
user: "Per-account risk signals as of a snapshot date. Source: dbt mart_customer_health."
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
name: mart_nrr_quarterly
|
||||
measures:
|
||||
- name: avg_net_revenue_retention
|
||||
expr: avg(net_revenue_retention)
|
||||
description: Average NRR (net revenue retention) across quarters and segments.
|
||||
- name: total_expansion_arr_cents
|
||||
expr: sum(expansion_arr_cents)
|
||||
description: Total expansion ARR in cents across all quarters and segments.
|
||||
- name: total_contraction_arr_cents
|
||||
expr: sum(contraction_arr_cents)
|
||||
description: Total contraction ARR in cents across all quarters and segments.
|
||||
- name: total_churned_arr_cents
|
||||
expr: sum(churned_arr_cents)
|
||||
description: Total churned ARR in cents across all quarters and segments.
|
||||
- name: total_starting_arr_cents
|
||||
expr: sum(starting_arr_cents)
|
||||
description: Total starting ARR in cents at the beginning of each quarter.
|
||||
descriptions:
|
||||
user: "Quarterly NRR per segment. Source: dbt mart_nrr_quarterly. Enterprise-focused in current dataset."
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
name: mart_procurement_activity
|
||||
measures:
|
||||
- name: total_active_requesters
|
||||
expr: sum(active_requesters)
|
||||
description: Total count of active requesters summed across all weeks.
|
||||
- name: avg_weekly_active_requesters
|
||||
expr: avg(active_requesters)
|
||||
description: Average number of active requesters per week (golden week metric).
|
||||
- name: avg_contract_arr_threshold_cents
|
||||
expr: avg(contract_arr_threshold_cents)
|
||||
description: Average contract ARR threshold in cents used to qualify large active contracts.
|
||||
descriptions:
|
||||
user: "Weekly procurement KPI. Source: dbt mart_procurement_activity. Tracks active requesters on large active contracts."
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
name: mart_retention_movement_breakout
|
||||
measures:
|
||||
- name: total_expansion_arr_cents
|
||||
expr: sum(expansion_arr_cents)
|
||||
description: Total expansion ARR in cents
|
||||
- name: total_contraction_arr_cents
|
||||
expr: sum(contraction_arr_cents)
|
||||
description: Total contraction ARR in cents (includes discount expiration contraction)
|
||||
- name: total_churned_arr_cents
|
||||
expr: sum(churned_arr_cents)
|
||||
description: Total churned ARR in cents
|
||||
- name: parent_account_count
|
||||
expr: sum(parent_account_count)
|
||||
description: Total number of parent accounts affected by the movement
|
||||
- name: expansion_arr_millions
|
||||
expr: round(sum(expansion_arr_cents) / 100000000.0, 3)
|
||||
description: Expansion ARR in millions of dollars
|
||||
- name: contraction_arr_millions
|
||||
expr: round(sum(contraction_arr_cents) / 100000000.0, 3)
|
||||
description: Contraction ARR in millions of dollars
|
||||
- name: churned_arr_millions
|
||||
expr: round(sum(churned_arr_cents) / 100000000.0, 3)
|
||||
description: Churned ARR in millions of dollars
|
||||
descriptions:
|
||||
user: Quarterly ARR movement breakout by segment, movement_type (expansion/contraction/churn), and movement_reason. One row per quarter × segment × movement_type × movement_reason. Contraction includes discount expirations (is_discount_expiration_contraction). Used for NRR waterfall analysis.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
name: mart_revenue_daily
|
||||
measures:
|
||||
- name: total_gross_revenue_cents
|
||||
expr: sum(gross_revenue_cents)
|
||||
description: Total gross invoice revenue in cents across all days.
|
||||
- name: total_credits_cents
|
||||
expr: sum(credits_cents)
|
||||
description: Total credits applied in cents across all days.
|
||||
- name: total_refunds_cents
|
||||
expr: sum(refunds_cents)
|
||||
description: Total refunds issued in cents across all days.
|
||||
- name: total_net_revenue_cents
|
||||
expr: sum(net_revenue_cents)
|
||||
description: Total net revenue in cents (gross minus credits and refunds) across all days.
|
||||
- name: count_reconciled_days
|
||||
expr: count(*)
|
||||
filter: reconciliation_check = true
|
||||
description: Number of days where reconciliation_check passed (gross - credits - refunds = net).
|
||||
descriptions:
|
||||
user: "Daily gross-to-net revenue. Source: dbt mart_revenue_daily. Aggregate-only — no account dimension."
|
||||
|
|
@ -91,22 +91,17 @@ describe('demo assets', () => {
|
|||
expect(manifest.sources.bi.explores).toBeGreaterThanOrEqual(2);
|
||||
expect(manifest.sources.bi.dashboards).toBeGreaterThanOrEqual(2);
|
||||
expect(manifest.sources.notion.pages).toBeGreaterThanOrEqual(5);
|
||||
expect(manifest.generated.semanticLayer.sourceCount).toBeGreaterThanOrEqual(5);
|
||||
expect(manifest.generated.knowledge.pageCount).toBeGreaterThanOrEqual(10);
|
||||
expect(manifest.generated.semanticLayer.sourceCount).toBeGreaterThanOrEqual(40);
|
||||
expect(manifest.generated.knowledge.pageCount).toBeGreaterThanOrEqual(20);
|
||||
expect(manifest.generated.links.linkCount).toBeGreaterThanOrEqual(10);
|
||||
|
||||
const dbStat = await stat(packagedDemoAssetPath('demo.db'));
|
||||
expect(dbStat.size).toBeGreaterThan(0);
|
||||
expect(dbStat.size).toBeLessThan(10 * 1024 * 1024);
|
||||
|
||||
await expect(access(packagedDemoAssetPath('raw-sources/warehouse/accounts.csv'))).resolves.toBeUndefined();
|
||||
await expect(access(packagedDemoAssetPath('raw-sources/dbt/schema.yml'))).resolves.toBeUndefined();
|
||||
await expect(access(packagedDemoAssetPath('raw-sources/bi/revenue_exec.dashboard.lookml'))).resolves.toBeUndefined();
|
||||
await expect(access(packagedDemoAssetPath('raw-sources/notion/revenue-reporting-policy.md'))).resolves.toBeUndefined();
|
||||
expect(manifest.generated.semanticLayer.path).toBe('semantic-layer/orbit_demo');
|
||||
|
||||
await expect(access(packagedDemoAssetPath('semantic-layer/orbit_demo/accounts.yaml'))).resolves.toBeUndefined();
|
||||
await expect(access(packagedDemoAssetPath('knowledge/global/arr-contract-first.md'))).resolves.toBeUndefined();
|
||||
await expect(access(packagedDemoAssetPath('semantic-layer/dbt-main/mart_arr_daily.yaml'))).resolves.toBeUndefined();
|
||||
await expect(access(packagedDemoAssetPath('semantic-layer/postgres-warehouse/mart_account_activity.yaml'))).resolves.toBeUndefined();
|
||||
await expect(access(packagedDemoAssetPath('knowledge/global/orbit-company-overview.md'))).resolves.toBeUndefined();
|
||||
await expect(access(packagedDemoAssetPath('links/provenance.json'))).resolves.toBeUndefined();
|
||||
await expect(access(packagedDemoAssetPath('reports/seeded-demo-report.json'))).resolves.toBeUndefined();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -45,14 +45,9 @@ const REQUIRED_SEEDED_ASSET_PATHS = [
|
|||
'demo.db',
|
||||
'manifest.json',
|
||||
DEMO_REPLAY_FILE,
|
||||
join('raw-sources', 'warehouse', 'accounts.csv'),
|
||||
join('raw-sources', 'dbt', 'schema.yml'),
|
||||
join('raw-sources', 'bi', 'revenue_exec.dashboard.lookml'),
|
||||
join('raw-sources', 'notion', 'revenue-reporting-policy.md'),
|
||||
join('semantic-layer', 'orbit_demo', 'accounts.yaml'),
|
||||
join('knowledge', 'global', 'arr-contract-first.md'),
|
||||
join('links', 'provenance.json'),
|
||||
join('reports', 'seeded-demo-report.json'),
|
||||
join('semantic-layer', 'dbt-main', 'mart_arr_daily.yaml'),
|
||||
join('semantic-layer', 'postgres-warehouse', 'mart_account_activity.yaml'),
|
||||
join('knowledge', 'global', 'orbit-company-overview.md'),
|
||||
] as const;
|
||||
|
||||
function assetDir(): string {
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ describe('seeded demo inspect contract', () => {
|
|||
notion: { label: 'Notion', path: 'raw-sources/notion', pageCount: 8 },
|
||||
},
|
||||
generatedOutputs: {
|
||||
semanticLayer: { path: 'semantic-layer/orbit_demo', manifestSourceCount: 6, fileCount: 6 },
|
||||
knowledge: { path: 'knowledge/global', manifestPageCount: 10, fileCount: 10 },
|
||||
semanticLayer: { path: 'semantic-layer', manifestSourceCount: 46, fileCount: 46 },
|
||||
knowledge: { path: 'knowledge/global', manifestPageCount: 28, fileCount: 28 },
|
||||
links: { path: 'links/provenance.json', manifestLinkCount: 23, linkCount: 23 },
|
||||
reports: { primaryPath: 'reports/seeded-demo-report.json', fileCount: 1 },
|
||||
replays: { primaryPath: 'replays/replay.memory-flow.v1.json', latestPath: 'replays/latest.memory-flow.v1.json' },
|
||||
|
|
@ -83,8 +83,8 @@ describe('seeded demo inspect contract', () => {
|
|||
expect(output).toContain('dbt: 3 models, 8 source tables');
|
||||
expect(output).toContain('BI: 5 explores, 2 dashboards');
|
||||
expect(output).toContain('Notion: 8 pages');
|
||||
expect(output).toContain('Semantic-layer sources: 6 manifest, 6 files');
|
||||
expect(output).toContain('Knowledge pages: 10 manifest, 10 files');
|
||||
expect(output).toContain('Semantic-layer sources: 46 manifest, 46 files');
|
||||
expect(output).toContain('Knowledge pages: 28 manifest, 28 files');
|
||||
expect(output).toContain('Evidence links: 23 manifest, 23 links');
|
||||
expect(output).toContain('Report: reports/seeded-demo-report.json');
|
||||
expect(output).toContain('Replay: replays/replay.memory-flow.v1.json');
|
||||
|
|
|
|||
|
|
@ -71,12 +71,9 @@ const REQUIRED_SEEDED_PROJECT_PATHS = [
|
|||
'state.sqlite',
|
||||
'manifest.json',
|
||||
join('replays', 'replay.memory-flow.v1.json'),
|
||||
join('raw-sources', 'warehouse', 'accounts.csv'),
|
||||
join('raw-sources', 'dbt', 'schema.yml'),
|
||||
join('raw-sources', 'bi', 'revenue_exec.dashboard.lookml'),
|
||||
join('raw-sources', 'notion', 'revenue-reporting-policy.md'),
|
||||
join('semantic-layer', 'orbit_demo', 'accounts.yaml'),
|
||||
join('knowledge', 'global', 'arr-contract-first.md'),
|
||||
join('semantic-layer', 'dbt-main', 'mart_arr_daily.yaml'),
|
||||
join('semantic-layer', 'postgres-warehouse', 'mart_account_activity.yaml'),
|
||||
join('knowledge', 'global', 'orbit-company-overview.md'),
|
||||
join('links', 'provenance.json'),
|
||||
join('reports', 'seeded-demo-report.json'),
|
||||
] as const;
|
||||
|
|
|
|||
|
|
@ -19,11 +19,9 @@ describe('demo seeded mode', () => {
|
|||
await expect(access(join(projectDir, 'demo.db'))).resolves.toBeUndefined();
|
||||
await expect(access(join(projectDir, 'ktx.yaml'))).resolves.toBeUndefined();
|
||||
await expect(access(join(projectDir, 'manifest.json'))).resolves.toBeUndefined();
|
||||
await expect(access(join(projectDir, 'semantic-layer/orbit_demo/accounts.yaml'))).resolves.toBeUndefined();
|
||||
await expect(access(join(projectDir, 'knowledge/global/arr-contract-first.md'))).resolves.toBeUndefined();
|
||||
await expect(access(join(projectDir, 'raw-sources/dbt/schema.yml'))).resolves.toBeUndefined();
|
||||
await expect(access(join(projectDir, 'raw-sources/bi/revenue_exec.dashboard.lookml'))).resolves.toBeUndefined();
|
||||
await expect(access(join(projectDir, 'raw-sources/notion/revenue-reporting-policy.md'))).resolves.toBeUndefined();
|
||||
await expect(access(join(projectDir, 'semantic-layer/dbt-main/mart_arr_daily.yaml'))).resolves.toBeUndefined();
|
||||
await expect(access(join(projectDir, 'semantic-layer/postgres-warehouse/mart_account_activity.yaml'))).resolves.toBeUndefined();
|
||||
await expect(access(join(projectDir, 'knowledge/global/orbit-company-overview.md'))).resolves.toBeUndefined();
|
||||
await expect(access(join(projectDir, 'links/provenance.json'))).resolves.toBeUndefined();
|
||||
await expect(access(join(projectDir, 'reports/seeded-demo-report.json'))).resolves.toBeUndefined();
|
||||
});
|
||||
|
|
@ -88,8 +86,8 @@ describe('demo seeded mode', () => {
|
|||
|
||||
it('SL YAML validates correctly', async () => {
|
||||
await ensureSeededDemoProject({ projectDir, force: false });
|
||||
const slYaml = await readFile(join(projectDir, 'semantic-layer/orbit_demo/accounts.yaml'), 'utf-8');
|
||||
expect(slYaml).toContain('name: accounts');
|
||||
const slYaml = await readFile(join(projectDir, 'semantic-layer/dbt-main/mart_arr_daily.yaml'), 'utf-8');
|
||||
expect(slYaml).toContain('name: mart_arr_daily');
|
||||
expect(slYaml).toContain('grain:');
|
||||
expect(slYaml).toContain('columns:');
|
||||
expect(slYaml).toContain('measures:');
|
||||
|
|
@ -98,11 +96,11 @@ describe('demo seeded mode', () => {
|
|||
|
||||
it('wiki pages have valid frontmatter', async () => {
|
||||
await ensureSeededDemoProject({ projectDir, force: false });
|
||||
const wiki = await readFile(join(projectDir, 'knowledge/global/arr-contract-first.md'), 'utf-8');
|
||||
const wiki = await readFile(join(projectDir, 'knowledge/global/orbit-company-overview.md'), 'utf-8');
|
||||
expect(wiki).toContain('---');
|
||||
expect(wiki).toContain('summary:');
|
||||
expect(wiki).toContain('tags:');
|
||||
expect(wiki).toContain('sl_refs:');
|
||||
expect(wiki).toContain('refs:');
|
||||
expect(wiki).toContain('usage_mode: auto');
|
||||
});
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue