Skip to main content

Batch import pilot for xplain via YAML-driven configuration

Project description

xplain-import-pilot

YAML-driven batch import tool for xplain analytics platform

Import database tables into XOE (Xplain Object Explorer) sessions through declarative configuration files. Define your data model once in YAML, then import entire table hierarchies with a single command.

Features

  • ๐ŸŽฏ Declarative Configuration โ€” Define database connections and object hierarchies in YAML
  • ๐Ÿ”— Multi-Level Relationships โ€” Support for root objects and nested child relationships
  • ๐ŸŽฒ Smart Sampling โ€” Built-in sampling with database-specific random functions
  • ๐Ÿงฎ SQL Dimensions โ€” Add computed dimensions using SQL expressions
  • โœ… Validation First โ€” Comprehensive config validation before any network calls
  • ๐Ÿ” Dry-Run Mode โ€” Preview import plan without connecting to databases
  • ๐Ÿ—„๏ธ Multi-Database Support โ€” PostgreSQL, MySQL, Oracle, DB2, MariaDB, Databricks
  • ๐Ÿงฌ Attribute Import โ€” Import lookup/dimension tables as XAttributes with hierarchy levels
  • ๐Ÿ““ Notebook Export โ€” Generate a Jupyter notebook mirroring the import steps

Installation

Prerequisites

  • Python โ‰ฅ3.12
  • uv package manager

Install from Source

cd /Users/peide/xplainrepo/xplain_import_pilot
uv sync

This installs the xplain-import command-line tool.


Quick Start

1. Get Your XOE Session ID

  1. Open your XOE instance in a browser
  2. Open DevTools (F12 or right-click โ†’ Inspect)
  3. Navigate to Application โ†’ Cookies
  4. Copy the value of JSESSIONID

2. Create a Configuration File

Create my-import.yaml:

database:
  type: POSTGRESQL
  host: localhost
  port: 5432
  user: analytics_user
  password: changeme
  database: sales_db
  schema: public  # optional

objects:
  # Root object
  - name: customers
    table: customers
    role: root
    primary_key: customer_id

  # Child of customers
  - name: orders
    table: orders
    role: child
    parent: customers
    foreign_keys:
      - customer_id

3. Run Import

Dry-run (validate config without connecting):

uv run xplain-import --session-id dummy --config my-import.yaml --dry-run

Execute import:

# Using environment variable (recommended)
export XPLAIN_SESSION_ID=<your-session-id>
uv run xplain-import --config my-import.yaml

# Or using CLI flag
uv run xplain-import --session-id <your-session-id> --config my-import.yaml

Configuration Reference

Database Configuration

database:
  type: POSTGRESQL | MYSQL | ORACLE | DB2 | MARIADB | DATABRICKS
  host: <hostname>
  port: <port-number>
  user: <username>
  password: <password>
  database: <database-name>
  schema: <schema-name>          # optional
  driver_properties:             # optional JDBC properties
    key: value

Object Configuration

Root Object

- name: <object-name>
  table: <table-name>
  role: root
  primary_key: <column-name>
  where_clause: "status = 'active'"  # optional SQL filter
  sampling_rate: 0.8                 # optional (0.0-1.0)
  sql_dimensions:                    # optional computed dimensions
    - name: <dimension-name>
      expression: "EXTRACT(YEAR FROM created_at)"
      type: INT | LONG | CATEGORIAL

Child Object

- name: <object-name>
  table: <table-name>
  role: child
  parent: <parent-object-name>
  foreign_keys:
    - <column-name>
  where_clause: "amount > 0"    # optional
  sampling_rate: 0.5            # optional
  sql_dimensions:               # optional
    - name: <dimension-name>
      expression: "EXTRACT(MONTH FROM order_date)"
      type: INT

Field Descriptions

