Initial open-source release

This commit is contained in:
Andrey Avtomonov 2026-05-10 23:12:26 +02:00
commit 1a42152e6f
1199 changed files with 257054 additions and 0 deletions

72
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,72 @@
name: KLO CI
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: klo-ci-${{ github.ref }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: "24"
cache: "pnpm"
cache-dependency-path: "pnpm-lock.yaml"
- name: Install TypeScript dependencies
run: pnpm install --frozen-lockfile
- name: Run TypeScript checks
run: pnpm run check
- name: Setup Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.13"
- name: Setup uv
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Install Python dependencies
run: uv sync --all-packages
- name: Run Python checks
run: uv run pytest
- name: Build and verify package artifacts
run: pnpm run artifacts:check
- name: Upload package artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: klo-package-artifacts-${{ github.sha }}
path: |
dist/artifacts/manifest.json
dist/artifacts/npm/*.tgz
dist/artifacts/python/*.whl
dist/artifacts/python/*.tar.gz
if-no-files-found: error
retention-days: 7

61
.gitignore vendored Normal file
View file

@ -0,0 +1,61 @@
# Python
__pycache__/
*.py[cod]
*.so
.Python
.venv/
venv/
env/
build/
dist/
*.egg-info/
.pytest_cache/
.coverage
coverage/
htmlcov/
.ruff_cache/
.mypy_cache/
.hypothesis/
# Secrets and local environment
.env
.env.*
!.env.example
*.pem
*.key
*.p12
*.crt
*.cert
# Node
node_modules/
.npm/
.pnpm-store/
*.tsbuildinfo
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# Local project runtime state
.klo/
*.db
*.sqlite
*.sqlite3
!packages/cli/assets/demo/orbit/demo.db
!packages/context/test/fixtures/relationship-benchmarks/**/data.sqlite
# Private local agent overlays
.agents/
.claude/
# Editors and OS files
.idea/
.vscode/
.DS_Store
.DS_Store?
._*
*.swp
*.swo
*~

251
AGENTS.md Normal file
View file

@ -0,0 +1,251 @@
# KTX Development Notes
KTX is a standalone open-source context layer for database agents. These
instructions apply to all agents working in this repository (Codex, Claude,
Gemini, and similar tools). Do not assume an external app server, frontend,
database migrations, ORPC contracts, or `python-service/` layout exist here.
## Critical Rules
### Absolute Requirements
- **MUST**: Use the active agent's task tracker for tasks with 3+ steps or
complex operations (`TodoWrite` in Claude, `update_plan` in Codex).
- **MUST**: Read files before editing them.
- **MUST**: Complete all tracked tasks before finishing.
- **MUST**: Activate `.venv` before running Python code when a local virtualenv
exists. If no `.venv` exists, use `uv run ...` from the relevant project root.
- **MUST**: After modifying Python files, run the relevant Python tests and run
`uv run pre-commit run --files [FILES]` when a pre-commit config exists. If
pre-commit cannot run because config or tool versions are missing, state that
explicitly and run the closest available checks.
- **MUST**: Remove dead code; do not leave commented-out code, unused wrappers,
or empty directories.
- **MUST**: Keep package/public API changes intentional. Do not add compatibility
wrappers for old KLO names unless the user explicitly asks for a migration
bridge.
### Absolute Prohibitions
- **MUST NOT**: Use raw `pip`; use `uv`.
- **MUST NOT**: Use `npm` or `bun`; use `pnpm`.
- **MUST NOT**: Run destructive git cleanup commands (`git clean`,
`git reset --hard`, `git checkout .`) unless the user explicitly requested
that exact operation.
- **MUST NOT**: Run `git stash`, `git stash pop`, `git stash apply`, or
`git stash drop` without explicit user instruction. Prefer a branch plus
commit when the user asks to save work in progress.
- **MUST NOT**: Reintroduce external app conventions such as ORPC contracts,
NestJS controllers, frontend routes, `routeTree.gen.ts`, or app database
migration commands unless those systems are intentionally added to KTX later.
### Language Convention
- **MUST**: Absolute requirement, never deviate.
- **MUST NOT**: Absolute prohibition.
- **SHOULD**: Strong recommendation, deviate only with good reason.
- **MAY**: Optional, at agent's discretion.
## Priority Hierarchy
When rules conflict, follow this order:
1. Safety and user intent
2. Correctness: code works and verification passes
3. Single source of truth and DRY design
4. Code quality: types, readable boundaries, focused modules
5. Performance where it matters
## Repository Shape
KTX is a pnpm + uv workspace.
- TypeScript packages: `packages/*`
- CLI package: `packages/cli`
- Core context package: `packages/context`
- LLM package: `packages/llm`
- Database connectors: `packages/connector-*`
- Python semantic layer: `python/klo-sl`
- Python daemon: `python/klo-daemon`
- Examples and fixtures: `examples/`
- Workspace scripts: `scripts/`
- Local agent skills are private overlays. Do not commit `.agents/` or
`.claude/` to this public repository.
Some package names still contain `klo` during the split. Do not mass-rename
symbols, package names, paths, or docs to `ktx` unless the task asks for that
rename.
## Quick Commands
### TypeScript Workspace
```bash
pnpm install
pnpm run build
pnpm run type-check
pnpm run test
pnpm run check
pnpm --filter @klo/cli run smoke
pnpm --filter './packages/*' run build
pnpm --filter './packages/*' run test
pnpm --filter './packages/*' run type-check
```
### Python Workspace
```bash
uv sync --all-groups
uv run pytest -q
uv run pytest python/klo-sl/tests -q
uv run pytest python/klo-daemon/tests -q
uv run pre-commit run --files [FILES]
```
If `pyproject.toml` pins a newer `uv` than the local binary, do not edit the
pin just to make checks pass. Report the version mismatch and run checks that
do not require changing project configuration.
### CLI and Release Checks
```bash
pnpm run setup:dev
pnpm run link:dev
pnpm run artifacts:verify
pnpm run release:readiness
pnpm run release:published-smoke
```
## Verification After Changes
Choose the smallest checks that cover the changed surface, then broaden when
shared contracts or package exports are affected.
- TypeScript package code: `pnpm --filter <package> run type-check` and
`pnpm --filter <package> run test`
- Cross-package TypeScript changes: `pnpm run type-check` and `pnpm run test`
- Build/export changes: `pnpm run build`
- Workspace scripts: `node --test scripts/*.test.mjs` or the specific script
test file
- Python semantic layer: `uv run pytest python/klo-sl/tests -q`
- Python daemon: `uv run pytest python/klo-daemon/tests -q`
- Python files: also run `uv run pre-commit run --files [FILES]` when
pre-commit is configured
For test suites that take a while, capture full output once and inspect that
file instead of rerunning to apply different filters:
```bash
pnpm run test 2>&1 | tee /tmp/ktx-test-output.log
```
## TypeScript Standards
- Use Node 22+ and pnpm workspace commands.
- Keep packages ESM (`"type": "module"`) and preserve `NodeNext` TypeScript
semantics.
- Prefer strict types over `any`; do not use `as unknown as`.
- Keep package exports, `types`, and built `dist` expectations aligned when
changing public APIs.
- Use `zod` schemas for runtime validation at CLI/config/API boundaries.
- Keep connector packages thin: connector-specific scanning/auth behavior
belongs in `packages/connector-*`; shared types and orchestration belong in
`packages/context`.
- Avoid circular package dependencies. Shared code should move to the lowest
sensible package, not be duplicated across connectors.
- Do not manually edit generated or built output under `dist/`; edit source and
rebuild.
### Zod Naming Convention
```typescript
const userSchema = z.object({
id: z.uuid(),
email: z.string().email(),
name: z.string(),
});
type User = z.infer<typeof userSchema>;
```
Runtime schemas use `camelCase` plus the `Schema` suffix. Static inferred types
use `PascalCase` without the suffix.
## Python Standards
- Use `pyproject.toml`; do not add `requirements.txt`.
- Use type hints for new and changed Python code.
- Use `pathlib` instead of `os.path`.
- Use `logger.exception()` when catching and logging exceptions.
- Prefer explicit exception types over broad `except Exception`.
- Keep `python/klo-sl` focused on semantic-layer planning and SQL generation.
- Keep `python/klo-daemon` focused on portable daemon/API behavior around the
semantic layer.
### SQL and Structured Parsing
- Prefer AST-based parsing over regex for structured input.
- For SQL, use `sqlglot`; it is already a dependency.
- In `python/klo-sl`, follow the local `python/klo-sl/AGENTS.md` guidance:
parse expressions with sqlglot, quote reserved identifiers before parsing,
and generate postgres-shaped SQL before final dialect transpilation.
- Regex may be used for non-structural sanitization, but not to interpret SQL
structure.
## Documentation and Specs
- Keep public documentation in `README.md`, package READMEs, and example
READMEs unless the repository intentionally adds a public docs tree.
- Prefer concrete commands, file paths, and acceptance criteria over broad
prose.
- When documenting examples, ensure referenced files and commands exist in the
standalone KTX tree.
- Remove or rewrite stale external app references unless the doc is explicitly
historical.
## LLM and Prompt Development
When creating or modifying agent prompts, system prompts, tool descriptions, or
skills:
- Use XML tags for major structure when it helps model reliability:
`<role>`, `<workflow>`, `<examples>`, `<success_criteria>`.
- Use positive framing: tell the model what to do.
- Keep prompts compact and avoid duplicating the same rule in multiple places.
- Include 1-3 concrete examples when examples materially reduce ambiguity.
- Use AI SDK v6 patterns for TypeScript LLM work.
- Use the local `ai-sdk` skill when working with AI SDK code.
## Context7 and External Docs
- Use Context7 when official, current library documentation would materially
reduce risk.
- Context7 "Monthly quota exceeded" errors are often transient. Retry before
assuming the quota is exhausted.
- If Context7 remains unavailable, state the blocked lookup and use the best
available local/source documentation.
## When to Ask vs Act
Act without asking when:
- Following explicit user instructions
- Running verification
- Fixing clear bugs or tool failures within the requested scope
Ask first when:
- Requirements are ambiguous
- The next step is destructive or would discard user work
- A breaking public API decision is not already implied by the task
- Missing credentials, live services, or external accounts are required
## Git and Worktree Safety
- The worktree may contain unrelated user changes. Do not revert files you did
not change unless explicitly asked.
- Before committing, inspect `git status --short` and commit only intended
files.
- Do not commit ignored dependency/build artifacts such as `node_modules/`,
`.venv/`, `dist/`, coverage output, or local databases unless the task
explicitly concerns packaged artifacts.

1
CLAUDE.md Symbolic link
View file

@ -0,0 +1 @@
AGENTS.md

1
GEMINI.md Symbolic link
View file

@ -0,0 +1 @@
AGENTS.md

202
LICENSE Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

270
README.md Normal file
View file

@ -0,0 +1,270 @@
# KLO
KLO is a workspace-first context layer for database agents. It stores warehouse
memory in a project directory, generates and validates semantic-layer YAML,
indexes knowledge, scans database schemas, and exposes the result through a CLI
and MCP server.
KLO projects are plain files: YAML, Markdown, SQLite state, and generated
artifacts. You can inspect them, commit them, and serve them to any MCP client.
## What KLO provides
- Durable warehouse memory with semantic-layer sources and knowledge pages.
- Native scan connectors for SQLite, Postgres, MySQL, ClickHouse, SQL Server,
BigQuery, Snowflake, and PostHog.
- Agentic ingest with provenance links, tool transcripts, and replay metadata.
- Local semantic-layer query planning and optional query execution.
- A stdio MCP server with tools for connections, knowledge, semantic-layer
sources, ingest reports, and replay.
## Quick start
Run the pre-seeded demo from the repository root:
```bash
pnpm install
pnpm run setup:dev
pnpm run klo -- setup demo --no-input
pnpm run klo -- setup demo inspect
```
The default demo uses packaged sample data and prebuilt context. It does not
require API keys, network access, or an LLM provider.
To replay the packaged ingest run, use:
```bash
pnpm run klo -- setup demo --mode replay --no-input
```
To run the full agentic demo with an LLM provider, set a provider key for the
current process:
```bash
ANTHROPIC_API_KEY=$YOUR_ANTHROPIC_API_KEY \
pnpm run klo -- setup demo --mode full --no-input
```
Interactive full-demo setup can prompt for a provider key without writing the
key to `klo.yaml`.
## Build a local project
Create a project from the repository root:
```bash
uv sync --all-packages
source .venv/bin/activate
PROJECT_DIR="$(mktemp -d)/klo-demo"
pnpm run klo -- init "$PROJECT_DIR" --name klo-demo
```
Create a SQLite warehouse:
```bash
python - "$PROJECT_DIR/demo.db" <<'PY'
import sqlite3
import sys
conn = sqlite3.connect(sys.argv[1])
conn.executescript("""
DROP TABLE IF EXISTS accounts;
CREATE TABLE accounts (
account_id INTEGER PRIMARY KEY,
account_name TEXT NOT NULL,
segment TEXT NOT NULL,
region TEXT NOT NULL
);
INSERT INTO accounts VALUES
(1, 'Acme Analytics', 'Mid-Market', 'NA'),
(2, 'Beacon Bank', 'Enterprise', 'EMEA'),
(3, 'Cobalt Coffee', 'SMB', 'NA'),
(4, 'Delta Devices', 'Mid-Market', 'APAC'),
(5, 'Evergreen Energy', 'Enterprise', 'NA');
""")
conn.close()
PY
```
Replace the generated `klo.yaml`:
```bash
cat > "$PROJECT_DIR/klo.yaml" <<YAML
project: klo-demo
connections:
warehouse:
driver: sqlite
path: $PROJECT_DIR/demo.db
readonly: true
storage:
state: sqlite
search: sqlite-fts5
git:
auto_commit: true
author: "klo <klo@example.com>"
memory:
auto_commit: true
YAML
```
Write and validate a semantic-layer source:
```bash
pnpm run klo -- sl write accounts --project-dir "$PROJECT_DIR" \
--connection-id warehouse --yaml 'name: accounts
table: accounts
description: CRM accounts with segmentation attributes.
grain:
- account_id
columns:
- name: account_id
type: number
- name: account_name
type: string
- name: segment
type: string
- name: region
type: string
measures:
- name: account_count
expr: count(account_id)
joins: []
'
pnpm run klo -- sl validate accounts --project-dir "$PROJECT_DIR" \
--connection-id warehouse
```
Generate SQL and execute the query:
```bash
pnpm run klo -- sl query --project-dir "$PROJECT_DIR" \
--connection-id warehouse \
--measure accounts.account_count \
--dimension accounts.segment \
--order-by accounts.account_count:desc \
--limit 5 \
--format sql
pnpm run klo -- sl query --project-dir "$PROJECT_DIR" \
--connection-id warehouse \
--measure accounts.account_count \
--dimension accounts.segment \
--order-by accounts.account_count:desc \
--limit 5 \
--execute \
--max-rows 5
```
List and test the warehouse connection:
```bash
pnpm run klo -- connection list --project-dir "$PROJECT_DIR"
pnpm run klo -- connection test warehouse --project-dir "$PROJECT_DIR"
```
The connection test prints the configured driver and discovered table count:
```text
Driver: sqlite
Tables: 1
```
### Scan the demo warehouse
Scan artifacts are written under
`raw-sources/warehouse/live-database/<syncId>/` in the project directory.
```bash
SCAN_OUTPUT="$(pnpm run klo -- scan warehouse --project-dir "$PROJECT_DIR")"
printf '%s\n' "$SCAN_OUTPUT"
SCAN_RUN_ID="$(printf '%s\n' "$SCAN_OUTPUT" | awk '/^Run: / { print $2 }')"
pnpm run klo -- scan status --project-dir "$PROJECT_DIR" "$SCAN_RUN_ID"
pnpm run klo -- scan report --project-dir "$PROJECT_DIR" "$SCAN_RUN_ID"
```
For non-SQLite drivers, prefer credential references such as `--url env:NAME`
or `--url file:PATH` over literal credential URLs.
## Serve MCP
Start the Python compute daemon in one terminal:
```bash
source .venv/bin/activate
uv run klo-daemon serve-http --host 127.0.0.1 --port 8765
```
Start the stdio MCP server in another terminal:
```bash
pnpm run klo -- serve --mcp stdio --project-dir "$PROJECT_DIR" \
--user-id local \
--semantic-compute-url http://127.0.0.1:8765 \
--execute-queries
```
The MCP server exposes `connection_list`, `knowledge_search`,
`knowledge_read`, `knowledge_write`, `sl_list_sources`, `sl_read_source`,
`sl_write_source`, `sl_validate`, `sl_query`, `ingest_trigger`,
`ingest_status`, `ingest_report`, and `ingest_replay`.
## Workspace packages
- `packages/context`: core TypeScript context library.
- `packages/cli`: CLI wrapper over the context package.
- `packages/llm`: LLM and embedding provider helpers.
- `packages/connector-bigquery`: BigQuery scan connector.
- `packages/connector-clickhouse`: ClickHouse scan connector.
- `packages/connector-mysql`: MySQL scan connector.
- `packages/connector-postgres`: Postgres scan connector.
- `packages/connector-posthog`: PostHog scan connector.
- `packages/connector-snowflake`: Snowflake scan connector.
- `packages/connector-sqlite`: SQLite scan connector.
- `packages/connector-sqlserver`: SQL Server scan connector.
- `python/klo-sl`: semantic-layer engine.
- `python/klo-daemon`: portable compute service for semantic-layer operations.
## Development
Install dependencies and run checks:
```bash
pnpm install
pnpm run check
uv sync --all-packages
source .venv/bin/activate
uv run pytest
```
Use the optional development binary when you want a local `klo-dev` command:
```bash
pnpm run link:dev
klo-dev --help
```
The repository uses `pnpm` for TypeScript packages and `uv` for Python
packages.
## Release status
This repository is prepared for source publication. Package publishing is still
disabled by `release-policy.json`; registry names, public versions, package
visibility, and provenance policy must be chosen before publishing artifacts to
npm or Python package indexes.
Build local package artifacts with:
```bash
source .venv/bin/activate
pnpm run artifacts:check
pnpm run release:readiness
```
## License
KLO is licensed under the Apache License, Version 2.0. See `LICENSE`.

40
examples/README.md Normal file
View file

@ -0,0 +1,40 @@
# klo examples
## local-warehouse
`local-warehouse/` is a runnable standalone KLO project for local CLI and MCP
smoke testing. It uses the fake ingest adapter and does not require a database
or external app server.
Copy it before running commands:
```bash
pnpm --filter @klo/cli run build
EXAMPLE_DIR="$(mktemp -d)/local-warehouse"
cp -R examples/local-warehouse "$EXAMPLE_DIR"
node packages/cli/dist/bin.js knowledge list --project-dir "$EXAMPLE_DIR"
node packages/cli/dist/bin.js sl list --project-dir "$EXAMPLE_DIR" --connection-id warehouse
node packages/cli/dist/bin.js ingest run --project-dir "$EXAMPLE_DIR" --connection-id warehouse --adapter fake --source-dir "$EXAMPLE_DIR/source"
```
The copied project initializes its own Git repository on first use.
## orbit-relationship-verification
`orbit-relationship-verification/` is a checked-in KLO project used by
`pnpm run relationships:verify-orbit`. It points the `orbit` SQLite connection
at the Orbit-style no-declared-constraint relationship fixture and verifies that
relationship enrichment writes nine accepted joins without requiring a local
warehouse credential.
## postgres-historic
`postgres-historic/` is a manual Docker-backed smoke for Postgres
historic-SQL ingest via `pg_stat_statements`. It verifies setup, first-run
baseline creation, delta-only follow-up ingest, and reset handling without
requiring a managed Postgres service.
## package-artifacts
`package-artifacts/` documents the artifact smoke checks. Those checks create
temporary projects instead of storing sample projects in this directory.

View file

@ -0,0 +1,20 @@
# Local Warehouse Example
This example is a standalone KLO project that can be copied to a temp directory
and used with the local CLI and stdio MCP server. It uses the `fake` ingest
adapter so it does not require a database or external app server.
Run the example from the repository root after building the CLI:
```bash
pnpm --filter @klo/cli run build
EXAMPLE_DIR="$(mktemp -d)/local-warehouse"
cp -R examples/local-warehouse "$EXAMPLE_DIR"
node packages/cli/dist/bin.js knowledge list --project-dir "$EXAMPLE_DIR"
node packages/cli/dist/bin.js sl list --project-dir "$EXAMPLE_DIR" --connection-id warehouse
node packages/cli/dist/bin.js ingest run --project-dir "$EXAMPLE_DIR" --connection-id warehouse --adapter fake --source-dir "$EXAMPLE_DIR/source"
```
The copied project creates its own Git repository on first use. Keep commands
pointed at a copy when experimenting so the checked-in example fixture stays
unchanged.

View file

@ -0,0 +1,25 @@
project: local-warehouse
connections:
warehouse:
driver: postgres
readonly: true
storage:
state: sqlite
search: sqlite-fts5
git:
auto_commit: true
author: "klo <klo@example.com>"
ingest:
adapters:
- fake
- live-database
agent:
run_research:
enabled: false
max_iterations: 20
default_toolset:
- sl_query
- knowledge_search
- sl_read_source
memory:
auto_commit: true

View file

@ -0,0 +1,15 @@
---
summary: Paid order value after refunds
tags:
- finance
- orders
refs: []
sl_refs:
- warehouse.orders
usage_mode: auto
---
Revenue is paid order amount after refund adjustments.
Use `orders.total_revenue` for recognized order value and `orders.order_count`
for paid order volume.

View file

@ -0,0 +1,18 @@
name: orders
table: public.orders
description: Orders placed through the storefront.
grain:
- id
columns:
- name: id
type: number
- name: status
type: string
- name: amount
type: number
measures:
- name: order_count
expr: count(*)
- name: total_revenue
expr: sum(amount)
joins: []

View file

@ -0,0 +1 @@
{"source":"orders","description":"Example raw file staged by the fake adapter"}

View file

@ -0,0 +1,33 @@
# Orbit-style relationship discovery verification
This KLO project backs the default `relationships:verify-orbit` command. It uses
the checked-in Orbit-style SQLite fixture from the relationship discovery
benchmark corpus, with no declared primary keys or foreign keys in the database
schema.
Run from the KLO workspace root:
```bash
pnpm run relationships:verify-orbit
```
Expected relationship summary:
```text
Accepted: 9
Review: 0
Rejected: 0
Skipped: 0
```
The command refreshes:
```text
examples/orbit-relationship-verification/reports/orbit-verification.md
```
Use a real local Orbit project by overriding the project directory:
```bash
KLO_ORBIT_PROJECT_DIR=/path/to/orbit-project pnpm run relationships:verify-orbit
```

View file

@ -0,0 +1,28 @@
project: orbit-relationship-verification
connections:
orbit:
driver: sqlite
path: ../../packages/context/test/fixtures/relationship-benchmarks/orbit_style_product_no_declared_constraints/data.sqlite
readonly: true
storage:
state: sqlite
search: sqlite-fts5
git:
auto_commit: true
author: "klo <klo@example.com>"
ingest:
adapters:
- live-database
scan:
enrichment:
backend: none
relationships:
enabled: true
llm_proposals: false
validation_required_for_manifest: true
accept_threshold: 0.85
review_threshold: 0.55
max_llm_tables_per_batch: 40
max_candidates_per_column: 25
profile_sample_rows: 10000
validation_concurrency: 4

View file

@ -0,0 +1,17 @@
# Package artifact smoke checks
The package artifact smoke checks create temporary projects instead of storing
sample projects in this directory. Run the checks from `klo/`:
```bash
source .venv/bin/activate
pnpm run artifacts:check
```
The npm smoke project installs the generated `@klo/context` and `@klo/cli`
tarballs, imports public package entry points, and runs installed `klo`
commands against a generated local project.
The Python smoke project installs `klo-daemon` through the local artifact
directory, imports `semantic_layer` and `klo_daemon`, and runs
`python -m klo_daemon semantic-validate`.

View file

@ -0,0 +1,115 @@
# Postgres Historic SQL Example
This example is a manual smoke for Postgres historic-SQL ingest through
`pg_stat_statements`. It starts Postgres 14 with the extension preloaded,
generates query workload under separate users, runs `klo setup` with
`--enable-historic-sql`, and verifies three local ingest runs:
- first run creates a fresh PGSS baseline
- second run emits only positive deltas
- reset run treats `pg_stat_statements_reset()` as a fresh baseline
## Prerequisites
- Docker with Compose v2
- Node and pnpm matching the KLO workspace
- `python-service/.venv` already created, or `KLO_SQL_ANALYSIS_URL` pointing at
a running service that exposes `/api/sql/analyze-for-fingerprint`
## Run
From the KLO repository root:
```bash
examples/postgres-historic/scripts/smoke.sh
```
The smoke creates a temporary KLO project, starts Postgres on
`127.0.0.1:55432`, and uses this connection URL:
```bash
postgresql://klo_reader:klo_reader@127.0.0.1:55432/analytics # pragma: allowlist secret
```
Set `KLO_POSTGRES_HISTORIC_KEEP_DOCKER=1` to leave the container running after
the script exits.
The smoke validates the historic-SQL raw snapshot path without requiring LLM
credentials. It uses KLO's local stage-only ingest API after `klo setup` so the
PGSS baseline and delta behavior can be checked independently from curation.
## Manual Commands
Start Postgres and generate the base workload:
```bash
docker compose -f examples/postgres-historic/docker-compose.yml up -d --wait
examples/postgres-historic/scripts/generate-workload.sh base
```
Create a project and enable historic SQL:
```bash
export WAREHOUSE_DATABASE_URL=postgresql://klo_reader:klo_reader@127.0.0.1:55432/analytics # pragma: allowlist secret
pnpm --filter @klo/cli run build
node packages/cli/dist/bin.js --project-dir /tmp/klo-postgres-historic setup \
--new \
--skip-agents \
--skip-llm \
--skip-embeddings \
--skip-sources \
--database postgres \
--new-database-connection-id warehouse \
--database-url env:WAREHOUSE_DATABASE_URL \
--database-schema public \
--enable-historic-sql \
--historic-sql-min-calls 2 \
--yes \
--no-input
```
### Readiness check
```bash
pnpm run klo -- dev doctor --project-dir /tmp/klo-postgres-historic --no-input
```
The installed CLI form is `klo dev doctor --project-dir
/tmp/klo-postgres-historic --no-input`. Expected output includes `PASS Postgres
Historic SQL (warehouse)` when `pg_stat_statements` is installed,
`pg_read_all_stats` is granted, tracking is enabled, and
`pg_stat_statements.max` is at least 5000.
Run local historic-SQL ingest:
```bash
node packages/cli/dist/bin.js --project-dir /tmp/klo-postgres-historic dev ingest run \
--connection-id warehouse \
--adapter historic-sql \
--plain \
--no-input
```
The full `dev ingest run` path also runs curation work units, so it requires a
configured LLM provider.
Inspect the latest manifest:
```bash
find /tmp/klo-postgres-historic/raw-sources/warehouse/historic-sql -name manifest.json | sort | tail -n 1
```
The manifest should have `dialect: "postgres"`, `degraded: true`,
`baselineFirstRun: true` on the first run, and populated `pgServerVersion` and
`statsResetAt`.
## Troubleshooting
- Missing extension: confirm `shared_preload_libraries=pg_stat_statements` and
`CREATE EXTENSION pg_stat_statements;` both happened in the `analytics`
database.
- Missing grants: confirm `GRANT pg_read_all_stats TO klo_reader;`.
- Empty templates: rerun `scripts/generate-workload.sh base` and keep
`--historic-sql-min-calls 2` for the smoke.
- SQL-analysis failures: set `KLO_SQL_ANALYSIS_URL` to the running service URL
or create `python-service/.venv` before running `scripts/smoke.sh`.

View file

@ -0,0 +1,24 @@
services:
postgres:
image: postgres:14
command:
- postgres
- -c
- shared_preload_libraries=pg_stat_statements
- -c
- pg_stat_statements.track=top
- -c
- pg_stat_statements.max=10000
environment:
POSTGRES_DB: analytics
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres # pragma: allowlist secret
ports:
- "55432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d analytics"]
interval: 2s
timeout: 5s
retries: 30
volumes:
- ./init:/docker-entrypoint-initdb.d:ro

View file

@ -0,0 +1,51 @@
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE ROLE app_user LOGIN PASSWORD 'app_pass';
CREATE ROLE etl_user LOGIN PASSWORD 'etl_pass';
CREATE ROLE klo_reader LOGIN PASSWORD 'klo_reader';
GRANT pg_read_all_stats TO klo_reader;
CREATE TABLE customers (
id integer PRIMARY KEY,
region text NOT NULL,
plan text NOT NULL
);
CREATE TABLE orders (
id integer PRIMARY KEY,
customer_id integer NOT NULL REFERENCES customers(id),
status text NOT NULL,
total numeric(12, 2) NOT NULL,
created_at timestamptz NOT NULL
);
CREATE TABLE events (
id integer PRIMARY KEY,
customer_id integer NOT NULL REFERENCES customers(id),
event_name text NOT NULL,
occurred_at timestamptz NOT NULL
);
INSERT INTO customers (id, region, plan) VALUES
(1, 'na', 'enterprise'),
(2, 'na', 'team'),
(3, 'eu', 'enterprise'),
(4, 'apac', 'team');
INSERT INTO orders (id, customer_id, status, total, created_at) VALUES
(1, 1, 'paid', 125.50, now() - interval '9 days'),
(2, 1, 'paid', 89.00, now() - interval '4 days'),
(3, 2, 'pending', 42.00, now() - interval '2 days'),
(4, 3, 'paid', 301.25, now() - interval '1 day'),
(5, 4, 'refunded', 77.70, now() - interval '3 hours');
INSERT INTO events (id, customer_id, event_name, occurred_at) VALUES
(1, 1, 'dashboard_viewed', now() - interval '1 day'),
(2, 1, 'export_started', now() - interval '8 hours'),
(3, 2, 'dashboard_viewed', now() - interval '7 hours'),
(4, 3, 'sync_completed', now() - interval '6 hours'),
(5, 4, 'dashboard_viewed', now() - interval '5 hours');
GRANT USAGE ON SCHEMA public TO app_user, etl_user, klo_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_user, etl_user, klo_reader;

View file

@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EXAMPLE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
COMPOSE_FILE="$EXAMPLE_DIR/docker-compose.yml"
MODE="${1:-base}"
run_sql() {
local user="$1"
local password="$2"
local sql="$3"
docker compose -f "$COMPOSE_FILE" exec -T -e PGPASSWORD="$password" postgres \
psql -h 127.0.0.1 -U "$user" -d analytics -v ON_ERROR_STOP=1 -c "$sql" >/dev/null
}
for _ in $(seq 1 12); do
run_sql app_user app_pass "SELECT c.region, count(*) AS order_count FROM orders o JOIN customers c ON c.id = o.customer_id WHERE o.status = 'paid' GROUP BY c.region ORDER BY c.region"
done
for _ in $(seq 1 7); do
run_sql app_user app_pass "SELECT c.plan, sum(o.total) AS revenue FROM orders o JOIN customers c ON c.id = o.customer_id WHERE o.created_at >= now() - interval '14 days' GROUP BY c.plan ORDER BY revenue DESC"
done
for _ in $(seq 1 5); do
run_sql etl_user etl_pass "SELECT e.event_name, count(*) AS event_count FROM events e JOIN customers c ON c.id = e.customer_id WHERE c.region = 'na' GROUP BY e.event_name ORDER BY event_count DESC"
done
if [[ "$MODE" == "extra" ]]; then
for _ in $(seq 1 4); do
run_sql etl_user etl_pass "SELECT c.region, avg(o.total) AS avg_total FROM orders o JOIN customers c ON c.id = o.customer_id WHERE o.status <> 'refunded' GROUP BY c.region ORDER BY avg_total DESC"
done
fi

View file

@ -0,0 +1,152 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EXAMPLE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
KLO_ROOT="$(cd "$EXAMPLE_DIR/../.." && pwd)"
REPO_ROOT="$(cd "$KLO_ROOT/.." && pwd)"
COMPOSE_FILE="$EXAMPLE_DIR/docker-compose.yml"
PROJECT_PARENT="${KLO_POSTGRES_HISTORIC_PROJECT_PARENT:-$(mktemp -d)}"
PROJECT_DIR="$PROJECT_PARENT/postgres-historic-klo"
KLO_BIN="$KLO_ROOT/packages/cli/dist/bin.js"
PYTHON_SERVICE_LOG="$PROJECT_PARENT/python-service.log"
PYTHON_SERVICE_PID=""
cleanup() {
if [[ -n "$PYTHON_SERVICE_PID" ]]; then
kill "$PYTHON_SERVICE_PID" >/dev/null 2>&1 || true
fi
if [[ "${KLO_POSTGRES_HISTORIC_KEEP_DOCKER:-0}" != "1" ]]; then
docker compose -f "$COMPOSE_FILE" down -v >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT
start_sql_analysis_if_needed() {
if [[ -n "${KLO_SQL_ANALYSIS_URL:-}" ]]; then
return
fi
if [[ ! -d "$REPO_ROOT/python-service/.venv" ]]; then
echo "Set KLO_SQL_ANALYSIS_URL or create python-service/.venv before running this smoke." >&2
exit 1
fi
(
cd "$REPO_ROOT/python-service"
source .venv/bin/activate
uvicorn app.main:app --host 127.0.0.1 --port 18081 >"$PYTHON_SERVICE_LOG" 2>&1
) &
PYTHON_SERVICE_PID="$!"
export KLO_SQL_ANALYSIS_URL="http://127.0.0.1:18081"
for _ in $(seq 1 60); do
if curl -fsS "$KLO_SQL_ANALYSIS_URL/health" >/dev/null 2>&1; then
return
fi
sleep 1
done
echo "SQL analysis service did not become healthy. Log: $PYTHON_SERVICE_LOG" >&2
exit 1
}
latest_manifest() {
find "$PROJECT_DIR/raw-sources/warehouse/historic-sql" -name manifest.json | sort | tail -n 1
}
assert_manifest() {
local manifest_path="$1"
local expected_first_run="$2"
node - "$manifest_path" "$expected_first_run" <<'NODE'
const { readFileSync } = require('node:fs');
const manifestPath = process.argv[2];
const expectedFirstRun = process.argv[3] === 'true';
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
if (manifest.dialect !== 'postgres') throw new Error(`Expected dialect postgres, got ${manifest.dialect}`);
if (manifest.degraded !== true) throw new Error('Expected degraded:true for Postgres PGSS v1');
if (manifest.baselineFirstRun !== expectedFirstRun) {
throw new Error(`Expected baselineFirstRun:${expectedFirstRun}, got ${manifest.baselineFirstRun}`);
}
if (!manifest.pgServerVersion) throw new Error('Expected pgServerVersion');
if (!manifest.statsResetAt) throw new Error('Expected statsResetAt');
if (!Array.isArray(manifest.templates) || manifest.templates.length === 0) {
throw new Error('Expected at least one staged historic-SQL template');
}
NODE
}
run_historic_stage_only() {
local job_id="$1"
node - "$KLO_ROOT" "$PROJECT_DIR" "$job_id" <<'NODE'
const { join } = await import('node:path');
const kloRoot = process.argv[2];
const projectDir = process.argv[3];
const jobId = process.argv[4];
const { loadKloProject } = await import(join(kloRoot, 'packages/context/dist/project/index.js'));
const { runLocalStageOnlyIngest } = await import(join(kloRoot, 'packages/context/dist/ingest/index.js'));
const { createKloCliLocalIngestAdapters } = await import(join(kloRoot, 'packages/cli/dist/local-adapters.js'));
const project = await loadKloProject({ projectDir });
const adapters = createKloCliLocalIngestAdapters(project, { historicSqlConnectionId: 'warehouse' });
const adapter = adapters.find((candidate) => candidate.source === 'historic-sql');
if (!adapter) throw new Error('historic-sql adapter was not registered for local run');
const record = await runLocalStageOnlyIngest({
project,
adapters,
adapter: 'historic-sql',
connectionId: 'warehouse',
trigger: 'manual_resync',
jobId,
});
await adapter.onPullSucceeded?.({
connectionId: 'warehouse',
sourceKey: 'historic-sql',
syncId: record.syncId,
trigger: 'manual_resync',
completedAt: new Date(record.completedAt),
stagedDir: join(project.projectDir, '.klo/cache/local-ingest', jobId, 'staged'),
});
console.log(record.syncId);
NODE
}
cd "$KLO_ROOT"
pnpm --filter @klo/context run build
pnpm --filter @klo/cli run build
start_sql_analysis_if_needed
docker compose -f "$COMPOSE_FILE" up -d --wait
"$EXAMPLE_DIR/scripts/generate-workload.sh" base
export WAREHOUSE_DATABASE_URL="${WAREHOUSE_DATABASE_URL:-postgresql://klo_reader:klo_reader@127.0.0.1:55432/analytics}" # pragma: allowlist secret
node "$KLO_BIN" --project-dir "$PROJECT_DIR" setup \
--new \
--skip-agents \
--skip-llm \
--skip-embeddings \
--skip-sources \
--database postgres \
--new-database-connection-id warehouse \
--database-url env:WAREHOUSE_DATABASE_URL \
--database-schema public \
--enable-historic-sql \
--historic-sql-min-calls 2 \
--yes \
--no-input
run_historic_stage_only "historic-first-$$"
FIRST_MANIFEST="$(latest_manifest)"
assert_manifest "$FIRST_MANIFEST" true
"$EXAMPLE_DIR/scripts/generate-workload.sh" extra
run_historic_stage_only "historic-second-$$"
SECOND_MANIFEST="$(latest_manifest)"
assert_manifest "$SECOND_MANIFEST" false
docker compose -f "$COMPOSE_FILE" exec -T postgres \
psql -U postgres -d analytics -v ON_ERROR_STOP=1 -c "SELECT pg_stat_statements_reset();" >/dev/null
"$EXAMPLE_DIR/scripts/generate-workload.sh" extra
run_historic_stage_only "historic-reset-$$"
RESET_MANIFEST="$(latest_manifest)"
assert_manifest "$RESET_MANIFEST" true
echo "Postgres historic SQL smoke passed"
echo "Project dir: $PROJECT_DIR"

54
package.json Normal file
View file

@ -0,0 +1,54 @@
{
"name": "klo-workspace",
"version": "0.0.0-private",
"description": "Workspace root for klo packages",
"private": true,
"type": "module",
"packageManager": "pnpm@10.28.0",
"engines": {
"node": ">=22.0.0",
"pnpm": ">=10.20.0"
},
"scripts": {
"artifacts:build": "node scripts/package-artifacts.mjs build",
"artifacts:check": "node scripts/package-artifacts.mjs check",
"artifacts:live-db-smoke": "node scripts/installed-live-database-smoke.mjs",
"artifacts:verify": "node scripts/package-artifacts.mjs verify",
"artifacts:verify-demo": "node scripts/package-artifacts.mjs verify-demo",
"artifacts:verify-manifest": "node scripts/package-artifacts.mjs verify-manifest",
"build": "pnpm --filter './packages/*' run build",
"check": "node scripts/check-boundaries.mjs && node --test scripts/*.test.mjs && pnpm --filter './packages/*' run build && pnpm --filter './packages/*' run test",
"klo": "node scripts/run-klo.mjs",
"link:dev": "node scripts/link-dev-cli.mjs",
"native:rebuild": "pnpm -r rebuild better-sqlite3",
"setup:dev": "node scripts/setup-dev.mjs",
"release:published-smoke": "node scripts/published-package-smoke.mjs --require-config",
"release:readiness": "node scripts/release-readiness.mjs",
"relationships:acquire-public-fixtures": "node scripts/acquire-public-benchmark-fixtures.mjs",
"relationships:rebuild-public-snapshots": "node scripts/build-benchmark-snapshot.mjs --rebuild-all",
"relationships:build-adventureworks-oltp": "node scripts/build-adventureworks-oltp-fixture.mjs",
"relationships:verify-orbit": "node scripts/relationship-orbit-verification.mjs",
"smoke": "pnpm run build && pnpm --filter @klo/cli run smoke",
"test": "node --test scripts/*.test.mjs && pnpm --filter './packages/*' run test",
"type-check": "pnpm --filter './packages/*' run type-check"
},
"devDependencies": {
"@types/node": "^24.3.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"pnpm": {
"onlyBuiltDependencies": [
"better-sqlite3"
]
},
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/kaelio/ktx.git"
},
"bugs": {
"url": "https://github.com/kaelio/ktx/issues"
},
"homepage": "https://github.com/kaelio/ktx#readme"
}

Binary file not shown.

View file

@ -0,0 +1,18 @@
---
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.

View file

@ -0,0 +1,18 @@
---
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.

View file

@ -0,0 +1,20 @@
---
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.

View file

@ -0,0 +1,19 @@
---
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.

View file

@ -0,0 +1,16 @@
---
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.

View file

@ -0,0 +1,19 @@
---
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.

View file

@ -0,0 +1,17 @@
---
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.

View file

@ -0,0 +1,17 @@
---
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.

View file

@ -0,0 +1,17 @@
---
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.

View file

@ -0,0 +1,17 @@
---
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.

View file

@ -0,0 +1,209 @@
[
{
"id": "link-001",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/arr-contract-first.md",
"sourceKind": "warehouse",
"sourcePath": "contracts",
"relationship": "describes",
"confidence": 1
},
{
"id": "link-002",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/arr-contract-first.md",
"sourceKind": "notion",
"sourcePath": "raw-sources/notion/arr-and-contract-reporting-notes.md",
"relationship": "derived_from",
"confidence": 0.95
},
{
"id": "link-003",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/revenue-gross-to-net.md",
"sourceKind": "warehouse",
"sourcePath": "invoices",
"relationship": "describes",
"confidence": 1
},
{
"id": "link-004",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/revenue-gross-to-net.md",
"sourceKind": "notion",
"sourcePath": "raw-sources/notion/revenue-reporting-policy.md",
"relationship": "derived_from",
"confidence": 0.95
},
{
"id": "link-005",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/discount-expiration.md",
"sourceKind": "warehouse",
"sourcePath": "arr_movements",
"relationship": "describes",
"confidence": 1
},
{
"id": "link-006",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/nrr-retention.md",
"sourceKind": "warehouse",
"sourcePath": "arr_movements",
"relationship": "describes",
"confidence": 1
},
{
"id": "link-007",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/nrr-retention.md",
"sourceKind": "notion",
"sourcePath": "raw-sources/notion/retention-and-nrr-definition-notes.md",
"relationship": "derived_from",
"confidence": 0.95
},
{
"id": "link-008",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/nrr-retention.md",
"sourceKind": "bi",
"sourcePath": "raw-sources/bi/account_retention.view.lkml",
"relationship": "derived_from",
"confidence": 0.85
},
{
"id": "link-009",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/segment-classification.md",
"sourceKind": "warehouse",
"sourcePath": "plans",
"relationship": "describes",
"confidence": 1
},
{
"id": "link-010",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/segment-classification.md",
"sourceKind": "notion",
"sourcePath": "raw-sources/notion/sales-ops-segmentation-guide.md",
"relationship": "derived_from",
"confidence": 0.9
},
{
"id": "link-011",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/activation-policy.md",
"sourceKind": "notion",
"sourcePath": "raw-sources/notion/activation-policy-decision-record.md",
"relationship": "derived_from",
"confidence": 0.95
},
{
"id": "link-012",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/procurement-workflows.md",
"sourceKind": "warehouse",
"sourcePath": "purchase_requests",
"relationship": "describes",
"confidence": 1
},
{
"id": "link-013",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/customer-health-scoring.md",
"sourceKind": "notion",
"sourcePath": "raw-sources/notion/customer-health-playbook.md",
"relationship": "derived_from",
"confidence": 0.9
},
{
"id": "link-014",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/customer-health-scoring.md",
"sourceKind": "warehouse",
"sourcePath": "support_tickets",
"relationship": "describes",
"confidence": 1
},
{
"id": "link-015",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/support-escalation.md",
"sourceKind": "notion",
"sourcePath": "raw-sources/notion/support-escalation-runbook.md",
"relationship": "derived_from",
"confidence": 0.9
},
{
"id": "link-016",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/internal-test-exclusion.md",
"sourceKind": "notion",
"sourcePath": "raw-sources/notion/analyst-onboarding.md",
"relationship": "derived_from",
"confidence": 0.9
},
{
"id": "link-017",
"artifactKind": "sl",
"artifactKey": "orbit_demo.accounts",
"sourceKind": "warehouse",
"sourcePath": "accounts",
"relationship": "models",
"confidence": 1
},
{
"id": "link-018",
"artifactKind": "sl",
"artifactKey": "orbit_demo.accounts",
"sourceKind": "dbt",
"sourcePath": "raw-sources/dbt/schema.yml",
"relationship": "inherits_from",
"confidence": 0.95
},
{
"id": "link-019",
"artifactKind": "sl",
"artifactKey": "orbit_demo.contracts",
"sourceKind": "warehouse",
"sourcePath": "contracts",
"relationship": "models",
"confidence": 1
},
{
"id": "link-020",
"artifactKind": "sl",
"artifactKey": "orbit_demo.invoices",
"sourceKind": "warehouse",
"sourcePath": "invoices",
"relationship": "models",
"confidence": 1
},
{
"id": "link-021",
"artifactKind": "sl",
"artifactKey": "orbit_demo.arr_movements",
"sourceKind": "warehouse",
"sourcePath": "arr_movements",
"relationship": "models",
"confidence": 1
},
{
"id": "link-022",
"artifactKind": "sl",
"artifactKey": "orbit_demo.purchase_requests",
"sourceKind": "warehouse",
"sourcePath": "purchase_requests",
"relationship": "models",
"confidence": 1
},
{
"id": "link-023",
"artifactKind": "sl",
"artifactKey": "orbit_demo.support_tickets",
"sourceKind": "warehouse",
"sourcePath": "support_tickets",
"relationship": "models",
"confidence": 1
}
]

View file

@ -0,0 +1,58 @@
{
"demoAssetSchemaVersion": 2,
"name": "orbit",
"displayName": "Orbit Demo",
"mode": "seeded",
"sqliteDatabase": "demo.db",
"replay": "replay.memory-flow.v1.json",
"report": "reports/seeded-demo-report.json",
"source": "packaged-orbit-demo",
"sources": {
"warehouse": {
"label": "Warehouse",
"path": "demo.db",
"tables": 8,
"rowCounts": {
"accounts": 210,
"contracts": 320,
"users": 1260,
"invoices": 3000,
"arr_movements": 720,
"support_tickets": 520,
"purchase_requests": 5200,
"plans": 4
}
},
"dbt": {
"label": "dbt",
"path": "raw-sources/dbt",
"models": 3,
"sourceTables": 8
},
"bi": {
"label": "BI",
"path": "raw-sources/bi",
"explores": 5,
"dashboards": 2
},
"notion": {
"label": "Notion",
"path": "raw-sources/notion",
"pages": 8
}
},
"generated": {
"semanticLayer": {
"path": "semantic-layer/orbit_demo",
"sourceCount": 6
},
"knowledge": {
"path": "knowledge/global",
"pageCount": 10
},
"links": {
"path": "links",
"linkCount": 23
}
}
}

View file

@ -0,0 +1,67 @@
view: account_retention {
sql_table_name: orbit_analytics.mart_nrr_quarterly ;;
description: "Canonical dbt mart dbt://ktx_demo.mart_nrr_quarterly with governed policy notion://notion_page_retention_policy_current#nrr-definition."
dimension: retention_key {
primary_key: yes
type: string
sql: CONCAT(${TABLE}.segment, '-', ${TABLE}.quarter_label) ;;
}
dimension: account_id {
type: string
sql: ${TABLE}.segment ;;
}
dimension: parent_account_id {
type: string
sql: ${TABLE}.segment ;;
}
dimension: fiscal_quarter {
type: string
sql: ${TABLE}.quarter_label ;;
}
dimension: segment {
type: string
sql: ${TABLE}.segment ;;
}
dimension: net_revenue_retention {
type: number
sql: ${TABLE}.net_revenue_retention ;;
}
measure: nrr {
type: average
sql: ${net_revenue_retention} ;;
value_format_name: percent_1
description: "Enterprise parent-account NRR from dbt://ktx_demo.mart_nrr_quarterly and notion://notion_page_retention_policy_current#nrr-definition."
}
measure: starting_arr {
type: sum
sql: ${TABLE}.starting_arr_cents ;;
value_format_name: usd_0
}
measure: expansion_arr {
type: sum
sql: ${TABLE}.expansion_arr_cents ;;
value_format_name: usd_0
description: "Expansion ARR used by the enterprise_expansions_q1_2026 expected answer."
}
measure: contraction_arr {
type: sum
sql: ${TABLE}.contraction_arr_cents ;;
value_format_name: usd_0
}
measure: churned_arr {
type: sum
sql: ${TABLE}.churned_arr_cents ;;
value_format_name: usd_0
}
}

View file

@ -0,0 +1,28 @@
view: arr_daily {
sql_table_name: orbit_analytics.mart_arr_daily ;;
description: "Contract-first ARR from dbt://ktx_demo.mart_arr_daily and notion://notion_page_arr_contract_reporting#arr-contract-first."
dimension: arr_daily_key {
primary_key: yes
type: string
sql: CONCAT('all_accounts-', ${TABLE}.metric_date) ;;
}
dimension: account_id {
type: string
sql: 'all_accounts' ;;
}
dimension_group: metric {
type: time
timeframes: [date, week, month, quarter]
sql: ${TABLE}.metric_date ;;
}
measure: arr {
type: sum
sql: ${TABLE}.arr_cents ;;
value_format_name: usd_0
description: "Active contract ARR as of the requested metric date."
}
}

View file

@ -0,0 +1,50 @@
view: customer_health {
sql_table_name: orbit_analytics.mart_customer_health ;;
description: "Customer health mart dbt://ktx_demo.mart_customer_health governed by notion://notion_page_customer_health_playbook#risk-definition."
dimension: customer_health_key {
primary_key: yes
type: string
sql: CONCAT(${TABLE}.account_id, '-', ${TABLE}.as_of_date) ;;
}
dimension: account_id {
type: string
sql: ${TABLE}.account_id ;;
}
dimension_group: metric {
type: time
timeframes: [date, week, month]
sql: ${TABLE}.as_of_date ;;
}
dimension: health_risk_tier {
type: string
sql: ${TABLE}.risk_level ;;
}
dimension: is_paying_customer {
type: yesno
sql: ${TABLE}.is_active_customer ;;
}
measure: active_customers {
type: count_distinct
sql: ${account_id} ;;
filters: [is_paying_customer: "yes"]
description: "Active paying customer accounts in the health mart."
}
measure: high_risk_accounts {
type: count_distinct
sql: ${account_id} ;;
filters: [health_risk_tier: "high"]
description: "High-risk paying accounts used by the customer_health_risk_accounts expected answer."
}
measure: open_support_tickets {
type: sum
sql: case when ${TABLE}.has_unresolved_high_ticket then 1 else 0 end ;;
}
}

View file

@ -0,0 +1,46 @@
view: procurement_activity {
sql_table_name: orbit_analytics.mart_procurement_activity ;;
description: "Procurement activity mart dbt://ktx_demo.mart_procurement_activity with governed context notion://notion_page_procurement_instrumentation#qualifying-procurement-actions."
dimension: procurement_activity_key {
primary_key: yes
type: string
sql: CONCAT(${TABLE}.contract_arr_threshold_cents, '-', ${TABLE}.week_start_date) ;;
}
dimension: account_id {
type: string
sql: 'all_accounts' ;;
}
dimension_group: week_start {
type: time
timeframes: [date, week]
sql: ${TABLE}.week_start_date ;;
}
dimension: contract_arr_band {
type: string
sql: case
when ${TABLE}.contract_arr_threshold_cents >= 20000000 then 'over_200k'
else 'under_200k'
end ;;
description: "Contract ARR band represented by the procurement activity threshold."
}
measure: weekly_active_requesters {
type: sum
sql: ${TABLE}.active_requesters ;;
description: "Distinct non-internal requesters with qualifying procurement workflow actions during the requested week."
}
measure: purchase_requests {
type: sum
sql: 0 ;;
}
measure: approval_actions {
type: sum
sql: 0 ;;
}
}

View file

@ -0,0 +1,28 @@
# looker_dashboard_id: dash_retention_exec_q1
dashboard: retention_exec_q1 {
title: "Enterprise Retention Executive Review"
element: retention_tile {
title: "Enterprise NRR"
explore: retention
fields: [retention.fiscal_quarter, retention.nrr]
}
element: movement_breakout_tile {
title: "Movement Breakout"
explore: retention
fields: [retention.expansion_arr, retention.contraction_arr, retention.churned_arr]
}
element: discount_expiration_contraction_tile {
title: "Discount Expiration Contraction"
explore: retention
fields: [retention.parent_account_id, retention.contraction_arr]
}
element: q4_vs_q1_comparison_tile {
title: "Q4 vs Q1 Comparison"
explore: retention
fields: [retention.fiscal_quarter, retention.nrr]
}
}

View file

@ -0,0 +1,52 @@
view: revenue_daily {
sql_table_name: orbit_analytics.mart_revenue_daily ;;
description: "Revenue recognition mart dbt://ktx_demo.mart_revenue_daily governed by notion://notion_page_revenue_reporting_policy#gross-to-net-reconciliation."
dimension: revenue_daily_key {
primary_key: yes
type: string
sql: CONCAT('all_accounts-', ${TABLE}.revenue_date) ;;
}
dimension: account_id {
type: string
sql: 'all_accounts' ;;
}
dimension_group: revenue {
type: time
timeframes: [date, week, quarter]
sql: ${TABLE}.revenue_date ;;
}
dimension: revenue_month {
type: string
sql: TO_CHAR(${TABLE}.revenue_date, 'YYYY-MM') ;;
}
measure: gross_revenue {
type: sum
sql: ${TABLE}.gross_revenue_cents ;;
value_format_name: usd_0
description: "Paid invoice line revenue before credits and refunds."
}
measure: credits {
type: sum
sql: ${TABLE}.credits_cents ;;
value_format_name: usd_0
}
measure: refunds {
type: sum
sql: ${TABLE}.refunds_cents ;;
value_format_name: usd_0
}
measure: net_revenue {
type: sum
sql: ${TABLE}.net_revenue_cents ;;
value_format_name: usd_0
description: "Gross revenue minus credits and successful refunds, recognized by paid/refund dates."
}
}

View file

@ -0,0 +1,28 @@
# looker_dashboard_id: dash_revenue_exec
dashboard: revenue_exec {
title: "Gross and Net Revenue Executive Dashboard"
element: gross_revenue_tile {
title: "Gross Revenue"
explore: revenue
fields: [revenue.revenue_month, revenue.gross_revenue]
}
element: credits_tile {
title: "Credits"
explore: revenue
fields: [revenue.revenue_month, revenue.credits]
}
element: refunds_tile {
title: "Refunds"
explore: revenue
fields: [revenue.revenue_month, revenue.refunds]
}
element: february_reconciliation_tile {
title: "February Reconciliation"
explore: revenue
fields: [revenue.gross_revenue, revenue.credits, revenue.refunds, revenue.net_revenue]
}
}

View file

@ -0,0 +1,10 @@
name: ktx_demo
version: "1.0.0"
config-version: 2
profile: ktx_demo
model-paths: ["models"]
models:
ktx_demo:
+materialized: view

View file

@ -0,0 +1,5 @@
select
date '2026-03-31' as metric_date,
sum(contract_arr_cents)::bigint as arr_cents,
'$18.742M' as display
from {{ ref('int_active_contract_arr') }}

View file

@ -0,0 +1,10 @@
select
date '2026-03-31' as as_of_date,
account_id,
parent_account_id,
account_name,
is_active_customer,
has_unresolved_high_ticket,
has_recent_procurement_activity,
risk_level
from {{ ref('int_customer_health_signals') }}

View file

@ -0,0 +1,8 @@
select
revenue_date,
gross_revenue_cents::bigint as gross_revenue_cents,
credits_cents::bigint as credits_cents,
refunds_cents::bigint as refunds_cents,
net_revenue_cents::bigint as net_revenue_cents,
(gross_revenue_cents - credits_cents - refunds_cents = net_revenue_cents) as reconciliation_check
from {{ ref('int_revenue_components') }}

View file

@ -0,0 +1,455 @@
version: 2
models:
- name: stg_accounts
description: 'Customer and internal/test account records for Orbit.'
columns:
- name: account_id
data_tests:
- not_null
- unique
- name: sales_region
data_tests:
- accepted_values:
arguments:
values: [na, emea, apac]
- name: size_band
data_tests:
- accepted_values:
arguments:
values: [smb, mid_market, enterprise]
- name: lifecycle_status
data_tests:
- accepted_values:
arguments:
values: [prospect, active, churned, internal, test]
- name: stg_account_hierarchy
description: 'Parent-child account relationships used for enterprise retention grain.'
columns:
- name: account_hierarchy_id
data_tests:
- not_null
- unique
- name: relationship_type
data_tests:
- accepted_values:
arguments:
values: [subsidiary, division, billing_group]
- name: stg_plans
description: 'Canonical and historical Orbit pricing plans.'
columns:
- name: plan_id
data_tests:
- not_null
- unique
- name: canonical_plan_code
data_tests:
- accepted_values:
arguments:
values: [starter, growth, enterprise]
- name: stg_contracts
description: 'Contract records that provide contract-first ARR for active accounts.'
columns:
- name: contract_id
data_tests:
- not_null
- unique
- name: status
data_tests:
- accepted_values:
arguments:
values: [draft, active, cancelled, expired]
- name: renewal_type
data_tests:
- accepted_values:
arguments:
values: [new, renewal, expansion, downgrade]
- name: stg_subscriptions
description: 'Subscription rows used when active contract ARR is not present for a covered period.'
columns:
- name: subscription_id
data_tests:
- not_null
- unique
- name: status
data_tests:
- accepted_values:
arguments:
values: [active, cancelled, past_due, trialing]
- name: stg_contract_discount_terms
description: 'Contract discount terms that explain Q1 2026 enterprise contraction movement.'
columns:
- name: discount_term_id
data_tests:
- not_null
- unique
- name: discount_type
data_tests:
- accepted_values:
arguments:
values: [launch, renewal, migration, goodwill]
- name: stg_arr_movements
description: 'ARR movement ledger used by retention and expansion marts.'
columns:
- name: arr_movement_id
data_tests:
- not_null
- unique
- name: movement_type
data_tests:
- accepted_values:
arguments:
values: [new, expansion, contraction, churn, reactivation]
- name: stg_invoices
description: 'Billing invoices that anchor gross revenue recognition dates.'
columns:
- name: invoice_id
data_tests:
- not_null
- unique
- name: status
data_tests:
- accepted_values:
arguments:
values: [draft, open, paid, void, failed]
- name: currency
data_tests:
- accepted_values:
arguments:
values: [USD]
- name: stg_invoice_line_items
description: 'Invoice line items used to split gross revenue, credits, seats, usage, and addons.'
columns:
- name: invoice_line_item_id
data_tests:
- not_null
- unique
- name: line_item_type
data_tests:
- accepted_values:
arguments:
values: [subscription, seat, usage, addon, credit]
- name: stg_refunds
description: 'Refund events that reduce net revenue in the refund month.'
columns:
- name: refund_id
data_tests:
- not_null
- unique
- name: status
data_tests:
- accepted_values:
arguments:
values: [pending, succeeded, failed, cancelled]
- name: stg_plan_segment_mapping
description: 'Effective-dated mapping from canonical plans and size bands to reporting segments.'
columns:
- name: plan_segment_mapping_id
data_tests:
- not_null
- unique
- name: canonical_plan_code
data_tests:
- accepted_values:
arguments:
values: [starter, growth, enterprise]
- name: size_band
data_tests:
- accepted_values:
arguments:
values: [smb, mid_market, enterprise]
- name: segment
data_tests:
- accepted_values:
arguments:
values: [self_serve, commercial, enterprise]
- name: stg_users
description: 'Orbit user identities shared across warehouse, Slack, Looker, Notion, and Drive artifacts.'
columns:
- name: user_id
data_tests:
- not_null
- unique
- name: stg_activation_events
description: 'Account and requester activation events across the January policy change.'
columns:
- name: activation_event_id
data_tests:
- not_null
- unique
- name: event_type
data_tests:
- accepted_values:
arguments:
values: [first_requester_login, requester_activated, first_approved_purchase_request, account_activated]
- name: policy_version
data_tests:
- accepted_values:
arguments:
values: [pre_2026_01_15, post_2026_01_15]
- name: stg_sessions
description: 'Product sessions used for pre-policy activation and activity exclusions.'
columns:
- name: session_id
data_tests:
- not_null
- unique
- name: stg_purchase_requests
description: 'Procurement request records used for activation, requester activity, and health signals.'
columns:
- name: purchase_request_id
data_tests:
- not_null
- unique
- name: status
data_tests:
- accepted_values:
arguments:
values: [draft, submitted, approved, rejected, cancelled]
- name: stg_approval_events
description: 'Approval decisions tied to procurement requests.'
columns:
- name: approval_event_id
data_tests:
- not_null
- unique
- name: decision
data_tests:
- accepted_values:
arguments:
values: [approved, rejected, returned]
- name: stg_suppliers
description: 'Supplier directory records associated with procurement workflow events.'
columns:
- name: supplier_id
data_tests:
- not_null
- unique
- name: status
data_tests:
- accepted_values:
arguments:
values: [invited, onboarding, active, inactive]
- name: stg_supplier_onboarding_events
description: 'Supplier onboarding milestones that qualify as procurement workflow activity.'
columns:
- name: supplier_onboarding_event_id
data_tests:
- not_null
- unique
- name: event_type
data_tests:
- accepted_values:
arguments:
values: [invited, profile_started, profile_completed, approved]
- name: status
data_tests:
- accepted_values:
arguments:
values: [pending, completed, blocked]
- name: stg_purchase_orders
description: 'Purchase orders generated from approved procurement requests.'
columns:
- name: purchase_order_id
data_tests:
- not_null
- unique
- name: status
data_tests:
- accepted_values:
arguments:
values: [created, sent, fulfilled, cancelled]
- name: stg_support_tickets
description: 'Customer support tickets that inform account health and risk.'
columns:
- name: support_ticket_id
data_tests:
- not_null
- unique
- name: severity
data_tests:
- accepted_values:
arguments:
values: [low, medium, high, critical]
- name: status
data_tests:
- accepted_values:
arguments:
values: [open, pending, solved, closed]
- name: stg_account_owners
description: 'Effective-dated ownership assignments for account health, renewals, and escalation context.'
columns:
- name: account_owner_id
data_tests:
- not_null
- unique
- name: owner_team
data_tests:
- accepted_values:
arguments:
values: [sales_ops, customer_success, finance]
- name: int_active_contract_arr
description: Active contract ARR as of 2026-03-31.
columns:
- name: contract_id
data_tests:
- not_null
- unique
- name: int_parent_account_arr_movements
description: Parent-account movement rollups for retention metrics.
columns:
- name: arr_movement_id
data_tests:
- not_null
- unique
- name: movement_type
data_tests:
- accepted_values:
arguments:
values: [new, expansion, contraction, churn, reactivation]
- name: is_discount_expiration_contraction
description: Discount expiration contraction flag used to keep discount movement separate from churn.
- name: int_revenue_components
description: Daily gross, credit, refund, and net revenue components.
- name: int_procurement_qualifying_actions
description: Non-internal, non-test requester activity for large active contracts in the golden week.
- name: int_activation_policy_windows
description: Activation cohort counts around the January 2026 policy change.
- name: int_customer_health_signals
description: Support-ticket and recent-procurement signals for customer health risk.
- name: mart_arr_daily
description: Board-prep ARR as of the metric date.
meta:
governed_metric_key: arr
owner_team: finance
notion_locator: notion://notion_page_arr_contract_reporting#arr-contract-first
expected_answer: expected-answer://arr_as_of_2026_03_31
columns:
- name: metric_date
data_tests:
- not_null
- unique
- name: arr_cents
data_tests:
- accepted_values:
arguments:
values: [1874200000]
quote: false
- name: mart_nrr_quarterly
description: Enterprise quarterly net revenue retention.
meta:
governed_metric_key: net_revenue_retention
owner_team: analytics
notion_locator: notion://notion_page_retention_policy_current#nrr-definition
expected_answer: expected-answer://enterprise_nrr_q1_vs_q4_breakout
columns:
- name: quarter_label
data_tests:
- not_null
- name: net_revenue_retention
data_tests:
- accepted_values:
arguments:
values: [1.018]
quote: false
config:
where: "quarter_label = '2026-Q1' and segment = 'enterprise'"
- accepted_values:
arguments:
values: [1.064]
quote: false
config:
where: "quarter_label = '2025-Q4' and segment = 'enterprise'"
- name: mart_retention_movement_breakout
description: Q1 2026 enterprise retention movement breakout.
meta:
governed_metric_key: net_revenue_retention
owner_team: analytics
notion_locator: notion://notion_page_retention_policy_current#discount-expiration-treatment
expected_answer: expected-answer://enterprise_expansions_q1_2026
columns:
- name: movement_type
data_tests:
- accepted_values:
arguments:
values: [expansion, contraction, churn]
- name: movement_reason
description: Includes discount_expiration contraction, which is not churn.
- name: parent_account_count
data_tests:
- accepted_values:
arguments:
values: [11]
quote: false
config:
where: "movement_type = 'contraction' and movement_reason = 'discount_expiration'"
- name: expansion_arr_cents
description: Expansion ARR cents for Q1 enterprise movement rows.
- name: mart_revenue_daily
description: Daily revenue mart that reconciles gross, credits, refunds, and net revenue.
meta:
governed_metric_key: net_revenue
owner_team: finance
notion_locator: notion://notion_page_revenue_reporting_policy#gross-to-net-reconciliation
expected_answer: expected-answer://revenue_net_vs_gross_reconciliation
columns:
- name: revenue_date
data_tests:
- not_null
- unique
- name: reconciliation_check
data_tests:
- accepted_values:
arguments:
values: [true]
quote: false
- name: net_revenue_cents
description: Daily net revenue in cents; February 2026 total is covered by assert_february_2026_net_revenue.
- name: mart_account_activity
description: Activation policy comparison values.
meta:
governed_metric_key: activated_accounts
owner_team: growth
notion_locator: notion://notion_page_activation_policy_decision#policy-change
expected_answer: expected-answer://activation_after_policy_change
- name: mart_procurement_activity
description: Weekly active requester counts for large active contracts.
meta:
governed_metric_key: weekly_active_requesters
owner_team: product
notion_locator: notion://notion_page_procurement_instrumentation#qualifying-procurement-actions
expected_answer: expected-answer://active_requesters_last_week_large_contracts
columns:
- name: active_requesters
description: Weekly active requesters for large active contracts.
- name: mart_customer_health
description: Customer-health risk mart as of 2026-03-31.
meta:
governed_metric_key: active_customers
owner_team: customer_success
notion_locator: notion://notion_page_customer_health_playbook#risk-definition
expected_answer: expected-answer://customer_health_risk_accounts
columns:
- name: account_id
data_tests:
- not_null
- unique
- name: risk_level
data_tests:
- accepted_values:
arguments:
values: [low, medium, high]
- name: mart_account_segments
description: Current plan, size band, and reporting segment for accounts.
meta:
governed_metric_key: segment
owner_team: sales_ops
notion_locator: notion://notion_page_sales_ops_segmentation#growth-plan-normalization
expected_answer: expected-answer://enterprise_nrr_q1_vs_q4_breakout
columns:
- name: account_id
data_tests:
- not_null
- unique
- name: normalized_plan_code
description: pro_plus is normalized to growth through plans.canonical_plan_code.

View file

@ -0,0 +1,48 @@
version: 2
sources:
- name: orbit_raw
schema: orbit_raw
tables:
- name: accounts
description: 'Customer and internal/test account records for Orbit.'
- name: account_hierarchy
description: 'Parent-child account relationships used for enterprise retention grain.'
- name: plans
description: 'Canonical and historical Orbit pricing plans.'
- name: contracts
description: 'Contract records that provide contract-first ARR for active accounts.'
- name: subscriptions
description: 'Subscription rows used when active contract ARR is not present for a covered period.'
- name: contract_discount_terms
description: 'Contract discount terms that explain Q1 2026 enterprise contraction movement.'
- name: arr_movements
description: 'ARR movement ledger used by retention and expansion marts.'
- name: invoices
description: 'Billing invoices that anchor gross revenue recognition dates.'
- name: invoice_line_items
description: 'Invoice line items used to split gross revenue, credits, seats, usage, and addons.'
- name: refunds
description: 'Refund events that reduce net revenue in the refund month.'
- name: plan_segment_mapping
description: 'Effective-dated mapping from canonical plans and size bands to reporting segments.'
- name: users
description: 'Orbit user identities shared across warehouse, Slack, Looker, Notion, and Drive artifacts.'
- name: activation_events
description: 'Account and requester activation events across the January policy change.'
- name: sessions
description: 'Product sessions used for pre-policy activation and activity exclusions.'
- name: purchase_requests
description: 'Procurement request records used for activation, requester activity, and health signals.'
- name: approval_events
description: 'Approval decisions tied to procurement requests.'
- name: suppliers
description: 'Supplier directory records associated with procurement workflow events.'
- name: supplier_onboarding_events
description: 'Supplier onboarding milestones that qualify as procurement workflow activity.'
- name: purchase_orders
description: 'Purchase orders generated from approved procurement requests.'
- name: support_tickets
description: 'Customer support tickets that inform account health and risk.'
- name: account_owners
description: 'Effective-dated ownership assignments for account health, renewals, and escalation context.'

View file

@ -0,0 +1,49 @@
---
page_id: notion_page_activation_policy_decision
title: 'Activation Policy Decision Record'
owner_person_key: leo_martin
owner_team: growth
owner_notion_user_id: notion_user_0003
status: current
created_time: 2026-01-10T14:00:00-08:00
last_edited_time: 2026-02-18T11:10:00-08:00
tags:
- growth
- activation
- policy
related_expected_answers:
- activation_after_policy_change
related_metric_keys:
- activated_accounts
anchors:
- notion://notion_page_activation_policy_decision#policy-change
---
# Activation Policy Decision Record
Owner: Leo Martin (growth)
## Policy Change
Anchor: notion://notion_page_activation_policy_decision#policy-change
Before 2026-01-15, account activation means first requester login.
On and after 2026-01-15, account activation means first approved purchase request plus at least three activated requesters.
Activated requesters are non-internal, non-test requester users with either a qualifying session before the policy date or a qualifying procurement action after it.
The governed comparison reports a 0.563 pre-policy 30-day activation rate and a 0.639 post-policy 30-day activation rate.
## Pre-Change Definition
Anchor: notion://notion_page_activation_policy_decision#pre-change-definition
## Post-Change Definition
Anchor: notion://notion_page_activation_policy_decision#post-change-definition
## Dashboard Impact
Anchor: notion://notion_page_activation_policy_decision#dashboard-impact
## Related Evidence
- notion://notion_page_activation_policy_decision#policy-change
- expected-answer://activation_after_policy_change

View file

@ -0,0 +1,35 @@
---
page_id: notion_page_analyst_onboarding
title: 'Analyst Onboarding'
owner_person_key: maya_chen
owner_team: analytics
owner_notion_user_id: notion_user_0001
status: current
created_time: 2026-03-02T09:00:00-08:00
last_edited_time: 2026-03-17T15:30:00-07:00
parent_page_id: notion_page_analytics_team_handbook
tags:
- analytics
- onboarding
source_anchors:
- notion://notion_page_analyst_onboarding#first-week
- lookml://orbit/account_retention.view.lkml#measure=nrr
- slack://analytics-team/2026-03-31/1774942174.200142?thread_ts=1774942174.200142
---
# Analyst Onboarding
Owner: Maya Chen (analytics)
## Operating Context
Anchor: notion://notion_page_analyst_onboarding#analyst-onboarding
New analysts start with dbt://ktx_demo.mart_arr_daily and then review LookML field ownership.
Do not answer metric questions from raw tables when lookml://orbit/account_retention.view.lkml#measure=nrr or a governed mart exists.
Escalate unclear board-week requests in slack://analytics-team/2026-03-31/1774942174.200142?thread_ts=1774942174.200142.
## Source Anchors
- notion://notion_page_analyst_onboarding#first-week
- lookml://orbit/account_retention.view.lkml#measure=nrr
- slack://analytics-team/2026-03-31/1774942174.200142?thread_ts=1774942174.200142

View file

@ -0,0 +1,64 @@
---
page_id: notion_page_arr_contract_reporting
title: 'ARR and Contract Reporting Notes'
owner_person_key: rina_patel
owner_team: finance
owner_notion_user_id: notion_user_0002
status: current
created_time: 2025-11-20T10:30:00-08:00
last_edited_time: 2026-03-26T15:05:00-07:00
tags:
- finance
- arr
- contracts
related_expected_answers:
- arr_as_of_2026_03_31
- active_requesters_last_week_large_contracts
related_metric_keys:
- arr
- contract_arr_band
anchors:
- notion://notion_page_arr_contract_reporting#arr-contract-first
- notion://notion_page_arr_contract_reporting#contract-arr-band
---
# ARR and Contract Reporting Notes
Owner: Rina Patel (finance)
## ARR Contract First
Anchor: notion://notion_page_arr_contract_reporting#arr-contract-first
ARR uses active contract_arr_cents first when a contract covers the account and metric date.
Recurring subscription MRR is annualized only for account periods without active contract ARR.
Contract ARR banding uses active contract ARR as of 2026-03-31, including the contracts over 20000000 cents threshold.
Booked ARR is not active ARR, and internal or test accounts are excluded from board reporting.
## Contract ARR Band
Anchor: notion://notion_page_arr_contract_reporting#contract-arr-band
ARR uses active contract_arr_cents first when a contract covers the account and metric date.
Recurring subscription MRR is annualized only for account periods without active contract ARR.
Contract ARR banding uses active contract ARR as of 2026-03-31, including the contracts over 20000000 cents threshold.
## Booked ARR vs Active ARR
Anchor: notion://notion_page_arr_contract_reporting#booked-arr-vs-active-arr
Booked ARR is not active ARR, and internal or test accounts are excluded from board reporting.
## Internal and Test Exclusions
Anchor: notion://notion_page_arr_contract_reporting#internal-and-test-exclusions
Booked ARR is not active ARR, and internal or test accounts are excluded from board reporting.
## Related Evidence
- notion://notion_page_arr_contract_reporting#arr-contract-first
- notion://notion_page_arr_contract_reporting#contract-arr-band
- expected-answer://arr_as_of_2026_03_31
- expected-answer://active_requesters_last_week_large_contracts

View file

@ -0,0 +1,55 @@
---
page_id: notion_page_customer_health_playbook
title: 'Customer Health Playbook'
owner_person_key: priya_shah
owner_team: customer_success
owner_notion_user_id: notion_user_0005
status: current
created_time: 2026-02-03T13:45:00-08:00
last_edited_time: 2026-03-27T14:50:00-07:00
tags:
- customer-success
- health
- risk
related_expected_answers:
- customer_health_risk_accounts
related_metric_keys:
- active_customers
anchors:
- notion://notion_page_customer_health_playbook#risk-definition
---
# Customer Health Playbook
Owner: Priya Shah (customer_success)
## Risk Definition
Anchor: notion://notion_page_customer_health_playbook#risk-definition
Customer health combines support ticket severity and recent requisition or approval usage.
Active customers must have an active paid subscription and at least one qualifying procurement action in the trailing 30-day window.
High-risk accounts are reviewed for renewal action when severe open tickets coincide with falling workflow activity.
As of 2026-03-31 the governed customer-health mart reports 9 high-risk accounts.
## Support Signals
Anchor: notion://notion_page_customer_health_playbook#support-signals
Customer health combines support ticket severity and recent requisition or approval usage.
## Procurement Activity Signals
Anchor: notion://notion_page_customer_health_playbook#procurement-activity-signals
Active customers must have an active paid subscription and at least one qualifying procurement action in the trailing 30-day window.
## Renewal Review
Anchor: notion://notion_page_customer_health_playbook#renewal-review
High-risk accounts are reviewed for renewal action when severe open tickets coincide with falling workflow activity.
## Related Evidence
- notion://notion_page_customer_health_playbook#risk-definition
- expected-answer://customer_health_risk_accounts

View file

@ -0,0 +1,58 @@
---
page_id: notion_page_retention_policy_current
title: 'Retention and NRR Definition Notes'
owner_person_key: maya_chen
owner_team: analytics
owner_notion_user_id: notion_user_0001
status: current
created_time: 2026-01-08T10:00:00-08:00
last_edited_time: 2026-03-30T16:40:00-07:00
tags:
- analytics
- retention
- board-reporting
related_expected_answers:
- enterprise_nrr_q1_vs_q4_breakout
- enterprise_expansions_q1_2026
related_metric_keys:
- net_revenue_retention
- segment
anchors:
- notion://notion_page_retention_policy_current#nrr-definition
- notion://notion_page_retention_policy_current#discount-expiration-treatment
---
# Retention and NRR Definition Notes
Owner: Maya Chen (analytics)
## NRR Definition
Anchor: notion://notion_page_retention_policy_current#nrr-definition
Enterprise NRR is calculated as (starting_arr + expansion_arr - contraction_arr - churned_arr) / starting_arr.
Movement classification happens after child accounts roll up to parent_account_id.
Reactivations within 30 days are excluded from NRR movement components and kept in audit columns.
Q1 2026 discount expiration is contraction, not churn; the board-prep view calls out 11 enterprise parent accounts.
## Parent-Account Grain
Anchor: notion://notion_page_retention_policy_current#parent-account-grain
## Reactivation Exclusion
Anchor: notion://notion_page_retention_policy_current#reactivation-exclusion
Reactivations within 30 days are excluded from NRR movement components and kept in audit columns.
## Discount Expiration Treatment
Anchor: notion://notion_page_retention_policy_current#discount-expiration-treatment
Q1 2026 discount expiration is contraction, not churn; the board-prep view calls out 11 enterprise parent accounts.
## Related Evidence
- notion://notion_page_retention_policy_current#nrr-definition
- notion://notion_page_retention_policy_current#discount-expiration-treatment
- expected-answer://enterprise_nrr_q1_vs_q4_breakout
- expected-answer://enterprise_expansions_q1_2026

View file

@ -0,0 +1,64 @@
---
page_id: notion_page_revenue_reporting_policy
title: 'Revenue Reporting Policy'
owner_person_key: rina_patel
owner_team: finance
owner_notion_user_id: notion_user_0002
status: current
created_time: 2025-12-12T09:30:00-08:00
last_edited_time: 2026-03-28T13:15:00-07:00
tags:
- finance
- revenue
- board-reporting
related_expected_answers:
- revenue_net_vs_gross_reconciliation
related_metric_keys:
- gross_revenue
- net_revenue
anchors:
- notion://notion_page_revenue_reporting_policy#gross-revenue
- notion://notion_page_revenue_reporting_policy#gross-to-net-reconciliation
---
# Revenue Reporting Policy
Owner: Rina Patel (finance)
## Gross Revenue
Anchor: notion://notion_page_revenue_reporting_policy#gross-revenue
Gross revenue includes paid subscription, seat, usage, and addon invoice line items recognized on invoices.paid_at.
Credit line items are negative in raw invoice lines and reported as absolute credits.
Successful refunds reduce net revenue in the refund month based on refunds.refunded_at.
For February 2026 the governed reconciliation is gross revenue 213000000 cents, credits 13400000 cents, refunds 31200000 cents, and net revenue 168400000 cents.
## Credits
Anchor: notion://notion_page_revenue_reporting_policy#credits
Credit line items are negative in raw invoice lines and reported as absolute credits.
For February 2026 the governed reconciliation is gross revenue 213000000 cents, credits 13400000 cents, refunds 31200000 cents, and net revenue 168400000 cents.
## Refunds
Anchor: notion://notion_page_revenue_reporting_policy#refunds
Successful refunds reduce net revenue in the refund month based on refunds.refunded_at.
For February 2026 the governed reconciliation is gross revenue 213000000 cents, credits 13400000 cents, refunds 31200000 cents, and net revenue 168400000 cents.
## Gross To Net Reconciliation
Anchor: notion://notion_page_revenue_reporting_policy#gross-to-net-reconciliation
Gross revenue includes paid subscription, seat, usage, and addon invoice line items recognized on invoices.paid_at.
For February 2026 the governed reconciliation is gross revenue 213000000 cents, credits 13400000 cents, refunds 31200000 cents, and net revenue 168400000 cents.
## Related Evidence
- notion://notion_page_revenue_reporting_policy#gross-revenue
- notion://notion_page_revenue_reporting_policy#gross-to-net-reconciliation
- expected-answer://revenue_net_vs_gross_reconciliation

View file

@ -0,0 +1,58 @@
---
page_id: notion_page_sales_ops_segmentation
title: 'Sales Ops Segmentation Guide'
owner_person_key: jordan_lee
owner_team: sales_ops
owner_notion_user_id: notion_user_0004
status: current
created_time: 2025-10-03T09:00:00-07:00
last_edited_time: 2026-03-25T10:35:00-07:00
tags:
- sales-ops
- segmentation
- plans
related_expected_answers:
- enterprise_nrr_q1_vs_q4_breakout
- enterprise_expansions_q1_2026
related_metric_keys:
- segment
- net_revenue_retention
anchors:
- notion://notion_page_sales_ops_segmentation#growth-plan-normalization
---
# Sales Ops Segmentation Guide
Owner: Jordan Lee (sales_ops)
## Growth Plan Normalization
Anchor: notion://notion_page_sales_ops_segmentation#growth-plan-normalization
The current plan language is starter, growth, and enterprise.
Raw historical pro_plus values normalize to growth for current artifacts after 2025-10-01.
Retention cohort membership is evaluated at quarter start unless the golden question states another as-of date.
Segment membership changes are bridge items and are not silently classified as expansion or churn.
## Segment Membership
Anchor: notion://notion_page_sales_ops_segmentation#segment-membership
Segment membership changes are bridge items and are not silently classified as expansion or churn.
## Quarter Start Cohorts
Anchor: notion://notion_page_sales_ops_segmentation#quarter-start-cohorts
Retention cohort membership is evaluated at quarter start unless the golden question states another as-of date.
## Historical Plan Alias
Anchor: notion://notion_page_sales_ops_segmentation#historical-plan-alias
Raw historical pro_plus values normalize to growth for current artifacts after 2025-10-01.
## Related Evidence
- notion://notion_page_sales_ops_segmentation#growth-plan-normalization
- expected-answer://enterprise_nrr_q1_vs_q4_breakout
- expected-answer://enterprise_expansions_q1_2026

View file

@ -0,0 +1,38 @@
---
page_id: notion_page_support_escalation_runbook
title: 'Support Escalation Runbook'
owner_person_key: priya_shah
owner_team: customer_success
owner_notion_user_id: notion_user_0005
status: current
created_time: 2026-03-06T09:00:00-08:00
last_edited_time: 2026-03-21T15:30:00-07:00
parent_page_id: notion_page_customer_health_playbook
tags:
- customer-success
- support
- risk
source_anchors:
- notion://notion_page_support_escalation_runbook#triage
- looker://dashboard/dash_customer_health_risk
- drive://drive_file_customer_health_scorecard_q1#high-risk-accounts
- slack://customer-success/2026-03-31/1774976400.000100?thread_ts=1774976400.000100
---
# Support Escalation Runbook
Owner: Priya Shah (customer_success)
## Operating Context
Anchor: notion://notion_page_support_escalation_runbook#support-escalation-runbook
High-risk review combines open support tickets with recent requisition and approval activity drops.
Use looker://dashboard/dash_customer_health_risk for the list and drive://drive_file_customer_health_scorecard_q1#high-risk-accounts for the scorecard.
Escalations are coordinated in slack://customer-success/2026-03-31/1774976400.000100?thread_ts=1774976400.000100.
## Source Anchors
- notion://notion_page_support_escalation_runbook#triage
- looker://dashboard/dash_customer_health_risk
- drive://drive_file_customer_health_scorecard_q1#high-risk-accounts
- slack://customer-success/2026-03-31/1774976400.000100?thread_ts=1774976400.000100

View file

@ -0,0 +1,211 @@
account_id,parent_account_id,account_name,domain,industry,sales_region,size_band,lifecycle_status,is_internal,is_test,created_at
acct_0001,parent_0001,Orbit Customer 001,customer-001.example.com,software,na,enterprise,active,false,false,2025-01-01T00:00:00Z
acct_0002,parent_0002,Orbit Customer 002,customer-002.example.com,manufacturing,emea,enterprise,active,false,false,2025-02-02T00:00:00Z
acct_0003,parent_0003,Orbit Customer 003,customer-003.example.com,healthcare,apac,enterprise,active,false,false,2025-03-03T00:00:00Z
acct_0004,parent_0004,Orbit Customer 004,customer-004.example.com,financial_services,na,enterprise,active,false,false,2025-04-04T00:00:00Z
acct_0005,parent_0005,Orbit Customer 005,customer-005.example.com,retail,emea,enterprise,active,false,false,2025-05-05T00:00:00Z
acct_0006,parent_0006,Orbit Customer 006,customer-006.example.com,software,apac,enterprise,active,false,false,2025-06-06T00:00:00Z
acct_0007,parent_0007,Orbit Customer 007,customer-007.example.com,manufacturing,na,enterprise,active,false,false,2025-07-07T00:00:00Z
acct_0008,parent_0008,Orbit Customer 008,customer-008.example.com,healthcare,emea,enterprise,active,false,false,2025-08-08T00:00:00Z
acct_0009,parent_0009,Orbit Customer 009,customer-009.example.com,financial_services,apac,enterprise,active,false,false,2025-09-09T00:00:00Z
acct_0010,parent_0010,Orbit Customer 010,customer-010.example.com,retail,na,enterprise,active,false,false,2025-10-10T00:00:00Z
acct_0011,parent_0011,Orbit Customer 011,customer-011.example.com,software,emea,enterprise,active,false,false,2025-11-11T00:00:00Z
acct_0012,parent_0012,Orbit Customer 012,customer-012.example.com,manufacturing,apac,enterprise,active,false,false,2025-12-12T00:00:00Z
acct_0013,parent_0013,Orbit Customer 013,customer-013.example.com,healthcare,na,enterprise,active,false,false,2025-01-13T00:00:00Z
acct_0014,parent_0014,Orbit Customer 014,customer-014.example.com,financial_services,emea,enterprise,active,false,false,2025-02-14T00:00:00Z
acct_0015,parent_0015,Orbit Customer 015,customer-015.example.com,retail,apac,enterprise,active,false,false,2025-03-15T00:00:00Z
acct_0016,parent_0016,Orbit Customer 016,customer-016.example.com,software,na,enterprise,active,false,false,2025-04-16T00:00:00Z
acct_0017,parent_0017,Orbit Customer 017,customer-017.example.com,manufacturing,emea,enterprise,active,false,false,2025-05-17T00:00:00Z
acct_0018,parent_0018,Orbit Customer 018,customer-018.example.com,healthcare,apac,enterprise,active,false,false,2025-06-18T00:00:00Z
acct_0019,parent_0019,Orbit Customer 019,customer-019.example.com,financial_services,na,enterprise,active,false,false,2025-07-19T00:00:00Z
acct_0020,parent_0020,Orbit Customer 020,customer-020.example.com,retail,emea,enterprise,active,false,false,2025-08-20T00:00:00Z
acct_0021,parent_0021,Orbit Customer 021,customer-021.example.com,software,apac,enterprise,active,false,false,2025-09-21T00:00:00Z
acct_0022,parent_0022,Orbit Customer 022,customer-022.example.com,manufacturing,na,enterprise,active,false,false,2025-10-22T00:00:00Z
acct_0023,parent_0023,Orbit Customer 023,customer-023.example.com,healthcare,emea,enterprise,active,false,false,2025-11-23T00:00:00Z
acct_0024,parent_0024,Orbit Customer 024,customer-024.example.com,financial_services,apac,enterprise,active,false,false,2025-12-24T00:00:00Z
acct_0025,parent_0025,Orbit Customer 025,customer-025.example.com,retail,na,enterprise,active,false,false,2025-01-25T00:00:00Z
acct_0026,parent_0026,Orbit Customer 026,customer-026.example.com,software,emea,enterprise,active,false,false,2025-02-26T00:00:00Z
acct_0027,parent_0027,Orbit Customer 027,customer-027.example.com,manufacturing,apac,enterprise,active,false,false,2025-03-27T00:00:00Z
acct_0028,parent_0028,Orbit Customer 028,customer-028.example.com,healthcare,na,enterprise,active,false,false,2025-04-28T00:00:00Z
acct_0029,parent_0029,Orbit Customer 029,customer-029.example.com,financial_services,emea,enterprise,active,false,false,2025-05-01T00:00:00Z
acct_0030,parent_0030,Orbit Customer 030,customer-030.example.com,retail,apac,enterprise,active,false,false,2025-06-02T00:00:00Z
acct_0031,parent_0031,Orbit Customer 031,customer-031.example.com,software,na,enterprise,active,false,false,2025-07-03T00:00:00Z
acct_0032,parent_0032,Orbit Customer 032,customer-032.example.com,manufacturing,emea,enterprise,active,false,false,2025-08-04T00:00:00Z
acct_0033,parent_0033,Orbit Customer 033,customer-033.example.com,healthcare,apac,enterprise,active,false,false,2025-09-05T00:00:00Z
acct_0034,parent_0034,Orbit Customer 034,customer-034.example.com,financial_services,na,enterprise,active,false,false,2025-10-06T00:00:00Z
acct_0035,parent_0035,Orbit Customer 035,customer-035.example.com,retail,emea,enterprise,active,false,false,2025-11-07T00:00:00Z
acct_0036,parent_0036,Orbit Customer 036,customer-036.example.com,software,apac,enterprise,active,false,false,2025-12-08T00:00:00Z
acct_0037,parent_0037,Orbit Customer 037,customer-037.example.com,manufacturing,na,enterprise,active,false,false,2025-01-09T00:00:00Z
acct_0038,parent_0038,Orbit Customer 038,customer-038.example.com,healthcare,emea,enterprise,active,false,false,2025-02-10T00:00:00Z
acct_0039,parent_0039,Orbit Customer 039,customer-039.example.com,financial_services,apac,enterprise,active,false,false,2025-03-11T00:00:00Z
acct_0040,parent_0040,Orbit Customer 040,customer-040.example.com,retail,na,enterprise,active,false,false,2025-04-12T00:00:00Z
acct_0041,parent_0041,Orbit Customer 041,customer-041.example.com,software,emea,enterprise,active,false,false,2025-05-13T00:00:00Z
acct_0042,parent_0042,Orbit Customer 042,customer-042.example.com,manufacturing,apac,enterprise,active,false,false,2025-06-14T00:00:00Z
acct_0043,parent_0043,Orbit Customer 043,customer-043.example.com,healthcare,na,enterprise,active,false,false,2025-07-15T00:00:00Z
acct_0044,parent_0044,Orbit Customer 044,customer-044.example.com,financial_services,emea,enterprise,active,false,false,2025-08-16T00:00:00Z
acct_0045,parent_0045,Orbit Customer 045,customer-045.example.com,retail,apac,enterprise,active,false,false,2025-09-17T00:00:00Z
acct_0046,parent_0046,Orbit Customer 046,customer-046.example.com,software,na,enterprise,active,false,false,2025-10-18T00:00:00Z
acct_0047,parent_0047,Orbit Customer 047,customer-047.example.com,manufacturing,emea,enterprise,active,false,false,2025-11-19T00:00:00Z
acct_0048,parent_0048,Orbit Customer 048,customer-048.example.com,healthcare,apac,enterprise,active,false,false,2025-12-20T00:00:00Z
acct_0049,parent_0049,Orbit Customer 049,customer-049.example.com,financial_services,na,enterprise,active,false,false,2025-01-21T00:00:00Z
acct_0050,parent_0050,Orbit Customer 050,customer-050.example.com,retail,emea,enterprise,active,false,false,2025-02-22T00:00:00Z
acct_0051,parent_0051,Orbit Customer 051,customer-051.example.com,software,apac,enterprise,active,false,false,2025-03-23T00:00:00Z
acct_0052,parent_0052,Orbit Customer 052,customer-052.example.com,manufacturing,na,enterprise,active,false,false,2025-04-24T00:00:00Z
acct_0053,parent_0053,Orbit Customer 053,customer-053.example.com,healthcare,emea,enterprise,active,false,false,2025-05-25T00:00:00Z
acct_0054,parent_0054,Orbit Customer 054,customer-054.example.com,financial_services,apac,enterprise,active,false,false,2025-06-26T00:00:00Z
acct_0055,parent_0055,Orbit Customer 055,customer-055.example.com,retail,na,enterprise,active,false,false,2025-07-27T00:00:00Z
acct_0056,parent_0056,Orbit Customer 056,customer-056.example.com,software,emea,enterprise,active,false,false,2025-08-28T00:00:00Z
acct_0057,parent_0057,Orbit Customer 057,customer-057.example.com,manufacturing,apac,enterprise,active,false,false,2025-09-01T00:00:00Z
acct_0058,parent_0058,Orbit Customer 058,customer-058.example.com,healthcare,na,enterprise,active,false,false,2025-10-02T00:00:00Z
acct_0059,parent_0059,Orbit Customer 059,customer-059.example.com,financial_services,emea,enterprise,active,false,false,2025-11-03T00:00:00Z
acct_0060,parent_0060,Orbit Customer 060,customer-060.example.com,retail,apac,enterprise,active,false,false,2025-12-04T00:00:00Z
acct_0061,parent_0061,Orbit Customer 061,customer-061.example.com,software,na,enterprise,active,false,false,2025-01-05T00:00:00Z
acct_0062,parent_0062,Orbit Customer 062,customer-062.example.com,manufacturing,emea,enterprise,active,false,false,2025-02-06T00:00:00Z
acct_0063,parent_0063,Orbit Customer 063,customer-063.example.com,healthcare,apac,enterprise,active,false,false,2025-03-07T00:00:00Z
acct_0064,parent_0064,Orbit Customer 064,customer-064.example.com,financial_services,na,enterprise,active,false,false,2025-04-08T00:00:00Z
acct_0065,parent_0065,Orbit Customer 065,customer-065.example.com,retail,emea,enterprise,active,false,false,2025-05-09T00:00:00Z
acct_0066,parent_0066,Orbit Customer 066,customer-066.example.com,software,apac,enterprise,active,false,false,2025-06-10T00:00:00Z
acct_0067,parent_0067,Orbit Customer 067,customer-067.example.com,manufacturing,na,enterprise,active,false,false,2025-07-11T00:00:00Z
acct_0068,parent_0068,Orbit Customer 068,customer-068.example.com,healthcare,emea,enterprise,active,false,false,2025-08-12T00:00:00Z
acct_0069,parent_0069,Orbit Customer 069,customer-069.example.com,financial_services,apac,enterprise,active,false,false,2025-09-13T00:00:00Z
acct_0070,parent_0070,Orbit Customer 070,customer-070.example.com,retail,na,enterprise,active,false,false,2025-10-14T00:00:00Z
acct_0071,parent_0071,Orbit Customer 071,customer-071.example.com,software,emea,enterprise,active,false,false,2025-11-15T00:00:00Z
acct_0072,parent_0072,Orbit Customer 072,customer-072.example.com,manufacturing,apac,enterprise,active,false,false,2025-12-16T00:00:00Z
acct_0073,parent_0073,Orbit Customer 073,customer-073.example.com,healthcare,na,enterprise,active,false,false,2025-01-17T00:00:00Z
acct_0074,parent_0074,Orbit Customer 074,customer-074.example.com,financial_services,emea,enterprise,active,false,false,2025-02-18T00:00:00Z
acct_0075,parent_0075,Orbit Customer 075,customer-075.example.com,retail,apac,enterprise,active,false,false,2025-03-19T00:00:00Z
acct_0076,parent_0076,Orbit Customer 076,customer-076.example.com,software,na,enterprise,active,false,false,2025-04-20T00:00:00Z
acct_0077,parent_0077,Orbit Customer 077,customer-077.example.com,manufacturing,emea,enterprise,active,false,false,2025-05-21T00:00:00Z
acct_0078,parent_0078,Orbit Customer 078,customer-078.example.com,healthcare,apac,enterprise,active,false,false,2025-06-22T00:00:00Z
acct_0079,parent_0079,Orbit Customer 079,customer-079.example.com,financial_services,na,enterprise,active,false,false,2025-07-23T00:00:00Z
acct_0080,parent_0080,Orbit Customer 080,customer-080.example.com,retail,emea,enterprise,active,false,false,2025-08-24T00:00:00Z
acct_0081,parent_0081,Orbit Customer 081,customer-081.example.com,software,apac,mid_market,active,false,false,2025-09-25T00:00:00Z
acct_0082,parent_0082,Orbit Customer 082,customer-082.example.com,manufacturing,na,mid_market,active,false,false,2025-10-26T00:00:00Z
acct_0083,parent_0083,Orbit Customer 083,customer-083.example.com,healthcare,emea,mid_market,active,false,false,2025-11-27T00:00:00Z
acct_0084,parent_0084,Orbit Customer 084,customer-084.example.com,financial_services,apac,mid_market,active,false,false,2025-12-28T00:00:00Z
acct_0085,parent_0085,Orbit Customer 085,customer-085.example.com,retail,na,mid_market,active,false,false,2025-01-01T00:00:00Z
acct_0086,parent_0086,Orbit Customer 086,customer-086.example.com,software,emea,mid_market,active,false,false,2025-02-02T00:00:00Z
acct_0087,parent_0087,Orbit Customer 087,customer-087.example.com,manufacturing,apac,mid_market,active,false,false,2025-03-03T00:00:00Z
acct_0088,parent_0088,Orbit Customer 088,customer-088.example.com,healthcare,na,mid_market,active,false,false,2025-04-04T00:00:00Z
acct_0089,parent_0089,Orbit Customer 089,customer-089.example.com,financial_services,emea,mid_market,active,false,false,2025-05-05T00:00:00Z
acct_0090,parent_0090,Orbit Customer 090,customer-090.example.com,retail,apac,mid_market,active,false,false,2025-06-06T00:00:00Z
acct_0091,parent_0091,Orbit Customer 091,customer-091.example.com,software,na,mid_market,active,false,false,2025-07-07T00:00:00Z
acct_0092,parent_0092,Orbit Customer 092,customer-092.example.com,manufacturing,emea,mid_market,active,false,false,2025-08-08T00:00:00Z
acct_0093,parent_0093,Orbit Customer 093,customer-093.example.com,healthcare,apac,mid_market,active,false,false,2025-09-09T00:00:00Z
acct_0094,parent_0094,Orbit Customer 094,customer-094.example.com,financial_services,na,mid_market,active,false,false,2025-10-10T00:00:00Z
acct_0095,parent_0095,Orbit Customer 095,customer-095.example.com,retail,emea,mid_market,active,false,false,2025-11-11T00:00:00Z
acct_0096,parent_0096,Orbit Customer 096,customer-096.example.com,software,apac,mid_market,active,false,false,2025-12-12T00:00:00Z
acct_0097,parent_0097,Orbit Customer 097,customer-097.example.com,manufacturing,na,mid_market,active,false,false,2025-01-13T00:00:00Z
acct_0098,parent_0098,Orbit Customer 098,customer-098.example.com,healthcare,emea,mid_market,active,false,false,2025-02-14T00:00:00Z
acct_0099,parent_0099,Orbit Customer 099,customer-099.example.com,financial_services,apac,mid_market,active,false,false,2025-03-15T00:00:00Z
acct_0100,parent_0100,Orbit Customer 100,customer-100.example.com,retail,na,mid_market,active,false,false,2025-04-16T00:00:00Z
acct_0101,parent_0101,Orbit Customer 101,customer-101.example.com,software,emea,mid_market,active,false,false,2025-05-17T00:00:00Z
acct_0102,parent_0102,Orbit Customer 102,customer-102.example.com,manufacturing,apac,mid_market,active,false,false,2025-06-18T00:00:00Z
acct_0103,parent_0103,Orbit Customer 103,customer-103.example.com,healthcare,na,mid_market,active,false,false,2025-07-19T00:00:00Z
acct_0104,parent_0104,Orbit Customer 104,customer-104.example.com,financial_services,emea,mid_market,active,false,false,2025-08-20T00:00:00Z
acct_0105,parent_0105,Orbit Customer 105,customer-105.example.com,retail,apac,mid_market,active,false,false,2025-09-21T00:00:00Z
acct_0106,parent_0106,Orbit Customer 106,customer-106.example.com,software,na,mid_market,active,false,false,2025-10-22T00:00:00Z
acct_0107,parent_0107,Orbit Customer 107,customer-107.example.com,manufacturing,emea,mid_market,active,false,false,2025-11-23T00:00:00Z
acct_0108,parent_0108,Orbit Customer 108,customer-108.example.com,healthcare,apac,mid_market,active,false,false,2025-12-24T00:00:00Z
acct_0109,parent_0109,Orbit Customer 109,customer-109.example.com,financial_services,na,mid_market,active,false,false,2025-01-25T00:00:00Z
acct_0110,parent_0110,Orbit Customer 110,customer-110.example.com,retail,emea,mid_market,active,false,false,2025-02-26T00:00:00Z
acct_0111,parent_0111,Orbit Customer 111,customer-111.example.com,software,apac,mid_market,active,false,false,2025-03-27T00:00:00Z
acct_0112,parent_0112,Orbit Customer 112,customer-112.example.com,manufacturing,na,mid_market,active,false,false,2025-04-28T00:00:00Z
acct_0113,parent_0113,Orbit Customer 113,customer-113.example.com,healthcare,emea,mid_market,active,false,false,2025-05-01T00:00:00Z
acct_0114,parent_0114,Orbit Customer 114,customer-114.example.com,financial_services,apac,mid_market,active,false,false,2025-06-02T00:00:00Z
acct_0115,parent_0115,Orbit Customer 115,customer-115.example.com,retail,na,mid_market,active,false,false,2025-07-03T00:00:00Z
acct_0116,parent_0116,Orbit Customer 116,customer-116.example.com,software,emea,mid_market,active,false,false,2025-08-04T00:00:00Z
acct_0117,parent_0117,Orbit Customer 117,customer-117.example.com,manufacturing,apac,mid_market,active,false,false,2025-09-05T00:00:00Z
acct_0118,parent_0118,Orbit Customer 118,customer-118.example.com,healthcare,na,mid_market,active,false,false,2025-10-06T00:00:00Z
acct_0119,parent_0119,Orbit Customer 119,customer-119.example.com,financial_services,emea,mid_market,active,false,false,2025-11-07T00:00:00Z
acct_0120,parent_0120,Orbit Customer 120,customer-120.example.com,retail,apac,mid_market,active,false,false,2025-12-08T00:00:00Z
acct_0121,parent_0121,Orbit Customer 121,customer-121.example.com,software,na,mid_market,active,false,false,2025-01-09T00:00:00Z
acct_0122,parent_0122,Orbit Customer 122,customer-122.example.com,manufacturing,emea,mid_market,active,false,false,2025-02-10T00:00:00Z
acct_0123,parent_0123,Orbit Customer 123,customer-123.example.com,healthcare,apac,mid_market,active,false,false,2025-03-11T00:00:00Z
acct_0124,parent_0124,Orbit Customer 124,customer-124.example.com,financial_services,na,mid_market,active,false,false,2025-04-12T00:00:00Z
acct_0125,parent_0125,Orbit Customer 125,customer-125.example.com,retail,emea,mid_market,active,false,false,2025-05-13T00:00:00Z
acct_0126,parent_0126,Orbit Customer 126,customer-126.example.com,software,apac,mid_market,active,false,false,2025-06-14T00:00:00Z
acct_0127,parent_0127,Orbit Customer 127,customer-127.example.com,manufacturing,na,mid_market,active,false,false,2025-07-15T00:00:00Z
acct_0128,parent_0128,Orbit Customer 128,customer-128.example.com,healthcare,emea,mid_market,active,false,false,2025-08-16T00:00:00Z
acct_0129,parent_0129,Orbit Customer 129,customer-129.example.com,financial_services,apac,mid_market,active,false,false,2025-09-17T00:00:00Z
acct_0130,parent_0130,Orbit Customer 130,customer-130.example.com,retail,na,mid_market,active,false,false,2025-10-18T00:00:00Z
acct_0131,parent_0131,Orbit Customer 131,customer-131.example.com,software,emea,mid_market,active,false,false,2025-11-19T00:00:00Z
acct_0132,parent_0132,Orbit Customer 132,customer-132.example.com,manufacturing,apac,mid_market,active,false,false,2025-12-20T00:00:00Z
acct_0133,parent_0001,Orbit Customer 133,customer-133.example.com,healthcare,na,mid_market,active,false,false,2025-01-21T00:00:00Z
acct_0134,parent_0002,Orbit Customer 134,customer-134.example.com,financial_services,emea,mid_market,active,false,false,2025-02-22T00:00:00Z
acct_0135,parent_0003,Orbit Customer 135,customer-135.example.com,retail,apac,mid_market,active,false,false,2025-03-23T00:00:00Z
acct_0136,parent_0004,Orbit Customer 136,customer-136.example.com,software,na,mid_market,active,false,false,2025-04-24T00:00:00Z
acct_0137,parent_0005,Orbit Customer 137,customer-137.example.com,manufacturing,emea,mid_market,active,false,false,2025-05-25T00:00:00Z
acct_0138,parent_0006,Orbit Customer 138,customer-138.example.com,healthcare,apac,mid_market,active,false,false,2025-06-26T00:00:00Z
acct_0139,parent_0007,Orbit Customer 139,customer-139.example.com,financial_services,na,mid_market,active,false,false,2025-07-27T00:00:00Z
acct_0140,parent_0008,Orbit Customer 140,customer-140.example.com,retail,emea,mid_market,active,false,false,2025-08-28T00:00:00Z
acct_0141,parent_0009,Orbit Customer 141,customer-141.example.com,software,apac,mid_market,active,false,false,2025-09-01T00:00:00Z
acct_0142,parent_0010,Orbit Customer 142,customer-142.example.com,manufacturing,na,mid_market,active,false,false,2025-10-02T00:00:00Z
acct_0143,parent_0011,Orbit Customer 143,customer-143.example.com,healthcare,emea,mid_market,active,false,false,2025-11-03T00:00:00Z
acct_0144,parent_0012,Orbit Customer 144,customer-144.example.com,financial_services,apac,mid_market,active,false,false,2025-12-04T00:00:00Z
acct_0145,parent_0013,Orbit Customer 145,customer-145.example.com,retail,na,mid_market,active,false,false,2025-01-05T00:00:00Z
acct_0146,parent_0014,Orbit Customer 146,customer-146.example.com,software,emea,mid_market,active,false,false,2025-02-06T00:00:00Z
acct_0147,parent_0015,Orbit Customer 147,customer-147.example.com,manufacturing,apac,mid_market,active,false,false,2025-03-07T00:00:00Z
acct_0148,parent_0016,Orbit Customer 148,customer-148.example.com,healthcare,na,mid_market,active,false,false,2025-04-08T00:00:00Z
acct_0149,parent_0017,Orbit Customer 149,customer-149.example.com,financial_services,emea,mid_market,active,false,false,2025-05-09T00:00:00Z
acct_0150,parent_0018,Orbit Customer 150,customer-150.example.com,retail,apac,mid_market,active,false,false,2025-06-10T00:00:00Z
acct_0151,parent_0019,Orbit Customer 151,customer-151.example.com,software,na,smb,active,false,false,2025-07-11T00:00:00Z
acct_0152,parent_0020,Orbit Customer 152,customer-152.example.com,manufacturing,emea,smb,active,false,false,2025-08-12T00:00:00Z
acct_0153,parent_0021,Orbit Customer 153,customer-153.example.com,healthcare,apac,smb,active,false,false,2025-09-13T00:00:00Z
acct_0154,parent_0022,Orbit Customer 154,customer-154.example.com,financial_services,na,smb,active,false,false,2025-10-14T00:00:00Z
acct_0155,parent_0023,Orbit Customer 155,customer-155.example.com,retail,emea,smb,active,false,false,2025-11-15T00:00:00Z
acct_0156,parent_0024,Orbit Customer 156,customer-156.example.com,software,apac,smb,active,false,false,2025-12-16T00:00:00Z
acct_0157,parent_0025,Orbit Customer 157,customer-157.example.com,manufacturing,na,smb,active,false,false,2025-01-17T00:00:00Z
acct_0158,parent_0026,Orbit Customer 158,customer-158.example.com,healthcare,emea,smb,active,false,false,2025-02-18T00:00:00Z
acct_0159,parent_0027,Orbit Customer 159,customer-159.example.com,financial_services,apac,smb,active,false,false,2025-03-19T00:00:00Z
acct_0160,parent_0028,Orbit Customer 160,customer-160.example.com,retail,na,smb,active,false,false,2025-04-20T00:00:00Z
acct_0161,parent_0029,Orbit Customer 161,customer-161.example.com,software,emea,smb,active,false,false,2025-05-21T00:00:00Z
acct_0162,parent_0030,Orbit Customer 162,customer-162.example.com,manufacturing,apac,smb,active,false,false,2025-06-22T00:00:00Z
acct_0163,parent_0031,Orbit Customer 163,customer-163.example.com,healthcare,na,smb,active,false,false,2025-07-23T00:00:00Z
acct_0164,parent_0032,Orbit Customer 164,customer-164.example.com,financial_services,emea,smb,active,false,false,2025-08-24T00:00:00Z
acct_0165,parent_0033,Orbit Customer 165,customer-165.example.com,retail,apac,smb,active,false,false,2025-09-25T00:00:00Z
acct_0166,parent_0034,Orbit Customer 166,customer-166.example.com,software,na,smb,active,false,false,2025-10-26T00:00:00Z
acct_0167,parent_0035,Orbit Customer 167,customer-167.example.com,manufacturing,emea,smb,active,false,false,2025-11-27T00:00:00Z
acct_0168,parent_0036,Orbit Customer 168,customer-168.example.com,healthcare,apac,smb,active,false,false,2025-12-28T00:00:00Z
acct_0169,parent_0037,Orbit Customer 169,customer-169.example.com,financial_services,na,smb,active,false,false,2025-01-01T00:00:00Z
acct_0170,parent_0038,Orbit Customer 170,customer-170.example.com,retail,emea,smb,active,false,false,2025-02-02T00:00:00Z
acct_0171,parent_0039,Orbit Customer 171,customer-171.example.com,software,apac,smb,active,false,false,2025-03-03T00:00:00Z
acct_0172,parent_0040,Orbit Customer 172,customer-172.example.com,manufacturing,na,smb,active,false,false,2025-04-04T00:00:00Z
acct_0173,parent_0041,Orbit Customer 173,customer-173.example.com,healthcare,emea,smb,active,false,false,2025-05-05T00:00:00Z
acct_0174,parent_0042,Orbit Customer 174,customer-174.example.com,financial_services,apac,smb,active,false,false,2025-06-06T00:00:00Z
acct_0175,parent_0043,Orbit Customer 175,customer-175.example.com,retail,na,smb,active,false,false,2025-07-07T00:00:00Z
acct_0176,parent_0044,Orbit Customer 176,customer-176.example.com,software,emea,smb,active,false,false,2025-08-08T00:00:00Z
acct_0177,parent_0045,Orbit Customer 177,customer-177.example.com,manufacturing,apac,smb,active,false,false,2025-09-09T00:00:00Z
acct_0178,parent_0046,Orbit Customer 178,customer-178.example.com,healthcare,na,smb,active,false,false,2025-10-10T00:00:00Z
acct_0179,parent_0047,Orbit Customer 179,customer-179.example.com,financial_services,emea,smb,active,false,false,2025-11-11T00:00:00Z
acct_0180,parent_0048,Orbit Customer 180,customer-180.example.com,retail,apac,smb,active,false,false,2025-12-12T00:00:00Z
acct_0181,parent_0049,Orbit Customer 181,customer-181.example.com,software,na,smb,active,false,false,2025-01-13T00:00:00Z
acct_0182,parent_0050,Orbit Customer 182,customer-182.example.com,manufacturing,emea,smb,active,false,false,2025-02-14T00:00:00Z
acct_0183,parent_0051,Orbit Customer 183,customer-183.example.com,healthcare,apac,smb,active,false,false,2025-03-15T00:00:00Z
acct_0184,parent_0052,Orbit Customer 184,customer-184.example.com,financial_services,na,smb,active,false,false,2025-04-16T00:00:00Z
acct_0185,parent_0053,Orbit Customer 185,customer-185.example.com,retail,emea,smb,active,false,false,2025-05-17T00:00:00Z
acct_0186,parent_0054,Orbit Customer 186,customer-186.example.com,software,apac,smb,active,false,false,2025-06-18T00:00:00Z
acct_0187,parent_0055,Orbit Customer 187,customer-187.example.com,manufacturing,na,smb,active,false,false,2025-07-19T00:00:00Z
acct_0188,parent_0056,Orbit Customer 188,customer-188.example.com,healthcare,emea,smb,active,false,false,2025-08-20T00:00:00Z
acct_0189,parent_0057,Orbit Customer 189,customer-189.example.com,financial_services,apac,smb,active,false,false,2025-09-21T00:00:00Z
acct_0190,parent_0058,Orbit Customer 190,customer-190.example.com,retail,na,smb,active,false,false,2025-10-22T00:00:00Z
acct_0191,parent_0059,Orbit Customer 191,customer-191.example.com,software,emea,smb,active,false,false,2025-11-23T00:00:00Z
acct_0192,parent_0060,Orbit Customer 192,customer-192.example.com,manufacturing,apac,smb,active,false,false,2025-12-24T00:00:00Z
acct_0193,parent_0061,Orbit Customer 193,customer-193.example.com,healthcare,na,smb,active,false,false,2025-01-25T00:00:00Z
acct_0194,parent_0062,Orbit Customer 194,customer-194.example.com,financial_services,emea,smb,active,false,false,2025-02-26T00:00:00Z
acct_0195,parent_0063,Orbit Customer 195,customer-195.example.com,retail,apac,smb,active,false,false,2025-03-27T00:00:00Z
acct_0196,parent_0064,Orbit Customer 196,customer-196.example.com,software,na,smb,active,false,false,2025-04-28T00:00:00Z
acct_0197,parent_0065,Orbit Customer 197,customer-197.example.com,manufacturing,emea,smb,active,false,false,2025-05-01T00:00:00Z
acct_0198,parent_0066,Orbit Customer 198,customer-198.example.com,healthcare,apac,smb,active,false,false,2025-06-02T00:00:00Z
acct_0199,parent_0067,Orbit Customer 199,customer-199.example.com,financial_services,na,smb,churned,false,false,2025-07-03T00:00:00Z
acct_0200,parent_0068,Orbit Customer 200,customer-200.example.com,retail,emea,smb,churned,false,false,2025-08-04T00:00:00Z
acct_0201,parent_0069,Orbit Customer 201,customer-201.example.com,software,apac,smb,internal,true,false,2025-09-05T00:00:00Z
acct_0202,parent_0070,Orbit Customer 202,customer-202.example.com,manufacturing,na,smb,internal,true,false,2025-10-06T00:00:00Z
acct_0203,parent_0071,Orbit Customer 203,customer-203.example.com,healthcare,emea,smb,internal,true,false,2025-11-07T00:00:00Z
acct_0204,parent_0072,Orbit Customer 204,customer-204.example.com,financial_services,apac,smb,internal,true,false,2025-12-08T00:00:00Z
acct_0205,parent_0073,Orbit Customer 205,customer-205.example.com,retail,na,smb,internal,true,false,2025-01-09T00:00:00Z
acct_0206,parent_0074,Orbit Customer 206,customer-206.example.com,software,emea,smb,test,false,true,2025-02-10T00:00:00Z
acct_0207,parent_0075,Orbit Customer 207,customer-207.example.com,manufacturing,apac,smb,test,false,true,2025-03-11T00:00:00Z
acct_0208,parent_0076,Orbit Customer 208,customer-208.example.com,healthcare,na,smb,test,false,true,2025-04-12T00:00:00Z
acct_0209,parent_0077,Orbit Customer 209,customer-209.example.com,financial_services,emea,smb,test,false,true,2025-05-13T00:00:00Z
acct_0210,parent_0078,Orbit Customer 210,customer-210.example.com,retail,apac,smb,test,false,true,2025-06-14T00:00:00Z
1 account_id parent_account_id account_name domain industry sales_region size_band lifecycle_status is_internal is_test created_at
2 acct_0001 parent_0001 Orbit Customer 001 customer-001.example.com software na enterprise active false false 2025-01-01T00:00:00Z
3 acct_0002 parent_0002 Orbit Customer 002 customer-002.example.com manufacturing emea enterprise active false false 2025-02-02T00:00:00Z
4 acct_0003 parent_0003 Orbit Customer 003 customer-003.example.com healthcare apac enterprise active false false 2025-03-03T00:00:00Z
5 acct_0004 parent_0004 Orbit Customer 004 customer-004.example.com financial_services na enterprise active false false 2025-04-04T00:00:00Z
6 acct_0005 parent_0005 Orbit Customer 005 customer-005.example.com retail emea enterprise active false false 2025-05-05T00:00:00Z
7 acct_0006 parent_0006 Orbit Customer 006 customer-006.example.com software apac enterprise active false false 2025-06-06T00:00:00Z
8 acct_0007 parent_0007 Orbit Customer 007 customer-007.example.com manufacturing na enterprise active false false 2025-07-07T00:00:00Z
9 acct_0008 parent_0008 Orbit Customer 008 customer-008.example.com healthcare emea enterprise active false false 2025-08-08T00:00:00Z
10 acct_0009 parent_0009 Orbit Customer 009 customer-009.example.com financial_services apac enterprise active false false 2025-09-09T00:00:00Z
11 acct_0010 parent_0010 Orbit Customer 010 customer-010.example.com retail na enterprise active false false 2025-10-10T00:00:00Z
12 acct_0011 parent_0011 Orbit Customer 011 customer-011.example.com software emea enterprise active false false 2025-11-11T00:00:00Z
13 acct_0012 parent_0012 Orbit Customer 012 customer-012.example.com manufacturing apac enterprise active false false 2025-12-12T00:00:00Z
14 acct_0013 parent_0013 Orbit Customer 013 customer-013.example.com healthcare na enterprise active false false 2025-01-13T00:00:00Z
15 acct_0014 parent_0014 Orbit Customer 014 customer-014.example.com financial_services emea enterprise active false false 2025-02-14T00:00:00Z
16 acct_0015 parent_0015 Orbit Customer 015 customer-015.example.com retail apac enterprise active false false 2025-03-15T00:00:00Z
17 acct_0016 parent_0016 Orbit Customer 016 customer-016.example.com software na enterprise active false false 2025-04-16T00:00:00Z
18 acct_0017 parent_0017 Orbit Customer 017 customer-017.example.com manufacturing emea enterprise active false false 2025-05-17T00:00:00Z
19 acct_0018 parent_0018 Orbit Customer 018 customer-018.example.com healthcare apac enterprise active false false 2025-06-18T00:00:00Z
20 acct_0019 parent_0019 Orbit Customer 019 customer-019.example.com financial_services na enterprise active false false 2025-07-19T00:00:00Z
21 acct_0020 parent_0020 Orbit Customer 020 customer-020.example.com retail emea enterprise active false false 2025-08-20T00:00:00Z
22 acct_0021 parent_0021 Orbit Customer 021 customer-021.example.com software apac enterprise active false false 2025-09-21T00:00:00Z
23 acct_0022 parent_0022 Orbit Customer 022 customer-022.example.com manufacturing na enterprise active false false 2025-10-22T00:00:00Z
24 acct_0023 parent_0023 Orbit Customer 023 customer-023.example.com healthcare emea enterprise active false false 2025-11-23T00:00:00Z
25 acct_0024 parent_0024 Orbit Customer 024 customer-024.example.com financial_services apac enterprise active false false 2025-12-24T00:00:00Z
26 acct_0025 parent_0025 Orbit Customer 025 customer-025.example.com retail na enterprise active false false 2025-01-25T00:00:00Z
27 acct_0026 parent_0026 Orbit Customer 026 customer-026.example.com software emea enterprise active false false 2025-02-26T00:00:00Z
28 acct_0027 parent_0027 Orbit Customer 027 customer-027.example.com manufacturing apac enterprise active false false 2025-03-27T00:00:00Z
29 acct_0028 parent_0028 Orbit Customer 028 customer-028.example.com healthcare na enterprise active false false 2025-04-28T00:00:00Z
30 acct_0029 parent_0029 Orbit Customer 029 customer-029.example.com financial_services emea enterprise active false false 2025-05-01T00:00:00Z
31 acct_0030 parent_0030 Orbit Customer 030 customer-030.example.com retail apac enterprise active false false 2025-06-02T00:00:00Z
32 acct_0031 parent_0031 Orbit Customer 031 customer-031.example.com software na enterprise active false false 2025-07-03T00:00:00Z
33 acct_0032 parent_0032 Orbit Customer 032 customer-032.example.com manufacturing emea enterprise active false false 2025-08-04T00:00:00Z
34 acct_0033 parent_0033 Orbit Customer 033 customer-033.example.com healthcare apac enterprise active false false 2025-09-05T00:00:00Z
35 acct_0034 parent_0034 Orbit Customer 034 customer-034.example.com financial_services na enterprise active false false 2025-10-06T00:00:00Z
36 acct_0035 parent_0035 Orbit Customer 035 customer-035.example.com retail emea enterprise active false false 2025-11-07T00:00:00Z
37 acct_0036 parent_0036 Orbit Customer 036 customer-036.example.com software apac enterprise active false false 2025-12-08T00:00:00Z
38 acct_0037 parent_0037 Orbit Customer 037 customer-037.example.com manufacturing na enterprise active false false 2025-01-09T00:00:00Z
39 acct_0038 parent_0038 Orbit Customer 038 customer-038.example.com healthcare emea enterprise active false false 2025-02-10T00:00:00Z
40 acct_0039 parent_0039 Orbit Customer 039 customer-039.example.com financial_services apac enterprise active false false 2025-03-11T00:00:00Z
41 acct_0040 parent_0040 Orbit Customer 040 customer-040.example.com retail na enterprise active false false 2025-04-12T00:00:00Z
42 acct_0041 parent_0041 Orbit Customer 041 customer-041.example.com software emea enterprise active false false 2025-05-13T00:00:00Z
43 acct_0042 parent_0042 Orbit Customer 042 customer-042.example.com manufacturing apac enterprise active false false 2025-06-14T00:00:00Z
44 acct_0043 parent_0043 Orbit Customer 043 customer-043.example.com healthcare na enterprise active false false 2025-07-15T00:00:00Z
45 acct_0044 parent_0044 Orbit Customer 044 customer-044.example.com financial_services emea enterprise active false false 2025-08-16T00:00:00Z
46 acct_0045 parent_0045 Orbit Customer 045 customer-045.example.com retail apac enterprise active false false 2025-09-17T00:00:00Z
47 acct_0046 parent_0046 Orbit Customer 046 customer-046.example.com software na enterprise active false false 2025-10-18T00:00:00Z
48 acct_0047 parent_0047 Orbit Customer 047 customer-047.example.com manufacturing emea enterprise active false false 2025-11-19T00:00:00Z
49 acct_0048 parent_0048 Orbit Customer 048 customer-048.example.com healthcare apac enterprise active false false 2025-12-20T00:00:00Z
50 acct_0049 parent_0049 Orbit Customer 049 customer-049.example.com financial_services na enterprise active false false 2025-01-21T00:00:00Z
51 acct_0050 parent_0050 Orbit Customer 050 customer-050.example.com retail emea enterprise active false false 2025-02-22T00:00:00Z
52 acct_0051 parent_0051 Orbit Customer 051 customer-051.example.com software apac enterprise active false false 2025-03-23T00:00:00Z
53 acct_0052 parent_0052 Orbit Customer 052 customer-052.example.com manufacturing na enterprise active false false 2025-04-24T00:00:00Z
54 acct_0053 parent_0053 Orbit Customer 053 customer-053.example.com healthcare emea enterprise active false false 2025-05-25T00:00:00Z
55 acct_0054 parent_0054 Orbit Customer 054 customer-054.example.com financial_services apac enterprise active false false 2025-06-26T00:00:00Z
56 acct_0055 parent_0055 Orbit Customer 055 customer-055.example.com retail na enterprise active false false 2025-07-27T00:00:00Z
57 acct_0056 parent_0056 Orbit Customer 056 customer-056.example.com software emea enterprise active false false 2025-08-28T00:00:00Z
58 acct_0057 parent_0057 Orbit Customer 057 customer-057.example.com manufacturing apac enterprise active false false 2025-09-01T00:00:00Z
59 acct_0058 parent_0058 Orbit Customer 058 customer-058.example.com healthcare na enterprise active false false 2025-10-02T00:00:00Z
60 acct_0059 parent_0059 Orbit Customer 059 customer-059.example.com financial_services emea enterprise active false false 2025-11-03T00:00:00Z
61 acct_0060 parent_0060 Orbit Customer 060 customer-060.example.com retail apac enterprise active false false 2025-12-04T00:00:00Z
62 acct_0061 parent_0061 Orbit Customer 061 customer-061.example.com software na enterprise active false false 2025-01-05T00:00:00Z
63 acct_0062 parent_0062 Orbit Customer 062 customer-062.example.com manufacturing emea enterprise active false false 2025-02-06T00:00:00Z
64 acct_0063 parent_0063 Orbit Customer 063 customer-063.example.com healthcare apac enterprise active false false 2025-03-07T00:00:00Z
65 acct_0064 parent_0064 Orbit Customer 064 customer-064.example.com financial_services na enterprise active false false 2025-04-08T00:00:00Z
66 acct_0065 parent_0065 Orbit Customer 065 customer-065.example.com retail emea enterprise active false false 2025-05-09T00:00:00Z
67 acct_0066 parent_0066 Orbit Customer 066 customer-066.example.com software apac enterprise active false false 2025-06-10T00:00:00Z
68 acct_0067 parent_0067 Orbit Customer 067 customer-067.example.com manufacturing na enterprise active false false 2025-07-11T00:00:00Z
69 acct_0068 parent_0068 Orbit Customer 068 customer-068.example.com healthcare emea enterprise active false false 2025-08-12T00:00:00Z
70 acct_0069 parent_0069 Orbit Customer 069 customer-069.example.com financial_services apac enterprise active false false 2025-09-13T00:00:00Z
71 acct_0070 parent_0070 Orbit Customer 070 customer-070.example.com retail na enterprise active false false 2025-10-14T00:00:00Z
72 acct_0071 parent_0071 Orbit Customer 071 customer-071.example.com software emea enterprise active false false 2025-11-15T00:00:00Z
73 acct_0072 parent_0072 Orbit Customer 072 customer-072.example.com manufacturing apac enterprise active false false 2025-12-16T00:00:00Z
74 acct_0073 parent_0073 Orbit Customer 073 customer-073.example.com healthcare na enterprise active false false 2025-01-17T00:00:00Z
75 acct_0074 parent_0074 Orbit Customer 074 customer-074.example.com financial_services emea enterprise active false false 2025-02-18T00:00:00Z
76 acct_0075 parent_0075 Orbit Customer 075 customer-075.example.com retail apac enterprise active false false 2025-03-19T00:00:00Z
77 acct_0076 parent_0076 Orbit Customer 076 customer-076.example.com software na enterprise active false false 2025-04-20T00:00:00Z
78 acct_0077 parent_0077 Orbit Customer 077 customer-077.example.com manufacturing emea enterprise active false false 2025-05-21T00:00:00Z
79 acct_0078 parent_0078 Orbit Customer 078 customer-078.example.com healthcare apac enterprise active false false 2025-06-22T00:00:00Z
80 acct_0079 parent_0079 Orbit Customer 079 customer-079.example.com financial_services na enterprise active false false 2025-07-23T00:00:00Z
81 acct_0080 parent_0080 Orbit Customer 080 customer-080.example.com retail emea enterprise active false false 2025-08-24T00:00:00Z
82 acct_0081 parent_0081 Orbit Customer 081 customer-081.example.com software apac mid_market active false false 2025-09-25T00:00:00Z
83 acct_0082 parent_0082 Orbit Customer 082 customer-082.example.com manufacturing na mid_market active false false 2025-10-26T00:00:00Z
84 acct_0083 parent_0083 Orbit Customer 083 customer-083.example.com healthcare emea mid_market active false false 2025-11-27T00:00:00Z
85 acct_0084 parent_0084 Orbit Customer 084 customer-084.example.com financial_services apac mid_market active false false 2025-12-28T00:00:00Z
86 acct_0085 parent_0085 Orbit Customer 085 customer-085.example.com retail na mid_market active false false 2025-01-01T00:00:00Z
87 acct_0086 parent_0086 Orbit Customer 086 customer-086.example.com software emea mid_market active false false 2025-02-02T00:00:00Z
88 acct_0087 parent_0087 Orbit Customer 087 customer-087.example.com manufacturing apac mid_market active false false 2025-03-03T00:00:00Z
89 acct_0088 parent_0088 Orbit Customer 088 customer-088.example.com healthcare na mid_market active false false 2025-04-04T00:00:00Z
90 acct_0089 parent_0089 Orbit Customer 089 customer-089.example.com financial_services emea mid_market active false false 2025-05-05T00:00:00Z
91 acct_0090 parent_0090 Orbit Customer 090 customer-090.example.com retail apac mid_market active false false 2025-06-06T00:00:00Z
92 acct_0091 parent_0091 Orbit Customer 091 customer-091.example.com software na mid_market active false false 2025-07-07T00:00:00Z
93 acct_0092 parent_0092 Orbit Customer 092 customer-092.example.com manufacturing emea mid_market active false false 2025-08-08T00:00:00Z
94 acct_0093 parent_0093 Orbit Customer 093 customer-093.example.com healthcare apac mid_market active false false 2025-09-09T00:00:00Z
95 acct_0094 parent_0094 Orbit Customer 094 customer-094.example.com financial_services na mid_market active false false 2025-10-10T00:00:00Z
96 acct_0095 parent_0095 Orbit Customer 095 customer-095.example.com retail emea mid_market active false false 2025-11-11T00:00:00Z
97 acct_0096 parent_0096 Orbit Customer 096 customer-096.example.com software apac mid_market active false false 2025-12-12T00:00:00Z
98 acct_0097 parent_0097 Orbit Customer 097 customer-097.example.com manufacturing na mid_market active false false 2025-01-13T00:00:00Z
99 acct_0098 parent_0098 Orbit Customer 098 customer-098.example.com healthcare emea mid_market active false false 2025-02-14T00:00:00Z
100 acct_0099 parent_0099 Orbit Customer 099 customer-099.example.com financial_services apac mid_market active false false 2025-03-15T00:00:00Z
101 acct_0100 parent_0100 Orbit Customer 100 customer-100.example.com retail na mid_market active false false 2025-04-16T00:00:00Z
102 acct_0101 parent_0101 Orbit Customer 101 customer-101.example.com software emea mid_market active false false 2025-05-17T00:00:00Z
103 acct_0102 parent_0102 Orbit Customer 102 customer-102.example.com manufacturing apac mid_market active false false 2025-06-18T00:00:00Z
104 acct_0103 parent_0103 Orbit Customer 103 customer-103.example.com healthcare na mid_market active false false 2025-07-19T00:00:00Z
105 acct_0104 parent_0104 Orbit Customer 104 customer-104.example.com financial_services emea mid_market active false false 2025-08-20T00:00:00Z
106 acct_0105 parent_0105 Orbit Customer 105 customer-105.example.com retail apac mid_market active false false 2025-09-21T00:00:00Z
107 acct_0106 parent_0106 Orbit Customer 106 customer-106.example.com software na mid_market active false false 2025-10-22T00:00:00Z
108 acct_0107 parent_0107 Orbit Customer 107 customer-107.example.com manufacturing emea mid_market active false false 2025-11-23T00:00:00Z
109 acct_0108 parent_0108 Orbit Customer 108 customer-108.example.com healthcare apac mid_market active false false 2025-12-24T00:00:00Z
110 acct_0109 parent_0109 Orbit Customer 109 customer-109.example.com financial_services na mid_market active false false 2025-01-25T00:00:00Z
111 acct_0110 parent_0110 Orbit Customer 110 customer-110.example.com retail emea mid_market active false false 2025-02-26T00:00:00Z
112 acct_0111 parent_0111 Orbit Customer 111 customer-111.example.com software apac mid_market active false false 2025-03-27T00:00:00Z
113 acct_0112 parent_0112 Orbit Customer 112 customer-112.example.com manufacturing na mid_market active false false 2025-04-28T00:00:00Z
114 acct_0113 parent_0113 Orbit Customer 113 customer-113.example.com healthcare emea mid_market active false false 2025-05-01T00:00:00Z
115 acct_0114 parent_0114 Orbit Customer 114 customer-114.example.com financial_services apac mid_market active false false 2025-06-02T00:00:00Z
116 acct_0115 parent_0115 Orbit Customer 115 customer-115.example.com retail na mid_market active false false 2025-07-03T00:00:00Z
117 acct_0116 parent_0116 Orbit Customer 116 customer-116.example.com software emea mid_market active false false 2025-08-04T00:00:00Z
118 acct_0117 parent_0117 Orbit Customer 117 customer-117.example.com manufacturing apac mid_market active false false 2025-09-05T00:00:00Z
119 acct_0118 parent_0118 Orbit Customer 118 customer-118.example.com healthcare na mid_market active false false 2025-10-06T00:00:00Z
120 acct_0119 parent_0119 Orbit Customer 119 customer-119.example.com financial_services emea mid_market active false false 2025-11-07T00:00:00Z
121 acct_0120 parent_0120 Orbit Customer 120 customer-120.example.com retail apac mid_market active false false 2025-12-08T00:00:00Z
122 acct_0121 parent_0121 Orbit Customer 121 customer-121.example.com software na mid_market active false false 2025-01-09T00:00:00Z
123 acct_0122 parent_0122 Orbit Customer 122 customer-122.example.com manufacturing emea mid_market active false false 2025-02-10T00:00:00Z
124 acct_0123 parent_0123 Orbit Customer 123 customer-123.example.com healthcare apac mid_market active false false 2025-03-11T00:00:00Z
125 acct_0124 parent_0124 Orbit Customer 124 customer-124.example.com financial_services na mid_market active false false 2025-04-12T00:00:00Z
126 acct_0125 parent_0125 Orbit Customer 125 customer-125.example.com retail emea mid_market active false false 2025-05-13T00:00:00Z
127 acct_0126 parent_0126 Orbit Customer 126 customer-126.example.com software apac mid_market active false false 2025-06-14T00:00:00Z
128 acct_0127 parent_0127 Orbit Customer 127 customer-127.example.com manufacturing na mid_market active false false 2025-07-15T00:00:00Z
129 acct_0128 parent_0128 Orbit Customer 128 customer-128.example.com healthcare emea mid_market active false false 2025-08-16T00:00:00Z
130 acct_0129 parent_0129 Orbit Customer 129 customer-129.example.com financial_services apac mid_market active false false 2025-09-17T00:00:00Z
131 acct_0130 parent_0130 Orbit Customer 130 customer-130.example.com retail na mid_market active false false 2025-10-18T00:00:00Z
132 acct_0131 parent_0131 Orbit Customer 131 customer-131.example.com software emea mid_market active false false 2025-11-19T00:00:00Z
133 acct_0132 parent_0132 Orbit Customer 132 customer-132.example.com manufacturing apac mid_market active false false 2025-12-20T00:00:00Z
134 acct_0133 parent_0001 Orbit Customer 133 customer-133.example.com healthcare na mid_market active false false 2025-01-21T00:00:00Z
135 acct_0134 parent_0002 Orbit Customer 134 customer-134.example.com financial_services emea mid_market active false false 2025-02-22T00:00:00Z
136 acct_0135 parent_0003 Orbit Customer 135 customer-135.example.com retail apac mid_market active false false 2025-03-23T00:00:00Z
137 acct_0136 parent_0004 Orbit Customer 136 customer-136.example.com software na mid_market active false false 2025-04-24T00:00:00Z
138 acct_0137 parent_0005 Orbit Customer 137 customer-137.example.com manufacturing emea mid_market active false false 2025-05-25T00:00:00Z
139 acct_0138 parent_0006 Orbit Customer 138 customer-138.example.com healthcare apac mid_market active false false 2025-06-26T00:00:00Z
140 acct_0139 parent_0007 Orbit Customer 139 customer-139.example.com financial_services na mid_market active false false 2025-07-27T00:00:00Z
141 acct_0140 parent_0008 Orbit Customer 140 customer-140.example.com retail emea mid_market active false false 2025-08-28T00:00:00Z
142 acct_0141 parent_0009 Orbit Customer 141 customer-141.example.com software apac mid_market active false false 2025-09-01T00:00:00Z
143 acct_0142 parent_0010 Orbit Customer 142 customer-142.example.com manufacturing na mid_market active false false 2025-10-02T00:00:00Z
144 acct_0143 parent_0011 Orbit Customer 143 customer-143.example.com healthcare emea mid_market active false false 2025-11-03T00:00:00Z
145 acct_0144 parent_0012 Orbit Customer 144 customer-144.example.com financial_services apac mid_market active false false 2025-12-04T00:00:00Z
146 acct_0145 parent_0013 Orbit Customer 145 customer-145.example.com retail na mid_market active false false 2025-01-05T00:00:00Z
147 acct_0146 parent_0014 Orbit Customer 146 customer-146.example.com software emea mid_market active false false 2025-02-06T00:00:00Z
148 acct_0147 parent_0015 Orbit Customer 147 customer-147.example.com manufacturing apac mid_market active false false 2025-03-07T00:00:00Z
149 acct_0148 parent_0016 Orbit Customer 148 customer-148.example.com healthcare na mid_market active false false 2025-04-08T00:00:00Z
150 acct_0149 parent_0017 Orbit Customer 149 customer-149.example.com financial_services emea mid_market active false false 2025-05-09T00:00:00Z
151 acct_0150 parent_0018 Orbit Customer 150 customer-150.example.com retail apac mid_market active false false 2025-06-10T00:00:00Z
152 acct_0151 parent_0019 Orbit Customer 151 customer-151.example.com software na smb active false false 2025-07-11T00:00:00Z
153 acct_0152 parent_0020 Orbit Customer 152 customer-152.example.com manufacturing emea smb active false false 2025-08-12T00:00:00Z
154 acct_0153 parent_0021 Orbit Customer 153 customer-153.example.com healthcare apac smb active false false 2025-09-13T00:00:00Z
155 acct_0154 parent_0022 Orbit Customer 154 customer-154.example.com financial_services na smb active false false 2025-10-14T00:00:00Z
156 acct_0155 parent_0023 Orbit Customer 155 customer-155.example.com retail emea smb active false false 2025-11-15T00:00:00Z
157 acct_0156 parent_0024 Orbit Customer 156 customer-156.example.com software apac smb active false false 2025-12-16T00:00:00Z
158 acct_0157 parent_0025 Orbit Customer 157 customer-157.example.com manufacturing na smb active false false 2025-01-17T00:00:00Z
159 acct_0158 parent_0026 Orbit Customer 158 customer-158.example.com healthcare emea smb active false false 2025-02-18T00:00:00Z
160 acct_0159 parent_0027 Orbit Customer 159 customer-159.example.com financial_services apac smb active false false 2025-03-19T00:00:00Z
161 acct_0160 parent_0028 Orbit Customer 160 customer-160.example.com retail na smb active false false 2025-04-20T00:00:00Z
162 acct_0161 parent_0029 Orbit Customer 161 customer-161.example.com software emea smb active false false 2025-05-21T00:00:00Z
163 acct_0162 parent_0030 Orbit Customer 162 customer-162.example.com manufacturing apac smb active false false 2025-06-22T00:00:00Z
164 acct_0163 parent_0031 Orbit Customer 163 customer-163.example.com healthcare na smb active false false 2025-07-23T00:00:00Z
165 acct_0164 parent_0032 Orbit Customer 164 customer-164.example.com financial_services emea smb active false false 2025-08-24T00:00:00Z
166 acct_0165 parent_0033 Orbit Customer 165 customer-165.example.com retail apac smb active false false 2025-09-25T00:00:00Z
167 acct_0166 parent_0034 Orbit Customer 166 customer-166.example.com software na smb active false false 2025-10-26T00:00:00Z
168 acct_0167 parent_0035 Orbit Customer 167 customer-167.example.com manufacturing emea smb active false false 2025-11-27T00:00:00Z
169 acct_0168 parent_0036 Orbit Customer 168 customer-168.example.com healthcare apac smb active false false 2025-12-28T00:00:00Z
170 acct_0169 parent_0037 Orbit Customer 169 customer-169.example.com financial_services na smb active false false 2025-01-01T00:00:00Z
171 acct_0170 parent_0038 Orbit Customer 170 customer-170.example.com retail emea smb active false false 2025-02-02T00:00:00Z
172 acct_0171 parent_0039 Orbit Customer 171 customer-171.example.com software apac smb active false false 2025-03-03T00:00:00Z
173 acct_0172 parent_0040 Orbit Customer 172 customer-172.example.com manufacturing na smb active false false 2025-04-04T00:00:00Z
174 acct_0173 parent_0041 Orbit Customer 173 customer-173.example.com healthcare emea smb active false false 2025-05-05T00:00:00Z
175 acct_0174 parent_0042 Orbit Customer 174 customer-174.example.com financial_services apac smb active false false 2025-06-06T00:00:00Z
176 acct_0175 parent_0043 Orbit Customer 175 customer-175.example.com retail na smb active false false 2025-07-07T00:00:00Z
177 acct_0176 parent_0044 Orbit Customer 176 customer-176.example.com software emea smb active false false 2025-08-08T00:00:00Z
178 acct_0177 parent_0045 Orbit Customer 177 customer-177.example.com manufacturing apac smb active false false 2025-09-09T00:00:00Z
179 acct_0178 parent_0046 Orbit Customer 178 customer-178.example.com healthcare na smb active false false 2025-10-10T00:00:00Z
180 acct_0179 parent_0047 Orbit Customer 179 customer-179.example.com financial_services emea smb active false false 2025-11-11T00:00:00Z
181 acct_0180 parent_0048 Orbit Customer 180 customer-180.example.com retail apac smb active false false 2025-12-12T00:00:00Z
182 acct_0181 parent_0049 Orbit Customer 181 customer-181.example.com software na smb active false false 2025-01-13T00:00:00Z
183 acct_0182 parent_0050 Orbit Customer 182 customer-182.example.com manufacturing emea smb active false false 2025-02-14T00:00:00Z
184 acct_0183 parent_0051 Orbit Customer 183 customer-183.example.com healthcare apac smb active false false 2025-03-15T00:00:00Z
185 acct_0184 parent_0052 Orbit Customer 184 customer-184.example.com financial_services na smb active false false 2025-04-16T00:00:00Z
186 acct_0185 parent_0053 Orbit Customer 185 customer-185.example.com retail emea smb active false false 2025-05-17T00:00:00Z
187 acct_0186 parent_0054 Orbit Customer 186 customer-186.example.com software apac smb active false false 2025-06-18T00:00:00Z
188 acct_0187 parent_0055 Orbit Customer 187 customer-187.example.com manufacturing na smb active false false 2025-07-19T00:00:00Z
189 acct_0188 parent_0056 Orbit Customer 188 customer-188.example.com healthcare emea smb active false false 2025-08-20T00:00:00Z
190 acct_0189 parent_0057 Orbit Customer 189 customer-189.example.com financial_services apac smb active false false 2025-09-21T00:00:00Z
191 acct_0190 parent_0058 Orbit Customer 190 customer-190.example.com retail na smb active false false 2025-10-22T00:00:00Z
192 acct_0191 parent_0059 Orbit Customer 191 customer-191.example.com software emea smb active false false 2025-11-23T00:00:00Z
193 acct_0192 parent_0060 Orbit Customer 192 customer-192.example.com manufacturing apac smb active false false 2025-12-24T00:00:00Z
194 acct_0193 parent_0061 Orbit Customer 193 customer-193.example.com healthcare na smb active false false 2025-01-25T00:00:00Z
195 acct_0194 parent_0062 Orbit Customer 194 customer-194.example.com financial_services emea smb active false false 2025-02-26T00:00:00Z
196 acct_0195 parent_0063 Orbit Customer 195 customer-195.example.com retail apac smb active false false 2025-03-27T00:00:00Z
197 acct_0196 parent_0064 Orbit Customer 196 customer-196.example.com software na smb active false false 2025-04-28T00:00:00Z
198 acct_0197 parent_0065 Orbit Customer 197 customer-197.example.com manufacturing emea smb active false false 2025-05-01T00:00:00Z
199 acct_0198 parent_0066 Orbit Customer 198 customer-198.example.com healthcare apac smb active false false 2025-06-02T00:00:00Z
200 acct_0199 parent_0067 Orbit Customer 199 customer-199.example.com financial_services na smb churned false false 2025-07-03T00:00:00Z
201 acct_0200 parent_0068 Orbit Customer 200 customer-200.example.com retail emea smb churned false false 2025-08-04T00:00:00Z
202 acct_0201 parent_0069 Orbit Customer 201 customer-201.example.com software apac smb internal true false 2025-09-05T00:00:00Z
203 acct_0202 parent_0070 Orbit Customer 202 customer-202.example.com manufacturing na smb internal true false 2025-10-06T00:00:00Z
204 acct_0203 parent_0071 Orbit Customer 203 customer-203.example.com healthcare emea smb internal true false 2025-11-07T00:00:00Z
205 acct_0204 parent_0072 Orbit Customer 204 customer-204.example.com financial_services apac smb internal true false 2025-12-08T00:00:00Z
206 acct_0205 parent_0073 Orbit Customer 205 customer-205.example.com retail na smb internal true false 2025-01-09T00:00:00Z
207 acct_0206 parent_0074 Orbit Customer 206 customer-206.example.com software emea smb test false true 2025-02-10T00:00:00Z
208 acct_0207 parent_0075 Orbit Customer 207 customer-207.example.com manufacturing apac smb test false true 2025-03-11T00:00:00Z
209 acct_0208 parent_0076 Orbit Customer 208 customer-208.example.com healthcare na smb test false true 2025-04-12T00:00:00Z
210 acct_0209 parent_0077 Orbit Customer 209 customer-209.example.com financial_services emea smb test false true 2025-05-13T00:00:00Z
211 acct_0210 parent_0078 Orbit Customer 210 customer-210.example.com retail apac smb test false true 2025-06-14T00:00:00Z

View file

@ -0,0 +1,721 @@
arr_movement_id,account_id,parent_account_id,contract_id,movement_date,movement_type,movement_reason,arr_delta_cents,starting_arr_cents,ending_arr_cents
arr_move_0001,acct_0001,parent_0001,contract_0001,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0002,acct_0002,parent_0002,contract_0002,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0003,acct_0003,parent_0003,contract_0003,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0004,acct_0004,parent_0004,contract_0004,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0005,acct_0005,parent_0005,contract_0005,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0006,acct_0006,parent_0006,contract_0006,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0007,acct_0007,parent_0007,contract_0007,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0008,acct_0008,parent_0008,contract_0008,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0009,acct_0009,parent_0009,contract_0009,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0010,acct_0010,parent_0010,contract_0010,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0011,acct_0011,parent_0011,contract_0011,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0012,acct_0012,parent_0012,contract_0012,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0013,acct_0013,parent_0013,contract_0013,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0014,acct_0014,parent_0014,contract_0014,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0015,acct_0015,parent_0015,contract_0015,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0016,acct_0016,parent_0016,contract_0016,2026-02-15,expansion,seat_growth,4500000,30000000,34500000
arr_move_0017,acct_0017,parent_0017,contract_0017,2026-02-15,expansion,seat_growth,6000000,70000000,76000000
arr_move_0018,acct_0018,parent_0018,contract_0018,2026-02-20,contraction,discount_expiration,-4500000,50000000,45500000
arr_move_0019,acct_0019,parent_0019,contract_0019,2026-02-20,contraction,discount_expiration,-4500000,50000000,45500000
arr_move_0020,acct_0020,parent_0020,contract_0020,2026-02-20,contraction,discount_expiration,-4500000,50000000,45500000
arr_move_0021,acct_0021,parent_0021,contract_0021,2026-02-20,contraction,discount_expiration,-4500000,50000000,45500000
arr_move_0022,acct_0022,parent_0022,contract_0022,2026-02-20,contraction,discount_expiration,-4500000,50000000,45500000
arr_move_0023,acct_0023,parent_0023,contract_0023,2026-02-20,contraction,discount_expiration,-4500000,50000000,45500000
arr_move_0024,acct_0024,parent_0024,contract_0024,2026-02-20,contraction,discount_expiration,-4500000,50000000,45500000
arr_move_0025,acct_0025,parent_0025,contract_0025,2026-02-20,contraction,discount_expiration,-4500000,50000000,45500000
arr_move_0026,acct_0026,parent_0026,contract_0026,2026-02-20,contraction,discount_expiration,-4500000,50000000,45500000
arr_move_0027,acct_0027,parent_0027,contract_0027,2026-02-20,contraction,discount_expiration,-4500000,50000000,45500000
arr_move_0028,acct_0028,parent_0028,contract_0028,2026-02-20,contraction,discount_expiration,-5500000,100000000,94500000
arr_move_0029,acct_0029,parent_0029,contract_0029,2026-03-10,churn,budget_loss,-5000000,100000000,95000000
arr_move_0030,acct_0030,parent_0030,contract_0030,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0031,acct_0031,parent_0031,contract_0031,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0032,acct_0032,parent_0032,contract_0032,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0033,acct_0033,parent_0033,contract_0033,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0034,acct_0034,parent_0034,contract_0034,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0035,acct_0035,parent_0035,contract_0035,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0036,acct_0036,parent_0036,contract_0036,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0037,acct_0037,parent_0037,contract_0037,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0038,acct_0038,parent_0038,contract_0038,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0039,acct_0039,parent_0039,contract_0039,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0040,acct_0040,parent_0040,contract_0040,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0041,acct_0041,parent_0041,contract_0041,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0042,acct_0042,parent_0042,contract_0042,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0043,acct_0043,parent_0043,contract_0043,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0044,acct_0044,parent_0044,contract_0044,2025-11-15,expansion,seat_growth,5000000,50000000,55000000
arr_move_0045,acct_0045,parent_0045,contract_0045,2025-11-15,expansion,seat_growth,11800000,50000000,61800000
arr_move_0046,acct_0046,parent_0046,contract_0046,2025-11-20,contraction,scope_reduction,-2500000,100000000,97500000
arr_move_0047,acct_0047,parent_0047,contract_0047,2025-11-20,contraction,scope_reduction,-2500000,100000000,97500000
arr_move_0048,acct_0048,parent_0048,contract_0048,2025-11-20,contraction,scope_reduction,-2500000,100000000,97500000
arr_move_0049,acct_0049,parent_0049,contract_0049,2025-11-20,contraction,scope_reduction,-2500000,100000000,97500000
arr_move_0050,acct_0090,parent_0090,contract_0101,2025-06-15,new,generated_history,360000,3200000,3560000
arr_move_0051,acct_0091,parent_0091,contract_0102,2025-06-15,expansion,generated_history,370000,3300000,3670000
arr_move_0052,acct_0092,parent_0092,contract_0103,2025-06-15,contraction,generated_history,-250000,3400000,3150000
arr_move_0053,acct_0093,parent_0093,contract_0104,2025-06-15,reactivation,generated_history,260000,3500000,3760000
arr_move_0054,acct_0094,parent_0094,contract_0105,2025-06-15,new,generated_history,270000,3600000,3870000
arr_move_0055,acct_0095,parent_0095,contract_0106,2025-06-15,expansion,generated_history,280000,3700000,3980000
arr_move_0056,acct_0096,parent_0096,contract_0107,2025-06-15,contraction,generated_history,-290000,3800000,3510000
arr_move_0057,acct_0097,parent_0097,contract_0108,2025-06-15,reactivation,generated_history,300000,2000000,2300000
arr_move_0058,acct_0098,parent_0098,contract_0109,2025-06-15,new,generated_history,310000,2100000,2410000
arr_move_0059,acct_0099,parent_0099,contract_0110,2025-06-15,expansion,generated_history,320000,2200000,2520000
arr_move_0060,acct_0100,parent_0100,contract_0111,2025-06-15,contraction,generated_history,-330000,2300000,1970000
arr_move_0061,acct_0101,parent_0101,contract_0112,2025-06-15,reactivation,generated_history,340000,2400000,2740000
arr_move_0062,acct_0102,parent_0102,contract_0113,2025-06-15,new,generated_history,350000,2500000,2850000
arr_move_0063,acct_0103,parent_0103,contract_0114,2025-06-15,expansion,generated_history,360000,2600000,2960000
arr_move_0064,acct_0104,parent_0104,contract_0115,2025-06-15,contraction,generated_history,-370000,2700000,2330000
arr_move_0065,acct_0105,parent_0105,contract_0116,2025-06-15,reactivation,generated_history,250000,2800000,3050000
arr_move_0066,acct_0106,parent_0106,contract_0117,2025-06-15,new,generated_history,260000,2900000,3160000
arr_move_0067,acct_0107,parent_0107,contract_0118,2025-06-15,expansion,generated_history,270000,3000000,3270000
arr_move_0068,acct_0108,parent_0108,contract_0119,2025-06-15,contraction,generated_history,-280000,3100000,2820000
arr_move_0069,acct_0109,parent_0109,contract_0120,2025-06-15,reactivation,generated_history,290000,3200000,3490000
arr_move_0070,acct_0110,parent_0110,contract_0121,2025-06-15,new,generated_history,300000,3300000,3600000
arr_move_0071,acct_0111,parent_0111,contract_0122,2025-06-15,expansion,generated_history,310000,3400000,3710000
arr_move_0072,acct_0112,parent_0112,contract_0123,2025-06-15,contraction,generated_history,-320000,3500000,3180000
arr_move_0073,acct_0113,parent_0113,contract_0124,2025-06-15,reactivation,generated_history,330000,3600000,3930000
arr_move_0074,acct_0114,parent_0114,contract_0125,2025-06-15,new,generated_history,340000,3700000,4040000
arr_move_0075,acct_0115,parent_0115,contract_0126,2025-06-15,expansion,generated_history,350000,3800000,4150000
arr_move_0076,acct_0116,parent_0116,contract_0127,2025-06-15,contraction,generated_history,-360000,2000000,1640000
arr_move_0077,acct_0117,parent_0117,contract_0128,2025-06-15,reactivation,generated_history,370000,2100000,2470000
arr_move_0078,acct_0118,parent_0118,contract_0129,2025-06-15,new,generated_history,250000,2200000,2450000
arr_move_0079,acct_0119,parent_0119,contract_0130,2025-06-15,expansion,generated_history,260000,2300000,2560000
arr_move_0080,acct_0120,parent_0120,contract_0131,2025-06-15,contraction,generated_history,-270000,2400000,2130000
arr_move_0081,acct_0121,parent_0121,contract_0132,2025-06-15,reactivation,generated_history,280000,2500000,2780000
arr_move_0082,acct_0122,parent_0122,contract_0133,2025-06-15,new,generated_history,290000,2600000,2890000
arr_move_0083,acct_0123,parent_0123,contract_0134,2025-06-15,expansion,generated_history,300000,2700000,3000000
arr_move_0084,acct_0124,parent_0124,contract_0135,2025-06-15,contraction,generated_history,-310000,2800000,2490000
arr_move_0085,acct_0125,parent_0125,contract_0136,2025-06-15,reactivation,generated_history,320000,2900000,3220000
arr_move_0086,acct_0126,parent_0126,contract_0137,2025-06-15,new,generated_history,330000,3000000,3330000
arr_move_0087,acct_0127,parent_0127,contract_0138,2025-06-15,expansion,generated_history,340000,3100000,3440000
arr_move_0088,acct_0128,parent_0128,contract_0139,2025-06-15,contraction,generated_history,-350000,3200000,2850000
arr_move_0089,acct_0129,parent_0129,contract_0140,2025-06-15,reactivation,generated_history,360000,3300000,3660000
arr_move_0090,acct_0130,parent_0130,contract_0141,2025-06-15,new,generated_history,370000,3400000,3770000
arr_move_0091,acct_0131,parent_0131,contract_0142,2025-06-15,expansion,generated_history,250000,3500000,3750000
arr_move_0092,acct_0132,parent_0132,contract_0143,2025-06-15,contraction,generated_history,-260000,3600000,3340000
arr_move_0093,acct_0133,parent_0001,contract_0144,2025-06-15,reactivation,generated_history,270000,3700000,3970000
arr_move_0094,acct_0134,parent_0002,contract_0145,2025-06-15,new,generated_history,280000,3800000,4080000
arr_move_0095,acct_0135,parent_0003,contract_0146,2025-06-15,expansion,generated_history,290000,2000000,2290000
arr_move_0096,acct_0136,parent_0004,contract_0147,2025-06-15,contraction,generated_history,-300000,2100000,1800000
arr_move_0097,acct_0137,parent_0005,contract_0148,2025-06-15,reactivation,generated_history,310000,2200000,2510000
arr_move_0098,acct_0138,parent_0006,contract_0149,2025-06-15,new,generated_history,320000,2300000,2620000
arr_move_0099,acct_0139,parent_0007,contract_0150,2025-06-15,expansion,generated_history,330000,2400000,2730000
arr_move_0100,acct_0140,parent_0008,contract_0151,2025-06-15,contraction,generated_history,-340000,2500000,2160000
arr_move_0101,acct_0141,parent_0009,contract_0152,2025-06-15,reactivation,generated_history,350000,2600000,2950000
arr_move_0102,acct_0142,parent_0010,contract_0153,2025-06-15,new,generated_history,360000,2700000,3060000
arr_move_0103,acct_0143,parent_0011,contract_0154,2025-06-15,expansion,generated_history,370000,2800000,3170000
arr_move_0104,acct_0144,parent_0012,contract_0155,2025-06-15,contraction,generated_history,-250000,2900000,2650000
arr_move_0105,acct_0145,parent_0013,contract_0156,2025-06-15,reactivation,generated_history,260000,3000000,3260000
arr_move_0106,acct_0146,parent_0014,contract_0157,2025-06-15,new,generated_history,270000,3100000,3370000
arr_move_0107,acct_0147,parent_0015,contract_0158,2025-06-15,expansion,generated_history,280000,3200000,3480000
arr_move_0108,acct_0148,parent_0016,contract_0159,2025-06-15,contraction,generated_history,-290000,3300000,3010000
arr_move_0109,acct_0149,parent_0017,contract_0160,2025-06-15,reactivation,generated_history,300000,3400000,3700000
arr_move_0110,acct_0150,parent_0018,contract_0161,2025-06-15,new,generated_history,310000,3500000,3810000
arr_move_0111,acct_0151,parent_0019,contract_0162,2025-06-15,expansion,generated_history,320000,3600000,3920000
arr_move_0112,acct_0152,parent_0020,contract_0163,2025-06-15,contraction,generated_history,-330000,3700000,3370000
arr_move_0113,acct_0153,parent_0021,contract_0164,2025-06-15,reactivation,generated_history,340000,3800000,4140000
arr_move_0114,acct_0154,parent_0022,contract_0165,2025-06-15,new,generated_history,350000,2000000,2350000
arr_move_0115,acct_0155,parent_0023,contract_0166,2025-06-15,expansion,generated_history,360000,2100000,2460000
arr_move_0116,acct_0156,parent_0024,contract_0167,2025-06-15,contraction,generated_history,-370000,2200000,1830000
arr_move_0117,acct_0157,parent_0025,contract_0168,2025-06-15,reactivation,generated_history,250000,2300000,2550000
arr_move_0118,acct_0158,parent_0026,contract_0169,2025-06-15,new,generated_history,260000,2400000,2660000
arr_move_0119,acct_0159,parent_0027,contract_0170,2025-06-15,expansion,generated_history,270000,2500000,2770000
arr_move_0120,acct_0160,parent_0028,contract_0171,2025-06-15,contraction,generated_history,-280000,2600000,2320000
arr_move_0121,acct_0161,parent_0029,contract_0172,2025-06-15,reactivation,generated_history,290000,2700000,2990000
arr_move_0122,acct_0162,parent_0030,contract_0173,2025-06-15,new,generated_history,300000,2800000,3100000
arr_move_0123,acct_0163,parent_0031,contract_0174,2025-06-15,expansion,generated_history,310000,2900000,3210000
arr_move_0124,acct_0164,parent_0032,contract_0175,2025-06-15,contraction,generated_history,-320000,3000000,2680000
arr_move_0125,acct_0165,parent_0033,contract_0176,2025-06-15,reactivation,generated_history,330000,3100000,3430000
arr_move_0126,acct_0166,parent_0034,contract_0177,2025-06-15,new,generated_history,340000,3200000,3540000
arr_move_0127,acct_0167,parent_0035,contract_0178,2025-06-15,expansion,generated_history,350000,3300000,3650000
arr_move_0128,acct_0168,parent_0036,contract_0179,2025-06-15,contraction,generated_history,-360000,3400000,3040000
arr_move_0129,acct_0169,parent_0037,contract_0180,2025-06-15,reactivation,generated_history,370000,3500000,3870000
arr_move_0130,acct_0170,parent_0038,contract_0181,2025-06-15,new,generated_history,250000,3600000,3850000
arr_move_0131,acct_0171,parent_0039,contract_0182,2025-06-15,expansion,generated_history,260000,3700000,3960000
arr_move_0132,acct_0172,parent_0040,contract_0183,2025-06-15,contraction,generated_history,-270000,3800000,3530000
arr_move_0133,acct_0173,parent_0041,contract_0184,2025-06-15,reactivation,generated_history,280000,2000000,2280000
arr_move_0134,acct_0174,parent_0042,contract_0185,2025-06-15,new,generated_history,290000,2100000,2390000
arr_move_0135,acct_0175,parent_0043,contract_0186,2025-06-15,expansion,generated_history,300000,2200000,2500000
arr_move_0136,acct_0176,parent_0044,contract_0187,2025-06-15,contraction,generated_history,-310000,2300000,1990000
arr_move_0137,acct_0177,parent_0045,contract_0188,2025-06-15,reactivation,generated_history,320000,2400000,2720000
arr_move_0138,acct_0178,parent_0046,contract_0189,2025-06-15,new,generated_history,330000,2500000,2830000
arr_move_0139,acct_0179,parent_0047,contract_0190,2025-06-15,expansion,generated_history,340000,2600000,2940000
arr_move_0140,acct_0180,parent_0048,contract_0191,2025-06-15,contraction,generated_history,-350000,2700000,2350000
arr_move_0141,acct_0181,parent_0049,contract_0192,2025-06-15,reactivation,generated_history,360000,2800000,3160000
arr_move_0142,acct_0182,parent_0050,contract_0193,2025-06-15,new,generated_history,370000,2900000,3270000
arr_move_0143,acct_0183,parent_0051,contract_0194,2025-06-15,expansion,generated_history,250000,3000000,3250000
arr_move_0144,acct_0184,parent_0052,contract_0195,2025-06-15,contraction,generated_history,-260000,3100000,2840000
arr_move_0145,acct_0185,parent_0053,contract_0196,2025-06-15,reactivation,generated_history,270000,3200000,3470000
arr_move_0146,acct_0186,parent_0054,contract_0197,2025-06-15,new,generated_history,280000,3300000,3580000
arr_move_0147,acct_0187,parent_0055,contract_0198,2025-06-15,expansion,generated_history,290000,3400000,3690000
arr_move_0148,acct_0188,parent_0056,contract_0199,2025-06-15,contraction,generated_history,-300000,3500000,3200000
arr_move_0149,acct_0189,parent_0057,contract_0200,2025-06-15,reactivation,generated_history,310000,3600000,3910000
arr_move_0150,acct_0090,parent_0090,contract_0201,2025-06-15,new,generated_history,320000,3700000,4020000
arr_move_0151,acct_0091,parent_0091,contract_0202,2025-06-15,expansion,generated_history,330000,3800000,4130000
arr_move_0152,acct_0092,parent_0092,contract_0203,2025-06-15,contraction,generated_history,-340000,2000000,1660000
arr_move_0153,acct_0093,parent_0093,contract_0204,2025-06-15,reactivation,generated_history,350000,2100000,2450000
arr_move_0154,acct_0094,parent_0094,contract_0205,2025-06-15,new,generated_history,360000,2200000,2560000
arr_move_0155,acct_0095,parent_0095,contract_0206,2025-06-15,expansion,generated_history,370000,2300000,2670000
arr_move_0156,acct_0096,parent_0096,contract_0207,2025-06-15,contraction,generated_history,-250000,2400000,2150000
arr_move_0157,acct_0097,parent_0097,contract_0208,2025-06-15,reactivation,generated_history,260000,2500000,2760000
arr_move_0158,acct_0098,parent_0098,contract_0209,2025-06-15,new,generated_history,270000,2600000,2870000
arr_move_0159,acct_0099,parent_0099,contract_0210,2025-06-15,expansion,generated_history,280000,2700000,2980000
arr_move_0160,acct_0100,parent_0100,contract_0211,2025-06-15,contraction,generated_history,-290000,2800000,2510000
arr_move_0161,acct_0101,parent_0101,contract_0212,2025-06-15,reactivation,generated_history,300000,2900000,3200000
arr_move_0162,acct_0102,parent_0102,contract_0213,2025-06-15,new,generated_history,310000,3000000,3310000
arr_move_0163,acct_0103,parent_0103,contract_0214,2025-06-15,expansion,generated_history,320000,3100000,3420000
arr_move_0164,acct_0104,parent_0104,contract_0215,2025-06-15,contraction,generated_history,-330000,3200000,2870000
arr_move_0165,acct_0105,parent_0105,contract_0216,2025-06-15,reactivation,generated_history,340000,3300000,3640000
arr_move_0166,acct_0106,parent_0106,contract_0217,2025-06-15,new,generated_history,350000,3400000,3750000
arr_move_0167,acct_0107,parent_0107,contract_0218,2025-06-15,expansion,generated_history,360000,3500000,3860000
arr_move_0168,acct_0108,parent_0108,contract_0219,2025-06-15,contraction,generated_history,-370000,3600000,3230000
arr_move_0169,acct_0109,parent_0109,contract_0220,2025-06-15,reactivation,generated_history,250000,3700000,3950000
arr_move_0170,acct_0110,parent_0110,contract_0221,2025-06-15,new,generated_history,260000,3800000,4060000
arr_move_0171,acct_0111,parent_0111,contract_0222,2025-06-15,expansion,generated_history,270000,2000000,2270000
arr_move_0172,acct_0112,parent_0112,contract_0223,2025-06-15,contraction,generated_history,-280000,2100000,1820000
arr_move_0173,acct_0113,parent_0113,contract_0224,2025-06-15,reactivation,generated_history,290000,2200000,2490000
arr_move_0174,acct_0114,parent_0114,contract_0225,2025-06-15,new,generated_history,300000,2300000,2600000
arr_move_0175,acct_0115,parent_0115,contract_0226,2025-06-15,expansion,generated_history,310000,2400000,2710000
arr_move_0176,acct_0116,parent_0116,contract_0227,2025-06-15,contraction,generated_history,-320000,2500000,2180000
arr_move_0177,acct_0117,parent_0117,contract_0228,2025-06-15,reactivation,generated_history,330000,2600000,2930000
arr_move_0178,acct_0118,parent_0118,contract_0229,2025-06-15,new,generated_history,340000,2700000,3040000
arr_move_0179,acct_0119,parent_0119,contract_0230,2025-06-15,expansion,generated_history,350000,2800000,3150000
arr_move_0180,acct_0120,parent_0120,contract_0231,2025-06-15,contraction,generated_history,-360000,2900000,2540000
arr_move_0181,acct_0121,parent_0121,contract_0232,2025-06-15,reactivation,generated_history,370000,3000000,3370000
arr_move_0182,acct_0122,parent_0122,contract_0233,2025-06-15,new,generated_history,250000,3100000,3350000
arr_move_0183,acct_0123,parent_0123,contract_0234,2025-06-15,expansion,generated_history,260000,3200000,3460000
arr_move_0184,acct_0124,parent_0124,contract_0235,2025-06-15,contraction,generated_history,-270000,3300000,3030000
arr_move_0185,acct_0125,parent_0125,contract_0236,2025-06-15,reactivation,generated_history,280000,3400000,3680000
arr_move_0186,acct_0126,parent_0126,contract_0237,2025-06-15,new,generated_history,290000,3500000,3790000
arr_move_0187,acct_0127,parent_0127,contract_0238,2025-06-15,expansion,generated_history,300000,3600000,3900000
arr_move_0188,acct_0128,parent_0128,contract_0239,2025-06-15,contraction,generated_history,-310000,3700000,3390000
arr_move_0189,acct_0129,parent_0129,contract_0240,2025-06-15,reactivation,generated_history,320000,3800000,4120000
arr_move_0190,acct_0130,parent_0130,contract_0241,2025-06-15,new,generated_history,330000,2000000,2330000
arr_move_0191,acct_0131,parent_0131,contract_0242,2025-06-15,expansion,generated_history,340000,2100000,2440000
arr_move_0192,acct_0132,parent_0132,contract_0243,2025-06-15,contraction,generated_history,-350000,2200000,1850000
arr_move_0193,acct_0133,parent_0001,contract_0244,2025-06-15,reactivation,generated_history,360000,2300000,2660000
arr_move_0194,acct_0134,parent_0002,contract_0245,2025-06-15,new,generated_history,370000,2400000,2770000
arr_move_0195,acct_0135,parent_0003,contract_0246,2025-06-15,expansion,generated_history,250000,2500000,2750000
arr_move_0196,acct_0136,parent_0004,contract_0247,2025-06-15,contraction,generated_history,-260000,2600000,2340000
arr_move_0197,acct_0137,parent_0005,contract_0248,2025-06-15,reactivation,generated_history,270000,2700000,2970000
arr_move_0198,acct_0138,parent_0006,contract_0249,2025-06-15,new,generated_history,280000,2800000,3080000
arr_move_0199,acct_0139,parent_0007,contract_0250,2025-06-15,expansion,generated_history,290000,2900000,3190000
arr_move_0200,acct_0140,parent_0008,contract_0251,2025-06-15,contraction,generated_history,-300000,3000000,2700000
arr_move_0201,acct_0141,parent_0009,contract_0252,2025-06-15,reactivation,generated_history,310000,3100000,3410000
arr_move_0202,acct_0142,parent_0010,contract_0253,2025-06-15,new,generated_history,320000,3200000,3520000
arr_move_0203,acct_0143,parent_0011,contract_0254,2025-06-15,expansion,generated_history,330000,3300000,3630000
arr_move_0204,acct_0144,parent_0012,contract_0255,2025-06-15,contraction,generated_history,-340000,3400000,3060000
arr_move_0205,acct_0145,parent_0013,contract_0256,2025-06-15,reactivation,generated_history,350000,3500000,3850000
arr_move_0206,acct_0146,parent_0014,contract_0257,2025-06-15,new,generated_history,360000,3600000,3960000
arr_move_0207,acct_0147,parent_0015,contract_0258,2025-06-15,expansion,generated_history,370000,3700000,4070000
arr_move_0208,acct_0148,parent_0016,contract_0259,2025-06-15,contraction,generated_history,-250000,3800000,3550000
arr_move_0209,acct_0149,parent_0017,contract_0260,2025-06-15,reactivation,generated_history,260000,2000000,2260000
arr_move_0210,acct_0150,parent_0018,contract_0261,2025-06-15,new,generated_history,270000,2100000,2370000
arr_move_0211,acct_0151,parent_0019,contract_0262,2025-06-15,expansion,generated_history,280000,2200000,2480000
arr_move_0212,acct_0152,parent_0020,contract_0263,2025-06-15,contraction,generated_history,-290000,2300000,2010000
arr_move_0213,acct_0153,parent_0021,contract_0264,2025-06-15,reactivation,generated_history,300000,2400000,2700000
arr_move_0214,acct_0154,parent_0022,contract_0265,2025-06-15,new,generated_history,310000,2500000,2810000
arr_move_0215,acct_0155,parent_0023,contract_0266,2025-06-15,expansion,generated_history,320000,2600000,2920000
arr_move_0216,acct_0156,parent_0024,contract_0267,2025-06-15,contraction,generated_history,-330000,2700000,2370000
arr_move_0217,acct_0157,parent_0025,contract_0268,2025-06-15,reactivation,generated_history,340000,2800000,3140000
arr_move_0218,acct_0158,parent_0026,contract_0269,2025-06-15,new,generated_history,350000,2900000,3250000
arr_move_0219,acct_0159,parent_0027,contract_0270,2025-06-15,expansion,generated_history,360000,3000000,3360000
arr_move_0220,acct_0160,parent_0028,contract_0271,2025-06-15,contraction,generated_history,-370000,3100000,2730000
arr_move_0221,acct_0161,parent_0029,contract_0272,2025-06-15,reactivation,generated_history,250000,3200000,3450000
arr_move_0222,acct_0162,parent_0030,contract_0273,2025-06-15,new,generated_history,260000,3300000,3560000
arr_move_0223,acct_0163,parent_0031,contract_0274,2025-06-15,expansion,generated_history,270000,3400000,3670000
arr_move_0224,acct_0164,parent_0032,contract_0275,2025-06-15,contraction,generated_history,-280000,3500000,3220000
arr_move_0225,acct_0165,parent_0033,contract_0276,2025-06-15,reactivation,generated_history,290000,3600000,3890000
arr_move_0226,acct_0166,parent_0034,contract_0277,2025-06-15,new,generated_history,300000,3700000,4000000
arr_move_0227,acct_0167,parent_0035,contract_0278,2025-06-15,expansion,generated_history,310000,3800000,4110000
arr_move_0228,acct_0168,parent_0036,contract_0279,2025-06-15,contraction,generated_history,-320000,2000000,1680000
arr_move_0229,acct_0169,parent_0037,contract_0280,2025-06-15,reactivation,generated_history,330000,2100000,2430000
arr_move_0230,acct_0170,parent_0038,contract_0281,2025-06-15,new,generated_history,340000,2200000,2540000
arr_move_0231,acct_0171,parent_0039,contract_0282,2025-06-15,expansion,generated_history,350000,2300000,2650000
arr_move_0232,acct_0172,parent_0040,contract_0283,2025-06-15,contraction,generated_history,-360000,2400000,2040000
arr_move_0233,acct_0173,parent_0041,contract_0284,2025-06-15,reactivation,generated_history,370000,2500000,2870000
arr_move_0234,acct_0174,parent_0042,contract_0285,2025-06-15,new,generated_history,250000,2600000,2850000
arr_move_0235,acct_0175,parent_0043,contract_0286,2025-06-15,expansion,generated_history,260000,2700000,2960000
arr_move_0236,acct_0176,parent_0044,contract_0287,2025-06-15,contraction,generated_history,-270000,2800000,2530000
arr_move_0237,acct_0177,parent_0045,contract_0288,2025-06-15,reactivation,generated_history,280000,2900000,3180000
arr_move_0238,acct_0178,parent_0046,contract_0289,2025-06-15,new,generated_history,290000,3000000,3290000
arr_move_0239,acct_0179,parent_0047,contract_0290,2025-06-15,expansion,generated_history,300000,3100000,3400000
arr_move_0240,acct_0180,parent_0048,contract_0291,2025-06-15,contraction,generated_history,-310000,3200000,2890000
arr_move_0241,acct_0181,parent_0049,contract_0292,2025-06-15,reactivation,generated_history,320000,3300000,3620000
arr_move_0242,acct_0182,parent_0050,contract_0293,2025-06-15,new,generated_history,330000,3400000,3730000
arr_move_0243,acct_0183,parent_0051,contract_0294,2025-06-15,expansion,generated_history,340000,3500000,3840000
arr_move_0244,acct_0184,parent_0052,contract_0295,2025-06-15,contraction,generated_history,-350000,3600000,3250000
arr_move_0245,acct_0185,parent_0053,contract_0296,2025-06-15,reactivation,generated_history,360000,3700000,4060000
arr_move_0246,acct_0186,parent_0054,contract_0297,2025-06-15,new,generated_history,370000,3800000,4170000
arr_move_0247,acct_0187,parent_0055,contract_0298,2025-06-15,expansion,generated_history,250000,2000000,2250000
arr_move_0248,acct_0188,parent_0056,contract_0299,2025-06-15,contraction,generated_history,-260000,2100000,1840000
arr_move_0249,acct_0189,parent_0057,contract_0300,2025-06-15,reactivation,generated_history,270000,2200000,2470000
arr_move_0250,acct_0090,parent_0090,contract_0301,2025-06-15,new,generated_history,280000,2300000,2580000
arr_move_0251,acct_0091,parent_0091,contract_0302,2025-06-15,expansion,generated_history,290000,2400000,2690000
arr_move_0252,acct_0092,parent_0092,contract_0303,2025-06-15,contraction,generated_history,-300000,2500000,2200000
arr_move_0253,acct_0093,parent_0093,contract_0304,2025-06-15,reactivation,generated_history,310000,2600000,2910000
arr_move_0254,acct_0094,parent_0094,contract_0305,2025-06-15,new,generated_history,320000,2700000,3020000
arr_move_0255,acct_0095,parent_0095,contract_0306,2025-06-15,expansion,generated_history,330000,2800000,3130000
arr_move_0256,acct_0096,parent_0096,contract_0307,2025-06-15,contraction,generated_history,-340000,2900000,2560000
arr_move_0257,acct_0097,parent_0097,contract_0308,2025-06-15,reactivation,generated_history,350000,3000000,3350000
arr_move_0258,acct_0098,parent_0098,contract_0309,2025-06-15,new,generated_history,360000,3100000,3460000
arr_move_0259,acct_0099,parent_0099,contract_0310,2025-06-15,expansion,generated_history,370000,3200000,3570000
arr_move_0260,acct_0100,parent_0100,contract_0311,2025-06-15,contraction,generated_history,-250000,3300000,3050000
arr_move_0261,acct_0101,parent_0101,contract_0312,2025-06-15,reactivation,generated_history,260000,3400000,3660000
arr_move_0262,acct_0102,parent_0102,contract_0313,2025-06-15,new,generated_history,270000,3500000,3770000
arr_move_0263,acct_0103,parent_0103,contract_0314,2025-06-15,expansion,generated_history,280000,3600000,3880000
arr_move_0264,acct_0104,parent_0104,contract_0315,2025-06-15,contraction,generated_history,-290000,3700000,3410000
arr_move_0265,acct_0105,parent_0105,contract_0316,2025-06-15,reactivation,generated_history,300000,3800000,4100000
arr_move_0266,acct_0106,parent_0106,contract_0317,2025-06-15,new,generated_history,310000,2000000,2310000
arr_move_0267,acct_0107,parent_0107,contract_0318,2025-06-15,expansion,generated_history,320000,2100000,2420000
arr_move_0268,acct_0108,parent_0108,contract_0319,2025-06-15,contraction,generated_history,-330000,2200000,1870000
arr_move_0269,acct_0109,parent_0109,contract_0320,2025-06-15,reactivation,generated_history,340000,2300000,2640000
arr_move_0270,acct_0110,parent_0110,contract_0101,2025-06-15,new,generated_history,350000,2400000,2750000
arr_move_0271,acct_0111,parent_0111,contract_0102,2025-06-15,expansion,generated_history,360000,2500000,2860000
arr_move_0272,acct_0112,parent_0112,contract_0103,2025-06-15,contraction,generated_history,-370000,2600000,2230000
arr_move_0273,acct_0113,parent_0113,contract_0104,2025-06-15,reactivation,generated_history,250000,2700000,2950000
arr_move_0274,acct_0114,parent_0114,contract_0105,2025-06-15,new,generated_history,260000,2800000,3060000
arr_move_0275,acct_0115,parent_0115,contract_0106,2025-06-15,expansion,generated_history,270000,2900000,3170000
arr_move_0276,acct_0116,parent_0116,contract_0107,2025-06-15,contraction,generated_history,-280000,3000000,2720000
arr_move_0277,acct_0117,parent_0117,contract_0108,2025-06-15,reactivation,generated_history,290000,3100000,3390000
arr_move_0278,acct_0118,parent_0118,contract_0109,2025-06-15,new,generated_history,300000,3200000,3500000
arr_move_0279,acct_0119,parent_0119,contract_0110,2025-06-15,expansion,generated_history,310000,3300000,3610000
arr_move_0280,acct_0120,parent_0120,contract_0111,2025-06-15,contraction,generated_history,-320000,3400000,3080000
arr_move_0281,acct_0121,parent_0121,contract_0112,2025-06-15,reactivation,generated_history,330000,3500000,3830000
arr_move_0282,acct_0122,parent_0122,contract_0113,2025-06-15,new,generated_history,340000,3600000,3940000
arr_move_0283,acct_0123,parent_0123,contract_0114,2025-06-15,expansion,generated_history,350000,3700000,4050000
arr_move_0284,acct_0124,parent_0124,contract_0115,2025-06-15,contraction,generated_history,-360000,3800000,3440000
arr_move_0285,acct_0125,parent_0125,contract_0116,2025-06-15,reactivation,generated_history,370000,2000000,2370000
arr_move_0286,acct_0126,parent_0126,contract_0117,2025-06-15,new,generated_history,250000,2100000,2350000
arr_move_0287,acct_0127,parent_0127,contract_0118,2025-06-15,expansion,generated_history,260000,2200000,2460000
arr_move_0288,acct_0128,parent_0128,contract_0119,2025-06-15,contraction,generated_history,-270000,2300000,2030000
arr_move_0289,acct_0129,parent_0129,contract_0120,2025-06-15,reactivation,generated_history,280000,2400000,2680000
arr_move_0290,acct_0130,parent_0130,contract_0121,2025-06-15,new,generated_history,290000,2500000,2790000
arr_move_0291,acct_0131,parent_0131,contract_0122,2025-06-15,expansion,generated_history,300000,2600000,2900000
arr_move_0292,acct_0132,parent_0132,contract_0123,2025-06-15,contraction,generated_history,-310000,2700000,2390000
arr_move_0293,acct_0133,parent_0001,contract_0124,2025-06-15,reactivation,generated_history,320000,2800000,3120000
arr_move_0294,acct_0134,parent_0002,contract_0125,2025-06-15,new,generated_history,330000,2900000,3230000
arr_move_0295,acct_0135,parent_0003,contract_0126,2025-06-15,expansion,generated_history,340000,3000000,3340000
arr_move_0296,acct_0136,parent_0004,contract_0127,2025-06-15,contraction,generated_history,-350000,3100000,2750000
arr_move_0297,acct_0137,parent_0005,contract_0128,2025-06-15,reactivation,generated_history,360000,3200000,3560000
arr_move_0298,acct_0138,parent_0006,contract_0129,2025-06-15,new,generated_history,370000,3300000,3670000
arr_move_0299,acct_0139,parent_0007,contract_0130,2025-06-15,expansion,generated_history,250000,3400000,3650000
arr_move_0300,acct_0140,parent_0008,contract_0131,2025-06-15,contraction,generated_history,-260000,3500000,3240000
arr_move_0301,acct_0141,parent_0009,contract_0132,2025-06-15,reactivation,generated_history,270000,3600000,3870000
arr_move_0302,acct_0142,parent_0010,contract_0133,2025-06-15,new,generated_history,280000,3700000,3980000
arr_move_0303,acct_0143,parent_0011,contract_0134,2025-06-15,expansion,generated_history,290000,3800000,4090000
arr_move_0304,acct_0144,parent_0012,contract_0135,2025-06-15,contraction,generated_history,-300000,2000000,1700000
arr_move_0305,acct_0145,parent_0013,contract_0136,2025-06-15,reactivation,generated_history,310000,2100000,2410000
arr_move_0306,acct_0146,parent_0014,contract_0137,2025-06-15,new,generated_history,320000,2200000,2520000
arr_move_0307,acct_0147,parent_0015,contract_0138,2025-06-15,expansion,generated_history,330000,2300000,2630000
arr_move_0308,acct_0148,parent_0016,contract_0139,2025-06-15,contraction,generated_history,-340000,2400000,2060000
arr_move_0309,acct_0149,parent_0017,contract_0140,2025-06-15,reactivation,generated_history,350000,2500000,2850000
arr_move_0310,acct_0150,parent_0018,contract_0141,2025-06-15,new,generated_history,360000,2600000,2960000
arr_move_0311,acct_0151,parent_0019,contract_0142,2025-06-15,expansion,generated_history,370000,2700000,3070000
arr_move_0312,acct_0152,parent_0020,contract_0143,2025-06-15,contraction,generated_history,-250000,2800000,2550000
arr_move_0313,acct_0153,parent_0021,contract_0144,2025-06-15,reactivation,generated_history,260000,2900000,3160000
arr_move_0314,acct_0154,parent_0022,contract_0145,2025-06-15,new,generated_history,270000,3000000,3270000
arr_move_0315,acct_0155,parent_0023,contract_0146,2025-06-15,expansion,generated_history,280000,3100000,3380000
arr_move_0316,acct_0156,parent_0024,contract_0147,2025-06-15,contraction,generated_history,-290000,3200000,2910000
arr_move_0317,acct_0157,parent_0025,contract_0148,2025-06-15,reactivation,generated_history,300000,3300000,3600000
arr_move_0318,acct_0158,parent_0026,contract_0149,2025-06-15,new,generated_history,310000,3400000,3710000
arr_move_0319,acct_0159,parent_0027,contract_0150,2025-06-15,expansion,generated_history,320000,3500000,3820000
arr_move_0320,acct_0160,parent_0028,contract_0151,2025-06-15,contraction,generated_history,-330000,3600000,3270000
arr_move_0321,acct_0161,parent_0029,contract_0152,2025-06-15,reactivation,generated_history,340000,3700000,4040000
arr_move_0322,acct_0162,parent_0030,contract_0153,2025-06-15,new,generated_history,350000,3800000,4150000
arr_move_0323,acct_0163,parent_0031,contract_0154,2025-06-15,expansion,generated_history,360000,2000000,2360000
arr_move_0324,acct_0164,parent_0032,contract_0155,2025-06-15,contraction,generated_history,-370000,2100000,1730000
arr_move_0325,acct_0165,parent_0033,contract_0156,2025-06-15,reactivation,generated_history,250000,2200000,2450000
arr_move_0326,acct_0166,parent_0034,contract_0157,2025-06-15,new,generated_history,260000,2300000,2560000
arr_move_0327,acct_0167,parent_0035,contract_0158,2025-06-15,expansion,generated_history,270000,2400000,2670000
arr_move_0328,acct_0168,parent_0036,contract_0159,2025-06-15,contraction,generated_history,-280000,2500000,2220000
arr_move_0329,acct_0169,parent_0037,contract_0160,2025-06-15,reactivation,generated_history,290000,2600000,2890000
arr_move_0330,acct_0170,parent_0038,contract_0161,2025-06-15,new,generated_history,300000,2700000,3000000
arr_move_0331,acct_0171,parent_0039,contract_0162,2025-06-15,expansion,generated_history,310000,2800000,3110000
arr_move_0332,acct_0172,parent_0040,contract_0163,2025-06-15,contraction,generated_history,-320000,2900000,2580000
arr_move_0333,acct_0173,parent_0041,contract_0164,2025-06-15,reactivation,generated_history,330000,3000000,3330000
arr_move_0334,acct_0174,parent_0042,contract_0165,2025-06-15,new,generated_history,340000,3100000,3440000
arr_move_0335,acct_0175,parent_0043,contract_0166,2025-06-15,expansion,generated_history,350000,3200000,3550000
arr_move_0336,acct_0176,parent_0044,contract_0167,2025-06-15,contraction,generated_history,-360000,3300000,2940000
arr_move_0337,acct_0177,parent_0045,contract_0168,2025-06-15,reactivation,generated_history,370000,3400000,3770000
arr_move_0338,acct_0178,parent_0046,contract_0169,2025-06-15,new,generated_history,250000,3500000,3750000
arr_move_0339,acct_0179,parent_0047,contract_0170,2025-06-15,expansion,generated_history,260000,3600000,3860000
arr_move_0340,acct_0180,parent_0048,contract_0171,2025-06-15,contraction,generated_history,-270000,3700000,3430000
arr_move_0341,acct_0181,parent_0049,contract_0172,2025-06-15,reactivation,generated_history,280000,3800000,4080000
arr_move_0342,acct_0182,parent_0050,contract_0173,2025-06-15,new,generated_history,290000,2000000,2290000
arr_move_0343,acct_0183,parent_0051,contract_0174,2025-06-15,expansion,generated_history,300000,2100000,2400000
arr_move_0344,acct_0184,parent_0052,contract_0175,2025-06-15,contraction,generated_history,-310000,2200000,1890000
arr_move_0345,acct_0185,parent_0053,contract_0176,2025-06-15,reactivation,generated_history,320000,2300000,2620000
arr_move_0346,acct_0186,parent_0054,contract_0177,2025-06-15,new,generated_history,330000,2400000,2730000
arr_move_0347,acct_0187,parent_0055,contract_0178,2025-06-15,expansion,generated_history,340000,2500000,2840000
arr_move_0348,acct_0188,parent_0056,contract_0179,2025-06-15,contraction,generated_history,-350000,2600000,2250000
arr_move_0349,acct_0189,parent_0057,contract_0180,2025-06-15,reactivation,generated_history,360000,2700000,3060000
arr_move_0350,acct_0090,parent_0090,contract_0181,2025-06-15,new,generated_history,370000,2800000,3170000
arr_move_0351,acct_0091,parent_0091,contract_0182,2025-06-15,expansion,generated_history,250000,2900000,3150000
arr_move_0352,acct_0092,parent_0092,contract_0183,2025-06-15,contraction,generated_history,-260000,3000000,2740000
arr_move_0353,acct_0093,parent_0093,contract_0184,2025-06-15,reactivation,generated_history,270000,3100000,3370000
arr_move_0354,acct_0094,parent_0094,contract_0185,2025-06-15,new,generated_history,280000,3200000,3480000
arr_move_0355,acct_0095,parent_0095,contract_0186,2025-06-15,expansion,generated_history,290000,3300000,3590000
arr_move_0356,acct_0096,parent_0096,contract_0187,2025-06-15,contraction,generated_history,-300000,3400000,3100000
arr_move_0357,acct_0097,parent_0097,contract_0188,2025-06-15,reactivation,generated_history,310000,3500000,3810000
arr_move_0358,acct_0098,parent_0098,contract_0189,2025-06-15,new,generated_history,320000,3600000,3920000
arr_move_0359,acct_0099,parent_0099,contract_0190,2025-06-15,expansion,generated_history,330000,3700000,4030000
arr_move_0360,acct_0100,parent_0100,contract_0191,2025-06-15,contraction,generated_history,-340000,3800000,3460000
arr_move_0361,acct_0101,parent_0101,contract_0192,2025-06-15,reactivation,generated_history,350000,2000000,2350000
arr_move_0362,acct_0102,parent_0102,contract_0193,2025-06-15,new,generated_history,360000,2100000,2460000
arr_move_0363,acct_0103,parent_0103,contract_0194,2025-06-15,expansion,generated_history,370000,2200000,2570000
arr_move_0364,acct_0104,parent_0104,contract_0195,2025-06-15,contraction,generated_history,-250000,2300000,2050000
arr_move_0365,acct_0105,parent_0105,contract_0196,2025-06-15,reactivation,generated_history,260000,2400000,2660000
arr_move_0366,acct_0106,parent_0106,contract_0197,2025-06-15,new,generated_history,270000,2500000,2770000
arr_move_0367,acct_0107,parent_0107,contract_0198,2025-06-15,expansion,generated_history,280000,2600000,2880000
arr_move_0368,acct_0108,parent_0108,contract_0199,2025-06-15,contraction,generated_history,-290000,2700000,2410000
arr_move_0369,acct_0109,parent_0109,contract_0200,2025-06-15,reactivation,generated_history,300000,2800000,3100000
arr_move_0370,acct_0110,parent_0110,contract_0201,2025-06-15,new,generated_history,310000,2900000,3210000
arr_move_0371,acct_0111,parent_0111,contract_0202,2025-06-15,expansion,generated_history,320000,3000000,3320000
arr_move_0372,acct_0112,parent_0112,contract_0203,2025-06-15,contraction,generated_history,-330000,3100000,2770000
arr_move_0373,acct_0113,parent_0113,contract_0204,2025-06-15,reactivation,generated_history,340000,3200000,3540000
arr_move_0374,acct_0114,parent_0114,contract_0205,2025-06-15,new,generated_history,350000,3300000,3650000
arr_move_0375,acct_0115,parent_0115,contract_0206,2025-06-15,expansion,generated_history,360000,3400000,3760000
arr_move_0376,acct_0116,parent_0116,contract_0207,2025-06-15,contraction,generated_history,-370000,3500000,3130000
arr_move_0377,acct_0117,parent_0117,contract_0208,2025-06-15,reactivation,generated_history,250000,3600000,3850000
arr_move_0378,acct_0118,parent_0118,contract_0209,2025-06-15,new,generated_history,260000,3700000,3960000
arr_move_0379,acct_0119,parent_0119,contract_0210,2025-06-15,expansion,generated_history,270000,3800000,4070000
arr_move_0380,acct_0120,parent_0120,contract_0211,2025-06-15,contraction,generated_history,-280000,2000000,1720000
arr_move_0381,acct_0121,parent_0121,contract_0212,2025-06-15,reactivation,generated_history,290000,2100000,2390000
arr_move_0382,acct_0122,parent_0122,contract_0213,2025-06-15,new,generated_history,300000,2200000,2500000
arr_move_0383,acct_0123,parent_0123,contract_0214,2025-06-15,expansion,generated_history,310000,2300000,2610000
arr_move_0384,acct_0124,parent_0124,contract_0215,2025-06-15,contraction,generated_history,-320000,2400000,2080000
arr_move_0385,acct_0125,parent_0125,contract_0216,2025-06-15,reactivation,generated_history,330000,2500000,2830000
arr_move_0386,acct_0126,parent_0126,contract_0217,2025-06-15,new,generated_history,340000,2600000,2940000
arr_move_0387,acct_0127,parent_0127,contract_0218,2025-06-15,expansion,generated_history,350000,2700000,3050000
arr_move_0388,acct_0128,parent_0128,contract_0219,2025-06-15,contraction,generated_history,-360000,2800000,2440000
arr_move_0389,acct_0129,parent_0129,contract_0220,2025-06-15,reactivation,generated_history,370000,2900000,3270000
arr_move_0390,acct_0130,parent_0130,contract_0221,2025-06-15,new,generated_history,250000,3000000,3250000
arr_move_0391,acct_0131,parent_0131,contract_0222,2025-06-15,expansion,generated_history,260000,3100000,3360000
arr_move_0392,acct_0132,parent_0132,contract_0223,2025-06-15,contraction,generated_history,-270000,3200000,2930000
arr_move_0393,acct_0133,parent_0001,contract_0224,2025-06-15,reactivation,generated_history,280000,3300000,3580000
arr_move_0394,acct_0134,parent_0002,contract_0225,2025-06-15,new,generated_history,290000,3400000,3690000
arr_move_0395,acct_0135,parent_0003,contract_0226,2025-06-15,expansion,generated_history,300000,3500000,3800000
arr_move_0396,acct_0136,parent_0004,contract_0227,2025-06-15,contraction,generated_history,-310000,3600000,3290000
arr_move_0397,acct_0137,parent_0005,contract_0228,2025-06-15,reactivation,generated_history,320000,3700000,4020000
arr_move_0398,acct_0138,parent_0006,contract_0229,2025-06-15,new,generated_history,330000,3800000,4130000
arr_move_0399,acct_0139,parent_0007,contract_0230,2025-06-15,expansion,generated_history,340000,2000000,2340000
arr_move_0400,acct_0140,parent_0008,contract_0231,2025-06-15,contraction,generated_history,-350000,2100000,1750000
arr_move_0401,acct_0141,parent_0009,contract_0232,2025-06-15,reactivation,generated_history,360000,2200000,2560000
arr_move_0402,acct_0142,parent_0010,contract_0233,2025-06-15,new,generated_history,370000,2300000,2670000
arr_move_0403,acct_0143,parent_0011,contract_0234,2025-06-15,expansion,generated_history,250000,2400000,2650000
arr_move_0404,acct_0144,parent_0012,contract_0235,2025-06-15,contraction,generated_history,-260000,2500000,2240000
arr_move_0405,acct_0145,parent_0013,contract_0236,2025-06-15,reactivation,generated_history,270000,2600000,2870000
arr_move_0406,acct_0146,parent_0014,contract_0237,2025-06-15,new,generated_history,280000,2700000,2980000
arr_move_0407,acct_0147,parent_0015,contract_0238,2025-06-15,expansion,generated_history,290000,2800000,3090000
arr_move_0408,acct_0148,parent_0016,contract_0239,2025-06-15,contraction,generated_history,-300000,2900000,2600000
arr_move_0409,acct_0149,parent_0017,contract_0240,2025-06-15,reactivation,generated_history,310000,3000000,3310000
arr_move_0410,acct_0150,parent_0018,contract_0241,2025-06-15,new,generated_history,320000,3100000,3420000
arr_move_0411,acct_0151,parent_0019,contract_0242,2025-06-15,expansion,generated_history,330000,3200000,3530000
arr_move_0412,acct_0152,parent_0020,contract_0243,2025-06-15,contraction,generated_history,-340000,3300000,2960000
arr_move_0413,acct_0153,parent_0021,contract_0244,2025-06-15,reactivation,generated_history,350000,3400000,3750000
arr_move_0414,acct_0154,parent_0022,contract_0245,2025-06-15,new,generated_history,360000,3500000,3860000
arr_move_0415,acct_0155,parent_0023,contract_0246,2025-06-15,expansion,generated_history,370000,3600000,3970000
arr_move_0416,acct_0156,parent_0024,contract_0247,2025-06-15,contraction,generated_history,-250000,3700000,3450000
arr_move_0417,acct_0157,parent_0025,contract_0248,2025-06-15,reactivation,generated_history,260000,3800000,4060000
arr_move_0418,acct_0158,parent_0026,contract_0249,2025-06-15,new,generated_history,270000,2000000,2270000
arr_move_0419,acct_0159,parent_0027,contract_0250,2025-06-15,expansion,generated_history,280000,2100000,2380000
arr_move_0420,acct_0160,parent_0028,contract_0251,2025-06-15,contraction,generated_history,-290000,2200000,1910000
arr_move_0421,acct_0161,parent_0029,contract_0252,2025-06-15,reactivation,generated_history,300000,2300000,2600000
arr_move_0422,acct_0162,parent_0030,contract_0253,2025-06-15,new,generated_history,310000,2400000,2710000
arr_move_0423,acct_0163,parent_0031,contract_0254,2025-06-15,expansion,generated_history,320000,2500000,2820000
arr_move_0424,acct_0164,parent_0032,contract_0255,2025-06-15,contraction,generated_history,-330000,2600000,2270000
arr_move_0425,acct_0165,parent_0033,contract_0256,2025-06-15,reactivation,generated_history,340000,2700000,3040000
arr_move_0426,acct_0166,parent_0034,contract_0257,2025-06-15,new,generated_history,350000,2800000,3150000
arr_move_0427,acct_0167,parent_0035,contract_0258,2025-06-15,expansion,generated_history,360000,2900000,3260000
arr_move_0428,acct_0168,parent_0036,contract_0259,2025-06-15,contraction,generated_history,-370000,3000000,2630000
arr_move_0429,acct_0169,parent_0037,contract_0260,2025-06-15,reactivation,generated_history,250000,3100000,3350000
arr_move_0430,acct_0170,parent_0038,contract_0261,2025-06-15,new,generated_history,260000,3200000,3460000
arr_move_0431,acct_0171,parent_0039,contract_0262,2025-06-15,expansion,generated_history,270000,3300000,3570000
arr_move_0432,acct_0172,parent_0040,contract_0263,2025-06-15,contraction,generated_history,-280000,3400000,3120000
arr_move_0433,acct_0173,parent_0041,contract_0264,2025-06-15,reactivation,generated_history,290000,3500000,3790000
arr_move_0434,acct_0174,parent_0042,contract_0265,2025-06-15,new,generated_history,300000,3600000,3900000
arr_move_0435,acct_0175,parent_0043,contract_0266,2025-06-15,expansion,generated_history,310000,3700000,4010000
arr_move_0436,acct_0176,parent_0044,contract_0267,2025-06-15,contraction,generated_history,-320000,3800000,3480000
arr_move_0437,acct_0177,parent_0045,contract_0268,2025-06-15,reactivation,generated_history,330000,2000000,2330000
arr_move_0438,acct_0178,parent_0046,contract_0269,2025-06-15,new,generated_history,340000,2100000,2440000
arr_move_0439,acct_0179,parent_0047,contract_0270,2025-06-15,expansion,generated_history,350000,2200000,2550000
arr_move_0440,acct_0180,parent_0048,contract_0271,2025-06-15,contraction,generated_history,-360000,2300000,1940000
arr_move_0441,acct_0181,parent_0049,contract_0272,2025-06-15,reactivation,generated_history,370000,2400000,2770000
arr_move_0442,acct_0182,parent_0050,contract_0273,2025-06-15,new,generated_history,250000,2500000,2750000
arr_move_0443,acct_0183,parent_0051,contract_0274,2025-06-15,expansion,generated_history,260000,2600000,2860000
arr_move_0444,acct_0184,parent_0052,contract_0275,2025-06-15,contraction,generated_history,-270000,2700000,2430000
arr_move_0445,acct_0185,parent_0053,contract_0276,2025-06-15,reactivation,generated_history,280000,2800000,3080000
arr_move_0446,acct_0186,parent_0054,contract_0277,2025-06-15,new,generated_history,290000,2900000,3190000
arr_move_0447,acct_0187,parent_0055,contract_0278,2025-06-15,expansion,generated_history,300000,3000000,3300000
arr_move_0448,acct_0188,parent_0056,contract_0279,2025-06-15,contraction,generated_history,-310000,3100000,2790000
arr_move_0449,acct_0189,parent_0057,contract_0280,2025-06-15,reactivation,generated_history,320000,3200000,3520000
arr_move_0450,acct_0090,parent_0090,contract_0281,2025-06-15,new,generated_history,330000,3300000,3630000
arr_move_0451,acct_0091,parent_0091,contract_0282,2025-06-15,expansion,generated_history,340000,3400000,3740000
arr_move_0452,acct_0092,parent_0092,contract_0283,2025-06-15,contraction,generated_history,-350000,3500000,3150000
arr_move_0453,acct_0093,parent_0093,contract_0284,2025-06-15,reactivation,generated_history,360000,3600000,3960000
arr_move_0454,acct_0094,parent_0094,contract_0285,2025-06-15,new,generated_history,370000,3700000,4070000
arr_move_0455,acct_0095,parent_0095,contract_0286,2025-06-15,expansion,generated_history,250000,3800000,4050000
arr_move_0456,acct_0096,parent_0096,contract_0287,2025-06-15,contraction,generated_history,-260000,2000000,1740000
arr_move_0457,acct_0097,parent_0097,contract_0288,2025-06-15,reactivation,generated_history,270000,2100000,2370000
arr_move_0458,acct_0098,parent_0098,contract_0289,2025-06-15,new,generated_history,280000,2200000,2480000
arr_move_0459,acct_0099,parent_0099,contract_0290,2025-06-15,expansion,generated_history,290000,2300000,2590000
arr_move_0460,acct_0100,parent_0100,contract_0291,2025-06-15,contraction,generated_history,-300000,2400000,2100000
arr_move_0461,acct_0101,parent_0101,contract_0292,2025-06-15,reactivation,generated_history,310000,2500000,2810000
arr_move_0462,acct_0102,parent_0102,contract_0293,2025-06-15,new,generated_history,320000,2600000,2920000
arr_move_0463,acct_0103,parent_0103,contract_0294,2025-06-15,expansion,generated_history,330000,2700000,3030000
arr_move_0464,acct_0104,parent_0104,contract_0295,2025-06-15,contraction,generated_history,-340000,2800000,2460000
arr_move_0465,acct_0105,parent_0105,contract_0296,2025-06-15,reactivation,generated_history,350000,2900000,3250000
arr_move_0466,acct_0106,parent_0106,contract_0297,2025-06-15,new,generated_history,360000,3000000,3360000
arr_move_0467,acct_0107,parent_0107,contract_0298,2025-06-15,expansion,generated_history,370000,3100000,3470000
arr_move_0468,acct_0108,parent_0108,contract_0299,2025-06-15,contraction,generated_history,-250000,3200000,2950000
arr_move_0469,acct_0109,parent_0109,contract_0300,2025-06-15,reactivation,generated_history,260000,3300000,3560000
arr_move_0470,acct_0110,parent_0110,contract_0301,2025-06-15,new,generated_history,270000,3400000,3670000
arr_move_0471,acct_0111,parent_0111,contract_0302,2025-06-15,expansion,generated_history,280000,3500000,3780000
arr_move_0472,acct_0112,parent_0112,contract_0303,2025-06-15,contraction,generated_history,-290000,3600000,3310000
arr_move_0473,acct_0113,parent_0113,contract_0304,2025-06-15,reactivation,generated_history,300000,3700000,4000000
arr_move_0474,acct_0114,parent_0114,contract_0305,2025-06-15,new,generated_history,310000,3800000,4110000
arr_move_0475,acct_0115,parent_0115,contract_0306,2025-06-15,expansion,generated_history,320000,2000000,2320000
arr_move_0476,acct_0116,parent_0116,contract_0307,2025-06-15,contraction,generated_history,-330000,2100000,1770000
arr_move_0477,acct_0117,parent_0117,contract_0308,2025-06-15,reactivation,generated_history,340000,2200000,2540000
arr_move_0478,acct_0118,parent_0118,contract_0309,2025-06-15,new,generated_history,350000,2300000,2650000
arr_move_0479,acct_0119,parent_0119,contract_0310,2025-06-15,expansion,generated_history,360000,2400000,2760000
arr_move_0480,acct_0120,parent_0120,contract_0311,2025-06-15,contraction,generated_history,-370000,2500000,2130000
arr_move_0481,acct_0121,parent_0121,contract_0312,2025-06-15,reactivation,generated_history,250000,2600000,2850000
arr_move_0482,acct_0122,parent_0122,contract_0313,2025-06-15,new,generated_history,260000,2700000,2960000
arr_move_0483,acct_0123,parent_0123,contract_0314,2025-06-15,expansion,generated_history,270000,2800000,3070000
arr_move_0484,acct_0124,parent_0124,contract_0315,2025-06-15,contraction,generated_history,-280000,2900000,2620000
arr_move_0485,acct_0125,parent_0125,contract_0316,2025-06-15,reactivation,generated_history,290000,3000000,3290000
arr_move_0486,acct_0126,parent_0126,contract_0317,2025-06-15,new,generated_history,300000,3100000,3400000
arr_move_0487,acct_0127,parent_0127,contract_0318,2025-06-15,expansion,generated_history,310000,3200000,3510000
arr_move_0488,acct_0128,parent_0128,contract_0319,2025-06-15,contraction,generated_history,-320000,3300000,2980000
arr_move_0489,acct_0129,parent_0129,contract_0320,2025-06-15,reactivation,generated_history,330000,3400000,3730000
arr_move_0490,acct_0130,parent_0130,contract_0101,2025-06-15,new,generated_history,340000,3500000,3840000
arr_move_0491,acct_0131,parent_0131,contract_0102,2025-06-15,expansion,generated_history,350000,3600000,3950000
arr_move_0492,acct_0132,parent_0132,contract_0103,2025-06-15,contraction,generated_history,-360000,3700000,3340000
arr_move_0493,acct_0133,parent_0001,contract_0104,2025-06-15,reactivation,generated_history,370000,3800000,4170000
arr_move_0494,acct_0134,parent_0002,contract_0105,2025-06-15,new,generated_history,250000,2000000,2250000
arr_move_0495,acct_0135,parent_0003,contract_0106,2025-06-15,expansion,generated_history,260000,2100000,2360000
arr_move_0496,acct_0136,parent_0004,contract_0107,2025-06-15,contraction,generated_history,-270000,2200000,1930000
arr_move_0497,acct_0137,parent_0005,contract_0108,2025-06-15,reactivation,generated_history,280000,2300000,2580000
arr_move_0498,acct_0138,parent_0006,contract_0109,2025-06-15,new,generated_history,290000,2400000,2690000
arr_move_0499,acct_0139,parent_0007,contract_0110,2025-06-15,expansion,generated_history,300000,2500000,2800000
arr_move_0500,acct_0140,parent_0008,contract_0111,2025-06-15,contraction,generated_history,-310000,2600000,2290000
arr_move_0501,acct_0141,parent_0009,contract_0112,2025-06-15,reactivation,generated_history,320000,2700000,3020000
arr_move_0502,acct_0142,parent_0010,contract_0113,2025-06-15,new,generated_history,330000,2800000,3130000
arr_move_0503,acct_0143,parent_0011,contract_0114,2025-06-15,expansion,generated_history,340000,2900000,3240000
arr_move_0504,acct_0144,parent_0012,contract_0115,2025-06-15,contraction,generated_history,-350000,3000000,2650000
arr_move_0505,acct_0145,parent_0013,contract_0116,2025-06-15,reactivation,generated_history,360000,3100000,3460000
arr_move_0506,acct_0146,parent_0014,contract_0117,2025-06-15,new,generated_history,370000,3200000,3570000
arr_move_0507,acct_0147,parent_0015,contract_0118,2025-06-15,expansion,generated_history,250000,3300000,3550000
arr_move_0508,acct_0148,parent_0016,contract_0119,2025-06-15,contraction,generated_history,-260000,3400000,3140000
arr_move_0509,acct_0149,parent_0017,contract_0120,2025-06-15,reactivation,generated_history,270000,3500000,3770000
arr_move_0510,acct_0150,parent_0018,contract_0121,2025-06-15,new,generated_history,280000,3600000,3880000
arr_move_0511,acct_0151,parent_0019,contract_0122,2025-06-15,expansion,generated_history,290000,3700000,3990000
arr_move_0512,acct_0152,parent_0020,contract_0123,2025-06-15,contraction,generated_history,-300000,3800000,3500000
arr_move_0513,acct_0153,parent_0021,contract_0124,2025-06-15,reactivation,generated_history,310000,2000000,2310000
arr_move_0514,acct_0154,parent_0022,contract_0125,2025-06-15,new,generated_history,320000,2100000,2420000
arr_move_0515,acct_0155,parent_0023,contract_0126,2025-06-15,expansion,generated_history,330000,2200000,2530000
arr_move_0516,acct_0156,parent_0024,contract_0127,2025-06-15,contraction,generated_history,-340000,2300000,1960000
arr_move_0517,acct_0157,parent_0025,contract_0128,2025-06-15,reactivation,generated_history,350000,2400000,2750000
arr_move_0518,acct_0158,parent_0026,contract_0129,2025-06-15,new,generated_history,360000,2500000,2860000
arr_move_0519,acct_0159,parent_0027,contract_0130,2025-06-15,expansion,generated_history,370000,2600000,2970000
arr_move_0520,acct_0160,parent_0028,contract_0131,2025-06-15,contraction,generated_history,-250000,2700000,2450000
arr_move_0521,acct_0161,parent_0029,contract_0132,2025-06-15,reactivation,generated_history,260000,2800000,3060000
arr_move_0522,acct_0162,parent_0030,contract_0133,2025-06-15,new,generated_history,270000,2900000,3170000
arr_move_0523,acct_0163,parent_0031,contract_0134,2025-06-15,expansion,generated_history,280000,3000000,3280000
arr_move_0524,acct_0164,parent_0032,contract_0135,2025-06-15,contraction,generated_history,-290000,3100000,2810000
arr_move_0525,acct_0165,parent_0033,contract_0136,2025-06-15,reactivation,generated_history,300000,3200000,3500000
arr_move_0526,acct_0166,parent_0034,contract_0137,2025-06-15,new,generated_history,310000,3300000,3610000
arr_move_0527,acct_0167,parent_0035,contract_0138,2025-06-15,expansion,generated_history,320000,3400000,3720000
arr_move_0528,acct_0168,parent_0036,contract_0139,2025-06-15,contraction,generated_history,-330000,3500000,3170000
arr_move_0529,acct_0169,parent_0037,contract_0140,2025-06-15,reactivation,generated_history,340000,3600000,3940000
arr_move_0530,acct_0170,parent_0038,contract_0141,2025-06-15,new,generated_history,350000,3700000,4050000
arr_move_0531,acct_0171,parent_0039,contract_0142,2025-06-15,expansion,generated_history,360000,3800000,4160000
arr_move_0532,acct_0172,parent_0040,contract_0143,2025-06-15,contraction,generated_history,-370000,2000000,1630000
arr_move_0533,acct_0173,parent_0041,contract_0144,2025-06-15,reactivation,generated_history,250000,2100000,2350000
arr_move_0534,acct_0174,parent_0042,contract_0145,2025-06-15,new,generated_history,260000,2200000,2460000
arr_move_0535,acct_0175,parent_0043,contract_0146,2025-06-15,expansion,generated_history,270000,2300000,2570000
arr_move_0536,acct_0176,parent_0044,contract_0147,2025-06-15,contraction,generated_history,-280000,2400000,2120000
arr_move_0537,acct_0177,parent_0045,contract_0148,2025-06-15,reactivation,generated_history,290000,2500000,2790000
arr_move_0538,acct_0178,parent_0046,contract_0149,2025-06-15,new,generated_history,300000,2600000,2900000
arr_move_0539,acct_0179,parent_0047,contract_0150,2025-06-15,expansion,generated_history,310000,2700000,3010000
arr_move_0540,acct_0180,parent_0048,contract_0151,2025-06-15,contraction,generated_history,-320000,2800000,2480000
arr_move_0541,acct_0181,parent_0049,contract_0152,2025-06-15,reactivation,generated_history,330000,2900000,3230000
arr_move_0542,acct_0182,parent_0050,contract_0153,2025-06-15,new,generated_history,340000,3000000,3340000
arr_move_0543,acct_0183,parent_0051,contract_0154,2025-06-15,expansion,generated_history,350000,3100000,3450000
arr_move_0544,acct_0184,parent_0052,contract_0155,2025-06-15,contraction,generated_history,-360000,3200000,2840000
arr_move_0545,acct_0185,parent_0053,contract_0156,2025-06-15,reactivation,generated_history,370000,3300000,3670000
arr_move_0546,acct_0186,parent_0054,contract_0157,2025-06-15,new,generated_history,250000,3400000,3650000
arr_move_0547,acct_0187,parent_0055,contract_0158,2025-06-15,expansion,generated_history,260000,3500000,3760000
arr_move_0548,acct_0188,parent_0056,contract_0159,2025-06-15,contraction,generated_history,-270000,3600000,3330000
arr_move_0549,acct_0189,parent_0057,contract_0160,2025-06-15,reactivation,generated_history,280000,3700000,3980000
arr_move_0550,acct_0090,parent_0090,contract_0161,2025-06-15,new,generated_history,290000,3800000,4090000
arr_move_0551,acct_0091,parent_0091,contract_0162,2025-06-15,expansion,generated_history,300000,2000000,2300000
arr_move_0552,acct_0092,parent_0092,contract_0163,2025-06-15,contraction,generated_history,-310000,2100000,1790000
arr_move_0553,acct_0093,parent_0093,contract_0164,2025-06-15,reactivation,generated_history,320000,2200000,2520000
arr_move_0554,acct_0094,parent_0094,contract_0165,2025-06-15,new,generated_history,330000,2300000,2630000
arr_move_0555,acct_0095,parent_0095,contract_0166,2025-06-15,expansion,generated_history,340000,2400000,2740000
arr_move_0556,acct_0096,parent_0096,contract_0167,2025-06-15,contraction,generated_history,-350000,2500000,2150000
arr_move_0557,acct_0097,parent_0097,contract_0168,2025-06-15,reactivation,generated_history,360000,2600000,2960000
arr_move_0558,acct_0098,parent_0098,contract_0169,2025-06-15,new,generated_history,370000,2700000,3070000
arr_move_0559,acct_0099,parent_0099,contract_0170,2025-06-15,expansion,generated_history,250000,2800000,3050000
arr_move_0560,acct_0100,parent_0100,contract_0171,2025-06-15,contraction,generated_history,-260000,2900000,2640000
arr_move_0561,acct_0101,parent_0101,contract_0172,2025-06-15,reactivation,generated_history,270000,3000000,3270000
arr_move_0562,acct_0102,parent_0102,contract_0173,2025-06-15,new,generated_history,280000,3100000,3380000
arr_move_0563,acct_0103,parent_0103,contract_0174,2025-06-15,expansion,generated_history,290000,3200000,3490000
arr_move_0564,acct_0104,parent_0104,contract_0175,2025-06-15,contraction,generated_history,-300000,3300000,3000000
arr_move_0565,acct_0105,parent_0105,contract_0176,2025-06-15,reactivation,generated_history,310000,3400000,3710000
arr_move_0566,acct_0106,parent_0106,contract_0177,2025-06-15,new,generated_history,320000,3500000,3820000
arr_move_0567,acct_0107,parent_0107,contract_0178,2025-06-15,expansion,generated_history,330000,3600000,3930000
arr_move_0568,acct_0108,parent_0108,contract_0179,2025-06-15,contraction,generated_history,-340000,3700000,3360000
arr_move_0569,acct_0109,parent_0109,contract_0180,2025-06-15,reactivation,generated_history,350000,3800000,4150000
arr_move_0570,acct_0110,parent_0110,contract_0181,2025-06-15,new,generated_history,360000,2000000,2360000
arr_move_0571,acct_0111,parent_0111,contract_0182,2025-06-15,expansion,generated_history,370000,2100000,2470000
arr_move_0572,acct_0112,parent_0112,contract_0183,2025-06-15,contraction,generated_history,-250000,2200000,1950000
arr_move_0573,acct_0113,parent_0113,contract_0184,2025-06-15,reactivation,generated_history,260000,2300000,2560000
arr_move_0574,acct_0114,parent_0114,contract_0185,2025-06-15,new,generated_history,270000,2400000,2670000
arr_move_0575,acct_0115,parent_0115,contract_0186,2025-06-15,expansion,generated_history,280000,2500000,2780000
arr_move_0576,acct_0116,parent_0116,contract_0187,2025-06-15,contraction,generated_history,-290000,2600000,2310000
arr_move_0577,acct_0117,parent_0117,contract_0188,2025-06-15,reactivation,generated_history,300000,2700000,3000000
arr_move_0578,acct_0118,parent_0118,contract_0189,2025-06-15,new,generated_history,310000,2800000,3110000
arr_move_0579,acct_0119,parent_0119,contract_0190,2025-06-15,expansion,generated_history,320000,2900000,3220000
arr_move_0580,acct_0120,parent_0120,contract_0191,2025-06-15,contraction,generated_history,-330000,3000000,2670000
arr_move_0581,acct_0121,parent_0121,contract_0192,2025-06-15,reactivation,generated_history,340000,3100000,3440000
arr_move_0582,acct_0122,parent_0122,contract_0193,2025-06-15,new,generated_history,350000,3200000,3550000
arr_move_0583,acct_0123,parent_0123,contract_0194,2025-06-15,expansion,generated_history,360000,3300000,3660000
arr_move_0584,acct_0124,parent_0124,contract_0195,2025-06-15,contraction,generated_history,-370000,3400000,3030000
arr_move_0585,acct_0125,parent_0125,contract_0196,2025-06-15,reactivation,generated_history,250000,3500000,3750000
arr_move_0586,acct_0126,parent_0126,contract_0197,2025-06-15,new,generated_history,260000,3600000,3860000
arr_move_0587,acct_0127,parent_0127,contract_0198,2025-06-15,expansion,generated_history,270000,3700000,3970000
arr_move_0588,acct_0128,parent_0128,contract_0199,2025-06-15,contraction,generated_history,-280000,3800000,3520000
arr_move_0589,acct_0129,parent_0129,contract_0200,2025-06-15,reactivation,generated_history,290000,2000000,2290000
arr_move_0590,acct_0130,parent_0130,contract_0201,2025-06-15,new,generated_history,300000,2100000,2400000
arr_move_0591,acct_0131,parent_0131,contract_0202,2025-06-15,expansion,generated_history,310000,2200000,2510000
arr_move_0592,acct_0132,parent_0132,contract_0203,2025-06-15,contraction,generated_history,-320000,2300000,1980000
arr_move_0593,acct_0133,parent_0001,contract_0204,2025-06-15,reactivation,generated_history,330000,2400000,2730000
arr_move_0594,acct_0134,parent_0002,contract_0205,2025-06-15,new,generated_history,340000,2500000,2840000
arr_move_0595,acct_0135,parent_0003,contract_0206,2025-06-15,expansion,generated_history,350000,2600000,2950000
arr_move_0596,acct_0136,parent_0004,contract_0207,2025-06-15,contraction,generated_history,-360000,2700000,2340000
arr_move_0597,acct_0137,parent_0005,contract_0208,2025-06-15,reactivation,generated_history,370000,2800000,3170000
arr_move_0598,acct_0138,parent_0006,contract_0209,2025-06-15,new,generated_history,250000,2900000,3150000
arr_move_0599,acct_0139,parent_0007,contract_0210,2025-06-15,expansion,generated_history,260000,3000000,3260000
arr_move_0600,acct_0140,parent_0008,contract_0211,2025-06-15,contraction,generated_history,-270000,3100000,2830000
arr_move_0601,acct_0141,parent_0009,contract_0212,2025-06-15,reactivation,generated_history,280000,3200000,3480000
arr_move_0602,acct_0142,parent_0010,contract_0213,2025-06-15,new,generated_history,290000,3300000,3590000
arr_move_0603,acct_0143,parent_0011,contract_0214,2025-06-15,expansion,generated_history,300000,3400000,3700000
arr_move_0604,acct_0144,parent_0012,contract_0215,2025-06-15,contraction,generated_history,-310000,3500000,3190000
arr_move_0605,acct_0145,parent_0013,contract_0216,2025-06-15,reactivation,generated_history,320000,3600000,3920000
arr_move_0606,acct_0146,parent_0014,contract_0217,2025-06-15,new,generated_history,330000,3700000,4030000
arr_move_0607,acct_0147,parent_0015,contract_0218,2025-06-15,expansion,generated_history,340000,3800000,4140000
arr_move_0608,acct_0148,parent_0016,contract_0219,2025-06-15,contraction,generated_history,-350000,2000000,1650000
arr_move_0609,acct_0149,parent_0017,contract_0220,2025-06-15,reactivation,generated_history,360000,2100000,2460000
arr_move_0610,acct_0150,parent_0018,contract_0221,2025-06-15,new,generated_history,370000,2200000,2570000
arr_move_0611,acct_0151,parent_0019,contract_0222,2025-06-15,expansion,generated_history,250000,2300000,2550000
arr_move_0612,acct_0152,parent_0020,contract_0223,2025-06-15,contraction,generated_history,-260000,2400000,2140000
arr_move_0613,acct_0153,parent_0021,contract_0224,2025-06-15,reactivation,generated_history,270000,2500000,2770000
arr_move_0614,acct_0154,parent_0022,contract_0225,2025-06-15,new,generated_history,280000,2600000,2880000
arr_move_0615,acct_0155,parent_0023,contract_0226,2025-06-15,expansion,generated_history,290000,2700000,2990000
arr_move_0616,acct_0156,parent_0024,contract_0227,2025-06-15,contraction,generated_history,-300000,2800000,2500000
arr_move_0617,acct_0157,parent_0025,contract_0228,2025-06-15,reactivation,generated_history,310000,2900000,3210000
arr_move_0618,acct_0158,parent_0026,contract_0229,2025-06-15,new,generated_history,320000,3000000,3320000
arr_move_0619,acct_0159,parent_0027,contract_0230,2025-06-15,expansion,generated_history,330000,3100000,3430000
arr_move_0620,acct_0160,parent_0028,contract_0231,2025-06-15,contraction,generated_history,-340000,3200000,2860000
arr_move_0621,acct_0161,parent_0029,contract_0232,2025-06-15,reactivation,generated_history,350000,3300000,3650000
arr_move_0622,acct_0162,parent_0030,contract_0233,2025-06-15,new,generated_history,360000,3400000,3760000
arr_move_0623,acct_0163,parent_0031,contract_0234,2025-06-15,expansion,generated_history,370000,3500000,3870000
arr_move_0624,acct_0164,parent_0032,contract_0235,2025-06-15,contraction,generated_history,-250000,3600000,3350000
arr_move_0625,acct_0165,parent_0033,contract_0236,2025-06-15,reactivation,generated_history,260000,3700000,3960000
arr_move_0626,acct_0166,parent_0034,contract_0237,2025-06-15,new,generated_history,270000,3800000,4070000
arr_move_0627,acct_0167,parent_0035,contract_0238,2025-06-15,expansion,generated_history,280000,2000000,2280000
arr_move_0628,acct_0168,parent_0036,contract_0239,2025-06-15,contraction,generated_history,-290000,2100000,1810000
arr_move_0629,acct_0169,parent_0037,contract_0240,2025-06-15,reactivation,generated_history,300000,2200000,2500000
arr_move_0630,acct_0170,parent_0038,contract_0241,2025-06-15,new,generated_history,310000,2300000,2610000
arr_move_0631,acct_0171,parent_0039,contract_0242,2025-06-15,expansion,generated_history,320000,2400000,2720000
arr_move_0632,acct_0172,parent_0040,contract_0243,2025-06-15,contraction,generated_history,-330000,2500000,2170000
arr_move_0633,acct_0173,parent_0041,contract_0244,2025-06-15,reactivation,generated_history,340000,2600000,2940000
arr_move_0634,acct_0174,parent_0042,contract_0245,2025-06-15,new,generated_history,350000,2700000,3050000
arr_move_0635,acct_0175,parent_0043,contract_0246,2025-06-15,expansion,generated_history,360000,2800000,3160000
arr_move_0636,acct_0176,parent_0044,contract_0247,2025-06-15,contraction,generated_history,-370000,2900000,2530000
arr_move_0637,acct_0177,parent_0045,contract_0248,2025-06-15,reactivation,generated_history,250000,3000000,3250000
arr_move_0638,acct_0178,parent_0046,contract_0249,2025-06-15,new,generated_history,260000,3100000,3360000
arr_move_0639,acct_0179,parent_0047,contract_0250,2025-06-15,expansion,generated_history,270000,3200000,3470000
arr_move_0640,acct_0180,parent_0048,contract_0251,2025-06-15,contraction,generated_history,-280000,3300000,3020000
arr_move_0641,acct_0181,parent_0049,contract_0252,2025-06-15,reactivation,generated_history,290000,3400000,3690000
arr_move_0642,acct_0182,parent_0050,contract_0253,2025-06-15,new,generated_history,300000,3500000,3800000
arr_move_0643,acct_0183,parent_0051,contract_0254,2025-06-15,expansion,generated_history,310000,3600000,3910000
arr_move_0644,acct_0184,parent_0052,contract_0255,2025-06-15,contraction,generated_history,-320000,3700000,3380000
arr_move_0645,acct_0185,parent_0053,contract_0256,2025-06-15,reactivation,generated_history,330000,3800000,4130000
arr_move_0646,acct_0186,parent_0054,contract_0257,2025-06-15,new,generated_history,340000,2000000,2340000
arr_move_0647,acct_0187,parent_0055,contract_0258,2025-06-15,expansion,generated_history,350000,2100000,2450000
arr_move_0648,acct_0188,parent_0056,contract_0259,2025-06-15,contraction,generated_history,-360000,2200000,1840000
arr_move_0649,acct_0189,parent_0057,contract_0260,2025-06-15,reactivation,generated_history,370000,2300000,2670000
arr_move_0650,acct_0090,parent_0090,contract_0261,2025-06-15,new,generated_history,250000,2400000,2650000
arr_move_0651,acct_0091,parent_0091,contract_0262,2025-06-15,expansion,generated_history,260000,2500000,2760000
arr_move_0652,acct_0092,parent_0092,contract_0263,2025-06-15,contraction,generated_history,-270000,2600000,2330000
arr_move_0653,acct_0093,parent_0093,contract_0264,2025-06-15,reactivation,generated_history,280000,2700000,2980000
arr_move_0654,acct_0094,parent_0094,contract_0265,2025-06-15,new,generated_history,290000,2800000,3090000
arr_move_0655,acct_0095,parent_0095,contract_0266,2025-06-15,expansion,generated_history,300000,2900000,3200000
arr_move_0656,acct_0096,parent_0096,contract_0267,2025-06-15,contraction,generated_history,-310000,3000000,2690000
arr_move_0657,acct_0097,parent_0097,contract_0268,2025-06-15,reactivation,generated_history,320000,3100000,3420000
arr_move_0658,acct_0098,parent_0098,contract_0269,2025-06-15,new,generated_history,330000,3200000,3530000
arr_move_0659,acct_0099,parent_0099,contract_0270,2025-06-15,expansion,generated_history,340000,3300000,3640000
arr_move_0660,acct_0100,parent_0100,contract_0271,2025-06-15,contraction,generated_history,-350000,3400000,3050000
arr_move_0661,acct_0101,parent_0101,contract_0272,2025-06-15,reactivation,generated_history,360000,3500000,3860000
arr_move_0662,acct_0102,parent_0102,contract_0273,2025-06-15,new,generated_history,370000,3600000,3970000
arr_move_0663,acct_0103,parent_0103,contract_0274,2025-06-15,expansion,generated_history,250000,3700000,3950000
arr_move_0664,acct_0104,parent_0104,contract_0275,2025-06-15,contraction,generated_history,-260000,3800000,3540000
arr_move_0665,acct_0105,parent_0105,contract_0276,2025-06-15,reactivation,generated_history,270000,2000000,2270000
arr_move_0666,acct_0106,parent_0106,contract_0277,2025-06-15,new,generated_history,280000,2100000,2380000
arr_move_0667,acct_0107,parent_0107,contract_0278,2025-06-15,expansion,generated_history,290000,2200000,2490000
arr_move_0668,acct_0108,parent_0108,contract_0279,2025-06-15,contraction,generated_history,-300000,2300000,2000000
arr_move_0669,acct_0109,parent_0109,contract_0280,2025-06-15,reactivation,generated_history,310000,2400000,2710000
arr_move_0670,acct_0110,parent_0110,contract_0281,2025-06-15,new,generated_history,320000,2500000,2820000
arr_move_0671,acct_0111,parent_0111,contract_0282,2025-06-15,expansion,generated_history,330000,2600000,2930000
arr_move_0672,acct_0112,parent_0112,contract_0283,2025-06-15,contraction,generated_history,-340000,2700000,2360000
arr_move_0673,acct_0113,parent_0113,contract_0284,2025-06-15,reactivation,generated_history,350000,2800000,3150000
arr_move_0674,acct_0114,parent_0114,contract_0285,2025-06-15,new,generated_history,360000,2900000,3260000
arr_move_0675,acct_0115,parent_0115,contract_0286,2025-06-15,expansion,generated_history,370000,3000000,3370000
arr_move_0676,acct_0116,parent_0116,contract_0287,2025-06-15,contraction,generated_history,-250000,3100000,2850000
arr_move_0677,acct_0117,parent_0117,contract_0288,2025-06-15,reactivation,generated_history,260000,3200000,3460000
arr_move_0678,acct_0118,parent_0118,contract_0289,2025-06-15,new,generated_history,270000,3300000,3570000
arr_move_0679,acct_0119,parent_0119,contract_0290,2025-06-15,expansion,generated_history,280000,3400000,3680000
arr_move_0680,acct_0120,parent_0120,contract_0291,2025-06-15,contraction,generated_history,-290000,3500000,3210000
arr_move_0681,acct_0121,parent_0121,contract_0292,2025-06-15,reactivation,generated_history,300000,3600000,3900000
arr_move_0682,acct_0122,parent_0122,contract_0293,2025-06-15,new,generated_history,310000,3700000,4010000
arr_move_0683,acct_0123,parent_0123,contract_0294,2025-06-15,expansion,generated_history,320000,3800000,4120000
arr_move_0684,acct_0124,parent_0124,contract_0295,2025-06-15,contraction,generated_history,-330000,2000000,1670000
arr_move_0685,acct_0125,parent_0125,contract_0296,2025-06-15,reactivation,generated_history,340000,2100000,2440000
arr_move_0686,acct_0126,parent_0126,contract_0297,2025-06-15,new,generated_history,350000,2200000,2550000
arr_move_0687,acct_0127,parent_0127,contract_0298,2025-06-15,expansion,generated_history,360000,2300000,2660000
arr_move_0688,acct_0128,parent_0128,contract_0299,2025-06-15,contraction,generated_history,-370000,2400000,2030000
arr_move_0689,acct_0129,parent_0129,contract_0300,2025-06-15,reactivation,generated_history,250000,2500000,2750000
arr_move_0690,acct_0130,parent_0130,contract_0301,2025-06-15,new,generated_history,260000,2600000,2860000
arr_move_0691,acct_0131,parent_0131,contract_0302,2025-06-15,expansion,generated_history,270000,2700000,2970000
arr_move_0692,acct_0132,parent_0132,contract_0303,2025-06-15,contraction,generated_history,-280000,2800000,2520000
arr_move_0693,acct_0133,parent_0001,contract_0304,2025-06-15,reactivation,generated_history,290000,2900000,3190000
arr_move_0694,acct_0134,parent_0002,contract_0305,2025-06-15,new,generated_history,300000,3000000,3300000
arr_move_0695,acct_0135,parent_0003,contract_0306,2025-06-15,expansion,generated_history,310000,3100000,3410000
arr_move_0696,acct_0136,parent_0004,contract_0307,2025-06-15,contraction,generated_history,-320000,3200000,2880000
arr_move_0697,acct_0137,parent_0005,contract_0308,2025-06-15,reactivation,generated_history,330000,3300000,3630000
arr_move_0698,acct_0138,parent_0006,contract_0309,2025-06-15,new,generated_history,340000,3400000,3740000
arr_move_0699,acct_0139,parent_0007,contract_0310,2025-06-15,expansion,generated_history,350000,3500000,3850000
arr_move_0700,acct_0140,parent_0008,contract_0311,2025-06-15,contraction,generated_history,-360000,3600000,3240000
arr_move_0701,acct_0141,parent_0009,contract_0312,2025-06-15,reactivation,generated_history,370000,3700000,4070000
arr_move_0702,acct_0142,parent_0010,contract_0313,2025-06-15,new,generated_history,250000,3800000,4050000
arr_move_0703,acct_0143,parent_0011,contract_0314,2025-06-15,expansion,generated_history,260000,2000000,2260000
arr_move_0704,acct_0144,parent_0012,contract_0315,2025-06-15,contraction,generated_history,-270000,2100000,1830000
arr_move_0705,acct_0145,parent_0013,contract_0316,2025-06-15,reactivation,generated_history,280000,2200000,2480000
arr_move_0706,acct_0146,parent_0014,contract_0317,2025-06-15,new,generated_history,290000,2300000,2590000
arr_move_0707,acct_0147,parent_0015,contract_0318,2025-06-15,expansion,generated_history,300000,2400000,2700000
arr_move_0708,acct_0148,parent_0016,contract_0319,2025-06-15,contraction,generated_history,-310000,2500000,2190000
arr_move_0709,acct_0149,parent_0017,contract_0320,2025-06-15,reactivation,generated_history,320000,2600000,2920000
arr_move_0710,acct_0150,parent_0018,contract_0101,2025-06-15,new,generated_history,330000,2700000,3030000
arr_move_0711,acct_0151,parent_0019,contract_0102,2025-06-15,expansion,generated_history,340000,2800000,3140000
arr_move_0712,acct_0152,parent_0020,contract_0103,2025-06-15,contraction,generated_history,-350000,2900000,2550000
arr_move_0713,acct_0153,parent_0021,contract_0104,2025-06-15,reactivation,generated_history,360000,3000000,3360000
arr_move_0714,acct_0154,parent_0022,contract_0105,2025-06-15,new,generated_history,370000,3100000,3470000
arr_move_0715,acct_0155,parent_0023,contract_0106,2025-06-15,expansion,generated_history,250000,3200000,3450000
arr_move_0716,acct_0156,parent_0024,contract_0107,2025-06-15,contraction,generated_history,-260000,3300000,3040000
arr_move_0717,acct_0157,parent_0025,contract_0108,2025-06-15,reactivation,generated_history,270000,3400000,3670000
arr_move_0718,acct_0158,parent_0026,contract_0109,2025-06-15,new,generated_history,280000,3500000,3780000
arr_move_0719,acct_0159,parent_0027,contract_0110,2025-06-15,expansion,generated_history,290000,3600000,3890000
arr_move_0720,acct_0160,parent_0028,contract_0111,2025-06-15,contraction,generated_history,-300000,3700000,3400000
1 arr_movement_id account_id parent_account_id contract_id movement_date movement_type movement_reason arr_delta_cents starting_arr_cents ending_arr_cents
2 arr_move_0001 acct_0001 parent_0001 contract_0001 2026-02-15 expansion seat_growth 4500000 30000000 34500000
3 arr_move_0002 acct_0002 parent_0002 contract_0002 2026-02-15 expansion seat_growth 4500000 30000000 34500000
4 arr_move_0003 acct_0003 parent_0003 contract_0003 2026-02-15 expansion seat_growth 4500000 30000000 34500000
5 arr_move_0004 acct_0004 parent_0004 contract_0004 2026-02-15 expansion seat_growth 4500000 30000000 34500000
6 arr_move_0005 acct_0005 parent_0005 contract_0005 2026-02-15 expansion seat_growth 4500000 30000000 34500000
7 arr_move_0006 acct_0006 parent_0006 contract_0006 2026-02-15 expansion seat_growth 4500000 30000000 34500000
8 arr_move_0007 acct_0007 parent_0007 contract_0007 2026-02-15 expansion seat_growth 4500000 30000000 34500000
9 arr_move_0008 acct_0008 parent_0008 contract_0008 2026-02-15 expansion seat_growth 4500000 30000000 34500000
10 arr_move_0009 acct_0009 parent_0009 contract_0009 2026-02-15 expansion seat_growth 4500000 30000000 34500000
11 arr_move_0010 acct_0010 parent_0010 contract_0010 2026-02-15 expansion seat_growth 4500000 30000000 34500000
12 arr_move_0011 acct_0011 parent_0011 contract_0011 2026-02-15 expansion seat_growth 4500000 30000000 34500000
13 arr_move_0012 acct_0012 parent_0012 contract_0012 2026-02-15 expansion seat_growth 4500000 30000000 34500000
14 arr_move_0013 acct_0013 parent_0013 contract_0013 2026-02-15 expansion seat_growth 4500000 30000000 34500000
15 arr_move_0014 acct_0014 parent_0014 contract_0014 2026-02-15 expansion seat_growth 4500000 30000000 34500000
16 arr_move_0015 acct_0015 parent_0015 contract_0015 2026-02-15 expansion seat_growth 4500000 30000000 34500000
17 arr_move_0016 acct_0016 parent_0016 contract_0016 2026-02-15 expansion seat_growth 4500000 30000000 34500000
18 arr_move_0017 acct_0017 parent_0017 contract_0017 2026-02-15 expansion seat_growth 6000000 70000000 76000000
19 arr_move_0018 acct_0018 parent_0018 contract_0018 2026-02-20 contraction discount_expiration -4500000 50000000 45500000
20 arr_move_0019 acct_0019 parent_0019 contract_0019 2026-02-20 contraction discount_expiration -4500000 50000000 45500000
21 arr_move_0020 acct_0020 parent_0020 contract_0020 2026-02-20 contraction discount_expiration -4500000 50000000 45500000
22 arr_move_0021 acct_0021 parent_0021 contract_0021 2026-02-20 contraction discount_expiration -4500000 50000000 45500000
23 arr_move_0022 acct_0022 parent_0022 contract_0022 2026-02-20 contraction discount_expiration -4500000 50000000 45500000
24 arr_move_0023 acct_0023 parent_0023 contract_0023 2026-02-20 contraction discount_expiration -4500000 50000000 45500000
25 arr_move_0024 acct_0024 parent_0024 contract_0024 2026-02-20 contraction discount_expiration -4500000 50000000 45500000
26 arr_move_0025 acct_0025 parent_0025 contract_0025 2026-02-20 contraction discount_expiration -4500000 50000000 45500000
27 arr_move_0026 acct_0026 parent_0026 contract_0026 2026-02-20 contraction discount_expiration -4500000 50000000 45500000
28 arr_move_0027 acct_0027 parent_0027 contract_0027 2026-02-20 contraction discount_expiration -4500000 50000000 45500000
29 arr_move_0028 acct_0028 parent_0028 contract_0028 2026-02-20 contraction discount_expiration -5500000 100000000 94500000
30 arr_move_0029 acct_0029 parent_0029 contract_0029 2026-03-10 churn budget_loss -5000000 100000000 95000000
31 arr_move_0030 acct_0030 parent_0030 contract_0030 2025-11-15 expansion seat_growth 5000000 50000000 55000000
32 arr_move_0031 acct_0031 parent_0031 contract_0031 2025-11-15 expansion seat_growth 5000000 50000000 55000000
33 arr_move_0032 acct_0032 parent_0032 contract_0032 2025-11-15 expansion seat_growth 5000000 50000000 55000000
34 arr_move_0033 acct_0033 parent_0033 contract_0033 2025-11-15 expansion seat_growth 5000000 50000000 55000000
35 arr_move_0034 acct_0034 parent_0034 contract_0034 2025-11-15 expansion seat_growth 5000000 50000000 55000000
36 arr_move_0035 acct_0035 parent_0035 contract_0035 2025-11-15 expansion seat_growth 5000000 50000000 55000000
37 arr_move_0036 acct_0036 parent_0036 contract_0036 2025-11-15 expansion seat_growth 5000000 50000000 55000000
38 arr_move_0037 acct_0037 parent_0037 contract_0037 2025-11-15 expansion seat_growth 5000000 50000000 55000000
39 arr_move_0038 acct_0038 parent_0038 contract_0038 2025-11-15 expansion seat_growth 5000000 50000000 55000000
40 arr_move_0039 acct_0039 parent_0039 contract_0039 2025-11-15 expansion seat_growth 5000000 50000000 55000000
41 arr_move_0040 acct_0040 parent_0040 contract_0040 2025-11-15 expansion seat_growth 5000000 50000000 55000000
42 arr_move_0041 acct_0041 parent_0041 contract_0041 2025-11-15 expansion seat_growth 5000000 50000000 55000000
43 arr_move_0042 acct_0042 parent_0042 contract_0042 2025-11-15 expansion seat_growth 5000000 50000000 55000000
44 arr_move_0043 acct_0043 parent_0043 contract_0043 2025-11-15 expansion seat_growth 5000000 50000000 55000000
45 arr_move_0044 acct_0044 parent_0044 contract_0044 2025-11-15 expansion seat_growth 5000000 50000000 55000000
46 arr_move_0045 acct_0045 parent_0045 contract_0045 2025-11-15 expansion seat_growth 11800000 50000000 61800000
47 arr_move_0046 acct_0046 parent_0046 contract_0046 2025-11-20 contraction scope_reduction -2500000 100000000 97500000
48 arr_move_0047 acct_0047 parent_0047 contract_0047 2025-11-20 contraction scope_reduction -2500000 100000000 97500000
49 arr_move_0048 acct_0048 parent_0048 contract_0048 2025-11-20 contraction scope_reduction -2500000 100000000 97500000
50 arr_move_0049 acct_0049 parent_0049 contract_0049 2025-11-20 contraction scope_reduction -2500000 100000000 97500000
51 arr_move_0050 acct_0090 parent_0090 contract_0101 2025-06-15 new generated_history 360000 3200000 3560000
52 arr_move_0051 acct_0091 parent_0091 contract_0102 2025-06-15 expansion generated_history 370000 3300000 3670000
53 arr_move_0052 acct_0092 parent_0092 contract_0103 2025-06-15 contraction generated_history -250000 3400000 3150000
54 arr_move_0053 acct_0093 parent_0093 contract_0104 2025-06-15 reactivation generated_history 260000 3500000 3760000
55 arr_move_0054 acct_0094 parent_0094 contract_0105 2025-06-15 new generated_history 270000 3600000 3870000
56 arr_move_0055 acct_0095 parent_0095 contract_0106 2025-06-15 expansion generated_history 280000 3700000 3980000
57 arr_move_0056 acct_0096 parent_0096 contract_0107 2025-06-15 contraction generated_history -290000 3800000 3510000
58 arr_move_0057 acct_0097 parent_0097 contract_0108 2025-06-15 reactivation generated_history 300000 2000000 2300000
59 arr_move_0058 acct_0098 parent_0098 contract_0109 2025-06-15 new generated_history 310000 2100000 2410000
60 arr_move_0059 acct_0099 parent_0099 contract_0110 2025-06-15 expansion generated_history 320000 2200000 2520000
61 arr_move_0060 acct_0100 parent_0100 contract_0111 2025-06-15 contraction generated_history -330000 2300000 1970000
62 arr_move_0061 acct_0101 parent_0101 contract_0112 2025-06-15 reactivation generated_history 340000 2400000 2740000
63 arr_move_0062 acct_0102 parent_0102 contract_0113 2025-06-15 new generated_history 350000 2500000 2850000
64 arr_move_0063 acct_0103 parent_0103 contract_0114 2025-06-15 expansion generated_history 360000 2600000 2960000
65 arr_move_0064 acct_0104 parent_0104 contract_0115 2025-06-15 contraction generated_history -370000 2700000 2330000
66 arr_move_0065 acct_0105 parent_0105 contract_0116 2025-06-15 reactivation generated_history 250000 2800000 3050000
67 arr_move_0066 acct_0106 parent_0106 contract_0117 2025-06-15 new generated_history 260000 2900000 3160000
68 arr_move_0067 acct_0107 parent_0107 contract_0118 2025-06-15 expansion generated_history 270000 3000000 3270000
69 arr_move_0068 acct_0108 parent_0108 contract_0119 2025-06-15 contraction generated_history -280000 3100000 2820000
70 arr_move_0069 acct_0109 parent_0109 contract_0120 2025-06-15 reactivation generated_history 290000 3200000 3490000
71 arr_move_0070 acct_0110 parent_0110 contract_0121 2025-06-15 new generated_history 300000 3300000 3600000
72 arr_move_0071 acct_0111 parent_0111 contract_0122 2025-06-15 expansion generated_history 310000 3400000 3710000
73 arr_move_0072 acct_0112 parent_0112 contract_0123 2025-06-15 contraction generated_history -320000 3500000 3180000
74 arr_move_0073 acct_0113 parent_0113 contract_0124 2025-06-15 reactivation generated_history 330000 3600000 3930000
75 arr_move_0074 acct_0114 parent_0114 contract_0125 2025-06-15 new generated_history 340000 3700000 4040000
76 arr_move_0075 acct_0115 parent_0115 contract_0126 2025-06-15 expansion generated_history 350000 3800000 4150000
77 arr_move_0076 acct_0116 parent_0116 contract_0127 2025-06-15 contraction generated_history -360000 2000000 1640000
78 arr_move_0077 acct_0117 parent_0117 contract_0128 2025-06-15 reactivation generated_history 370000 2100000 2470000
79 arr_move_0078 acct_0118 parent_0118 contract_0129 2025-06-15 new generated_history 250000 2200000 2450000
80 arr_move_0079 acct_0119 parent_0119 contract_0130 2025-06-15 expansion generated_history 260000 2300000 2560000
81 arr_move_0080 acct_0120 parent_0120 contract_0131 2025-06-15 contraction generated_history -270000 2400000 2130000
82 arr_move_0081 acct_0121 parent_0121 contract_0132 2025-06-15 reactivation generated_history 280000 2500000 2780000
83 arr_move_0082 acct_0122 parent_0122 contract_0133 2025-06-15 new generated_history 290000 2600000 2890000
84 arr_move_0083 acct_0123 parent_0123 contract_0134 2025-06-15 expansion generated_history 300000 2700000 3000000
85 arr_move_0084 acct_0124 parent_0124 contract_0135 2025-06-15 contraction generated_history -310000 2800000 2490000
86 arr_move_0085 acct_0125 parent_0125 contract_0136 2025-06-15 reactivation generated_history 320000 2900000 3220000
87 arr_move_0086 acct_0126 parent_0126 contract_0137 2025-06-15 new generated_history 330000 3000000 3330000
88 arr_move_0087 acct_0127 parent_0127 contract_0138 2025-06-15 expansion generated_history 340000 3100000 3440000
89 arr_move_0088 acct_0128 parent_0128 contract_0139 2025-06-15 contraction generated_history -350000 3200000 2850000
90 arr_move_0089 acct_0129 parent_0129 contract_0140 2025-06-15 reactivation generated_history 360000 3300000 3660000
91 arr_move_0090 acct_0130 parent_0130 contract_0141 2025-06-15 new generated_history 370000 3400000 3770000
92 arr_move_0091 acct_0131 parent_0131 contract_0142 2025-06-15 expansion generated_history 250000 3500000 3750000
93 arr_move_0092 acct_0132 parent_0132 contract_0143 2025-06-15 contraction generated_history -260000 3600000 3340000
94 arr_move_0093 acct_0133 parent_0001 contract_0144 2025-06-15 reactivation generated_history 270000 3700000 3970000
95 arr_move_0094 acct_0134 parent_0002 contract_0145 2025-06-15 new generated_history 280000 3800000 4080000
96 arr_move_0095 acct_0135 parent_0003 contract_0146 2025-06-15 expansion generated_history 290000 2000000 2290000
97 arr_move_0096 acct_0136 parent_0004 contract_0147 2025-06-15 contraction generated_history -300000 2100000 1800000
98 arr_move_0097 acct_0137 parent_0005 contract_0148 2025-06-15 reactivation generated_history 310000 2200000 2510000
99 arr_move_0098 acct_0138 parent_0006 contract_0149 2025-06-15 new generated_history 320000 2300000 2620000
100 arr_move_0099 acct_0139 parent_0007 contract_0150 2025-06-15 expansion generated_history 330000 2400000 2730000
101 arr_move_0100 acct_0140 parent_0008 contract_0151 2025-06-15 contraction generated_history -340000 2500000 2160000
102 arr_move_0101 acct_0141 parent_0009 contract_0152 2025-06-15 reactivation generated_history 350000 2600000 2950000
103 arr_move_0102 acct_0142 parent_0010 contract_0153 2025-06-15 new generated_history 360000 2700000 3060000
104 arr_move_0103 acct_0143 parent_0011 contract_0154 2025-06-15 expansion generated_history 370000 2800000 3170000
105 arr_move_0104 acct_0144 parent_0012 contract_0155 2025-06-15 contraction generated_history -250000 2900000 2650000
106 arr_move_0105 acct_0145 parent_0013 contract_0156 2025-06-15 reactivation generated_history 260000 3000000 3260000
107 arr_move_0106 acct_0146 parent_0014 contract_0157 2025-06-15 new generated_history 270000 3100000 3370000
108 arr_move_0107 acct_0147 parent_0015 contract_0158 2025-06-15 expansion generated_history 280000 3200000 3480000
109 arr_move_0108 acct_0148 parent_0016 contract_0159 2025-06-15 contraction generated_history -290000 3300000 3010000
110 arr_move_0109 acct_0149 parent_0017 contract_0160 2025-06-15 reactivation generated_history 300000 3400000 3700000
111 arr_move_0110 acct_0150 parent_0018 contract_0161 2025-06-15 new generated_history 310000 3500000 3810000
112 arr_move_0111 acct_0151 parent_0019 contract_0162 2025-06-15 expansion generated_history 320000 3600000 3920000
113 arr_move_0112 acct_0152 parent_0020 contract_0163 2025-06-15 contraction generated_history -330000 3700000 3370000
114 arr_move_0113 acct_0153 parent_0021 contract_0164 2025-06-15 reactivation generated_history 340000 3800000 4140000
115 arr_move_0114 acct_0154 parent_0022 contract_0165 2025-06-15 new generated_history 350000 2000000 2350000
116 arr_move_0115 acct_0155 parent_0023 contract_0166 2025-06-15 expansion generated_history 360000 2100000 2460000
117 arr_move_0116 acct_0156 parent_0024 contract_0167 2025-06-15 contraction generated_history -370000 2200000 1830000
118 arr_move_0117 acct_0157 parent_0025 contract_0168 2025-06-15 reactivation generated_history 250000 2300000 2550000
119 arr_move_0118 acct_0158 parent_0026 contract_0169 2025-06-15 new generated_history 260000 2400000 2660000
120 arr_move_0119 acct_0159 parent_0027 contract_0170 2025-06-15 expansion generated_history 270000 2500000 2770000
121 arr_move_0120 acct_0160 parent_0028 contract_0171 2025-06-15 contraction generated_history -280000 2600000 2320000
122 arr_move_0121 acct_0161 parent_0029 contract_0172 2025-06-15 reactivation generated_history 290000 2700000 2990000
123 arr_move_0122 acct_0162 parent_0030 contract_0173 2025-06-15 new generated_history 300000 2800000 3100000
124 arr_move_0123 acct_0163 parent_0031 contract_0174 2025-06-15 expansion generated_history 310000 2900000 3210000
125 arr_move_0124 acct_0164 parent_0032 contract_0175 2025-06-15 contraction generated_history -320000 3000000 2680000
126 arr_move_0125 acct_0165 parent_0033 contract_0176 2025-06-15 reactivation generated_history 330000 3100000 3430000
127 arr_move_0126 acct_0166 parent_0034 contract_0177 2025-06-15 new generated_history 340000 3200000 3540000
128 arr_move_0127 acct_0167 parent_0035 contract_0178 2025-06-15 expansion generated_history 350000 3300000 3650000
129 arr_move_0128 acct_0168 parent_0036 contract_0179 2025-06-15 contraction generated_history -360000 3400000 3040000
130 arr_move_0129 acct_0169 parent_0037 contract_0180 2025-06-15 reactivation generated_history 370000 3500000 3870000
131 arr_move_0130 acct_0170 parent_0038 contract_0181 2025-06-15 new generated_history 250000 3600000 3850000
132 arr_move_0131 acct_0171 parent_0039 contract_0182 2025-06-15 expansion generated_history 260000 3700000 3960000
133 arr_move_0132 acct_0172 parent_0040 contract_0183 2025-06-15 contraction generated_history -270000 3800000 3530000
134 arr_move_0133 acct_0173 parent_0041 contract_0184 2025-06-15 reactivation generated_history 280000 2000000 2280000
135 arr_move_0134 acct_0174 parent_0042 contract_0185 2025-06-15 new generated_history 290000 2100000 2390000
136 arr_move_0135 acct_0175 parent_0043 contract_0186 2025-06-15 expansion generated_history 300000 2200000 2500000
137 arr_move_0136 acct_0176 parent_0044 contract_0187 2025-06-15 contraction generated_history -310000 2300000 1990000
138 arr_move_0137 acct_0177 parent_0045 contract_0188 2025-06-15 reactivation generated_history 320000 2400000 2720000
139 arr_move_0138 acct_0178 parent_0046 contract_0189 2025-06-15 new generated_history 330000 2500000 2830000
140 arr_move_0139 acct_0179 parent_0047 contract_0190 2025-06-15 expansion generated_history 340000 2600000 2940000
141 arr_move_0140 acct_0180 parent_0048 contract_0191 2025-06-15 contraction generated_history -350000 2700000 2350000
142 arr_move_0141 acct_0181 parent_0049 contract_0192 2025-06-15 reactivation generated_history 360000 2800000 3160000
143 arr_move_0142 acct_0182 parent_0050 contract_0193 2025-06-15 new generated_history 370000 2900000 3270000
144 arr_move_0143 acct_0183 parent_0051 contract_0194 2025-06-15 expansion generated_history 250000 3000000 3250000
145 arr_move_0144 acct_0184 parent_0052 contract_0195 2025-06-15 contraction generated_history -260000 3100000 2840000
146 arr_move_0145 acct_0185 parent_0053 contract_0196 2025-06-15 reactivation generated_history 270000 3200000 3470000
147 arr_move_0146 acct_0186 parent_0054 contract_0197 2025-06-15 new generated_history 280000 3300000 3580000
148 arr_move_0147 acct_0187 parent_0055 contract_0198 2025-06-15 expansion generated_history 290000 3400000 3690000
149 arr_move_0148 acct_0188 parent_0056 contract_0199 2025-06-15 contraction generated_history -300000 3500000 3200000
150 arr_move_0149 acct_0189 parent_0057 contract_0200 2025-06-15 reactivation generated_history 310000 3600000 3910000
151 arr_move_0150 acct_0090 parent_0090 contract_0201 2025-06-15 new generated_history 320000 3700000 4020000
152 arr_move_0151 acct_0091 parent_0091 contract_0202 2025-06-15 expansion generated_history 330000 3800000 4130000
153 arr_move_0152 acct_0092 parent_0092 contract_0203 2025-06-15 contraction generated_history -340000 2000000 1660000
154 arr_move_0153 acct_0093 parent_0093 contract_0204 2025-06-15 reactivation generated_history 350000 2100000 2450000
155 arr_move_0154 acct_0094 parent_0094 contract_0205 2025-06-15 new generated_history 360000 2200000 2560000
156 arr_move_0155 acct_0095 parent_0095 contract_0206 2025-06-15 expansion generated_history 370000 2300000 2670000
157 arr_move_0156 acct_0096 parent_0096 contract_0207 2025-06-15 contraction generated_history -250000 2400000 2150000
158 arr_move_0157 acct_0097 parent_0097 contract_0208 2025-06-15 reactivation generated_history 260000 2500000 2760000
159 arr_move_0158 acct_0098 parent_0098 contract_0209 2025-06-15 new generated_history 270000 2600000 2870000
160 arr_move_0159 acct_0099 parent_0099 contract_0210 2025-06-15 expansion generated_history 280000 2700000 2980000
161 arr_move_0160 acct_0100 parent_0100 contract_0211 2025-06-15 contraction generated_history -290000 2800000 2510000
162 arr_move_0161 acct_0101 parent_0101 contract_0212 2025-06-15 reactivation generated_history 300000 2900000 3200000
163 arr_move_0162 acct_0102 parent_0102 contract_0213 2025-06-15 new generated_history 310000 3000000 3310000
164 arr_move_0163 acct_0103 parent_0103 contract_0214 2025-06-15 expansion generated_history 320000 3100000 3420000
165 arr_move_0164 acct_0104 parent_0104 contract_0215 2025-06-15 contraction generated_history -330000 3200000 2870000
166 arr_move_0165 acct_0105 parent_0105 contract_0216 2025-06-15 reactivation generated_history 340000 3300000 3640000
167 arr_move_0166 acct_0106 parent_0106 contract_0217 2025-06-15 new generated_history 350000 3400000 3750000
168 arr_move_0167 acct_0107 parent_0107 contract_0218 2025-06-15 expansion generated_history 360000 3500000 3860000
169 arr_move_0168 acct_0108 parent_0108 contract_0219 2025-06-15 contraction generated_history -370000 3600000 3230000
170 arr_move_0169 acct_0109 parent_0109 contract_0220 2025-06-15 reactivation generated_history 250000 3700000 3950000
171 arr_move_0170 acct_0110 parent_0110 contract_0221 2025-06-15 new generated_history 260000 3800000 4060000
172 arr_move_0171 acct_0111 parent_0111 contract_0222 2025-06-15 expansion generated_history 270000 2000000 2270000
173 arr_move_0172 acct_0112 parent_0112 contract_0223 2025-06-15 contraction generated_history -280000 2100000 1820000
174 arr_move_0173 acct_0113 parent_0113 contract_0224 2025-06-15 reactivation generated_history 290000 2200000 2490000
175 arr_move_0174 acct_0114 parent_0114 contract_0225 2025-06-15 new generated_history 300000 2300000 2600000
176 arr_move_0175 acct_0115 parent_0115 contract_0226 2025-06-15 expansion generated_history 310000 2400000 2710000
177 arr_move_0176 acct_0116 parent_0116 contract_0227 2025-06-15 contraction generated_history -320000 2500000 2180000
178 arr_move_0177 acct_0117 parent_0117 contract_0228 2025-06-15 reactivation generated_history 330000 2600000 2930000
179 arr_move_0178 acct_0118 parent_0118 contract_0229 2025-06-15 new generated_history 340000 2700000 3040000
180 arr_move_0179 acct_0119 parent_0119 contract_0230 2025-06-15 expansion generated_history 350000 2800000 3150000
181 arr_move_0180 acct_0120 parent_0120 contract_0231 2025-06-15 contraction generated_history -360000 2900000 2540000
182 arr_move_0181 acct_0121 parent_0121 contract_0232 2025-06-15 reactivation generated_history 370000 3000000 3370000
183 arr_move_0182 acct_0122 parent_0122 contract_0233 2025-06-15 new generated_history 250000 3100000 3350000
184 arr_move_0183 acct_0123 parent_0123 contract_0234 2025-06-15 expansion generated_history 260000 3200000 3460000
185 arr_move_0184 acct_0124 parent_0124 contract_0235 2025-06-15 contraction generated_history -270000 3300000 3030000
186 arr_move_0185 acct_0125 parent_0125 contract_0236 2025-06-15 reactivation generated_history 280000 3400000 3680000
187 arr_move_0186 acct_0126 parent_0126 contract_0237 2025-06-15 new generated_history 290000 3500000 3790000
188 arr_move_0187 acct_0127 parent_0127 contract_0238 2025-06-15 expansion generated_history 300000 3600000 3900000
189 arr_move_0188 acct_0128 parent_0128 contract_0239 2025-06-15 contraction generated_history -310000 3700000 3390000
190 arr_move_0189 acct_0129 parent_0129 contract_0240 2025-06-15 reactivation generated_history 320000 3800000 4120000
191 arr_move_0190 acct_0130 parent_0130 contract_0241 2025-06-15 new generated_history 330000 2000000 2330000
192 arr_move_0191 acct_0131 parent_0131 contract_0242 2025-06-15 expansion generated_history 340000 2100000 2440000
193 arr_move_0192 acct_0132 parent_0132 contract_0243 2025-06-15 contraction generated_history -350000 2200000 1850000
194 arr_move_0193 acct_0133 parent_0001 contract_0244 2025-06-15 reactivation generated_history 360000 2300000 2660000
195 arr_move_0194 acct_0134 parent_0002 contract_0245 2025-06-15 new generated_history 370000 2400000 2770000
196 arr_move_0195 acct_0135 parent_0003 contract_0246 2025-06-15 expansion generated_history 250000 2500000 2750000
197 arr_move_0196 acct_0136 parent_0004 contract_0247 2025-06-15 contraction generated_history -260000 2600000 2340000
198 arr_move_0197 acct_0137 parent_0005 contract_0248 2025-06-15 reactivation generated_history 270000 2700000 2970000
199 arr_move_0198 acct_0138 parent_0006 contract_0249 2025-06-15 new generated_history 280000 2800000 3080000
200 arr_move_0199 acct_0139 parent_0007 contract_0250 2025-06-15 expansion generated_history 290000 2900000 3190000
201 arr_move_0200 acct_0140 parent_0008 contract_0251 2025-06-15 contraction generated_history -300000 3000000 2700000
202 arr_move_0201 acct_0141 parent_0009 contract_0252 2025-06-15 reactivation generated_history 310000 3100000 3410000
203 arr_move_0202 acct_0142 parent_0010 contract_0253 2025-06-15 new generated_history 320000 3200000 3520000
204 arr_move_0203 acct_0143 parent_0011 contract_0254 2025-06-15 expansion generated_history 330000 3300000 3630000
205 arr_move_0204 acct_0144 parent_0012 contract_0255 2025-06-15 contraction generated_history -340000 3400000 3060000
206 arr_move_0205 acct_0145 parent_0013 contract_0256 2025-06-15 reactivation generated_history 350000 3500000 3850000
207 arr_move_0206 acct_0146 parent_0014 contract_0257 2025-06-15 new generated_history 360000 3600000 3960000
208 arr_move_0207 acct_0147 parent_0015 contract_0258 2025-06-15 expansion generated_history 370000 3700000 4070000
209 arr_move_0208 acct_0148 parent_0016 contract_0259 2025-06-15 contraction generated_history -250000 3800000 3550000
210 arr_move_0209 acct_0149 parent_0017 contract_0260 2025-06-15 reactivation generated_history 260000 2000000 2260000
211 arr_move_0210 acct_0150 parent_0018 contract_0261 2025-06-15 new generated_history 270000 2100000 2370000
212 arr_move_0211 acct_0151 parent_0019 contract_0262 2025-06-15 expansion generated_history 280000 2200000 2480000
213 arr_move_0212 acct_0152 parent_0020 contract_0263 2025-06-15 contraction generated_history -290000 2300000 2010000
214 arr_move_0213 acct_0153 parent_0021 contract_0264 2025-06-15 reactivation generated_history 300000 2400000 2700000
215 arr_move_0214 acct_0154 parent_0022 contract_0265 2025-06-15 new generated_history 310000 2500000 2810000
216 arr_move_0215 acct_0155 parent_0023 contract_0266 2025-06-15 expansion generated_history 320000 2600000 2920000
217 arr_move_0216 acct_0156 parent_0024 contract_0267 2025-06-15 contraction generated_history -330000 2700000 2370000
218 arr_move_0217 acct_0157 parent_0025 contract_0268 2025-06-15 reactivation generated_history 340000 2800000 3140000
219 arr_move_0218 acct_0158 parent_0026 contract_0269 2025-06-15 new generated_history 350000 2900000 3250000
220 arr_move_0219 acct_0159 parent_0027 contract_0270 2025-06-15 expansion generated_history 360000 3000000 3360000
221 arr_move_0220 acct_0160 parent_0028 contract_0271 2025-06-15 contraction generated_history -370000 3100000 2730000
222 arr_move_0221 acct_0161 parent_0029 contract_0272 2025-06-15 reactivation generated_history 250000 3200000 3450000
223 arr_move_0222 acct_0162 parent_0030 contract_0273 2025-06-15 new generated_history 260000 3300000 3560000
224 arr_move_0223 acct_0163 parent_0031 contract_0274 2025-06-15 expansion generated_history 270000 3400000 3670000
225 arr_move_0224 acct_0164 parent_0032 contract_0275 2025-06-15 contraction generated_history -280000 3500000 3220000
226 arr_move_0225 acct_0165 parent_0033 contract_0276 2025-06-15 reactivation generated_history 290000 3600000 3890000
227 arr_move_0226 acct_0166 parent_0034 contract_0277 2025-06-15 new generated_history 300000 3700000 4000000
228 arr_move_0227 acct_0167 parent_0035 contract_0278 2025-06-15 expansion generated_history 310000 3800000 4110000
229 arr_move_0228 acct_0168 parent_0036 contract_0279 2025-06-15 contraction generated_history -320000 2000000 1680000
230 arr_move_0229 acct_0169 parent_0037 contract_0280 2025-06-15 reactivation generated_history 330000 2100000 2430000
231 arr_move_0230 acct_0170 parent_0038 contract_0281 2025-06-15 new generated_history 340000 2200000 2540000
232 arr_move_0231 acct_0171 parent_0039 contract_0282 2025-06-15 expansion generated_history 350000 2300000 2650000
233 arr_move_0232 acct_0172 parent_0040 contract_0283 2025-06-15 contraction generated_history -360000 2400000 2040000
234 arr_move_0233 acct_0173 parent_0041 contract_0284 2025-06-15 reactivation generated_history 370000 2500000 2870000
235 arr_move_0234 acct_0174 parent_0042 contract_0285 2025-06-15 new generated_history 250000 2600000 2850000
236 arr_move_0235 acct_0175 parent_0043 contract_0286 2025-06-15 expansion generated_history 260000 2700000 2960000
237 arr_move_0236 acct_0176 parent_0044 contract_0287 2025-06-15 contraction generated_history -270000 2800000 2530000
238 arr_move_0237 acct_0177 parent_0045 contract_0288 2025-06-15 reactivation generated_history 280000 2900000 3180000
239 arr_move_0238 acct_0178 parent_0046 contract_0289 2025-06-15 new generated_history 290000 3000000 3290000
240 arr_move_0239 acct_0179 parent_0047 contract_0290 2025-06-15 expansion generated_history 300000 3100000 3400000
241 arr_move_0240 acct_0180 parent_0048 contract_0291 2025-06-15 contraction generated_history -310000 3200000 2890000
242 arr_move_0241 acct_0181 parent_0049 contract_0292 2025-06-15 reactivation generated_history 320000 3300000 3620000
243 arr_move_0242 acct_0182 parent_0050 contract_0293 2025-06-15 new generated_history 330000 3400000 3730000
244 arr_move_0243 acct_0183 parent_0051 contract_0294 2025-06-15 expansion generated_history 340000 3500000 3840000
245 arr_move_0244 acct_0184 parent_0052 contract_0295 2025-06-15 contraction generated_history -350000 3600000 3250000
246 arr_move_0245 acct_0185 parent_0053 contract_0296 2025-06-15 reactivation generated_history 360000 3700000 4060000
247 arr_move_0246 acct_0186 parent_0054 contract_0297 2025-06-15 new generated_history 370000 3800000 4170000
248 arr_move_0247 acct_0187 parent_0055 contract_0298 2025-06-15 expansion generated_history 250000 2000000 2250000
249 arr_move_0248 acct_0188 parent_0056 contract_0299 2025-06-15 contraction generated_history -260000 2100000 1840000
250 arr_move_0249 acct_0189 parent_0057 contract_0300 2025-06-15 reactivation generated_history 270000 2200000 2470000
251 arr_move_0250 acct_0090 parent_0090 contract_0301 2025-06-15 new generated_history 280000 2300000 2580000
252 arr_move_0251 acct_0091 parent_0091 contract_0302 2025-06-15 expansion generated_history 290000 2400000 2690000
253 arr_move_0252 acct_0092 parent_0092 contract_0303 2025-06-15 contraction generated_history -300000 2500000 2200000
254 arr_move_0253 acct_0093 parent_0093 contract_0304 2025-06-15 reactivation generated_history 310000 2600000 2910000
255 arr_move_0254 acct_0094 parent_0094 contract_0305 2025-06-15 new generated_history 320000 2700000 3020000
256 arr_move_0255 acct_0095 parent_0095 contract_0306 2025-06-15 expansion generated_history 330000 2800000 3130000
257 arr_move_0256 acct_0096 parent_0096 contract_0307 2025-06-15 contraction generated_history -340000 2900000 2560000
258 arr_move_0257 acct_0097 parent_0097 contract_0308 2025-06-15 reactivation generated_history 350000 3000000 3350000
259 arr_move_0258 acct_0098 parent_0098 contract_0309 2025-06-15 new generated_history 360000 3100000 3460000
260 arr_move_0259 acct_0099 parent_0099 contract_0310 2025-06-15 expansion generated_history 370000 3200000 3570000
261 arr_move_0260 acct_0100 parent_0100 contract_0311 2025-06-15 contraction generated_history -250000 3300000 3050000
262 arr_move_0261 acct_0101 parent_0101 contract_0312 2025-06-15 reactivation generated_history 260000 3400000 3660000
263 arr_move_0262 acct_0102 parent_0102 contract_0313 2025-06-15 new generated_history 270000 3500000 3770000
264 arr_move_0263 acct_0103 parent_0103 contract_0314 2025-06-15 expansion generated_history 280000 3600000 3880000
265 arr_move_0264 acct_0104 parent_0104 contract_0315 2025-06-15 contraction generated_history -290000 3700000 3410000
266 arr_move_0265 acct_0105 parent_0105 contract_0316 2025-06-15 reactivation generated_history 300000 3800000 4100000
267 arr_move_0266 acct_0106 parent_0106 contract_0317 2025-06-15 new generated_history 310000 2000000 2310000
268 arr_move_0267 acct_0107 parent_0107 contract_0318 2025-06-15 expansion generated_history 320000 2100000 2420000
269 arr_move_0268 acct_0108 parent_0108 contract_0319 2025-06-15 contraction generated_history -330000 2200000 1870000
270 arr_move_0269 acct_0109 parent_0109 contract_0320 2025-06-15 reactivation generated_history 340000 2300000 2640000
271 arr_move_0270 acct_0110 parent_0110 contract_0101 2025-06-15 new generated_history 350000 2400000 2750000
272 arr_move_0271 acct_0111 parent_0111 contract_0102 2025-06-15 expansion generated_history 360000 2500000 2860000
273 arr_move_0272 acct_0112 parent_0112 contract_0103 2025-06-15 contraction generated_history -370000 2600000 2230000
274 arr_move_0273 acct_0113 parent_0113 contract_0104 2025-06-15 reactivation generated_history 250000 2700000 2950000
275 arr_move_0274 acct_0114 parent_0114 contract_0105 2025-06-15 new generated_history 260000 2800000 3060000
276 arr_move_0275 acct_0115 parent_0115 contract_0106 2025-06-15 expansion generated_history 270000 2900000 3170000
277 arr_move_0276 acct_0116 parent_0116 contract_0107 2025-06-15 contraction generated_history -280000 3000000 2720000
278 arr_move_0277 acct_0117 parent_0117 contract_0108 2025-06-15 reactivation generated_history 290000 3100000 3390000
279 arr_move_0278 acct_0118 parent_0118 contract_0109 2025-06-15 new generated_history 300000 3200000 3500000
280 arr_move_0279 acct_0119 parent_0119 contract_0110 2025-06-15 expansion generated_history 310000 3300000 3610000
281 arr_move_0280 acct_0120 parent_0120 contract_0111 2025-06-15 contraction generated_history -320000 3400000 3080000
282 arr_move_0281 acct_0121 parent_0121 contract_0112 2025-06-15 reactivation generated_history 330000 3500000 3830000
283 arr_move_0282 acct_0122 parent_0122 contract_0113 2025-06-15 new generated_history 340000 3600000 3940000
284 arr_move_0283 acct_0123 parent_0123 contract_0114 2025-06-15 expansion generated_history 350000 3700000 4050000
285 arr_move_0284 acct_0124 parent_0124 contract_0115 2025-06-15 contraction generated_history -360000 3800000 3440000
286 arr_move_0285 acct_0125 parent_0125 contract_0116 2025-06-15 reactivation generated_history 370000 2000000 2370000
287 arr_move_0286 acct_0126 parent_0126 contract_0117 2025-06-15 new generated_history 250000 2100000 2350000
288 arr_move_0287 acct_0127 parent_0127 contract_0118 2025-06-15 expansion generated_history 260000 2200000 2460000
289 arr_move_0288 acct_0128 parent_0128 contract_0119 2025-06-15 contraction generated_history -270000 2300000 2030000
290 arr_move_0289 acct_0129 parent_0129 contract_0120 2025-06-15 reactivation generated_history 280000 2400000 2680000
291 arr_move_0290 acct_0130 parent_0130 contract_0121 2025-06-15 new generated_history 290000 2500000 2790000
292 arr_move_0291 acct_0131 parent_0131 contract_0122 2025-06-15 expansion generated_history 300000 2600000 2900000
293 arr_move_0292 acct_0132 parent_0132 contract_0123 2025-06-15 contraction generated_history -310000 2700000 2390000
294 arr_move_0293 acct_0133 parent_0001 contract_0124 2025-06-15 reactivation generated_history 320000 2800000 3120000
295 arr_move_0294 acct_0134 parent_0002 contract_0125 2025-06-15 new generated_history 330000 2900000 3230000
296 arr_move_0295 acct_0135 parent_0003 contract_0126 2025-06-15 expansion generated_history 340000 3000000 3340000
297 arr_move_0296 acct_0136 parent_0004 contract_0127 2025-06-15 contraction generated_history -350000 3100000 2750000
298 arr_move_0297 acct_0137 parent_0005 contract_0128 2025-06-15 reactivation generated_history 360000 3200000 3560000
299 arr_move_0298 acct_0138 parent_0006 contract_0129 2025-06-15 new generated_history 370000 3300000 3670000
300 arr_move_0299 acct_0139 parent_0007 contract_0130 2025-06-15 expansion generated_history 250000 3400000 3650000
301 arr_move_0300 acct_0140 parent_0008 contract_0131 2025-06-15 contraction generated_history -260000 3500000 3240000
302 arr_move_0301 acct_0141 parent_0009 contract_0132 2025-06-15 reactivation generated_history 270000 3600000 3870000
303 arr_move_0302 acct_0142 parent_0010 contract_0133 2025-06-15 new generated_history 280000 3700000 3980000
304 arr_move_0303 acct_0143 parent_0011 contract_0134 2025-06-15 expansion generated_history 290000 3800000 4090000
305 arr_move_0304 acct_0144 parent_0012 contract_0135 2025-06-15 contraction generated_history -300000 2000000 1700000
306 arr_move_0305 acct_0145 parent_0013 contract_0136 2025-06-15 reactivation generated_history 310000 2100000 2410000
307 arr_move_0306 acct_0146 parent_0014 contract_0137 2025-06-15 new generated_history 320000 2200000 2520000
308 arr_move_0307 acct_0147 parent_0015 contract_0138 2025-06-15 expansion generated_history 330000 2300000 2630000
309 arr_move_0308 acct_0148 parent_0016 contract_0139 2025-06-15 contraction generated_history -340000 2400000 2060000
310 arr_move_0309 acct_0149 parent_0017 contract_0140 2025-06-15 reactivation generated_history 350000 2500000 2850000
311 arr_move_0310 acct_0150 parent_0018 contract_0141 2025-06-15 new generated_history 360000 2600000 2960000
312 arr_move_0311 acct_0151 parent_0019 contract_0142 2025-06-15 expansion generated_history 370000 2700000 3070000
313 arr_move_0312 acct_0152 parent_0020 contract_0143 2025-06-15 contraction generated_history -250000 2800000 2550000
314 arr_move_0313 acct_0153 parent_0021 contract_0144 2025-06-15 reactivation generated_history 260000 2900000 3160000
315 arr_move_0314 acct_0154 parent_0022 contract_0145 2025-06-15 new generated_history 270000 3000000 3270000
316 arr_move_0315 acct_0155 parent_0023 contract_0146 2025-06-15 expansion generated_history 280000 3100000 3380000
317 arr_move_0316 acct_0156 parent_0024 contract_0147 2025-06-15 contraction generated_history -290000 3200000 2910000
318 arr_move_0317 acct_0157 parent_0025 contract_0148 2025-06-15 reactivation generated_history 300000 3300000 3600000
319 arr_move_0318 acct_0158 parent_0026 contract_0149 2025-06-15 new generated_history 310000 3400000 3710000
320 arr_move_0319 acct_0159 parent_0027 contract_0150 2025-06-15 expansion generated_history 320000 3500000 3820000
321 arr_move_0320 acct_0160 parent_0028 contract_0151 2025-06-15 contraction generated_history -330000 3600000 3270000
322 arr_move_0321 acct_0161 parent_0029 contract_0152 2025-06-15 reactivation generated_history 340000 3700000 4040000
323 arr_move_0322 acct_0162 parent_0030 contract_0153 2025-06-15 new generated_history 350000 3800000 4150000
324 arr_move_0323 acct_0163 parent_0031 contract_0154 2025-06-15 expansion generated_history 360000 2000000 2360000
325 arr_move_0324 acct_0164 parent_0032 contract_0155 2025-06-15 contraction generated_history -370000 2100000 1730000
326 arr_move_0325 acct_0165 parent_0033 contract_0156 2025-06-15 reactivation generated_history 250000 2200000 2450000
327 arr_move_0326 acct_0166 parent_0034 contract_0157 2025-06-15 new generated_history 260000 2300000 2560000
328 arr_move_0327 acct_0167 parent_0035 contract_0158 2025-06-15 expansion generated_history 270000 2400000 2670000
329 arr_move_0328 acct_0168 parent_0036 contract_0159 2025-06-15 contraction generated_history -280000 2500000 2220000
330 arr_move_0329 acct_0169 parent_0037 contract_0160 2025-06-15 reactivation generated_history 290000 2600000 2890000
331 arr_move_0330 acct_0170 parent_0038 contract_0161 2025-06-15 new generated_history 300000 2700000 3000000
332 arr_move_0331 acct_0171 parent_0039 contract_0162 2025-06-15 expansion generated_history 310000 2800000 3110000
333 arr_move_0332 acct_0172 parent_0040 contract_0163 2025-06-15 contraction generated_history -320000 2900000 2580000
334 arr_move_0333 acct_0173 parent_0041 contract_0164 2025-06-15 reactivation generated_history 330000 3000000 3330000
335 arr_move_0334 acct_0174 parent_0042 contract_0165 2025-06-15 new generated_history 340000 3100000 3440000
336 arr_move_0335 acct_0175 parent_0043 contract_0166 2025-06-15 expansion generated_history 350000 3200000 3550000
337 arr_move_0336 acct_0176 parent_0044 contract_0167 2025-06-15 contraction generated_history -360000 3300000 2940000
338 arr_move_0337 acct_0177 parent_0045 contract_0168 2025-06-15 reactivation generated_history 370000 3400000 3770000
339 arr_move_0338 acct_0178 parent_0046 contract_0169 2025-06-15 new generated_history 250000 3500000 3750000
340 arr_move_0339 acct_0179 parent_0047 contract_0170 2025-06-15 expansion generated_history 260000 3600000 3860000
341 arr_move_0340 acct_0180 parent_0048 contract_0171 2025-06-15 contraction generated_history -270000 3700000 3430000
342 arr_move_0341 acct_0181 parent_0049 contract_0172 2025-06-15 reactivation generated_history 280000 3800000 4080000
343 arr_move_0342 acct_0182 parent_0050 contract_0173 2025-06-15 new generated_history 290000 2000000 2290000
344 arr_move_0343 acct_0183 parent_0051 contract_0174 2025-06-15 expansion generated_history 300000 2100000 2400000
345 arr_move_0344 acct_0184 parent_0052 contract_0175 2025-06-15 contraction generated_history -310000 2200000 1890000
346 arr_move_0345 acct_0185 parent_0053 contract_0176 2025-06-15 reactivation generated_history 320000 2300000 2620000
347 arr_move_0346 acct_0186 parent_0054 contract_0177 2025-06-15 new generated_history 330000 2400000 2730000
348 arr_move_0347 acct_0187 parent_0055 contract_0178 2025-06-15 expansion generated_history 340000 2500000 2840000
349 arr_move_0348 acct_0188 parent_0056 contract_0179 2025-06-15 contraction generated_history -350000 2600000 2250000
350 arr_move_0349 acct_0189 parent_0057 contract_0180 2025-06-15 reactivation generated_history 360000 2700000 3060000
351 arr_move_0350 acct_0090 parent_0090 contract_0181 2025-06-15 new generated_history 370000 2800000 3170000
352 arr_move_0351 acct_0091 parent_0091 contract_0182 2025-06-15 expansion generated_history 250000 2900000 3150000
353 arr_move_0352 acct_0092 parent_0092 contract_0183 2025-06-15 contraction generated_history -260000 3000000 2740000
354 arr_move_0353 acct_0093 parent_0093 contract_0184 2025-06-15 reactivation generated_history 270000 3100000 3370000
355 arr_move_0354 acct_0094 parent_0094 contract_0185 2025-06-15 new generated_history 280000 3200000 3480000
356 arr_move_0355 acct_0095 parent_0095 contract_0186 2025-06-15 expansion generated_history 290000 3300000 3590000
357 arr_move_0356 acct_0096 parent_0096 contract_0187 2025-06-15 contraction generated_history -300000 3400000 3100000
358 arr_move_0357 acct_0097 parent_0097 contract_0188 2025-06-15 reactivation generated_history 310000 3500000 3810000
359 arr_move_0358 acct_0098 parent_0098 contract_0189 2025-06-15 new generated_history 320000 3600000 3920000
360 arr_move_0359 acct_0099 parent_0099 contract_0190 2025-06-15 expansion generated_history 330000 3700000 4030000
361 arr_move_0360 acct_0100 parent_0100 contract_0191 2025-06-15 contraction generated_history -340000 3800000 3460000
362 arr_move_0361 acct_0101 parent_0101 contract_0192 2025-06-15 reactivation generated_history 350000 2000000 2350000
363 arr_move_0362 acct_0102 parent_0102 contract_0193 2025-06-15 new generated_history 360000 2100000 2460000
364 arr_move_0363 acct_0103 parent_0103 contract_0194 2025-06-15 expansion generated_history 370000 2200000 2570000
365 arr_move_0364 acct_0104 parent_0104 contract_0195 2025-06-15 contraction generated_history -250000 2300000 2050000
366 arr_move_0365 acct_0105 parent_0105 contract_0196 2025-06-15 reactivation generated_history 260000 2400000 2660000
367 arr_move_0366 acct_0106 parent_0106 contract_0197 2025-06-15 new generated_history 270000 2500000 2770000
368 arr_move_0367 acct_0107 parent_0107 contract_0198 2025-06-15 expansion generated_history 280000 2600000 2880000
369 arr_move_0368 acct_0108 parent_0108 contract_0199 2025-06-15 contraction generated_history -290000 2700000 2410000
370 arr_move_0369 acct_0109 parent_0109 contract_0200 2025-06-15 reactivation generated_history 300000 2800000 3100000
371 arr_move_0370 acct_0110 parent_0110 contract_0201 2025-06-15 new generated_history 310000 2900000 3210000
372 arr_move_0371 acct_0111 parent_0111 contract_0202 2025-06-15 expansion generated_history 320000 3000000 3320000
373 arr_move_0372 acct_0112 parent_0112 contract_0203 2025-06-15 contraction generated_history -330000 3100000 2770000
374 arr_move_0373 acct_0113 parent_0113 contract_0204 2025-06-15 reactivation generated_history 340000 3200000 3540000
375 arr_move_0374 acct_0114 parent_0114 contract_0205 2025-06-15 new generated_history 350000 3300000 3650000
376 arr_move_0375 acct_0115 parent_0115 contract_0206 2025-06-15 expansion generated_history 360000 3400000 3760000
377 arr_move_0376 acct_0116 parent_0116 contract_0207 2025-06-15 contraction generated_history -370000 3500000 3130000
378 arr_move_0377 acct_0117 parent_0117 contract_0208 2025-06-15 reactivation generated_history 250000 3600000 3850000
379 arr_move_0378 acct_0118 parent_0118 contract_0209 2025-06-15 new generated_history 260000 3700000 3960000
380 arr_move_0379 acct_0119 parent_0119 contract_0210 2025-06-15 expansion generated_history 270000 3800000 4070000
381 arr_move_0380 acct_0120 parent_0120 contract_0211 2025-06-15 contraction generated_history -280000 2000000 1720000
382 arr_move_0381 acct_0121 parent_0121 contract_0212 2025-06-15 reactivation generated_history 290000 2100000 2390000
383 arr_move_0382 acct_0122 parent_0122 contract_0213 2025-06-15 new generated_history 300000 2200000 2500000
384 arr_move_0383 acct_0123 parent_0123 contract_0214 2025-06-15 expansion generated_history 310000 2300000 2610000
385 arr_move_0384 acct_0124 parent_0124 contract_0215 2025-06-15 contraction generated_history -320000 2400000 2080000
386 arr_move_0385 acct_0125 parent_0125 contract_0216 2025-06-15 reactivation generated_history 330000 2500000 2830000
387 arr_move_0386 acct_0126 parent_0126 contract_0217 2025-06-15 new generated_history 340000 2600000 2940000
388 arr_move_0387 acct_0127 parent_0127 contract_0218 2025-06-15 expansion generated_history 350000 2700000 3050000
389 arr_move_0388 acct_0128 parent_0128 contract_0219 2025-06-15 contraction generated_history -360000 2800000 2440000
390 arr_move_0389 acct_0129 parent_0129 contract_0220 2025-06-15 reactivation generated_history 370000 2900000 3270000
391 arr_move_0390 acct_0130 parent_0130 contract_0221 2025-06-15 new generated_history 250000 3000000 3250000
392 arr_move_0391 acct_0131 parent_0131 contract_0222 2025-06-15 expansion generated_history 260000 3100000 3360000
393 arr_move_0392 acct_0132 parent_0132 contract_0223 2025-06-15 contraction generated_history -270000 3200000 2930000
394 arr_move_0393 acct_0133 parent_0001 contract_0224 2025-06-15 reactivation generated_history 280000 3300000 3580000
395 arr_move_0394 acct_0134 parent_0002 contract_0225 2025-06-15 new generated_history 290000 3400000 3690000
396 arr_move_0395 acct_0135 parent_0003 contract_0226 2025-06-15 expansion generated_history 300000 3500000 3800000
397 arr_move_0396 acct_0136 parent_0004 contract_0227 2025-06-15 contraction generated_history -310000 3600000 3290000
398 arr_move_0397 acct_0137 parent_0005 contract_0228 2025-06-15 reactivation generated_history 320000 3700000 4020000
399 arr_move_0398 acct_0138 parent_0006 contract_0229 2025-06-15 new generated_history 330000 3800000 4130000
400 arr_move_0399 acct_0139 parent_0007 contract_0230 2025-06-15 expansion generated_history 340000 2000000 2340000
401 arr_move_0400 acct_0140 parent_0008 contract_0231 2025-06-15 contraction generated_history -350000 2100000 1750000
402 arr_move_0401 acct_0141 parent_0009 contract_0232 2025-06-15 reactivation generated_history 360000 2200000 2560000
403 arr_move_0402 acct_0142 parent_0010 contract_0233 2025-06-15 new generated_history 370000 2300000 2670000
404 arr_move_0403 acct_0143 parent_0011 contract_0234 2025-06-15 expansion generated_history 250000 2400000 2650000
405 arr_move_0404 acct_0144 parent_0012 contract_0235 2025-06-15 contraction generated_history -260000 2500000 2240000
406 arr_move_0405 acct_0145 parent_0013 contract_0236 2025-06-15 reactivation generated_history 270000 2600000 2870000
407 arr_move_0406 acct_0146 parent_0014 contract_0237 2025-06-15 new generated_history 280000 2700000 2980000
408 arr_move_0407 acct_0147 parent_0015 contract_0238 2025-06-15 expansion generated_history 290000 2800000 3090000
409 arr_move_0408 acct_0148 parent_0016 contract_0239 2025-06-15 contraction generated_history -300000 2900000 2600000
410 arr_move_0409 acct_0149 parent_0017 contract_0240 2025-06-15 reactivation generated_history 310000 3000000 3310000
411 arr_move_0410 acct_0150 parent_0018 contract_0241 2025-06-15 new generated_history 320000 3100000 3420000
412 arr_move_0411 acct_0151 parent_0019 contract_0242 2025-06-15 expansion generated_history 330000 3200000 3530000
413 arr_move_0412 acct_0152 parent_0020 contract_0243 2025-06-15 contraction generated_history -340000 3300000 2960000
414 arr_move_0413 acct_0153 parent_0021 contract_0244 2025-06-15 reactivation generated_history 350000 3400000 3750000
415 arr_move_0414 acct_0154 parent_0022 contract_0245 2025-06-15 new generated_history 360000 3500000 3860000
416 arr_move_0415 acct_0155 parent_0023 contract_0246 2025-06-15 expansion generated_history 370000 3600000 3970000
417 arr_move_0416 acct_0156 parent_0024 contract_0247 2025-06-15 contraction generated_history -250000 3700000 3450000
418 arr_move_0417 acct_0157 parent_0025 contract_0248 2025-06-15 reactivation generated_history 260000 3800000 4060000
419 arr_move_0418 acct_0158 parent_0026 contract_0249 2025-06-15 new generated_history 270000 2000000 2270000
420 arr_move_0419 acct_0159 parent_0027 contract_0250 2025-06-15 expansion generated_history 280000 2100000 2380000
421 arr_move_0420 acct_0160 parent_0028 contract_0251 2025-06-15 contraction generated_history -290000 2200000 1910000
422 arr_move_0421 acct_0161 parent_0029 contract_0252 2025-06-15 reactivation generated_history 300000 2300000 2600000
423 arr_move_0422 acct_0162 parent_0030 contract_0253 2025-06-15 new generated_history 310000 2400000 2710000
424 arr_move_0423 acct_0163 parent_0031 contract_0254 2025-06-15 expansion generated_history 320000 2500000 2820000
425 arr_move_0424 acct_0164 parent_0032 contract_0255 2025-06-15 contraction generated_history -330000 2600000 2270000
426 arr_move_0425 acct_0165 parent_0033 contract_0256 2025-06-15 reactivation generated_history 340000 2700000 3040000
427 arr_move_0426 acct_0166 parent_0034 contract_0257 2025-06-15 new generated_history 350000 2800000 3150000
428 arr_move_0427 acct_0167 parent_0035 contract_0258 2025-06-15 expansion generated_history 360000 2900000 3260000
429 arr_move_0428 acct_0168 parent_0036 contract_0259 2025-06-15 contraction generated_history -370000 3000000 2630000
430 arr_move_0429 acct_0169 parent_0037 contract_0260 2025-06-15 reactivation generated_history 250000 3100000 3350000
431 arr_move_0430 acct_0170 parent_0038 contract_0261 2025-06-15 new generated_history 260000 3200000 3460000
432 arr_move_0431 acct_0171 parent_0039 contract_0262 2025-06-15 expansion generated_history 270000 3300000 3570000
433 arr_move_0432 acct_0172 parent_0040 contract_0263 2025-06-15 contraction generated_history -280000 3400000 3120000
434 arr_move_0433 acct_0173 parent_0041 contract_0264 2025-06-15 reactivation generated_history 290000 3500000 3790000
435 arr_move_0434 acct_0174 parent_0042 contract_0265 2025-06-15 new generated_history 300000 3600000 3900000
436 arr_move_0435 acct_0175 parent_0043 contract_0266 2025-06-15 expansion generated_history 310000 3700000 4010000
437 arr_move_0436 acct_0176 parent_0044 contract_0267 2025-06-15 contraction generated_history -320000 3800000 3480000
438 arr_move_0437 acct_0177 parent_0045 contract_0268 2025-06-15 reactivation generated_history 330000 2000000 2330000
439 arr_move_0438 acct_0178 parent_0046 contract_0269 2025-06-15 new generated_history 340000 2100000 2440000
440 arr_move_0439 acct_0179 parent_0047 contract_0270 2025-06-15 expansion generated_history 350000 2200000 2550000
441 arr_move_0440 acct_0180 parent_0048 contract_0271 2025-06-15 contraction generated_history -360000 2300000 1940000
442 arr_move_0441 acct_0181 parent_0049 contract_0272 2025-06-15 reactivation generated_history 370000 2400000 2770000
443 arr_move_0442 acct_0182 parent_0050 contract_0273 2025-06-15 new generated_history 250000 2500000 2750000
444 arr_move_0443 acct_0183 parent_0051 contract_0274 2025-06-15 expansion generated_history 260000 2600000 2860000
445 arr_move_0444 acct_0184 parent_0052 contract_0275 2025-06-15 contraction generated_history -270000 2700000 2430000
446 arr_move_0445 acct_0185 parent_0053 contract_0276 2025-06-15 reactivation generated_history 280000 2800000 3080000
447 arr_move_0446 acct_0186 parent_0054 contract_0277 2025-06-15 new generated_history 290000 2900000 3190000
448 arr_move_0447 acct_0187 parent_0055 contract_0278 2025-06-15 expansion generated_history 300000 3000000 3300000
449 arr_move_0448 acct_0188 parent_0056 contract_0279 2025-06-15 contraction generated_history -310000 3100000 2790000
450 arr_move_0449 acct_0189 parent_0057 contract_0280 2025-06-15 reactivation generated_history 320000 3200000 3520000
451 arr_move_0450 acct_0090 parent_0090 contract_0281 2025-06-15 new generated_history 330000 3300000 3630000
452 arr_move_0451 acct_0091 parent_0091 contract_0282 2025-06-15 expansion generated_history 340000 3400000 3740000
453 arr_move_0452 acct_0092 parent_0092 contract_0283 2025-06-15 contraction generated_history -350000 3500000 3150000
454 arr_move_0453 acct_0093 parent_0093 contract_0284 2025-06-15 reactivation generated_history 360000 3600000 3960000
455 arr_move_0454 acct_0094 parent_0094 contract_0285 2025-06-15 new generated_history 370000 3700000 4070000
456 arr_move_0455 acct_0095 parent_0095 contract_0286 2025-06-15 expansion generated_history 250000 3800000 4050000
457 arr_move_0456 acct_0096 parent_0096 contract_0287 2025-06-15 contraction generated_history -260000 2000000 1740000
458 arr_move_0457 acct_0097 parent_0097 contract_0288 2025-06-15 reactivation generated_history 270000 2100000 2370000
459 arr_move_0458 acct_0098 parent_0098 contract_0289 2025-06-15 new generated_history 280000 2200000 2480000
460 arr_move_0459 acct_0099 parent_0099 contract_0290 2025-06-15 expansion generated_history 290000 2300000 2590000
461 arr_move_0460 acct_0100 parent_0100 contract_0291 2025-06-15 contraction generated_history -300000 2400000 2100000
462 arr_move_0461 acct_0101 parent_0101 contract_0292 2025-06-15 reactivation generated_history 310000 2500000 2810000
463 arr_move_0462 acct_0102 parent_0102 contract_0293 2025-06-15 new generated_history 320000 2600000 2920000
464 arr_move_0463 acct_0103 parent_0103 contract_0294 2025-06-15 expansion generated_history 330000 2700000 3030000
465 arr_move_0464 acct_0104 parent_0104 contract_0295 2025-06-15 contraction generated_history -340000 2800000 2460000
466 arr_move_0465 acct_0105 parent_0105 contract_0296 2025-06-15 reactivation generated_history 350000 2900000 3250000
467 arr_move_0466 acct_0106 parent_0106 contract_0297 2025-06-15 new generated_history 360000 3000000 3360000
468 arr_move_0467 acct_0107 parent_0107 contract_0298 2025-06-15 expansion generated_history 370000 3100000 3470000
469 arr_move_0468 acct_0108 parent_0108 contract_0299 2025-06-15 contraction generated_history -250000 3200000 2950000
470 arr_move_0469 acct_0109 parent_0109 contract_0300 2025-06-15 reactivation generated_history 260000 3300000 3560000
471 arr_move_0470 acct_0110 parent_0110 contract_0301 2025-06-15 new generated_history 270000 3400000 3670000
472 arr_move_0471 acct_0111 parent_0111 contract_0302 2025-06-15 expansion generated_history 280000 3500000 3780000
473 arr_move_0472 acct_0112 parent_0112 contract_0303 2025-06-15 contraction generated_history -290000 3600000 3310000
474 arr_move_0473 acct_0113 parent_0113 contract_0304 2025-06-15 reactivation generated_history 300000 3700000 4000000
475 arr_move_0474 acct_0114 parent_0114 contract_0305 2025-06-15 new generated_history 310000 3800000 4110000
476 arr_move_0475 acct_0115 parent_0115 contract_0306 2025-06-15 expansion generated_history 320000 2000000 2320000
477 arr_move_0476 acct_0116 parent_0116 contract_0307 2025-06-15 contraction generated_history -330000 2100000 1770000
478 arr_move_0477 acct_0117 parent_0117 contract_0308 2025-06-15 reactivation generated_history 340000 2200000 2540000
479 arr_move_0478 acct_0118 parent_0118 contract_0309 2025-06-15 new generated_history 350000 2300000 2650000
480 arr_move_0479 acct_0119 parent_0119 contract_0310 2025-06-15 expansion generated_history 360000 2400000 2760000
481 arr_move_0480 acct_0120 parent_0120 contract_0311 2025-06-15 contraction generated_history -370000 2500000 2130000
482 arr_move_0481 acct_0121 parent_0121 contract_0312 2025-06-15 reactivation generated_history 250000 2600000 2850000
483 arr_move_0482 acct_0122 parent_0122 contract_0313 2025-06-15 new generated_history 260000 2700000 2960000
484 arr_move_0483 acct_0123 parent_0123 contract_0314 2025-06-15 expansion generated_history 270000 2800000 3070000
485 arr_move_0484 acct_0124 parent_0124 contract_0315 2025-06-15 contraction generated_history -280000 2900000 2620000
486 arr_move_0485 acct_0125 parent_0125 contract_0316 2025-06-15 reactivation generated_history 290000 3000000 3290000
487 arr_move_0486 acct_0126 parent_0126 contract_0317 2025-06-15 new generated_history 300000 3100000 3400000
488 arr_move_0487 acct_0127 parent_0127 contract_0318 2025-06-15 expansion generated_history 310000 3200000 3510000
489 arr_move_0488 acct_0128 parent_0128 contract_0319 2025-06-15 contraction generated_history -320000 3300000 2980000
490 arr_move_0489 acct_0129 parent_0129 contract_0320 2025-06-15 reactivation generated_history 330000 3400000 3730000
491 arr_move_0490 acct_0130 parent_0130 contract_0101 2025-06-15 new generated_history 340000 3500000 3840000
492 arr_move_0491 acct_0131 parent_0131 contract_0102 2025-06-15 expansion generated_history 350000 3600000 3950000
493 arr_move_0492 acct_0132 parent_0132 contract_0103 2025-06-15 contraction generated_history -360000 3700000 3340000
494 arr_move_0493 acct_0133 parent_0001 contract_0104 2025-06-15 reactivation generated_history 370000 3800000 4170000
495 arr_move_0494 acct_0134 parent_0002 contract_0105 2025-06-15 new generated_history 250000 2000000 2250000
496 arr_move_0495 acct_0135 parent_0003 contract_0106 2025-06-15 expansion generated_history 260000 2100000 2360000
497 arr_move_0496 acct_0136 parent_0004 contract_0107 2025-06-15 contraction generated_history -270000 2200000 1930000
498 arr_move_0497 acct_0137 parent_0005 contract_0108 2025-06-15 reactivation generated_history 280000 2300000 2580000
499 arr_move_0498 acct_0138 parent_0006 contract_0109 2025-06-15 new generated_history 290000 2400000 2690000
500 arr_move_0499 acct_0139 parent_0007 contract_0110 2025-06-15 expansion generated_history 300000 2500000 2800000
501 arr_move_0500 acct_0140 parent_0008 contract_0111 2025-06-15 contraction generated_history -310000 2600000 2290000
502 arr_move_0501 acct_0141 parent_0009 contract_0112 2025-06-15 reactivation generated_history 320000 2700000 3020000
503 arr_move_0502 acct_0142 parent_0010 contract_0113 2025-06-15 new generated_history 330000 2800000 3130000
504 arr_move_0503 acct_0143 parent_0011 contract_0114 2025-06-15 expansion generated_history 340000 2900000 3240000
505 arr_move_0504 acct_0144 parent_0012 contract_0115 2025-06-15 contraction generated_history -350000 3000000 2650000
506 arr_move_0505 acct_0145 parent_0013 contract_0116 2025-06-15 reactivation generated_history 360000 3100000 3460000
507 arr_move_0506 acct_0146 parent_0014 contract_0117 2025-06-15 new generated_history 370000 3200000 3570000
508 arr_move_0507 acct_0147 parent_0015 contract_0118 2025-06-15 expansion generated_history 250000 3300000 3550000
509 arr_move_0508 acct_0148 parent_0016 contract_0119 2025-06-15 contraction generated_history -260000 3400000 3140000
510 arr_move_0509 acct_0149 parent_0017 contract_0120 2025-06-15 reactivation generated_history 270000 3500000 3770000
511 arr_move_0510 acct_0150 parent_0018 contract_0121 2025-06-15 new generated_history 280000 3600000 3880000
512 arr_move_0511 acct_0151 parent_0019 contract_0122 2025-06-15 expansion generated_history 290000 3700000 3990000
513 arr_move_0512 acct_0152 parent_0020 contract_0123 2025-06-15 contraction generated_history -300000 3800000 3500000
514 arr_move_0513 acct_0153 parent_0021 contract_0124 2025-06-15 reactivation generated_history 310000 2000000 2310000
515 arr_move_0514 acct_0154 parent_0022 contract_0125 2025-06-15 new generated_history 320000 2100000 2420000
516 arr_move_0515 acct_0155 parent_0023 contract_0126 2025-06-15 expansion generated_history 330000 2200000 2530000
517 arr_move_0516 acct_0156 parent_0024 contract_0127 2025-06-15 contraction generated_history -340000 2300000 1960000
518 arr_move_0517 acct_0157 parent_0025 contract_0128 2025-06-15 reactivation generated_history 350000 2400000 2750000
519 arr_move_0518 acct_0158 parent_0026 contract_0129 2025-06-15 new generated_history 360000 2500000 2860000
520 arr_move_0519 acct_0159 parent_0027 contract_0130 2025-06-15 expansion generated_history 370000 2600000 2970000
521 arr_move_0520 acct_0160 parent_0028 contract_0131 2025-06-15 contraction generated_history -250000 2700000 2450000
522 arr_move_0521 acct_0161 parent_0029 contract_0132 2025-06-15 reactivation generated_history 260000 2800000 3060000
523 arr_move_0522 acct_0162 parent_0030 contract_0133 2025-06-15 new generated_history 270000 2900000 3170000
524 arr_move_0523 acct_0163 parent_0031 contract_0134 2025-06-15 expansion generated_history 280000 3000000 3280000
525 arr_move_0524 acct_0164 parent_0032 contract_0135 2025-06-15 contraction generated_history -290000 3100000 2810000
526 arr_move_0525 acct_0165 parent_0033 contract_0136 2025-06-15 reactivation generated_history 300000 3200000 3500000
527 arr_move_0526 acct_0166 parent_0034 contract_0137 2025-06-15 new generated_history 310000 3300000 3610000
528 arr_move_0527 acct_0167 parent_0035 contract_0138 2025-06-15 expansion generated_history 320000 3400000 3720000
529 arr_move_0528 acct_0168 parent_0036 contract_0139 2025-06-15 contraction generated_history -330000 3500000 3170000
530 arr_move_0529 acct_0169 parent_0037 contract_0140 2025-06-15 reactivation generated_history 340000 3600000 3940000
531 arr_move_0530 acct_0170 parent_0038 contract_0141 2025-06-15 new generated_history 350000 3700000 4050000
532 arr_move_0531 acct_0171 parent_0039 contract_0142 2025-06-15 expansion generated_history 360000 3800000 4160000
533 arr_move_0532 acct_0172 parent_0040 contract_0143 2025-06-15 contraction generated_history -370000 2000000 1630000
534 arr_move_0533 acct_0173 parent_0041 contract_0144 2025-06-15 reactivation generated_history 250000 2100000 2350000
535 arr_move_0534 acct_0174 parent_0042 contract_0145 2025-06-15 new generated_history 260000 2200000 2460000
536 arr_move_0535 acct_0175 parent_0043 contract_0146 2025-06-15 expansion generated_history 270000 2300000 2570000
537 arr_move_0536 acct_0176 parent_0044 contract_0147 2025-06-15 contraction generated_history -280000 2400000 2120000
538 arr_move_0537 acct_0177 parent_0045 contract_0148 2025-06-15 reactivation generated_history 290000 2500000 2790000
539 arr_move_0538 acct_0178 parent_0046 contract_0149 2025-06-15 new generated_history 300000 2600000 2900000
540 arr_move_0539 acct_0179 parent_0047 contract_0150 2025-06-15 expansion generated_history 310000 2700000 3010000
541 arr_move_0540 acct_0180 parent_0048 contract_0151 2025-06-15 contraction generated_history -320000 2800000 2480000
542 arr_move_0541 acct_0181 parent_0049 contract_0152 2025-06-15 reactivation generated_history 330000 2900000 3230000
543 arr_move_0542 acct_0182 parent_0050 contract_0153 2025-06-15 new generated_history 340000 3000000 3340000
544 arr_move_0543 acct_0183 parent_0051 contract_0154 2025-06-15 expansion generated_history 350000 3100000 3450000
545 arr_move_0544 acct_0184 parent_0052 contract_0155 2025-06-15 contraction generated_history -360000 3200000 2840000
546 arr_move_0545 acct_0185 parent_0053 contract_0156 2025-06-15 reactivation generated_history 370000 3300000 3670000
547 arr_move_0546 acct_0186 parent_0054 contract_0157 2025-06-15 new generated_history 250000 3400000 3650000
548 arr_move_0547 acct_0187 parent_0055 contract_0158 2025-06-15 expansion generated_history 260000 3500000 3760000
549 arr_move_0548 acct_0188 parent_0056 contract_0159 2025-06-15 contraction generated_history -270000 3600000 3330000
550 arr_move_0549 acct_0189 parent_0057 contract_0160 2025-06-15 reactivation generated_history 280000 3700000 3980000
551 arr_move_0550 acct_0090 parent_0090 contract_0161 2025-06-15 new generated_history 290000 3800000 4090000
552 arr_move_0551 acct_0091 parent_0091 contract_0162 2025-06-15 expansion generated_history 300000 2000000 2300000
553 arr_move_0552 acct_0092 parent_0092 contract_0163 2025-06-15 contraction generated_history -310000 2100000 1790000
554 arr_move_0553 acct_0093 parent_0093 contract_0164 2025-06-15 reactivation generated_history 320000 2200000 2520000
555 arr_move_0554 acct_0094 parent_0094 contract_0165 2025-06-15 new generated_history 330000 2300000 2630000
556 arr_move_0555 acct_0095 parent_0095 contract_0166 2025-06-15 expansion generated_history 340000 2400000 2740000
557 arr_move_0556 acct_0096 parent_0096 contract_0167 2025-06-15 contraction generated_history -350000 2500000 2150000
558 arr_move_0557 acct_0097 parent_0097 contract_0168 2025-06-15 reactivation generated_history 360000 2600000 2960000
559 arr_move_0558 acct_0098 parent_0098 contract_0169 2025-06-15 new generated_history 370000 2700000 3070000
560 arr_move_0559 acct_0099 parent_0099 contract_0170 2025-06-15 expansion generated_history 250000 2800000 3050000
561 arr_move_0560 acct_0100 parent_0100 contract_0171 2025-06-15 contraction generated_history -260000 2900000 2640000
562 arr_move_0561 acct_0101 parent_0101 contract_0172 2025-06-15 reactivation generated_history 270000 3000000 3270000
563 arr_move_0562 acct_0102 parent_0102 contract_0173 2025-06-15 new generated_history 280000 3100000 3380000
564 arr_move_0563 acct_0103 parent_0103 contract_0174 2025-06-15 expansion generated_history 290000 3200000 3490000
565 arr_move_0564 acct_0104 parent_0104 contract_0175 2025-06-15 contraction generated_history -300000 3300000 3000000
566 arr_move_0565 acct_0105 parent_0105 contract_0176 2025-06-15 reactivation generated_history 310000 3400000 3710000
567 arr_move_0566 acct_0106 parent_0106 contract_0177 2025-06-15 new generated_history 320000 3500000 3820000
568 arr_move_0567 acct_0107 parent_0107 contract_0178 2025-06-15 expansion generated_history 330000 3600000 3930000
569 arr_move_0568 acct_0108 parent_0108 contract_0179 2025-06-15 contraction generated_history -340000 3700000 3360000
570 arr_move_0569 acct_0109 parent_0109 contract_0180 2025-06-15 reactivation generated_history 350000 3800000 4150000
571 arr_move_0570 acct_0110 parent_0110 contract_0181 2025-06-15 new generated_history 360000 2000000 2360000
572 arr_move_0571 acct_0111 parent_0111 contract_0182 2025-06-15 expansion generated_history 370000 2100000 2470000
573 arr_move_0572 acct_0112 parent_0112 contract_0183 2025-06-15 contraction generated_history -250000 2200000 1950000
574 arr_move_0573 acct_0113 parent_0113 contract_0184 2025-06-15 reactivation generated_history 260000 2300000 2560000
575 arr_move_0574 acct_0114 parent_0114 contract_0185 2025-06-15 new generated_history 270000 2400000 2670000
576 arr_move_0575 acct_0115 parent_0115 contract_0186 2025-06-15 expansion generated_history 280000 2500000 2780000
577 arr_move_0576 acct_0116 parent_0116 contract_0187 2025-06-15 contraction generated_history -290000 2600000 2310000
578 arr_move_0577 acct_0117 parent_0117 contract_0188 2025-06-15 reactivation generated_history 300000 2700000 3000000
579 arr_move_0578 acct_0118 parent_0118 contract_0189 2025-06-15 new generated_history 310000 2800000 3110000
580 arr_move_0579 acct_0119 parent_0119 contract_0190 2025-06-15 expansion generated_history 320000 2900000 3220000
581 arr_move_0580 acct_0120 parent_0120 contract_0191 2025-06-15 contraction generated_history -330000 3000000 2670000
582 arr_move_0581 acct_0121 parent_0121 contract_0192 2025-06-15 reactivation generated_history 340000 3100000 3440000
583 arr_move_0582 acct_0122 parent_0122 contract_0193 2025-06-15 new generated_history 350000 3200000 3550000
584 arr_move_0583 acct_0123 parent_0123 contract_0194 2025-06-15 expansion generated_history 360000 3300000 3660000
585 arr_move_0584 acct_0124 parent_0124 contract_0195 2025-06-15 contraction generated_history -370000 3400000 3030000
586 arr_move_0585 acct_0125 parent_0125 contract_0196 2025-06-15 reactivation generated_history 250000 3500000 3750000
587 arr_move_0586 acct_0126 parent_0126 contract_0197 2025-06-15 new generated_history 260000 3600000 3860000
588 arr_move_0587 acct_0127 parent_0127 contract_0198 2025-06-15 expansion generated_history 270000 3700000 3970000
589 arr_move_0588 acct_0128 parent_0128 contract_0199 2025-06-15 contraction generated_history -280000 3800000 3520000
590 arr_move_0589 acct_0129 parent_0129 contract_0200 2025-06-15 reactivation generated_history 290000 2000000 2290000
591 arr_move_0590 acct_0130 parent_0130 contract_0201 2025-06-15 new generated_history 300000 2100000 2400000
592 arr_move_0591 acct_0131 parent_0131 contract_0202 2025-06-15 expansion generated_history 310000 2200000 2510000
593 arr_move_0592 acct_0132 parent_0132 contract_0203 2025-06-15 contraction generated_history -320000 2300000 1980000
594 arr_move_0593 acct_0133 parent_0001 contract_0204 2025-06-15 reactivation generated_history 330000 2400000 2730000
595 arr_move_0594 acct_0134 parent_0002 contract_0205 2025-06-15 new generated_history 340000 2500000 2840000
596 arr_move_0595 acct_0135 parent_0003 contract_0206 2025-06-15 expansion generated_history 350000 2600000 2950000
597 arr_move_0596 acct_0136 parent_0004 contract_0207 2025-06-15 contraction generated_history -360000 2700000 2340000
598 arr_move_0597 acct_0137 parent_0005 contract_0208 2025-06-15 reactivation generated_history 370000 2800000 3170000
599 arr_move_0598 acct_0138 parent_0006 contract_0209 2025-06-15 new generated_history 250000 2900000 3150000
600 arr_move_0599 acct_0139 parent_0007 contract_0210 2025-06-15 expansion generated_history 260000 3000000 3260000
601 arr_move_0600 acct_0140 parent_0008 contract_0211 2025-06-15 contraction generated_history -270000 3100000 2830000
602 arr_move_0601 acct_0141 parent_0009 contract_0212 2025-06-15 reactivation generated_history 280000 3200000 3480000
603 arr_move_0602 acct_0142 parent_0010 contract_0213 2025-06-15 new generated_history 290000 3300000 3590000
604 arr_move_0603 acct_0143 parent_0011 contract_0214 2025-06-15 expansion generated_history 300000 3400000 3700000
605 arr_move_0604 acct_0144 parent_0012 contract_0215 2025-06-15 contraction generated_history -310000 3500000 3190000
606 arr_move_0605 acct_0145 parent_0013 contract_0216 2025-06-15 reactivation generated_history 320000 3600000 3920000
607 arr_move_0606 acct_0146 parent_0014 contract_0217 2025-06-15 new generated_history 330000 3700000 4030000
608 arr_move_0607 acct_0147 parent_0015 contract_0218 2025-06-15 expansion generated_history 340000 3800000 4140000
609 arr_move_0608 acct_0148 parent_0016 contract_0219 2025-06-15 contraction generated_history -350000 2000000 1650000
610 arr_move_0609 acct_0149 parent_0017 contract_0220 2025-06-15 reactivation generated_history 360000 2100000 2460000
611 arr_move_0610 acct_0150 parent_0018 contract_0221 2025-06-15 new generated_history 370000 2200000 2570000
612 arr_move_0611 acct_0151 parent_0019 contract_0222 2025-06-15 expansion generated_history 250000 2300000 2550000
613 arr_move_0612 acct_0152 parent_0020 contract_0223 2025-06-15 contraction generated_history -260000 2400000 2140000
614 arr_move_0613 acct_0153 parent_0021 contract_0224 2025-06-15 reactivation generated_history 270000 2500000 2770000
615 arr_move_0614 acct_0154 parent_0022 contract_0225 2025-06-15 new generated_history 280000 2600000 2880000
616 arr_move_0615 acct_0155 parent_0023 contract_0226 2025-06-15 expansion generated_history 290000 2700000 2990000
617 arr_move_0616 acct_0156 parent_0024 contract_0227 2025-06-15 contraction generated_history -300000 2800000 2500000
618 arr_move_0617 acct_0157 parent_0025 contract_0228 2025-06-15 reactivation generated_history 310000 2900000 3210000
619 arr_move_0618 acct_0158 parent_0026 contract_0229 2025-06-15 new generated_history 320000 3000000 3320000
620 arr_move_0619 acct_0159 parent_0027 contract_0230 2025-06-15 expansion generated_history 330000 3100000 3430000
621 arr_move_0620 acct_0160 parent_0028 contract_0231 2025-06-15 contraction generated_history -340000 3200000 2860000
622 arr_move_0621 acct_0161 parent_0029 contract_0232 2025-06-15 reactivation generated_history 350000 3300000 3650000
623 arr_move_0622 acct_0162 parent_0030 contract_0233 2025-06-15 new generated_history 360000 3400000 3760000
624 arr_move_0623 acct_0163 parent_0031 contract_0234 2025-06-15 expansion generated_history 370000 3500000 3870000
625 arr_move_0624 acct_0164 parent_0032 contract_0235 2025-06-15 contraction generated_history -250000 3600000 3350000
626 arr_move_0625 acct_0165 parent_0033 contract_0236 2025-06-15 reactivation generated_history 260000 3700000 3960000
627 arr_move_0626 acct_0166 parent_0034 contract_0237 2025-06-15 new generated_history 270000 3800000 4070000
628 arr_move_0627 acct_0167 parent_0035 contract_0238 2025-06-15 expansion generated_history 280000 2000000 2280000
629 arr_move_0628 acct_0168 parent_0036 contract_0239 2025-06-15 contraction generated_history -290000 2100000 1810000
630 arr_move_0629 acct_0169 parent_0037 contract_0240 2025-06-15 reactivation generated_history 300000 2200000 2500000
631 arr_move_0630 acct_0170 parent_0038 contract_0241 2025-06-15 new generated_history 310000 2300000 2610000
632 arr_move_0631 acct_0171 parent_0039 contract_0242 2025-06-15 expansion generated_history 320000 2400000 2720000
633 arr_move_0632 acct_0172 parent_0040 contract_0243 2025-06-15 contraction generated_history -330000 2500000 2170000
634 arr_move_0633 acct_0173 parent_0041 contract_0244 2025-06-15 reactivation generated_history 340000 2600000 2940000
635 arr_move_0634 acct_0174 parent_0042 contract_0245 2025-06-15 new generated_history 350000 2700000 3050000
636 arr_move_0635 acct_0175 parent_0043 contract_0246 2025-06-15 expansion generated_history 360000 2800000 3160000
637 arr_move_0636 acct_0176 parent_0044 contract_0247 2025-06-15 contraction generated_history -370000 2900000 2530000
638 arr_move_0637 acct_0177 parent_0045 contract_0248 2025-06-15 reactivation generated_history 250000 3000000 3250000
639 arr_move_0638 acct_0178 parent_0046 contract_0249 2025-06-15 new generated_history 260000 3100000 3360000
640 arr_move_0639 acct_0179 parent_0047 contract_0250 2025-06-15 expansion generated_history 270000 3200000 3470000
641 arr_move_0640 acct_0180 parent_0048 contract_0251 2025-06-15 contraction generated_history -280000 3300000 3020000
642 arr_move_0641 acct_0181 parent_0049 contract_0252 2025-06-15 reactivation generated_history 290000 3400000 3690000
643 arr_move_0642 acct_0182 parent_0050 contract_0253 2025-06-15 new generated_history 300000 3500000 3800000
644 arr_move_0643 acct_0183 parent_0051 contract_0254 2025-06-15 expansion generated_history 310000 3600000 3910000
645 arr_move_0644 acct_0184 parent_0052 contract_0255 2025-06-15 contraction generated_history -320000 3700000 3380000
646 arr_move_0645 acct_0185 parent_0053 contract_0256 2025-06-15 reactivation generated_history 330000 3800000 4130000
647 arr_move_0646 acct_0186 parent_0054 contract_0257 2025-06-15 new generated_history 340000 2000000 2340000
648 arr_move_0647 acct_0187 parent_0055 contract_0258 2025-06-15 expansion generated_history 350000 2100000 2450000
649 arr_move_0648 acct_0188 parent_0056 contract_0259 2025-06-15 contraction generated_history -360000 2200000 1840000
650 arr_move_0649 acct_0189 parent_0057 contract_0260 2025-06-15 reactivation generated_history 370000 2300000 2670000
651 arr_move_0650 acct_0090 parent_0090 contract_0261 2025-06-15 new generated_history 250000 2400000 2650000
652 arr_move_0651 acct_0091 parent_0091 contract_0262 2025-06-15 expansion generated_history 260000 2500000 2760000
653 arr_move_0652 acct_0092 parent_0092 contract_0263 2025-06-15 contraction generated_history -270000 2600000 2330000
654 arr_move_0653 acct_0093 parent_0093 contract_0264 2025-06-15 reactivation generated_history 280000 2700000 2980000
655 arr_move_0654 acct_0094 parent_0094 contract_0265 2025-06-15 new generated_history 290000 2800000 3090000
656 arr_move_0655 acct_0095 parent_0095 contract_0266 2025-06-15 expansion generated_history 300000 2900000 3200000
657 arr_move_0656 acct_0096 parent_0096 contract_0267 2025-06-15 contraction generated_history -310000 3000000 2690000
658 arr_move_0657 acct_0097 parent_0097 contract_0268 2025-06-15 reactivation generated_history 320000 3100000 3420000
659 arr_move_0658 acct_0098 parent_0098 contract_0269 2025-06-15 new generated_history 330000 3200000 3530000
660 arr_move_0659 acct_0099 parent_0099 contract_0270 2025-06-15 expansion generated_history 340000 3300000 3640000
661 arr_move_0660 acct_0100 parent_0100 contract_0271 2025-06-15 contraction generated_history -350000 3400000 3050000
662 arr_move_0661 acct_0101 parent_0101 contract_0272 2025-06-15 reactivation generated_history 360000 3500000 3860000
663 arr_move_0662 acct_0102 parent_0102 contract_0273 2025-06-15 new generated_history 370000 3600000 3970000
664 arr_move_0663 acct_0103 parent_0103 contract_0274 2025-06-15 expansion generated_history 250000 3700000 3950000
665 arr_move_0664 acct_0104 parent_0104 contract_0275 2025-06-15 contraction generated_history -260000 3800000 3540000
666 arr_move_0665 acct_0105 parent_0105 contract_0276 2025-06-15 reactivation generated_history 270000 2000000 2270000
667 arr_move_0666 acct_0106 parent_0106 contract_0277 2025-06-15 new generated_history 280000 2100000 2380000
668 arr_move_0667 acct_0107 parent_0107 contract_0278 2025-06-15 expansion generated_history 290000 2200000 2490000
669 arr_move_0668 acct_0108 parent_0108 contract_0279 2025-06-15 contraction generated_history -300000 2300000 2000000
670 arr_move_0669 acct_0109 parent_0109 contract_0280 2025-06-15 reactivation generated_history 310000 2400000 2710000
671 arr_move_0670 acct_0110 parent_0110 contract_0281 2025-06-15 new generated_history 320000 2500000 2820000
672 arr_move_0671 acct_0111 parent_0111 contract_0282 2025-06-15 expansion generated_history 330000 2600000 2930000
673 arr_move_0672 acct_0112 parent_0112 contract_0283 2025-06-15 contraction generated_history -340000 2700000 2360000
674 arr_move_0673 acct_0113 parent_0113 contract_0284 2025-06-15 reactivation generated_history 350000 2800000 3150000
675 arr_move_0674 acct_0114 parent_0114 contract_0285 2025-06-15 new generated_history 360000 2900000 3260000
676 arr_move_0675 acct_0115 parent_0115 contract_0286 2025-06-15 expansion generated_history 370000 3000000 3370000
677 arr_move_0676 acct_0116 parent_0116 contract_0287 2025-06-15 contraction generated_history -250000 3100000 2850000
678 arr_move_0677 acct_0117 parent_0117 contract_0288 2025-06-15 reactivation generated_history 260000 3200000 3460000
679 arr_move_0678 acct_0118 parent_0118 contract_0289 2025-06-15 new generated_history 270000 3300000 3570000
680 arr_move_0679 acct_0119 parent_0119 contract_0290 2025-06-15 expansion generated_history 280000 3400000 3680000
681 arr_move_0680 acct_0120 parent_0120 contract_0291 2025-06-15 contraction generated_history -290000 3500000 3210000
682 arr_move_0681 acct_0121 parent_0121 contract_0292 2025-06-15 reactivation generated_history 300000 3600000 3900000
683 arr_move_0682 acct_0122 parent_0122 contract_0293 2025-06-15 new generated_history 310000 3700000 4010000
684 arr_move_0683 acct_0123 parent_0123 contract_0294 2025-06-15 expansion generated_history 320000 3800000 4120000
685 arr_move_0684 acct_0124 parent_0124 contract_0295 2025-06-15 contraction generated_history -330000 2000000 1670000
686 arr_move_0685 acct_0125 parent_0125 contract_0296 2025-06-15 reactivation generated_history 340000 2100000 2440000
687 arr_move_0686 acct_0126 parent_0126 contract_0297 2025-06-15 new generated_history 350000 2200000 2550000
688 arr_move_0687 acct_0127 parent_0127 contract_0298 2025-06-15 expansion generated_history 360000 2300000 2660000
689 arr_move_0688 acct_0128 parent_0128 contract_0299 2025-06-15 contraction generated_history -370000 2400000 2030000
690 arr_move_0689 acct_0129 parent_0129 contract_0300 2025-06-15 reactivation generated_history 250000 2500000 2750000
691 arr_move_0690 acct_0130 parent_0130 contract_0301 2025-06-15 new generated_history 260000 2600000 2860000
692 arr_move_0691 acct_0131 parent_0131 contract_0302 2025-06-15 expansion generated_history 270000 2700000 2970000
693 arr_move_0692 acct_0132 parent_0132 contract_0303 2025-06-15 contraction generated_history -280000 2800000 2520000
694 arr_move_0693 acct_0133 parent_0001 contract_0304 2025-06-15 reactivation generated_history 290000 2900000 3190000
695 arr_move_0694 acct_0134 parent_0002 contract_0305 2025-06-15 new generated_history 300000 3000000 3300000
696 arr_move_0695 acct_0135 parent_0003 contract_0306 2025-06-15 expansion generated_history 310000 3100000 3410000
697 arr_move_0696 acct_0136 parent_0004 contract_0307 2025-06-15 contraction generated_history -320000 3200000 2880000
698 arr_move_0697 acct_0137 parent_0005 contract_0308 2025-06-15 reactivation generated_history 330000 3300000 3630000
699 arr_move_0698 acct_0138 parent_0006 contract_0309 2025-06-15 new generated_history 340000 3400000 3740000
700 arr_move_0699 acct_0139 parent_0007 contract_0310 2025-06-15 expansion generated_history 350000 3500000 3850000
701 arr_move_0700 acct_0140 parent_0008 contract_0311 2025-06-15 contraction generated_history -360000 3600000 3240000
702 arr_move_0701 acct_0141 parent_0009 contract_0312 2025-06-15 reactivation generated_history 370000 3700000 4070000
703 arr_move_0702 acct_0142 parent_0010 contract_0313 2025-06-15 new generated_history 250000 3800000 4050000
704 arr_move_0703 acct_0143 parent_0011 contract_0314 2025-06-15 expansion generated_history 260000 2000000 2260000
705 arr_move_0704 acct_0144 parent_0012 contract_0315 2025-06-15 contraction generated_history -270000 2100000 1830000
706 arr_move_0705 acct_0145 parent_0013 contract_0316 2025-06-15 reactivation generated_history 280000 2200000 2480000
707 arr_move_0706 acct_0146 parent_0014 contract_0317 2025-06-15 new generated_history 290000 2300000 2590000
708 arr_move_0707 acct_0147 parent_0015 contract_0318 2025-06-15 expansion generated_history 300000 2400000 2700000
709 arr_move_0708 acct_0148 parent_0016 contract_0319 2025-06-15 contraction generated_history -310000 2500000 2190000
710 arr_move_0709 acct_0149 parent_0017 contract_0320 2025-06-15 reactivation generated_history 320000 2600000 2920000
711 arr_move_0710 acct_0150 parent_0018 contract_0101 2025-06-15 new generated_history 330000 2700000 3030000
712 arr_move_0711 acct_0151 parent_0019 contract_0102 2025-06-15 expansion generated_history 340000 2800000 3140000
713 arr_move_0712 acct_0152 parent_0020 contract_0103 2025-06-15 contraction generated_history -350000 2900000 2550000
714 arr_move_0713 acct_0153 parent_0021 contract_0104 2025-06-15 reactivation generated_history 360000 3000000 3360000
715 arr_move_0714 acct_0154 parent_0022 contract_0105 2025-06-15 new generated_history 370000 3100000 3470000
716 arr_move_0715 acct_0155 parent_0023 contract_0106 2025-06-15 expansion generated_history 250000 3200000 3450000
717 arr_move_0716 acct_0156 parent_0024 contract_0107 2025-06-15 contraction generated_history -260000 3300000 3040000
718 arr_move_0717 acct_0157 parent_0025 contract_0108 2025-06-15 reactivation generated_history 270000 3400000 3670000
719 arr_move_0718 acct_0158 parent_0026 contract_0109 2025-06-15 new generated_history 280000 3500000 3780000
720 arr_move_0719 acct_0159 parent_0027 contract_0110 2025-06-15 expansion generated_history 290000 3600000 3890000
721 arr_move_0720 acct_0160 parent_0028 contract_0111 2025-06-15 contraction generated_history -300000 3700000 3400000

View file

@ -0,0 +1,321 @@
contract_id,account_id,parent_account_id,plan_id,contract_arr_cents,booked_arr_cents,start_date,end_date,status,renewal_type
contract_0001,acct_0001,parent_0001,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0002,acct_0002,parent_0002,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0003,acct_0003,parent_0003,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,expansion
contract_0004,acct_0004,parent_0004,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,downgrade
contract_0005,acct_0005,parent_0005,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0006,acct_0006,parent_0006,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0007,acct_0007,parent_0007,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,expansion
contract_0008,acct_0008,parent_0008,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,downgrade
contract_0009,acct_0009,parent_0009,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0010,acct_0010,parent_0010,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0011,acct_0011,parent_0011,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,expansion
contract_0012,acct_0012,parent_0012,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,downgrade
contract_0013,acct_0013,parent_0013,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0014,acct_0014,parent_0014,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0015,acct_0015,parent_0015,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,expansion
contract_0016,acct_0016,parent_0016,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,downgrade
contract_0017,acct_0017,parent_0017,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0018,acct_0018,parent_0018,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0019,acct_0019,parent_0019,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,expansion
contract_0020,acct_0020,parent_0020,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,downgrade
contract_0021,acct_0021,parent_0021,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0022,acct_0022,parent_0022,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0023,acct_0023,parent_0023,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,expansion
contract_0024,acct_0024,parent_0024,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,downgrade
contract_0025,acct_0025,parent_0025,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0026,acct_0026,parent_0026,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0027,acct_0027,parent_0027,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,expansion
contract_0028,acct_0028,parent_0028,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,downgrade
contract_0029,acct_0029,parent_0029,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0030,acct_0030,parent_0030,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0031,acct_0031,parent_0031,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,expansion
contract_0032,acct_0032,parent_0032,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,downgrade
contract_0033,acct_0033,parent_0033,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0034,acct_0034,parent_0034,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0035,acct_0035,parent_0035,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,expansion
contract_0036,acct_0036,parent_0036,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,downgrade
contract_0037,acct_0037,parent_0037,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0038,acct_0038,parent_0038,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0039,acct_0039,parent_0039,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,expansion
contract_0040,acct_0040,parent_0040,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,downgrade
contract_0041,acct_0041,parent_0041,plan_004,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0042,acct_0042,parent_0042,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0043,acct_0043,parent_0043,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,expansion
contract_0044,acct_0044,parent_0044,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,downgrade
contract_0045,acct_0045,parent_0045,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0046,acct_0046,parent_0046,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0047,acct_0047,parent_0047,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,expansion
contract_0048,acct_0048,parent_0048,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,downgrade
contract_0049,acct_0049,parent_0049,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,new
contract_0050,acct_0050,parent_0050,plan_003,25000000,25000000,2025-01-01,2026-12-31,active,renewal
contract_0051,acct_0051,parent_0051,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0052,acct_0052,parent_0052,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,downgrade
contract_0053,acct_0053,parent_0053,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,new
contract_0054,acct_0054,parent_0054,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,renewal
contract_0055,acct_0055,parent_0055,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0056,acct_0056,parent_0056,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,downgrade
contract_0057,acct_0057,parent_0057,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,new
contract_0058,acct_0058,parent_0058,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,renewal
contract_0059,acct_0059,parent_0059,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0060,acct_0060,parent_0060,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,downgrade
contract_0061,acct_0061,parent_0061,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,new
contract_0062,acct_0062,parent_0062,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,renewal
contract_0063,acct_0063,parent_0063,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0064,acct_0064,parent_0064,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,downgrade
contract_0065,acct_0065,parent_0065,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,new
contract_0066,acct_0066,parent_0066,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,renewal
contract_0067,acct_0067,parent_0067,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0068,acct_0068,parent_0068,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,downgrade
contract_0069,acct_0069,parent_0069,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,new
contract_0070,acct_0070,parent_0070,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,renewal
contract_0071,acct_0071,parent_0071,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0072,acct_0072,parent_0072,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,downgrade
contract_0073,acct_0073,parent_0073,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,new
contract_0074,acct_0074,parent_0074,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,renewal
contract_0075,acct_0075,parent_0075,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0076,acct_0076,parent_0076,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,downgrade
contract_0077,acct_0077,parent_0077,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,new
contract_0078,acct_0078,parent_0078,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,renewal
contract_0079,acct_0079,parent_0079,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0080,acct_0080,parent_0080,plan_003,12000000,12000000,2025-01-01,2026-12-31,active,downgrade
contract_0081,acct_0081,parent_0081,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,new
contract_0082,acct_0082,parent_0082,plan_004,12000000,12000000,2025-01-01,2026-12-31,active,renewal
contract_0083,acct_0083,parent_0083,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0084,acct_0084,parent_0084,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,downgrade
contract_0085,acct_0085,parent_0085,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,new
contract_0086,acct_0086,parent_0086,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,renewal
contract_0087,acct_0087,parent_0087,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0088,acct_0088,parent_0088,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,downgrade
contract_0089,acct_0089,parent_0089,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,new
contract_0090,acct_0090,parent_0090,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,renewal
contract_0091,acct_0091,parent_0091,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0092,acct_0092,parent_0092,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,downgrade
contract_0093,acct_0093,parent_0093,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,new
contract_0094,acct_0094,parent_0094,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,renewal
contract_0095,acct_0095,parent_0095,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0096,acct_0096,parent_0096,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,downgrade
contract_0097,acct_0097,parent_0097,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,new
contract_0098,acct_0098,parent_0098,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,renewal
contract_0099,acct_0099,parent_0099,plan_002,12000000,12000000,2025-01-01,2026-12-31,active,expansion
contract_0100,acct_0100,parent_0100,plan_002,36200000,36200000,2025-01-01,2026-12-31,active,downgrade
contract_0101,acct_0101,parent_0101,plan_002,4010000,4010000,2025-03-01,2025-12-31,expired,new
contract_0102,acct_0102,parent_0102,plan_002,4020000,4020000,2025-03-01,2025-12-31,cancelled,renewal
contract_0103,acct_0103,parent_0103,plan_002,4030000,4030000,2025-03-01,2025-12-31,expired,expansion
contract_0104,acct_0104,parent_0104,plan_002,4040000,4040000,2025-03-01,2025-12-31,expired,downgrade
contract_0105,acct_0105,parent_0105,plan_002,4050000,4050000,2025-03-01,2025-12-31,cancelled,new
contract_0106,acct_0106,parent_0106,plan_002,4060000,4060000,2025-03-01,2025-12-31,expired,renewal
contract_0107,acct_0107,parent_0107,plan_002,4070000,4070000,2025-03-01,2025-12-31,expired,expansion
contract_0108,acct_0108,parent_0108,plan_002,4080000,4080000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0109,acct_0109,parent_0109,plan_002,4090000,4090000,2025-03-01,2025-12-31,expired,new
contract_0110,acct_0110,parent_0110,plan_002,4100000,4100000,2025-03-01,2025-12-31,expired,renewal
contract_0111,acct_0111,parent_0111,plan_002,4110000,4110000,2025-03-01,2025-12-31,cancelled,expansion
contract_0112,acct_0112,parent_0112,plan_002,4120000,4120000,2025-03-01,2025-12-31,expired,downgrade
contract_0113,acct_0113,parent_0113,plan_002,4130000,4130000,2025-03-01,2025-12-31,expired,new
contract_0114,acct_0114,parent_0114,plan_002,4140000,4140000,2025-03-01,2025-12-31,cancelled,renewal
contract_0115,acct_0115,parent_0115,plan_002,4150000,4150000,2025-03-01,2025-12-31,expired,expansion
contract_0116,acct_0116,parent_0116,plan_002,4160000,4160000,2025-03-01,2025-12-31,expired,downgrade
contract_0117,acct_0117,parent_0117,plan_002,4170000,4170000,2025-03-01,2025-12-31,cancelled,new
contract_0118,acct_0118,parent_0118,plan_002,4180000,4180000,2025-03-01,2025-12-31,expired,renewal
contract_0119,acct_0119,parent_0119,plan_002,4190000,4190000,2025-03-01,2025-12-31,expired,expansion
contract_0120,acct_0120,parent_0120,plan_002,4200000,4200000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0121,acct_0121,parent_0121,plan_002,4210000,4210000,2025-03-01,2025-12-31,expired,new
contract_0122,acct_0122,parent_0122,plan_002,4220000,4220000,2025-03-01,2025-12-31,expired,renewal
contract_0123,acct_0123,parent_0123,plan_004,4230000,4230000,2025-03-01,2025-12-31,cancelled,expansion
contract_0124,acct_0124,parent_0124,plan_002,4240000,4240000,2025-03-01,2025-12-31,expired,downgrade
contract_0125,acct_0125,parent_0125,plan_002,4250000,4250000,2025-03-01,2025-12-31,expired,new
contract_0126,acct_0126,parent_0126,plan_002,4260000,4260000,2025-03-01,2025-12-31,cancelled,renewal
contract_0127,acct_0127,parent_0127,plan_002,4270000,4270000,2025-03-01,2025-12-31,expired,expansion
contract_0128,acct_0128,parent_0128,plan_002,4280000,4280000,2025-03-01,2025-12-31,expired,downgrade
contract_0129,acct_0129,parent_0129,plan_002,4290000,4290000,2025-03-01,2025-12-31,cancelled,new
contract_0130,acct_0130,parent_0130,plan_002,4300000,4300000,2025-03-01,2025-12-31,expired,renewal
contract_0131,acct_0131,parent_0131,plan_002,4310000,4310000,2025-03-01,2025-12-31,expired,expansion
contract_0132,acct_0132,parent_0132,plan_002,4320000,4320000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0133,acct_0133,parent_0001,plan_002,4330000,4330000,2025-03-01,2025-12-31,expired,new
contract_0134,acct_0134,parent_0002,plan_002,4340000,4340000,2025-03-01,2025-12-31,expired,renewal
contract_0135,acct_0135,parent_0003,plan_002,4350000,4350000,2025-03-01,2025-12-31,cancelled,expansion
contract_0136,acct_0136,parent_0004,plan_002,4360000,4360000,2025-03-01,2025-12-31,expired,downgrade
contract_0137,acct_0137,parent_0005,plan_002,4370000,4370000,2025-03-01,2025-12-31,expired,new
contract_0138,acct_0138,parent_0006,plan_002,4380000,4380000,2025-03-01,2025-12-31,cancelled,renewal
contract_0139,acct_0139,parent_0007,plan_002,4390000,4390000,2025-03-01,2025-12-31,expired,expansion
contract_0140,acct_0140,parent_0008,plan_002,4400000,4400000,2025-03-01,2025-12-31,expired,downgrade
contract_0141,acct_0141,parent_0009,plan_002,4410000,4410000,2025-03-01,2025-12-31,cancelled,new
contract_0142,acct_0142,parent_0010,plan_002,4420000,4420000,2025-03-01,2025-12-31,expired,renewal
contract_0143,acct_0143,parent_0011,plan_002,4430000,4430000,2025-03-01,2025-12-31,expired,expansion
contract_0144,acct_0144,parent_0012,plan_002,4440000,4440000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0145,acct_0145,parent_0013,plan_002,4450000,4450000,2025-03-01,2025-12-31,expired,new
contract_0146,acct_0146,parent_0014,plan_002,4460000,4460000,2025-03-01,2025-12-31,expired,renewal
contract_0147,acct_0147,parent_0015,plan_002,4470000,4470000,2025-03-01,2025-12-31,cancelled,expansion
contract_0148,acct_0148,parent_0016,plan_002,4480000,4480000,2025-03-01,2025-12-31,expired,downgrade
contract_0149,acct_0149,parent_0017,plan_002,4490000,4490000,2025-03-01,2025-12-31,expired,new
contract_0150,acct_0150,parent_0018,plan_002,4500000,4500000,2025-03-01,2025-12-31,cancelled,renewal
contract_0151,acct_0151,parent_0019,plan_001,4510000,4510000,2025-03-01,2025-12-31,expired,expansion
contract_0152,acct_0152,parent_0020,plan_001,4520000,4520000,2025-03-01,2025-12-31,expired,downgrade
contract_0153,acct_0153,parent_0021,plan_001,4530000,4530000,2025-03-01,2025-12-31,cancelled,new
contract_0154,acct_0154,parent_0022,plan_001,4540000,4540000,2025-03-01,2025-12-31,expired,renewal
contract_0155,acct_0155,parent_0023,plan_001,4550000,4550000,2025-03-01,2025-12-31,expired,expansion
contract_0156,acct_0156,parent_0024,plan_001,4560000,4560000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0157,acct_0157,parent_0025,plan_001,4570000,4570000,2025-03-01,2025-12-31,expired,new
contract_0158,acct_0158,parent_0026,plan_001,4580000,4580000,2025-03-01,2025-12-31,expired,renewal
contract_0159,acct_0159,parent_0027,plan_001,4590000,4590000,2025-03-01,2025-12-31,cancelled,expansion
contract_0160,acct_0160,parent_0028,plan_001,4600000,4600000,2025-03-01,2025-12-31,expired,downgrade
contract_0161,acct_0161,parent_0029,plan_001,4610000,4610000,2025-03-01,2025-12-31,expired,new
contract_0162,acct_0162,parent_0030,plan_001,4620000,4620000,2025-03-01,2025-12-31,cancelled,renewal
contract_0163,acct_0163,parent_0031,plan_001,4630000,4630000,2025-03-01,2025-12-31,expired,expansion
contract_0164,acct_0164,parent_0032,plan_004,4640000,4640000,2025-03-01,2025-12-31,expired,downgrade
contract_0165,acct_0165,parent_0033,plan_001,4650000,4650000,2025-03-01,2025-12-31,cancelled,new
contract_0166,acct_0166,parent_0034,plan_001,4660000,4660000,2025-03-01,2025-12-31,expired,renewal
contract_0167,acct_0167,parent_0035,plan_001,4670000,4670000,2025-03-01,2025-12-31,expired,expansion
contract_0168,acct_0168,parent_0036,plan_001,4680000,4680000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0169,acct_0169,parent_0037,plan_001,4690000,4690000,2025-03-01,2025-12-31,expired,new
contract_0170,acct_0170,parent_0038,plan_001,4700000,4700000,2025-03-01,2025-12-31,expired,renewal
contract_0171,acct_0171,parent_0039,plan_001,4710000,4710000,2025-03-01,2025-12-31,cancelled,expansion
contract_0172,acct_0172,parent_0040,plan_001,4720000,4720000,2025-03-01,2025-12-31,expired,downgrade
contract_0173,acct_0173,parent_0041,plan_001,4730000,4730000,2025-03-01,2025-12-31,expired,new
contract_0174,acct_0174,parent_0042,plan_001,4740000,4740000,2025-03-01,2025-12-31,cancelled,renewal
contract_0175,acct_0175,parent_0043,plan_001,4750000,4750000,2025-03-01,2025-12-31,expired,expansion
contract_0176,acct_0176,parent_0044,plan_001,4760000,4760000,2025-03-01,2025-12-31,expired,downgrade
contract_0177,acct_0177,parent_0045,plan_001,4770000,4770000,2025-03-01,2025-12-31,cancelled,new
contract_0178,acct_0178,parent_0046,plan_001,4780000,4780000,2025-03-01,2025-12-31,expired,renewal
contract_0179,acct_0179,parent_0047,plan_001,4790000,4790000,2025-03-01,2025-12-31,expired,expansion
contract_0180,acct_0180,parent_0048,plan_001,4800000,4800000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0181,acct_0181,parent_0049,plan_001,4810000,4810000,2025-03-01,2025-12-31,expired,new
contract_0182,acct_0182,parent_0050,plan_001,4820000,4820000,2025-03-01,2025-12-31,expired,renewal
contract_0183,acct_0183,parent_0051,plan_001,4830000,4830000,2025-03-01,2025-12-31,cancelled,expansion
contract_0184,acct_0184,parent_0052,plan_001,4840000,4840000,2025-03-01,2025-12-31,expired,downgrade
contract_0185,acct_0185,parent_0053,plan_001,4850000,4850000,2025-03-01,2025-12-31,expired,new
contract_0186,acct_0186,parent_0054,plan_001,4860000,4860000,2025-03-01,2025-12-31,cancelled,renewal
contract_0187,acct_0187,parent_0055,plan_001,4870000,4870000,2025-03-01,2025-12-31,expired,expansion
contract_0188,acct_0188,parent_0056,plan_001,4880000,4880000,2025-03-01,2025-12-31,expired,downgrade
contract_0189,acct_0189,parent_0057,plan_001,4890000,4890000,2025-03-01,2025-12-31,cancelled,new
contract_0190,acct_0190,parent_0058,plan_001,4900000,4900000,2025-03-01,2025-12-31,expired,renewal
contract_0191,acct_0191,parent_0059,plan_001,4910000,4910000,2025-03-01,2025-12-31,expired,expansion
contract_0192,acct_0192,parent_0060,plan_001,4920000,4920000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0193,acct_0193,parent_0061,plan_001,4930000,4930000,2025-03-01,2025-12-31,expired,new
contract_0194,acct_0194,parent_0062,plan_001,4940000,4940000,2025-03-01,2025-12-31,expired,renewal
contract_0195,acct_0195,parent_0063,plan_001,4950000,4950000,2025-03-01,2025-12-31,cancelled,expansion
contract_0196,acct_0196,parent_0064,plan_001,4960000,4960000,2025-03-01,2025-12-31,expired,downgrade
contract_0197,acct_0197,parent_0065,plan_001,4970000,4970000,2025-03-01,2025-12-31,expired,new
contract_0198,acct_0198,parent_0066,plan_001,4980000,4980000,2025-03-01,2025-12-31,cancelled,renewal
contract_0199,acct_0199,parent_0067,plan_001,4990000,4990000,2025-03-01,2025-12-31,expired,expansion
contract_0200,acct_0200,parent_0068,plan_001,5000000,5000000,2025-03-01,2025-12-31,expired,downgrade
contract_0201,acct_0001,parent_0001,plan_003,5010000,5010000,2025-03-01,2025-12-31,cancelled,new
contract_0202,acct_0002,parent_0002,plan_003,5020000,5020000,2025-03-01,2025-12-31,expired,renewal
contract_0203,acct_0003,parent_0003,plan_003,5030000,5030000,2025-03-01,2025-12-31,expired,expansion
contract_0204,acct_0004,parent_0004,plan_003,5040000,5040000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0205,acct_0005,parent_0005,plan_004,5050000,5050000,2025-03-01,2025-12-31,expired,new
contract_0206,acct_0006,parent_0006,plan_003,5060000,5060000,2025-03-01,2025-12-31,expired,renewal
contract_0207,acct_0007,parent_0007,plan_003,5070000,5070000,2025-03-01,2025-12-31,cancelled,expansion
contract_0208,acct_0008,parent_0008,plan_003,5080000,5080000,2025-03-01,2025-12-31,expired,downgrade
contract_0209,acct_0009,parent_0009,plan_003,5090000,5090000,2025-03-01,2025-12-31,expired,new
contract_0210,acct_0010,parent_0010,plan_003,5100000,5100000,2025-03-01,2025-12-31,cancelled,renewal
contract_0211,acct_0011,parent_0011,plan_003,5110000,5110000,2025-03-01,2025-12-31,expired,expansion
contract_0212,acct_0012,parent_0012,plan_003,5120000,5120000,2025-03-01,2025-12-31,expired,downgrade
contract_0213,acct_0013,parent_0013,plan_003,5130000,5130000,2025-03-01,2025-12-31,cancelled,new
contract_0214,acct_0014,parent_0014,plan_003,5140000,5140000,2025-03-01,2025-12-31,expired,renewal
contract_0215,acct_0015,parent_0015,plan_003,5150000,5150000,2025-03-01,2025-12-31,expired,expansion
contract_0216,acct_0016,parent_0016,plan_003,5160000,5160000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0217,acct_0017,parent_0017,plan_003,5170000,5170000,2025-03-01,2025-12-31,expired,new
contract_0218,acct_0018,parent_0018,plan_003,5180000,5180000,2025-03-01,2025-12-31,expired,renewal
contract_0219,acct_0019,parent_0019,plan_003,5190000,5190000,2025-03-01,2025-12-31,cancelled,expansion
contract_0220,acct_0020,parent_0020,plan_003,5200000,5200000,2025-03-01,2025-12-31,expired,downgrade
contract_0221,acct_0021,parent_0021,plan_003,5210000,5210000,2025-03-01,2025-12-31,expired,new
contract_0222,acct_0022,parent_0022,plan_003,5220000,5220000,2025-03-01,2025-12-31,cancelled,renewal
contract_0223,acct_0023,parent_0023,plan_003,5230000,5230000,2025-03-01,2025-12-31,expired,expansion
contract_0224,acct_0024,parent_0024,plan_003,5240000,5240000,2025-03-01,2025-12-31,expired,downgrade
contract_0225,acct_0025,parent_0025,plan_003,5250000,5250000,2025-03-01,2025-12-31,cancelled,new
contract_0226,acct_0026,parent_0026,plan_003,5260000,5260000,2025-03-01,2025-12-31,expired,renewal
contract_0227,acct_0027,parent_0027,plan_003,5270000,5270000,2025-03-01,2025-12-31,expired,expansion
contract_0228,acct_0028,parent_0028,plan_003,5280000,5280000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0229,acct_0029,parent_0029,plan_003,5290000,5290000,2025-03-01,2025-12-31,expired,new
contract_0230,acct_0030,parent_0030,plan_003,5300000,5300000,2025-03-01,2025-12-31,expired,renewal
contract_0231,acct_0031,parent_0031,plan_003,5310000,5310000,2025-03-01,2025-12-31,cancelled,expansion
contract_0232,acct_0032,parent_0032,plan_003,5320000,5320000,2025-03-01,2025-12-31,expired,downgrade
contract_0233,acct_0033,parent_0033,plan_003,5330000,5330000,2025-03-01,2025-12-31,expired,new
contract_0234,acct_0034,parent_0034,plan_003,5340000,5340000,2025-03-01,2025-12-31,cancelled,renewal
contract_0235,acct_0035,parent_0035,plan_003,5350000,5350000,2025-03-01,2025-12-31,expired,expansion
contract_0236,acct_0036,parent_0036,plan_003,5360000,5360000,2025-03-01,2025-12-31,expired,downgrade
contract_0237,acct_0037,parent_0037,plan_003,5370000,5370000,2025-03-01,2025-12-31,cancelled,new
contract_0238,acct_0038,parent_0038,plan_003,5380000,5380000,2025-03-01,2025-12-31,expired,renewal
contract_0239,acct_0039,parent_0039,plan_003,5390000,5390000,2025-03-01,2025-12-31,expired,expansion
contract_0240,acct_0040,parent_0040,plan_003,5400000,5400000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0241,acct_0041,parent_0041,plan_003,5410000,5410000,2025-03-01,2025-12-31,expired,new
contract_0242,acct_0042,parent_0042,plan_003,5420000,5420000,2025-03-01,2025-12-31,expired,renewal
contract_0243,acct_0043,parent_0043,plan_003,5430000,5430000,2025-03-01,2025-12-31,cancelled,expansion
contract_0244,acct_0044,parent_0044,plan_003,5440000,5440000,2025-03-01,2025-12-31,expired,downgrade
contract_0245,acct_0045,parent_0045,plan_003,5450000,5450000,2025-03-01,2025-12-31,expired,new
contract_0246,acct_0046,parent_0046,plan_004,5460000,5460000,2025-03-01,2025-12-31,cancelled,renewal
contract_0247,acct_0047,parent_0047,plan_003,5470000,5470000,2025-03-01,2025-12-31,expired,expansion
contract_0248,acct_0048,parent_0048,plan_003,5480000,5480000,2025-03-01,2025-12-31,expired,downgrade
contract_0249,acct_0049,parent_0049,plan_003,5490000,5490000,2025-03-01,2025-12-31,cancelled,new
contract_0250,acct_0050,parent_0050,plan_003,5500000,5500000,2025-03-01,2025-12-31,expired,renewal
contract_0251,acct_0051,parent_0051,plan_003,5510000,5510000,2025-03-01,2025-12-31,expired,expansion
contract_0252,acct_0052,parent_0052,plan_003,5520000,5520000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0253,acct_0053,parent_0053,plan_003,5530000,5530000,2025-03-01,2025-12-31,expired,new
contract_0254,acct_0054,parent_0054,plan_003,5540000,5540000,2025-03-01,2025-12-31,expired,renewal
contract_0255,acct_0055,parent_0055,plan_003,5550000,5550000,2025-03-01,2025-12-31,cancelled,expansion
contract_0256,acct_0056,parent_0056,plan_003,5560000,5560000,2025-03-01,2025-12-31,expired,downgrade
contract_0257,acct_0057,parent_0057,plan_003,5570000,5570000,2025-03-01,2025-12-31,expired,new
contract_0258,acct_0058,parent_0058,plan_003,5580000,5580000,2025-03-01,2025-12-31,cancelled,renewal
contract_0259,acct_0059,parent_0059,plan_003,5590000,5590000,2025-03-01,2025-12-31,expired,expansion
contract_0260,acct_0060,parent_0060,plan_003,5600000,5600000,2025-03-01,2025-12-31,expired,downgrade
contract_0261,acct_0061,parent_0061,plan_003,5610000,5610000,2025-03-01,2025-12-31,cancelled,new
contract_0262,acct_0062,parent_0062,plan_003,5620000,5620000,2025-03-01,2025-12-31,expired,renewal
contract_0263,acct_0063,parent_0063,plan_003,5630000,5630000,2025-03-01,2025-12-31,expired,expansion
contract_0264,acct_0064,parent_0064,plan_003,5640000,5640000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0265,acct_0065,parent_0065,plan_003,5650000,5650000,2025-03-01,2025-12-31,expired,new
contract_0266,acct_0066,parent_0066,plan_003,5660000,5660000,2025-03-01,2025-12-31,expired,renewal
contract_0267,acct_0067,parent_0067,plan_003,5670000,5670000,2025-03-01,2025-12-31,cancelled,expansion
contract_0268,acct_0068,parent_0068,plan_003,5680000,5680000,2025-03-01,2025-12-31,expired,downgrade
contract_0269,acct_0069,parent_0069,plan_003,5690000,5690000,2025-03-01,2025-12-31,expired,new
contract_0270,acct_0070,parent_0070,plan_003,5700000,5700000,2025-03-01,2025-12-31,cancelled,renewal
contract_0271,acct_0071,parent_0071,plan_003,5710000,5710000,2025-03-01,2025-12-31,expired,expansion
contract_0272,acct_0072,parent_0072,plan_003,5720000,5720000,2025-03-01,2025-12-31,expired,downgrade
contract_0273,acct_0073,parent_0073,plan_003,5730000,5730000,2025-03-01,2025-12-31,cancelled,new
contract_0274,acct_0074,parent_0074,plan_003,5740000,5740000,2025-03-01,2025-12-31,expired,renewal
contract_0275,acct_0075,parent_0075,plan_003,5750000,5750000,2025-03-01,2025-12-31,expired,expansion
contract_0276,acct_0076,parent_0076,plan_003,5760000,5760000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0277,acct_0077,parent_0077,plan_003,5770000,5770000,2025-03-01,2025-12-31,expired,new
contract_0278,acct_0078,parent_0078,plan_003,5780000,5780000,2025-03-01,2025-12-31,expired,renewal
contract_0279,acct_0079,parent_0079,plan_003,5790000,5790000,2025-03-01,2025-12-31,cancelled,expansion
contract_0280,acct_0080,parent_0080,plan_003,5800000,5800000,2025-03-01,2025-12-31,expired,downgrade
contract_0281,acct_0081,parent_0081,plan_002,5810000,5810000,2025-03-01,2025-12-31,expired,new
contract_0282,acct_0082,parent_0082,plan_002,5820000,5820000,2025-03-01,2025-12-31,cancelled,renewal
contract_0283,acct_0083,parent_0083,plan_002,5830000,5830000,2025-03-01,2025-12-31,expired,expansion
contract_0284,acct_0084,parent_0084,plan_002,5840000,5840000,2025-03-01,2025-12-31,expired,downgrade
contract_0285,acct_0085,parent_0085,plan_002,5850000,5850000,2025-03-01,2025-12-31,cancelled,new
contract_0286,acct_0086,parent_0086,plan_002,5860000,5860000,2025-03-01,2025-12-31,expired,renewal
contract_0287,acct_0087,parent_0087,plan_004,5870000,5870000,2025-03-01,2025-12-31,expired,expansion
contract_0288,acct_0088,parent_0088,plan_002,5880000,5880000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0289,acct_0089,parent_0089,plan_002,5890000,5890000,2025-03-01,2025-12-31,expired,new
contract_0290,acct_0090,parent_0090,plan_002,5900000,5900000,2025-03-01,2025-12-31,expired,renewal
contract_0291,acct_0091,parent_0091,plan_002,5910000,5910000,2025-03-01,2025-12-31,cancelled,expansion
contract_0292,acct_0092,parent_0092,plan_002,5920000,5920000,2025-03-01,2025-12-31,expired,downgrade
contract_0293,acct_0093,parent_0093,plan_002,5930000,5930000,2025-03-01,2025-12-31,expired,new
contract_0294,acct_0094,parent_0094,plan_002,5940000,5940000,2025-03-01,2025-12-31,cancelled,renewal
contract_0295,acct_0095,parent_0095,plan_002,5950000,5950000,2025-03-01,2025-12-31,expired,expansion
contract_0296,acct_0096,parent_0096,plan_002,5960000,5960000,2025-03-01,2025-12-31,expired,downgrade
contract_0297,acct_0097,parent_0097,plan_002,5970000,5970000,2025-03-01,2025-12-31,cancelled,new
contract_0298,acct_0098,parent_0098,plan_002,5980000,5980000,2025-03-01,2025-12-31,expired,renewal
contract_0299,acct_0099,parent_0099,plan_002,5990000,5990000,2025-03-01,2025-12-31,expired,expansion
contract_0300,acct_0100,parent_0100,plan_002,6000000,6000000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0301,acct_0101,parent_0101,plan_002,6010000,6010000,2025-03-01,2025-12-31,expired,new
contract_0302,acct_0102,parent_0102,plan_002,6020000,6020000,2025-03-01,2025-12-31,expired,renewal
contract_0303,acct_0103,parent_0103,plan_002,6030000,6030000,2025-03-01,2025-12-31,cancelled,expansion
contract_0304,acct_0104,parent_0104,plan_002,6040000,6040000,2025-03-01,2025-12-31,expired,downgrade
contract_0305,acct_0105,parent_0105,plan_002,6050000,6050000,2025-03-01,2025-12-31,expired,new
contract_0306,acct_0106,parent_0106,plan_002,6060000,6060000,2025-03-01,2025-12-31,cancelled,renewal
contract_0307,acct_0107,parent_0107,plan_002,6070000,6070000,2025-03-01,2025-12-31,expired,expansion
contract_0308,acct_0108,parent_0108,plan_002,6080000,6080000,2025-03-01,2025-12-31,expired,downgrade
contract_0309,acct_0109,parent_0109,plan_002,6090000,6090000,2025-03-01,2025-12-31,cancelled,new
contract_0310,acct_0110,parent_0110,plan_002,6100000,6100000,2025-03-01,2025-12-31,expired,renewal
contract_0311,acct_0111,parent_0111,plan_002,6110000,6110000,2025-03-01,2025-12-31,expired,expansion
contract_0312,acct_0112,parent_0112,plan_002,6120000,6120000,2025-03-01,2025-12-31,cancelled,downgrade
contract_0313,acct_0113,parent_0113,plan_002,6130000,6130000,2025-03-01,2025-12-31,expired,new
contract_0314,acct_0114,parent_0114,plan_002,6140000,6140000,2025-03-01,2025-12-31,expired,renewal
contract_0315,acct_0115,parent_0115,plan_002,6150000,6150000,2025-03-01,2025-12-31,cancelled,expansion
contract_0316,acct_0116,parent_0116,plan_002,6160000,6160000,2025-03-01,2025-12-31,expired,downgrade
contract_0317,acct_0117,parent_0117,plan_002,6170000,6170000,2025-03-01,2025-12-31,expired,new
contract_0318,acct_0118,parent_0118,plan_002,6180000,6180000,2025-03-01,2025-12-31,cancelled,renewal
contract_0319,acct_0119,parent_0119,plan_002,6190000,6190000,2025-03-01,2025-12-31,expired,expansion
contract_0320,acct_0120,parent_0120,plan_002,6200000,6200000,2025-03-01,2025-12-31,expired,downgrade
1 contract_id account_id parent_account_id plan_id contract_arr_cents booked_arr_cents start_date end_date status renewal_type
2 contract_0001 acct_0001 parent_0001 plan_003 25000000 25000000 2025-01-01 2026-12-31 active new
3 contract_0002 acct_0002 parent_0002 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
4 contract_0003 acct_0003 parent_0003 plan_003 25000000 25000000 2025-01-01 2026-12-31 active expansion
5 contract_0004 acct_0004 parent_0004 plan_003 25000000 25000000 2025-01-01 2026-12-31 active downgrade
6 contract_0005 acct_0005 parent_0005 plan_003 25000000 25000000 2025-01-01 2026-12-31 active new
7 contract_0006 acct_0006 parent_0006 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
8 contract_0007 acct_0007 parent_0007 plan_003 25000000 25000000 2025-01-01 2026-12-31 active expansion
9 contract_0008 acct_0008 parent_0008 plan_003 25000000 25000000 2025-01-01 2026-12-31 active downgrade
10 contract_0009 acct_0009 parent_0009 plan_003 25000000 25000000 2025-01-01 2026-12-31 active new
11 contract_0010 acct_0010 parent_0010 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
12 contract_0011 acct_0011 parent_0011 plan_003 25000000 25000000 2025-01-01 2026-12-31 active expansion
13 contract_0012 acct_0012 parent_0012 plan_003 25000000 25000000 2025-01-01 2026-12-31 active downgrade
14 contract_0013 acct_0013 parent_0013 plan_003 25000000 25000000 2025-01-01 2026-12-31 active new
15 contract_0014 acct_0014 parent_0014 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
16 contract_0015 acct_0015 parent_0015 plan_003 25000000 25000000 2025-01-01 2026-12-31 active expansion
17 contract_0016 acct_0016 parent_0016 plan_003 25000000 25000000 2025-01-01 2026-12-31 active downgrade
18 contract_0017 acct_0017 parent_0017 plan_003 25000000 25000000 2025-01-01 2026-12-31 active new
19 contract_0018 acct_0018 parent_0018 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
20 contract_0019 acct_0019 parent_0019 plan_003 25000000 25000000 2025-01-01 2026-12-31 active expansion
21 contract_0020 acct_0020 parent_0020 plan_003 25000000 25000000 2025-01-01 2026-12-31 active downgrade
22 contract_0021 acct_0021 parent_0021 plan_003 25000000 25000000 2025-01-01 2026-12-31 active new
23 contract_0022 acct_0022 parent_0022 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
24 contract_0023 acct_0023 parent_0023 plan_003 25000000 25000000 2025-01-01 2026-12-31 active expansion
25 contract_0024 acct_0024 parent_0024 plan_003 25000000 25000000 2025-01-01 2026-12-31 active downgrade
26 contract_0025 acct_0025 parent_0025 plan_003 25000000 25000000 2025-01-01 2026-12-31 active new
27 contract_0026 acct_0026 parent_0026 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
28 contract_0027 acct_0027 parent_0027 plan_003 25000000 25000000 2025-01-01 2026-12-31 active expansion
29 contract_0028 acct_0028 parent_0028 plan_003 25000000 25000000 2025-01-01 2026-12-31 active downgrade
30 contract_0029 acct_0029 parent_0029 plan_003 25000000 25000000 2025-01-01 2026-12-31 active new
31 contract_0030 acct_0030 parent_0030 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
32 contract_0031 acct_0031 parent_0031 plan_003 25000000 25000000 2025-01-01 2026-12-31 active expansion
33 contract_0032 acct_0032 parent_0032 plan_003 25000000 25000000 2025-01-01 2026-12-31 active downgrade
34 contract_0033 acct_0033 parent_0033 plan_003 25000000 25000000 2025-01-01 2026-12-31 active new
35 contract_0034 acct_0034 parent_0034 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
36 contract_0035 acct_0035 parent_0035 plan_003 25000000 25000000 2025-01-01 2026-12-31 active expansion
37 contract_0036 acct_0036 parent_0036 plan_003 25000000 25000000 2025-01-01 2026-12-31 active downgrade
38 contract_0037 acct_0037 parent_0037 plan_003 25000000 25000000 2025-01-01 2026-12-31 active new
39 contract_0038 acct_0038 parent_0038 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
40 contract_0039 acct_0039 parent_0039 plan_003 25000000 25000000 2025-01-01 2026-12-31 active expansion
41 contract_0040 acct_0040 parent_0040 plan_003 25000000 25000000 2025-01-01 2026-12-31 active downgrade
42 contract_0041 acct_0041 parent_0041 plan_004 25000000 25000000 2025-01-01 2026-12-31 active new
43 contract_0042 acct_0042 parent_0042 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
44 contract_0043 acct_0043 parent_0043 plan_003 25000000 25000000 2025-01-01 2026-12-31 active expansion
45 contract_0044 acct_0044 parent_0044 plan_003 25000000 25000000 2025-01-01 2026-12-31 active downgrade
46 contract_0045 acct_0045 parent_0045 plan_003 25000000 25000000 2025-01-01 2026-12-31 active new
47 contract_0046 acct_0046 parent_0046 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
48 contract_0047 acct_0047 parent_0047 plan_003 25000000 25000000 2025-01-01 2026-12-31 active expansion
49 contract_0048 acct_0048 parent_0048 plan_003 25000000 25000000 2025-01-01 2026-12-31 active downgrade
50 contract_0049 acct_0049 parent_0049 plan_003 25000000 25000000 2025-01-01 2026-12-31 active new
51 contract_0050 acct_0050 parent_0050 plan_003 25000000 25000000 2025-01-01 2026-12-31 active renewal
52 contract_0051 acct_0051 parent_0051 plan_003 12000000 12000000 2025-01-01 2026-12-31 active expansion
53 contract_0052 acct_0052 parent_0052 plan_003 12000000 12000000 2025-01-01 2026-12-31 active downgrade
54 contract_0053 acct_0053 parent_0053 plan_003 12000000 12000000 2025-01-01 2026-12-31 active new
55 contract_0054 acct_0054 parent_0054 plan_003 12000000 12000000 2025-01-01 2026-12-31 active renewal
56 contract_0055 acct_0055 parent_0055 plan_003 12000000 12000000 2025-01-01 2026-12-31 active expansion
57 contract_0056 acct_0056 parent_0056 plan_003 12000000 12000000 2025-01-01 2026-12-31 active downgrade
58 contract_0057 acct_0057 parent_0057 plan_003 12000000 12000000 2025-01-01 2026-12-31 active new
59 contract_0058 acct_0058 parent_0058 plan_003 12000000 12000000 2025-01-01 2026-12-31 active renewal
60 contract_0059 acct_0059 parent_0059 plan_003 12000000 12000000 2025-01-01 2026-12-31 active expansion
61 contract_0060 acct_0060 parent_0060 plan_003 12000000 12000000 2025-01-01 2026-12-31 active downgrade
62 contract_0061 acct_0061 parent_0061 plan_003 12000000 12000000 2025-01-01 2026-12-31 active new
63 contract_0062 acct_0062 parent_0062 plan_003 12000000 12000000 2025-01-01 2026-12-31 active renewal
64 contract_0063 acct_0063 parent_0063 plan_003 12000000 12000000 2025-01-01 2026-12-31 active expansion
65 contract_0064 acct_0064 parent_0064 plan_003 12000000 12000000 2025-01-01 2026-12-31 active downgrade
66 contract_0065 acct_0065 parent_0065 plan_003 12000000 12000000 2025-01-01 2026-12-31 active new
67 contract_0066 acct_0066 parent_0066 plan_003 12000000 12000000 2025-01-01 2026-12-31 active renewal
68 contract_0067 acct_0067 parent_0067 plan_003 12000000 12000000 2025-01-01 2026-12-31 active expansion
69 contract_0068 acct_0068 parent_0068 plan_003 12000000 12000000 2025-01-01 2026-12-31 active downgrade
70 contract_0069 acct_0069 parent_0069 plan_003 12000000 12000000 2025-01-01 2026-12-31 active new
71 contract_0070 acct_0070 parent_0070 plan_003 12000000 12000000 2025-01-01 2026-12-31 active renewal
72 contract_0071 acct_0071 parent_0071 plan_003 12000000 12000000 2025-01-01 2026-12-31 active expansion
73 contract_0072 acct_0072 parent_0072 plan_003 12000000 12000000 2025-01-01 2026-12-31 active downgrade
74 contract_0073 acct_0073 parent_0073 plan_003 12000000 12000000 2025-01-01 2026-12-31 active new
75 contract_0074 acct_0074 parent_0074 plan_003 12000000 12000000 2025-01-01 2026-12-31 active renewal
76 contract_0075 acct_0075 parent_0075 plan_003 12000000 12000000 2025-01-01 2026-12-31 active expansion
77 contract_0076 acct_0076 parent_0076 plan_003 12000000 12000000 2025-01-01 2026-12-31 active downgrade
78 contract_0077 acct_0077 parent_0077 plan_003 12000000 12000000 2025-01-01 2026-12-31 active new
79 contract_0078 acct_0078 parent_0078 plan_003 12000000 12000000 2025-01-01 2026-12-31 active renewal
80 contract_0079 acct_0079 parent_0079 plan_003 12000000 12000000 2025-01-01 2026-12-31 active expansion
81 contract_0080 acct_0080 parent_0080 plan_003 12000000 12000000 2025-01-01 2026-12-31 active downgrade
82 contract_0081 acct_0081 parent_0081 plan_002 12000000 12000000 2025-01-01 2026-12-31 active new
83 contract_0082 acct_0082 parent_0082 plan_004 12000000 12000000 2025-01-01 2026-12-31 active renewal
84 contract_0083 acct_0083 parent_0083 plan_002 12000000 12000000 2025-01-01 2026-12-31 active expansion
85 contract_0084 acct_0084 parent_0084 plan_002 12000000 12000000 2025-01-01 2026-12-31 active downgrade
86 contract_0085 acct_0085 parent_0085 plan_002 12000000 12000000 2025-01-01 2026-12-31 active new
87 contract_0086 acct_0086 parent_0086 plan_002 12000000 12000000 2025-01-01 2026-12-31 active renewal
88 contract_0087 acct_0087 parent_0087 plan_002 12000000 12000000 2025-01-01 2026-12-31 active expansion
89 contract_0088 acct_0088 parent_0088 plan_002 12000000 12000000 2025-01-01 2026-12-31 active downgrade
90 contract_0089 acct_0089 parent_0089 plan_002 12000000 12000000 2025-01-01 2026-12-31 active new
91 contract_0090 acct_0090 parent_0090 plan_002 12000000 12000000 2025-01-01 2026-12-31 active renewal
92 contract_0091 acct_0091 parent_0091 plan_002 12000000 12000000 2025-01-01 2026-12-31 active expansion
93 contract_0092 acct_0092 parent_0092 plan_002 12000000 12000000 2025-01-01 2026-12-31 active downgrade
94 contract_0093 acct_0093 parent_0093 plan_002 12000000 12000000 2025-01-01 2026-12-31 active new
95 contract_0094 acct_0094 parent_0094 plan_002 12000000 12000000 2025-01-01 2026-12-31 active renewal
96 contract_0095 acct_0095 parent_0095 plan_002 12000000 12000000 2025-01-01 2026-12-31 active expansion
97 contract_0096 acct_0096 parent_0096 plan_002 12000000 12000000 2025-01-01 2026-12-31 active downgrade
98 contract_0097 acct_0097 parent_0097 plan_002 12000000 12000000 2025-01-01 2026-12-31 active new
99 contract_0098 acct_0098 parent_0098 plan_002 12000000 12000000 2025-01-01 2026-12-31 active renewal
100 contract_0099 acct_0099 parent_0099 plan_002 12000000 12000000 2025-01-01 2026-12-31 active expansion
101 contract_0100 acct_0100 parent_0100 plan_002 36200000 36200000 2025-01-01 2026-12-31 active downgrade
102 contract_0101 acct_0101 parent_0101 plan_002 4010000 4010000 2025-03-01 2025-12-31 expired new
103 contract_0102 acct_0102 parent_0102 plan_002 4020000 4020000 2025-03-01 2025-12-31 cancelled renewal
104 contract_0103 acct_0103 parent_0103 plan_002 4030000 4030000 2025-03-01 2025-12-31 expired expansion
105 contract_0104 acct_0104 parent_0104 plan_002 4040000 4040000 2025-03-01 2025-12-31 expired downgrade
106 contract_0105 acct_0105 parent_0105 plan_002 4050000 4050000 2025-03-01 2025-12-31 cancelled new
107 contract_0106 acct_0106 parent_0106 plan_002 4060000 4060000 2025-03-01 2025-12-31 expired renewal
108 contract_0107 acct_0107 parent_0107 plan_002 4070000 4070000 2025-03-01 2025-12-31 expired expansion
109 contract_0108 acct_0108 parent_0108 plan_002 4080000 4080000 2025-03-01 2025-12-31 cancelled downgrade
110 contract_0109 acct_0109 parent_0109 plan_002 4090000 4090000 2025-03-01 2025-12-31 expired new
111 contract_0110 acct_0110 parent_0110 plan_002 4100000 4100000 2025-03-01 2025-12-31 expired renewal
112 contract_0111 acct_0111 parent_0111 plan_002 4110000 4110000 2025-03-01 2025-12-31 cancelled expansion
113 contract_0112 acct_0112 parent_0112 plan_002 4120000 4120000 2025-03-01 2025-12-31 expired downgrade
114 contract_0113 acct_0113 parent_0113 plan_002 4130000 4130000 2025-03-01 2025-12-31 expired new
115 contract_0114 acct_0114 parent_0114 plan_002 4140000 4140000 2025-03-01 2025-12-31 cancelled renewal
116 contract_0115 acct_0115 parent_0115 plan_002 4150000 4150000 2025-03-01 2025-12-31 expired expansion
117 contract_0116 acct_0116 parent_0116 plan_002 4160000 4160000 2025-03-01 2025-12-31 expired downgrade
118 contract_0117 acct_0117 parent_0117 plan_002 4170000 4170000 2025-03-01 2025-12-31 cancelled new
119 contract_0118 acct_0118 parent_0118 plan_002 4180000 4180000 2025-03-01 2025-12-31 expired renewal
120 contract_0119 acct_0119 parent_0119 plan_002 4190000 4190000 2025-03-01 2025-12-31 expired expansion
121 contract_0120 acct_0120 parent_0120 plan_002 4200000 4200000 2025-03-01 2025-12-31 cancelled downgrade
122 contract_0121 acct_0121 parent_0121 plan_002 4210000 4210000 2025-03-01 2025-12-31 expired new
123 contract_0122 acct_0122 parent_0122 plan_002 4220000 4220000 2025-03-01 2025-12-31 expired renewal
124 contract_0123 acct_0123 parent_0123 plan_004 4230000 4230000 2025-03-01 2025-12-31 cancelled expansion
125 contract_0124 acct_0124 parent_0124 plan_002 4240000 4240000 2025-03-01 2025-12-31 expired downgrade
126 contract_0125 acct_0125 parent_0125 plan_002 4250000 4250000 2025-03-01 2025-12-31 expired new
127 contract_0126 acct_0126 parent_0126 plan_002 4260000 4260000 2025-03-01 2025-12-31 cancelled renewal
128 contract_0127 acct_0127 parent_0127 plan_002 4270000 4270000 2025-03-01 2025-12-31 expired expansion
129 contract_0128 acct_0128 parent_0128 plan_002 4280000 4280000 2025-03-01 2025-12-31 expired downgrade
130 contract_0129 acct_0129 parent_0129 plan_002 4290000 4290000 2025-03-01 2025-12-31 cancelled new
131 contract_0130 acct_0130 parent_0130 plan_002 4300000 4300000 2025-03-01 2025-12-31 expired renewal
132 contract_0131 acct_0131 parent_0131 plan_002 4310000 4310000 2025-03-01 2025-12-31 expired expansion
133 contract_0132 acct_0132 parent_0132 plan_002 4320000 4320000 2025-03-01 2025-12-31 cancelled downgrade
134 contract_0133 acct_0133 parent_0001 plan_002 4330000 4330000 2025-03-01 2025-12-31 expired new
135 contract_0134 acct_0134 parent_0002 plan_002 4340000 4340000 2025-03-01 2025-12-31 expired renewal
136 contract_0135 acct_0135 parent_0003 plan_002 4350000 4350000 2025-03-01 2025-12-31 cancelled expansion
137 contract_0136 acct_0136 parent_0004 plan_002 4360000 4360000 2025-03-01 2025-12-31 expired downgrade
138 contract_0137 acct_0137 parent_0005 plan_002 4370000 4370000 2025-03-01 2025-12-31 expired new
139 contract_0138 acct_0138 parent_0006 plan_002 4380000 4380000 2025-03-01 2025-12-31 cancelled renewal
140 contract_0139 acct_0139 parent_0007 plan_002 4390000 4390000 2025-03-01 2025-12-31 expired expansion
141 contract_0140 acct_0140 parent_0008 plan_002 4400000 4400000 2025-03-01 2025-12-31 expired downgrade
142 contract_0141 acct_0141 parent_0009 plan_002 4410000 4410000 2025-03-01 2025-12-31 cancelled new
143 contract_0142 acct_0142 parent_0010 plan_002 4420000 4420000 2025-03-01 2025-12-31 expired renewal
144 contract_0143 acct_0143 parent_0011 plan_002 4430000 4430000 2025-03-01 2025-12-31 expired expansion
145 contract_0144 acct_0144 parent_0012 plan_002 4440000 4440000 2025-03-01 2025-12-31 cancelled downgrade
146 contract_0145 acct_0145 parent_0013 plan_002 4450000 4450000 2025-03-01 2025-12-31 expired new
147 contract_0146 acct_0146 parent_0014 plan_002 4460000 4460000 2025-03-01 2025-12-31 expired renewal
148 contract_0147 acct_0147 parent_0015 plan_002 4470000 4470000 2025-03-01 2025-12-31 cancelled expansion
149 contract_0148 acct_0148 parent_0016 plan_002 4480000 4480000 2025-03-01 2025-12-31 expired downgrade
150 contract_0149 acct_0149 parent_0017 plan_002 4490000 4490000 2025-03-01 2025-12-31 expired new
151 contract_0150 acct_0150 parent_0018 plan_002 4500000 4500000 2025-03-01 2025-12-31 cancelled renewal
152 contract_0151 acct_0151 parent_0019 plan_001 4510000 4510000 2025-03-01 2025-12-31 expired expansion
153 contract_0152 acct_0152 parent_0020 plan_001 4520000 4520000 2025-03-01 2025-12-31 expired downgrade
154 contract_0153 acct_0153 parent_0021 plan_001 4530000 4530000 2025-03-01 2025-12-31 cancelled new
155 contract_0154 acct_0154 parent_0022 plan_001 4540000 4540000 2025-03-01 2025-12-31 expired renewal
156 contract_0155 acct_0155 parent_0023 plan_001 4550000 4550000 2025-03-01 2025-12-31 expired expansion
157 contract_0156 acct_0156 parent_0024 plan_001 4560000 4560000 2025-03-01 2025-12-31 cancelled downgrade
158 contract_0157 acct_0157 parent_0025 plan_001 4570000 4570000 2025-03-01 2025-12-31 expired new
159 contract_0158 acct_0158 parent_0026 plan_001 4580000 4580000 2025-03-01 2025-12-31 expired renewal
160 contract_0159 acct_0159 parent_0027 plan_001 4590000 4590000 2025-03-01 2025-12-31 cancelled expansion
161 contract_0160 acct_0160 parent_0028 plan_001 4600000 4600000 2025-03-01 2025-12-31 expired downgrade
162 contract_0161 acct_0161 parent_0029 plan_001 4610000 4610000 2025-03-01 2025-12-31 expired new
163 contract_0162 acct_0162 parent_0030 plan_001 4620000 4620000 2025-03-01 2025-12-31 cancelled renewal
164 contract_0163 acct_0163 parent_0031 plan_001 4630000 4630000 2025-03-01 2025-12-31 expired expansion
165 contract_0164 acct_0164 parent_0032 plan_004 4640000 4640000 2025-03-01 2025-12-31 expired downgrade
166 contract_0165 acct_0165 parent_0033 plan_001 4650000 4650000 2025-03-01 2025-12-31 cancelled new
167 contract_0166 acct_0166 parent_0034 plan_001 4660000 4660000 2025-03-01 2025-12-31 expired renewal
168 contract_0167 acct_0167 parent_0035 plan_001 4670000 4670000 2025-03-01 2025-12-31 expired expansion
169 contract_0168 acct_0168 parent_0036 plan_001 4680000 4680000 2025-03-01 2025-12-31 cancelled downgrade
170 contract_0169 acct_0169 parent_0037 plan_001 4690000 4690000 2025-03-01 2025-12-31 expired new
171 contract_0170 acct_0170 parent_0038 plan_001 4700000 4700000 2025-03-01 2025-12-31 expired renewal
172 contract_0171 acct_0171 parent_0039 plan_001 4710000 4710000 2025-03-01 2025-12-31 cancelled expansion
173 contract_0172 acct_0172 parent_0040 plan_001 4720000 4720000 2025-03-01 2025-12-31 expired downgrade
174 contract_0173 acct_0173 parent_0041 plan_001 4730000 4730000 2025-03-01 2025-12-31 expired new
175 contract_0174 acct_0174 parent_0042 plan_001 4740000 4740000 2025-03-01 2025-12-31 cancelled renewal
176 contract_0175 acct_0175 parent_0043 plan_001 4750000 4750000 2025-03-01 2025-12-31 expired expansion
177 contract_0176 acct_0176 parent_0044 plan_001 4760000 4760000 2025-03-01 2025-12-31 expired downgrade
178 contract_0177 acct_0177 parent_0045 plan_001 4770000 4770000 2025-03-01 2025-12-31 cancelled new
179 contract_0178 acct_0178 parent_0046 plan_001 4780000 4780000 2025-03-01 2025-12-31 expired renewal
180 contract_0179 acct_0179 parent_0047 plan_001 4790000 4790000 2025-03-01 2025-12-31 expired expansion
181 contract_0180 acct_0180 parent_0048 plan_001 4800000 4800000 2025-03-01 2025-12-31 cancelled downgrade
182 contract_0181 acct_0181 parent_0049 plan_001 4810000 4810000 2025-03-01 2025-12-31 expired new
183 contract_0182 acct_0182 parent_0050 plan_001 4820000 4820000 2025-03-01 2025-12-31 expired renewal
184 contract_0183 acct_0183 parent_0051 plan_001 4830000 4830000 2025-03-01 2025-12-31 cancelled expansion
185 contract_0184 acct_0184 parent_0052 plan_001 4840000 4840000 2025-03-01 2025-12-31 expired downgrade
186 contract_0185 acct_0185 parent_0053 plan_001 4850000 4850000 2025-03-01 2025-12-31 expired new
187 contract_0186 acct_0186 parent_0054 plan_001 4860000 4860000 2025-03-01 2025-12-31 cancelled renewal
188 contract_0187 acct_0187 parent_0055 plan_001 4870000 4870000 2025-03-01 2025-12-31 expired expansion
189 contract_0188 acct_0188 parent_0056 plan_001 4880000 4880000 2025-03-01 2025-12-31 expired downgrade
190 contract_0189 acct_0189 parent_0057 plan_001 4890000 4890000 2025-03-01 2025-12-31 cancelled new
191 contract_0190 acct_0190 parent_0058 plan_001 4900000 4900000 2025-03-01 2025-12-31 expired renewal
192 contract_0191 acct_0191 parent_0059 plan_001 4910000 4910000 2025-03-01 2025-12-31 expired expansion
193 contract_0192 acct_0192 parent_0060 plan_001 4920000 4920000 2025-03-01 2025-12-31 cancelled downgrade
194 contract_0193 acct_0193 parent_0061 plan_001 4930000 4930000 2025-03-01 2025-12-31 expired new
195 contract_0194 acct_0194 parent_0062 plan_001 4940000 4940000 2025-03-01 2025-12-31 expired renewal
196 contract_0195 acct_0195 parent_0063 plan_001 4950000 4950000 2025-03-01 2025-12-31 cancelled expansion
197 contract_0196 acct_0196 parent_0064 plan_001 4960000 4960000 2025-03-01 2025-12-31 expired downgrade
198 contract_0197 acct_0197 parent_0065 plan_001 4970000 4970000 2025-03-01 2025-12-31 expired new
199 contract_0198 acct_0198 parent_0066 plan_001 4980000 4980000 2025-03-01 2025-12-31 cancelled renewal
200 contract_0199 acct_0199 parent_0067 plan_001 4990000 4990000 2025-03-01 2025-12-31 expired expansion
201 contract_0200 acct_0200 parent_0068 plan_001 5000000 5000000 2025-03-01 2025-12-31 expired downgrade
202 contract_0201 acct_0001 parent_0001 plan_003 5010000 5010000 2025-03-01 2025-12-31 cancelled new
203 contract_0202 acct_0002 parent_0002 plan_003 5020000 5020000 2025-03-01 2025-12-31 expired renewal
204 contract_0203 acct_0003 parent_0003 plan_003 5030000 5030000 2025-03-01 2025-12-31 expired expansion
205 contract_0204 acct_0004 parent_0004 plan_003 5040000 5040000 2025-03-01 2025-12-31 cancelled downgrade
206 contract_0205 acct_0005 parent_0005 plan_004 5050000 5050000 2025-03-01 2025-12-31 expired new
207 contract_0206 acct_0006 parent_0006 plan_003 5060000 5060000 2025-03-01 2025-12-31 expired renewal
208 contract_0207 acct_0007 parent_0007 plan_003 5070000 5070000 2025-03-01 2025-12-31 cancelled expansion
209 contract_0208 acct_0008 parent_0008 plan_003 5080000 5080000 2025-03-01 2025-12-31 expired downgrade
210 contract_0209 acct_0009 parent_0009 plan_003 5090000 5090000 2025-03-01 2025-12-31 expired new
211 contract_0210 acct_0010 parent_0010 plan_003 5100000 5100000 2025-03-01 2025-12-31 cancelled renewal
212 contract_0211 acct_0011 parent_0011 plan_003 5110000 5110000 2025-03-01 2025-12-31 expired expansion
213 contract_0212 acct_0012 parent_0012 plan_003 5120000 5120000 2025-03-01 2025-12-31 expired downgrade
214 contract_0213 acct_0013 parent_0013 plan_003 5130000 5130000 2025-03-01 2025-12-31 cancelled new
215 contract_0214 acct_0014 parent_0014 plan_003 5140000 5140000 2025-03-01 2025-12-31 expired renewal
216 contract_0215 acct_0015 parent_0015 plan_003 5150000 5150000 2025-03-01 2025-12-31 expired expansion
217 contract_0216 acct_0016 parent_0016 plan_003 5160000 5160000 2025-03-01 2025-12-31 cancelled downgrade
218 contract_0217 acct_0017 parent_0017 plan_003 5170000 5170000 2025-03-01 2025-12-31 expired new
219 contract_0218 acct_0018 parent_0018 plan_003 5180000 5180000 2025-03-01 2025-12-31 expired renewal
220 contract_0219 acct_0019 parent_0019 plan_003 5190000 5190000 2025-03-01 2025-12-31 cancelled expansion
221 contract_0220 acct_0020 parent_0020 plan_003 5200000 5200000 2025-03-01 2025-12-31 expired downgrade
222 contract_0221 acct_0021 parent_0021 plan_003 5210000 5210000 2025-03-01 2025-12-31 expired new
223 contract_0222 acct_0022 parent_0022 plan_003 5220000 5220000 2025-03-01 2025-12-31 cancelled renewal
224 contract_0223 acct_0023 parent_0023 plan_003 5230000 5230000 2025-03-01 2025-12-31 expired expansion
225 contract_0224 acct_0024 parent_0024 plan_003 5240000 5240000 2025-03-01 2025-12-31 expired downgrade
226 contract_0225 acct_0025 parent_0025 plan_003 5250000 5250000 2025-03-01 2025-12-31 cancelled new
227 contract_0226 acct_0026 parent_0026 plan_003 5260000 5260000 2025-03-01 2025-12-31 expired renewal
228 contract_0227 acct_0027 parent_0027 plan_003 5270000 5270000 2025-03-01 2025-12-31 expired expansion
229 contract_0228 acct_0028 parent_0028 plan_003 5280000 5280000 2025-03-01 2025-12-31 cancelled downgrade
230 contract_0229 acct_0029 parent_0029 plan_003 5290000 5290000 2025-03-01 2025-12-31 expired new
231 contract_0230 acct_0030 parent_0030 plan_003 5300000 5300000 2025-03-01 2025-12-31 expired renewal
232 contract_0231 acct_0031 parent_0031 plan_003 5310000 5310000 2025-03-01 2025-12-31 cancelled expansion
233 contract_0232 acct_0032 parent_0032 plan_003 5320000 5320000 2025-03-01 2025-12-31 expired downgrade
234 contract_0233 acct_0033 parent_0033 plan_003 5330000 5330000 2025-03-01 2025-12-31 expired new
235 contract_0234 acct_0034 parent_0034 plan_003 5340000 5340000 2025-03-01 2025-12-31 cancelled renewal
236 contract_0235 acct_0035 parent_0035 plan_003 5350000 5350000 2025-03-01 2025-12-31 expired expansion
237 contract_0236 acct_0036 parent_0036 plan_003 5360000 5360000 2025-03-01 2025-12-31 expired downgrade
238 contract_0237 acct_0037 parent_0037 plan_003 5370000 5370000 2025-03-01 2025-12-31 cancelled new
239 contract_0238 acct_0038 parent_0038 plan_003 5380000 5380000 2025-03-01 2025-12-31 expired renewal
240 contract_0239 acct_0039 parent_0039 plan_003 5390000 5390000 2025-03-01 2025-12-31 expired expansion
241 contract_0240 acct_0040 parent_0040 plan_003 5400000 5400000 2025-03-01 2025-12-31 cancelled downgrade
242 contract_0241 acct_0041 parent_0041 plan_003 5410000 5410000 2025-03-01 2025-12-31 expired new
243 contract_0242 acct_0042 parent_0042 plan_003 5420000 5420000 2025-03-01 2025-12-31 expired renewal
244 contract_0243 acct_0043 parent_0043 plan_003 5430000 5430000 2025-03-01 2025-12-31 cancelled expansion
245 contract_0244 acct_0044 parent_0044 plan_003 5440000 5440000 2025-03-01 2025-12-31 expired downgrade
246 contract_0245 acct_0045 parent_0045 plan_003 5450000 5450000 2025-03-01 2025-12-31 expired new
247 contract_0246 acct_0046 parent_0046 plan_004 5460000 5460000 2025-03-01 2025-12-31 cancelled renewal
248 contract_0247 acct_0047 parent_0047 plan_003 5470000 5470000 2025-03-01 2025-12-31 expired expansion
249 contract_0248 acct_0048 parent_0048 plan_003 5480000 5480000 2025-03-01 2025-12-31 expired downgrade
250 contract_0249 acct_0049 parent_0049 plan_003 5490000 5490000 2025-03-01 2025-12-31 cancelled new
251 contract_0250 acct_0050 parent_0050 plan_003 5500000 5500000 2025-03-01 2025-12-31 expired renewal
252 contract_0251 acct_0051 parent_0051 plan_003 5510000 5510000 2025-03-01 2025-12-31 expired expansion
253 contract_0252 acct_0052 parent_0052 plan_003 5520000 5520000 2025-03-01 2025-12-31 cancelled downgrade
254 contract_0253 acct_0053 parent_0053 plan_003 5530000 5530000 2025-03-01 2025-12-31 expired new
255 contract_0254 acct_0054 parent_0054 plan_003 5540000 5540000 2025-03-01 2025-12-31 expired renewal
256 contract_0255 acct_0055 parent_0055 plan_003 5550000 5550000 2025-03-01 2025-12-31 cancelled expansion
257 contract_0256 acct_0056 parent_0056 plan_003 5560000 5560000 2025-03-01 2025-12-31 expired downgrade
258 contract_0257 acct_0057 parent_0057 plan_003 5570000 5570000 2025-03-01 2025-12-31 expired new
259 contract_0258 acct_0058 parent_0058 plan_003 5580000 5580000 2025-03-01 2025-12-31 cancelled renewal
260 contract_0259 acct_0059 parent_0059 plan_003 5590000 5590000 2025-03-01 2025-12-31 expired expansion
261 contract_0260 acct_0060 parent_0060 plan_003 5600000 5600000 2025-03-01 2025-12-31 expired downgrade
262 contract_0261 acct_0061 parent_0061 plan_003 5610000 5610000 2025-03-01 2025-12-31 cancelled new
263 contract_0262 acct_0062 parent_0062 plan_003 5620000 5620000 2025-03-01 2025-12-31 expired renewal
264 contract_0263 acct_0063 parent_0063 plan_003 5630000 5630000 2025-03-01 2025-12-31 expired expansion
265 contract_0264 acct_0064 parent_0064 plan_003 5640000 5640000 2025-03-01 2025-12-31 cancelled downgrade
266 contract_0265 acct_0065 parent_0065 plan_003 5650000 5650000 2025-03-01 2025-12-31 expired new
267 contract_0266 acct_0066 parent_0066 plan_003 5660000 5660000 2025-03-01 2025-12-31 expired renewal
268 contract_0267 acct_0067 parent_0067 plan_003 5670000 5670000 2025-03-01 2025-12-31 cancelled expansion
269 contract_0268 acct_0068 parent_0068 plan_003 5680000 5680000 2025-03-01 2025-12-31 expired downgrade
270 contract_0269 acct_0069 parent_0069 plan_003 5690000 5690000 2025-03-01 2025-12-31 expired new
271 contract_0270 acct_0070 parent_0070 plan_003 5700000 5700000 2025-03-01 2025-12-31 cancelled renewal
272 contract_0271 acct_0071 parent_0071 plan_003 5710000 5710000 2025-03-01 2025-12-31 expired expansion
273 contract_0272 acct_0072 parent_0072 plan_003 5720000 5720000 2025-03-01 2025-12-31 expired downgrade
274 contract_0273 acct_0073 parent_0073 plan_003 5730000 5730000 2025-03-01 2025-12-31 cancelled new
275 contract_0274 acct_0074 parent_0074 plan_003 5740000 5740000 2025-03-01 2025-12-31 expired renewal
276 contract_0275 acct_0075 parent_0075 plan_003 5750000 5750000 2025-03-01 2025-12-31 expired expansion
277 contract_0276 acct_0076 parent_0076 plan_003 5760000 5760000 2025-03-01 2025-12-31 cancelled downgrade
278 contract_0277 acct_0077 parent_0077 plan_003 5770000 5770000 2025-03-01 2025-12-31 expired new
279 contract_0278 acct_0078 parent_0078 plan_003 5780000 5780000 2025-03-01 2025-12-31 expired renewal
280 contract_0279 acct_0079 parent_0079 plan_003 5790000 5790000 2025-03-01 2025-12-31 cancelled expansion
281 contract_0280 acct_0080 parent_0080 plan_003 5800000 5800000 2025-03-01 2025-12-31 expired downgrade
282 contract_0281 acct_0081 parent_0081 plan_002 5810000 5810000 2025-03-01 2025-12-31 expired new
283 contract_0282 acct_0082 parent_0082 plan_002 5820000 5820000 2025-03-01 2025-12-31 cancelled renewal
284 contract_0283 acct_0083 parent_0083 plan_002 5830000 5830000 2025-03-01 2025-12-31 expired expansion
285 contract_0284 acct_0084 parent_0084 plan_002 5840000 5840000 2025-03-01 2025-12-31 expired downgrade
286 contract_0285 acct_0085 parent_0085 plan_002 5850000 5850000 2025-03-01 2025-12-31 cancelled new
287 contract_0286 acct_0086 parent_0086 plan_002 5860000 5860000 2025-03-01 2025-12-31 expired renewal
288 contract_0287 acct_0087 parent_0087 plan_004 5870000 5870000 2025-03-01 2025-12-31 expired expansion
289 contract_0288 acct_0088 parent_0088 plan_002 5880000 5880000 2025-03-01 2025-12-31 cancelled downgrade
290 contract_0289 acct_0089 parent_0089 plan_002 5890000 5890000 2025-03-01 2025-12-31 expired new
291 contract_0290 acct_0090 parent_0090 plan_002 5900000 5900000 2025-03-01 2025-12-31 expired renewal
292 contract_0291 acct_0091 parent_0091 plan_002 5910000 5910000 2025-03-01 2025-12-31 cancelled expansion
293 contract_0292 acct_0092 parent_0092 plan_002 5920000 5920000 2025-03-01 2025-12-31 expired downgrade
294 contract_0293 acct_0093 parent_0093 plan_002 5930000 5930000 2025-03-01 2025-12-31 expired new
295 contract_0294 acct_0094 parent_0094 plan_002 5940000 5940000 2025-03-01 2025-12-31 cancelled renewal
296 contract_0295 acct_0095 parent_0095 plan_002 5950000 5950000 2025-03-01 2025-12-31 expired expansion
297 contract_0296 acct_0096 parent_0096 plan_002 5960000 5960000 2025-03-01 2025-12-31 expired downgrade
298 contract_0297 acct_0097 parent_0097 plan_002 5970000 5970000 2025-03-01 2025-12-31 cancelled new
299 contract_0298 acct_0098 parent_0098 plan_002 5980000 5980000 2025-03-01 2025-12-31 expired renewal
300 contract_0299 acct_0099 parent_0099 plan_002 5990000 5990000 2025-03-01 2025-12-31 expired expansion
301 contract_0300 acct_0100 parent_0100 plan_002 6000000 6000000 2025-03-01 2025-12-31 cancelled downgrade
302 contract_0301 acct_0101 parent_0101 plan_002 6010000 6010000 2025-03-01 2025-12-31 expired new
303 contract_0302 acct_0102 parent_0102 plan_002 6020000 6020000 2025-03-01 2025-12-31 expired renewal
304 contract_0303 acct_0103 parent_0103 plan_002 6030000 6030000 2025-03-01 2025-12-31 cancelled expansion
305 contract_0304 acct_0104 parent_0104 plan_002 6040000 6040000 2025-03-01 2025-12-31 expired downgrade
306 contract_0305 acct_0105 parent_0105 plan_002 6050000 6050000 2025-03-01 2025-12-31 expired new
307 contract_0306 acct_0106 parent_0106 plan_002 6060000 6060000 2025-03-01 2025-12-31 cancelled renewal
308 contract_0307 acct_0107 parent_0107 plan_002 6070000 6070000 2025-03-01 2025-12-31 expired expansion
309 contract_0308 acct_0108 parent_0108 plan_002 6080000 6080000 2025-03-01 2025-12-31 expired downgrade
310 contract_0309 acct_0109 parent_0109 plan_002 6090000 6090000 2025-03-01 2025-12-31 cancelled new
311 contract_0310 acct_0110 parent_0110 plan_002 6100000 6100000 2025-03-01 2025-12-31 expired renewal
312 contract_0311 acct_0111 parent_0111 plan_002 6110000 6110000 2025-03-01 2025-12-31 expired expansion
313 contract_0312 acct_0112 parent_0112 plan_002 6120000 6120000 2025-03-01 2025-12-31 cancelled downgrade
314 contract_0313 acct_0113 parent_0113 plan_002 6130000 6130000 2025-03-01 2025-12-31 expired new
315 contract_0314 acct_0114 parent_0114 plan_002 6140000 6140000 2025-03-01 2025-12-31 expired renewal
316 contract_0315 acct_0115 parent_0115 plan_002 6150000 6150000 2025-03-01 2025-12-31 cancelled expansion
317 contract_0316 acct_0116 parent_0116 plan_002 6160000 6160000 2025-03-01 2025-12-31 expired downgrade
318 contract_0317 acct_0117 parent_0117 plan_002 6170000 6170000 2025-03-01 2025-12-31 expired new
319 contract_0318 acct_0118 parent_0118 plan_002 6180000 6180000 2025-03-01 2025-12-31 cancelled renewal
320 contract_0319 acct_0119 parent_0119 plan_002 6190000 6190000 2025-03-01 2025-12-31 expired expansion
321 contract_0320 acct_0120 parent_0120 plan_002 6200000 6200000 2025-03-01 2025-12-31 expired downgrade

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
plan_id,plan_code,plan_name,canonical_plan_code,is_retired,retired_at
plan_001,starter,Starter,starter,false,2099-12-31T00:00:00Z
plan_002,growth,Growth,growth,false,2099-12-31T00:00:00Z
plan_003,enterprise,Enterprise,enterprise,false,2099-12-31T00:00:00Z
plan_004,pro_plus,Pro Plus,growth,true,2025-10-01T00:00:00Z
1 plan_id plan_code plan_name canonical_plan_code is_retired retired_at
2 plan_001 starter Starter starter false 2099-12-31T00:00:00Z
3 plan_002 growth Growth growth false 2099-12-31T00:00:00Z
4 plan_003 enterprise Enterprise enterprise false 2099-12-31T00:00:00Z
5 plan_004 pro_plus Pro Plus growth true 2025-10-01T00:00:00Z

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,521 @@
support_ticket_id,account_id,requester_user_id,severity,category,status,created_at,resolved_at,owner_user_id
ticket_0001,acct_0001,user_000287,critical,approval_routing,open,2026-03-10T17:00:00Z,2099-12-31T00:00:00Z,user_000901
ticket_0002,acct_0002,user_000288,high,approval_routing,open,2026-03-11T17:00:00Z,2099-12-31T00:00:00Z,user_000902
ticket_0003,acct_0003,user_000289,critical,approval_routing,open,2026-03-12T17:00:00Z,2099-12-31T00:00:00Z,user_000903
ticket_0004,acct_0004,user_000290,high,approval_routing,open,2026-03-13T17:00:00Z,2099-12-31T00:00:00Z,user_000904
ticket_0005,acct_0005,user_000291,critical,approval_routing,open,2026-03-14T17:00:00Z,2099-12-31T00:00:00Z,user_000905
ticket_0006,acct_0006,user_000292,high,approval_routing,open,2026-03-15T17:00:00Z,2099-12-31T00:00:00Z,user_000906
ticket_0007,acct_0007,user_000293,critical,approval_routing,open,2026-03-16T17:00:00Z,2099-12-31T00:00:00Z,user_000907
ticket_0008,acct_0008,user_000294,high,approval_routing,open,2026-03-17T17:00:00Z,2099-12-31T00:00:00Z,user_000908
ticket_0009,acct_0009,user_000295,critical,approval_routing,open,2026-03-18T17:00:00Z,2099-12-31T00:00:00Z,user_000909
ticket_0010,acct_0010,user_000010,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_000909
ticket_0011,acct_0011,user_000011,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_000910
ticket_0012,acct_0012,user_000012,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_000911
ticket_0013,acct_0013,user_000013,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_000912
ticket_0014,acct_0014,user_000014,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_000913
ticket_0015,acct_0015,user_000015,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_000914
ticket_0016,acct_0016,user_000016,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_000915
ticket_0017,acct_0017,user_000017,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_000916
ticket_0018,acct_0018,user_000018,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_000917
ticket_0019,acct_0019,user_000019,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_000918
ticket_0020,acct_0020,user_000020,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_000919
ticket_0021,acct_0021,user_000021,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_000920
ticket_0022,acct_0022,user_000022,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_000921
ticket_0023,acct_0023,user_000023,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_000922
ticket_0024,acct_0024,user_000024,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_000923
ticket_0025,acct_0025,user_000025,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_000924
ticket_0026,acct_0026,user_000026,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_000925
ticket_0027,acct_0027,user_000027,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_000926
ticket_0028,acct_0028,user_000028,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_000927
ticket_0029,acct_0029,user_000029,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_000928
ticket_0030,acct_0030,user_000030,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_000929
ticket_0031,acct_0031,user_000031,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_000930
ticket_0032,acct_0032,user_000032,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_000931
ticket_0033,acct_0033,user_000033,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_000932
ticket_0034,acct_0034,user_000034,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_000933
ticket_0035,acct_0035,user_000035,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_000934
ticket_0036,acct_0036,user_000036,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_000935
ticket_0037,acct_0037,user_000037,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_000936
ticket_0038,acct_0038,user_000038,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_000937
ticket_0039,acct_0039,user_000039,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_000938
ticket_0040,acct_0040,user_000040,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_000939
ticket_0041,acct_0041,user_000041,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_000940
ticket_0042,acct_0042,user_000042,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_000941
ticket_0043,acct_0043,user_000043,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_000942
ticket_0044,acct_0044,user_000044,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_000943
ticket_0045,acct_0045,user_000045,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_000944
ticket_0046,acct_0046,user_000046,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_000945
ticket_0047,acct_0047,user_000047,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_000946
ticket_0048,acct_0048,user_000048,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_000947
ticket_0049,acct_0049,user_000049,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_000948
ticket_0050,acct_0050,user_000050,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_000949
ticket_0051,acct_0051,user_000051,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_000950
ticket_0052,acct_0052,user_000052,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_000951
ticket_0053,acct_0053,user_000053,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_000952
ticket_0054,acct_0054,user_000054,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_000953
ticket_0055,acct_0055,user_000055,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_000954
ticket_0056,acct_0056,user_000056,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_000955
ticket_0057,acct_0057,user_000057,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_000956
ticket_0058,acct_0058,user_000058,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_000957
ticket_0059,acct_0059,user_000059,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_000958
ticket_0060,acct_0060,user_000060,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_000959
ticket_0061,acct_0061,user_000061,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_000960
ticket_0062,acct_0062,user_000062,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_000961
ticket_0063,acct_0063,user_000063,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_000962
ticket_0064,acct_0064,user_000064,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_000963
ticket_0065,acct_0065,user_000065,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_000964
ticket_0066,acct_0066,user_000066,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_000965
ticket_0067,acct_0067,user_000067,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_000966
ticket_0068,acct_0068,user_000068,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_000967
ticket_0069,acct_0069,user_000069,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_000968
ticket_0070,acct_0070,user_000070,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_000969
ticket_0071,acct_0071,user_000071,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_000970
ticket_0072,acct_0072,user_000072,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_000971
ticket_0073,acct_0073,user_000073,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_000972
ticket_0074,acct_0074,user_000074,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_000973
ticket_0075,acct_0075,user_000075,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_000974
ticket_0076,acct_0076,user_000076,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_000975
ticket_0077,acct_0077,user_000077,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_000976
ticket_0078,acct_0078,user_000078,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_000977
ticket_0079,acct_0079,user_000079,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_000978
ticket_0080,acct_0080,user_000080,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_000979
ticket_0081,acct_0081,user_000081,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_000980
ticket_0082,acct_0082,user_000082,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_000981
ticket_0083,acct_0083,user_000083,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_000982
ticket_0084,acct_0084,user_000084,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_000983
ticket_0085,acct_0085,user_000085,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_000984
ticket_0086,acct_0086,user_000086,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_000985
ticket_0087,acct_0087,user_000087,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_000986
ticket_0088,acct_0088,user_000088,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_000987
ticket_0089,acct_0089,user_000089,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_000988
ticket_0090,acct_0090,user_000090,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_000989
ticket_0091,acct_0091,user_000091,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_000990
ticket_0092,acct_0092,user_000092,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_000991
ticket_0093,acct_0093,user_000093,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_000992
ticket_0094,acct_0094,user_000094,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_000993
ticket_0095,acct_0095,user_000095,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_000994
ticket_0096,acct_0096,user_000096,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_000995
ticket_0097,acct_0097,user_000097,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_000996
ticket_0098,acct_0098,user_000098,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_000997
ticket_0099,acct_0099,user_000099,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_000998
ticket_0100,acct_0100,user_000100,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_000999
ticket_0101,acct_0101,user_000101,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_001000
ticket_0102,acct_0102,user_000102,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_001001
ticket_0103,acct_0103,user_000103,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_001002
ticket_0104,acct_0104,user_000104,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_001003
ticket_0105,acct_0105,user_000105,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_001004
ticket_0106,acct_0106,user_000106,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_001005
ticket_0107,acct_0107,user_000107,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_001006
ticket_0108,acct_0108,user_000108,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_001007
ticket_0109,acct_0109,user_000109,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_001008
ticket_0110,acct_0110,user_000110,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_001009
ticket_0111,acct_0111,user_000111,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_001010
ticket_0112,acct_0112,user_000112,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_001011
ticket_0113,acct_0113,user_000113,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_001012
ticket_0114,acct_0114,user_000114,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_001013
ticket_0115,acct_0115,user_000115,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_001014
ticket_0116,acct_0116,user_000116,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_001015
ticket_0117,acct_0117,user_000117,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_001016
ticket_0118,acct_0118,user_000118,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_001017
ticket_0119,acct_0119,user_000119,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_001018
ticket_0120,acct_0120,user_000120,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_001019
ticket_0121,acct_0121,user_000121,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_001020
ticket_0122,acct_0122,user_000122,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_001021
ticket_0123,acct_0123,user_000123,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_001022
ticket_0124,acct_0124,user_000124,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_001023
ticket_0125,acct_0125,user_000125,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_001024
ticket_0126,acct_0126,user_000126,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_001025
ticket_0127,acct_0127,user_000127,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_001026
ticket_0128,acct_0128,user_000128,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_001027
ticket_0129,acct_0129,user_000129,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_001028
ticket_0130,acct_0130,user_000130,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_001029
ticket_0131,acct_0131,user_000131,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_001030
ticket_0132,acct_0132,user_000132,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_001031
ticket_0133,acct_0133,user_000133,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_001032
ticket_0134,acct_0134,user_000134,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_001033
ticket_0135,acct_0135,user_000135,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_001034
ticket_0136,acct_0136,user_000136,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_001035
ticket_0137,acct_0137,user_000137,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_001036
ticket_0138,acct_0138,user_000138,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_001037
ticket_0139,acct_0139,user_000139,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_001038
ticket_0140,acct_0140,user_000140,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_001039
ticket_0141,acct_0141,user_000141,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_001040
ticket_0142,acct_0142,user_000142,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_001041
ticket_0143,acct_0143,user_000143,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_001042
ticket_0144,acct_0144,user_000144,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_001043
ticket_0145,acct_0145,user_000145,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_001044
ticket_0146,acct_0146,user_000146,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_001045
ticket_0147,acct_0147,user_000147,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_001046
ticket_0148,acct_0148,user_000148,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_001047
ticket_0149,acct_0149,user_000149,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_001048
ticket_0150,acct_0150,user_000150,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_001049
ticket_0151,acct_0151,user_000151,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_001050
ticket_0152,acct_0152,user_000152,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_001051
ticket_0153,acct_0153,user_000153,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_001052
ticket_0154,acct_0154,user_000154,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_001053
ticket_0155,acct_0155,user_000155,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_001054
ticket_0156,acct_0156,user_000156,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_001055
ticket_0157,acct_0157,user_000157,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_001056
ticket_0158,acct_0158,user_000158,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_001057
ticket_0159,acct_0159,user_000159,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_001058
ticket_0160,acct_0160,user_000160,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_001059
ticket_0161,acct_0161,user_000161,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_001060
ticket_0162,acct_0162,user_000162,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_001061
ticket_0163,acct_0163,user_000163,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_001062
ticket_0164,acct_0164,user_000164,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_001063
ticket_0165,acct_0165,user_000165,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_001064
ticket_0166,acct_0166,user_000166,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_001065
ticket_0167,acct_0167,user_000167,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_001066
ticket_0168,acct_0168,user_000168,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_001067
ticket_0169,acct_0169,user_000169,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_001068
ticket_0170,acct_0170,user_000170,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_001069
ticket_0171,acct_0171,user_000171,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_001070
ticket_0172,acct_0172,user_000172,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_001071
ticket_0173,acct_0173,user_000173,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_001072
ticket_0174,acct_0174,user_000174,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_001073
ticket_0175,acct_0175,user_000175,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_001074
ticket_0176,acct_0176,user_000176,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_001075
ticket_0177,acct_0177,user_000177,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_001076
ticket_0178,acct_0178,user_000178,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_001077
ticket_0179,acct_0179,user_000179,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_001078
ticket_0180,acct_0180,user_000180,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_001079
ticket_0181,acct_0181,user_000181,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_001080
ticket_0182,acct_0182,user_000182,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_001081
ticket_0183,acct_0183,user_000183,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_001082
ticket_0184,acct_0184,user_000184,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_001083
ticket_0185,acct_0185,user_000185,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_001084
ticket_0186,acct_0186,user_000186,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_001085
ticket_0187,acct_0187,user_000187,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_001086
ticket_0188,acct_0188,user_000188,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_001087
ticket_0189,acct_0189,user_000189,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_001088
ticket_0190,acct_0190,user_000190,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_001089
ticket_0191,acct_0191,user_000191,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_001090
ticket_0192,acct_0192,user_000192,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_001091
ticket_0193,acct_0193,user_000193,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_001092
ticket_0194,acct_0194,user_000194,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_001093
ticket_0195,acct_0195,user_000195,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_001094
ticket_0196,acct_0196,user_000196,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_001095
ticket_0197,acct_0197,user_000197,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_001096
ticket_0198,acct_0198,user_000198,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_001097
ticket_0199,acct_0199,user_000199,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_001098
ticket_0200,acct_0010,user_000200,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_001099
ticket_0201,acct_0011,user_000201,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_000900
ticket_0202,acct_0012,user_000202,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_000901
ticket_0203,acct_0013,user_000203,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_000902
ticket_0204,acct_0014,user_000204,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_000903
ticket_0205,acct_0015,user_000205,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_000904
ticket_0206,acct_0016,user_000206,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_000905
ticket_0207,acct_0017,user_000207,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_000906
ticket_0208,acct_0018,user_000208,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_000907
ticket_0209,acct_0019,user_000209,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_000908
ticket_0210,acct_0020,user_000210,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_000909
ticket_0211,acct_0021,user_000211,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_000910
ticket_0212,acct_0022,user_000212,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_000911
ticket_0213,acct_0023,user_000213,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_000912
ticket_0214,acct_0024,user_000214,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_000913
ticket_0215,acct_0025,user_000215,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_000914
ticket_0216,acct_0026,user_000216,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_000915
ticket_0217,acct_0027,user_000217,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_000916
ticket_0218,acct_0028,user_000218,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_000917
ticket_0219,acct_0029,user_000219,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_000918
ticket_0220,acct_0030,user_000220,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_000919
ticket_0221,acct_0031,user_000221,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_000920
ticket_0222,acct_0032,user_000222,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_000921
ticket_0223,acct_0033,user_000223,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_000922
ticket_0224,acct_0034,user_000224,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_000923
ticket_0225,acct_0035,user_000225,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_000924
ticket_0226,acct_0036,user_000226,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_000925
ticket_0227,acct_0037,user_000227,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_000926
ticket_0228,acct_0038,user_000228,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_000927
ticket_0229,acct_0039,user_000229,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_000928
ticket_0230,acct_0040,user_000230,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_000929
ticket_0231,acct_0041,user_000231,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_000930
ticket_0232,acct_0042,user_000232,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_000931
ticket_0233,acct_0043,user_000233,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_000932
ticket_0234,acct_0044,user_000234,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_000933
ticket_0235,acct_0045,user_000235,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_000934
ticket_0236,acct_0046,user_000236,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_000935
ticket_0237,acct_0047,user_000237,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_000936
ticket_0238,acct_0048,user_000238,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_000937
ticket_0239,acct_0049,user_000239,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_000938
ticket_0240,acct_0050,user_000240,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_000939
ticket_0241,acct_0051,user_000241,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_000940
ticket_0242,acct_0052,user_000242,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_000941
ticket_0243,acct_0053,user_000243,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_000942
ticket_0244,acct_0054,user_000244,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_000943
ticket_0245,acct_0055,user_000245,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_000944
ticket_0246,acct_0056,user_000246,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_000945
ticket_0247,acct_0057,user_000247,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_000946
ticket_0248,acct_0058,user_000248,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_000947
ticket_0249,acct_0059,user_000249,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_000948
ticket_0250,acct_0060,user_000250,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_000949
ticket_0251,acct_0061,user_000251,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_000950
ticket_0252,acct_0062,user_000252,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_000951
ticket_0253,acct_0063,user_000253,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_000952
ticket_0254,acct_0064,user_000254,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_000953
ticket_0255,acct_0065,user_000255,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_000954
ticket_0256,acct_0066,user_000256,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_000955
ticket_0257,acct_0067,user_000257,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_000956
ticket_0258,acct_0068,user_000258,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_000957
ticket_0259,acct_0069,user_000259,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_000958
ticket_0260,acct_0070,user_000260,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_000959
ticket_0261,acct_0071,user_000261,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_000960
ticket_0262,acct_0072,user_000262,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_000961
ticket_0263,acct_0073,user_000263,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_000962
ticket_0264,acct_0074,user_000264,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_000963
ticket_0265,acct_0075,user_000265,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_000964
ticket_0266,acct_0076,user_000266,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_000965
ticket_0267,acct_0077,user_000267,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_000966
ticket_0268,acct_0078,user_000268,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_000967
ticket_0269,acct_0079,user_000269,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_000968
ticket_0270,acct_0080,user_000270,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_000969
ticket_0271,acct_0081,user_000271,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_000970
ticket_0272,acct_0082,user_000272,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_000971
ticket_0273,acct_0083,user_000273,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_000972
ticket_0274,acct_0084,user_000274,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_000973
ticket_0275,acct_0085,user_000275,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_000974
ticket_0276,acct_0086,user_000276,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_000975
ticket_0277,acct_0087,user_000277,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_000976
ticket_0278,acct_0088,user_000278,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_000977
ticket_0279,acct_0089,user_000279,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_000978
ticket_0280,acct_0090,user_000280,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_000979
ticket_0281,acct_0091,user_000281,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_000980
ticket_0282,acct_0092,user_000282,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_000981
ticket_0283,acct_0093,user_000283,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_000982
ticket_0284,acct_0094,user_000284,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_000983
ticket_0285,acct_0095,user_000285,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_000984
ticket_0286,acct_0096,user_000286,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_000985
ticket_0287,acct_0097,user_000287,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_000986
ticket_0288,acct_0098,user_000288,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_000987
ticket_0289,acct_0099,user_000289,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_000988
ticket_0290,acct_0100,user_000290,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_000989
ticket_0291,acct_0101,user_000291,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_000990
ticket_0292,acct_0102,user_000292,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_000991
ticket_0293,acct_0103,user_000293,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_000992
ticket_0294,acct_0104,user_000294,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_000993
ticket_0295,acct_0105,user_000295,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_000994
ticket_0296,acct_0106,user_000296,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_000995
ticket_0297,acct_0107,user_000297,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_000996
ticket_0298,acct_0108,user_000298,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_000997
ticket_0299,acct_0109,user_000299,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_000998
ticket_0300,acct_0110,user_000300,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_000999
ticket_0301,acct_0111,user_000301,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_001000
ticket_0302,acct_0112,user_000302,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_001001
ticket_0303,acct_0113,user_000303,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_001002
ticket_0304,acct_0114,user_000304,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_001003
ticket_0305,acct_0115,user_000305,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_001004
ticket_0306,acct_0116,user_000306,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_001005
ticket_0307,acct_0117,user_000307,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_001006
ticket_0308,acct_0118,user_000308,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_001007
ticket_0309,acct_0119,user_000309,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_001008
ticket_0310,acct_0120,user_000310,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_001009
ticket_0311,acct_0121,user_000311,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_001010
ticket_0312,acct_0122,user_000312,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_001011
ticket_0313,acct_0123,user_000313,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_001012
ticket_0314,acct_0124,user_000314,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_001013
ticket_0315,acct_0125,user_000315,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_001014
ticket_0316,acct_0126,user_000316,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_001015
ticket_0317,acct_0127,user_000317,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_001016
ticket_0318,acct_0128,user_000318,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_001017
ticket_0319,acct_0129,user_000319,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_001018
ticket_0320,acct_0130,user_000320,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_001019
ticket_0321,acct_0131,user_000321,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_001020
ticket_0322,acct_0132,user_000322,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_001021
ticket_0323,acct_0133,user_000323,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_001022
ticket_0324,acct_0134,user_000324,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_001023
ticket_0325,acct_0135,user_000325,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_001024
ticket_0326,acct_0136,user_000326,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_001025
ticket_0327,acct_0137,user_000327,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_001026
ticket_0328,acct_0138,user_000328,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_001027
ticket_0329,acct_0139,user_000329,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_001028
ticket_0330,acct_0140,user_000330,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_001029
ticket_0331,acct_0141,user_000331,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_001030
ticket_0332,acct_0142,user_000332,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_001031
ticket_0333,acct_0143,user_000333,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_001032
ticket_0334,acct_0144,user_000334,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_001033
ticket_0335,acct_0145,user_000335,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_001034
ticket_0336,acct_0146,user_000336,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_001035
ticket_0337,acct_0147,user_000337,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_001036
ticket_0338,acct_0148,user_000338,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_001037
ticket_0339,acct_0149,user_000339,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_001038
ticket_0340,acct_0150,user_000340,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_001039
ticket_0341,acct_0151,user_000341,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_001040
ticket_0342,acct_0152,user_000342,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_001041
ticket_0343,acct_0153,user_000343,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_001042
ticket_0344,acct_0154,user_000344,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_001043
ticket_0345,acct_0155,user_000345,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_001044
ticket_0346,acct_0156,user_000346,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_001045
ticket_0347,acct_0157,user_000347,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_001046
ticket_0348,acct_0158,user_000348,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_001047
ticket_0349,acct_0159,user_000349,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_001048
ticket_0350,acct_0160,user_000350,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_001049
ticket_0351,acct_0161,user_000351,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_001050
ticket_0352,acct_0162,user_000352,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_001051
ticket_0353,acct_0163,user_000353,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_001052
ticket_0354,acct_0164,user_000354,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_001053
ticket_0355,acct_0165,user_000355,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_001054
ticket_0356,acct_0166,user_000356,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_001055
ticket_0357,acct_0167,user_000357,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_001056
ticket_0358,acct_0168,user_000358,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_001057
ticket_0359,acct_0169,user_000359,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_001058
ticket_0360,acct_0170,user_000360,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_001059
ticket_0361,acct_0171,user_000361,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_001060
ticket_0362,acct_0172,user_000362,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_001061
ticket_0363,acct_0173,user_000363,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_001062
ticket_0364,acct_0174,user_000364,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_001063
ticket_0365,acct_0175,user_000365,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_001064
ticket_0366,acct_0176,user_000366,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_001065
ticket_0367,acct_0177,user_000367,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_001066
ticket_0368,acct_0178,user_000368,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_001067
ticket_0369,acct_0179,user_000369,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_001068
ticket_0370,acct_0180,user_000370,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_001069
ticket_0371,acct_0181,user_000371,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_001070
ticket_0372,acct_0182,user_000372,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_001071
ticket_0373,acct_0183,user_000373,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_001072
ticket_0374,acct_0184,user_000374,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_001073
ticket_0375,acct_0185,user_000375,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_001074
ticket_0376,acct_0186,user_000376,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_001075
ticket_0377,acct_0187,user_000377,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_001076
ticket_0378,acct_0188,user_000378,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_001077
ticket_0379,acct_0189,user_000379,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_001078
ticket_0380,acct_0190,user_000380,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_001079
ticket_0381,acct_0191,user_000381,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_001080
ticket_0382,acct_0192,user_000382,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_001081
ticket_0383,acct_0193,user_000383,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_001082
ticket_0384,acct_0194,user_000384,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_001083
ticket_0385,acct_0195,user_000385,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_001084
ticket_0386,acct_0196,user_000386,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_001085
ticket_0387,acct_0197,user_000387,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_001086
ticket_0388,acct_0198,user_000388,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_001087
ticket_0389,acct_0199,user_000389,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_001088
ticket_0390,acct_0010,user_000390,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_001089
ticket_0391,acct_0011,user_000391,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_001090
ticket_0392,acct_0012,user_000392,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_001091
ticket_0393,acct_0013,user_000393,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_001092
ticket_0394,acct_0014,user_000394,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_001093
ticket_0395,acct_0015,user_000395,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_001094
ticket_0396,acct_0016,user_000396,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_001095
ticket_0397,acct_0017,user_000397,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_001096
ticket_0398,acct_0018,user_000398,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_001097
ticket_0399,acct_0019,user_000399,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_001098
ticket_0400,acct_0020,user_000400,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_001099
ticket_0401,acct_0021,user_000401,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_000900
ticket_0402,acct_0022,user_000402,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_000901
ticket_0403,acct_0023,user_000403,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_000902
ticket_0404,acct_0024,user_000404,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_000903
ticket_0405,acct_0025,user_000405,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_000904
ticket_0406,acct_0026,user_000406,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_000905
ticket_0407,acct_0027,user_000407,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_000906
ticket_0408,acct_0028,user_000408,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_000907
ticket_0409,acct_0029,user_000409,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_000908
ticket_0410,acct_0030,user_000410,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_000909
ticket_0411,acct_0031,user_000411,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_000910
ticket_0412,acct_0032,user_000412,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_000911
ticket_0413,acct_0033,user_000413,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_000912
ticket_0414,acct_0034,user_000414,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_000913
ticket_0415,acct_0035,user_000415,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_000914
ticket_0416,acct_0036,user_000416,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_000915
ticket_0417,acct_0037,user_000417,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_000916
ticket_0418,acct_0038,user_000418,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_000917
ticket_0419,acct_0039,user_000419,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_000918
ticket_0420,acct_0040,user_000420,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_000919
ticket_0421,acct_0041,user_000421,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_000920
ticket_0422,acct_0042,user_000422,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_000921
ticket_0423,acct_0043,user_000423,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_000922
ticket_0424,acct_0044,user_000424,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_000923
ticket_0425,acct_0045,user_000425,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_000924
ticket_0426,acct_0046,user_000426,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_000925
ticket_0427,acct_0047,user_000427,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_000926
ticket_0428,acct_0048,user_000428,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_000927
ticket_0429,acct_0049,user_000429,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_000928
ticket_0430,acct_0050,user_000430,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_000929
ticket_0431,acct_0051,user_000431,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_000930
ticket_0432,acct_0052,user_000432,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_000931
ticket_0433,acct_0053,user_000433,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_000932
ticket_0434,acct_0054,user_000434,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_000933
ticket_0435,acct_0055,user_000435,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_000934
ticket_0436,acct_0056,user_000436,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_000935
ticket_0437,acct_0057,user_000437,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_000936
ticket_0438,acct_0058,user_000438,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_000937
ticket_0439,acct_0059,user_000439,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_000938
ticket_0440,acct_0060,user_000440,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_000939
ticket_0441,acct_0061,user_000441,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_000940
ticket_0442,acct_0062,user_000442,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_000941
ticket_0443,acct_0063,user_000443,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_000942
ticket_0444,acct_0064,user_000444,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_000943
ticket_0445,acct_0065,user_000445,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_000944
ticket_0446,acct_0066,user_000446,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_000945
ticket_0447,acct_0067,user_000447,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_000946
ticket_0448,acct_0068,user_000448,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_000947
ticket_0449,acct_0069,user_000449,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_000948
ticket_0450,acct_0070,user_000450,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_000949
ticket_0451,acct_0071,user_000451,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_000950
ticket_0452,acct_0072,user_000452,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_000951
ticket_0453,acct_0073,user_000453,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_000952
ticket_0454,acct_0074,user_000454,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_000953
ticket_0455,acct_0075,user_000455,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_000954
ticket_0456,acct_0076,user_000456,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_000955
ticket_0457,acct_0077,user_000457,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_000956
ticket_0458,acct_0078,user_000458,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_000957
ticket_0459,acct_0079,user_000459,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_000958
ticket_0460,acct_0080,user_000460,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_000959
ticket_0461,acct_0081,user_000461,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_000960
ticket_0462,acct_0082,user_000462,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_000961
ticket_0463,acct_0083,user_000463,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_000962
ticket_0464,acct_0084,user_000464,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_000963
ticket_0465,acct_0085,user_000465,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_000964
ticket_0466,acct_0086,user_000466,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_000965
ticket_0467,acct_0087,user_000467,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_000966
ticket_0468,acct_0088,user_000468,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_000967
ticket_0469,acct_0089,user_000469,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_000968
ticket_0470,acct_0090,user_000470,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_000969
ticket_0471,acct_0091,user_000471,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_000970
ticket_0472,acct_0092,user_000472,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_000971
ticket_0473,acct_0093,user_000473,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_000972
ticket_0474,acct_0094,user_000474,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_000973
ticket_0475,acct_0095,user_000475,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_000974
ticket_0476,acct_0096,user_000476,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_000975
ticket_0477,acct_0097,user_000477,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_000976
ticket_0478,acct_0098,user_000478,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_000977
ticket_0479,acct_0099,user_000479,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_000978
ticket_0480,acct_0100,user_000480,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_000979
ticket_0481,acct_0101,user_000481,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_000980
ticket_0482,acct_0102,user_000482,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_000981
ticket_0483,acct_0103,user_000483,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_000982
ticket_0484,acct_0104,user_000484,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_000983
ticket_0485,acct_0105,user_000485,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_000984
ticket_0486,acct_0106,user_000486,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_000985
ticket_0487,acct_0107,user_000487,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_000986
ticket_0488,acct_0108,user_000488,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_000987
ticket_0489,acct_0109,user_000489,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_000988
ticket_0490,acct_0110,user_000490,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_000989
ticket_0491,acct_0111,user_000491,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_000990
ticket_0492,acct_0112,user_000492,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_000991
ticket_0493,acct_0113,user_000493,low,approval_routing,open,2026-02-17T17:00:00Z,2026-02-28T17:00:00Z,user_000992
ticket_0494,acct_0114,user_000494,medium,supplier_onboarding,pending,2026-02-18T17:00:00Z,2026-02-28T17:00:00Z,user_000993
ticket_0495,acct_0115,user_000495,high,billing,solved,2026-02-19T17:00:00Z,2026-02-28T17:00:00Z,user_000994
ticket_0496,acct_0116,user_000496,critical,permissions,closed,2026-02-20T17:00:00Z,2026-02-28T17:00:00Z,user_000995
ticket_0497,acct_0117,user_000497,low,approval_routing,open,2026-02-21T17:00:00Z,2026-02-28T17:00:00Z,user_000996
ticket_0498,acct_0118,user_000498,medium,supplier_onboarding,pending,2026-02-22T17:00:00Z,2026-02-28T17:00:00Z,user_000997
ticket_0499,acct_0119,user_000499,high,billing,solved,2026-02-23T17:00:00Z,2026-02-28T17:00:00Z,user_000998
ticket_0500,acct_0120,user_000500,critical,permissions,closed,2026-02-24T17:00:00Z,2026-02-28T17:00:00Z,user_000999
ticket_0501,acct_0121,user_000501,low,approval_routing,open,2026-02-25T17:00:00Z,2026-02-28T17:00:00Z,user_001000
ticket_0502,acct_0122,user_000502,medium,supplier_onboarding,pending,2026-02-26T17:00:00Z,2026-02-28T17:00:00Z,user_001001
ticket_0503,acct_0123,user_000503,high,billing,solved,2026-02-27T17:00:00Z,2026-02-28T17:00:00Z,user_001002
ticket_0504,acct_0124,user_000504,critical,permissions,closed,2026-02-28T17:00:00Z,2026-02-28T17:00:00Z,user_001003
ticket_0505,acct_0125,user_000505,low,approval_routing,open,2026-02-01T17:00:00Z,2026-02-28T17:00:00Z,user_001004
ticket_0506,acct_0126,user_000506,medium,supplier_onboarding,pending,2026-02-02T17:00:00Z,2026-02-28T17:00:00Z,user_001005
ticket_0507,acct_0127,user_000507,high,billing,solved,2026-02-03T17:00:00Z,2026-02-28T17:00:00Z,user_001006
ticket_0508,acct_0128,user_000508,critical,permissions,closed,2026-02-04T17:00:00Z,2026-02-28T17:00:00Z,user_001007
ticket_0509,acct_0129,user_000509,low,approval_routing,open,2026-02-05T17:00:00Z,2026-02-28T17:00:00Z,user_001008
ticket_0510,acct_0130,user_000510,medium,supplier_onboarding,pending,2026-02-06T17:00:00Z,2026-02-28T17:00:00Z,user_001009
ticket_0511,acct_0131,user_000511,high,billing,solved,2026-02-07T17:00:00Z,2026-02-28T17:00:00Z,user_001010
ticket_0512,acct_0132,user_000512,critical,permissions,closed,2026-02-08T17:00:00Z,2026-02-28T17:00:00Z,user_001011
ticket_0513,acct_0133,user_000513,low,approval_routing,open,2026-02-09T17:00:00Z,2026-02-28T17:00:00Z,user_001012
ticket_0514,acct_0134,user_000514,medium,supplier_onboarding,pending,2026-02-10T17:00:00Z,2026-02-28T17:00:00Z,user_001013
ticket_0515,acct_0135,user_000515,high,billing,solved,2026-02-11T17:00:00Z,2026-02-28T17:00:00Z,user_001014
ticket_0516,acct_0136,user_000516,critical,permissions,closed,2026-02-12T17:00:00Z,2026-02-28T17:00:00Z,user_001015
ticket_0517,acct_0137,user_000517,low,approval_routing,open,2026-02-13T17:00:00Z,2026-02-28T17:00:00Z,user_001016
ticket_0518,acct_0138,user_000518,medium,supplier_onboarding,pending,2026-02-14T17:00:00Z,2026-02-28T17:00:00Z,user_001017
ticket_0519,acct_0139,user_000519,high,billing,solved,2026-02-15T17:00:00Z,2026-02-28T17:00:00Z,user_001018
ticket_0520,acct_0140,user_000520,critical,permissions,closed,2026-02-16T17:00:00Z,2026-02-28T17:00:00Z,user_001019
1 support_ticket_id account_id requester_user_id severity category status created_at resolved_at owner_user_id
2 ticket_0001 acct_0001 user_000287 critical approval_routing open 2026-03-10T17:00:00Z 2099-12-31T00:00:00Z user_000901
3 ticket_0002 acct_0002 user_000288 high approval_routing open 2026-03-11T17:00:00Z 2099-12-31T00:00:00Z user_000902
4 ticket_0003 acct_0003 user_000289 critical approval_routing open 2026-03-12T17:00:00Z 2099-12-31T00:00:00Z user_000903
5 ticket_0004 acct_0004 user_000290 high approval_routing open 2026-03-13T17:00:00Z 2099-12-31T00:00:00Z user_000904
6 ticket_0005 acct_0005 user_000291 critical approval_routing open 2026-03-14T17:00:00Z 2099-12-31T00:00:00Z user_000905
7 ticket_0006 acct_0006 user_000292 high approval_routing open 2026-03-15T17:00:00Z 2099-12-31T00:00:00Z user_000906
8 ticket_0007 acct_0007 user_000293 critical approval_routing open 2026-03-16T17:00:00Z 2099-12-31T00:00:00Z user_000907
9 ticket_0008 acct_0008 user_000294 high approval_routing open 2026-03-17T17:00:00Z 2099-12-31T00:00:00Z user_000908
10 ticket_0009 acct_0009 user_000295 critical approval_routing open 2026-03-18T17:00:00Z 2099-12-31T00:00:00Z user_000909
11 ticket_0010 acct_0010 user_000010 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_000909
12 ticket_0011 acct_0011 user_000011 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_000910
13 ticket_0012 acct_0012 user_000012 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_000911
14 ticket_0013 acct_0013 user_000013 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_000912
15 ticket_0014 acct_0014 user_000014 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_000913
16 ticket_0015 acct_0015 user_000015 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_000914
17 ticket_0016 acct_0016 user_000016 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_000915
18 ticket_0017 acct_0017 user_000017 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_000916
19 ticket_0018 acct_0018 user_000018 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_000917
20 ticket_0019 acct_0019 user_000019 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_000918
21 ticket_0020 acct_0020 user_000020 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_000919
22 ticket_0021 acct_0021 user_000021 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_000920
23 ticket_0022 acct_0022 user_000022 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_000921
24 ticket_0023 acct_0023 user_000023 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_000922
25 ticket_0024 acct_0024 user_000024 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_000923
26 ticket_0025 acct_0025 user_000025 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_000924
27 ticket_0026 acct_0026 user_000026 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_000925
28 ticket_0027 acct_0027 user_000027 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_000926
29 ticket_0028 acct_0028 user_000028 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_000927
30 ticket_0029 acct_0029 user_000029 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_000928
31 ticket_0030 acct_0030 user_000030 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_000929
32 ticket_0031 acct_0031 user_000031 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_000930
33 ticket_0032 acct_0032 user_000032 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_000931
34 ticket_0033 acct_0033 user_000033 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_000932
35 ticket_0034 acct_0034 user_000034 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_000933
36 ticket_0035 acct_0035 user_000035 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_000934
37 ticket_0036 acct_0036 user_000036 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_000935
38 ticket_0037 acct_0037 user_000037 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_000936
39 ticket_0038 acct_0038 user_000038 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_000937
40 ticket_0039 acct_0039 user_000039 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_000938
41 ticket_0040 acct_0040 user_000040 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_000939
42 ticket_0041 acct_0041 user_000041 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_000940
43 ticket_0042 acct_0042 user_000042 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_000941
44 ticket_0043 acct_0043 user_000043 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_000942
45 ticket_0044 acct_0044 user_000044 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_000943
46 ticket_0045 acct_0045 user_000045 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_000944
47 ticket_0046 acct_0046 user_000046 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_000945
48 ticket_0047 acct_0047 user_000047 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_000946
49 ticket_0048 acct_0048 user_000048 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_000947
50 ticket_0049 acct_0049 user_000049 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_000948
51 ticket_0050 acct_0050 user_000050 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_000949
52 ticket_0051 acct_0051 user_000051 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_000950
53 ticket_0052 acct_0052 user_000052 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_000951
54 ticket_0053 acct_0053 user_000053 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_000952
55 ticket_0054 acct_0054 user_000054 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_000953
56 ticket_0055 acct_0055 user_000055 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_000954
57 ticket_0056 acct_0056 user_000056 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_000955
58 ticket_0057 acct_0057 user_000057 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_000956
59 ticket_0058 acct_0058 user_000058 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_000957
60 ticket_0059 acct_0059 user_000059 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_000958
61 ticket_0060 acct_0060 user_000060 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_000959
62 ticket_0061 acct_0061 user_000061 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_000960
63 ticket_0062 acct_0062 user_000062 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_000961
64 ticket_0063 acct_0063 user_000063 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_000962
65 ticket_0064 acct_0064 user_000064 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_000963
66 ticket_0065 acct_0065 user_000065 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_000964
67 ticket_0066 acct_0066 user_000066 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_000965
68 ticket_0067 acct_0067 user_000067 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_000966
69 ticket_0068 acct_0068 user_000068 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_000967
70 ticket_0069 acct_0069 user_000069 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_000968
71 ticket_0070 acct_0070 user_000070 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_000969
72 ticket_0071 acct_0071 user_000071 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_000970
73 ticket_0072 acct_0072 user_000072 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_000971
74 ticket_0073 acct_0073 user_000073 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_000972
75 ticket_0074 acct_0074 user_000074 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_000973
76 ticket_0075 acct_0075 user_000075 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_000974
77 ticket_0076 acct_0076 user_000076 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_000975
78 ticket_0077 acct_0077 user_000077 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_000976
79 ticket_0078 acct_0078 user_000078 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_000977
80 ticket_0079 acct_0079 user_000079 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_000978
81 ticket_0080 acct_0080 user_000080 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_000979
82 ticket_0081 acct_0081 user_000081 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_000980
83 ticket_0082 acct_0082 user_000082 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_000981
84 ticket_0083 acct_0083 user_000083 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_000982
85 ticket_0084 acct_0084 user_000084 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_000983
86 ticket_0085 acct_0085 user_000085 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_000984
87 ticket_0086 acct_0086 user_000086 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_000985
88 ticket_0087 acct_0087 user_000087 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_000986
89 ticket_0088 acct_0088 user_000088 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_000987
90 ticket_0089 acct_0089 user_000089 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_000988
91 ticket_0090 acct_0090 user_000090 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_000989
92 ticket_0091 acct_0091 user_000091 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_000990
93 ticket_0092 acct_0092 user_000092 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_000991
94 ticket_0093 acct_0093 user_000093 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_000992
95 ticket_0094 acct_0094 user_000094 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_000993
96 ticket_0095 acct_0095 user_000095 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_000994
97 ticket_0096 acct_0096 user_000096 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_000995
98 ticket_0097 acct_0097 user_000097 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_000996
99 ticket_0098 acct_0098 user_000098 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_000997
100 ticket_0099 acct_0099 user_000099 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_000998
101 ticket_0100 acct_0100 user_000100 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_000999
102 ticket_0101 acct_0101 user_000101 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_001000
103 ticket_0102 acct_0102 user_000102 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_001001
104 ticket_0103 acct_0103 user_000103 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_001002
105 ticket_0104 acct_0104 user_000104 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_001003
106 ticket_0105 acct_0105 user_000105 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_001004
107 ticket_0106 acct_0106 user_000106 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_001005
108 ticket_0107 acct_0107 user_000107 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_001006
109 ticket_0108 acct_0108 user_000108 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_001007
110 ticket_0109 acct_0109 user_000109 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_001008
111 ticket_0110 acct_0110 user_000110 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_001009
112 ticket_0111 acct_0111 user_000111 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_001010
113 ticket_0112 acct_0112 user_000112 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_001011
114 ticket_0113 acct_0113 user_000113 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_001012
115 ticket_0114 acct_0114 user_000114 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_001013
116 ticket_0115 acct_0115 user_000115 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_001014
117 ticket_0116 acct_0116 user_000116 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_001015
118 ticket_0117 acct_0117 user_000117 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_001016
119 ticket_0118 acct_0118 user_000118 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_001017
120 ticket_0119 acct_0119 user_000119 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_001018
121 ticket_0120 acct_0120 user_000120 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_001019
122 ticket_0121 acct_0121 user_000121 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_001020
123 ticket_0122 acct_0122 user_000122 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_001021
124 ticket_0123 acct_0123 user_000123 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_001022
125 ticket_0124 acct_0124 user_000124 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_001023
126 ticket_0125 acct_0125 user_000125 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_001024
127 ticket_0126 acct_0126 user_000126 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_001025
128 ticket_0127 acct_0127 user_000127 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_001026
129 ticket_0128 acct_0128 user_000128 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_001027
130 ticket_0129 acct_0129 user_000129 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_001028
131 ticket_0130 acct_0130 user_000130 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_001029
132 ticket_0131 acct_0131 user_000131 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_001030
133 ticket_0132 acct_0132 user_000132 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_001031
134 ticket_0133 acct_0133 user_000133 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_001032
135 ticket_0134 acct_0134 user_000134 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_001033
136 ticket_0135 acct_0135 user_000135 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_001034
137 ticket_0136 acct_0136 user_000136 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_001035
138 ticket_0137 acct_0137 user_000137 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_001036
139 ticket_0138 acct_0138 user_000138 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_001037
140 ticket_0139 acct_0139 user_000139 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_001038
141 ticket_0140 acct_0140 user_000140 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_001039
142 ticket_0141 acct_0141 user_000141 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_001040
143 ticket_0142 acct_0142 user_000142 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_001041
144 ticket_0143 acct_0143 user_000143 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_001042
145 ticket_0144 acct_0144 user_000144 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_001043
146 ticket_0145 acct_0145 user_000145 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_001044
147 ticket_0146 acct_0146 user_000146 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_001045
148 ticket_0147 acct_0147 user_000147 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_001046
149 ticket_0148 acct_0148 user_000148 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_001047
150 ticket_0149 acct_0149 user_000149 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_001048
151 ticket_0150 acct_0150 user_000150 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_001049
152 ticket_0151 acct_0151 user_000151 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_001050
153 ticket_0152 acct_0152 user_000152 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_001051
154 ticket_0153 acct_0153 user_000153 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_001052
155 ticket_0154 acct_0154 user_000154 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_001053
156 ticket_0155 acct_0155 user_000155 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_001054
157 ticket_0156 acct_0156 user_000156 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_001055
158 ticket_0157 acct_0157 user_000157 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_001056
159 ticket_0158 acct_0158 user_000158 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_001057
160 ticket_0159 acct_0159 user_000159 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_001058
161 ticket_0160 acct_0160 user_000160 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_001059
162 ticket_0161 acct_0161 user_000161 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_001060
163 ticket_0162 acct_0162 user_000162 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_001061
164 ticket_0163 acct_0163 user_000163 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_001062
165 ticket_0164 acct_0164 user_000164 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_001063
166 ticket_0165 acct_0165 user_000165 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_001064
167 ticket_0166 acct_0166 user_000166 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_001065
168 ticket_0167 acct_0167 user_000167 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_001066
169 ticket_0168 acct_0168 user_000168 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_001067
170 ticket_0169 acct_0169 user_000169 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_001068
171 ticket_0170 acct_0170 user_000170 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_001069
172 ticket_0171 acct_0171 user_000171 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_001070
173 ticket_0172 acct_0172 user_000172 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_001071
174 ticket_0173 acct_0173 user_000173 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_001072
175 ticket_0174 acct_0174 user_000174 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_001073
176 ticket_0175 acct_0175 user_000175 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_001074
177 ticket_0176 acct_0176 user_000176 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_001075
178 ticket_0177 acct_0177 user_000177 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_001076
179 ticket_0178 acct_0178 user_000178 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_001077
180 ticket_0179 acct_0179 user_000179 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_001078
181 ticket_0180 acct_0180 user_000180 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_001079
182 ticket_0181 acct_0181 user_000181 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_001080
183 ticket_0182 acct_0182 user_000182 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_001081
184 ticket_0183 acct_0183 user_000183 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_001082
185 ticket_0184 acct_0184 user_000184 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_001083
186 ticket_0185 acct_0185 user_000185 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_001084
187 ticket_0186 acct_0186 user_000186 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_001085
188 ticket_0187 acct_0187 user_000187 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_001086
189 ticket_0188 acct_0188 user_000188 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_001087
190 ticket_0189 acct_0189 user_000189 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_001088
191 ticket_0190 acct_0190 user_000190 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_001089
192 ticket_0191 acct_0191 user_000191 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_001090
193 ticket_0192 acct_0192 user_000192 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_001091
194 ticket_0193 acct_0193 user_000193 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_001092
195 ticket_0194 acct_0194 user_000194 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_001093
196 ticket_0195 acct_0195 user_000195 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_001094
197 ticket_0196 acct_0196 user_000196 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_001095
198 ticket_0197 acct_0197 user_000197 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_001096
199 ticket_0198 acct_0198 user_000198 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_001097
200 ticket_0199 acct_0199 user_000199 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_001098
201 ticket_0200 acct_0010 user_000200 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_001099
202 ticket_0201 acct_0011 user_000201 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_000900
203 ticket_0202 acct_0012 user_000202 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_000901
204 ticket_0203 acct_0013 user_000203 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_000902
205 ticket_0204 acct_0014 user_000204 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_000903
206 ticket_0205 acct_0015 user_000205 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_000904
207 ticket_0206 acct_0016 user_000206 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_000905
208 ticket_0207 acct_0017 user_000207 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_000906
209 ticket_0208 acct_0018 user_000208 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_000907
210 ticket_0209 acct_0019 user_000209 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_000908
211 ticket_0210 acct_0020 user_000210 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_000909
212 ticket_0211 acct_0021 user_000211 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_000910
213 ticket_0212 acct_0022 user_000212 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_000911
214 ticket_0213 acct_0023 user_000213 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_000912
215 ticket_0214 acct_0024 user_000214 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_000913
216 ticket_0215 acct_0025 user_000215 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_000914
217 ticket_0216 acct_0026 user_000216 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_000915
218 ticket_0217 acct_0027 user_000217 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_000916
219 ticket_0218 acct_0028 user_000218 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_000917
220 ticket_0219 acct_0029 user_000219 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_000918
221 ticket_0220 acct_0030 user_000220 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_000919
222 ticket_0221 acct_0031 user_000221 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_000920
223 ticket_0222 acct_0032 user_000222 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_000921
224 ticket_0223 acct_0033 user_000223 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_000922
225 ticket_0224 acct_0034 user_000224 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_000923
226 ticket_0225 acct_0035 user_000225 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_000924
227 ticket_0226 acct_0036 user_000226 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_000925
228 ticket_0227 acct_0037 user_000227 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_000926
229 ticket_0228 acct_0038 user_000228 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_000927
230 ticket_0229 acct_0039 user_000229 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_000928
231 ticket_0230 acct_0040 user_000230 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_000929
232 ticket_0231 acct_0041 user_000231 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_000930
233 ticket_0232 acct_0042 user_000232 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_000931
234 ticket_0233 acct_0043 user_000233 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_000932
235 ticket_0234 acct_0044 user_000234 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_000933
236 ticket_0235 acct_0045 user_000235 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_000934
237 ticket_0236 acct_0046 user_000236 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_000935
238 ticket_0237 acct_0047 user_000237 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_000936
239 ticket_0238 acct_0048 user_000238 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_000937
240 ticket_0239 acct_0049 user_000239 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_000938
241 ticket_0240 acct_0050 user_000240 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_000939
242 ticket_0241 acct_0051 user_000241 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_000940
243 ticket_0242 acct_0052 user_000242 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_000941
244 ticket_0243 acct_0053 user_000243 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_000942
245 ticket_0244 acct_0054 user_000244 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_000943
246 ticket_0245 acct_0055 user_000245 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_000944
247 ticket_0246 acct_0056 user_000246 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_000945
248 ticket_0247 acct_0057 user_000247 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_000946
249 ticket_0248 acct_0058 user_000248 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_000947
250 ticket_0249 acct_0059 user_000249 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_000948
251 ticket_0250 acct_0060 user_000250 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_000949
252 ticket_0251 acct_0061 user_000251 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_000950
253 ticket_0252 acct_0062 user_000252 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_000951
254 ticket_0253 acct_0063 user_000253 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_000952
255 ticket_0254 acct_0064 user_000254 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_000953
256 ticket_0255 acct_0065 user_000255 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_000954
257 ticket_0256 acct_0066 user_000256 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_000955
258 ticket_0257 acct_0067 user_000257 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_000956
259 ticket_0258 acct_0068 user_000258 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_000957
260 ticket_0259 acct_0069 user_000259 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_000958
261 ticket_0260 acct_0070 user_000260 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_000959
262 ticket_0261 acct_0071 user_000261 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_000960
263 ticket_0262 acct_0072 user_000262 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_000961
264 ticket_0263 acct_0073 user_000263 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_000962
265 ticket_0264 acct_0074 user_000264 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_000963
266 ticket_0265 acct_0075 user_000265 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_000964
267 ticket_0266 acct_0076 user_000266 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_000965
268 ticket_0267 acct_0077 user_000267 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_000966
269 ticket_0268 acct_0078 user_000268 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_000967
270 ticket_0269 acct_0079 user_000269 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_000968
271 ticket_0270 acct_0080 user_000270 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_000969
272 ticket_0271 acct_0081 user_000271 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_000970
273 ticket_0272 acct_0082 user_000272 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_000971
274 ticket_0273 acct_0083 user_000273 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_000972
275 ticket_0274 acct_0084 user_000274 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_000973
276 ticket_0275 acct_0085 user_000275 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_000974
277 ticket_0276 acct_0086 user_000276 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_000975
278 ticket_0277 acct_0087 user_000277 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_000976
279 ticket_0278 acct_0088 user_000278 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_000977
280 ticket_0279 acct_0089 user_000279 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_000978
281 ticket_0280 acct_0090 user_000280 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_000979
282 ticket_0281 acct_0091 user_000281 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_000980
283 ticket_0282 acct_0092 user_000282 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_000981
284 ticket_0283 acct_0093 user_000283 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_000982
285 ticket_0284 acct_0094 user_000284 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_000983
286 ticket_0285 acct_0095 user_000285 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_000984
287 ticket_0286 acct_0096 user_000286 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_000985
288 ticket_0287 acct_0097 user_000287 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_000986
289 ticket_0288 acct_0098 user_000288 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_000987
290 ticket_0289 acct_0099 user_000289 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_000988
291 ticket_0290 acct_0100 user_000290 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_000989
292 ticket_0291 acct_0101 user_000291 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_000990
293 ticket_0292 acct_0102 user_000292 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_000991
294 ticket_0293 acct_0103 user_000293 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_000992
295 ticket_0294 acct_0104 user_000294 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_000993
296 ticket_0295 acct_0105 user_000295 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_000994
297 ticket_0296 acct_0106 user_000296 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_000995
298 ticket_0297 acct_0107 user_000297 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_000996
299 ticket_0298 acct_0108 user_000298 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_000997
300 ticket_0299 acct_0109 user_000299 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_000998
301 ticket_0300 acct_0110 user_000300 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_000999
302 ticket_0301 acct_0111 user_000301 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_001000
303 ticket_0302 acct_0112 user_000302 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_001001
304 ticket_0303 acct_0113 user_000303 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_001002
305 ticket_0304 acct_0114 user_000304 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_001003
306 ticket_0305 acct_0115 user_000305 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_001004
307 ticket_0306 acct_0116 user_000306 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_001005
308 ticket_0307 acct_0117 user_000307 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_001006
309 ticket_0308 acct_0118 user_000308 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_001007
310 ticket_0309 acct_0119 user_000309 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_001008
311 ticket_0310 acct_0120 user_000310 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_001009
312 ticket_0311 acct_0121 user_000311 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_001010
313 ticket_0312 acct_0122 user_000312 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_001011
314 ticket_0313 acct_0123 user_000313 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_001012
315 ticket_0314 acct_0124 user_000314 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_001013
316 ticket_0315 acct_0125 user_000315 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_001014
317 ticket_0316 acct_0126 user_000316 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_001015
318 ticket_0317 acct_0127 user_000317 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_001016
319 ticket_0318 acct_0128 user_000318 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_001017
320 ticket_0319 acct_0129 user_000319 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_001018
321 ticket_0320 acct_0130 user_000320 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_001019
322 ticket_0321 acct_0131 user_000321 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_001020
323 ticket_0322 acct_0132 user_000322 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_001021
324 ticket_0323 acct_0133 user_000323 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_001022
325 ticket_0324 acct_0134 user_000324 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_001023
326 ticket_0325 acct_0135 user_000325 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_001024
327 ticket_0326 acct_0136 user_000326 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_001025
328 ticket_0327 acct_0137 user_000327 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_001026
329 ticket_0328 acct_0138 user_000328 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_001027
330 ticket_0329 acct_0139 user_000329 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_001028
331 ticket_0330 acct_0140 user_000330 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_001029
332 ticket_0331 acct_0141 user_000331 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_001030
333 ticket_0332 acct_0142 user_000332 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_001031
334 ticket_0333 acct_0143 user_000333 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_001032
335 ticket_0334 acct_0144 user_000334 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_001033
336 ticket_0335 acct_0145 user_000335 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_001034
337 ticket_0336 acct_0146 user_000336 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_001035
338 ticket_0337 acct_0147 user_000337 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_001036
339 ticket_0338 acct_0148 user_000338 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_001037
340 ticket_0339 acct_0149 user_000339 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_001038
341 ticket_0340 acct_0150 user_000340 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_001039
342 ticket_0341 acct_0151 user_000341 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_001040
343 ticket_0342 acct_0152 user_000342 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_001041
344 ticket_0343 acct_0153 user_000343 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_001042
345 ticket_0344 acct_0154 user_000344 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_001043
346 ticket_0345 acct_0155 user_000345 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_001044
347 ticket_0346 acct_0156 user_000346 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_001045
348 ticket_0347 acct_0157 user_000347 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_001046
349 ticket_0348 acct_0158 user_000348 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_001047
350 ticket_0349 acct_0159 user_000349 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_001048
351 ticket_0350 acct_0160 user_000350 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_001049
352 ticket_0351 acct_0161 user_000351 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_001050
353 ticket_0352 acct_0162 user_000352 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_001051
354 ticket_0353 acct_0163 user_000353 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_001052
355 ticket_0354 acct_0164 user_000354 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_001053
356 ticket_0355 acct_0165 user_000355 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_001054
357 ticket_0356 acct_0166 user_000356 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_001055
358 ticket_0357 acct_0167 user_000357 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_001056
359 ticket_0358 acct_0168 user_000358 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_001057
360 ticket_0359 acct_0169 user_000359 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_001058
361 ticket_0360 acct_0170 user_000360 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_001059
362 ticket_0361 acct_0171 user_000361 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_001060
363 ticket_0362 acct_0172 user_000362 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_001061
364 ticket_0363 acct_0173 user_000363 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_001062
365 ticket_0364 acct_0174 user_000364 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_001063
366 ticket_0365 acct_0175 user_000365 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_001064
367 ticket_0366 acct_0176 user_000366 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_001065
368 ticket_0367 acct_0177 user_000367 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_001066
369 ticket_0368 acct_0178 user_000368 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_001067
370 ticket_0369 acct_0179 user_000369 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_001068
371 ticket_0370 acct_0180 user_000370 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_001069
372 ticket_0371 acct_0181 user_000371 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_001070
373 ticket_0372 acct_0182 user_000372 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_001071
374 ticket_0373 acct_0183 user_000373 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_001072
375 ticket_0374 acct_0184 user_000374 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_001073
376 ticket_0375 acct_0185 user_000375 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_001074
377 ticket_0376 acct_0186 user_000376 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_001075
378 ticket_0377 acct_0187 user_000377 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_001076
379 ticket_0378 acct_0188 user_000378 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_001077
380 ticket_0379 acct_0189 user_000379 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_001078
381 ticket_0380 acct_0190 user_000380 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_001079
382 ticket_0381 acct_0191 user_000381 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_001080
383 ticket_0382 acct_0192 user_000382 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_001081
384 ticket_0383 acct_0193 user_000383 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_001082
385 ticket_0384 acct_0194 user_000384 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_001083
386 ticket_0385 acct_0195 user_000385 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_001084
387 ticket_0386 acct_0196 user_000386 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_001085
388 ticket_0387 acct_0197 user_000387 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_001086
389 ticket_0388 acct_0198 user_000388 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_001087
390 ticket_0389 acct_0199 user_000389 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_001088
391 ticket_0390 acct_0010 user_000390 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_001089
392 ticket_0391 acct_0011 user_000391 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_001090
393 ticket_0392 acct_0012 user_000392 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_001091
394 ticket_0393 acct_0013 user_000393 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_001092
395 ticket_0394 acct_0014 user_000394 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_001093
396 ticket_0395 acct_0015 user_000395 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_001094
397 ticket_0396 acct_0016 user_000396 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_001095
398 ticket_0397 acct_0017 user_000397 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_001096
399 ticket_0398 acct_0018 user_000398 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_001097
400 ticket_0399 acct_0019 user_000399 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_001098
401 ticket_0400 acct_0020 user_000400 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_001099
402 ticket_0401 acct_0021 user_000401 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_000900
403 ticket_0402 acct_0022 user_000402 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_000901
404 ticket_0403 acct_0023 user_000403 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_000902
405 ticket_0404 acct_0024 user_000404 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_000903
406 ticket_0405 acct_0025 user_000405 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_000904
407 ticket_0406 acct_0026 user_000406 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_000905
408 ticket_0407 acct_0027 user_000407 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_000906
409 ticket_0408 acct_0028 user_000408 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_000907
410 ticket_0409 acct_0029 user_000409 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_000908
411 ticket_0410 acct_0030 user_000410 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_000909
412 ticket_0411 acct_0031 user_000411 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_000910
413 ticket_0412 acct_0032 user_000412 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_000911
414 ticket_0413 acct_0033 user_000413 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_000912
415 ticket_0414 acct_0034 user_000414 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_000913
416 ticket_0415 acct_0035 user_000415 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_000914
417 ticket_0416 acct_0036 user_000416 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_000915
418 ticket_0417 acct_0037 user_000417 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_000916
419 ticket_0418 acct_0038 user_000418 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_000917
420 ticket_0419 acct_0039 user_000419 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_000918
421 ticket_0420 acct_0040 user_000420 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_000919
422 ticket_0421 acct_0041 user_000421 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_000920
423 ticket_0422 acct_0042 user_000422 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_000921
424 ticket_0423 acct_0043 user_000423 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_000922
425 ticket_0424 acct_0044 user_000424 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_000923
426 ticket_0425 acct_0045 user_000425 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_000924
427 ticket_0426 acct_0046 user_000426 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_000925
428 ticket_0427 acct_0047 user_000427 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_000926
429 ticket_0428 acct_0048 user_000428 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_000927
430 ticket_0429 acct_0049 user_000429 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_000928
431 ticket_0430 acct_0050 user_000430 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_000929
432 ticket_0431 acct_0051 user_000431 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_000930
433 ticket_0432 acct_0052 user_000432 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_000931
434 ticket_0433 acct_0053 user_000433 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_000932
435 ticket_0434 acct_0054 user_000434 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_000933
436 ticket_0435 acct_0055 user_000435 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_000934
437 ticket_0436 acct_0056 user_000436 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_000935
438 ticket_0437 acct_0057 user_000437 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_000936
439 ticket_0438 acct_0058 user_000438 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_000937
440 ticket_0439 acct_0059 user_000439 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_000938
441 ticket_0440 acct_0060 user_000440 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_000939
442 ticket_0441 acct_0061 user_000441 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_000940
443 ticket_0442 acct_0062 user_000442 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_000941
444 ticket_0443 acct_0063 user_000443 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_000942
445 ticket_0444 acct_0064 user_000444 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_000943
446 ticket_0445 acct_0065 user_000445 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_000944
447 ticket_0446 acct_0066 user_000446 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_000945
448 ticket_0447 acct_0067 user_000447 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_000946
449 ticket_0448 acct_0068 user_000448 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_000947
450 ticket_0449 acct_0069 user_000449 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_000948
451 ticket_0450 acct_0070 user_000450 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_000949
452 ticket_0451 acct_0071 user_000451 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_000950
453 ticket_0452 acct_0072 user_000452 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_000951
454 ticket_0453 acct_0073 user_000453 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_000952
455 ticket_0454 acct_0074 user_000454 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_000953
456 ticket_0455 acct_0075 user_000455 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_000954
457 ticket_0456 acct_0076 user_000456 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_000955
458 ticket_0457 acct_0077 user_000457 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_000956
459 ticket_0458 acct_0078 user_000458 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_000957
460 ticket_0459 acct_0079 user_000459 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_000958
461 ticket_0460 acct_0080 user_000460 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_000959
462 ticket_0461 acct_0081 user_000461 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_000960
463 ticket_0462 acct_0082 user_000462 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_000961
464 ticket_0463 acct_0083 user_000463 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_000962
465 ticket_0464 acct_0084 user_000464 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_000963
466 ticket_0465 acct_0085 user_000465 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_000964
467 ticket_0466 acct_0086 user_000466 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_000965
468 ticket_0467 acct_0087 user_000467 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_000966
469 ticket_0468 acct_0088 user_000468 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_000967
470 ticket_0469 acct_0089 user_000469 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_000968
471 ticket_0470 acct_0090 user_000470 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_000969
472 ticket_0471 acct_0091 user_000471 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_000970
473 ticket_0472 acct_0092 user_000472 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_000971
474 ticket_0473 acct_0093 user_000473 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_000972
475 ticket_0474 acct_0094 user_000474 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_000973
476 ticket_0475 acct_0095 user_000475 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_000974
477 ticket_0476 acct_0096 user_000476 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_000975
478 ticket_0477 acct_0097 user_000477 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_000976
479 ticket_0478 acct_0098 user_000478 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_000977
480 ticket_0479 acct_0099 user_000479 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_000978
481 ticket_0480 acct_0100 user_000480 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_000979
482 ticket_0481 acct_0101 user_000481 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_000980
483 ticket_0482 acct_0102 user_000482 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_000981
484 ticket_0483 acct_0103 user_000483 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_000982
485 ticket_0484 acct_0104 user_000484 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_000983
486 ticket_0485 acct_0105 user_000485 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_000984
487 ticket_0486 acct_0106 user_000486 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_000985
488 ticket_0487 acct_0107 user_000487 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_000986
489 ticket_0488 acct_0108 user_000488 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_000987
490 ticket_0489 acct_0109 user_000489 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_000988
491 ticket_0490 acct_0110 user_000490 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_000989
492 ticket_0491 acct_0111 user_000491 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_000990
493 ticket_0492 acct_0112 user_000492 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_000991
494 ticket_0493 acct_0113 user_000493 low approval_routing open 2026-02-17T17:00:00Z 2026-02-28T17:00:00Z user_000992
495 ticket_0494 acct_0114 user_000494 medium supplier_onboarding pending 2026-02-18T17:00:00Z 2026-02-28T17:00:00Z user_000993
496 ticket_0495 acct_0115 user_000495 high billing solved 2026-02-19T17:00:00Z 2026-02-28T17:00:00Z user_000994
497 ticket_0496 acct_0116 user_000496 critical permissions closed 2026-02-20T17:00:00Z 2026-02-28T17:00:00Z user_000995
498 ticket_0497 acct_0117 user_000497 low approval_routing open 2026-02-21T17:00:00Z 2026-02-28T17:00:00Z user_000996
499 ticket_0498 acct_0118 user_000498 medium supplier_onboarding pending 2026-02-22T17:00:00Z 2026-02-28T17:00:00Z user_000997
500 ticket_0499 acct_0119 user_000499 high billing solved 2026-02-23T17:00:00Z 2026-02-28T17:00:00Z user_000998
501 ticket_0500 acct_0120 user_000500 critical permissions closed 2026-02-24T17:00:00Z 2026-02-28T17:00:00Z user_000999
502 ticket_0501 acct_0121 user_000501 low approval_routing open 2026-02-25T17:00:00Z 2026-02-28T17:00:00Z user_001000
503 ticket_0502 acct_0122 user_000502 medium supplier_onboarding pending 2026-02-26T17:00:00Z 2026-02-28T17:00:00Z user_001001
504 ticket_0503 acct_0123 user_000503 high billing solved 2026-02-27T17:00:00Z 2026-02-28T17:00:00Z user_001002
505 ticket_0504 acct_0124 user_000504 critical permissions closed 2026-02-28T17:00:00Z 2026-02-28T17:00:00Z user_001003
506 ticket_0505 acct_0125 user_000505 low approval_routing open 2026-02-01T17:00:00Z 2026-02-28T17:00:00Z user_001004
507 ticket_0506 acct_0126 user_000506 medium supplier_onboarding pending 2026-02-02T17:00:00Z 2026-02-28T17:00:00Z user_001005
508 ticket_0507 acct_0127 user_000507 high billing solved 2026-02-03T17:00:00Z 2026-02-28T17:00:00Z user_001006
509 ticket_0508 acct_0128 user_000508 critical permissions closed 2026-02-04T17:00:00Z 2026-02-28T17:00:00Z user_001007
510 ticket_0509 acct_0129 user_000509 low approval_routing open 2026-02-05T17:00:00Z 2026-02-28T17:00:00Z user_001008
511 ticket_0510 acct_0130 user_000510 medium supplier_onboarding pending 2026-02-06T17:00:00Z 2026-02-28T17:00:00Z user_001009
512 ticket_0511 acct_0131 user_000511 high billing solved 2026-02-07T17:00:00Z 2026-02-28T17:00:00Z user_001010
513 ticket_0512 acct_0132 user_000512 critical permissions closed 2026-02-08T17:00:00Z 2026-02-28T17:00:00Z user_001011
514 ticket_0513 acct_0133 user_000513 low approval_routing open 2026-02-09T17:00:00Z 2026-02-28T17:00:00Z user_001012
515 ticket_0514 acct_0134 user_000514 medium supplier_onboarding pending 2026-02-10T17:00:00Z 2026-02-28T17:00:00Z user_001013
516 ticket_0515 acct_0135 user_000515 high billing solved 2026-02-11T17:00:00Z 2026-02-28T17:00:00Z user_001014
517 ticket_0516 acct_0136 user_000516 critical permissions closed 2026-02-12T17:00:00Z 2026-02-28T17:00:00Z user_001015
518 ticket_0517 acct_0137 user_000517 low approval_routing open 2026-02-13T17:00:00Z 2026-02-28T17:00:00Z user_001016
519 ticket_0518 acct_0138 user_000518 medium supplier_onboarding pending 2026-02-14T17:00:00Z 2026-02-28T17:00:00Z user_001017
520 ticket_0519 acct_0139 user_000519 high billing solved 2026-02-15T17:00:00Z 2026-02-28T17:00:00Z user_001018
521 ticket_0520 acct_0140 user_000520 critical permissions closed 2026-02-16T17:00:00Z 2026-02-28T17:00:00Z user_001019

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,707 @@
{
"memoryFlowReplaySchemaVersion": 1,
"replay": {
"runId": "demo-seeded-orbit",
"connectionId": "orbit_demo",
"adapter": "live-database",
"status": "done",
"sourceDir": null,
"syncId": "demo-seeded-sync",
"reportId": "demo-seeded-report",
"reportPath": "reports/seeded-demo-report.json",
"errors": [],
"metadata": {
"schemaVersion": 1,
"mode": "seeded",
"origin": "packaged",
"timing": "prebuilt",
"capturedAt": "2026-05-06T00:00:00.000Z",
"sourceReportId": "demo-seeded-report",
"sourceReportPath": "reports/seeded-demo-report.json",
"fallbackReason": null
},
"events": [
{
"type": "source_acquired",
"adapter": "live-database",
"trigger": "demo_seeded",
"fileCount": 8
},
{
"type": "source_acquired",
"adapter": "dbt_descriptions",
"trigger": "demo_seeded",
"fileCount": 6
},
{
"type": "source_acquired",
"adapter": "looker",
"trigger": "demo_seeded",
"fileCount": 7
},
{
"type": "source_acquired",
"adapter": "notion",
"trigger": "demo_seeded",
"fileCount": 8
},
{
"type": "scope_detected",
"fingerprint": "sqlite:orbit-demo"
},
{
"type": "raw_snapshot_written",
"syncId": "demo-seeded-sync",
"rawFileCount": 29
},
{
"type": "diff_computed",
"added": 29,
"modified": 0,
"deleted": 0,
"unchanged": 0
},
{
"type": "chunks_planned",
"chunkCount": 5,
"workUnitCount": 5,
"evictionCount": 0
},
{
"type": "work_unit_started",
"unitKey": "revenue-and-contracts",
"skills": [
"knowledge_capture",
"sl_capture"
],
"stepBudget": 40
},
{
"type": "candidate_action",
"unitKey": "revenue-and-contracts",
"target": "wiki",
"action": "created",
"key": "knowledge/global/arr-contract-first.md"
},
{
"type": "candidate_action",
"unitKey": "revenue-and-contracts",
"target": "wiki",
"action": "created",
"key": "knowledge/global/revenue-gross-to-net.md"
},
{
"type": "candidate_action",
"unitKey": "revenue-and-contracts",
"target": "wiki",
"action": "created",
"key": "knowledge/global/discount-expiration.md"
},
{
"type": "candidate_action",
"unitKey": "revenue-and-contracts",
"target": "sl",
"action": "created",
"key": "orbit_demo.contracts"
},
{
"type": "candidate_action",
"unitKey": "revenue-and-contracts",
"target": "sl",
"action": "created",
"key": "orbit_demo.invoices"
},
{
"type": "candidate_action",
"unitKey": "revenue-and-contracts",
"target": "sl",
"action": "created",
"key": "orbit_demo.arr_movements"
},
{
"type": "work_unit_finished",
"unitKey": "revenue-and-contracts",
"status": "success"
},
{
"type": "work_unit_started",
"unitKey": "retention-and-segments",
"skills": [
"knowledge_capture",
"sl_capture"
],
"stepBudget": 40
},
{
"type": "candidate_action",
"unitKey": "retention-and-segments",
"target": "wiki",
"action": "created",
"key": "knowledge/global/nrr-retention.md"
},
{
"type": "candidate_action",
"unitKey": "retention-and-segments",
"target": "wiki",
"action": "created",
"key": "knowledge/global/segment-classification.md"
},
{
"type": "candidate_action",
"unitKey": "retention-and-segments",
"target": "sl",
"action": "created",
"key": "orbit_demo.accounts"
},
{
"type": "work_unit_finished",
"unitKey": "retention-and-segments",
"status": "success"
},
{
"type": "work_unit_started",
"unitKey": "procurement-and-activation",
"skills": [
"knowledge_capture",
"sl_capture"
],
"stepBudget": 40
},
{
"type": "candidate_action",
"unitKey": "procurement-and-activation",
"target": "wiki",
"action": "created",
"key": "knowledge/global/activation-policy.md"
},
{
"type": "candidate_action",
"unitKey": "procurement-and-activation",
"target": "wiki",
"action": "created",
"key": "knowledge/global/procurement-workflows.md"
},
{
"type": "candidate_action",
"unitKey": "procurement-and-activation",
"target": "sl",
"action": "created",
"key": "orbit_demo.purchase_requests"
},
{
"type": "work_unit_finished",
"unitKey": "procurement-and-activation",
"status": "success"
},
{
"type": "work_unit_started",
"unitKey": "support-and-health",
"skills": [
"knowledge_capture",
"sl_capture"
],
"stepBudget": 40
},
{
"type": "candidate_action",
"unitKey": "support-and-health",
"target": "wiki",
"action": "created",
"key": "knowledge/global/customer-health-scoring.md"
},
{
"type": "candidate_action",
"unitKey": "support-and-health",
"target": "wiki",
"action": "created",
"key": "knowledge/global/support-escalation.md"
},
{
"type": "candidate_action",
"unitKey": "support-and-health",
"target": "sl",
"action": "created",
"key": "orbit_demo.support_tickets"
},
{
"type": "work_unit_finished",
"unitKey": "support-and-health",
"status": "success"
},
{
"type": "work_unit_started",
"unitKey": "governance-and-exclusions",
"skills": [
"knowledge_capture"
],
"stepBudget": 40
},
{
"type": "candidate_action",
"unitKey": "governance-and-exclusions",
"target": "wiki",
"action": "created",
"key": "knowledge/global/internal-test-exclusion.md"
},
{
"type": "work_unit_finished",
"unitKey": "governance-and-exclusions",
"status": "success"
},
{
"type": "reconciliation_finished",
"conflictCount": 0,
"fallbackCount": 0
},
{
"type": "saved",
"commitSha": "demo-seeded",
"wikiCount": 10,
"slCount": 6
},
{
"type": "provenance_recorded",
"rowCount": 23
},
{
"type": "report_created",
"runId": "demo-seeded-orbit",
"reportPath": "reports/seeded-demo-report.json"
}
],
"plannedWorkUnits": [
{
"unitKey": "revenue-and-contracts",
"rawFiles": [
"contracts",
"invoices",
"arr_movements"
],
"peerFileCount": 3,
"dependencyCount": 3
},
{
"unitKey": "retention-and-segments",
"rawFiles": [
"accounts",
"plans"
],
"peerFileCount": 2,
"dependencyCount": 2
},
{
"unitKey": "procurement-and-activation",
"rawFiles": [
"purchase_requests",
"users"
],
"peerFileCount": 2,
"dependencyCount": 2
},
{
"unitKey": "support-and-health",
"rawFiles": [
"support_tickets"
],
"peerFileCount": 1,
"dependencyCount": 1
},
{
"unitKey": "governance-and-exclusions",
"rawFiles": [
"notion/export/pages/analyst-onboarding.md"
],
"peerFileCount": 1,
"dependencyCount": 0
}
],
"details": {
"actions": [
{
"unitKey": "revenue-and-contracts",
"target": "wiki",
"action": "created",
"key": "knowledge/global/arr-contract-first.md",
"summary": "ARR follows contract precedence with cancellation and discount caveats.",
"rawFiles": [
"contracts",
"arr_movements",
"raw-sources/notion/arr-and-contract-reporting-notes.md"
],
"status": "success"
},
{
"unitKey": "revenue-and-contracts",
"target": "wiki",
"action": "created",
"key": "knowledge/global/revenue-gross-to-net.md",
"summary": "Invoice, refund, and revenue dashboard evidence reconcile gross to net revenue.",
"rawFiles": [
"invoices",
"raw-sources/bi/revenue_exec.dashboard.lookml"
],
"status": "success"
},
{
"unitKey": "revenue-and-contracts",
"target": "wiki",
"action": "created",
"key": "knowledge/global/discount-expiration.md",
"summary": "Discount expiration is separated from organic contraction for retention reporting.",
"rawFiles": [
"contracts",
"arr_movements"
],
"status": "success"
},
{
"unitKey": "revenue-and-contracts",
"target": "sl",
"action": "created",
"key": "orbit_demo.contracts",
"summary": "Contract grain with active ARR measures and account joins.",
"rawFiles": [
"contracts",
"raw-sources/dbt/schema.yml"
],
"status": "success"
},
{
"unitKey": "revenue-and-contracts",
"target": "sl",
"action": "created",
"key": "orbit_demo.invoices",
"summary": "Invoice status measures tied to gross and net revenue reporting.",
"rawFiles": [
"invoices",
"raw-sources/bi/revenue_daily.view.lkml"
],
"status": "success"
},
{
"unitKey": "revenue-and-contracts",
"target": "sl",
"action": "created",
"key": "orbit_demo.arr_movements",
"summary": "ARR movement ledger for expansion, contraction, churn, and NRR.",
"rawFiles": [
"arr_movements",
"raw-sources/bi/account_retention.view.lkml"
],
"status": "success"
},
{
"unitKey": "retention-and-segments",
"target": "wiki",
"action": "created",
"key": "knowledge/global/nrr-retention.md",
"summary": "NRR uses parent-account rollups and quarterly ARR movement windows.",
"rawFiles": [
"accounts",
"arr_movements",
"raw-sources/notion/retention-and-nrr-definition-notes.md"
],
"status": "success"
},
{
"unitKey": "retention-and-segments",
"target": "wiki",
"action": "created",
"key": "knowledge/global/segment-classification.md",
"summary": "Segment labels come from plan mapping and sales-ops policy notes.",
"rawFiles": [
"accounts",
"plans",
"raw-sources/notion/sales-ops-segmentation-guide.md"
],
"status": "success"
},
{
"unitKey": "retention-and-segments",
"target": "sl",
"action": "created",
"key": "orbit_demo.accounts",
"summary": "Account dimensions with lifecycle, segment, and internal-test exclusions.",
"rawFiles": [
"accounts",
"plans"
],
"status": "success"
},
{
"unitKey": "procurement-and-activation",
"target": "wiki",
"action": "created",
"key": "knowledge/global/activation-policy.md",
"summary": "Activation policy changed on January 15, 2026 and is encoded for agents.",
"rawFiles": [
"purchase_requests",
"users",
"raw-sources/notion/activation-policy-decision-record.md"
],
"status": "success"
},
{
"unitKey": "procurement-and-activation",
"target": "wiki",
"action": "created",
"key": "knowledge/global/procurement-workflows.md",
"summary": "Procurement requester activity and approval events explain product usage.",
"rawFiles": [
"purchase_requests",
"raw-sources/bi/procurement_activity.view.lkml"
],
"status": "success"
},
{
"unitKey": "procurement-and-activation",
"target": "sl",
"action": "created",
"key": "orbit_demo.purchase_requests",
"summary": "Procurement request facts with requester and approval-state measures.",
"rawFiles": [
"purchase_requests"
],
"status": "success"
},
{
"unitKey": "support-and-health",
"target": "wiki",
"action": "created",
"key": "knowledge/global/customer-health-scoring.md",
"summary": "Customer health combines support severity, ARR exposure, and product usage.",
"rawFiles": [
"support_tickets",
"raw-sources/notion/customer-health-playbook.md"
],
"status": "success"
},
{
"unitKey": "support-and-health",
"target": "wiki",
"action": "created",
"key": "knowledge/global/support-escalation.md",
"summary": "Escalation tiers map ticket severity to SLA expectations.",
"rawFiles": [
"support_tickets",
"raw-sources/notion/support-escalation-runbook.md"
],
"status": "success"
},
{
"unitKey": "support-and-health",
"target": "sl",
"action": "created",
"key": "orbit_demo.support_tickets",
"summary": "Support ticket facts with severity, status, and resolution-hour measures.",
"rawFiles": [
"support_tickets"
],
"status": "success"
},
{
"unitKey": "governance-and-exclusions",
"target": "wiki",
"action": "created",
"key": "knowledge/global/internal-test-exclusion.md",
"summary": "Canonical metrics exclude internal and test accounts across source families.",
"rawFiles": [
"raw-sources/notion/analyst-onboarding.md"
],
"status": "success"
}
],
"provenance": [
{
"rawPath": "contracts",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/arr-contract-first.md",
"actionType": "wiki_written"
},
{
"rawPath": "raw-sources/notion/arr-and-contract-reporting-notes.md",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/arr-contract-first.md",
"actionType": "wiki_written"
},
{
"rawPath": "invoices",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/revenue-gross-to-net.md",
"actionType": "wiki_written"
},
{
"rawPath": "raw-sources/notion/revenue-reporting-policy.md",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/revenue-gross-to-net.md",
"actionType": "wiki_written"
},
{
"rawPath": "arr_movements",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/discount-expiration.md",
"actionType": "wiki_written"
},
{
"rawPath": "arr_movements",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/nrr-retention.md",
"actionType": "wiki_written"
},
{
"rawPath": "raw-sources/notion/retention-and-nrr-definition-notes.md",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/nrr-retention.md",
"actionType": "wiki_written"
},
{
"rawPath": "raw-sources/bi/account_retention.view.lkml",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/nrr-retention.md",
"actionType": "wiki_written"
},
{
"rawPath": "plans",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/segment-classification.md",
"actionType": "wiki_written"
},
{
"rawPath": "raw-sources/notion/sales-ops-segmentation-guide.md",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/segment-classification.md",
"actionType": "wiki_written"
},
{
"rawPath": "raw-sources/notion/activation-policy-decision-record.md",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/activation-policy.md",
"actionType": "wiki_written"
},
{
"rawPath": "purchase_requests",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/procurement-workflows.md",
"actionType": "wiki_written"
},
{
"rawPath": "raw-sources/notion/customer-health-playbook.md",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/customer-health-scoring.md",
"actionType": "wiki_written"
},
{
"rawPath": "support_tickets",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/customer-health-scoring.md",
"actionType": "wiki_written"
},
{
"rawPath": "raw-sources/notion/support-escalation-runbook.md",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/support-escalation.md",
"actionType": "wiki_written"
},
{
"rawPath": "raw-sources/notion/analyst-onboarding.md",
"artifactKind": "wiki",
"artifactKey": "knowledge/global/internal-test-exclusion.md",
"actionType": "wiki_written"
},
{
"rawPath": "accounts",
"artifactKind": "sl",
"artifactKey": "orbit_demo.accounts",
"actionType": "sl_written"
},
{
"rawPath": "raw-sources/dbt/schema.yml",
"artifactKind": "sl",
"artifactKey": "orbit_demo.accounts",
"actionType": "sl_written"
},
{
"rawPath": "contracts",
"artifactKind": "sl",
"artifactKey": "orbit_demo.contracts",
"actionType": "sl_written"
},
{
"rawPath": "invoices",
"artifactKind": "sl",
"artifactKey": "orbit_demo.invoices",
"actionType": "sl_written"
},
{
"rawPath": "arr_movements",
"artifactKind": "sl",
"artifactKey": "orbit_demo.arr_movements",
"actionType": "sl_written"
},
{
"rawPath": "purchase_requests",
"artifactKind": "sl",
"artifactKey": "orbit_demo.purchase_requests",
"actionType": "sl_written"
},
{
"rawPath": "support_tickets",
"artifactKind": "sl",
"artifactKey": "orbit_demo.support_tickets",
"actionType": "sl_written"
}
],
"transcripts": [
{
"unitKey": "revenue-and-contracts",
"path": "transcripts/revenue-and-contracts.jsonl",
"toolCallCount": 5,
"errorCount": 0,
"toolNames": [
"wiki_write",
"sl_write_source"
]
},
{
"unitKey": "retention-and-segments",
"path": "transcripts/retention-and-segments.jsonl",
"toolCallCount": 5,
"errorCount": 0,
"toolNames": [
"wiki_write",
"sl_write_source"
]
},
{
"unitKey": "procurement-and-activation",
"path": "transcripts/procurement-and-activation.jsonl",
"toolCallCount": 5,
"errorCount": 0,
"toolNames": [
"wiki_write",
"sl_write_source"
]
},
{
"unitKey": "support-and-health",
"path": "transcripts/support-and-health.jsonl",
"toolCallCount": 5,
"errorCount": 0,
"toolNames": [
"wiki_write",
"sl_write_source"
]
},
{
"unitKey": "governance-and-exclusions",
"path": "transcripts/governance-and-exclusions.jsonl",
"toolCallCount": 2,
"errorCount": 0,
"toolNames": [
"wiki_write"
]
}
]
}
}
}

View file

@ -0,0 +1,40 @@
{
"id": "demo-seeded-report",
"runId": "demo-seeded-orbit",
"connectionId": "orbit_demo",
"mode": "seeded",
"status": "complete",
"createdAt": "2026-05-06T00:00:00.000Z",
"summary": {
"sources": {
"warehouse": {
"tables": 8,
"rows": 11234
},
"dbt": {
"models": 3,
"sources": 8
},
"bi": {
"explores": 5,
"dashboards": 2,
"views": 5
},
"notion": {
"pages": 8
}
},
"generated": {
"semanticLayerSources": 6,
"knowledgePages": 10,
"provenanceLinks": 23
},
"metadata": {
"mode": "seeded",
"origin": "packaged",
"llmCalls": 0,
"timing": "prebuilt",
"source": "packaged-orbit-demo"
}
}
}

View file

@ -0,0 +1,44 @@
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"

View file

@ -0,0 +1,38 @@
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"

View file

@ -0,0 +1,39 @@
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"

View file

@ -0,0 +1,33 @@
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"

View file

@ -0,0 +1,33 @@
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"

View file

@ -0,0 +1,37 @@
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"

72
packages/cli/package.json Normal file
View file

@ -0,0 +1,72 @@
{
"name": "@klo/cli",
"version": "0.0.0-private",
"description": "CLI wrapper for klo context packages",
"private": true,
"type": "module",
"engines": {
"node": ">=22.0.0"
},
"bin": {
"klo": "./dist/bin.js"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"files": [
"dist",
"assets"
],
"scripts": {
"assets:demo": "node scripts/build-demo-assets.mjs",
"build": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json && node ../../scripts/prepare-cli-bin.mjs",
"smoke": "vitest run src/standalone-smoke.test.ts src/example-smoke.test.ts --testTimeout 30000",
"test": "vitest run",
"type-check": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@clack/prompts": "1.3.0",
"@commander-js/extra-typings": "14.0.0",
"@klo/connector-bigquery": "workspace:*",
"@klo/connector-clickhouse": "workspace:*",
"@klo/connector-mysql": "workspace:*",
"@klo/connector-postgres": "workspace:*",
"@klo/connector-posthog": "workspace:*",
"@klo/connector-snowflake": "workspace:*",
"@klo/connector-sqlite": "workspace:*",
"@klo/connector-sqlserver": "workspace:*",
"@klo/context": "workspace:*",
"@klo/llm": "workspace:*",
"@modelcontextprotocol/sdk": "^1.27.1",
"commander": "14.0.3",
"ink": "^7.0.1",
"react": "^19.2.5",
"zod": "^4.4.3"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^24.3.0",
"@types/react": "^19.2.14",
"better-sqlite3": "^12.6.2",
"ink-testing-library": "^4.0.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/kaelio/ktx.git",
"directory": "packages/cli"
},
"bugs": {
"url": "https://github.com/kaelio/ktx/issues"
},
"homepage": "https://github.com/kaelio/ktx#readme"
}

View file

@ -0,0 +1,954 @@
import { constants as fsConstants } from 'node:fs';
import { access, copyFile, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import Database from 'better-sqlite3';
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const repoRoot = resolve(packageRoot, '../..');
const defaultDemoSource = resolve(repoRoot, '../../../orbit-demo-source');
const sourceRoot = resolve(process.env.KLO_DEMO_SOURCE_DIR ?? defaultDemoSource);
const assetDir = join(packageRoot, 'assets/demo/orbit');
const dbPath = join(assetDir, 'demo.db');
const exampleDbtProjectDir = ['dbt', `${'kae'}lio_demo`].join('/');
const packagedDemoSource = 'packaged-orbit-demo';
const warehouseTables = [
'accounts',
'contracts',
'users',
'invoices',
'arr_movements',
'support_tickets',
'purchase_requests',
'plans',
];
const copyFiles = [
[`${exampleDbtProjectDir}/dbt_project.yml`, 'raw-sources/dbt/dbt_project.yml'],
[`${exampleDbtProjectDir}/models/sources.yml`, 'raw-sources/dbt/sources.yml'],
[`${exampleDbtProjectDir}/models/schema.yml`, 'raw-sources/dbt/schema.yml'],
[`${exampleDbtProjectDir}/models/marts/mart_revenue_daily.sql`, 'raw-sources/dbt/models/marts/mart_revenue_daily.sql'],
[`${exampleDbtProjectDir}/models/marts/mart_arr_daily.sql`, 'raw-sources/dbt/models/marts/mart_arr_daily.sql'],
[
`${exampleDbtProjectDir}/models/marts/mart_customer_health.sql`,
'raw-sources/dbt/models/marts/mart_customer_health.sql',
],
['views/account_retention.view.lkml', 'raw-sources/bi/account_retention.view.lkml'],
['views/arr_daily.view.lkml', 'raw-sources/bi/arr_daily.view.lkml'],
['views/customer_health.view.lkml', 'raw-sources/bi/customer_health.view.lkml'],
['views/procurement_activity.view.lkml', 'raw-sources/bi/procurement_activity.view.lkml'],
['views/revenue_daily.view.lkml', 'raw-sources/bi/revenue_daily.view.lkml'],
['dashboards/revenue_exec.dashboard.lookml', 'raw-sources/bi/revenue_exec.dashboard.lookml'],
['dashboards/retention_exec_q1.dashboard.lookml', 'raw-sources/bi/retention_exec_q1.dashboard.lookml'],
['notion/export/pages/revenue-reporting-policy.md', 'raw-sources/notion/revenue-reporting-policy.md'],
['notion/export/pages/sales-ops-segmentation-guide.md', 'raw-sources/notion/sales-ops-segmentation-guide.md'],
['notion/export/pages/customer-health-playbook.md', 'raw-sources/notion/customer-health-playbook.md'],
['notion/export/pages/support-escalation-runbook.md', 'raw-sources/notion/support-escalation-runbook.md'],
[
'notion/export/pages/arr-and-contract-reporting-notes.md',
'raw-sources/notion/arr-and-contract-reporting-notes.md',
],
[
'notion/export/pages/activation-policy-decision-record.md',
'raw-sources/notion/activation-policy-decision-record.md',
],
[
'notion/export/pages/retention-and-nrr-definition-notes.md',
'raw-sources/notion/retention-and-nrr-definition-notes.md',
],
['notion/export/pages/analyst-onboarding.md', 'raw-sources/notion/analyst-onboarding.md'],
];
const semanticLayerTables = [
'accounts',
'contracts',
'invoices',
'arr_movements',
'purchase_requests',
'support_tickets',
];
const semanticLayerDescriptions = {
accounts: 'Customer accounts with industry, region, lifecycle, and internal/test flags.',
contracts: 'Subscription contracts with ARR, plan, renewal, and status details.',
invoices: 'Billing invoices with payment status and revenue-recognition dates.',
arr_movements: 'ARR movement ledger for expansion, contraction, churn, and reactivation analysis.',
purchase_requests: 'Procurement workflow requests with requester, status, supplier, and spend fields.',
support_tickets: 'Customer support tickets with severity, category, status, and resolution tracking.',
};
const semanticLayerMeasures = {
accounts: [
{ name: 'account_count', expr: 'count(distinct account_id)' },
{ name: 'enterprise_count', expr: 'count(distinct account_id)', filter: "size_band = 'enterprise'" },
],
contracts: [
{ name: 'contract_count', expr: 'count(distinct contract_id)' },
{ name: 'total_arr', expr: 'sum(contract_arr_cents) / 100.0', filter: "status = 'active'" },
],
invoices: [
{ name: 'invoice_count', expr: 'count(*)' },
{ name: 'paid_invoice_count', expr: 'count(*)', filter: "status = 'paid'" },
],
arr_movements: [
{ name: 'movement_count', expr: 'count(*)' },
{ name: 'net_arr_delta', expr: 'sum(arr_delta_cents) / 100.0' },
],
purchase_requests: [
{ name: 'request_count', expr: 'count(*)' },
{ name: 'approved_spend', expr: 'sum(amount_cents) / 100.0', filter: "status = 'approved'" },
],
support_tickets: [
{ name: 'ticket_count', expr: 'count(*)' },
{ name: 'open_ticket_count', expr: 'count(*)', filter: "status != 'resolved'" },
],
};
const knowledgePages = [
{
file: 'arr-contract-first.md',
summary: 'ARR uses contract-first precedence before subscription-derived revenue.',
tags: ['finance', 'arr', 'revenue'],
refs: [],
slRefs: ['orbit_demo.contracts', 'orbit_demo.arr_movements'],
body: [
'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.',
],
},
{
file: 'revenue-gross-to-net.md',
summary: 'Gross-to-net revenue reconciles paid invoices, credits, and refunds.',
tags: ['finance', 'revenue'],
refs: ['arr-contract-first'],
slRefs: ['orbit_demo.invoices'],
body: [
'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.',
],
},
{
file: 'discount-expiration.md',
summary: 'Discount expirations are tracked separately from organic contraction.',
tags: ['finance', 'retention'],
refs: ['arr-contract-first', 'nrr-retention'],
slRefs: ['orbit_demo.contracts', 'orbit_demo.arr_movements'],
body: [
'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.',
],
},
{
file: 'nrr-retention.md',
summary: 'NRR is calculated at parent-account grain by calendar quarter.',
tags: ['analytics', 'retention', 'nrr'],
refs: ['arr-contract-first'],
slRefs: ['orbit_demo.arr_movements', 'orbit_demo.accounts'],
body: [
'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.',
],
},
{
file: 'segment-classification.md',
summary: 'Account segments derive from plan normalization and effective-dated mapping.',
tags: ['sales-ops', 'segmentation'],
refs: [],
slRefs: ['orbit_demo.accounts', 'orbit_demo.contracts'],
body: [
'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.',
],
},
{
file: 'activation-policy.md',
summary: 'Account activation policy changed on January 15, 2026.',
tags: ['growth', 'activation', 'policy'],
refs: [],
slRefs: ['orbit_demo.accounts', 'orbit_demo.purchase_requests'],
body: [
'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.',
],
},
{
file: 'procurement-workflows.md',
summary: 'Procurement workflow activity measures active requesters and qualifying actions.',
tags: ['product', 'procurement'],
refs: ['activation-policy'],
slRefs: ['orbit_demo.purchase_requests'],
body: [
'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.',
],
},
{
file: 'customer-health-scoring.md',
summary: 'Customer health combines support severity and procurement activity.',
tags: ['customer-success', 'health', 'churn-risk'],
refs: ['nrr-retention'],
slRefs: ['orbit_demo.support_tickets', 'orbit_demo.purchase_requests', 'orbit_demo.accounts'],
body: [
'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.',
],
},
{
file: 'support-escalation.md',
summary: 'Support escalation tiers map ticket severity to SLA targets.',
tags: ['support', 'sla'],
refs: ['customer-health-scoring'],
slRefs: ['orbit_demo.support_tickets'],
body: [
'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.',
],
},
{
file: 'internal-test-exclusion.md',
summary: 'Canonical metrics exclude internal and test accounts and users.',
tags: ['data-quality', 'governance'],
refs: [],
slRefs: ['orbit_demo.accounts'],
body: [
'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.',
],
},
];
const provenanceLinks = [
['wiki', 'knowledge/global/arr-contract-first.md', 'warehouse', 'contracts', 'describes', 1],
[
'wiki',
'knowledge/global/arr-contract-first.md',
'notion',
'raw-sources/notion/arr-and-contract-reporting-notes.md',
'derived_from',
0.95,
],
['wiki', 'knowledge/global/revenue-gross-to-net.md', 'warehouse', 'invoices', 'describes', 1],
[
'wiki',
'knowledge/global/revenue-gross-to-net.md',
'notion',
'raw-sources/notion/revenue-reporting-policy.md',
'derived_from',
0.95,
],
['wiki', 'knowledge/global/discount-expiration.md', 'warehouse', 'arr_movements', 'describes', 1],
['wiki', 'knowledge/global/nrr-retention.md', 'warehouse', 'arr_movements', 'describes', 1],
[
'wiki',
'knowledge/global/nrr-retention.md',
'notion',
'raw-sources/notion/retention-and-nrr-definition-notes.md',
'derived_from',
0.95,
],
['wiki', 'knowledge/global/nrr-retention.md', 'bi', 'raw-sources/bi/account_retention.view.lkml', 'derived_from', 0.85],
['wiki', 'knowledge/global/segment-classification.md', 'warehouse', 'plans', 'describes', 1],
[
'wiki',
'knowledge/global/segment-classification.md',
'notion',
'raw-sources/notion/sales-ops-segmentation-guide.md',
'derived_from',
0.9,
],
[
'wiki',
'knowledge/global/activation-policy.md',
'notion',
'raw-sources/notion/activation-policy-decision-record.md',
'derived_from',
0.95,
],
['wiki', 'knowledge/global/procurement-workflows.md', 'warehouse', 'purchase_requests', 'describes', 1],
[
'wiki',
'knowledge/global/customer-health-scoring.md',
'notion',
'raw-sources/notion/customer-health-playbook.md',
'derived_from',
0.9,
],
['wiki', 'knowledge/global/customer-health-scoring.md', 'warehouse', 'support_tickets', 'describes', 1],
[
'wiki',
'knowledge/global/support-escalation.md',
'notion',
'raw-sources/notion/support-escalation-runbook.md',
'derived_from',
0.9,
],
[
'wiki',
'knowledge/global/internal-test-exclusion.md',
'notion',
'raw-sources/notion/analyst-onboarding.md',
'derived_from',
0.9,
],
['sl', 'orbit_demo.accounts', 'warehouse', 'accounts', 'models', 1],
['sl', 'orbit_demo.accounts', 'dbt', 'raw-sources/dbt/schema.yml', 'inherits_from', 0.95],
['sl', 'orbit_demo.contracts', 'warehouse', 'contracts', 'models', 1],
['sl', 'orbit_demo.invoices', 'warehouse', 'invoices', 'models', 1],
['sl', 'orbit_demo.arr_movements', 'warehouse', 'arr_movements', 'models', 1],
['sl', 'orbit_demo.purchase_requests', 'warehouse', 'purchase_requests', 'models', 1],
['sl', 'orbit_demo.support_tickets', 'warehouse', 'support_tickets', 'models', 1],
].map(([artifactKind, artifactKey, sourceKind, sourcePath, relationship, confidence], index) => ({
id: `link-${String(index + 1).padStart(3, '0')}`,
artifactKind,
artifactKey,
sourceKind,
sourcePath,
relationship,
confidence,
}));
async function pathExists(path) {
try {
await access(path, fsConstants.F_OK);
return true;
} catch {
return false;
}
}
async function assertReadable(path, label) {
if (!(await pathExists(path))) {
throw new Error(
`${label} not found at ${path}. Set KLO_DEMO_SOURCE_DIR to the Orbit demo source directory.`,
);
}
}
function parseCsvLine(line) {
const values = [];
let current = '';
let quoted = false;
for (let index = 0; index < line.length; index += 1) {
const char = line[index];
const next = line[index + 1];
if (char === '"' && quoted && next === '"') {
current += '"';
index += 1;
} else if (char === '"') {
quoted = !quoted;
} else if (char === ',' && !quoted) {
values.push(current);
current = '';
} else {
current += char;
}
}
values.push(current);
return values;
}
function parseCsv(raw) {
const lines = raw.trimEnd().split(/\r?\n/);
const headers = parseCsvLine(lines[0]);
const rows = lines.slice(1).map((line) => parseCsvLine(line));
return { headers, rows };
}
function quoteIdentifier(value) {
return `"${value.replace(/"/g, '""')}"`;
}
function inferColumnType(column) {
if (column.startsWith('is_')) {
return 'boolean';
}
if (column.endsWith('_at') || column.endsWith('_date') || column === 'retired_at') {
return 'time';
}
if (column.endsWith('_cents') || column.endsWith('_count')) {
return 'number';
}
return 'string';
}
function renderKnowledgePage(page) {
const refs = page.refs.length > 0 ? ['refs:', ...page.refs.map((ref) => ` - ${ref}`)] : ['refs: []'];
const slRefs = page.slRefs.map((ref) => ` - ${ref}`).join('\n');
return [
'---',
`summary: ${page.summary}`,
'tags:',
...page.tags.map((tag) => ` - ${tag}`),
...refs,
'sl_refs:',
slRefs,
'usage_mode: auto',
'---',
'',
page.body.join('\n\n'),
'',
].join('\n');
}
function renderMeasure(measure) {
const lines = [` - name: ${measure.name}`, ` expr: ${JSON.stringify(measure.expr)}`];
if (measure.filter) {
lines.push(` filter: ${JSON.stringify(measure.filter)}`);
}
return lines.join('\n');
}
async function renderSemanticLayerSource(table) {
const raw = await readFile(join(sourceRoot, 'database/seeds', `${table}.csv`), 'utf-8');
const { headers } = parseCsv(raw);
const primaryKey = headers[0];
const joins =
table === 'accounts'
? [
' - 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',
]
: [' - to: accounts', ' "on": "account_id = accounts.account_id"', ' relationship: many_to_one'];
return [
`name: ${table}`,
`table: ${table}`,
`description: ${semanticLayerDescriptions[table]}`,
'grain:',
` - ${primaryKey}`,
'columns:',
...headers.flatMap((header) => [` - name: ${header}`, ` type: ${inferColumnType(header)}`]),
'joins:',
...joins,
'measures:',
...semanticLayerMeasures[table].map(renderMeasure),
'segments:',
' - name: external_only',
' expr: "coalesce(is_internal, 0) = 0 AND coalesce(is_test, 0) = 0"',
'',
].join('\n');
}
async function writeWarehouse(db, rowCounts) {
for (const table of warehouseTables) {
const sourceCsv = join(sourceRoot, 'database/seeds', `${table}.csv`);
const raw = await readFile(sourceCsv, 'utf-8');
const { headers, rows } = parseCsv(raw);
const columnsSql = headers.map((header) => `${quoteIdentifier(header)} TEXT`).join(', ');
db.exec(`CREATE TABLE ${quoteIdentifier(table)} (${columnsSql});`);
const placeholders = headers.map(() => '?').join(', ');
const statement = db.prepare(`INSERT INTO ${quoteIdentifier(table)} VALUES (${placeholders})`);
const insertAll = db.transaction((records) => {
for (const record of records) {
statement.run(record);
}
});
insertAll(rows);
rowCounts[table] = rows.length;
await copyFile(sourceCsv, join(assetDir, 'raw-sources/warehouse', `${table}.csv`));
}
}
async function copyCuratedSourceFiles() {
for (const [from, to] of copyFiles) {
const destination = join(assetDir, to);
await mkdir(dirname(destination), { recursive: true });
await copyFile(join(sourceRoot, from), destination);
}
}
async function writeJson(relativePath, value) {
const destination = join(assetDir, relativePath);
await mkdir(dirname(destination), { recursive: true });
await writeFile(destination, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
}
async function writeText(relativePath, value) {
const destination = join(assetDir, relativePath);
await mkdir(dirname(destination), { recursive: true });
await writeFile(destination, value, 'utf-8');
}
function buildActions() {
return [
{
unitKey: 'revenue-and-contracts',
target: 'wiki',
action: 'created',
key: 'knowledge/global/arr-contract-first.md',
summary: 'ARR follows contract precedence with cancellation and discount caveats.',
rawFiles: ['contracts', 'arr_movements', 'raw-sources/notion/arr-and-contract-reporting-notes.md'],
status: 'success',
},
{
unitKey: 'revenue-and-contracts',
target: 'wiki',
action: 'created',
key: 'knowledge/global/revenue-gross-to-net.md',
summary: 'Invoice, refund, and revenue dashboard evidence reconcile gross to net revenue.',
rawFiles: ['invoices', 'raw-sources/bi/revenue_exec.dashboard.lookml'],
status: 'success',
},
{
unitKey: 'revenue-and-contracts',
target: 'wiki',
action: 'created',
key: 'knowledge/global/discount-expiration.md',
summary: 'Discount expiration is separated from organic contraction for retention reporting.',
rawFiles: ['contracts', 'arr_movements'],
status: 'success',
},
{
unitKey: 'revenue-and-contracts',
target: 'sl',
action: 'created',
key: 'orbit_demo.contracts',
summary: 'Contract grain with active ARR measures and account joins.',
rawFiles: ['contracts', 'raw-sources/dbt/schema.yml'],
status: 'success',
},
{
unitKey: 'revenue-and-contracts',
target: 'sl',
action: 'created',
key: 'orbit_demo.invoices',
summary: 'Invoice status measures tied to gross and net revenue reporting.',
rawFiles: ['invoices', 'raw-sources/bi/revenue_daily.view.lkml'],
status: 'success',
},
{
unitKey: 'revenue-and-contracts',
target: 'sl',
action: 'created',
key: 'orbit_demo.arr_movements',
summary: 'ARR movement ledger for expansion, contraction, churn, and NRR.',
rawFiles: ['arr_movements', 'raw-sources/bi/account_retention.view.lkml'],
status: 'success',
},
{
unitKey: 'retention-and-segments',
target: 'wiki',
action: 'created',
key: 'knowledge/global/nrr-retention.md',
summary: 'NRR uses parent-account rollups and quarterly ARR movement windows.',
rawFiles: ['accounts', 'arr_movements', 'raw-sources/notion/retention-and-nrr-definition-notes.md'],
status: 'success',
},
{
unitKey: 'retention-and-segments',
target: 'wiki',
action: 'created',
key: 'knowledge/global/segment-classification.md',
summary: 'Segment labels come from plan mapping and sales-ops policy notes.',
rawFiles: ['accounts', 'plans', 'raw-sources/notion/sales-ops-segmentation-guide.md'],
status: 'success',
},
{
unitKey: 'retention-and-segments',
target: 'sl',
action: 'created',
key: 'orbit_demo.accounts',
summary: 'Account dimensions with lifecycle, segment, and internal-test exclusions.',
rawFiles: ['accounts', 'plans'],
status: 'success',
},
{
unitKey: 'procurement-and-activation',
target: 'wiki',
action: 'created',
key: 'knowledge/global/activation-policy.md',
summary: 'Activation policy changed on January 15, 2026 and is encoded for agents.',
rawFiles: ['purchase_requests', 'users', 'raw-sources/notion/activation-policy-decision-record.md'],
status: 'success',
},
{
unitKey: 'procurement-and-activation',
target: 'wiki',
action: 'created',
key: 'knowledge/global/procurement-workflows.md',
summary: 'Procurement requester activity and approval events explain product usage.',
rawFiles: ['purchase_requests', 'raw-sources/bi/procurement_activity.view.lkml'],
status: 'success',
},
{
unitKey: 'procurement-and-activation',
target: 'sl',
action: 'created',
key: 'orbit_demo.purchase_requests',
summary: 'Procurement request facts with requester and approval-state measures.',
rawFiles: ['purchase_requests'],
status: 'success',
},
{
unitKey: 'support-and-health',
target: 'wiki',
action: 'created',
key: 'knowledge/global/customer-health-scoring.md',
summary: 'Customer health combines support severity, ARR exposure, and product usage.',
rawFiles: ['support_tickets', 'raw-sources/notion/customer-health-playbook.md'],
status: 'success',
},
{
unitKey: 'support-and-health',
target: 'wiki',
action: 'created',
key: 'knowledge/global/support-escalation.md',
summary: 'Escalation tiers map ticket severity to SLA expectations.',
rawFiles: ['support_tickets', 'raw-sources/notion/support-escalation-runbook.md'],
status: 'success',
},
{
unitKey: 'support-and-health',
target: 'sl',
action: 'created',
key: 'orbit_demo.support_tickets',
summary: 'Support ticket facts with severity, status, and resolution-hour measures.',
rawFiles: ['support_tickets'],
status: 'success',
},
{
unitKey: 'governance-and-exclusions',
target: 'wiki',
action: 'created',
key: 'knowledge/global/internal-test-exclusion.md',
summary: 'Canonical metrics exclude internal and test accounts across source families.',
rawFiles: ['raw-sources/notion/analyst-onboarding.md'],
status: 'success',
},
];
}
function buildReplay(provenance, transcripts) {
return {
memoryFlowReplaySchemaVersion: 1,
replay: {
runId: 'demo-seeded-orbit',
connectionId: 'orbit_demo',
adapter: 'live-database',
status: 'done',
sourceDir: null,
syncId: 'demo-seeded-sync',
reportId: 'demo-seeded-report',
reportPath: 'reports/seeded-demo-report.json',
errors: [],
metadata: {
schemaVersion: 1,
mode: 'seeded',
origin: 'packaged',
timing: 'prebuilt',
capturedAt: '2026-05-06T00:00:00.000Z',
sourceReportId: 'demo-seeded-report',
sourceReportPath: 'reports/seeded-demo-report.json',
fallbackReason: null,
},
events: [
{ type: 'source_acquired', adapter: 'live-database', trigger: 'demo_seeded', fileCount: 8 },
{ type: 'source_acquired', adapter: 'dbt_descriptions', trigger: 'demo_seeded', fileCount: 6 },
{ type: 'source_acquired', adapter: 'looker', trigger: 'demo_seeded', fileCount: 7 },
{ type: 'source_acquired', adapter: 'notion', trigger: 'demo_seeded', fileCount: 8 },
{ type: 'scope_detected', fingerprint: 'sqlite:orbit-demo' },
{ type: 'raw_snapshot_written', syncId: 'demo-seeded-sync', rawFileCount: 29 },
{ type: 'diff_computed', added: 29, modified: 0, deleted: 0, unchanged: 0 },
{ type: 'chunks_planned', chunkCount: 5, workUnitCount: 5, evictionCount: 0 },
{ type: 'work_unit_started', unitKey: 'revenue-and-contracts', skills: ['knowledge_capture', 'sl_capture'], stepBudget: 40 },
{
type: 'candidate_action',
unitKey: 'revenue-and-contracts',
target: 'wiki',
action: 'created',
key: 'knowledge/global/arr-contract-first.md',
},
{
type: 'candidate_action',
unitKey: 'revenue-and-contracts',
target: 'wiki',
action: 'created',
key: 'knowledge/global/revenue-gross-to-net.md',
},
{
type: 'candidate_action',
unitKey: 'revenue-and-contracts',
target: 'wiki',
action: 'created',
key: 'knowledge/global/discount-expiration.md',
},
{
type: 'candidate_action',
unitKey: 'revenue-and-contracts',
target: 'sl',
action: 'created',
key: 'orbit_demo.contracts',
},
{
type: 'candidate_action',
unitKey: 'revenue-and-contracts',
target: 'sl',
action: 'created',
key: 'orbit_demo.invoices',
},
{
type: 'candidate_action',
unitKey: 'revenue-and-contracts',
target: 'sl',
action: 'created',
key: 'orbit_demo.arr_movements',
},
{ type: 'work_unit_finished', unitKey: 'revenue-and-contracts', status: 'success' },
{ type: 'work_unit_started', unitKey: 'retention-and-segments', skills: ['knowledge_capture', 'sl_capture'], stepBudget: 40 },
{
type: 'candidate_action',
unitKey: 'retention-and-segments',
target: 'wiki',
action: 'created',
key: 'knowledge/global/nrr-retention.md',
},
{
type: 'candidate_action',
unitKey: 'retention-and-segments',
target: 'wiki',
action: 'created',
key: 'knowledge/global/segment-classification.md',
},
{
type: 'candidate_action',
unitKey: 'retention-and-segments',
target: 'sl',
action: 'created',
key: 'orbit_demo.accounts',
},
{ type: 'work_unit_finished', unitKey: 'retention-and-segments', status: 'success' },
{
type: 'work_unit_started',
unitKey: 'procurement-and-activation',
skills: ['knowledge_capture', 'sl_capture'],
stepBudget: 40,
},
{
type: 'candidate_action',
unitKey: 'procurement-and-activation',
target: 'wiki',
action: 'created',
key: 'knowledge/global/activation-policy.md',
},
{
type: 'candidate_action',
unitKey: 'procurement-and-activation',
target: 'wiki',
action: 'created',
key: 'knowledge/global/procurement-workflows.md',
},
{
type: 'candidate_action',
unitKey: 'procurement-and-activation',
target: 'sl',
action: 'created',
key: 'orbit_demo.purchase_requests',
},
{ type: 'work_unit_finished', unitKey: 'procurement-and-activation', status: 'success' },
{ type: 'work_unit_started', unitKey: 'support-and-health', skills: ['knowledge_capture', 'sl_capture'], stepBudget: 40 },
{
type: 'candidate_action',
unitKey: 'support-and-health',
target: 'wiki',
action: 'created',
key: 'knowledge/global/customer-health-scoring.md',
},
{
type: 'candidate_action',
unitKey: 'support-and-health',
target: 'wiki',
action: 'created',
key: 'knowledge/global/support-escalation.md',
},
{
type: 'candidate_action',
unitKey: 'support-and-health',
target: 'sl',
action: 'created',
key: 'orbit_demo.support_tickets',
},
{ type: 'work_unit_finished', unitKey: 'support-and-health', status: 'success' },
{ type: 'work_unit_started', unitKey: 'governance-and-exclusions', skills: ['knowledge_capture'], stepBudget: 40 },
{
type: 'candidate_action',
unitKey: 'governance-and-exclusions',
target: 'wiki',
action: 'created',
key: 'knowledge/global/internal-test-exclusion.md',
},
{ type: 'work_unit_finished', unitKey: 'governance-and-exclusions', status: 'success' },
{ type: 'reconciliation_finished', conflictCount: 0, fallbackCount: 0 },
{ type: 'saved', commitSha: 'demo-seeded', wikiCount: 10, slCount: 6 },
{ type: 'provenance_recorded', rowCount: provenance.length },
{ type: 'report_created', runId: 'demo-seeded-orbit', reportPath: 'reports/seeded-demo-report.json' },
],
plannedWorkUnits: [
{
unitKey: 'revenue-and-contracts',
rawFiles: ['contracts', 'invoices', 'arr_movements'],
peerFileCount: 3,
dependencyCount: 3,
},
{
unitKey: 'retention-and-segments',
rawFiles: ['accounts', 'plans'],
peerFileCount: 2,
dependencyCount: 2,
},
{
unitKey: 'procurement-and-activation',
rawFiles: ['purchase_requests', 'users'],
peerFileCount: 2,
dependencyCount: 2,
},
{ unitKey: 'support-and-health', rawFiles: ['support_tickets'], peerFileCount: 1, dependencyCount: 1 },
{
unitKey: 'governance-and-exclusions',
rawFiles: ['notion/export/pages/analyst-onboarding.md'],
peerFileCount: 1,
dependencyCount: 0,
},
],
details: {
actions: buildActions(),
provenance,
transcripts,
},
},
};
}
async function writeGeneratedContext(rowCounts) {
for (const page of knowledgePages) {
await writeText(join('knowledge/global', page.file), renderKnowledgePage(page));
}
for (const table of semanticLayerTables) {
await writeText(join('semantic-layer/orbit_demo', `${table}.yaml`), await renderSemanticLayerSource(table));
}
const provenance = provenanceLinks.map((link) => ({
rawPath: link.sourcePath,
artifactKind: link.artifactKind,
artifactKey: link.artifactKey,
actionType: link.artifactKind === 'sl' ? 'sl_written' : 'wiki_written',
}));
const transcripts = [
'revenue-and-contracts',
'retention-and-segments',
'procurement-and-activation',
'support-and-health',
'governance-and-exclusions',
].map((unitKey) => ({
unitKey,
path: `transcripts/${unitKey}.jsonl`,
toolCallCount: unitKey === 'governance-and-exclusions' ? 2 : 5,
errorCount: 0,
toolNames: unitKey === 'governance-and-exclusions' ? ['wiki_write'] : ['wiki_write', 'sl_write_source'],
}));
await writeJson('links/provenance.json', provenanceLinks);
await writeJson('reports/seeded-demo-report.json', {
id: 'demo-seeded-report',
runId: 'demo-seeded-orbit',
connectionId: 'orbit_demo',
mode: 'seeded',
status: 'complete',
createdAt: '2026-05-06T00:00:00.000Z',
summary: {
sources: {
warehouse: { tables: 8, rows: Object.values(rowCounts).reduce((sum, count) => sum + count, 0) },
dbt: { models: 3, sources: 8 },
bi: { explores: 5, dashboards: 2, views: 5 },
notion: { pages: 8 },
},
generated: {
semanticLayerSources: 6,
knowledgePages: 10,
provenanceLinks: provenanceLinks.length,
},
metadata: {
mode: 'seeded',
origin: 'packaged',
llmCalls: 0,
timing: 'prebuilt',
source: packagedDemoSource,
},
},
});
await writeJson('manifest.json', {
demoAssetSchemaVersion: 2,
name: 'orbit',
displayName: 'Orbit Demo',
mode: 'seeded',
sqliteDatabase: 'demo.db',
replay: 'replay.memory-flow.v1.json',
report: 'reports/seeded-demo-report.json',
source: packagedDemoSource,
sources: {
warehouse: { label: 'Warehouse', path: 'demo.db', tables: 8, rowCounts },
dbt: { label: 'dbt', path: 'raw-sources/dbt', models: 3, sourceTables: 8 },
bi: { label: 'BI', path: 'raw-sources/bi', explores: 5, dashboards: 2 },
notion: { label: 'Notion', path: 'raw-sources/notion', pages: 8 },
},
generated: {
semanticLayer: { path: 'semantic-layer/orbit_demo', sourceCount: 6 },
knowledge: { path: 'knowledge/global', pageCount: 10 },
links: { path: 'links', linkCount: provenanceLinks.length },
},
});
await writeJson('replay.memory-flow.v1.json', buildReplay(provenance, transcripts));
}
await assertReadable(join(sourceRoot, 'database/seeds/accounts.csv'), `${packagedDemoSource} seed data`);
await assertReadable(join(sourceRoot, `${exampleDbtProjectDir}/models/schema.yml`), `${packagedDemoSource} dbt schema`);
await assertReadable(join(sourceRoot, 'views/revenue_daily.view.lkml'), `${packagedDemoSource} LookML views`);
await assertReadable(
join(sourceRoot, 'notion/export/pages/revenue-reporting-policy.md'),
`${packagedDemoSource} Notion export`,
);
await rm(assetDir, { recursive: true, force: true });
for (const relativeDir of [
'raw-sources/warehouse',
'raw-sources/dbt/models/marts',
'raw-sources/bi',
'raw-sources/notion',
'semantic-layer/orbit_demo',
'knowledge/global',
'links',
'reports',
]) {
await mkdir(join(assetDir, relativeDir), { recursive: true });
}
const rowCounts = {};
await rm(dbPath, { force: true });
const db = new Database(dbPath);
try {
await writeWarehouse(db, rowCounts);
} finally {
db.close();
}
await copyCuratedSourceFiles();
await writeGeneratedContext(rowCounts);
const dbStat = await stat(dbPath);
if (dbStat.size >= 10 * 1024 * 1024) {
throw new Error(`Seeded demo SQLite bundle is too large: ${dbStat.size} bytes`);
}

View file

@ -0,0 +1,108 @@
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
KLO_AGENT_MAX_ROWS_CAP,
createKloAgentRuntime,
parseAgentMaxRows,
readAgentJsonFile,
writeAgentJson,
writeAgentJsonError,
} from './agent-runtime.js';
function makeIo() {
let stdout = '';
let stderr = '';
return {
io: {
stdout: { write: (chunk: string) => (stdout += chunk) },
stderr: { write: (chunk: string) => (stderr += chunk) },
},
stdout: () => stdout,
stderr: () => stderr,
};
}
describe('agent runtime helpers', () => {
let tempDir: string;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'klo-agent-runtime-'));
});
afterEach(async () => {
await rm(tempDir, { recursive: true, force: true });
});
it('writes JSON success and error envelopes without color or spinners', () => {
const successIo = makeIo();
const errorIo = makeIo();
writeAgentJson(successIo.io, { ok: true });
writeAgentJsonError(errorIo.io, 'missing source', { code: 'NOT_FOUND' });
expect(JSON.parse(successIo.stdout())).toEqual({ ok: true });
expect(successIo.stderr()).toBe('');
expect(JSON.parse(errorIo.stderr())).toEqual({
ok: false,
error: { message: 'missing source', code: 'NOT_FOUND' },
});
expect(errorIo.stdout()).toBe('');
});
it('reads JSON query files as objects', async () => {
const path = join(tempDir, 'query.json');
await writeFile(path, '{"measures":["revenue"],"limit":50}', 'utf-8');
await expect(readAgentJsonFile(path)).resolves.toEqual({ measures: ['revenue'], limit: 50 });
});
it('rejects non-object JSON query files', async () => {
const path = join(tempDir, 'query.json');
await writeFile(path, '["revenue"]', 'utf-8');
await expect(readAgentJsonFile(path)).rejects.toThrow('must contain a JSON object');
});
it('requires positive row limits and enforces the agent cap', () => {
expect(parseAgentMaxRows(100)).toBe(100);
expect(() => parseAgentMaxRows(undefined)).toThrow('maxRows is required');
expect(() => parseAgentMaxRows(0)).toThrow('positive integer');
expect(() => parseAgentMaxRows(KLO_AGENT_MAX_ROWS_CAP + 1)).toThrow(String(KLO_AGENT_MAX_ROWS_CAP));
});
it('constructs local context ports with semantic compute and query executor', async () => {
const project = {
projectDir: tempDir,
configPath: join(tempDir, 'klo.yaml'),
config: { project: 'revenue', connections: {} },
coreConfig: {},
git: {},
fileStore: {},
} as never;
const ports = { knowledge: {}, semanticLayer: {} } as never;
const semanticLayerCompute = { query: vi.fn(), validateSources: vi.fn(), generateSources: vi.fn() };
const queryExecutor = { execute: vi.fn() };
const loadProject = vi.fn(async () => project);
const createContextTools = vi.fn(() => ports);
await expect(
createKloAgentRuntime(
{ projectDir: tempDir, enableSemanticCompute: true, enableQueryExecution: true },
{
loadProject,
createContextTools,
createSemanticLayerCompute: () => semanticLayerCompute,
createQueryExecutor: () => queryExecutor,
},
),
).resolves.toMatchObject({ project, ports, queryExecutor });
expect(loadProject).toHaveBeenCalledWith({ projectDir: tempDir });
expect(createContextTools).toHaveBeenCalledWith(project, {
semanticLayerCompute,
queryExecutor,
});
});
});

View file

@ -0,0 +1,81 @@
import { readFile } from 'node:fs/promises';
import { createDefaultLocalQueryExecutor, type KloSqlQueryExecutorPort } from '@klo/context/connections';
import { createPythonSemanticLayerComputePort, type KloSemanticLayerComputePort } from '@klo/context/daemon';
import { createLocalProjectMcpContextPorts, type KloMcpContextPorts } from '@klo/context/mcp';
import { type KloLocalProject, loadKloProject } from '@klo/context/project';
import type { KloCliIo } from './cli-runtime.js';
export const KLO_AGENT_MAX_ROWS_CAP = 1000;
export interface KloAgentRuntimeOptions {
projectDir: string;
enableSemanticCompute: boolean;
enableQueryExecution: boolean;
}
export interface KloAgentRuntime {
project: KloLocalProject;
ports: KloMcpContextPorts;
semanticLayerCompute?: KloSemanticLayerComputePort;
queryExecutor?: KloSqlQueryExecutorPort;
}
export interface KloAgentRuntimeDeps {
loadProject?: typeof loadKloProject;
createContextTools?: typeof createLocalProjectMcpContextPorts;
createSemanticLayerCompute?: () => KloSemanticLayerComputePort;
createQueryExecutor?: () => KloSqlQueryExecutorPort;
}
export function writeAgentJson(io: KloCliIo, value: unknown): void {
io.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
}
export function writeAgentJsonError(
io: KloCliIo,
message: string,
detail: Record<string, unknown> = {},
): void {
io.stderr.write(`${JSON.stringify({ ok: false, error: { message, ...detail } }, null, 2)}\n`);
}
export async function readAgentJsonFile(path: string): Promise<Record<string, unknown>> {
const parsed = JSON.parse(await readFile(path, 'utf-8')) as unknown;
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error(`${path} must contain a JSON object.`);
}
return parsed as Record<string, unknown>;
}
export function parseAgentMaxRows(value: number | undefined): number {
if (!Number.isInteger(value) || value === undefined || value <= 0) {
throw new Error('maxRows is required and must be a positive integer.');
}
if (value > KLO_AGENT_MAX_ROWS_CAP) {
throw new Error(`maxRows must be less than or equal to ${KLO_AGENT_MAX_ROWS_CAP}.`);
}
return value;
}
export async function createKloAgentRuntime(
options: KloAgentRuntimeOptions,
deps: KloAgentRuntimeDeps = {},
): Promise<KloAgentRuntime> {
const project = await (deps.loadProject ?? loadKloProject)({ projectDir: options.projectDir });
const semanticLayerCompute = options.enableSemanticCompute
? (deps.createSemanticLayerCompute ?? createPythonSemanticLayerComputePort)()
: undefined;
const queryExecutor = options.enableQueryExecution
? (deps.createQueryExecutor ?? createDefaultLocalQueryExecutor)()
: undefined;
const ports = (deps.createContextTools ?? createLocalProjectMcpContextPorts)(project, {
...(semanticLayerCompute ? { semanticLayerCompute } : {}),
...(queryExecutor ? { queryExecutor } : {}),
});
return {
project,
ports,
...(semanticLayerCompute ? { semanticLayerCompute } : {}),
...(queryExecutor ? { queryExecutor } : {}),
};
}

View file

@ -0,0 +1,51 @@
import { describe, expect, it } from 'vitest';
import {
isMissingProjectConfigError,
missingConnectionSlSearchReadiness,
missingProjectSlSearchReadiness,
noConnectionsSlSearchReadiness,
noIndexedSourcesSlSearchReadiness,
} from './agent-search-readiness.js';
describe('agent semantic-layer search readiness guidance', () => {
it('formats missing project guidance with exact recovery commands', () => {
expect(missingProjectSlSearchReadiness('/tmp/klo-search', 'gross revenue')).toEqual({
code: 'agent_sl_search_missing_project',
message: 'Semantic-layer search needs an initialized KLO project at /tmp/klo-search.',
nextSteps: [
'klo demo',
'klo setup --project-dir /tmp/klo-search',
'klo ingest <connection>',
'klo agent sl list --json --query "gross revenue" --project-dir /tmp/klo-search',
],
});
});
it('formats no-connection and no-index guidance without hiding the project path', () => {
expect(noConnectionsSlSearchReadiness('/tmp/klo-search', 'revenue')).toMatchObject({
code: 'agent_sl_search_no_connections',
message: 'Semantic-layer search found no configured connections in /tmp/klo-search.',
});
expect(noIndexedSourcesSlSearchReadiness('/tmp/klo-search', 'orders')).toMatchObject({
code: 'agent_sl_search_no_indexed_sources',
message: 'Semantic-layer search found no indexed semantic-layer sources in /tmp/klo-search.',
});
});
it('formats unknown connection guidance', () => {
expect(missingConnectionSlSearchReadiness('/tmp/klo-search', 'warehouse', 'revenue')).toMatchObject({
code: 'agent_sl_search_unknown_connection',
message: 'Semantic-layer search connection "warehouse" is not configured in /tmp/klo-search.',
});
});
it('detects missing klo.yaml read errors', () => {
const error = Object.assign(new Error('ENOENT: no such file or directory'), {
code: 'ENOENT',
path: '/tmp/klo-search/klo.yaml',
});
expect(isMissingProjectConfigError(error)).toBe(true);
expect(isMissingProjectConfigError(new Error('other'))).toBe(false);
});
});

View file

@ -0,0 +1,94 @@
export type KloAgentSlSearchReadinessCode =
| 'agent_sl_search_missing_project'
| 'agent_sl_search_no_connections'
| 'agent_sl_search_unknown_connection'
| 'agent_sl_search_no_indexed_sources';
export interface KloAgentSlSearchReadinessDetail {
code: KloAgentSlSearchReadinessCode;
message: string;
nextSteps: string[];
}
function queryForCommand(query: string | undefined): string {
const trimmed = query?.trim();
return trimmed && trimmed.length > 0 ? trimmed : 'revenue';
}
function projectSearchCommand(projectDir: string, query: string | undefined): string {
return `klo agent sl list --json --query ${JSON.stringify(queryForCommand(query))} --project-dir ${projectDir}`;
}
function baseNextSteps(projectDir: string, query: string | undefined): string[] {
return [
'klo demo',
`klo setup --project-dir ${projectDir}`,
'klo ingest <connection>',
projectSearchCommand(projectDir, query),
];
}
export function missingProjectSlSearchReadiness(
projectDir: string,
query: string | undefined,
): KloAgentSlSearchReadinessDetail {
return {
code: 'agent_sl_search_missing_project',
message: `Semantic-layer search needs an initialized KLO project at ${projectDir}.`,
nextSteps: baseNextSteps(projectDir, query),
};
}
export function noConnectionsSlSearchReadiness(
projectDir: string,
query: string | undefined,
): KloAgentSlSearchReadinessDetail {
return {
code: 'agent_sl_search_no_connections',
message: `Semantic-layer search found no configured connections in ${projectDir}.`,
nextSteps: baseNextSteps(projectDir, query),
};
}
export function missingConnectionSlSearchReadiness(
projectDir: string,
connectionId: string,
query: string | undefined,
): KloAgentSlSearchReadinessDetail {
return {
code: 'agent_sl_search_unknown_connection',
message: `Semantic-layer search connection "${connectionId}" is not configured in ${projectDir}.`,
nextSteps: baseNextSteps(projectDir, query),
};
}
export function noIndexedSourcesSlSearchReadiness(
projectDir: string,
query: string | undefined,
): KloAgentSlSearchReadinessDetail {
return {
code: 'agent_sl_search_no_indexed_sources',
message: `Semantic-layer search found no indexed semantic-layer sources in ${projectDir}.`,
nextSteps: baseNextSteps(projectDir, query),
};
}
function errorCode(error: unknown): string | undefined {
if (typeof error !== 'object' || error === null || !('code' in error)) {
return undefined;
}
const code = (error as { code?: unknown }).code;
return typeof code === 'string' ? code : undefined;
}
function errorPath(error: unknown): string | undefined {
if (typeof error !== 'object' || error === null || !('path' in error)) {
return undefined;
}
const path = (error as { path?: unknown }).path;
return typeof path === 'string' ? path : undefined;
}
export function isMissingProjectConfigError(error: unknown): boolean {
return errorCode(error) === 'ENOENT' && (errorPath(error)?.endsWith('klo.yaml') ?? false);
}

View file

@ -0,0 +1,393 @@
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { buildDefaultKloProjectConfig } from '@klo/context/project';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { runKloAgent } from './agent.js';
import type { KloAgentRuntime } from './agent-runtime.js';
function makeIo() {
let stdout = '';
let stderr = '';
return {
io: {
stdout: { write: (chunk: string) => (stdout += chunk) },
stderr: { write: (chunk: string) => (stderr += chunk) },
},
stdout: () => stdout,
stderr: () => stderr,
};
}
function runtime(overrides: Record<string, unknown> = {}): KloAgentRuntime {
const config = buildDefaultKloProjectConfig('revenue');
return {
project: {
projectDir: '/tmp/revenue',
configPath: '/tmp/revenue/klo.yaml',
config: {
...config,
connections: {
warehouse: { driver: 'sqlite', path: 'warehouse.sqlite', readonly: true as const },
},
},
coreConfig: {} as KloAgentRuntime['project']['coreConfig'],
git: {} as KloAgentRuntime['project']['git'],
fileStore: {} as KloAgentRuntime['project']['fileStore'],
},
ports: {
connections: { list: vi.fn(async () => [{ id: 'warehouse', name: 'warehouse', connectionType: 'sqlite' }]) },
semanticLayer: {
listSources: vi.fn(async () => ({
sources: [
{
connectionId: 'warehouse',
connectionName: 'warehouse',
name: 'orders',
columnCount: 2,
measureCount: 1,
joinCount: 0,
},
],
totalSources: 1,
})),
readSource: vi.fn(async () => ({ sourceName: 'orders', yaml: 'name: orders\n' })),
writeSource: vi.fn(async () => ({ success: true, sourceName: 'orders' })),
validate: vi.fn(async () => ({ success: true, errors: [], warnings: [] })),
query: vi.fn(async () => ({ sql: 'select 1', headers: ['x'], rows: [[1]], totalRows: 1, plan: {} })),
},
knowledge: {
search: vi.fn(async () => ({
results: [
{
key: 'page-1',
path: 'knowledge/global/page-1.md',
scope: 'GLOBAL' as const,
summary: 'Revenue logic',
score: 0.9,
matchReasons: ['lexical' as const],
},
],
totalFound: 1,
})),
read: vi.fn(async () => ({
key: 'page-1',
scope: 'GLOBAL' as const,
summary: 'Revenue logic',
content: 'Use net revenue.',
})),
write: vi.fn(async () => ({ success: true, key: 'page-1', action: 'created' as const })),
},
},
queryExecutor: {
execute: vi.fn(async () => ({ headers: ['x'], rows: [[1]], totalRows: 1, command: 'SELECT', rowCount: 1 })),
},
...overrides,
};
}
function runtimeWithoutConnections(): KloAgentRuntime {
const base = runtime();
return {
...base,
project: {
...base.project,
config: {
...base.project.config,
connections: {},
},
},
ports: {
...base.ports,
semanticLayer: {
...base.ports.semanticLayer!,
listSources: vi.fn(async () => ({ sources: [], totalSources: 0 })),
},
},
};
}
describe('runKloAgent', () => {
let tempDir: string;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'klo-agent-'));
});
afterEach(async () => {
await rm(tempDir, { recursive: true, force: true });
});
it('prints tool discovery with every stable command', async () => {
const io = makeIo();
await expect(runKloAgent({ command: 'tools', projectDir: tempDir, json: true }, io.io)).resolves.toBe(0);
const body = JSON.parse(io.stdout());
expect(body.projectDir).toBe(tempDir);
expect(body.tools.map((tool: { name: string }) => tool.name)).toEqual([
'context',
'sl.list',
'sl.read',
'sl.query',
'wiki.search',
'wiki.read',
'sql.execute',
]);
expect(io.stderr()).toBe('');
});
it('prints project context from setup status, connections, and SL summaries', async () => {
const io = makeIo();
const createRuntime = vi.fn(async () => runtime());
const readSetupStatus = vi.fn(async () => ({ project: { path: tempDir, ready: true }, agents: [] }));
await expect(
runKloAgent({ command: 'context', projectDir: tempDir, json: true }, io.io, { createRuntime, readSetupStatus }),
).resolves.toBe(0);
expect(JSON.parse(io.stdout())).toMatchObject({
projectDir: tempDir,
status: { project: { ready: true } },
connections: [{ id: 'warehouse' }],
semanticLayer: { totalSources: 1 },
});
});
it('dispatches SL list, SL read, wiki search, and wiki read through local ports', async () => {
for (const args of [
{ command: 'sl-list' as const, projectDir: tempDir, json: true as const, connectionId: 'warehouse' },
{
command: 'sl-read' as const,
projectDir: tempDir,
json: true as const,
connectionId: 'warehouse',
sourceName: 'orders',
},
{ command: 'wiki-search' as const, projectDir: tempDir, json: true as const, query: 'revenue', limit: 10 },
{ command: 'wiki-read' as const, projectDir: tempDir, json: true as const, pageId: 'page-1' },
]) {
const io = makeIo();
await expect(runKloAgent(args, io.io, { createRuntime: async () => runtime() })).resolves.toBe(0);
expect(JSON.parse(io.stdout())).toBeTruthy();
expect(io.stderr()).toBe('');
}
});
it('prints wiki hybrid search metadata from the hidden agent wiki search command', async () => {
const fakeRuntime = runtime();
const knowledge = fakeRuntime.ports.knowledge;
if (!knowledge) {
throw new Error('Expected runtime knowledge port');
}
fakeRuntime.ports.knowledge = {
...knowledge,
search: vi.fn(async () => ({
results: [
{
key: 'metrics/revenue',
path: 'knowledge/global/metrics/revenue.md',
scope: 'GLOBAL' as const,
summary: 'Revenue metric definition',
score: 0.02459016393442623,
matchReasons: ['lexical' as const, 'token' as const],
},
],
totalFound: 1,
})),
};
const io = makeIo();
await expect(
runKloAgent({ command: 'wiki-search', projectDir: tempDir, json: true, query: 'paid order', limit: 5 }, io.io, {
createRuntime: async () => fakeRuntime,
}),
).resolves.toBe(0);
expect(JSON.parse(io.stdout())).toEqual({
results: [
expect.objectContaining({
key: 'metrics/revenue',
path: 'knowledge/global/metrics/revenue.md',
matchReasons: ['lexical', 'token'],
}),
],
totalFound: 1,
});
});
it('executes SL queries from a JSON query file', async () => {
const queryFile = join(tempDir, 'sl-query.json');
const io = makeIo();
await writeFile(queryFile, '{"measures":["total_revenue"],"dimensions":[]}', 'utf-8');
await expect(
runKloAgent(
{
command: 'sl-query',
projectDir: tempDir,
json: true,
connectionId: 'warehouse',
queryFile,
execute: true,
maxRows: 100,
},
io.io,
{ createRuntime: async () => runtime() },
),
).resolves.toBe(0);
expect(JSON.parse(io.stdout())).toMatchObject({ sql: 'select 1', rows: [[1]] });
});
it('executes read-only SQL from a SQL file with an explicit row limit', async () => {
const sqlFile = join(tempDir, 'query.sql');
const fakeRuntime = runtime();
const io = makeIo();
await writeFile(sqlFile, 'select 1', 'utf-8');
await expect(
runKloAgent(
{
command: 'sql-execute',
projectDir: tempDir,
json: true,
connectionId: 'warehouse',
sqlFile,
maxRows: 100,
},
io.io,
{ createRuntime: async () => fakeRuntime as never },
),
).resolves.toBe(0);
expect(fakeRuntime.queryExecutor?.execute).toHaveBeenCalledWith({
connectionId: 'warehouse',
projectDir: '/tmp/revenue',
connection: { driver: 'sqlite', path: 'warehouse.sqlite', readonly: true },
sql: 'select 1',
maxRows: 100,
});
});
it('prints guided JSON when semantic-layer search runs outside a project', async () => {
const io = makeIo();
const missingProjectError = Object.assign(new Error('ENOENT: no such file or directory'), {
code: 'ENOENT',
path: join(tempDir, 'klo.yaml'),
});
await expect(
runKloAgent(
{ command: 'sl-list', projectDir: tempDir, json: true, query: 'gross revenue' },
io.io,
{ createRuntime: vi.fn(async () => Promise.reject(missingProjectError)) },
),
).resolves.toBe(1);
expect(JSON.parse(io.stderr())).toEqual({
ok: false,
error: {
code: 'agent_sl_search_missing_project',
message: `Semantic-layer search needs an initialized KLO project at ${tempDir}.`,
nextSteps: [
'klo demo',
`klo setup --project-dir ${tempDir}`,
'klo ingest <connection>',
`klo agent sl list --json --query "gross revenue" --project-dir ${tempDir}`,
],
},
});
expect(io.stdout()).toBe('');
});
it('prints guided JSON when semantic-layer search has no configured connections', async () => {
const io = makeIo();
await expect(
runKloAgent(
{ command: 'sl-list', projectDir: tempDir, json: true, query: 'revenue' },
io.io,
{ createRuntime: async () => runtimeWithoutConnections() },
),
).resolves.toBe(1);
expect(JSON.parse(io.stderr())).toMatchObject({
ok: false,
error: {
code: 'agent_sl_search_no_connections',
message: `Semantic-layer search found no configured connections in ${tempDir}.`,
nextSteps: [
'klo demo',
`klo setup --project-dir ${tempDir}`,
'klo ingest <connection>',
`klo agent sl list --json --query "revenue" --project-dir ${tempDir}`,
],
},
});
});
it('prints guided JSON when semantic-layer search asks for an unknown connection', async () => {
const io = makeIo();
await expect(
runKloAgent(
{ command: 'sl-list', projectDir: tempDir, json: true, connectionId: 'missing', query: 'revenue' },
io.io,
{ createRuntime: async () => runtime() },
),
).resolves.toBe(1);
expect(JSON.parse(io.stderr())).toMatchObject({
ok: false,
error: {
code: 'agent_sl_search_unknown_connection',
message: `Semantic-layer search connection "missing" is not configured in ${tempDir}.`,
},
});
});
it('prints guided JSON when semantic-layer search has no indexed sources', async () => {
const fakeRuntime = runtime();
const semanticLayer = fakeRuntime.ports.semanticLayer!;
fakeRuntime.ports.semanticLayer = {
...semanticLayer,
listSources: vi.fn(async () => ({ sources: [], totalSources: 0 })),
};
const io = makeIo();
await expect(
runKloAgent(
{ command: 'sl-list', projectDir: tempDir, json: true, connectionId: 'warehouse', query: 'revenue' },
io.io,
{ createRuntime: async () => fakeRuntime },
),
).resolves.toBe(1);
expect(JSON.parse(io.stderr())).toMatchObject({
ok: false,
error: {
code: 'agent_sl_search_no_indexed_sources',
message: `Semantic-layer search found no indexed semantic-layer sources in ${tempDir}.`,
},
});
});
it('returns JSON errors when required ports or records are missing', async () => {
const io = makeIo();
await expect(
runKloAgent({ command: 'wiki-read', projectDir: tempDir, json: true, pageId: 'missing' }, io.io, {
createRuntime: async () =>
runtime({
ports: { knowledge: { read: vi.fn(async () => null) } },
}) as never,
}),
).resolves.toBe(1);
expect(JSON.parse(io.stderr())).toMatchObject({
ok: false,
error: { message: expect.stringContaining('missing') },
});
});
});

214
packages/cli/src/agent.ts Normal file
View file

@ -0,0 +1,214 @@
import { readFile } from 'node:fs/promises';
import type { KloCliIo } from './cli-runtime.js';
import {
createKloAgentRuntime,
parseAgentMaxRows,
readAgentJsonFile,
writeAgentJson,
writeAgentJsonError,
type KloAgentRuntime,
type KloAgentRuntimeDeps,
} from './agent-runtime.js';
import {
isMissingProjectConfigError,
missingConnectionSlSearchReadiness,
missingProjectSlSearchReadiness,
noConnectionsSlSearchReadiness,
noIndexedSourcesSlSearchReadiness,
type KloAgentSlSearchReadinessDetail,
} from './agent-search-readiness.js';
import { readKloSetupStatus, type KloSetupStatus } from './setup.js';
export type KloAgentArgs =
| { command: 'tools'; projectDir: string; json: true }
| { command: 'context'; projectDir: string; json: true }
| { command: 'sl-list'; projectDir: string; json: true; connectionId?: string; query?: string }
| { command: 'sl-read'; projectDir: string; json: true; connectionId?: string; sourceName: string }
| {
command: 'sl-query';
projectDir: string;
json: true;
connectionId: string;
queryFile: string;
execute: boolean;
maxRows?: number;
}
| { command: 'wiki-search'; projectDir: string; json: true; query: string; limit: number }
| { command: 'wiki-read'; projectDir: string; json: true; pageId: string }
| { command: 'sql-execute'; projectDir: string; json: true; connectionId: string; sqlFile: string; maxRows?: number };
export interface KloAgentDeps extends KloAgentRuntimeDeps {
createRuntime?: (options: {
projectDir: string;
enableSemanticCompute: boolean;
enableQueryExecution: boolean;
}) => Promise<KloAgentRuntime>;
readSetupStatus?: (
projectDir: string,
) => Promise<KloSetupStatus | { project: { path?: string; ready: boolean }; agents: unknown[] }>;
}
const AGENT_TOOLS = [
{ name: 'context', command: 'klo agent context --json' },
{ name: 'sl.list', command: 'klo agent sl list --json [--connection-id <id>] [--query <text>]' },
{ name: 'sl.read', command: 'klo agent sl read <sourceName> --json [--connection-id <id>]' },
{
name: 'sl.query',
command: 'klo agent sl query --json --connection-id <id> --query-file <path> --execute --max-rows 100',
},
{ name: 'wiki.search', command: 'klo agent wiki search <query> --json [--limit 10]' },
{ name: 'wiki.read', command: 'klo agent wiki read <pageId> --json' },
{
name: 'sql.execute',
command: 'klo agent sql execute --json --connection-id <id> --sql-file <path> --max-rows 100',
},
] as const;
function writeAgentSlSearchReadinessError(io: KloCliIo, detail: KloAgentSlSearchReadinessDetail): void {
writeAgentJsonError(io, detail.message, { code: detail.code, nextSteps: detail.nextSteps });
}
async function runtimeFor(args: KloAgentArgs, deps: KloAgentDeps): Promise<KloAgentRuntime> {
const needsSemanticCompute = args.command === 'sl-query';
const needsQueryExecution = args.command === 'sql-execute' || (args.command === 'sl-query' && args.execute);
return deps.createRuntime
? deps.createRuntime({
projectDir: args.projectDir,
enableSemanticCompute: needsSemanticCompute,
enableQueryExecution: needsQueryExecution,
})
: createKloAgentRuntime(
{
projectDir: args.projectDir,
enableSemanticCompute: needsSemanticCompute,
enableQueryExecution: needsQueryExecution,
},
deps,
);
}
function connectionIdForSource(runtime: KloAgentRuntime, requested: string | undefined): string {
if (requested) return requested;
const ids = Object.keys(runtime.project.config.connections ?? {});
if (ids.length === 1) return ids[0] as string;
throw new Error('Use --connection-id when the project has zero or multiple connections.');
}
export async function runKloAgent(args: KloAgentArgs, io: KloCliIo, deps: KloAgentDeps = {}): Promise<number> {
try {
if (args.command === 'tools') {
writeAgentJson(io, { projectDir: args.projectDir, tools: AGENT_TOOLS });
return 0;
}
const runtime = await runtimeFor(args, deps);
if (args.command === 'context') {
const [status, connections, semanticLayer] = await Promise.all([
(deps.readSetupStatus ?? readKloSetupStatus)(args.projectDir),
runtime.ports.connections?.list() ?? [],
runtime.ports.semanticLayer?.listSources({}) ?? { sources: [], totalSources: 0 },
]);
writeAgentJson(io, { projectDir: args.projectDir, status, connections, semanticLayer, tools: AGENT_TOOLS });
return 0;
}
if (args.command === 'sl-list') {
const semanticLayer = runtime.ports.semanticLayer;
if (!semanticLayer) throw new Error('Semantic-layer tools are not available for this project.');
if (args.query) {
const connectionIds = Object.keys(runtime.project.config.connections ?? {});
if (args.connectionId && !runtime.project.config.connections[args.connectionId]) {
writeAgentSlSearchReadinessError(
io,
missingConnectionSlSearchReadiness(args.projectDir, args.connectionId, args.query),
);
return 1;
}
if (connectionIds.length === 0) {
writeAgentSlSearchReadinessError(io, noConnectionsSlSearchReadiness(args.projectDir, args.query));
return 1;
}
}
const listed = await semanticLayer.listSources({ connectionId: args.connectionId, query: args.query });
if (args.query && listed.sources.length === 0) {
const allSources = await semanticLayer.listSources({ connectionId: args.connectionId });
if (allSources.totalSources === 0) {
writeAgentSlSearchReadinessError(io, noIndexedSourcesSlSearchReadiness(args.projectDir, args.query));
return 1;
}
}
writeAgentJson(io, listed);
return 0;
}
if (args.command === 'sl-read') {
const semanticLayer = runtime.ports.semanticLayer;
if (!semanticLayer) throw new Error('Semantic-layer tools are not available for this project.');
const source = await semanticLayer.readSource({
connectionId: connectionIdForSource(runtime, args.connectionId),
sourceName: args.sourceName,
});
if (!source) throw new Error(`Semantic-layer source "${args.sourceName}" was not found.`);
writeAgentJson(io, source);
return 0;
}
if (args.command === 'sl-query') {
const semanticLayer = runtime.ports.semanticLayer;
if (!semanticLayer) throw new Error('Semantic-layer tools are not available for this project.');
const query = await readAgentJsonFile(args.queryFile);
const maxRows = args.execute ? parseAgentMaxRows(args.maxRows) : args.maxRows;
writeAgentJson(
io,
await semanticLayer.query({
connectionId: args.connectionId,
query: { ...query, ...(maxRows !== undefined ? { limit: maxRows } : {}) } as never,
}),
);
return 0;
}
if (args.command === 'wiki-search') {
const knowledge = runtime.ports.knowledge;
if (!knowledge) throw new Error('Wiki tools are not available for this project.');
writeAgentJson(io, await knowledge.search({ userId: 'agent', query: args.query, limit: args.limit }));
return 0;
}
if (args.command === 'wiki-read') {
const knowledge = runtime.ports.knowledge;
if (!knowledge) throw new Error('Wiki tools are not available for this project.');
const page = await knowledge.read({ userId: 'agent', key: args.pageId });
if (!page) throw new Error(`Wiki page "${args.pageId}" was not found.`);
writeAgentJson(io, page);
return 0;
}
const queryExecutor = runtime.queryExecutor;
if (!queryExecutor) throw new Error('SQL execution is not available for this project.');
const connection = runtime.project.config.connections[args.connectionId];
if (!connection) throw new Error(`Connection "${args.connectionId}" was not found.`);
const maxRows = parseAgentMaxRows(args.maxRows);
writeAgentJson(
io,
await queryExecutor.execute({
connectionId: args.connectionId,
projectDir: runtime.project.projectDir,
connection,
sql: await readFile(args.sqlFile, 'utf-8'),
maxRows,
}),
);
return 0;
} catch (error) {
if (args.command === 'sl-list' && args.query && isMissingProjectConfigError(error)) {
writeAgentSlSearchReadinessError(io, missingProjectSlSearchReadiness(args.projectDir, args.query));
return 1;
}
writeAgentJsonError(io, error instanceof Error ? error.message : String(error));
return 1;
}
}

9
packages/cli/src/bin.ts Normal file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env node
import { installStartupProfileReporter, profileMark, profileSpan } from './startup-profile.js';
installStartupProfileReporter();
profileMark('bin:entry');
const { runKloCli } = await profileSpan('import ./cli-runtime.js', () => import('./cli-runtime.js'));
profileMark('bin:runKloCli');
process.exitCode = await runKloCli(process.argv.slice(2));

11
packages/cli/src/clack.ts Normal file
View file

@ -0,0 +1,11 @@
import { spinner } from '@clack/prompts';
export interface KloCliSpinner {
start(message: string): void;
stop(message: string): void;
error(message: string): void;
}
export function createClackSpinner(): KloCliSpinner {
return spinner();
}

View file

@ -0,0 +1,268 @@
import { Command, InvalidArgumentError } from '@commander-js/extra-typings';
import type { KloCliDeps, KloCliIo, KloCliPackageInfo } from './cli-runtime.js';
import { registerAgentCommands } from './commands/agent-commands.js';
import { registerConnectionCommands } from './commands/connection-commands.js';
import { registerWikiCommands } from './commands/knowledge-commands.js';
import { registerPublicIngestCommands } from './commands/public-ingest-commands.js';
import { registerServeCommands } from './commands/serve-commands.js';
import { registerSetupCommands } from './commands/setup-commands.js';
import { registerSlCommands } from './commands/sl-commands.js';
import { registerStatusCommands } from './commands/status-commands.js';
import { registerDevCommands } from './dev.js';
import { findNearestKloProjectDir, resolveKloProjectDir } from './project-resolver.js';
import { profileMark, profileSpan } from './startup-profile.js';
profileMark('module:cli-program');
export interface KloCliCommandContext {
io: KloCliIo;
deps: KloCliDeps;
setExitCode: (code: number) => void;
runInit: (args: { projectDir: string; projectName?: string; force: boolean }, io: KloCliIo) => Promise<number>;
writeDebug?: (command: string, commandContext: CommandWithGlobalOptions) => void;
}
export interface OutputModeOptions {
plain?: boolean;
json?: boolean;
viz?: boolean;
input?: boolean;
}
interface KloCommanderProgramOptions {
runInit: (args: { projectDir: string; projectName?: string; force: boolean }, io: KloCliIo) => Promise<number>;
}
type CommanderExitLike = { exitCode: number; code: string; message: string };
interface KloGlobalOptionValues {
projectDir?: string;
debug?: boolean;
}
export interface CommandWithGlobalOptions {
opts: () => object;
optsWithGlobals?: () => object;
}
function isCommanderExit(error: unknown): error is CommanderExitLike {
return (
typeof error === 'object' &&
error !== null &&
'exitCode' in error &&
typeof (error as { exitCode: unknown }).exitCode === 'number' &&
'code' in error &&
typeof (error as { code: unknown }).code === 'string'
);
}
export function collectOption(value: string, previous: string[] = []): string[] {
return [...previous, value];
}
export function parsePositiveIntegerOption(value: string): number {
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed < 1) {
throw new InvalidArgumentError('must be a positive integer');
}
return parsed;
}
export function parseNonNegativeIntegerOption(value: string): number {
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed < 0) {
throw new InvalidArgumentError('must be a non-negative integer');
}
return parsed;
}
export function parseBooleanStringOption(value: string): boolean {
if (value === 'true') {
return true;
}
if (value === 'false') {
return false;
}
throw new InvalidArgumentError('must be true or false');
}
export function parseSafeConnectionIdOption(value: string): string {
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
throw new InvalidArgumentError(`Unsafe connection id: ${value}`);
}
return value;
}
export function parseNonEmptyAssignmentOption(value: string): { key: string; value: string } {
const separatorIndex = value.indexOf('=');
if (separatorIndex <= 0 || separatorIndex === value.length - 1) {
throw new InvalidArgumentError('must be a non-empty <key>=<value> assignment');
}
return {
key: value.slice(0, separatorIndex),
value: value.slice(separatorIndex + 1),
};
}
function optionsWithGlobals(command: CommandWithGlobalOptions): KloGlobalOptionValues {
const options = command.optsWithGlobals ? command.optsWithGlobals() : command.opts();
const values = options as { projectDir?: unknown; debug?: unknown };
return {
projectDir: typeof values.projectDir === 'string' ? values.projectDir : undefined,
debug: typeof values.debug === 'boolean' ? values.debug : undefined,
};
}
export function resolveCommandProjectDir(command: CommandWithGlobalOptions): string {
return resolveKloProjectDir({ explicitProjectDir: optionsWithGlobals(command).projectDir });
}
export function resolveCommandProjectDirOverride(command: CommandWithGlobalOptions): string | undefined {
return optionsWithGlobals(command).projectDir ?? process.env.KLO_PROJECT_DIR;
}
function createBaseProgram(info: KloCliPackageInfo, io: KloCliIo): Command {
return new Command()
.name('klo')
.description('Standalone KLO developer CLI')
.option('--project-dir <path>', 'KLO project directory (default: KLO_PROJECT_DIR, nearest klo.yaml, or cwd)')
.option('--debug', 'Enable diagnostic logging to stderr')
.version(`${info.name} ${info.version}`, '-v, --version', 'Show CLI version')
.helpOption('-h, --help', 'Show this help text')
.configureHelp({ showGlobalOptions: true })
.addHelpText(
'after',
'\nAdvanced:\n klo dev Low-level diagnostics, scans, adapter commands, and mapping tools.\n',
)
.showHelpAfterError()
.exitOverride()
.configureOutput({
writeOut: (chunk) => io.stdout.write(chunk),
writeErr: (chunk) => io.stderr.write(chunk),
outputError: (chunk, write) => write(chunk),
});
}
function writeDebug(io: KloCliIo, commandContext: CommandWithGlobalOptions, command: string): void {
const global = optionsWithGlobals(commandContext);
if (global.debug !== true) {
return;
}
io.stderr.write(`[debug] projectDir=${resolveCommandProjectDir(commandContext)}\n`);
io.stderr.write(`[debug] dispatch=${command}\n`);
}
function formatCliError(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
async function runBareInteractiveCommand(
program: Command,
io: KloCliIo,
context: KloCliCommandContext,
): Promise<number> {
const nearestProjectDir = findNearestKloProjectDir(process.cwd());
const envProjectDir = process.env.KLO_PROJECT_DIR;
const runner = context.deps.setup ?? (await import('./setup.js')).runKloSetup;
if (!nearestProjectDir && !envProjectDir) {
return await runner(
{
command: 'run',
projectDir: resolveKloProjectDir(),
mode: 'auto',
agents: false,
agentScope: 'project',
agentInstallMode: 'cli',
skipAgents: false,
inputMode: 'auto',
yes: false,
skipLlm: false,
skipEmbeddings: false,
databaseSchemas: [],
skipDatabases: false,
skipSources: false,
},
io,
);
}
program.outputHelp();
return 0;
}
export async function runCommanderKloCli(
argv: string[],
io: KloCliIo,
deps: KloCliDeps,
info: KloCliPackageInfo,
options: KloCommanderProgramOptions,
): Promise<number> {
profileMark('commander:entry');
let exitCode = 0;
const program = createBaseProgram(info, io);
profileMark('commander:base-program');
const context: KloCliCommandContext = {
io,
deps,
setExitCode: (code: number) => {
exitCode = code;
},
runInit: options.runInit,
writeDebug: (command: string, commandContext: CommandWithGlobalOptions) => {
writeDebug(io, commandContext, command);
},
};
registerSetupCommands(program, context);
profileMark('commander:register-setup');
registerConnectionCommands(program, context);
profileMark('commander:register-connection');
registerPublicIngestCommands(program, context);
profileMark('commander:register-public-ingest');
registerWikiCommands(program, context);
profileMark('commander:register-wiki');
registerSlCommands(program, context);
profileMark('commander:register-sl');
registerServeCommands(program, context);
profileMark('commander:register-serve');
registerStatusCommands(program, context);
profileMark('commander:register-status');
registerAgentCommands(program, context);
profileMark('commander:register-agent');
registerDevCommands(program, context);
profileMark('commander:register-dev');
if (argv.length === 0) {
if (io.stdout.isTTY === true) {
try {
return await runBareInteractiveCommand(program, io, context);
} catch (error) {
io.stderr.write(`${formatCliError(error)}\n`);
return 1;
}
}
program.outputHelp();
return 0;
}
try {
await profileSpan('commander:parseAsync', () => program.parseAsync(argv, { from: 'user' }));
} catch (error) {
if (isCommanderExit(error)) {
return error.exitCode === 0 ? 0 : 1;
}
io.stderr.write(`${formatCliError(error)}\n`);
return 1;
}
return exitCode;
}

View file

@ -0,0 +1,89 @@
import type { KloConnectionMetabaseSetupArgs } from './commands/connection-metabase-setup.js';
import type { KloConnectionNotionArgs } from './commands/connection-notion.js';
import type { KloAgentArgs } from './agent.js';
import type { KloConnectionArgs } from './connection.js';
import type { KloDemoArgs } from './demo.js';
import type { KloDoctorArgs } from './doctor.js';
import type { KloIngestArgs } from './ingest.js';
import type { KloKnowledgeArgs } from './knowledge.js';
import type { KloPublicIngestArgs } from './public-ingest.js';
import type { KloScanArgs } from './scan.js';
import type { KloServeArgs } from './serve.js';
import type { KloSetupArgs } from './setup.js';
import type { KloSlArgs } from './sl.js';
import { profileMark, profileSpan } from './startup-profile.js';
profileMark('module:cli-runtime');
export interface KloCliPackageInfo {
name: '@klo/cli';
version: '0.0.0-private';
contextPackageName: '@klo/context';
}
export interface KloCliIo {
stdout: { isTTY?: boolean; write(chunk: string): void };
stderr: { write(chunk: string): void };
}
export interface KloCliDeps {
serveStdio?: (args: KloServeArgs) => Promise<number>;
setup?: (args: KloSetupArgs, io: KloCliIo) => Promise<number>;
agent?: (args: KloAgentArgs, io: KloCliIo) => Promise<number>;
connection?: (args: KloConnectionArgs, io: KloCliIo) => Promise<number>;
connectionNotion?: (args: KloConnectionNotionArgs, io: KloCliIo) => Promise<number>;
connectionMetabaseSetup?: (args: KloConnectionMetabaseSetupArgs, io: KloCliIo) => Promise<number>;
demo?: (args: KloDemoArgs, io: KloCliIo) => Promise<number>;
doctor?: (args: KloDoctorArgs, io: KloCliIo) => Promise<number>;
ingest?: (args: KloIngestArgs, io: KloCliIo) => Promise<number>;
publicIngest?: (args: KloPublicIngestArgs, io: KloCliIo) => Promise<number>;
scan?: (args: KloScanArgs, io: KloCliIo) => Promise<number>;
knowledge?: (args: KloKnowledgeArgs, io: KloCliIo) => Promise<number>;
sl?: (args: KloSlArgs, io: KloCliIo) => Promise<number>;
}
export function getKloCliPackageInfo(): KloCliPackageInfo {
return {
name: '@klo/cli',
version: '0.0.0-private',
contextPackageName: '@klo/context',
};
}
async function runInit(
args: { projectDir: string; projectName?: string; force: boolean },
io: KloCliIo,
): Promise<number> {
const { initKloProject } = await import('@klo/context/project');
const result = await initKloProject({
projectDir: args.projectDir,
projectName: args.projectName,
force: args.force,
});
io.stdout.write(`Initialized KLO project at ${result.projectDir}\n`);
io.stdout.write(`Config: ${result.configPath}\n`);
io.stdout.write(`Commit: ${result.commitHash ?? 'none'}\n`);
return 0;
}
export async function runInitForCommander(
args: { projectDir: string; projectName?: string; force: boolean },
io: KloCliIo,
): Promise<number> {
return await runInit(args, io);
}
export async function runKloCli(
argv = process.argv.slice(2),
io: KloCliIo = process,
deps: KloCliDeps = {},
): Promise<number> {
const info = getKloCliPackageInfo();
profileMark('runtime:runKloCli');
const { runCommanderKloCli } = await profileSpan('import ./cli-program.js', () => import('./cli-program.js'));
return await runCommanderKloCli(argv, io, deps, info, {
runInit: runInitForCommander,
});
}

View file

@ -0,0 +1,85 @@
import { z } from 'zod';
const projectDirSchema = z.string().min(1);
const safeConnectionIdSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, 'Unsafe connection id');
const stringArraySchema = z.array(z.string());
export const connectionAddCommandSchema = z.object({
command: z.literal('add'),
projectDir: projectDirSchema,
driver: z.string().min(1),
connectionId: safeConnectionIdSchema,
url: z.string().optional(),
schemas: stringArraySchema,
readonly: z.boolean(),
force: z.boolean(),
allowLiteralCredentials: z.boolean(),
notion: z
.object({
authTokenRef: z.string().min(1),
crawlMode: z.enum(['all_accessible', 'selected_roots']),
rootPageIds: stringArraySchema,
rootDatabaseIds: stringArraySchema,
rootDataSourceIds: stringArraySchema,
maxPagesPerRun: z.number().int().positive().optional(),
maxKnowledgeCreatesPerRun: z.number().int().nonnegative().optional(),
maxKnowledgeUpdatesPerRun: z.number().int().nonnegative().optional(),
})
.optional(),
});
export const wikiWriteCommandSchema = z.object({
command: z.literal('write'),
projectDir: projectDirSchema,
key: z.string().min(1),
scope: z.enum(['GLOBAL', 'USER']),
userId: z.string().min(1),
summary: z.string().min(1),
content: z.string().min(1),
tags: stringArraySchema,
refs: stringArraySchema,
slRefs: stringArraySchema,
});
const orderBySchema = z.union([
z.string().min(1),
z.object({
field: z.string().min(1),
direction: z.enum(['asc', 'desc']).optional(),
}),
]);
export const slQueryCommandSchema = z.object({
command: z.literal('query'),
projectDir: projectDirSchema,
connectionId: z.string().min(1).optional(),
query: z.object({
measures: z.array(z.string().min(1)).min(1),
dimensions: stringArraySchema,
filters: stringArraySchema.optional(),
segments: stringArraySchema.optional(),
order_by: z.array(orderBySchema).optional(),
limit: z.number().int().positive().optional(),
include_empty: z.literal(true).optional(),
}),
format: z.enum(['json', 'sql']),
execute: z.boolean(),
maxRows: z.number().int().positive().optional(),
});
export const publicIngestRunCommandSchema = z.object({
command: z.literal('run'),
projectDir: projectDirSchema,
targetConnectionId: safeConnectionIdSchema.optional(),
all: z.boolean(),
json: z.boolean(),
inputMode: z.enum(['auto', 'disabled']),
});
export const publicIngestReadCommandSchema = z.object({
command: z.enum(['status', 'watch']),
projectDir: projectDirSchema,
runId: z.string().min(1).optional(),
json: z.boolean(),
inputMode: z.enum(['auto', 'disabled']),
});

View file

@ -0,0 +1,137 @@
import { Option, type Command } from '@commander-js/extra-typings';
import type { KloAgentArgs } from '../agent.js';
import type { KloCliCommandContext } from '../cli-program.js';
import { parsePositiveIntegerOption, resolveCommandProjectDir } from '../cli-program.js';
async function runAgent(context: KloCliCommandContext, args: KloAgentArgs): Promise<void> {
const runner = context.deps.agent ?? (await import('../agent.js')).runKloAgent;
context.setExitCode(await runner(args, context.io));
}
function jsonOption(): Option {
return new Option('--json', 'Print JSON output').makeOptionMandatory();
}
export function registerAgentCommands(program: Command, context: KloCliCommandContext): void {
const agent = program
.command('agent', { hidden: true })
.description('Machine-readable KLO commands for coding agents')
.showHelpAfterError();
agent.hook('preAction', (_thisCommand, actionCommand) => {
context.writeDebug?.('agent', actionCommand);
});
agent
.command('tools')
.description('Print available agent-facing KLO tools')
.addOption(jsonOption())
.action(async (_options, command) => {
await runAgent(context, { command: 'tools', projectDir: resolveCommandProjectDir(command), json: true });
});
agent
.command('context')
.description('Print project context for agent planning')
.addOption(jsonOption())
.action(async (_options, command) => {
await runAgent(context, { command: 'context', projectDir: resolveCommandProjectDir(command), json: true });
});
const sl = agent.command('sl').description('Semantic-layer agent commands');
sl.command('list')
.description('List semantic-layer sources')
.addOption(jsonOption())
.option('--connection-id <id>', 'Filter by connection id')
.option('--query <text>', 'Search source names and descriptions')
.action(async (options: { connectionId?: string; query?: string }, command) => {
await runAgent(context, {
command: 'sl-list',
projectDir: resolveCommandProjectDir(command),
json: true,
...(options.connectionId ? { connectionId: options.connectionId } : {}),
...(options.query ? { query: options.query } : {}),
});
});
sl.command('read')
.description('Read one semantic-layer source')
.argument('<sourceName>')
.addOption(jsonOption())
.option('--connection-id <id>', 'Connection id containing the source')
.action(async (sourceName: string, options: { connectionId?: string }, command) => {
await runAgent(context, {
command: 'sl-read',
projectDir: resolveCommandProjectDir(command),
json: true,
sourceName,
...(options.connectionId ? { connectionId: options.connectionId } : {}),
});
});
sl.command('query')
.description('Run a semantic-layer query JSON file')
.addOption(jsonOption())
.requiredOption('--connection-id <id>', 'Connection id for execution')
.requiredOption('--query-file <path>', 'JSON semantic-layer query file')
.option('--execute', 'Execute the compiled query against the connection', false)
.option('--max-rows <number>', 'Maximum rows to return when executing', parsePositiveIntegerOption)
.action(
async (
options: { connectionId: string; queryFile: string; execute: boolean; maxRows?: number },
command,
) => {
await runAgent(context, {
command: 'sl-query',
projectDir: resolveCommandProjectDir(command),
json: true,
connectionId: options.connectionId,
queryFile: options.queryFile,
execute: options.execute,
...(options.maxRows !== undefined ? { maxRows: options.maxRows } : {}),
});
},
);
const wiki = agent.command('wiki').description('KLO wiki agent commands');
wiki
.command('search')
.description('Search KLO wiki pages')
.argument('<query>')
.addOption(jsonOption())
.option('--limit <number>', 'Maximum search results', parsePositiveIntegerOption, 10)
.action(async (query: string, options: { limit: number }, command) => {
await runAgent(context, {
command: 'wiki-search',
projectDir: resolveCommandProjectDir(command),
json: true,
query,
limit: options.limit,
});
});
wiki
.command('read')
.description('Read one KLO wiki page')
.argument('<pageId>')
.addOption(jsonOption())
.action(async (pageId: string, _options, command) => {
await runAgent(context, { command: 'wiki-read', projectDir: resolveCommandProjectDir(command), json: true, pageId });
});
const sql = agent.command('sql').description('Safe SQL execution commands');
sql
.command('execute')
.description('Execute read-only SQL with a row limit')
.addOption(jsonOption())
.requiredOption('--connection-id <id>', 'Connection id for execution')
.requiredOption('--sql-file <path>', 'SQL file to execute')
.requiredOption('--max-rows <number>', 'Maximum rows to return', parsePositiveIntegerOption)
.action(async (options: { connectionId: string; sqlFile: string; maxRows: number }, command) => {
await runAgent(context, {
command: 'sql-execute',
projectDir: resolveCommandProjectDir(command),
json: true,
connectionId: options.connectionId,
sqlFile: options.sqlFile,
maxRows: options.maxRows,
});
});
}

View file

@ -0,0 +1,47 @@
import type { CommandUnknownOpts } from '@commander-js/extra-typings';
import type { KloCliCommandContext } from '../cli-program.js';
import { completeCommanderInput, installZshCompletion, zshCompletionScript } from '../completion.js';
export function registerCompletionCommands(
program: CommandUnknownOpts,
context: KloCliCommandContext,
completionRoot: CommandUnknownOpts = program,
): void {
program
.command('completion')
.description('Generate shell completion scripts')
.command('zsh')
.description('Generate zsh completion script')
.option('--install', 'Install zsh completion into ~/.zfunc and update ~/.zshrc', false)
.action(async (options: { install?: boolean }) => {
if (options.install === true) {
const result = await installZshCompletion();
context.io.stdout.write(`Installed zsh completion: ${result.completionPath}\n`);
context.io.stdout.write(`Updated zsh config: ${result.zshrcPath}\n`);
context.io.stdout.write('Restart your shell or run: source ~/.zshrc\n');
context.setExitCode(0);
return;
}
context.io.stdout.write(zshCompletionScript());
context.setExitCode(0);
});
program
.command('__complete', { hidden: true })
.description('Internal shell completion endpoint')
.requiredOption('--shell <shell>', 'Shell requesting completions')
.requiredOption('--position <position>', 'Current shell word position', (value) => Number(value))
.argument('[words...]', 'Current shell words')
.allowUnknownOption()
.allowExcessArguments()
.action((words: string[], options: { shell: string; position: number }) => {
if (options.shell !== 'zsh') {
context.setExitCode(1);
return;
}
for (const completion of completeCommanderInput(completionRoot, { position: options.position, words })) {
context.io.stdout.write(`${completion}\n`);
}
context.setExitCode(0);
});
}

View file

@ -0,0 +1,346 @@
import { type Command, InvalidArgumentError, Option } from '@commander-js/extra-typings';
import {
collectOption,
type KloCliCommandContext,
parseBooleanStringOption,
parseNonEmptyAssignmentOption,
parseNonNegativeIntegerOption,
parsePositiveIntegerOption,
parseSafeConnectionIdOption,
resolveCommandProjectDir,
} from '../cli-program.js';
import { connectionAddCommandSchema } from '../command-schemas.js';
import type { KloConnectionArgs } from '../connection.js';
import { profileMark } from '../startup-profile.js';
import type { KloConnectionMappingArgs } from './connection-mapping.js';
import { registerConnectionMetabaseCommands } from './connection-metabase-commands.js';
import { registerConnectionNotionCommands } from './connection-notion-commands.js';
profileMark('module:commands/connection-commands');
const CRAWL_MODE_CHOICES = ['all_accessible', 'selected_roots'] as const;
const SYNC_MODE_CHOICES = ['ALL', 'ONLY', 'EXCEPT'] as const;
function parseCsvIds(value: string): number[] {
return value
.split(',')
.filter(Boolean)
.map((item) => parsePositiveIntegerOption(item));
}
function parseCsvStrings(value: string): string[] {
return value
.split(',')
.map((item) => item.trim())
.filter(Boolean);
}
function parseMappingFieldOption(value: string): 'databaseMappings' | 'connectionMappings' {
if (value === 'databaseMappings' || value === 'connectionMappings') {
return value;
}
throw new InvalidArgumentError('must be databaseMappings or connectionMappings');
}
async function runConnectionArgs(context: KloCliCommandContext, args: KloConnectionArgs): Promise<void> {
const runner = context.deps.connection ?? (await import('../connection.js')).runKloConnection;
context.setExitCode(await runner(args, context.io));
}
async function runMappingArgs(context: KloCliCommandContext, args: KloConnectionMappingArgs): Promise<void> {
const { runKloConnectionMapping } = await import('./connection-mapping.js');
context.setExitCode(await runKloConnectionMapping(args, context.io));
}
export function registerConnectionCommands(program: Command, context: KloCliCommandContext, commandName = 'connection'): void {
const connection = program
.command(commandName)
.description('Add, list, test, and map data sources')
.showHelpAfterError()
.addHelpText(
'after',
'\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the nearest klo.yaml or current working directory.\n',
);
connection.hook('preAction', (_thisCommand, actionCommand) => {
context.writeDebug?.(commandName, actionCommand);
});
connection
.command('list')
.description('List configured connections')
.action(async (_options: unknown, command) => {
await runConnectionArgs(context, { command: 'list', projectDir: resolveCommandProjectDir(command) });
});
connection
.command('test')
.description('Test a configured connection')
.argument('<connectionId>', 'KLO connection id')
.action(async (connectionId: string, _options: unknown, command) => {
await runConnectionArgs(context, {
command: 'test',
projectDir: resolveCommandProjectDir(command),
connectionId,
});
});
connection
.command('add')
.description('Add or replace a configured connection')
.argument('<driver>', 'Connection driver')
.argument('<connectionId>', 'KLO connection id')
.option('--url <url>', 'Connection URL, env:NAME, or file:/path reference')
.option('--schema <schema>', 'Schema to include; repeatable', collectOption, [])
.option('--readonly', 'Mark the connection as read-only', false)
.option('--force', 'Replace an existing connection', false)
.option('--allow-literal-credentials', 'Allow writing a literal credential URL to klo.yaml', false)
.addOption(new Option('--token-env <name>', 'Environment variable containing Notion auth token').conflicts('tokenFile'))
.addOption(new Option('--token-file <path>', 'File containing Notion auth token').conflicts('tokenEnv'))
.addOption(
new Option('--crawl-mode <mode>', 'Notion crawl mode: all_accessible or selected_roots')
.choices(CRAWL_MODE_CHOICES)
.default('selected_roots'),
)
.option('--root-page-id <id>', 'Root page to crawl; repeatable', collectOption, [])
.option('--root-database-id <id>', 'Root database to crawl; repeatable', collectOption, [])
.option('--root-data-source-id <id>', 'Root data source to crawl; repeatable', collectOption, [])
.option('--max-pages <n>', 'Maximum pages per run', parsePositiveIntegerOption)
.option('--max-knowledge-creates <n>', 'Maximum knowledge creates per run', parseNonNegativeIntegerOption)
.option('--max-knowledge-updates <n>', 'Maximum knowledge updates per run', parseNonNegativeIntegerOption)
.action(async (driver: string, connectionId: string, options, command) => {
const notion =
driver === 'notion'
? {
authTokenRef: options.tokenEnv
? `env:${options.tokenEnv}`
: options.tokenFile
? `file:${options.tokenFile}`
: '',
crawlMode: options.crawlMode,
rootPageIds: options.rootPageId,
rootDatabaseIds: options.rootDatabaseId,
rootDataSourceIds: options.rootDataSourceId,
maxPagesPerRun: options.maxPages,
maxKnowledgeCreatesPerRun: options.maxKnowledgeCreates,
maxKnowledgeUpdatesPerRun: options.maxKnowledgeUpdates,
}
: undefined;
if (driver === 'notion' && !notion?.authTokenRef) {
throw new Error('connection add notion requires --token-env NAME or --token-file PATH');
}
if (
driver === 'notion' &&
notion?.crawlMode === 'selected_roots' &&
notion.rootPageIds.length + notion.rootDatabaseIds.length + notion.rootDataSourceIds.length === 0
) {
throw new Error('connection add notion selected_roots requires at least one root id');
}
const args = connectionAddCommandSchema.parse({
command: 'add',
projectDir: resolveCommandProjectDir(command),
driver,
connectionId,
url: options.url,
schemas: options.schema.filter(Boolean),
readonly: options.readonly === true,
force: options.force === true,
allowLiteralCredentials: options.allowLiteralCredentials === true,
notion,
});
await runConnectionArgs(context, args);
});
connection
.command('remove')
.description('Remove a configured connection from klo.yaml')
.argument('<connectionId>', 'KLO connection id')
.option('--force', 'Remove without prompting', false)
.option('--no-input', 'Disable interactive terminal input')
.action(async (connectionId: string, options: { force?: boolean; input?: boolean }, command) => {
await runConnectionArgs(context, {
command: 'remove',
projectDir: resolveCommandProjectDir(command),
connectionId,
force: options.force === true,
...(options.input === false ? { inputMode: 'disabled' } : {}),
});
});
connection
.command('map')
.description('Refresh and validate BI-to-warehouse mappings')
.argument('<sourceConnectionId>', 'Source BI connection id')
.option('--json', 'Print JSON output', false)
.action(async (sourceConnectionId: string, options: { json?: boolean }, command) => {
await runConnectionArgs(context, {
command: 'map',
projectDir: resolveCommandProjectDir(command),
sourceConnectionId,
json: options.json === true,
});
});
registerConnectionMappingCommands(connection, context);
registerConnectionMetabaseCommands(connection, context);
registerConnectionNotionCommands(connection, context);
}
export function registerConnectionMappingCommands(connection: Command, context: KloCliCommandContext): void {
const mapping = connection
.command('mapping')
.description('Manage Metabase warehouse mappings')
.showHelpAfterError()
.addHelpText(
'after',
'\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n',
);
mapping
.command('list')
.description('List Metabase database mappings')
.argument('<connectionId>', 'Metabase connection id')
.option('--json', 'Print JSON output where supported', false)
.action(async (connectionId: string, options: { json?: boolean }, command) => {
await runMappingArgs(context, {
command: 'list',
projectDir: resolveCommandProjectDir(command),
connectionId,
json: options.json === true,
});
});
mapping
.command('set')
.description('Set a Metabase or Looker warehouse mapping')
.argument('<connectionId>', 'Source connection id', parseSafeConnectionIdOption)
.argument('<field>', 'Mapping field', parseMappingFieldOption)
.argument('<assignment>', 'Mapping assignment such as 1=prod-warehouse', parseNonEmptyAssignmentOption)
.action(
async (
connectionId: string,
field: 'databaseMappings' | 'connectionMappings',
assignment: { key: string; value: string },
_options: unknown,
command,
) => {
await runMappingArgs(context, {
command: 'set',
projectDir: resolveCommandProjectDir(command),
connectionId,
field,
key: assignment.key,
value: assignment.value,
});
},
);
mapping
.command('apply-bulk')
.description('Apply mappings from JSON')
.argument('<connectionId>', 'Metabase connection id')
.requiredOption('--file <path>', 'JSON mapping file')
.action(async (connectionId: string, options: { file: string }, command) => {
await runMappingArgs(context, {
command: 'apply-bulk',
projectDir: resolveCommandProjectDir(command),
connectionId,
filePath: options.file,
});
});
mapping
.command('set-sync-enabled')
.description('Enable or disable sync for one Metabase database')
.argument('<connectionId>', 'Metabase connection id')
.argument('<metabaseDatabaseId>', 'Metabase database id', parsePositiveIntegerOption)
.requiredOption('--enabled <value>', 'true or false', parseBooleanStringOption)
.action(
async (connectionId: string, metabaseDatabaseId: number, options: { enabled: boolean }, command) => {
await runMappingArgs(context, {
command: 'set-sync-enabled',
projectDir: resolveCommandProjectDir(command),
connectionId,
metabaseDatabaseId,
enabled: options.enabled,
});
},
);
const syncState = mapping.command('sync-state').description('Manage Metabase sync-state selection');
syncState
.command('get')
.description('Read sync-state selection')
.argument('<connectionId>', 'Metabase connection id')
.option('--json', 'Print JSON output where supported', false)
.action(async (connectionId: string, options: { json?: boolean }, command) => {
await runMappingArgs(context, {
command: 'sync-state-get',
projectDir: resolveCommandProjectDir(command),
connectionId,
json: options.json === true,
});
});
syncState
.command('set')
.description('Write sync-state selection')
.argument('<connectionId>', 'Metabase connection id')
.addOption(new Option('--mode <mode>', 'ALL, ONLY, or EXCEPT').choices(SYNC_MODE_CHOICES).makeOptionMandatory())
.option('--collections <ids>', 'Comma-separated collection ids', parseCsvIds, [])
.option('--items <ids>', 'Comma-separated item ids', parseCsvIds, [])
.option('--tag-names <names>', 'Comma-separated tag names', parseCsvStrings, [])
.action(async (connectionId: string, options, command) => {
await runMappingArgs(context, {
command: 'sync-state-set',
projectDir: resolveCommandProjectDir(command),
connectionId,
syncMode: options.mode,
collectionIds: options.collections,
itemIds: options.items,
tagNames: options.tagNames,
});
});
mapping
.command('refresh')
.description('Refresh Metabase database mappings')
.argument('<connectionId>', 'Metabase connection id')
.option('--auto-accept', 'Accept refresh changes without prompting', false)
.action(async (connectionId: string, options: { autoAccept?: boolean }, command) => {
await runMappingArgs(context, {
command: 'refresh',
projectDir: resolveCommandProjectDir(command),
connectionId,
autoAccept: options.autoAccept === true,
});
});
mapping
.command('validate')
.description('Validate Metabase database mappings')
.argument('<connectionId>', 'Metabase connection id')
.action(async (connectionId: string, _options: unknown, command) => {
await runMappingArgs(context, {
command: 'validate',
projectDir: resolveCommandProjectDir(command),
connectionId,
});
});
mapping
.command('clear')
.description('Clear Metabase database mappings')
.argument('<connectionId>', 'Metabase connection id')
.argument('[metabaseDatabaseId]', 'Metabase database id', parsePositiveIntegerOption)
.action(async (connectionId: string, metabaseDatabaseId: number | undefined, _options: unknown, command) => {
await runMappingArgs(context, {
command: 'clear',
projectDir: resolveCommandProjectDir(command),
connectionId,
...(metabaseDatabaseId ? { metabaseDatabaseId } : {}),
});
});
}

View file

@ -0,0 +1,329 @@
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { LocalMetabaseSourceStateReader } from '@klo/context/ingest';
import { initKloProject, loadKloProject, serializeKloProjectConfig } from '@klo/context/project';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { runKloConnectionMapping } from './connection-mapping.js';
function makeIo() {
let stdout = '';
let stderr = '';
return {
io: {
stdout: {
write: (chunk: string) => {
stdout += chunk;
},
},
stderr: {
write: (chunk: string) => {
stderr += chunk;
},
},
},
stdout: () => stdout,
stderr: () => stderr,
};
}
describe('runKloConnectionMapping', () => {
let tempDir: string;
let projectDir: string;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-metabase-mapping-'));
projectDir = join(tempDir, 'project');
await initKloProject({ projectDir, projectName: 'mapping' });
const project = await loadKloProject({ projectDir });
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig({
...project.config,
connections: {
'prod-metabase': {
driver: 'metabase',
api_url: 'https://metabase.example.com',
api_key_ref: 'env:METABASE_API_KEY', // pragma: allowlist secret
},
'prod-warehouse': {
driver: 'postgres',
url: 'env:WAREHOUSE_URL',
readonly: true,
},
},
}),
'klo',
'klo@example.com',
'Seed Metabase mapping test connections',
);
});
async function replaceConnections(connections: Record<string, { driver: string; [key: string]: unknown }>) {
const project = await loadKloProject({ projectDir });
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig({
...project.config,
connections,
}),
'klo',
'klo@example.com',
'Replace mapping test connections',
);
}
afterEach(async () => {
await rm(tempDir, { recursive: true, force: true });
});
it('sets, lists, disables, and clears local Metabase mappings', async () => {
const io = makeIo();
await expect(
runKloConnectionMapping(
{
command: 'set',
projectDir,
connectionId: 'prod-metabase',
field: 'databaseMappings',
key: '1',
value: 'prod-warehouse',
},
io.io,
),
).resolves.toBe(0);
const listIo = makeIo();
await expect(
runKloConnectionMapping({ command: 'list', projectDir, connectionId: 'prod-metabase', json: false }, listIo.io),
).resolves.toBe(0);
expect(listIo.stdout()).toContain('1 -> prod-warehouse');
expect(listIo.stdout()).toContain('unhydrated');
await expect(
runKloConnectionMapping(
{
command: 'set-sync-enabled',
projectDir,
connectionId: 'prod-metabase',
metabaseDatabaseId: 1,
enabled: false,
},
makeIo().io,
),
).resolves.toBe(0);
await expect(
runKloConnectionMapping(
{
command: 'clear',
projectDir,
connectionId: 'prod-metabase',
metabaseDatabaseId: 1,
},
makeIo().io,
),
).resolves.toBe(0);
});
it('lists Metabase yaml mapping bootstrap rows before any SQLite command writes', async () => {
const projectDir = await mkdtemp(join(tmpdir(), 'klo-cli-yaml-mapping-'));
await initKloProject({ projectDir, projectName: 'yaml-mapping' });
const project = await loadKloProject({ projectDir });
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig({
...project.config,
connections: {
'prod-metabase': {
driver: 'metabase',
mappings: {
databaseMappings: { '1': 'prod-warehouse' },
syncEnabled: { '1': true },
},
},
'prod-warehouse': { driver: 'postgres', url: 'postgresql://readonly@db.test/analytics' },
},
}),
'klo',
'klo@example.com',
'Seed yaml mappings',
);
const io = makeIo();
await expect(
runKloConnectionMapping(
{ command: 'list', projectDir, connectionId: 'prod-metabase', json: false },
io.io,
),
).resolves.toBe(0);
expect(io.stdout()).toContain('1 -> prod-warehouse');
expect(io.stdout()).toContain('source: klo.yaml');
});
it('refreshes Metabase discovery metadata through the injected runtime client', async () => {
const client = {
getDatabases: vi.fn().mockResolvedValue([
{
id: 1,
name: 'Analytics',
engine: 'postgres',
details: { host: 'pg.internal', dbname: 'analytics' },
is_sample: false,
},
]),
cleanup: vi.fn(),
};
const io = makeIo();
await expect(
runKloConnectionMapping(
{
command: 'refresh',
projectDir,
connectionId: 'prod-metabase',
autoAccept: true,
},
io.io,
{
createMetabaseClient: async () => client as never,
},
),
).resolves.toBe(0);
expect(io.stdout()).toContain('Discovery: 1 database');
expect(client.cleanup).toHaveBeenCalledTimes(1);
const store = new LocalMetabaseSourceStateReader({ dbPath: join(projectDir, '.klo', 'db.sqlite') });
await expect(store.listDatabaseMappings('prod-metabase')).resolves.toMatchObject([
{ metabaseDatabaseId: 1, metabaseDatabaseName: 'Analytics', source: 'refresh' },
]);
});
it('sets and lists Looker connection mappings', async () => {
await replaceConnections({
'prod-looker': {
driver: 'looker',
base_url: 'https://looker.example.test',
client_id: 'id',
},
'prod-warehouse': {
driver: 'postgres',
url: 'postgresql://readonly@db.example.test/analytics',
},
});
const io = makeIo();
await expect(
runKloConnectionMapping(
{
command: 'set',
projectDir,
connectionId: 'prod-looker',
field: 'connectionMappings',
key: 'analytics',
value: 'prod-warehouse',
},
io.io,
),
).resolves.toBe(0);
await expect(
runKloConnectionMapping({ command: 'list', projectDir, connectionId: 'prod-looker', json: false }, io.io),
).resolves.toBe(0);
expect(io.stdout()).toContain('analytics -> prod-warehouse');
});
it('keeps driver-specific mapping field validation in the runner', async () => {
await replaceConnections({
'prod-looker': { driver: 'looker', base_url: 'https://looker.example.com' },
warehouse: { driver: 'postgres', url: 'env:WAREHOUSE_URL' },
});
const io = makeIo();
await expect(
runKloConnectionMapping(
{
command: 'set',
projectDir,
connectionId: 'prod-looker',
field: 'databaseMappings',
key: '1',
value: 'warehouse',
},
io.io,
),
).resolves.toBe(1);
expect(io.stderr()).toContain('Looker mapping set requires connectionMappings');
});
it('refreshes Looker mapping metadata and reports drift', async () => {
await replaceConnections({
'prod-looker': {
driver: 'looker',
base_url: 'https://looker.example.test',
client_id: 'id',
},
'prod-warehouse': {
driver: 'postgres',
url: 'postgresql://readonly@db.example.test/analytics',
},
});
const io = makeIo();
await expect(
runKloConnectionMapping(
{ command: 'refresh', projectDir, connectionId: 'prod-looker', autoAccept: true },
io.io,
{
createLookerClient: async () => ({
listLookerConnections: async () => [
{
name: 'analytics',
host: 'db.example.test',
database: 'analytics',
schema: null,
dialect: 'postgres',
},
],
cleanup: async () => {},
}),
},
),
).resolves.toBe(0);
expect(io.stdout()).toContain('Discovery: 1 connection');
expect(io.stdout()).toContain('Unmapped discovered: 1');
});
it('validates Looker mappings through the canonical local warehouse descriptor', async () => {
const projectDir = await mkdtemp(join(tmpdir(), 'klo-cli-descriptor-validation-'));
await initKloProject({ projectDir, projectName: 'descriptor-validation' });
const project = await loadKloProject({ projectDir });
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig({
...project.config,
connections: {
'prod-looker': {
driver: 'looker',
mappings: { connectionMappings: { analytics: 'prod-warehouse' } },
},
'prod-warehouse': { driver: 'postgresql', url: 'postgresql://readonly@db.test/analytics' },
},
}),
'klo',
'klo@example.com',
'Seed descriptor validation',
);
const io = makeIo();
await expect(
runKloConnectionMapping({ command: 'validate', projectDir, connectionId: 'prod-looker' }, io.io),
).resolves.toBe(0);
expect(io.stdout()).toContain('Mapping validation passed: prod-looker');
expect(io.stderr()).toBe('');
});
});

View file

@ -0,0 +1,426 @@
import { readFile } from 'node:fs/promises';
import { localConnectionToWarehouseDescriptor } from '@klo/context/connections';
import {
DEFAULT_METABASE_CLIENT_CONFIG,
DefaultLookerConnectionClientFactory,
DefaultMetabaseConnectionClientFactory,
LocalLookerRuntimeStore,
LocalMetabaseSourceStateReader,
computeLookerMappingDrift,
computeMetabaseMappingDrift,
discoverLookerConnections,
discoverMetabaseDatabases,
lookerCredentialsFromLocalConnection,
metabaseRuntimeConfigFromLocalConnection,
seedLocalMappingStateFromKloYaml,
validateLookerMappings,
validateMappingPhysicalMatch,
type LookerMappingClient,
type MetabaseRuntimeClient,
type MetabaseSyncMode,
} from '@klo/context/ingest';
import { type KloLocalProject, kloLocalStateDbPath, loadKloProject } from '@klo/context/project';
import type { KloCliIo } from '../index.js';
import { profileMark } from '../startup-profile.js';
profileMark('module:commands/connection-mapping');
export type KloConnectionMappingArgs =
| { command: 'list'; projectDir: string; connectionId: string; json: boolean }
| {
command: 'set';
projectDir: string;
connectionId: string;
field: 'databaseMappings' | 'connectionMappings';
key: string;
value: string;
}
| { command: 'apply-bulk'; projectDir: string; connectionId: string; filePath: string }
| {
command: 'set-sync-enabled';
projectDir: string;
connectionId: string;
metabaseDatabaseId: number;
enabled: boolean;
}
| { command: 'sync-state-get'; projectDir: string; connectionId: string; json: boolean }
| {
command: 'sync-state-set';
projectDir: string;
connectionId: string;
syncMode: MetabaseSyncMode;
collectionIds: number[];
itemIds: number[];
tagNames: string[];
}
| { command: 'refresh'; projectDir: string; connectionId: string; autoAccept: boolean }
| { command: 'validate'; projectDir: string; connectionId: string }
| { command: 'clear'; projectDir: string; connectionId: string; metabaseDatabaseId?: number; mappingKey?: string };
interface KloConnectionMappingDeps {
createMetabaseClient?: (
project: KloLocalProject,
connectionId: string,
) => Promise<Pick<MetabaseRuntimeClient, 'getDatabases' | 'cleanup'>>;
createLookerClient?: (
project: KloLocalProject,
connectionId: string,
) => Promise<Pick<LookerMappingClient, 'listLookerConnections'> & { cleanup?(): Promise<void> }>;
}
interface MetabaseBulkMappingPayload {
databaseMappings?: Record<string, string | null>;
syncEnabled?: Record<string, boolean>;
syncMode?: MetabaseSyncMode;
selections?: { collections?: number[]; items?: number[] };
defaultTagNames?: string[];
}
function parseId(value: string, label: string): number {
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed < 1) {
throw new Error(`${label} must be a positive integer`);
}
return parsed;
}
async function createDefaultMetabaseClient(
project: KloLocalProject,
connectionId: string,
): Promise<Pick<MetabaseRuntimeClient, 'getDatabases' | 'cleanup'>> {
const factory = new DefaultMetabaseConnectionClientFactory(
(metabaseConnectionId) =>
metabaseRuntimeConfigFromLocalConnection(metabaseConnectionId, project.config.connections[metabaseConnectionId]),
DEFAULT_METABASE_CLIENT_CONFIG,
);
return factory.createClient(connectionId);
}
async function createDefaultLookerClient(
project: KloLocalProject,
connectionId: string,
): Promise<Pick<LookerMappingClient, 'listLookerConnections'> & { cleanup?(): Promise<void> }> {
const factory = new DefaultLookerConnectionClientFactory({
async resolve(lookerConnectionId) {
return lookerCredentialsFromLocalConnection(lookerConnectionId, project.config.connections[lookerConnectionId]);
},
});
return factory.createClient(connectionId) as unknown as Pick<LookerMappingClient, 'listLookerConnections'> & {
cleanup?(): Promise<void>;
};
}
function isLookerConnection(project: KloLocalProject, connectionId: string): boolean {
return String(project.config.connections[connectionId]?.driver ?? '').toLowerCase() === 'looker';
}
function assertLookerConnection(project: KloLocalProject, connectionId: string): void {
if (!isLookerConnection(project, connectionId)) {
throw new Error(`Connection "${connectionId}" is not a Looker connection`);
}
}
function assertMetabaseConnection(project: KloLocalProject, connectionId: string): void {
const connection = project.config.connections[connectionId];
if (!connection || String(connection.driver).toLowerCase() !== 'metabase') {
throw new Error(`Connection "${connectionId}" is not a Metabase connection`);
}
}
function assertTargetConnection(project: KloLocalProject, connectionId: string): void {
if (!project.config.connections[connectionId]) {
throw new Error(`Target connection "${connectionId}" does not exist`);
}
}
function targetPhysicalInfo(project: KloLocalProject, connectionId: string) {
const descriptor = localConnectionToWarehouseDescriptor(connectionId, project.config.connections[connectionId]);
if (!descriptor) {
return { connection_type: 'UNKNOWN' };
}
return {
connection_type: descriptor.connection_type,
host: descriptor.host ?? null,
database: descriptor.database ?? null,
account: descriptor.account ?? null,
project_id: descriptor.project_id ?? null,
dataset_id: descriptor.dataset_id ?? null,
...descriptor.connection_params,
};
}
function renderMapping(
row: Awaited<ReturnType<LocalMetabaseSourceStateReader['listDatabaseMappings']>>[number],
): string {
const name = row.metabaseDatabaseName ?? 'unhydrated';
const target = row.targetConnectionId ?? '[unmapped]';
return `${row.metabaseDatabaseId} -> ${target} (${name}, sync: ${row.syncEnabled ? 'on' : 'off'}, source: ${
row.source
})`;
}
function renderLookerMapping(row: Awaited<ReturnType<LocalLookerRuntimeStore['listConnectionMappings']>>[number]): string {
const target = row.kloConnectionId ?? '[unmapped]';
const metadata = [row.lookerDialect, row.lookerHost, row.lookerDatabase].filter(Boolean).join(', ');
return `${row.lookerConnectionName} -> ${target}${metadata ? ` (${metadata}, source: ${row.source})` : ` (source: ${row.source})`}`;
}
export async function runKloConnectionMapping(
args: KloConnectionMappingArgs,
io: KloCliIo = process,
deps: KloConnectionMappingDeps = {},
): Promise<number> {
try {
const project = await loadKloProject({ projectDir: args.projectDir });
await seedLocalMappingStateFromKloYaml(project, args.connectionId);
if (isLookerConnection(project, args.connectionId)) {
assertLookerConnection(project, args.connectionId);
const store = new LocalLookerRuntimeStore({ dbPath: kloLocalStateDbPath(project) });
if (args.command === 'list') {
const rows = await store.listConnectionMappings(args.connectionId);
io.stdout.write(args.json ? `${JSON.stringify(rows, null, 2)}\n` : `${rows.map(renderLookerMapping).join('\n')}\n`);
return 0;
}
if (args.command === 'set') {
if (args.field !== 'connectionMappings') {
throw new Error('Looker mapping set requires connectionMappings <lookerConnectionName>=<targetConnectionId>');
}
assertTargetConnection(project, args.value);
await store.upsertConnectionMapping({
lookerConnectionId: args.connectionId,
lookerConnectionName: args.key,
kloConnectionId: args.value,
source: 'cli',
});
io.stdout.write(`Set connectionMappings.${args.key} = ${args.value}\n`);
return 0;
}
if (args.command === 'refresh') {
const client = await (deps.createLookerClient ?? createDefaultLookerClient)(project, args.connectionId);
try {
const discovered = await discoverLookerConnections(client);
const drift = computeLookerMappingDrift({
storedMappings: await store.readMappings(args.connectionId),
discovered,
});
if (args.autoAccept) {
await store.refreshDiscoveredConnections({ lookerConnectionId: args.connectionId, discovered });
}
io.stdout.write(`Discovery: ${discovered.length} ${discovered.length === 1 ? 'connection' : 'connections'}\n`);
io.stdout.write(`Unmapped discovered: ${drift.unmappedDiscovered.length}\n`);
io.stdout.write(`Stale mappings: ${drift.staleMappings.length}\n`);
return 0;
} finally {
await client.cleanup?.();
}
}
if (args.command === 'validate') {
const knownKloConnectionIds = new Set(Object.keys(project.config.connections));
const knownConnectionTypes = new Map(
Object.entries(project.config.connections).map(([id, _config]) => [id, targetPhysicalInfo(project, id).connection_type]),
);
const validation = validateLookerMappings({
mappings: await store.readMappings(args.connectionId),
knownKloConnectionIds,
knownConnectionTypes,
});
if (!validation.ok) {
for (const error of validation.errors) {
io.stderr.write(`${error.key}: ${error.reason}\n`);
}
return 1;
}
io.stdout.write(`Mapping validation passed: ${args.connectionId}\n`);
return 0;
}
if (args.command === 'clear') {
await store.clearConnectionMappings({
lookerConnectionId: args.connectionId,
lookerConnectionName: args.mappingKey ?? (args.metabaseDatabaseId ? String(args.metabaseDatabaseId) : undefined),
});
io.stdout.write(
args.mappingKey
? `Cleared connectionMappings.${args.mappingKey}\n`
: `Cleared mappings for ${args.connectionId}\n`,
);
return 0;
}
throw new Error(`Looker connection mapping does not support ${args.command}`);
}
assertMetabaseConnection(project, args.connectionId);
const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(project) });
if (args.command === 'list') {
const rows = await store.listDatabaseMappings(args.connectionId);
io.stdout.write(args.json ? `${JSON.stringify(rows, null, 2)}\n` : `${rows.map(renderMapping).join('\n')}\n`);
return 0;
}
if (args.command === 'set') {
assertTargetConnection(project, args.value);
await store.upsertDatabaseMapping({
connectionId: args.connectionId,
metabaseDatabaseId: parseId(args.key, 'metabaseDatabaseId'),
targetConnectionId: args.value,
syncEnabled: true,
source: 'cli',
});
io.stdout.write(`Set databaseMappings.${args.key} = ${args.value}\n`);
return 0;
}
if (args.command === 'apply-bulk') {
const payload = JSON.parse(await readFile(args.filePath, 'utf8')) as MetabaseBulkMappingPayload;
const existingState = await store.getSourceState(args.connectionId);
const existingRows = await store.listDatabaseMappings(args.connectionId);
const existingById = new Map(existingRows.map((row) => [row.metabaseDatabaseId, row]));
const databaseMappings = payload.databaseMappings ?? {};
for (const targetConnectionId of Object.values(databaseMappings)) {
if (targetConnectionId) {
assertTargetConnection(project, targetConnectionId);
}
}
const mappingIds = new Set([
...existingRows.map((row) => row.metabaseDatabaseId),
...Object.keys(databaseMappings).map((id) => parseId(id, 'metabaseDatabaseId')),
...Object.keys(payload.syncEnabled ?? {}).map((id) => parseId(id, 'metabaseDatabaseId')),
]);
await store.replaceSourceState({
connectionId: args.connectionId,
syncMode: payload.syncMode ?? existingState.syncMode,
defaultTagNames: payload.defaultTagNames ?? existingState.defaultTagNames,
selections:
payload.selections === undefined
? existingState.selections
: [
...(payload.selections.collections ?? []).map((id) => ({
selectionType: 'collection' as const,
metabaseObjectId: id,
})),
...(payload.selections.items ?? []).map((id) => ({
selectionType: 'item' as const,
metabaseObjectId: id,
})),
],
mappings: [...mappingIds]
.sort((a, b) => a - b)
.map((id) => {
const existing = existingById.get(id);
return {
metabaseDatabaseId: id,
metabaseDatabaseName: existing?.metabaseDatabaseName ?? null,
metabaseEngine: existing?.metabaseEngine ?? null,
metabaseHost: existing?.metabaseHost ?? null,
metabaseDbName: existing?.metabaseDbName ?? null,
targetConnectionId: databaseMappings[String(id)] ?? existing?.targetConnectionId ?? null,
syncEnabled: payload.syncEnabled?.[String(id)] ?? existing?.syncEnabled ?? false,
source: 'cli',
};
}),
});
io.stdout.write(`Applied bulk mappings for ${args.connectionId}\n`);
return 0;
}
if (args.command === 'set-sync-enabled') {
await store.setMappingSyncEnabled({
connectionId: args.connectionId,
metabaseDatabaseId: args.metabaseDatabaseId,
syncEnabled: args.enabled,
});
io.stdout.write(`Set syncEnabled.${args.metabaseDatabaseId} = ${args.enabled}\n`);
return 0;
}
if (args.command === 'sync-state-get') {
const state = await store.getSourceState(args.connectionId);
const payload = {
syncMode: state.syncMode,
selections: state.selections,
defaultTagNames: state.defaultTagNames,
};
io.stdout.write(args.json ? `${JSON.stringify(payload, null, 2)}\n` : `${payload.syncMode}\n`);
return 0;
}
if (args.command === 'sync-state-set') {
await store.setSyncState({
connectionId: args.connectionId,
syncMode: args.syncMode,
defaultTagNames: args.tagNames,
selections: [
...args.collectionIds.map((id) => ({ selectionType: 'collection' as const, metabaseObjectId: id })),
...args.itemIds.map((id) => ({ selectionType: 'item' as const, metabaseObjectId: id })),
],
});
io.stdout.write(`Set sync state for ${args.connectionId}\n`);
return 0;
}
if (args.command === 'refresh') {
const client = await (deps.createMetabaseClient ?? createDefaultMetabaseClient)(project, args.connectionId);
try {
const discovered = await discoverMetabaseDatabases(client);
const existing = Object.fromEntries(
(await store.listDatabaseMappings(args.connectionId)).map((row) => [
String(row.metabaseDatabaseId),
row.targetConnectionId,
]),
);
const drift = computeMetabaseMappingDrift({ currentMappings: existing, discovered });
if (args.autoAccept) {
await store.refreshDiscoveredDatabases({ connectionId: args.connectionId, discovered });
}
io.stdout.write(`Discovery: ${discovered.length} ${discovered.length === 1 ? 'database' : 'databases'}\n`);
io.stdout.write(`Unmapped discovered: ${drift.unmappedDiscovered.length}\n`);
io.stdout.write(`Stale mappings: ${drift.staleMappings.length}\n`);
return 0;
} finally {
await client.cleanup();
}
}
if (args.command === 'validate') {
const rows = await store.listDatabaseMappings(args.connectionId);
const failures = rows.flatMap((row) => {
if (!row.targetConnectionId) {
return [];
}
const reason = validateMappingPhysicalMatch(
{ metabaseEngine: row.metabaseEngine, metabaseDbName: row.metabaseDbName, metabaseHost: row.metabaseHost },
project.config.connections[row.targetConnectionId]
? targetPhysicalInfo(project, row.targetConnectionId)
: { connection_type: 'UNKNOWN' },
);
return reason ? [`${row.metabaseDatabaseId}: ${reason}`] : [];
});
if (failures.length > 0) {
for (const failure of failures) {
io.stderr.write(`${failure}\n`);
}
return 1;
}
io.stdout.write(`Mapping validation passed: ${args.connectionId}\n`);
return 0;
}
const metabaseDatabaseId = args.metabaseDatabaseId ?? (args.mappingKey ? parseId(args.mappingKey, 'metabaseDatabaseId') : undefined);
await store.clearDatabaseMappings({ connectionId: args.connectionId, metabaseDatabaseId });
io.stdout.write(
metabaseDatabaseId
? `Cleared databaseMappings.${metabaseDatabaseId}\n`
: `Cleared mappings for ${args.connectionId}\n`,
);
return 0;
} catch (error) {
io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
return 1;
}
}

View file

@ -0,0 +1,132 @@
import { type Command, Option } from '@commander-js/extra-typings';
import {
type KloCliCommandContext,
parseNonEmptyAssignmentOption,
parsePositiveIntegerOption,
parseSafeConnectionIdOption,
resolveCommandProjectDir,
} from '../cli-program.js';
import {
type KloConnectionMetabaseSetupArgs,
type MetabaseSetupMappingAssignment,
type MetabaseSetupSyncMode,
runKloConnectionMetabaseSetup,
} from './connection-metabase-setup.js';
const SYNC_MODE_CHOICES = ['ALL', 'ONLY', 'EXCEPT'] as const satisfies readonly MetabaseSetupSyncMode[];
interface ConnectionMetabaseSetupOptions {
id?: string;
url?: string;
apiKey?: string;
mintApiKey?: boolean;
username?: string;
password?: string;
map: MetabaseSetupMappingAssignment[];
sync: number[];
syncMode: MetabaseSetupSyncMode;
runIngest?: boolean;
yes?: boolean;
input?: boolean;
}
function collectPositiveIntegerOption(value: string, previous: number[] = []): number[] {
return [...previous, parsePositiveIntegerOption(value)];
}
function parseMappingAssignment(value: string): MetabaseSetupMappingAssignment {
const assignment = parseNonEmptyAssignmentOption(value);
return {
metabaseDatabaseId: parsePositiveIntegerOption(assignment.key),
targetConnectionId: parseSafeConnectionIdOption(assignment.value),
};
}
function collectMappingOption(
value: string,
previous: MetabaseSetupMappingAssignment[] = [],
): MetabaseSetupMappingAssignment[] {
return [...previous, parseMappingAssignment(value)];
}
async function runMetabaseSetupArgs(
context: KloCliCommandContext,
args: KloConnectionMetabaseSetupArgs,
): Promise<void> {
const runner = context.deps.connectionMetabaseSetup ?? runKloConnectionMetabaseSetup;
context.setExitCode(await runner(args, context.io));
}
export function registerConnectionMetabaseCommands(connection: Command, context: KloCliCommandContext): void {
const metabase = connection
.command('metabase')
.description('Configure Metabase connections')
.showHelpAfterError()
.addHelpText(
'after',
'\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n',
);
metabase.action(() => {
metabase.outputHelp();
context.setExitCode(0);
});
metabase
.command('setup')
.description('Guided setup for a Metabase connection')
.option('--id <connectionId>', 'KLO connection id to write', parseSafeConnectionIdOption)
.option('--url <url>', 'Metabase API URL')
.addOption(new Option('--api-key <key>', 'Metabase API key').conflicts('mintApiKey'))
.option('--mint-api-key', 'Mint a Metabase API key with credentials', false)
.option('--username <email>', 'Metabase admin username for API-key minting')
.option('--password <password>', 'Metabase admin password for API-key minting')
.addHelpText(
'after',
'\nGuided equivalent of:\n' +
' klo connection mapping refresh <connectionId> --auto-accept\n' +
' klo connection mapping set <connectionId> databaseMappings <id>=<target>\n' +
' klo connection mapping set-sync-enabled <connectionId> <id> --enabled true\n' +
' klo ingest <connectionId>\n',
)
.option(
'--map <metabaseDatabaseId=targetConnectionId>',
'Assign a Metabase database id to a warehouse connection; repeatable',
collectMappingOption,
[],
)
.option(
'--sync <metabaseDatabaseId>',
'Enable Metabase sync for a discovered database; repeatable',
collectPositiveIntegerOption,
[],
)
.addOption(
new Option('--sync-mode <mode>', 'Metabase sync selection mode')
.choices(SYNC_MODE_CHOICES)
.default('ALL' satisfies MetabaseSetupSyncMode),
)
.option('--run-ingest', 'Run ingest after setup', false)
.option('--yes', 'Confirm and apply setup changes without prompting', false)
.option('--no-input', 'Disable interactive terminal input')
.showHelpAfterError()
.action(async (options: ConnectionMetabaseSetupOptions, command) => {
await runMetabaseSetupArgs(context, {
command: 'setup',
projectDir: resolveCommandProjectDir(command),
connectionId: options.id,
url: options.url,
apiKey: options.apiKey,
mintApiKey: options.mintApiKey === true,
metabaseUsername: options.username,
metabasePassword: options.password,
mappings: options.map,
syncEnabledDatabaseIds: options.sync,
syncMode: options.syncMode ?? 'ALL',
runIngest: options.runIngest === true,
yes: options.yes === true,
inputMode: options.input === false ? 'disabled' : 'auto',
});
});
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,782 @@
import type { Option as ClackOption } from '@clack/prompts';
import {
cancel,
confirm,
intro,
isCancel,
log,
multiselect,
note,
outro,
password,
select,
text,
} from '@clack/prompts';
import { localConnectionToWarehouseDescriptor } from '@klo/context/connections';
import {
DEFAULT_METABASE_CLIENT_CONFIG,
DefaultMetabaseConnectionClientFactory,
LocalMetabaseSourceStateReader,
MetabaseClient,
type MetabaseDatabase,
type MetabaseRuntimeClient,
type MetabaseSyncMode,
metabaseRuntimeConfigFromLocalConnection,
validateMappingPhysicalMatch,
} from '@klo/context/ingest';
import {
type KloLocalProject,
type KloProjectConnectionConfig,
kloLocalStateDbPath,
loadKloProject,
serializeKloProjectConfig,
} from '@klo/context/project';
import { createClackSpinner, type KloCliSpinner } from '../clack.js';
import type { KloCliIo } from '../cli-runtime.js';
import { withMenuOptionsSpacing, withMultiselectNavigation } from '../prompt-navigation.js';
import { type KloPublicIngestArgs, runKloPublicIngest } from '../public-ingest.js';
export type KloMetabaseSetupInputMode = 'auto' | 'disabled';
export type MetabaseSetupSyncMode = MetabaseSyncMode;
type MetabaseSetupPromptOption<Value> = ClackOption<Value>;
export interface MetabaseSetupLogger {
info(message: string): void;
step(message: string): void;
success(message: string): void;
warn(message: string): void;
error(message: string): void;
}
export interface MetabaseSetupPromptAdapter {
intro(title?: string): void;
outro(message?: string): void;
note(message: string, title: string): void;
log: MetabaseSetupLogger;
spinner(): KloCliSpinner;
select<T extends string>(options: { message: string; options: Array<MetabaseSetupPromptOption<T>> }): Promise<T>;
multiselect<Value extends number | string>(options: {
message: string;
options: Array<MetabaseSetupPromptOption<Value>>;
initialValues?: Value[];
required?: boolean;
maxItems?: number;
}): Promise<Value[]>;
text(options: { message: string; placeholder?: string }): Promise<string>;
password(options: { message: string }): Promise<string>;
confirm(options: { message: string; initialValue?: boolean }): Promise<boolean>;
cancel(message: string): void;
}
type KloMetabaseSetupInteractiveIo = KloCliIo & {
stdin?: { isTTY?: boolean };
};
export interface MetabaseSetupMappingAssignment {
metabaseDatabaseId: number;
targetConnectionId: string;
}
export interface MintMetabaseApiKeyArgs {
url: string;
username: string;
password: string;
}
export type MintMetabaseApiKey = (args: MintMetabaseApiKeyArgs, io: KloCliIo) => Promise<string>;
export interface KloConnectionMetabaseSetupArgs {
command: 'setup';
projectDir: string;
connectionId?: string;
url?: string;
apiKey?: string;
mintApiKey: boolean;
metabaseUsername?: string;
metabasePassword?: string;
mappings: MetabaseSetupMappingAssignment[];
syncEnabledDatabaseIds: number[];
syncMode: MetabaseSetupSyncMode;
runIngest: boolean;
yes: boolean;
inputMode: KloMetabaseSetupInputMode;
}
export interface KloConnectionMetabaseSetupDeps {
createMetabaseClient?: (
project: KloLocalProject,
connectionId: string,
) => Promise<Pick<MetabaseRuntimeClient, 'testConnection' | 'getDatabases' | 'cleanup'>>;
mintMetabaseApiKey?: MintMetabaseApiKey;
prompts?: MetabaseSetupPromptAdapter;
runPublicIngest?: (args: Extract<KloPublicIngestArgs, { command: 'run' }>, io: KloCliIo) => Promise<number>;
}
function isMetabaseConnection(connection: KloProjectConnectionConfig | undefined): boolean {
return (
String(connection?.driver ?? '')
.trim()
.toLowerCase() === 'metabase'
);
}
function stringField(value: unknown): string | undefined {
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
}
function uniqueSorted(values: number[]): number[] {
return [...new Set(values)].sort((a, b) => a - b);
}
function resolveMetabaseUrl(connection: KloProjectConnectionConfig | undefined): string | undefined {
return stringField(connection?.api_url) ?? stringField(connection?.apiUrl) ?? stringField(connection?.url);
}
function resolveLiteralMetabaseApiKey(connection: KloProjectConnectionConfig | undefined): string | undefined {
return stringField(connection?.api_key) ?? stringField(connection?.apiKey);
}
function listMetabaseConnectionIds(project: KloLocalProject): string[] {
return Object.entries(project.config.connections)
.filter(([_connectionId, connection]) => isMetabaseConnection(connection))
.map(([connectionId]) => connectionId)
.sort();
}
function listWarehouseConnectionIds(project: KloLocalProject): string[] {
return Object.entries(project.config.connections)
.filter(([connectionId, connection]) => localConnectionToWarehouseDescriptor(connectionId, connection) != null)
.map(([connectionId]) => connectionId)
.sort();
}
function redactSecrets(message: string, secrets: string[]): string {
let result = message;
for (const secret of secrets) {
if (!secret) {
continue;
}
result = result.split(secret).join('[redacted]');
}
return result;
}
async function createDefaultMetabaseClient(
project: KloLocalProject,
connectionId: string,
): Promise<Pick<MetabaseRuntimeClient, 'testConnection' | 'getDatabases' | 'cleanup'>> {
const factory = new DefaultMetabaseConnectionClientFactory(
(metabaseConnectionId) =>
metabaseRuntimeConfigFromLocalConnection(metabaseConnectionId, project.config.connections[metabaseConnectionId]),
DEFAULT_METABASE_CLIENT_CONFIG,
);
return factory.createClient(connectionId);
}
async function defaultMintMetabaseApiKey(args: MintMetabaseApiKeyArgs): Promise<string> {
const loginClient = new MetabaseClient({ apiUrl: args.url, apiKey: '' }, DEFAULT_METABASE_CLIENT_CONFIG);
const sessionId = await loginClient.createSession(args.username, args.password);
const sessionClient = new MetabaseClient(
{ apiUrl: args.url, apiKey: sessionId, authHeaderName: 'X-Metabase-Session' },
DEFAULT_METABASE_CLIENT_CONFIG,
);
const groups = await sessionClient.getPermissionGroups();
const adminGroup = groups.find((group) => group.name === 'Administrators');
if (!adminGroup) {
throw new Error('Metabase Administrators group was not found; create an API key manually and pass --api-key');
}
const mintedKey = await sessionClient.createApiKey({
groupId: adminGroup.id,
name: `KLO CLI ${new Date().toISOString()}`,
});
const trimmedKey = stringField(mintedKey);
if (!trimmedKey) {
throw new Error('Metabase API key minting returned an empty key');
}
return trimmedKey;
}
function ensureNotCancelled<T>(value: T | symbol, prompts: Pick<MetabaseSetupPromptAdapter, 'cancel'>): T {
if (isCancel(value)) {
prompts.cancel('Setup cancelled.');
throw new Error('Setup cancelled.');
}
return value as T;
}
export function createClackMetabaseSetupPromptAdapter(): MetabaseSetupPromptAdapter {
return {
intro(title?: string): void {
intro(title);
},
outro(message?: string): void {
outro(message);
},
note(message: string, title: string): void {
note(message, title);
},
log: {
info(message: string): void {
log.info(message);
},
step(message: string): void {
log.step(message);
},
success(message: string): void {
log.success(message);
},
warn(message: string): void {
log.warn(message);
},
error(message: string): void {
log.error(message);
},
},
spinner(): KloCliSpinner {
return createClackSpinner();
},
async select<T extends string>(options: {
message: string;
options: Array<MetabaseSetupPromptOption<T>>;
}): Promise<T> {
return ensureNotCancelled(await select(withMenuOptionsSpacing(options)), this);
},
async multiselect<Value extends number | string>(options: {
message: string;
options: Array<MetabaseSetupPromptOption<Value>>;
initialValues?: Value[];
required?: boolean;
maxItems?: number;
}): Promise<Value[]> {
return ensureNotCancelled(await multiselect(withMenuOptionsSpacing(options)), this);
},
async text(options: { message: string; placeholder?: string }): Promise<string> {
return ensureNotCancelled(await text(options), this);
},
async password(options: { message: string }): Promise<string> {
return ensureNotCancelled(await password(options), this);
},
async confirm(options: { message: string; initialValue?: boolean }): Promise<boolean> {
return ensureNotCancelled(await confirm(options), this);
},
cancel(message: string): void {
cancel(message);
},
};
}
function isInteractiveMetabaseSetupIo(
args: Pick<KloConnectionMetabaseSetupArgs, 'inputMode'>,
io: KloMetabaseSetupInteractiveIo,
): boolean {
return args.inputMode !== 'disabled' && io.stdin?.isTTY === true && io.stdout.isTTY === true;
}
function normalizeDiscoveredDatabases(databases: MetabaseDatabase[]): Array<{
id: number;
name: string;
engine: string;
host: string | null;
dbName: string | null;
}> {
return databases
.filter((database) => database.is_sample !== true)
.map((database) => ({
id: database.id,
name: database.name,
engine: stringField(database.engine) ?? 'unknown',
host: stringField(database.details?.host) ?? null,
dbName: stringField(database.details?.dbname) ?? null,
}));
}
function targetPhysicalInfo(project: KloLocalProject, connectionId: string) {
const descriptor = localConnectionToWarehouseDescriptor(connectionId, project.config.connections[connectionId]);
if (!descriptor) {
return { connection_type: 'UNKNOWN' };
}
return {
connection_type: descriptor.connection_type,
host: descriptor.host ?? null,
database: descriptor.database ?? null,
account: descriptor.account ?? null,
project_id: descriptor.project_id ?? null,
dataset_id: descriptor.dataset_id ?? null,
...descriptor.connection_params,
};
}
function noteMetabaseSetupSummary(options: {
prompts: MetabaseSetupPromptAdapter;
connectionId: string;
url: string;
mappings: MetabaseSetupMappingAssignment[];
syncEnabledDatabaseIds: number[];
}): void {
const mappingLines = options.mappings
.map((mapping) => ` ${mapping.metabaseDatabaseId} -> ${mapping.targetConnectionId}`)
.join('\n');
const syncLines = options.syncEnabledDatabaseIds.map((id) => ` ${id}`).join('\n');
options.prompts.note(
[
`Connection: ${options.connectionId}`,
`URL: ${options.url}`,
'',
'Mappings:',
mappingLines || ' (none)',
'',
'Sync enabled:',
syncLines || ' (none)',
].join('\n'),
'Summary',
);
}
export async function runKloConnectionMetabaseSetup(
args: KloConnectionMetabaseSetupArgs,
io: KloCliIo,
deps: KloConnectionMetabaseSetupDeps = {},
): Promise<number> {
let apiKeyForRedaction = args.apiKey;
let passwordForRedaction = args.metabasePassword;
const interactiveIo = io as KloMetabaseSetupInteractiveIo;
const isInteractive = isInteractiveMetabaseSetupIo(args, interactiveIo);
const prompts = deps.prompts ?? (isInteractive ? createClackMetabaseSetupPromptAdapter() : undefined);
try {
if (isInteractive && prompts) {
prompts.intro('KLO Metabase setup');
}
const project = await loadKloProject({ projectDir: args.projectDir });
const existingMetabaseConnectionIds = listMetabaseConnectionIds(project);
let connectionId: string;
if (args.connectionId) {
connectionId = args.connectionId;
} else if (existingMetabaseConnectionIds.length === 1) {
const onlyMetabaseConnectionId = existingMetabaseConnectionIds[0];
if (!onlyMetabaseConnectionId) {
throw new Error('No Metabase connection id was resolved');
}
connectionId = onlyMetabaseConnectionId;
} else if (existingMetabaseConnectionIds.length > 1) {
if (!isInteractive || !prompts) {
throw new Error(
`Multiple Metabase connections found (${existingMetabaseConnectionIds.join(', ')}); select one with --id`,
);
}
connectionId = await prompts.select({
message: 'Select the Metabase connection to configure',
options: existingMetabaseConnectionIds.map((id) => ({ value: id, label: id })),
});
} else {
connectionId = 'metabase';
}
const existingConnection = project.config.connections[connectionId];
const warehouseConnectionIds = listWarehouseConnectionIds(project);
if (warehouseConnectionIds.length === 0) {
throw new Error('Add a warehouse connection first');
}
let url = args.url ?? resolveMetabaseUrl(existingConnection);
let apiKey = args.apiKey ?? resolveLiteralMetabaseApiKey(existingConnection);
apiKeyForRedaction = apiKey;
if (!url && isInteractive && prompts) {
url = stringField(
await prompts.text({
message: 'Metabase API URL',
placeholder: 'http://localhost:3000',
}),
);
}
if (args.inputMode === 'disabled' && !url) {
throw new Error('missing Metabase URL');
}
if (!args.apiKey && !args.mintApiKey && apiKey && isInteractive && prompts && !args.yes) {
const reuse = await prompts.confirm({
message: `Reuse the existing Metabase API key from connections.${connectionId}?`,
initialValue: true,
});
if (!reuse) {
apiKey = undefined;
apiKeyForRedaction = undefined;
}
}
if (args.mintApiKey) {
let username = stringField(args.metabaseUsername);
let metabasePassword = stringField(args.metabasePassword);
if (isInteractive && prompts) {
if (!username) {
username = stringField(await prompts.text({ message: 'Metabase admin username' }));
}
if (!metabasePassword) {
metabasePassword = stringField(await prompts.password({ message: 'Metabase admin password' }));
}
}
if (!username) {
throw new Error('--mint-api-key requires --username');
}
if (!metabasePassword) {
throw new Error('--mint-api-key requires --password');
}
if (!url) {
throw new Error('Metabase URL is required (use --url)');
}
passwordForRedaction = metabasePassword;
apiKey = await (deps.mintMetabaseApiKey ?? defaultMintMetabaseApiKey)(
{ url, username, password: metabasePassword },
io,
);
apiKeyForRedaction = apiKey;
}
if (!apiKey && isInteractive && prompts) {
const credentialMode = await prompts.select({
message: 'Metabase credentials',
options: [
{ value: 'paste', label: 'Paste API key' },
{ value: 'mint', label: 'Mint API key' },
],
});
if (credentialMode === 'paste') {
apiKey = stringField(await prompts.password({ message: 'Metabase API key' }));
apiKeyForRedaction = apiKey;
} else {
const username = stringField(await prompts.text({ message: 'Metabase admin username' }));
const metabasePassword = stringField(await prompts.password({ message: 'Metabase admin password' }));
if (!username) {
throw new Error('Metabase username is required');
}
if (!metabasePassword) {
throw new Error('Metabase password is required');
}
if (!url) {
throw new Error('Metabase URL is required (use --url)');
}
passwordForRedaction = metabasePassword;
apiKey = await (deps.mintMetabaseApiKey ?? defaultMintMetabaseApiKey)(
{ url, username, password: metabasePassword },
io,
);
apiKeyForRedaction = apiKey;
}
}
if (args.inputMode === 'disabled' && !apiKey) {
throw new Error('missing Metabase API key');
}
if (!url) {
throw new Error('Metabase URL is required (use --url)');
}
if (!apiKey) {
throw new Error('Metabase API key is required (use --api-key)');
}
const transientConnectionConfig: KloProjectConnectionConfig = {
...(existingConnection ?? {}),
driver: 'metabase',
api_url: url,
api_key: apiKey,
};
const configWithTransient = {
...project.config,
connections: {
...project.config.connections,
[connectionId]: transientConnectionConfig,
},
};
const discoveryProject: KloLocalProject = { ...project, config: configWithTransient };
for (const mapping of args.mappings) {
if (!configWithTransient.connections[mapping.targetConnectionId]) {
throw new Error(`Target connection "${mapping.targetConnectionId}" does not exist`);
}
}
const client = await (deps.createMetabaseClient ?? createDefaultMetabaseClient)(discoveryProject, connectionId);
try {
const authSpinner = isInteractive && prompts ? prompts.spinner() : undefined;
authSpinner?.start('Testing Metabase connection');
const testResult = await client.testConnection();
if (!testResult.success) {
authSpinner?.error('Metabase authentication failed');
throw new Error(
`Metabase authentication failed. Replace connections.${connectionId}.api_key or use --mint-api-key.`,
);
}
authSpinner?.stop('Metabase reachable');
const discoverySpinner = isInteractive && prompts ? prompts.spinner() : undefined;
discoverySpinner?.start('Discovering Metabase databases');
const discovered = normalizeDiscoveredDatabases(await client.getDatabases());
discoverySpinner?.stop(`Discovered ${discovered.length} ${discovered.length === 1 ? 'database' : 'databases'}`);
if (isInteractive && prompts) {
prompts.log.success(
`Discovered ${discovered.length} ${discovered.length === 1 ? 'database' : 'databases'}`,
);
}
if (discovered.length === 0) {
throw new Error('Metabase auth worked but no usable databases were returned');
}
let resolvedMappings = args.mappings;
let resolvedSyncEnabledDatabaseIds = args.syncEnabledDatabaseIds;
if (resolvedSyncEnabledDatabaseIds.length === 0 && args.yes && resolvedMappings.length > 0) {
resolvedSyncEnabledDatabaseIds = uniqueSorted(resolvedMappings.map((mapping) => mapping.metabaseDatabaseId));
}
if (resolvedMappings.length === 0 && resolvedSyncEnabledDatabaseIds.length === 0) {
const onlyDiscoveredDatabase = discovered.length === 1 ? discovered[0] : undefined;
const compatibleWarehouses = onlyDiscoveredDatabase
? warehouseConnectionIds.filter((warehouseConnectionId) => {
const mismatchReason = validateMappingPhysicalMatch(
{
metabaseEngine: onlyDiscoveredDatabase.engine,
metabaseDbName: onlyDiscoveredDatabase.dbName,
metabaseHost: onlyDiscoveredDatabase.host,
},
targetPhysicalInfo(project, warehouseConnectionId),
);
return !mismatchReason;
})
: [];
const onlyWarehouseConnectionId = compatibleWarehouses[0];
if (onlyDiscoveredDatabase && compatibleWarehouses.length === 1 && onlyWarehouseConnectionId) {
if (args.yes) {
resolvedMappings = [
{ metabaseDatabaseId: onlyDiscoveredDatabase.id, targetConnectionId: onlyWarehouseConnectionId },
];
resolvedSyncEnabledDatabaseIds = [onlyDiscoveredDatabase.id];
} else if (isInteractive && prompts) {
const proposedMappings = [
{ metabaseDatabaseId: onlyDiscoveredDatabase.id, targetConnectionId: onlyWarehouseConnectionId },
];
const proposedSyncEnabledDatabaseIds = [onlyDiscoveredDatabase.id];
noteMetabaseSetupSummary({
prompts,
connectionId,
url,
mappings: proposedMappings,
syncEnabledDatabaseIds: proposedSyncEnabledDatabaseIds,
});
const confirmed = await prompts.confirm({
message: `Map Metabase database "${onlyDiscoveredDatabase.name}" (${onlyDiscoveredDatabase.id}) to "${onlyWarehouseConnectionId}" and enable sync?`,
initialValue: true,
});
if (!confirmed) {
prompts.cancel('Setup cancelled.');
throw new Error('Setup cancelled.');
}
resolvedMappings = proposedMappings;
resolvedSyncEnabledDatabaseIds = proposedSyncEnabledDatabaseIds;
} else {
throw new Error('Metabase mapping/sync is required in --no-input mode; pass --map and --sync');
}
} else if (isInteractive && prompts) {
const selectedDatabaseIds = await prompts.multiselect<number>({
message: withMultiselectNavigation('Select Metabase databases to configure'),
options: discovered.map((database) => ({
value: database.id,
label: `${database.id}: ${database.name}`,
hint: [database.engine, database.host, database.dbName].filter(Boolean).join(' • '),
})),
required: true,
});
resolvedMappings = [];
for (const databaseId of selectedDatabaseIds) {
const database = discovered.find((candidate) => candidate.id === databaseId);
if (!database) {
throw new Error(`Selected database id ${databaseId} was not discovered`);
}
const existingMapping = args.mappings.find((mapping) => mapping.metabaseDatabaseId === databaseId);
if (existingMapping) {
resolvedMappings.push(existingMapping);
continue;
}
const targetConnectionId = await prompts.select({
message: `Map Metabase database ${database.id} ("${database.name}") to which KLO connection?`,
options: warehouseConnectionIds.map((warehouseId) => ({ value: warehouseId, label: warehouseId })),
});
resolvedMappings.push({ metabaseDatabaseId: databaseId, targetConnectionId });
}
const syncIds = await prompts.multiselect<number>({
message: withMultiselectNavigation('Enable sync for which databases?'),
options: selectedDatabaseIds.map((id) => ({ value: id, label: String(id) })),
initialValues: selectedDatabaseIds,
required: true,
});
resolvedSyncEnabledDatabaseIds = uniqueSorted(syncIds);
if (!args.yes) {
noteMetabaseSetupSummary({
prompts,
connectionId,
url,
mappings: resolvedMappings,
syncEnabledDatabaseIds: resolvedSyncEnabledDatabaseIds,
});
const confirmed = await prompts.confirm({
message: 'Write changes to klo.yaml and enable sync?',
initialValue: true,
});
if (!confirmed) {
prompts.cancel('Setup cancelled.');
throw new Error('Setup cancelled.');
}
}
} else if (args.inputMode === 'disabled') {
throw new Error('Metabase mapping/sync is required in --no-input mode; pass --map and --sync');
}
}
if (
args.inputMode === 'disabled' &&
resolvedMappings.length > 0 &&
resolvedSyncEnabledDatabaseIds.length === 0
) {
throw new Error('Metabase sync selection is required in --no-input mode; pass --sync <metabaseDatabaseId>');
}
const discoveredIds = new Set(discovered.map((database) => database.id));
for (const mapping of resolvedMappings) {
if (!discoveredIds.has(mapping.metabaseDatabaseId)) {
throw new Error(`Mapped database id ${mapping.metabaseDatabaseId} was not discovered`);
}
}
for (const syncId of resolvedSyncEnabledDatabaseIds) {
if (!discoveredIds.has(syncId)) {
throw new Error(`Sync database id ${syncId} was not discovered`);
}
}
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig(configWithTransient),
'klo',
'klo@example.com',
`Setup Metabase connection ${connectionId}`,
);
const updatedProject = await loadKloProject({ projectDir: args.projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) });
await store.refreshDiscoveredDatabases({ connectionId, discovered });
for (const mapping of resolvedMappings) {
await store.upsertDatabaseMapping({
connectionId,
metabaseDatabaseId: mapping.metabaseDatabaseId,
targetConnectionId: mapping.targetConnectionId,
syncEnabled: false,
source: 'cli',
});
}
for (const metabaseDatabaseId of resolvedSyncEnabledDatabaseIds) {
await store.setMappingSyncEnabled({
connectionId,
metabaseDatabaseId,
syncEnabled: true,
});
}
const existingSyncState = await store.getSourceState(connectionId);
await store.setSyncState({
connectionId,
syncMode: args.syncMode,
defaultTagNames: existingSyncState.defaultTagNames,
selections: existingSyncState.selections,
});
const unhydrated = await store.getUnhydratedSyncEnabledMappingIds(connectionId);
if (unhydrated.length > 0) {
io.stderr.write(
`Sync-enabled mappings are missing discovery metadata; run klo connection mapping refresh ${connectionId} --auto-accept\n`,
);
return 1;
}
const rows = await store.listDatabaseMappings(connectionId);
const physicalFailures = rows.flatMap((row) => {
if (!row.targetConnectionId) {
return [];
}
const reason = validateMappingPhysicalMatch(
{ metabaseEngine: row.metabaseEngine, metabaseDbName: row.metabaseDbName, metabaseHost: row.metabaseHost },
updatedProject.config.connections[row.targetConnectionId]
? targetPhysicalInfo(updatedProject, row.targetConnectionId)
: { connection_type: 'UNKNOWN' },
);
return reason ? [`${row.metabaseDatabaseId}: ${reason}`] : [];
});
if (physicalFailures.length > 0) {
for (const failure of physicalFailures) {
io.stderr.write(`${failure}\n`);
}
return 1;
}
io.stdout.write(`Connection: ${connectionId}\n`);
io.stdout.write(`Discovered ${discovered.length} ${discovered.length === 1 ? 'database' : 'databases'}\n`);
io.stdout.write(`Next: klo ingest ${connectionId} --project-dir ${args.projectDir}\n`);
if (args.runIngest) {
const ingestRunner = deps.runPublicIngest ?? runKloPublicIngest;
const exitCode = await ingestRunner(
{
command: 'run',
projectDir: args.projectDir,
targetConnectionId: connectionId,
all: false,
json: false,
inputMode: 'disabled',
},
io,
);
if (exitCode !== 0) {
io.stderr.write(`Ingest failed; re-run: klo ingest ${connectionId} --project-dir ${args.projectDir}\n`);
return 1;
}
}
if (isInteractive && prompts) {
prompts.outro('Metabase setup complete');
}
return 0;
} finally {
await client.cleanup();
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
io.stderr.write(
`${redactSecrets(message, [apiKeyForRedaction ?? '', passwordForRedaction ?? '', args.apiKey ?? ''])}\n`,
);
return 1;
}
}

View file

@ -0,0 +1,92 @@
import { type Command, InvalidArgumentError } from '@commander-js/extra-typings';
import { collectOption, type KloCliCommandContext, resolveCommandProjectDir } from '../cli-program.js';
import type { KloConnectionNotionArgs } from './connection-notion.js';
interface NotionPickOptions {
input?: boolean;
rootPageId: string[];
}
function parseSafeConnectionId(value: string): string {
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
throw new InvalidArgumentError(`Unsafe connection id: ${value}`);
}
return value;
}
function uniqueInOrder(values: string[]): string[] {
const seen = new Set<string>();
const result: string[] = [];
for (const value of values) {
if (!seen.has(value)) {
seen.add(value);
result.push(value);
}
}
return result;
}
function normalizeNotionPageId(value: string): string {
const trimmed = value.trim();
const compact = trimmed.includes('-') ? trimmed.replace(/-/g, '') : trimmed;
if (!/^[0-9a-fA-F]{32}$/.test(compact)) {
throw new Error(`Invalid Notion page UUID: ${value}`);
}
const lower = compact.toLowerCase();
return `${lower.slice(0, 8)}-${lower.slice(8, 12)}-${lower.slice(12, 16)}-${lower.slice(16, 20)}-${lower.slice(20)}`;
}
function buildPickArgs(connectionId: string, projectDir: string, options: NotionPickOptions): KloConnectionNotionArgs {
if (options.input !== false) {
return {
command: 'pick',
projectDir,
connectionId,
mode: 'interactive',
};
}
const rootPageIds = uniqueInOrder(options.rootPageId.map(normalizeNotionPageId));
if (rootPageIds.length === 0) {
throw new Error('connection notion pick --no-input requires at least one --root-page-id');
}
return {
command: 'pick',
projectDir,
connectionId,
mode: 'non-interactive',
rootPageIds,
};
}
async function runConnectionNotionArgs(context: KloCliCommandContext, args: KloConnectionNotionArgs): Promise<void> {
const runner = context.deps.connectionNotion ?? (await import('./connection-notion.js')).runKloConnectionNotion;
context.setExitCode(await runner(args, context.io));
}
export function registerConnectionNotionCommands(connect: Command, context: KloCliCommandContext): void {
const notion = connect
.command('notion')
.description('Configure Notion source selection')
.showHelpAfterError()
.addHelpText(
'after',
'\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n',
);
notion.action(() => {
notion.outputHelp();
context.setExitCode(0);
});
notion
.command('pick')
.description('Pick Notion root pages for a configured Notion connection')
.argument('<connectionId>', 'Notion connection id', parseSafeConnectionId)
.option('--no-input', 'Disable interactive terminal input')
.option('--root-page-id <id>', 'Root page UUID to crawl; repeatable with --no-input', collectOption, [])
.showHelpAfterError()
.action(async (connectionId: string, options: NotionPickOptions, command) => {
await runConnectionNotionArgs(context, buildPickArgs(connectionId, resolveCommandProjectDir(command), options));
});
}

View file

@ -0,0 +1,283 @@
import { describe, expect, it } from 'vitest';
import {
buildInitialState,
buildPickerTree,
canToggle,
clearExpiredTransientHint,
filterTree,
flattenSelection,
moveCursor,
reducer,
selectAllVisible,
selectNone,
toggleChecked,
TRANSIENT_HINT_DURATION_MS,
visibleNodeIds,
type NotionPickerPageInput,
} from './connection-notion-tree.js';
const IDS = {
engineering: '11111111-1111-1111-1111-111111111111',
architecture: '22222222-2222-2222-2222-222222222222',
onboarding: '33333333-3333-3333-3333-333333333333',
marketing: '44444444-4444-4444-4444-444444444444',
journal: '55555555-5555-5555-5555-555555555555',
orphan: '66666666-6666-6666-6666-666666666666',
duplicate: '77777777-7777-7777-7777-777777777777',
cycleA: '88888888-8888-8888-8888-888888888888',
cycleB: '99999999-9999-9999-9999-999999999999',
};
function pages(): NotionPickerPageInput[] {
return [
{ id: IDS.marketing, title: 'Marketing', archived: false, parentId: null },
{ id: IDS.onboarding, title: 'Onboarding', archived: false, parentId: IDS.engineering },
{ id: IDS.engineering, title: 'Engineering Docs', archived: false, parentId: null },
{ id: IDS.architecture, title: 'Architecture', archived: false, parentId: IDS.engineering },
{ id: IDS.journal, title: 'Daily journal', archived: true, parentId: IDS.marketing },
{ id: IDS.orphan, title: '', archived: false, parentId: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' },
{ id: IDS.duplicate, title: 'Original duplicate', archived: false, parentId: null },
{ id: IDS.duplicate, title: 'Ignored duplicate', archived: true, parentId: IDS.marketing },
{ id: IDS.cycleA, title: 'Cycle A', archived: false, parentId: IDS.cycleB },
{ id: IDS.cycleB, title: 'Cycle B', archived: false, parentId: IDS.cycleA },
];
}
describe('buildPickerTree', () => {
it('deduplicates pages, sorts siblings, preserves archived flags, roots orphans, and breaks cycles', () => {
const tree = buildPickerTree(pages());
const byId = new Map(tree.map((node) => [node.id, node]));
expect(tree.map((node) => node.title)).toEqual([
'Cycle A',
'Cycle B',
'Engineering Docs',
'Architecture',
'Onboarding',
'Marketing',
'Daily journal',
'Original duplicate',
'Untitled',
]);
expect(byId.get(IDS.engineering)?.childIds).toEqual([IDS.architecture, IDS.onboarding]);
expect(byId.get(IDS.architecture)).toMatchObject({
depth: 1,
parentId: IDS.engineering,
path: 'Engineering Docs / Architecture',
});
expect(byId.get(IDS.journal)).toMatchObject({
archived: true,
depth: 1,
path: 'Marketing / Daily journal',
});
expect(byId.get(IDS.orphan)).toMatchObject({
title: 'Untitled',
parentId: null,
depth: 0,
path: 'Untitled',
});
expect(byId.get(IDS.duplicate)).toMatchObject({
title: 'Original duplicate',
archived: false,
parentId: null,
});
expect(byId.get(IDS.cycleA)?.parentId).toBeNull();
expect(byId.get(IDS.cycleB)?.parentId).toBe(IDS.cycleA);
});
});
describe('selection invariants', () => {
it('checking a parent locks descendants and keeps checked ids minimal', () => {
const state = buildInitialState({
tree: buildPickerTree(pages()),
existingRootPageIds: [],
currentCrawlMode: 'selected_roots',
});
const checkedParent = toggleChecked(state, IDS.engineering, 1000);
expect([...checkedParent.checked]).toEqual([IDS.engineering]);
expect(canToggle(IDS.architecture, checkedParent)).toEqual({
ok: false,
reason: "Locked by 'Engineering Docs' - uncheck parent first",
});
const lockedChildAttempt = toggleChecked(checkedParent, IDS.architecture, 2000);
expect([...lockedChildAttempt.checked]).toEqual([IDS.engineering]);
expect(lockedChildAttempt.transientHint).toEqual({
text: "Locked by 'Engineering Docs' - uncheck parent first",
expiresAt: 4500,
});
const uncheckedParent = toggleChecked(lockedChildAttempt, IDS.engineering, 3000);
expect([...uncheckedParent.checked]).toEqual([]);
expect(canToggle(IDS.architecture, uncheckedParent)).toEqual({ ok: true });
});
it('normalizes stored roots, reports stale roots, expands checked ancestors, and flattens descendants', () => {
const state = buildInitialState({
tree: buildPickerTree(pages()),
existingRootPageIds: [
IDS.engineering.replaceAll('-', ''),
IDS.architecture,
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
],
currentCrawlMode: 'selected_roots',
});
expect([...state.checked]).toEqual([IDS.engineering]);
expect([...state.expanded]).toEqual([]);
expect(state.cursorId).toBe(IDS.cycleA);
expect(state.preLoadWarnings).toEqual(['1 stored root_page_ids no longer visible']);
expect(flattenSelection(new Set([IDS.engineering, IDS.architecture]), state.byId)).toEqual([IDS.engineering]);
});
});
describe('search and cursor movement', () => {
it('filters by title and path while deriving auto-expanded ancestors', () => {
const state = buildInitialState({
tree: buildPickerTree(pages()),
existingRootPageIds: [],
currentCrawlMode: 'selected_roots',
});
const searching = {
...state,
search: { editing: false, query: 'architecture' },
};
expect(filterTree(searching)).toEqual({
visibleIds: new Set([IDS.engineering, IDS.architecture]),
autoExpand: new Set([IDS.engineering]),
});
expect(visibleNodeIds(searching)).toEqual([IDS.engineering, IDS.architecture]);
});
it('moves the cursor through visible nodes and implements left/right tree semantics', () => {
const state = buildInitialState({
tree: buildPickerTree(pages()),
existingRootPageIds: [],
currentCrawlMode: 'selected_roots',
});
const atEngineering = {
...state,
cursorId: IDS.engineering,
expanded: new Set([IDS.engineering]),
};
expect(moveCursor(atEngineering, 'down').cursorId).toBe(IDS.architecture);
expect(moveCursor({ ...atEngineering, cursorId: IDS.architecture }, 'up').cursorId).toBe(IDS.engineering);
expect(moveCursor(atEngineering, 'right').cursorId).toBe(IDS.architecture);
expect(moveCursor({ ...atEngineering, cursorId: IDS.architecture }, 'left').cursorId).toBe(IDS.engineering);
expect([...moveCursor(atEngineering, 'left').expanded]).toEqual([]);
expect([...moveCursor({ ...state, cursorId: IDS.marketing }, 'right').expanded]).toContain(IDS.marketing);
});
});
describe('bulk actions and reducer effects', () => {
it('selects only matching visible roots under search and clears selection', () => {
const state = buildInitialState({
tree: buildPickerTree(pages()),
existingRootPageIds: [IDS.marketing],
currentCrawlMode: 'selected_roots',
});
const searching = {
...state,
search: { editing: false, query: 'architecture' },
};
const selected = selectAllVisible(searching);
expect(flattenSelection(selected.checked, selected.byId)).toEqual([IDS.architecture, IDS.marketing]);
expect([...selectNone(selected).checked]).toEqual([]);
});
it('returns save immediately for selected_roots and requires confirmation for all_accessible', () => {
const selectedRoots = toggleChecked(
buildInitialState({
tree: buildPickerTree(pages()),
existingRootPageIds: [],
currentCrawlMode: 'selected_roots',
}),
IDS.marketing,
1000,
);
expect(reducer(selectedRoots, 'save-request')).toEqual({
next: selectedRoots,
effect: 'save',
});
const allAccessible = {
...selectedRoots,
currentCrawlMode: 'all_accessible' as const,
};
const confirm = reducer(allAccessible, 'save-request');
expect(confirm).toEqual({
next: { ...allAccessible, pendingConfirm: 'mode-switch' },
effect: null,
});
expect(reducer(confirm.next, 'save-cancel')).toEqual({
next: { ...allAccessible, pendingConfirm: null },
effect: null,
});
expect(reducer(confirm.next, 'save-confirm')).toEqual({
next: { ...allAccessible, pendingConfirm: null },
effect: 'save',
});
});
it('blocks empty saves, updates search state, and quits without saving', () => {
const state = buildInitialState({
tree: buildPickerTree(pages()),
existingRootPageIds: [],
currentCrawlMode: 'selected_roots',
});
const blockedSave = reducer(state, 'save-request', 9000);
expect(blockedSave).toEqual({
next: {
...state,
transientHint: {
text: 'Select at least one page or press q to quit',
expiresAt: 9000 + TRANSIENT_HINT_DURATION_MS,
},
},
effect: null,
});
expect(
reducer(
reducer(reducer(state, 'search-start').next, { type: 'search-input', value: 'a' }).next,
'search-submit',
).next.search,
).toEqual({ editing: false, query: 'a' });
expect(reducer(state, 'quit')).toEqual({
next: state,
effect: 'quit-without-save',
});
});
it('clears transient hints only when their expiry time has passed', () => {
const state = buildInitialState({
tree: buildPickerTree(pages()),
existingRootPageIds: [],
currentCrawlMode: 'selected_roots',
});
const withHint = {
...state,
transientHint: {
text: 'Select at least one page or press q to quit',
expiresAt: 11500,
},
};
expect(clearExpiredTransientHint(withHint, 11499)).toBe(withHint);
expect(clearExpiredTransientHint(withHint, 11500)).toEqual({
...withHint,
transientHint: null,
});
expect(reducer(withHint, 'clear-transient-hint', 11501)).toEqual({
next: {
...withHint,
transientHint: null,
},
effect: null,
});
});
});

View file

@ -0,0 +1,529 @@
export interface NotionPickerPageInput {
id: string;
title?: string | null;
archived?: boolean;
parentId?: string | null;
}
interface NotionPickerNode {
id: string;
title: string;
archived: boolean;
parentId: string | null;
depth: number;
childIds: string[];
path: string;
}
export interface PickerState {
tree: NotionPickerNode[];
byId: Map<string, NotionPickerNode>;
expanded: Set<string>;
checked: Set<string>;
cursorId: string;
search: { editing: boolean; query: string };
pendingConfirm: 'mode-switch' | null;
preLoadWarnings: string[];
transientHint: { text: string; expiresAt: number } | null;
currentCrawlMode: 'all_accessible' | 'selected_roots';
}
export type PickerCommand =
| 'cursor-up'
| 'cursor-down'
| 'cursor-left'
| 'cursor-right'
| 'expand'
| 'collapse'
| 'expand-all'
| 'collapse-all'
| 'toggle-check'
| 'select-all-visible'
| 'select-none'
| 'clear-transient-hint'
| 'search-start'
| 'search-cancel'
| 'search-submit'
| 'search-backspace'
| { type: 'search-input'; value: string }
| 'save-request'
| 'save-confirm'
| 'save-cancel'
| 'quit';
type PickerEffect = null | 'save' | 'quit-without-save';
interface MutableNode {
id: string;
title: string;
archived: boolean;
parentId: string | null;
childIds: string[];
}
export const TRANSIENT_HINT_DURATION_MS = 2500;
const collator = new Intl.Collator('en', { sensitivity: 'base', numeric: true });
function normalizePageId(value: string): string {
const trimmed = value.trim();
const compact = trimmed.replace(/-/g, '');
if (/^[0-9a-fA-F]{32}$/.test(compact)) {
const lower = compact.toLowerCase();
return `${lower.slice(0, 8)}-${lower.slice(8, 12)}-${lower.slice(12, 16)}-${lower.slice(
16,
20,
)}-${lower.slice(20)}`;
}
return trimmed;
}
function titleValue(value: string | null | undefined): string {
const trimmed = value?.trim() ?? '';
return trimmed.length > 0 ? trimmed : 'Untitled';
}
function sortedNodeIds(ids: string[], nodes: Map<string, MutableNode | NotionPickerNode>): string[] {
return [...ids].sort((leftId, rightId) => {
const left = nodes.get(leftId);
const right = nodes.get(rightId);
const byTitle = collator.compare(left?.title ?? '', right?.title ?? '');
return byTitle === 0 ? leftId.localeCompare(rightId) : byTitle;
});
}
function cloneState(state: PickerState, patch: Partial<PickerState>): PickerState {
return { ...state, ...patch };
}
function transientHint(text: string, now: number): PickerState['transientHint'] {
return { text, expiresAt: now + TRANSIENT_HINT_DURATION_MS };
}
export function clearExpiredTransientHint(state: PickerState, now = Date.now()): PickerState {
if (!state.transientHint || state.transientHint.expiresAt > now) {
return state;
}
return cloneState(state, { transientHint: null });
}
function ancestorsOf(nodeId: string, byId: Map<string, NotionPickerNode>): string[] {
const ancestors: string[] = [];
let parentId = byId.get(nodeId)?.parentId ?? null;
const seen = new Set<string>();
while (parentId && !seen.has(parentId)) {
ancestors.push(parentId);
seen.add(parentId);
parentId = byId.get(parentId)?.parentId ?? null;
}
return ancestors;
}
function descendantsOf(nodeId: string, byId: Map<string, NotionPickerNode>): string[] {
const result: string[] = [];
const stack = [...(byId.get(nodeId)?.childIds ?? [])].reverse();
while (stack.length > 0) {
const id = stack.pop();
if (!id) {
continue;
}
result.push(id);
const node = byId.get(id);
if (node) {
stack.push(...[...node.childIds].reverse());
}
}
return result;
}
function matchingIds(state: PickerState): Set<string> {
const query = state.search.query.trim().toLocaleLowerCase();
if (!query) {
return new Set(state.tree.map((node) => node.id));
}
return new Set(
state.tree
.filter((node) => {
const title = node.title.toLocaleLowerCase();
const path = node.path.toLocaleLowerCase();
return title.includes(query) || path.includes(query);
})
.map((node) => node.id),
);
}
export function buildPickerTree(searchResults: NotionPickerPageInput[]): NotionPickerNode[] {
const nodes = new Map<string, MutableNode>();
for (const result of searchResults) {
const id = normalizePageId(result.id);
if (nodes.has(id)) {
continue;
}
nodes.set(id, {
id,
title: titleValue(result.title),
archived: result.archived === true,
parentId: result.parentId ? normalizePageId(result.parentId) : null,
childIds: [],
});
}
for (const node of nodes.values()) {
if (!node.parentId || node.parentId === node.id || !nodes.has(node.parentId)) {
node.parentId = null;
continue;
}
const seen = new Set([node.id]);
let cursor: string | null = node.parentId;
while (cursor) {
if (seen.has(cursor)) {
node.parentId = null;
break;
}
seen.add(cursor);
cursor = nodes.get(cursor)?.parentId ?? null;
}
}
for (const node of nodes.values()) {
node.childIds = [];
}
for (const node of nodes.values()) {
if (node.parentId) {
nodes.get(node.parentId)?.childIds.push(node.id);
}
}
for (const node of nodes.values()) {
node.childIds = sortedNodeIds(node.childIds, nodes);
}
const roots = sortedNodeIds(
[...nodes.values()].filter((node) => node.parentId === null).map((node) => node.id),
nodes,
);
const tree: NotionPickerNode[] = [];
function visit(nodeId: string, depth: number, pathPrefix: string[]): void {
const raw = nodes.get(nodeId);
if (!raw) {
return;
}
const path = [...pathPrefix, raw.title].join(' / ');
const node: NotionPickerNode = {
id: raw.id,
title: raw.title,
archived: raw.archived,
parentId: raw.parentId,
depth,
childIds: raw.childIds,
path,
};
tree.push(node);
for (const childId of raw.childIds) {
visit(childId, depth + 1, [...pathPrefix, raw.title]);
}
}
for (const rootId of roots) {
visit(rootId, 0, []);
}
return tree;
}
export function isAncestorChecked(nodeId: string, checked: Set<string>, byId: Map<string, NotionPickerNode>): boolean {
return ancestorsOf(nodeId, byId).some((ancestorId) => checked.has(ancestorId));
}
function checkedAncestor(nodeId: string, state: PickerState): NotionPickerNode | null {
for (const ancestorId of ancestorsOf(nodeId, state.byId)) {
if (state.checked.has(ancestorId)) {
return state.byId.get(ancestorId) ?? null;
}
}
return null;
}
export function canToggle(nodeId: string, state: PickerState): { ok: true } | { ok: false; reason: string } {
if (!state.byId.has(nodeId)) {
return { ok: false, reason: 'Page not found' };
}
const ancestor = checkedAncestor(nodeId, state);
if (ancestor) {
return { ok: false, reason: `Locked by '${ancestor.title}' - uncheck parent first` };
}
return { ok: true };
}
export function toggleChecked(state: PickerState, nodeId: string, now = Date.now()): PickerState {
const toggle = canToggle(nodeId, state);
if (!toggle.ok) {
return cloneState(state, {
transientHint: transientHint(toggle.reason, now),
});
}
const checked = new Set(state.checked);
if (checked.has(nodeId)) {
checked.delete(nodeId);
} else {
checked.add(nodeId);
for (const descendantId of descendantsOf(nodeId, state.byId)) {
checked.delete(descendantId);
}
}
return cloneState(state, { checked, transientHint: null });
}
export function flattenSelection(checked: Set<string>, byId: Map<string, NotionPickerNode>): string[] {
const result: string[] = [];
for (const node of byId.values()) {
if (checked.has(node.id) && !isAncestorChecked(node.id, checked, byId)) {
result.push(node.id);
}
}
return result;
}
export function filterTree(state: PickerState): { visibleIds: Set<string>; autoExpand: Set<string> } {
const matches = matchingIds(state);
if (state.search.query.trim().length === 0) {
return { visibleIds: matches, autoExpand: new Set() };
}
const visibleIds = new Set<string>();
const autoExpand = new Set<string>();
for (const matchId of matches) {
visibleIds.add(matchId);
for (const ancestorId of ancestorsOf(matchId, state.byId)) {
visibleIds.add(ancestorId);
autoExpand.add(ancestorId);
}
}
return { visibleIds, autoExpand };
}
export function visibleNodeIds(state: PickerState): string[] {
const { visibleIds, autoExpand } = filterTree(state);
const result: string[] = [];
const roots = state.tree.filter((node) => node.parentId === null).map((node) => node.id);
function visit(nodeId: string): void {
if (!visibleIds.has(nodeId)) {
return;
}
result.push(nodeId);
const node = state.byId.get(nodeId);
if (!node) {
return;
}
if (state.expanded.has(nodeId) || autoExpand.has(nodeId)) {
for (const childId of node.childIds) {
visit(childId);
}
}
}
for (const rootId of roots) {
visit(rootId);
}
return result;
}
export function selectAllVisible(state: PickerState): PickerState {
const candidates = state.search.query.trim().length > 0 ? matchingIds(state) : new Set(visibleNodeIds(state));
const checked = new Set(state.checked);
for (const node of state.tree) {
if (!candidates.has(node.id)) {
continue;
}
const hasCandidateAncestor = ancestorsOf(node.id, state.byId).some((ancestorId) => candidates.has(ancestorId));
if (!hasCandidateAncestor && !isAncestorChecked(node.id, checked, state.byId)) {
checked.add(node.id);
for (const descendantId of descendantsOf(node.id, state.byId)) {
checked.delete(descendantId);
}
}
}
return cloneState(state, {
checked: new Set(flattenSelection(checked, state.byId)),
transientHint: null,
});
}
export function selectNone(state: PickerState): PickerState {
return cloneState(state, { checked: new Set(), transientHint: null });
}
function setExpanded(state: PickerState, nodeId: string, value: boolean | 'toggle'): PickerState {
const expanded = new Set(state.expanded);
const nextValue = value === 'toggle' ? !expanded.has(nodeId) : value;
if (nextValue) {
expanded.add(nodeId);
} else {
expanded.delete(nodeId);
}
return cloneState(state, { expanded });
}
function expandPath(state: PickerState, nodeId: string): PickerState {
const expanded = new Set(state.expanded);
for (const ancestorId of ancestorsOf(nodeId, state.byId)) {
expanded.add(ancestorId);
}
return cloneState(state, { expanded });
}
export function moveCursor(state: PickerState, dir: 'up' | 'down' | 'left' | 'right'): PickerState {
const node = state.byId.get(state.cursorId);
if (!node) {
return state;
}
if (dir === 'left') {
if (node.childIds.length > 0 && state.expanded.has(node.id)) {
return setExpanded(state, node.id, false);
}
return node.parentId ? cloneState(state, { cursorId: node.parentId }) : state;
}
if (dir === 'right') {
if (node.childIds.length === 0) {
return state;
}
if (!state.expanded.has(node.id)) {
return setExpanded(state, node.id, true);
}
return cloneState(state, { cursorId: node.childIds[0] ?? node.id });
}
const ids = visibleNodeIds(state);
const index = ids.indexOf(state.cursorId);
if (index === -1) {
return ids[0] ? cloneState(state, { cursorId: ids[0] }) : state;
}
const nextIndex = dir === 'up' ? Math.max(0, index - 1) : Math.min(ids.length - 1, index + 1);
return cloneState(state, { cursorId: ids[nextIndex] ?? state.cursorId });
}
export function buildInitialState(args: {
tree: NotionPickerNode[];
existingRootPageIds: string[];
currentCrawlMode?: 'all_accessible' | 'selected_roots';
}): PickerState {
const byId = new Map(args.tree.map((node) => [node.id, node]));
const checked = new Set<string>();
let staleCount = 0;
for (const rawId of args.existingRootPageIds) {
const id = normalizePageId(rawId);
if (byId.has(id)) {
checked.add(id);
} else {
staleCount += 1;
}
}
const minimalChecked = new Set(flattenSelection(checked, byId));
const expanded = new Set<string>();
for (const checkedId of minimalChecked) {
for (const ancestorId of ancestorsOf(checkedId, byId)) {
expanded.add(ancestorId);
}
}
return {
tree: args.tree,
byId,
expanded,
checked: minimalChecked,
cursorId: args.tree[0]?.id ?? '',
search: { editing: false, query: '' },
pendingConfirm: null,
preLoadWarnings: staleCount > 0 ? [`${staleCount} stored root_page_ids no longer visible`] : [],
transientHint: null,
currentCrawlMode: args.currentCrawlMode ?? 'selected_roots',
};
}
export function reducer(state: PickerState, cmd: PickerCommand, now = Date.now()): { next: PickerState; effect: PickerEffect } {
if (state.pendingConfirm) {
if (cmd === 'save-confirm') {
return { next: cloneState(state, { pendingConfirm: null }), effect: 'save' };
}
if (cmd === 'save-cancel') {
return { next: cloneState(state, { pendingConfirm: null }), effect: null };
}
if (cmd === 'quit') {
return { next: state, effect: 'quit-without-save' };
}
return { next: state, effect: null };
}
switch (cmd) {
case 'cursor-up':
return { next: moveCursor(state, 'up'), effect: null };
case 'cursor-down':
return { next: moveCursor(state, 'down'), effect: null };
case 'cursor-left':
return { next: moveCursor(state, 'left'), effect: null };
case 'cursor-right':
return { next: moveCursor(state, 'right'), effect: null };
case 'expand':
return { next: setExpanded(state, state.cursorId, 'toggle'), effect: null };
case 'collapse':
return { next: setExpanded(state, state.cursorId, false), effect: null };
case 'expand-all':
return {
next: cloneState(state, {
expanded: new Set(state.tree.filter((node) => node.childIds.length > 0).map((node) => node.id)),
}),
effect: null,
};
case 'collapse-all':
return { next: cloneState(state, { expanded: new Set() }), effect: null };
case 'toggle-check':
return { next: toggleChecked(state, state.cursorId, now), effect: null };
case 'select-all-visible':
return { next: selectAllVisible(state), effect: null };
case 'select-none':
return { next: selectNone(state), effect: null };
case 'clear-transient-hint':
return { next: clearExpiredTransientHint(state, now), effect: null };
case 'search-start':
return { next: cloneState(state, { search: { ...state.search, editing: true } }), effect: null };
case 'search-cancel':
return { next: cloneState(state, { search: { editing: false, query: '' } }), effect: null };
case 'search-submit':
return { next: cloneState(state, { search: { ...state.search, editing: false } }), effect: null };
case 'search-backspace':
return {
next: cloneState(state, { search: { ...state.search, query: state.search.query.slice(0, -1) } }),
effect: null,
};
case 'save-request':
if (state.checked.size === 0) {
return {
next: cloneState(state, {
transientHint: transientHint('Select at least one page or press q to quit', now),
}),
effect: null,
};
}
if (state.currentCrawlMode === 'all_accessible') {
return { next: cloneState(state, { pendingConfirm: 'mode-switch' }), effect: null };
}
return { next: state, effect: 'save' };
case 'save-confirm':
return { next: state, effect: 'save' };
case 'save-cancel':
return { next: state, effect: null };
case 'quit':
return { next: state, effect: 'quit-without-save' };
default:
return { next: cloneState(state, { search: { ...state.search, query: state.search.query + cmd.value } }), effect: null };
}
}

View file

@ -0,0 +1,384 @@
/* @jsxImportSource react */
import { render as renderInkTest } from 'ink-testing-library';
import React, { act, type ReactNode } from 'react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { buildInitialState, buildPickerTree, type NotionPickerPageInput } from './connection-notion-tree.js';
import {
NotionPickerApp,
notionPickerCommandForInkInput,
renderNotionPickerTui,
resolveNotionPickerWidth,
sanitizeNotionPickerTuiError,
windowItems,
windowOffset,
type NotionPickerInkInstance,
type NotionPickerInkRenderOptions,
} from './connection-notion-tui.js';
const IDS = {
engineering: '11111111-1111-1111-1111-111111111111',
architecture: '22222222-2222-2222-2222-222222222222',
marketing: '33333333-3333-3333-3333-333333333333',
finance: '44444444-4444-4444-4444-444444444444',
ops: '55555555-5555-5555-5555-555555555555',
sales: '66666666-6666-6666-6666-666666666666',
support: '77777777-7777-7777-7777-777777777777',
product: '88888888-8888-8888-8888-888888888888',
design: '99999999-9999-9999-9999-999999999999',
};
function pages(): NotionPickerPageInput[] {
return [
{ id: IDS.engineering, title: 'Engineering Docs', archived: false, parentId: null },
{ id: IDS.architecture, title: 'Architecture', archived: false, parentId: IDS.engineering },
{ id: IDS.marketing, title: 'Marketing', archived: false, parentId: null },
];
}
function manyPages(): NotionPickerPageInput[] {
return [
{ id: IDS.engineering, title: 'Engineering Docs', archived: false, parentId: null },
{ id: IDS.architecture, title: 'Architecture', archived: false, parentId: IDS.engineering },
{ id: IDS.marketing, title: 'Marketing', archived: false, parentId: null },
{ id: IDS.finance, title: 'Finance', archived: false, parentId: null },
{ id: IDS.ops, title: 'Operations', archived: false, parentId: null },
{ id: IDS.sales, title: 'Sales', archived: false, parentId: null },
{ id: IDS.support, title: 'Support', archived: false, parentId: null },
{ id: IDS.product, title: 'Product', archived: false, parentId: null },
{ id: IDS.design, title: 'Design', archived: false, parentId: null },
];
}
function state(mode: 'all_accessible' | 'selected_roots' = 'selected_roots') {
return buildInitialState({
tree: buildPickerTree(pages()),
existingRootPageIds: [],
currentCrawlMode: mode,
});
}
async function waitForInkInput(): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, 10));
}
function fakeInkInstance(): NotionPickerInkInstance {
return {
rerender: vi.fn(),
unmount: vi.fn(),
waitUntilExit: vi.fn(async () => undefined),
};
}
function normalizeFrameWrap(frame: string | undefined): string {
return frame?.replace(/\n/g, ' ') ?? '';
}
afterEach(() => {
vi.useRealTimers();
});
describe('notionPickerCommandForInkInput', () => {
it('maps browse, search, and confirm input to reducer commands', () => {
expect(notionPickerCommandForInkInput('', { downArrow: true }, state().search, null)).toBe('cursor-down');
expect(notionPickerCommandForInkInput('', { upArrow: true }, state().search, null)).toBe('cursor-up');
expect(notionPickerCommandForInkInput('', { rightArrow: true }, state().search, null)).toBe('cursor-right');
expect(notionPickerCommandForInkInput('', { leftArrow: true }, state().search, null)).toBe('cursor-left');
expect(notionPickerCommandForInkInput(' ', {}, state().search, null)).toBe('toggle-check');
expect(notionPickerCommandForInkInput('/', {}, state().search, null)).toBe('search-start');
expect(notionPickerCommandForInkInput('a', {}, state().search, null)).toBe('select-all-visible');
expect(notionPickerCommandForInkInput('n', {}, state().search, null)).toBe('select-none');
expect(notionPickerCommandForInkInput('s', {}, state().search, null)).toBe('save-request');
expect(notionPickerCommandForInkInput('q', {}, state().search, null)).toBe('quit');
expect(notionPickerCommandForInkInput('c', { ctrl: true }, state().search, null)).toBe('quit');
expect(notionPickerCommandForInkInput('x', {}, { editing: true, query: '' }, null)).toEqual({
type: 'search-input',
value: 'x',
});
expect(notionPickerCommandForInkInput('', { backspace: true }, { editing: true, query: 'x' }, null)).toBe(
'search-backspace',
);
expect(notionPickerCommandForInkInput('', { return: true }, { editing: true, query: 'x' }, null)).toBe(
'search-submit',
);
expect(notionPickerCommandForInkInput('', { escape: true }, { editing: true, query: 'x' }, null)).toBe(
'search-cancel',
);
expect(notionPickerCommandForInkInput('y', {}, state().search, 'mode-switch')).toBe('save-confirm');
expect(notionPickerCommandForInkInput('', { return: true }, state().search, 'mode-switch')).toBe('save-confirm');
expect(notionPickerCommandForInkInput('n', {}, state().search, 'mode-switch')).toBe('save-cancel');
});
});
describe('window helpers', () => {
it('centers the selected row and returns the visible slice', () => {
expect(windowOffset(20, 10, 5)).toBe(8);
expect(windowItems(['a', 'b', 'c', 'd', 'e'], 3, 3)).toEqual({ items: ['c', 'd', 'e'], offset: 2 });
});
it('clamps picker width to the design rule', () => {
expect(resolveNotionPickerWidth(200)).toBe(120);
expect(resolveNotionPickerWidth(100)).toBe(96);
expect(resolveNotionPickerWidth(50)).toBe(60);
expect(resolveNotionPickerWidth(undefined)).toBe(96);
});
});
describe('NotionPickerApp', () => {
it('renders spec banners, row glyphs, search visibility, and hint text', () => {
const initialState = {
...state('all_accessible'),
preLoadWarnings: ['1 stored root_page_ids no longer visible'],
};
const { lastFrame } = renderInkTest(
<NotionPickerApp
initialState={initialState}
connectionId="notion-main"
workspaceLabel="Design Workspace"
cappedAtCount={5000}
currentCrawlMode="all_accessible"
terminalRows={24}
terminalWidth={100}
onExit={vi.fn()}
/>,
);
const frame = lastFrame() ?? '';
expect(frame).toContain('Notion pages visible to integration "Design Workspace"');
expect(frame).toContain('5000-page cap reached - some pages not shown');
expect(frame).toContain('1 stored root_page_ids no longer visible - they will be removed if you save');
expect(frame).toContain('▸ [ ] Engineering Docs ▸ (1)');
expect(frame).toContain(' [ ] Marketing');
expect(frame).not.toContain('Search ready: -');
expect(frame).toContain('space toggle · enter expand · / search · a all · n none · s save & exit · q quit');
});
it('renders partial discovery warnings without stale-root save suffix', () => {
const initialState = {
...state(),
preLoadWarnings: ['Notion search stopped early: rate limit after first page'],
};
const { lastFrame } = renderInkTest(
<NotionPickerApp
initialState={initialState}
connectionId="notion-main"
workspaceLabel="Design Workspace"
cappedAtCount={null}
currentCrawlMode="selected_roots"
terminalRows={24}
terminalWidth={100}
onExit={vi.fn()}
/>,
);
const frame = lastFrame() ?? '';
expect(frame).toContain('Notion search stopped early: rate limit after first page');
expect(frame).not.toContain(
'Notion search stopped early: rate limit after first page - they will be removed if you save',
);
});
it('renders checked parents and locked descendants with the locked design glyphs', () => {
const initialState = {
...state(),
checked: new Set([IDS.engineering]),
expanded: new Set([IDS.engineering]),
};
const { lastFrame } = renderInkTest(
<NotionPickerApp
initialState={initialState}
connectionId="notion-main"
workspaceLabel="Design Workspace"
cappedAtCount={null}
currentCrawlMode="selected_roots"
terminalRows={24}
terminalWidth={100}
onExit={vi.fn()}
/>,
);
const frame = lastFrame() ?? '';
expect(frame).toContain('▸ [×] Engineering Docs ▾');
expect(frame).toContain(' [~] Architecture');
});
it('supports keyboard selection, all_accessible confirmation, and save callback', async () => {
const onExit = vi.fn();
const { stdin, lastFrame } = renderInkTest(
<NotionPickerApp
initialState={state('all_accessible')}
connectionId="notion-main"
workspaceLabel="Design Workspace"
cappedAtCount={null}
currentCrawlMode="all_accessible"
terminalRows={24}
terminalWidth={100}
onExit={onExit}
/>,
);
stdin.write(' ');
await waitForInkInput();
expect(lastFrame()).toContain('[×] Engineering Docs');
stdin.write('s');
await waitForInkInput();
expect(normalizeFrameWrap(lastFrame())).toContain(
'Save will switch crawl_mode all_accessible -> selected_roots and limit ingest to 1 selected page. [y] confirm [esc] back',
);
stdin.write('y');
await waitForInkInput();
expect(onExit).toHaveBeenCalledWith({ kind: 'save', rootPageIds: [IDS.engineering] });
});
it('removes transient hints after their expiry time', async () => {
vi.useFakeTimers();
const onExit = vi.fn();
const { stdin, lastFrame } = renderInkTest(
<NotionPickerApp
initialState={state()}
connectionId="notion-main"
workspaceLabel="Design Workspace"
cappedAtCount={null}
currentCrawlMode="selected_roots"
terminalRows={24}
terminalWidth={100}
onExit={onExit}
/>,
);
await act(async () => {
stdin.write('s');
await vi.advanceTimersByTimeAsync(10);
});
expect(lastFrame()).toContain('Select at least one page or press q to quit');
await act(async () => {
await vi.advanceTimersByTimeAsync(2500);
});
expect(lastFrame()).not.toContain('Select at least one page or press q to quit');
expect(onExit).not.toHaveBeenCalled();
});
it('renders row-window overflow indicators when the visible list is clipped', async () => {
const onExit = vi.fn();
const initialState = buildInitialState({
tree: buildPickerTree(manyPages()),
existingRootPageIds: [],
currentCrawlMode: 'selected_roots',
});
initialState.expanded = new Set([IDS.engineering]);
const { stdin, lastFrame } = renderInkTest(
<NotionPickerApp
initialState={initialState}
connectionId="notion-main"
workspaceLabel="Design Workspace"
cappedAtCount={null}
currentCrawlMode="selected_roots"
terminalRows={13}
terminalWidth={100}
onExit={onExit}
/>,
);
expect(lastFrame()).toContain('↓ 4 more');
stdin.write('\u001B[B');
stdin.write('\u001B[B');
stdin.write('\u001B[B');
stdin.write('\u001B[B');
await waitForInkInput();
const frame = lastFrame() ?? '';
expect(frame).toContain('↑ ');
expect(frame).toContain('↓ ');
expect(onExit).not.toHaveBeenCalled();
});
it('returns quit without saving', async () => {
const onExit = vi.fn();
const { stdin } = renderInkTest(
<NotionPickerApp
initialState={state()}
connectionId="notion-main"
workspaceLabel="Design Workspace"
cappedAtCount={null}
currentCrawlMode="selected_roots"
terminalRows={24}
terminalWidth={100}
onExit={onExit}
/>,
);
stdin.write('q');
await waitForInkInput();
expect(onExit).toHaveBeenCalledWith({ kind: 'quit' });
});
});
describe('renderNotionPickerTui', () => {
it('returns the app result from the Ink runtime', async () => {
const io = {
stdin: { isTTY: true, setRawMode: vi.fn() },
stdout: { isTTY: true, columns: 100, rows: 24, write: vi.fn() },
stderr: { write: vi.fn() },
};
const renderInk = vi.fn((_tree: ReactNode, _options: NotionPickerInkRenderOptions) => fakeInkInstance());
await expect(
renderNotionPickerTui(
{
initialState: state(),
connectionId: 'notion-main',
workspaceLabel: 'Design Workspace',
cappedAtCount: null,
currentCrawlMode: 'selected_roots',
},
io,
{ renderInk },
),
).resolves.toEqual({ kind: 'quit' });
expect(renderInk).toHaveBeenCalledOnce();
});
it('sanitizes render errors and tells the user to use no-input mode', async () => {
expect(sanitizeNotionPickerTuiError(new Error('token=secret https://api.notion.com/v1/search'))).toBe(
'[redacted] [redacted-url]',
);
});
it('falls back to quit with a scripted-mode hint when Ink cannot initialize', async () => {
let stderr = '';
const io = {
stdin: { isTTY: false, setRawMode: vi.fn() },
stdout: { isTTY: false, columns: 100, rows: 24, write: vi.fn() },
stderr: {
write(chunk: string) {
stderr += chunk;
},
},
};
await expect(
renderNotionPickerTui(
{
initialState: state(),
connectionId: 'notion-main',
workspaceLabel: 'Design Workspace',
cappedAtCount: null,
currentCrawlMode: 'selected_roots',
},
io,
{
renderInk: vi.fn(() => {
throw new Error('token=secret');
}),
},
),
).resolves.toEqual({ kind: 'quit' });
expect(stderr).toContain('Use --no-input --root-page-id <UUID> for scripted mode');
expect(stderr).not.toContain('secret');
});
});

View file

@ -0,0 +1,338 @@
/* @jsxImportSource react */
import { Box, Text, render as renderInkRuntime, useApp, useInput } from 'ink';
import React, { type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import {
filterTree,
flattenSelection,
isAncestorChecked,
reducer,
visibleNodeIds,
type PickerCommand,
type PickerState,
} from './connection-notion-tree.js';
import type { KloCliIo } from '../index.js';
const COLOR_THEME = {
text: 'white',
muted: 'gray',
active: 'cyan',
warning: 'yellow',
} as const;
const NO_COLOR_THEME = {
text: 'white',
muted: 'white',
active: 'white',
warning: 'white',
} as const;
type NotionPickerTheme = Record<keyof typeof COLOR_THEME, string>;
export interface NotionPickerTuiIo extends KloCliIo {
stdin?: { isTTY?: boolean; setRawMode?(value: boolean): void };
stdout: KloCliIo['stdout'] & { isTTY?: boolean; columns?: number; rows?: number };
}
interface InkKey {
leftArrow?: boolean;
rightArrow?: boolean;
upArrow?: boolean;
downArrow?: boolean;
return?: boolean;
escape?: boolean;
ctrl?: boolean;
backspace?: boolean;
delete?: boolean;
}
export type PickerRenderResult = { kind: 'save'; rootPageIds: string[] } | { kind: 'quit' };
export interface PickerRenderInput {
initialState: PickerState;
connectionId: string;
workspaceLabel: string;
cappedAtCount: number | null;
currentCrawlMode: 'all_accessible' | 'selected_roots';
}
interface NotionPickerAppProps extends PickerRenderInput {
terminalRows?: number;
terminalWidth?: number;
env?: NodeJS.ProcessEnv;
onExit(result: PickerRenderResult): void;
}
export interface NotionPickerInkInstance {
rerender(tree: ReactNode): void;
unmount(): void;
waitUntilExit(): Promise<void>;
}
export interface NotionPickerInkRenderOptions {
stdin?: NotionPickerTuiIo['stdin'];
stdout: NotionPickerTuiIo['stdout'];
stderr: NotionPickerTuiIo['stderr'];
exitOnCtrlC: boolean;
patchConsole: boolean;
maxFps: number;
alternateScreen: boolean;
}
function resolveTheme(env: NodeJS.ProcessEnv = process.env): NotionPickerTheme {
return env.NO_COLOR || env.TERM === 'dumb' ? NO_COLOR_THEME : COLOR_THEME;
}
export function resolveNotionPickerWidth(columns: number | undefined): number {
const resolvedColumns = columns ?? 100;
return Math.max(60, Math.min(120, resolvedColumns - 4));
}
function staleWarningText(warning: string): string {
return warning.includes('stored root_page_ids no longer visible')
? `${warning} - they will be removed if you save`
: warning;
}
function selectedPageCountText(count: number): string {
return `${count} selected ${count === 1 ? 'page' : 'pages'}`;
}
function rowMatchesSearch(state: PickerState, nodeId: string): boolean {
const query = state.search.query.trim().toLocaleLowerCase();
if (!query) {
return false;
}
const node = state.byId.get(nodeId);
if (!node) {
return false;
}
return node.title.toLocaleLowerCase().includes(query) || node.path.toLocaleLowerCase().includes(query);
}
export function sanitizeNotionPickerTuiError(error: unknown): string {
const message = error instanceof Error ? error.message : String(error);
return message
.replace(/[a-z][a-z0-9+.-]*:\/\/[^\s]+/gi, '[redacted-url]')
.replace(/\b(api[_-]?key|password|token|secret)=\S+/gi, '[redacted]');
}
export function windowOffset(count: number, selected: number, visible: number): number {
if (count <= visible) return 0;
return Math.max(0, Math.min(count - visible, selected - Math.floor(visible / 2)));
}
export function windowItems<T>(items: T[], selected: number, visible: number): { items: T[]; offset: number } {
const offset = windowOffset(items.length, selected, visible);
return { items: items.slice(offset, offset + visible), offset };
}
function truncateText(value: string, width: number): string {
if (value.length <= width) return value;
if (width <= 3) return value.slice(0, width);
return `${value.slice(0, width - 3)}...`;
}
export function notionPickerCommandForInkInput(
input: string,
key: InkKey,
search: PickerState['search'],
pendingConfirm: PickerState['pendingConfirm'],
): PickerCommand | null {
if (pendingConfirm) {
if (input === 'y' || key.return) return 'save-confirm';
if (input === 'n' || key.escape) return 'save-cancel';
if (key.ctrl === true && input === 'c') return 'quit';
return null;
}
if (search.editing) {
if (key.escape) return 'search-cancel';
if (key.return) return 'search-submit';
if (key.backspace || key.delete) return 'search-backspace';
if (key.downArrow) return 'cursor-down';
if (key.upArrow) return 'cursor-up';
if (input.length === 1 && input >= ' ' && input !== '\u007f') return { type: 'search-input', value: input };
return null;
}
if (key.ctrl === true && input === 'c') return 'quit';
if (key.upArrow) return 'cursor-up';
if (key.downArrow) return 'cursor-down';
if (key.leftArrow) return 'cursor-left';
if (key.rightArrow) return 'cursor-right';
if (key.return) return 'expand';
if (input === ' ') return 'toggle-check';
if (input === '/') return 'search-start';
if (input === 'a') return 'select-all-visible';
if (input === 'n') return 'select-none';
if (input === 's') return 'save-request';
if (input === 'q' || key.escape) return 'quit';
return null;
}
function PickerRow(props: { state: PickerState; nodeId: string; width: number; theme: NotionPickerTheme }): ReactNode {
const node = props.state.byId.get(props.nodeId);
if (!node) return null;
const focused = props.state.cursorId === node.id;
const locked = isAncestorChecked(node.id, props.state.checked, props.state.byId);
const checked = props.state.checked.has(node.id);
const glyph = locked ? '[~]' : checked ? '[×]' : '[ ]';
const children =
node.childIds.length > 0 ? (props.state.expanded.has(node.id) ? ' ▾' : ` ▸ (${node.childIds.length})`) : '';
const prefix = `${focused ? '▸' : ' '} ${glyph} ${' '.repeat(node.depth * 2)}`;
const color = focused ? props.theme.active : locked || node.archived ? props.theme.muted : props.theme.text;
const title = truncateText(`${node.title}${children}`, Math.max(10, props.width - prefix.length));
const inverse = rowMatchesSearch(props.state, node.id);
return (
<Text color={color} strikethrough={node.archived}>
{prefix}
<Text inverse={inverse}>{title}</Text>
</Text>
);
}
export function NotionPickerApp(props: NotionPickerAppProps): ReactNode {
const app = useApp();
const [state, setState] = useState(props.initialState);
const stateRef = useRef(state);
const theme = useMemo(() => resolveTheme(props.env), [props.env]);
const visibleIds = visibleNodeIds(state);
const selectedIndex = Math.max(0, visibleIds.indexOf(state.cursorId));
const reservedRows = state.pendingConfirm === 'mode-switch' ? 9 : 8;
const visibleRows = Math.max(5, Math.min(20, (props.terminalRows ?? 24) - reservedRows));
const rows = windowItems(visibleIds, selectedIndex, visibleRows);
const hiddenAbove = rows.offset;
const hiddenBelow = Math.max(0, visibleIds.length - rows.offset - rows.items.length);
const searchMatchCount = filterTree(state).visibleIds.size;
const width = resolveNotionPickerWidth(props.terminalWidth);
const showSearch = state.search.editing || state.search.query.trim().length > 0;
const selectedCount = flattenSelection(state.checked, state.byId).length;
stateRef.current = state;
useEffect(() => {
const hint = state.transientHint;
if (!hint) {
return;
}
const clearHint = () => {
setState((current) => {
const { next } = reducer(current, 'clear-transient-hint');
stateRef.current = next;
return next;
});
};
const delay = hint.expiresAt - Date.now();
if (delay <= 0) {
clearHint();
return;
}
const timeout = setTimeout(clearHint, delay);
return () => clearTimeout(timeout);
}, [state.transientHint?.expiresAt]);
useInput((input, key) => {
const command = notionPickerCommandForInkInput(input, key, stateRef.current.search, stateRef.current.pendingConfirm);
if (!command) {
return;
}
const { next, effect } = reducer(stateRef.current, command);
stateRef.current = next;
setState(next);
if (effect === 'save') {
props.onExit({ kind: 'save', rootPageIds: flattenSelection(next.checked, next.byId) });
app.exit();
return;
}
if (effect === 'quit-without-save') {
props.onExit({ kind: 'quit' });
app.exit();
}
});
return (
<Box flexDirection="column">
<Text color={theme.active}>Notion pages visible to integration "{props.workspaceLabel}"</Text>
{props.cappedAtCount ? <Text color={theme.warning}>{props.cappedAtCount}-page cap reached - some pages not shown</Text> : null}
{state.preLoadWarnings.map((warning) => (
<Text key={warning} color={theme.warning}>
{staleWarningText(warning)}
</Text>
))}
{showSearch ? (
<Text color={theme.muted}>
/ {state.search.query}
{state.search.editing ? '█' : ''} ({searchMatchCount} matches)
</Text>
) : null}
<Box flexDirection="column">
{hiddenAbove > 0 ? <Text color={theme.muted}> {hiddenAbove} more</Text> : null}
{rows.items.map((nodeId) => (
<PickerRow key={nodeId} state={state} nodeId={nodeId} width={width} theme={theme} />
))}
{hiddenBelow > 0 ? <Text color={theme.muted}> {hiddenBelow} more</Text> : null}
</Box>
{state.pendingConfirm === 'mode-switch' ? (
<Text color={theme.warning}>
Save will switch crawl_mode all_accessible -&gt; selected_roots and limit ingest to{' '}
{selectedPageCountText(selectedCount)}. [y] confirm [esc] back
</Text>
) : null}
{state.transientHint ? <Text color={theme.warning}>{state.transientHint.text}</Text> : null}
<Text color={theme.muted}>space toggle · enter expand · / search · a all · n none · s save &amp; exit · q quit</Text>
</Box>
);
}
function renderInk(tree: ReactNode, options: NotionPickerInkRenderOptions): NotionPickerInkInstance {
return renderInkRuntime(tree, {
stdin: options.stdin as NodeJS.ReadStream | undefined,
stdout: options.stdout as NodeJS.WriteStream,
stderr: options.stderr as NodeJS.WriteStream,
exitOnCtrlC: options.exitOnCtrlC,
patchConsole: options.patchConsole,
maxFps: options.maxFps,
alternateScreen: options.alternateScreen,
}) as NotionPickerInkInstance;
}
export async function renderNotionPickerTui(
input: PickerRenderInput,
io: NotionPickerTuiIo,
options: { renderInk?: (tree: ReactNode, options: NotionPickerInkRenderOptions) => NotionPickerInkInstance } = {},
): Promise<PickerRenderResult> {
let result: PickerRenderResult = { kind: 'quit' };
let instance: NotionPickerInkInstance | null = null;
try {
instance = (options.renderInk ?? renderInk)(
<NotionPickerApp
{...input}
terminalRows={(io.stdout as { rows?: number }).rows ?? process.stdout.rows ?? 24}
terminalWidth={io.stdout.columns ?? process.stdout.columns}
onExit={(next) => {
result = next;
instance?.unmount();
}}
/>,
{
stdin: io.stdin,
stdout: io.stdout,
stderr: io.stderr,
exitOnCtrlC: false,
patchConsole: false,
maxFps: 30,
alternateScreen: true,
},
);
await instance.waitUntilExit();
instance.unmount();
return result;
} catch (error) {
io.stderr.write(
`Notion picker requires a TTY. Use --no-input --root-page-id <UUID> for scripted mode. ${sanitizeNotionPickerTuiError(error)}\n`,
);
return { kind: 'quit' };
}
}

View file

@ -0,0 +1,466 @@
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
initKloProject,
loadKloProject,
serializeKloProjectConfig,
type KloProjectConfig,
} from '@klo/context/project';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
applyNotionPickerWriteback,
discoverNotionPickerPages,
notionPickerPageFromSearchResult,
normalizeNotionPageId,
resolveNotionWorkspaceLabel,
runKloConnectionNotion,
type NotionPickerApi,
type PickerRenderInput,
type PickerRenderResult,
} from './connection-notion.js';
function makeIo() {
let stdout = '';
let stderr = '';
return {
io: {
stdout: {
write: (chunk: string) => {
stdout += chunk;
},
},
stderr: {
write: (chunk: string) => {
stderr += chunk;
},
},
},
stdout: () => stdout,
stderr: () => stderr,
};
}
type FakeNotionSearchPage = Record<string, unknown> & { id: string; object: 'page' };
const PAGE_IDS = {
engineering: '11111111-1111-1111-1111-111111111111',
architecture: '22222222-2222-2222-2222-222222222222',
stale: '99999999-9999-9999-9999-999999999999',
};
function notionPage(id: string, title: string, parentId: string | null = null): FakeNotionSearchPage {
return {
object: 'page',
id,
archived: false,
parent: parentId ? { type: 'page_id', page_id: parentId } : { type: 'workspace', workspace: true },
properties: {
title: {
type: 'title',
title: [{ plain_text: title }],
},
},
};
}
function fakeNotionApi(pages: FakeNotionSearchPage[]): NotionPickerApi {
return {
search: vi.fn(async (_filterValue, startCursor) => {
if (startCursor === 'page-2') {
return { results: pages.slice(2), hasMore: false, nextCursor: null };
}
return {
results: pages.slice(0, 2),
hasMore: pages.length > 2,
nextCursor: pages.length > 2 ? 'page-2' : null,
};
}),
retrieveBotUser: vi.fn(async () => ({ name: 'Notion bot', bot: { workspace_name: 'Design Workspace' } })),
};
}
describe('normalizeNotionPageId', () => {
it('accepts dashed and compact UUIDs', () => {
expect(normalizeNotionPageId('11111111222233334444555555555555')).toBe(
'11111111-2222-3333-4444-555555555555',
);
expect(normalizeNotionPageId('AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE')).toBe(
'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
);
});
});
describe('runKloConnectionNotion', () => {
let tempDir: string;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-notion-pick-'));
});
afterEach(async () => {
await rm(tempDir, { recursive: true, force: true });
});
async function writeProjectConfig(projectDir: string, config: KloProjectConfig): Promise<void> {
const project = await loadKloProject({ projectDir });
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig(config),
'klo',
'klo@example.com',
'seed test config',
);
}
it('rejects unsafe connection ids before loading a project', async () => {
const io = makeIo();
const loadProject = vi.fn(async () => {
throw new Error('loadProject should not be called');
});
await expect(
runKloConnectionNotion(
{
command: 'pick',
projectDir: '/tmp/project',
connectionId: '../evil',
mode: 'interactive',
},
io.io,
{ loadProject },
),
).resolves.toBe(1);
expect(loadProject).not.toHaveBeenCalled();
expect(io.stderr()).toContain('Unsafe connection id: ../evil');
});
it('writes selected root_page_ids while preserving every other Notion connection field', async () => {
const projectDir = join(tempDir, 'project');
const initialized = await initKloProject({ projectDir, projectName: 'warehouse' });
await writeProjectConfig(projectDir, {
...initialized.config,
connections: {
'notion-main': {
driver: 'notion',
auth_token_ref: 'env:NOTION_TOKEN',
crawl_mode: 'all_accessible',
root_page_ids: ['99999999-9999-9999-9999-999999999999'],
root_database_ids: ['database-1'],
root_data_source_ids: ['data-source-1'],
max_pages_per_run: 12,
max_knowledge_creates_per_run: 2,
max_knowledge_updates_per_run: 7,
last_successful_cursor: '{"phase":"all_accessible_pages","cursor":"cursor-1"}',
unknown_future_field: 'keep-me',
},
},
});
const io = makeIo();
await expect(
runKloConnectionNotion(
{
command: 'pick',
projectDir,
connectionId: 'notion-main',
mode: 'non-interactive',
rootPageIds: [
'11111111-2222-3333-4444-555555555555',
'66666666-7777-8888-9999-aaaaaaaaaaaa',
],
},
io.io,
),
).resolves.toBe(0);
const yaml = await readFile(join(projectDir, 'klo.yaml'), 'utf-8');
expect(yaml).toContain('crawl_mode: selected_roots');
expect(yaml).toContain('root_page_ids:');
expect(yaml).toContain('11111111-2222-3333-4444-555555555555');
expect(yaml).toContain('66666666-7777-8888-9999-aaaaaaaaaaaa');
expect(yaml).toContain('root_database_ids:');
expect(yaml).toContain('database-1');
expect(yaml).toContain('root_data_source_ids:');
expect(yaml).toContain('data-source-1');
expect(yaml).toContain('last_successful_cursor: \'{"phase":"all_accessible_pages","cursor":"cursor-1"}\'');
expect(yaml).toContain('unknown_future_field: keep-me');
expect(io.stdout()).toContain('Connection: notion-main');
expect(io.stdout()).toContain('rootPageIds: 2');
expect(io.stdout()).toContain('crawlMode: selected_roots');
});
it('rejects empty writeback, missing connections, and non-Notion connections', async () => {
const projectDir = join(tempDir, 'project');
const initialized = await initKloProject({ projectDir, projectName: 'warehouse' });
await writeProjectConfig(projectDir, {
...initialized.config,
connections: {
warehouse: {
driver: 'postgres',
url: 'env:DATABASE_URL',
readonly: true,
},
},
});
const project = await loadKloProject({ projectDir });
await expect(applyNotionPickerWriteback(project, 'warehouse', [])).rejects.toThrow(
'connection notion pick requires at least one root page id',
);
await expect(
applyNotionPickerWriteback(project, 'missing', ['11111111-2222-3333-4444-555555555555']),
).rejects.toThrow('Connection "missing" not found');
await expect(
applyNotionPickerWriteback(project, 'warehouse', ['11111111-2222-3333-4444-555555555555']),
).rejects.toThrow('Connection "warehouse" is not a Notion connection');
});
it('extracts picker page inputs from Notion search results', () => {
expect(notionPickerPageFromSearchResult(notionPage(PAGE_IDS.architecture, 'Architecture', PAGE_IDS.engineering)))
.toEqual({
id: PAGE_IDS.architecture,
title: 'Architecture',
archived: false,
parentId: PAGE_IDS.engineering,
});
expect(
notionPickerPageFromSearchResult({
object: 'page',
id: PAGE_IDS.engineering.replaceAll('-', ''),
archived: true,
parent: { type: 'workspace', workspace: true },
properties: {},
}),
).toEqual({
id: PAGE_IDS.engineering,
title: 'Untitled',
archived: true,
parentId: null,
});
});
it('discovers visible pages up to the cap and reports cap state', async () => {
const api = fakeNotionApi([
notionPage(PAGE_IDS.engineering, 'Engineering'),
notionPage(PAGE_IDS.architecture, 'Architecture', PAGE_IDS.engineering),
notionPage('33333333-3333-3333-3333-333333333333', 'Onboarding', PAGE_IDS.engineering),
]);
await expect(discoverNotionPickerPages(api, { cap: 2 })).resolves.toEqual({
pages: [
{ id: PAGE_IDS.engineering, title: 'Engineering', archived: false, parentId: null },
{ id: PAGE_IDS.architecture, title: 'Architecture', archived: false, parentId: PAGE_IDS.engineering },
],
cappedAtCount: 2,
warnings: [],
});
expect(api.search).toHaveBeenCalledTimes(1);
});
it('keeps partial discovery results when Notion search fails after at least one page', async () => {
const api: NotionPickerApi = {
search: vi
.fn()
.mockResolvedValueOnce({
results: [notionPage(PAGE_IDS.engineering, 'Engineering')],
hasMore: true,
nextCursor: 'cursor-2',
})
.mockRejectedValueOnce(new Error('rate limit after first page')),
retrieveBotUser: vi.fn(async () => ({ name: 'Notion bot' })),
};
await expect(discoverNotionPickerPages(api)).resolves.toEqual({
pages: [{ id: PAGE_IDS.engineering, title: 'Engineering', archived: false, parentId: null }],
cappedAtCount: null,
warnings: ['Notion search stopped early: rate limit after first page'],
});
});
it('uses the Notion workspace name when available and falls back to the connection id', async () => {
await expect(resolveNotionWorkspaceLabel(fakeNotionApi([]), 'notion-main')).resolves.toBe('Design Workspace');
await expect(
resolveNotionWorkspaceLabel(
{
search: vi.fn(),
retrieveBotUser: vi.fn(async () => {
throw new Error('users.me unavailable');
}),
},
'notion-main',
),
).resolves.toBe('notion-main');
});
it('runs interactive discovery, warns about stale roots, renders the TUI, and saves selected roots', async () => {
const projectDir = join(tempDir, 'project');
const initialized = await initKloProject({ projectDir, projectName: 'warehouse' });
await writeProjectConfig(projectDir, {
...initialized.config,
connections: {
'notion-main': {
driver: 'notion',
auth_token_ref: 'env:NOTION_TOKEN',
crawl_mode: 'all_accessible',
root_page_ids: [PAGE_IDS.stale],
root_database_ids: ['database-1'],
root_data_source_ids: ['data-source-1'],
max_pages_per_run: 12,
max_knowledge_creates_per_run: 2,
max_knowledge_updates_per_run: 7,
last_successful_cursor: null,
},
},
});
const api = fakeNotionApi([
notionPage(PAGE_IDS.engineering, 'Engineering'),
notionPage(PAGE_IDS.architecture, 'Architecture', PAGE_IDS.engineering),
]);
const renderPicker = vi.fn(async (input): Promise<PickerRenderResult> => {
expect(input.connectionId).toBe('notion-main');
expect(input.workspaceLabel).toBe('Design Workspace');
expect(input.currentCrawlMode).toBe('all_accessible');
expect(input.cappedAtCount).toBeNull();
expect(input.initialState.preLoadWarnings).toEqual(['1 stored root_page_ids no longer visible']);
return { kind: 'save', rootPageIds: [PAGE_IDS.engineering] };
});
const io = makeIo();
await expect(
runKloConnectionNotion(
{
command: 'pick',
projectDir,
connectionId: 'notion-main',
mode: 'interactive',
},
io.io,
{
env: { NOTION_TOKEN: 'ntn_test_token' },
createNotionApi: vi.fn(() => api),
renderPicker,
},
),
).resolves.toBe(0);
const yaml = await readFile(join(projectDir, 'klo.yaml'), 'utf-8');
expect(yaml).toContain('crawl_mode: selected_roots');
expect(yaml).toContain(PAGE_IDS.engineering);
expect(yaml).not.toContain(PAGE_IDS.stale);
expect(io.stderr()).toContain('1 stored root_page_ids no longer visible');
expect(io.stdout()).toContain('Connection: notion-main');
expect(io.stdout()).toContain('rootPageIds: 1');
});
it('passes partial-discovery warnings into the TUI banner state', async () => {
const projectDir = join(tempDir, 'project');
const initialized = await initKloProject({ projectDir, projectName: 'warehouse' });
await writeProjectConfig(projectDir, {
...initialized.config,
connections: {
'notion-main': {
driver: 'notion',
auth_token_ref: 'env:NOTION_TOKEN',
crawl_mode: 'selected_roots',
root_page_ids: [PAGE_IDS.engineering],
root_database_ids: [],
root_data_source_ids: [],
max_pages_per_run: 12,
max_knowledge_creates_per_run: 2,
max_knowledge_updates_per_run: 7,
last_successful_cursor: null,
},
},
});
const api: NotionPickerApi = {
search: vi
.fn()
.mockResolvedValueOnce({
results: [notionPage(PAGE_IDS.engineering, 'Engineering')],
hasMore: true,
nextCursor: 'cursor-2',
})
.mockRejectedValueOnce(new Error('rate limit after first page')),
retrieveBotUser: vi.fn(async () => ({ name: 'Notion bot', bot: { workspace_name: 'Design Workspace' } })),
};
let renderInput: PickerRenderInput | undefined;
const renderPicker = vi.fn(async (input: PickerRenderInput): Promise<PickerRenderResult> => {
renderInput = input;
return { kind: 'quit' };
});
const io = makeIo();
await expect(
runKloConnectionNotion(
{
command: 'pick',
projectDir,
connectionId: 'notion-main',
mode: 'interactive',
},
io.io,
{
env: { NOTION_TOKEN: 'ntn_test_token' },
createNotionApi: vi.fn(() => api),
renderPicker,
},
),
).resolves.toBe(0);
expect(renderPicker).toHaveBeenCalledOnce();
if (!renderInput) {
throw new Error('renderPicker was not called');
}
expect(renderInput.initialState.preLoadWarnings).toEqual(['Notion search stopped early: rate limit after first page']);
expect(renderInput.initialState.tree.map((node) => node.title)).toEqual(['Engineering']);
expect(io.stderr()).toContain('Notion search stopped early: rate limit after first page');
expect(io.stdout()).toContain('No changes saved.');
});
it('quits interactive mode without writing when the TUI returns quit', async () => {
const projectDir = join(tempDir, 'project');
const initialized = await initKloProject({ projectDir, projectName: 'warehouse' });
await writeProjectConfig(projectDir, {
...initialized.config,
connections: {
'notion-main': {
driver: 'notion',
auth_token_ref: 'env:NOTION_TOKEN',
crawl_mode: 'selected_roots',
root_page_ids: [PAGE_IDS.engineering],
root_database_ids: [],
root_data_source_ids: [],
max_pages_per_run: 12,
max_knowledge_creates_per_run: 2,
max_knowledge_updates_per_run: 7,
last_successful_cursor: null,
},
},
});
const before = await readFile(join(projectDir, 'klo.yaml'), 'utf-8');
const io = makeIo();
await expect(
runKloConnectionNotion(
{
command: 'pick',
projectDir,
connectionId: 'notion-main',
mode: 'interactive',
},
io.io,
{
env: { NOTION_TOKEN: 'ntn_test_token' },
createNotionApi: vi.fn(() => fakeNotionApi([notionPage(PAGE_IDS.engineering, 'Engineering')])),
renderPicker: vi.fn(async (): Promise<PickerRenderResult> => ({ kind: 'quit' })),
},
),
).resolves.toBe(0);
await expect(readFile(join(projectDir, 'klo.yaml'), 'utf-8')).resolves.toBe(before);
expect(io.stdout()).toContain('No changes saved.');
});
});

View file

@ -0,0 +1,278 @@
import { parseNotionConnectionConfig, resolveNotionAuthToken } from '@klo/context/connections';
import { type NotionApi, type NotionBotInfo, NotionClient } from '@klo/context/ingest';
import {
type KloLocalProject,
type KloProjectConnectionConfig,
loadKloProject,
serializeKloProjectConfig,
} from '@klo/context/project';
import type { KloCliIo } from '../index.js';
import { profileMark } from '../startup-profile.js';
import { buildInitialState, buildPickerTree, type NotionPickerPageInput } from './connection-notion-tree.js';
import {
type NotionPickerTuiIo,
type PickerRenderInput,
type PickerRenderResult,
renderNotionPickerTui,
} from './connection-notion-tui.js';
profileMark('module:commands/connection-notion');
export type KloConnectionNotionArgs =
| {
command: 'pick';
projectDir: string;
connectionId: string;
mode: 'interactive';
}
| {
command: 'pick';
projectDir: string;
connectionId: string;
mode: 'non-interactive';
rootPageIds: string[];
};
export type NotionPickerApi = Pick<NotionApi, 'search' | 'retrieveBotUser'>;
export type { PickerRenderInput, PickerRenderResult };
interface KloConnectionNotionDeps {
env?: Record<string, string | undefined>;
loadProject?: typeof loadKloProject;
createNotionApi?: (authToken: string) => NotionPickerApi;
renderPicker?: (input: PickerRenderInput, io: NotionPickerTuiIo) => Promise<PickerRenderResult>;
}
const NOTION_PICKER_PAGE_CAP = 5000;
function assertSafeConnectionId(connectionId: string): void {
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(connectionId)) {
throw new Error(`Unsafe connection id: ${connectionId}`);
}
}
export function normalizeNotionPageId(value: string): string {
const trimmed = value.trim();
const compact = trimmed.includes('-') ? trimmed.replace(/-/g, '') : trimmed;
if (!/^[0-9a-fA-F]{32}$/.test(compact)) {
throw new Error(`Invalid Notion page UUID: ${value}`);
}
const lower = compact.toLowerCase();
return `${lower.slice(0, 8)}-${lower.slice(8, 12)}-${lower.slice(12, 16)}-${lower.slice(16, 20)}-${lower.slice(20)}`;
}
function recordValue(value: unknown): Record<string, unknown> | null {
return typeof value === 'object' && value !== null && !Array.isArray(value)
? (value as Record<string, unknown>)
: null;
}
function extractTitleFromNotionPage(page: Record<string, unknown>): string {
const properties = recordValue(page.properties);
if (!properties) {
return 'Untitled';
}
for (const property of Object.values(properties)) {
const value = recordValue(property);
if (!value || value.type !== 'title' || !Array.isArray(value.title)) {
continue;
}
const text = value.title
.map((part) => {
const richText = recordValue(part);
return typeof richText?.plain_text === 'string' ? richText.plain_text : '';
})
.join('')
.trim();
if (text.length > 0) {
return text;
}
}
return 'Untitled';
}
function extractParentPageId(page: Record<string, unknown>): string | null {
const parent = recordValue(page.parent);
if (!parent || parent.type !== 'page_id' || typeof parent.page_id !== 'string') {
return null;
}
return normalizeNotionPageId(parent.page_id);
}
export function notionPickerPageFromSearchResult(result: Record<string, unknown>): NotionPickerPageInput {
const id = typeof result.id === 'string' ? normalizeNotionPageId(result.id) : '';
if (!id) {
throw new Error('Notion page search result is missing id');
}
return {
id,
title: extractTitleFromNotionPage(result),
archived: result.archived === true,
parentId: extractParentPageId(result),
};
}
export async function discoverNotionPickerPages(
api: NotionPickerApi,
options: { cap?: number } = {},
): Promise<{ pages: NotionPickerPageInput[]; cappedAtCount: number | null; warnings: string[] }> {
const cap = options.cap ?? NOTION_PICKER_PAGE_CAP;
const pages: NotionPickerPageInput[] = [];
const warnings: string[] = [];
let cursor: string | null | undefined = null;
while (pages.length < cap) {
let response: Awaited<ReturnType<NotionPickerApi['search']>>;
try {
response = await api.search('page', cursor, Math.min(100, cap - pages.length));
} catch (error) {
if (pages.length === 0) {
throw error;
}
const message = error instanceof Error ? error.message : String(error);
warnings.push(`Notion search stopped early: ${message}`);
return { pages, cappedAtCount: null, warnings };
}
for (const result of response.results) {
pages.push(notionPickerPageFromSearchResult(result));
if (pages.length >= cap) {
break;
}
}
if (!response.hasMore || !response.nextCursor || pages.length >= cap) {
return {
pages,
cappedAtCount: response.hasMore ? cap : null,
warnings,
};
}
cursor = response.nextCursor;
}
return { pages, cappedAtCount: cap, warnings };
}
export async function resolveNotionWorkspaceLabel(api: NotionPickerApi, connectionId: string): Promise<string> {
try {
const bot = (await api.retrieveBotUser()) as NotionBotInfo;
const workspaceName = typeof bot.bot?.workspace_name === 'string' ? bot.bot.workspace_name.trim() : '';
if (workspaceName.length > 0) {
return workspaceName;
}
const name = typeof bot.name === 'string' ? bot.name.trim() : '';
return name.length > 0 ? name : connectionId;
} catch {
return connectionId;
}
}
function notionConnection(project: KloLocalProject, connectionId: string): KloProjectConnectionConfig {
const connection = project.config.connections[connectionId];
if (!connection) {
throw new Error(`Connection "${connectionId}" not found`);
}
if (connection.driver !== 'notion') {
throw new Error(`Connection "${connectionId}" is not a Notion connection`);
}
return connection;
}
export async function applyNotionPickerWriteback(
project: KloLocalProject,
connectionId: string,
rootPageIds: string[],
): Promise<void> {
if (rootPageIds.length === 0) {
throw new Error('connection notion pick requires at least one root page id');
}
const existing = notionConnection(project, connectionId);
const nextConfig = {
...project.config,
connections: {
...project.config.connections,
[connectionId]: {
...existing,
crawl_mode: 'selected_roots',
root_page_ids: rootPageIds,
},
},
};
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig(nextConfig),
'klo',
'klo@example.com',
`Pick Notion roots: ${connectionId} (${rootPageIds.length} pages)`,
);
}
export async function runKloConnectionNotion(
args: KloConnectionNotionArgs,
io: KloCliIo = process,
deps: KloConnectionNotionDeps = {},
): Promise<number> {
try {
assertSafeConnectionId(args.connectionId);
const loadProject = deps.loadProject ?? loadKloProject;
if (args.mode === 'interactive') {
const project = await loadProject({ projectDir: args.projectDir });
const rawConnection = notionConnection(project, args.connectionId);
const notion = parseNotionConnectionConfig(rawConnection);
const authToken = await resolveNotionAuthToken(notion.auth_token_ref, { env: deps.env });
const api = deps.createNotionApi ? deps.createNotionApi(authToken) : new NotionClient(authToken);
const discovery = await discoverNotionPickerPages(api);
const tree = buildPickerTree(discovery.pages);
const initialState = buildInitialState({
tree,
existingRootPageIds: notion.root_page_ids,
currentCrawlMode: notion.crawl_mode,
});
const preLoadWarnings = [...discovery.warnings, ...initialState.preLoadWarnings];
const renderState =
preLoadWarnings.length > 0
? {
...initialState,
preLoadWarnings,
}
: initialState;
for (const warning of preLoadWarnings) {
io.stderr.write(`${warning}\n`);
}
const workspaceLabel = await resolveNotionWorkspaceLabel(api, args.connectionId);
const result = await (deps.renderPicker ?? renderNotionPickerTui)(
{
initialState: renderState,
connectionId: args.connectionId,
workspaceLabel,
cappedAtCount: discovery.cappedAtCount,
currentCrawlMode: notion.crawl_mode,
},
io as NotionPickerTuiIo,
);
if (result.kind === 'quit') {
io.stdout.write('No changes saved.\n');
return 0;
}
await applyNotionPickerWriteback(project, args.connectionId, result.rootPageIds);
io.stdout.write(`Connection: ${args.connectionId}\n`);
io.stdout.write(`rootPageIds: ${result.rootPageIds.length}\n`);
io.stdout.write('crawlMode: selected_roots\n');
return 0;
}
const project = await loadProject({ projectDir: args.projectDir });
await applyNotionPickerWriteback(project, args.connectionId, args.rootPageIds);
io.stdout.write(`Connection: ${args.connectionId}\n`);
io.stdout.write(`rootPageIds: ${args.rootPageIds.length}\n`);
io.stdout.write('crawlMode: selected_roots\n');
return 0;
} catch (error) {
io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
return 1;
}
}

Some files were not shown because too many files have changed in this diff Show more