Field Required Description
name โœ… Unique object name in XOE
table โœ… Database table name
role โœ… root or child
primary_key โœ… (root) Primary key column name
parent โœ… (child) Parent object name
foreign_keys โœ… (child) List of foreign key columns
where_clause โŒ SQL WHERE condition (without "WHERE")
sampling_rate โŒ Fraction of rows to import (0.0-1.0)
sql_dimensions โŒ Computed dimensions via SQL

Attributes (Dimension Lookup Tables)

Attributes import a lookup/dimension table (e.g. payers, categories) as an XAttribute with a drill-down hierarchy, rather than as a plain XTable. Add an attributes: section alongside objects::

attributes:
  - name: payers            # Unique name for this attribute config
    table: payers            # Source table
    attribute_name: Payer     # Display name in XOE
    primary_key: id
    hierarchy_level_columns: [name, ownership]   # leaf first โ†’ coarsest last
    hierarchy_level_names: [Payer, Ownership]    # optional display names per level
    xtable: encounters        # which XTable to attach this attribute to in the XView
Field Required Description
name โœ… Unique attribute config name
table โœ… Source database table
attribute_name โœ… Display name shown in XOE
primary_key โœ… Primary key column
hierarchy_level_columns โœ… Columns forming the drill-down hierarchy, leaf first
hierarchy_level_names โŒ Display names per hierarchy level (defaults to column names)
xtable โŒ XTable to attach the attribute to (default: the root object)

Usage Examples

Multi-Level Hierarchy

objects:
  - name: customers
    table: customers
    role: root
    primary_key: customer_id

  - name: orders
    table: orders
    role: child
    parent: customers
    primary_key: order_id        # required: orders is a parent to order_items below
    foreign_keys: [customer_id]

  - name: order_items
    table: order_items
    role: child
    parent: orders
    foreign_keys: [customer_id, order_id]   # full compound key of orders

Filtering and Sampling

objects:
  - name: active_customers
    table: customers
    role: root
    primary_key: customer_id
    where_clause: "status = 'active' AND created_at > '2023-01-01'"
    sampling_rate: 0.1  # Import 10% of matching rows

Computed Dimensions

objects:
  - name: orders
    table: orders
    role: root
    primary_key: order_id
    sql_dimensions:
      - name: order_year
        expression: "EXTRACT(YEAR FROM order_date)"
        type: INT
      - name: order_month
        expression: "EXTRACT(MONTH FROM order_date)"
        type: INT
      - name: order_quarter
        expression: "CONCAT('Q', CAST(CEIL(EXTRACT(MONTH FROM order_date)/3.0) AS VARCHAR))"
        type: CATEGORIAL

Command-Line Options

xplain-import [OPTIONS]

Options:
  --version                Show version and exit
  --session-id ID          XOE JSESSIONID (or set XPLAIN_SESSION_ID env var)
  --config PATH            Path to YAML configuration file (required)
  --url URL                XOE server URL (default: http://localhost:8080, or XPLAIN_SERVER_URL env var)
  --dry-run                Validate config and print plan without connecting
  --log-level LEVEL        Logging verbosity: DEBUG, INFO, WARNING, ERROR (default: INFO)
  --sampling-rate RATE     Override sampling rate for all objects (0.0-1.0]
  --persist                Save configs to XOE for reuse (connection, xtables, xview)
  --persist-prefix PREFIX  Prefix for saved config names (default: YAML filename)
  --persist-ownership LVL  Ownership: PUBLIC or PRIVATE (default: PUBLIC)
  --export-notebook        Export a Jupyter notebook with the xplain method sequence
  --notebook-output PATH   Output path for notebook (default: <config-name>_import.ipynb)
  -h, --help               Show help message

Exporting a Notebook

--export-notebook writes a Jupyter notebook that replays the same import steps (xplain.Xsession, connection setup, per-object import_xtable() calls, xview build) as executable cells โ€” useful for handing off an import to someone working in a notebook, or for tweaking a step interactively after the CLI run:

uv run xplain-import --config sales.yaml --export-notebook
uv run xplain-import --config sales.yaml --export-notebook --notebook-output sales_import.ipynb

Persisting Configurations

Use --persist to save all configurations to XOE for later reuse:

uv run xplain-import --config sales.yaml --persist

This saves:

  • Database connection โ†’ sales_connection.xdbconnection
  • XTable configs โ†’ sales_<object>.xtableconfig for each object
  • XView โ†’ sales_view.xview

Customize the prefix or ownership:

uv run xplain-import --config sales.yaml --persist --persist-prefix my_project --persist-ownership PRIVATE

Troubleshooting

Config Validation Errors

Error: object 'orders' references parent 'customers' which is not defined

Solution: Ensure the parent object is defined in the objects list before the child.


Error: object 'customers': root requires 'primary_key'

Solution: Add primary_key: <column-name> to root objects.


Error: validation error for DimensionType

Solution: Ensure type is one of: INT, LONG, CATEGORIAL (note spelling).


Connection Errors

Error: DB connection test failed

Solution:

  • Verify database credentials (host, port, user, password)
  • Check network connectivity to database
  • Ensure database user has SELECT permissions on tables

Error: Import failed: ... session ... not found

Solution:

  • Verify XOE session is still active (sessions expire)
  • Get a fresh JSESSIONID from browser
  • Ensure XOE server URL is correct

Import Errors

Error: xtable import failed for 'orders'

Solution:

  • Check that foreign key columns exist in child table
  • Verify WHERE clause syntax is valid SQL
  • Ensure parent object was imported successfully

Security Best Practices

[!WARNING] Credential Security

  • Never commit YAML files with real passwords to version control
  • Use environment variables for sensitive data
  • Prefer XPLAIN_SESSION_ID env var over --session-id CLI flag (CLI args are visible in process lists)

Recommended approach:

  1. Create .env file (already in .gitignore):

    XPLAIN_SESSION_ID=your_session_id_here
    
  2. Load environment variables:

    source .env  # or use direnv, dotenv, etc.
    
  3. Run without exposing secrets:

    uv run xplain-import --config my-import.yaml
    

Development

Setup Development Environment

# Install with dev dependencies
uv sync --extra dev

# Run tests
uv run pytest

# Type checking
uv run mypy xplain_import_pilot/

# Linting
uv run ruff check xplain_import_pilot/

Running Tests

# All tests with coverage
uv run pytest tests/ -v --cov=xplain_import_pilot --cov-report=term-missing

# Specific test file
uv run pytest tests/test_config.py -v

# With debug output
uv run pytest tests/ -v -s

How It Works

The import pipeline follows these steps:

  1. Connect โ€” Establish connection to existing XOE session via http_session_id
  2. Database Setup โ€” Create and test database connection
  3. Import XTables โ€” Import each table in dependency order (parents before children)
  4. Build XView โ€” Construct view configuration mirroring object hierarchy
  5. Load Session โ€” Load XView into live XOE session

Objects are imported in topological order using stable sorting (preserves YAML order for objects at the same depth).


Compound Keys & FK Dimension Alignment

How Xplain builds compound object keys

Xplain identifies every record in a hierarchy through a compound key โ€” the ordered concatenation of ancestor key dimensions down to the object itself.

Level Object Own PK column Compound key dimensions
1 (root) patients patientid [patientid]
2 (child) admissions admissionid [patientid, admissionid]
3 (grandchild) clinical_observations observationid [patientid, admissionid, observationid]

A child's foreignKeyDimensions must equal the full compound key of its parent โ€” not just the immediate FK column. The importer calculates and applies this expansion automatically; you only need to list the direct FK column(s) in the YAML.

FK column names vs. dimension names

After auto-mapping, every column starts with dimensionName == dbColumnName. But the parent's key dimension may have been named differently from the corresponding FK column in the child table:

Table DB column Must become dimension
patients patientid patientid (no change)
admissions patient_id patientid โ€” renamed
clinical_observations patient_id patientid โ€” renamed
clinical_observations admission_id admissionid โ€” renamed

foreignKeyDimensions must reference dimension names, not column names. The importer aligns them automatically using normalized name matching: both the column name and the parent dimension name are lowercased and stripped of underscores. If they match, dimensionName is updated to the parent's canonical form. dbColumnName is never changed.

Normalization rule

normalize(name) = name.lower().replace("_", "")

patient_id   โ†’ patientid
patientid    โ†’ patientid   โ† same โ†’ dimension is renamed to "patientid"

admission_id โ†’ admissionid
admissionid  โ†’ admissionid โ† same โ†’ dimension is renamed to "admissionid"

Worked example โ€” 3-level hierarchy

YAML config:

database:
  type: POSTGRESQL
  host: localhost
  port: 5432
  user: postgres
  password: secret
  database: hospital

objects:
  - name: patients
    table: patients
    role: root
    primary_key: patientid

  - name: admissions
    table: admissions
    role: child
    parent: patients
    primary_key: admissionid
    foreign_keys: [patient_id]        # direct FK column in the admissions table

  - name: clinical_observations
    table: clinical_observations
    role: child
    parent: admissions
    primary_key: observationid
    foreign_keys: [admission_id]      # direct FK column; importer expands to full compound key

Step 1 โ€” patients (root)

No FK processing needed. Compound key tracked: ["patientid"].

{
  "objectName": "patients",
  "keyDimension": "patientid",
  "foreignKeyDimensions": null,
  "dimensionConfigurations": [
    { "dbColumnName": "patientid", "dimensionName": "patientid" },
    { "dbColumnName": "name",      "dimensionName": "name" }
  ]
}

Step 2 โ€” admissions (child of patients)

Parent compound key: ["patientid"].

The admissions table has a column patient_id. Normalized: patientid == patientid โ†’ dimensionName is renamed from patient_id to patientid. foreignKeyDimensions is set to the full parent compound key ["patientid"].

Compound key tracked: ["patientid", "admissionid"].

{
  "objectName": "admissions",
  "keyDimension": "admissionid",
  "foreignKeyDimensions": ["patientid"],
  "dimensionConfigurations": [
    { "dbColumnName": "patient_id",   "dimensionName": "patientid"   },  โ† renamed
    { "dbColumnName": "admissionid",  "dimensionName": "admissionid" },
    { "dbColumnName": "admit_date",   "dimensionName": "admit_date"  }
  ]
}

Step 3 โ€” clinical_observations (child of admissions)

Parent compound key: ["patientid", "admissionid"].

Two columns are aligned:

  • patient_id โ†’ normalize โ†’ patientid == patientid โ†’ renamed to patientid
  • admission_id โ†’ normalize โ†’ admissionid == admissionid โ†’ renamed to admissionid

foreignKeyDimensions is set to the full parent compound key ["patientid", "admissionid"] (not just the direct admission_id).

Compound key tracked: ["patientid", "admissionid", "observationid"].

{
  "objectName": "clinical_observations",
  "keyDimension": "observationid",
  "foreignKeyDimensions": ["patientid", "admissionid"],
  "dimensionConfigurations": [
    { "dbColumnName": "patient_id",    "dimensionName": "patientid"    },  โ† renamed
    { "dbColumnName": "admission_id",  "dimensionName": "admissionid"  },  โ† renamed
    { "dbColumnName": "observationid", "dimensionName": "observationid"},
    { "dbColumnName": "value",         "dimensionName": "value"        }
  ]
}

Tables that do not carry grandparent FK columns

In a normalized (3NF) schema, clinical_observations may only have admission_id โ€” no patient_id column. In that case:

  • The importer still sets foreignKeyDimensions = ["patientid", "admissionid"]
  • admissionid dimension exists (column admission_id was renamed) โœ“
  • patientid dimension does not exist โ€” Xplain will reject the import

Options:

  1. Add patient_id to the clinical_observations table in the database (denormalize the key chain).
  2. Shorten the hierarchy โ€” import admissions only (without going one level deeper).
  3. Use admissions as the root object and import clinical_observations as its child.

Auto-Pilot Mode

Auto-pilot mode removes the need to write a YAML config by hand. It connects to your database, discovers all tables, infers primary keys and foreign-key relationships from column naming conventions, proposes a root/child hierarchy, writes a YAML file, and โ€” after a single confirmation โ€” executes the import.

Quick Start

# Using a DB config file
export XPLAIN_SESSION_ID=<your-session-id>
uv run xplain-import-autopilot --db-config conn.yaml

# Using inline flags
uv run xplain-import-autopilot \
    --db-type POSTGRESQL --db-host localhost --db-port 5432 \
    --db-user admin --db-password secret --db-name mydb

DB Config File Format

conn.yaml needs only the database: section (same format as a full import config):

database:
  type: POSTGRESQL
  host: localhost
  port: 5432
  user: postgres
  password: secret
  database: mydb
  schema: public   # optional โ€” restrict to one schema

What Happens Step by Step

  1. Connect โ€” Verifies XOE session and database credentials.
  2. Explore โ€” Lists all tables in the schema, fetches a small sample of rows per table for type inference.
  3. Infer relationships โ€” Detects foreign keys from column names ending in _id. A column foo_id in table bar is treated as a FK to table foo (or its plural variant). Exact name matches get 90 % confidence; singular/plural matches get 75 %.
  4. Suggest hierarchy โ€” Tables with no outgoing FK become roots; others become children of their highest-confidence parent. Primary keys are inferred from column names (id, <table>_id, etc.).
  5. Display โ€” Prints a schema table, an inferred-relationships table, and a tree view of the suggested hierarchy, with confidence indicators coloured green/yellow/red.
  6. Write YAML โ€” Saves the suggested config to <db-name>_autopilot.yaml (override with --output-yaml). Low-confidence items are flagged as # WARNING comments at the top of the file.
  7. Confirm โ€” Prompts Proceed with import? [y/N] (skip with --yes).
  8. Import โ€” Runs the same pipeline as xplain-import.

Command-Line Reference

xplain-import-autopilot [OPTIONS]

Database connection (one of --db-config or all --db-* flags required):
  --db-config YAML        YAML file with a 'database:' section
  --db-type TYPE          POSTGRESQL | MYSQL | ORACLE | DB2 | MARIADB | DATABRICKS
  --db-host HOST
  --db-port PORT
  --db-user USER
  --db-password PASSWORD
  --db-name DBNAME
  --db-schema SCHEMA      Restrict table discovery to this schema

XOE session:
  --session-id ID         XOE JSESSIONID (or set XPLAIN_SESSION_ID env var)
  --url URL               XOE server URL (default: http://localhost:8080, or XPLAIN_SERVER_URL env var)

Autopilot options:
  --output-yaml PATH      Save suggested config here (default: <db-name>_autopilot.yaml)
  --max-tables N          Cap table discovery at N tables (default: 50)
  --sample-rows N         Rows fetched per table for type inference (default: 5)
  --yes                   Skip confirmation prompt
  --dry-run               Explore and print suggestion only โ€” do not import
  --persist               Persist xtable configs and xview to XOE after import
  --persist-prefix PREFIX Prefix for saved config names (default: <db-name>)
  --log-level LEVEL       DEBUG | INFO | WARNING | ERROR (default: INFO)

Common Workflows

Preview without importing:

uv run xplain-import-autopilot --db-config conn.yaml --dry-run

Fully automated (no prompts, save configs to XOE):

uv run xplain-import-autopilot --db-config conn.yaml --yes --persist

Limit to a specific schema and cap table count:

uv run xplain-import-autopilot --db-config conn.yaml --db-schema analytics --max-tables 20

Review, edit, then import manually:

# 1. Generate the YAML (dry-run so nothing is imported yet)
uv run xplain-import-autopilot --db-config conn.yaml --dry-run

# 2. Edit the generated file
vi mydb_autopilot.yaml

# 3. Import using the regular CLI
uv run xplain-import --config mydb_autopilot.yaml

Reading the Output

Confidence colours in the hierarchy tree:

Colour Meaning
Green High confidence (โ‰ฅ 85 % for FKs, โ‰ฅ 70 % for PKs)
Yellow Medium confidence โ€” review before importing
Red Low confidence โ€” manual correction recommended

YAML warnings: Any relationship with confidence below 80 % (FK) or 70 % (PK) appears as a # WARNING comment at the top of the generated file. Review these before running the import.

Limitations

  • FK inference relies on column naming conventions (<table>_id). Columns that use non-standard naming won't be detected โ€” edit the generated YAML to add them.
  • Composite foreign keys are not currently inferred; add them manually in the YAML.
  • Tables with no columns matching the PK heuristics fall back to the first column โ€” check the warning and correct if needed.
  • The --max-tables limit applies to the first N tables returned by the database driver; if the most important tables appear later, increase the limit or use --db-schema to narrow the search.

AI/Claude Skill

This repository includes an AI skill for natural language database imports. The skill allows Claude (or other AI agents) to generate YAML configurations from natural language descriptions.

Skill Location

skill/
โ”œโ”€โ”€ SKILL.md                    # Main skill instructions
โ”œโ”€โ”€ references/
โ”‚   โ””โ”€โ”€ config-reference.md     # YAML field reference
โ””โ”€โ”€ examples/
    โ”œโ”€โ”€ example.yaml            # Basic example
    โ””โ”€โ”€ productronica.yaml      # Multi-level example

Installing the Skill

For Clawdbot users:

# Copy to your workspace skills directory
cp -r skill/ ~/.clawdbot/workspace/skills/xplain-import/

# Or symlink for development
ln -s $(pwd)/skill ~/.clawdbot/workspace/skills/xplain-import

For Claude Code users: Add the skill path to your project's .claude/settings.json or reference it in your AGENTS.md.

Using the Skill

Once installed, describe your data structure in natural language:

Example prompts:

  • "Import customers and their orders from PostgreSQL into xplain"
  • "Create an xplain import for products with categories as a computed dimension"
  • "Load boards โ†’ components โ†’ alerts hierarchy from the productronica database"

The AI will:

  1. Ask clarifying questions about database connection and structure
  2. Generate a YAML configuration file
  3. Optionally run the import with --dry-run first
  4. Execute the import with --persist to save configurations

Skill Triggers

The skill activates on phrases like:

  • "import into xplain"
  • "xplain import"
  • "create xtables"
  • "load database into XOE"
  • Describing table structures with parent-child relationships

License

See project repository for license information.


Support

For issues or questions:

  • Check the Troubleshooting section
  • Review example.yaml for reference configuration
  • Examine logs with --log-level DEBUG for detailed diagnostics

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

xplain_import_pilot-0.1.0.tar.gz (60.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

xplain_import_pilot-0.1.0-py3-none-any.whl (59.8 kB view details)

Uploaded Python 3

File details

Details for the file xplain_import_pilot-0.1.0.tar.gz.

File metadata

  • Download URL: xplain_import_pilot-0.1.0.tar.gz
  • Upload date:
  • Size: 60.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for xplain_import_pilot-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1eaf76a38fb0d9571f35b8d5f08aad35fff2534329f88096ef5e6e2b0579b1de
MD5 921cf8d1a59fa397a173ac6f2e3fe6bf
BLAKE2b-256 4d386221b016ed6ae4473aa5a7a9dd53e6d611ab03b11f5a3236cf2b60ba08c3

See more details on using hashes here.

File details

Details for the file xplain_import_pilot-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: xplain_import_pilot-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 59.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for xplain_import_pilot-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3ad715264d7196fbb6e6d9fee8209d95767439c86bf47125f504d2f3dd53daab
MD5 67f2aa8c4ffde18918f09529a1acbcfb
BLAKE2b-256 66ce8cd9fa9d9a73d6ce3e288b801549ce82e8d1ddcd3d90691b9e22f71787be

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page