diff --git a/packages/visualization/src/components/Chart.tsx b/packages/visualization/src/components/Chart.tsx
index 17930dea..dfb2a909 100644
--- a/packages/visualization/src/components/Chart.tsx
+++ b/packages/visualization/src/components/Chart.tsx
@@ -284,6 +284,7 @@ export function Chart({
const renderLoading = () => (
;
/**
* Extended vgplot API with coordinator access (not in public types).
* The coordinator provides direct DuckDB query access for data inspection.
+ *
+ * Note: The coordinator is at api.context.coordinator (not api.coordinator)
+ * as set up by createAPIContext({ coordinator }).
*/
interface VgplotAPIExtended extends VgplotAPI {
- coordinator?: {
- query: (sql: string) => Promise<{ toArray: () => { val: unknown }[] }>;
+ context?: {
+ coordinator?: {
+ query: (
+ sql: string,
+ options?: { type?: string; cache?: boolean },
+ ) => Promise;
+ };
};
colorDomain?: (domain: string[]) => void;
}
@@ -437,18 +445,18 @@ function setupColorDomain(
colorColumn: string,
tableName: string,
): void {
- const { coordinator } = api;
+ const coordinator = api.context?.coordinator;
if (!coordinator?.query) return;
coordinator
.query(
`SELECT DISTINCT "${colorColumn}" as val FROM ${tableName} ORDER BY "${colorColumn}"`,
+ { type: "json" },
)
.then((result) => {
- if (!result?.toArray) return;
+ if (!Array.isArray(result)) return;
- const rows = result.toArray();
- const domain = rows.map((row) => String(row.val));
+ const domain = result.map((row) => String((row as { val: unknown }).val));
if (domain.length > 0 && api.colorDomain) {
api.colorDomain(domain);
@@ -459,6 +467,115 @@ function setupColorDomain(
});
}
+// ============================================================================
+// Encoding Validation
+// ============================================================================
+
+/**
+ * Validation result for chart encoding.
+ */
+interface EncodingValidation {
+ valid: boolean;
+ missingChannels: string[];
+}
+
+/**
+ * Validate that required encoding channels are present for a chart type.
+ * All vgplot marks require at least x and y channels.
+ *
+ * @param encoding - The chart encoding to validate
+ * @param _chartType - The visualization type (unused, all types require x/y)
+ * @returns Validation result with missing channel names
+ */
+function validateEncoding(
+ encoding: ChartEncoding,
+ _chartType: VisualizationType,
+): EncodingValidation {
+ const missingChannels: string[] = [];
+
+ // All supported chart types require both x and y
+ if (!encoding.x) {
+ missingChannels.push("x");
+ }
+ if (!encoding.y) {
+ missingChannels.push("y");
+ }
+
+ return {
+ valid: missingChannels.length === 0,
+ missingChannels,
+ };
+}
+
+/**
+ * Render an "incomplete encoding" message in the container using safe DOM methods.
+ */
+function renderIncompleteEncoding(
+ container: HTMLElement,
+ missingChannels: string[],
+): void {
+ // Clear container safely
+ while (container.firstChild) {
+ container.removeChild(container.firstChild);
+ }
+
+ const channelList = missingChannels.map((c) => c.toUpperCase()).join(" and ");
+
+ // Create wrapper div
+ const wrapper = document.createElement("div");
+ wrapper.style.cssText = `
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ padding: 24px;
+ text-align: center;
+ color: var(--muted-foreground, #6b7280);
+ `;
+
+ // Create SVG icon
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg.setAttribute("viewBox", "0 0 24 24");
+ svg.setAttribute("fill", "none");
+ svg.setAttribute("stroke", "currentColor");
+ svg.setAttribute("stroke-width", "1.5");
+ svg.style.cssText =
+ "width: 48px; height: 48px; margin-bottom: 12px; opacity: 0.5;";
+
+ const path1 = document.createElementNS("http://www.w3.org/2000/svg", "path");
+ path1.setAttribute("d", "M3 3v18h18");
+ path1.setAttribute("stroke-linecap", "round");
+ path1.setAttribute("stroke-linejoin", "round");
+
+ const path2 = document.createElementNS("http://www.w3.org/2000/svg", "path");
+ path2.setAttribute("d", "M18.7 8l-5.1 5.2-2.8-2.7L7 14.3");
+ path2.setAttribute("stroke-linecap", "round");
+ path2.setAttribute("stroke-linejoin", "round");
+
+ svg.appendChild(path1);
+ svg.appendChild(path2);
+
+ // Create title text
+ const title = document.createElement("p");
+ title.style.cssText = "font-size: 14px; font-weight: 500; margin: 0 0 4px 0;";
+ title.textContent = `Select ${channelList} axis`;
+
+ // Create description text
+ const desc = document.createElement("p");
+ desc.style.cssText = "font-size: 12px; opacity: 0.7; margin: 0;";
+ desc.textContent = "Configure the encoding to render this chart";
+
+ wrapper.appendChild(svg);
+ wrapper.appendChild(title);
+ wrapper.appendChild(desc);
+ container.appendChild(wrapper);
+}
+
+// ============================================================================
+// Mark Building
+// ============================================================================
+
/**
* Build a vgplot mark for the given chart type.
*
@@ -566,6 +683,17 @@ export function createVgplotRenderer(api: VgplotAPI): ChartRenderer {
type: VisualizationType,
config: ChartConfig,
): ChartCleanup {
+ // Validate encoding has required channels before attempting to render
+ const validation = validateEncoding(config.encoding, type);
+ if (!validation.valid) {
+ renderIncompleteEncoding(container, validation.missingChannels);
+ return () => {
+ while (container.firstChild) {
+ container.removeChild(container.firstChild);
+ }
+ };
+ }
+
try {
// Build plot options
const mark = buildMark(api, type, config.tableName, config.encoding);
diff --git a/samples/README.md b/samples/README.md
new file mode 100644
index 00000000..3ee40f28
--- /dev/null
+++ b/samples/README.md
@@ -0,0 +1,282 @@
+# Sample Data: Acme Software Inc.
+
+This sample dataset represents a fictional B2B SaaS company with multiple products. It demonstrates how DashFrame can join data across different domains to unlock powerful cross-functional insights.
+
+## Data Overview
+
+### Company Profile
+
+- **Company**: Acme Software Inc.
+- **Products**: 5 SaaS applications (CloudSync Pro, DataVault, TeamFlow, AnalyticsHub, Mobile SDK)
+- **Team**: 15 employees across Engineering, Product, Design, Sales, and Operations
+- **Customers**: 12 business customers ranging from startups to enterprises
+
+### File Structure
+
+```
+samples/
+├── Internal Data (Company Operations)
+│ ├── employees.json # Team members, roles, salaries
+│ ├── departments.json # Org structure, budgets
+│ ├── projects.json # Products the company builds
+│ ├── sprints.csv # Engineering velocity metrics
+│ ├── expenses.csv # Operational costs
+│ └── revenue.csv # Revenue by product/channel
+│
+└── External Data (Product Analytics)
+ ├── app-users.json # Customer accounts
+ ├── app-events.csv # User activity/behavior
+ └── subscriptions.csv # Billing and plans
+```
+
+---
+
+## Join Relationships
+
+```
+┌─────────────┐ ┌──────────────┐ ┌─────────────┐
+│ departments │────▶│ employees │────▶│ sprints │
+│ │ │ │ │ │
+│ department_id │ employee_id │ │ project_id │
+└─────────────┘ │ department_id│ └──────┬──────┘
+ │ │ reports_to │ │
+ │ └──────────────┘ │
+ │ │ │
+ ▼ ▼ ▼
+┌─────────────┐ ┌──────────────┐ ┌─────────────┐
+│ expenses │ │ projects │◀────│ revenue │
+│ │ │ │ │ │
+│ department_id │ project_id │ │ project_id │
+│ approved_by │────▶│ team_lead │ └─────────────┘
+└─────────────┘ │ product_mgr │
+ └──────┬───────┘
+ │
+ ┌────────────────┼────────────────┐
+ ▼ ▼ ▼
+ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐
+ │ app-events │ │subscriptions│ │ app-users │
+ │ │ │ │ │ │
+ │ project_id │ │ project_id │ │projects_using│
+ │ user_id │──│ user_id │──│ user_id │
+ └─────────────┘ └─────────────┘ └──────────────┘
+```
+
+---
+
+## Sample Report Ideas
+
+### 1. Executive Dashboard
+
+**Audience**: CEO, Board
+**Data Sources**: `revenue.csv`, `expenses.csv`, `subscriptions.csv`
+
+| Metric | Query Logic |
+| --------------------- | --------------------------------------------------- |
+| Total MRR | `SUM(subscriptions.mrr) WHERE status = 'active'` |
+| MRR Growth | Compare `revenue.total_mrr` month-over-month |
+| Burn Rate | `SUM(expenses.amount)` by month |
+| Runway | Cash balance / monthly burn |
+| Net Revenue Retention | `(Starting MRR + Expansion - Churn) / Starting MRR` |
+
+**Visualizations**:
+
+- Line chart: MRR trend over time
+- Stacked bar: Revenue by product
+- Gauge: Burn rate vs budget
+
+---
+
+### 2. Engineering Velocity Report
+
+**Audience**: VP Engineering, Engineering Managers
+**Data Sources**: `sprints.csv`, `projects.json`, `employees.json`
+
+| Metric | Query Logic |
+| --------------- | ---------------------------------------------------- |
+| Sprint Velocity | `AVG(completed_points)` per project |
+| Completion Rate | `completed_points / planned_points * 100` |
+| Bug Ratio | `bugs_found / completed_points` |
+| Team Efficiency | Points per engineer (`completed_points / team_size`) |
+
+**Visualizations**:
+
+- Line chart: Velocity trend by project
+- Scatter plot: Team size vs velocity (diminishing returns?)
+- Bar chart: Bug found vs fixed ratio
+
+**Cross-Domain Insight**: Join with `revenue.csv` to calculate **Engineering Cost per MRR Dollar**:
+
+```sql
+SELECT
+ p.name,
+ SUM(e.salary) / 12 as monthly_eng_cost,
+ r.total_mrr,
+ (SUM(e.salary) / 12) / r.total_mrr as cost_per_mrr_dollar
+FROM projects p
+JOIN employees e ON e.employee_id = p.team_lead
+JOIN revenue r ON r.project_id = p.project_id
+GROUP BY p.project_id
+```
+
+---
+
+### 3. Customer Health Dashboard
+
+**Audience**: Customer Success, Product
+**Data Sources**: `app-users.json`, `app-events.csv`, `subscriptions.csv`
+
+| Metric | Query Logic |
+| -------------------- | --------------------------------------------------- |
+| DAU/MAU Ratio | Daily active / Monthly active users |
+| Avg Session Duration | `AVG(duration_secs)` from app-events |
+| Feature Adoption | Count of distinct `event_type` per user |
+| Churn Risk | Users with declining activity + approaching renewal |
+
+**Visualizations**:
+
+- Heatmap: Activity by hour/day of week
+- Cohort chart: Retention by signup month
+- Bar chart: Events by type
+
+**Cross-Domain Insight**: Join with `subscriptions.csv` to find **Revenue at Risk**:
+
+```sql
+SELECT
+ u.company,
+ s.mrr,
+ COUNT(e.event_id) as events_last_30d,
+ CASE WHEN COUNT(e.event_id) < 10 THEN 'High Risk'
+ WHEN COUNT(e.event_id) < 50 THEN 'Medium Risk'
+ ELSE 'Healthy' END as health_status
+FROM app_users u
+JOIN subscriptions s ON s.user_id = u.user_id
+LEFT JOIN app_events e ON e.user_id = u.user_id
+ AND e.timestamp > DATE_SUB(NOW(), INTERVAL 30 DAY)
+GROUP BY u.user_id
+ORDER BY s.mrr DESC
+```
+
+---
+
+### 4. Product-Market Fit Analysis
+
+**Audience**: Product, Growth
+**Data Sources**: `app-users.json`, `subscriptions.csv`, `revenue.csv`
+
+| Metric | Query Logic |
+| ------------------- | --------------------------------------------------- |
+| LTV by Segment | `SUM(mrr) * avg_lifetime` grouped by `company_size` |
+| CAC Payback | Months to recover acquisition cost |
+| Expansion Revenue % | `expansion_mrr / total_mrr` |
+| NPS by Plan | Survey data joined with plan tier |
+
+**Visualizations**:
+
+- Bubble chart: Company size vs LTV vs count
+- Funnel: Signup source → Trial → Paid → Expansion
+- Pie chart: Revenue by industry
+
+**Cross-Domain Insight**: Join with `projects.json` to find **Best Product-Segment Fit**:
+
+```sql
+SELECT
+ p.name as product,
+ u.industry,
+ u.company_size,
+ COUNT(DISTINCT s.user_id) as customers,
+ AVG(s.mrr) as avg_mrr,
+ SUM(s.mrr) as total_mrr
+FROM subscriptions s
+JOIN app_users u ON u.user_id = s.user_id
+JOIN projects p ON p.project_id = s.project_id
+WHERE s.status = 'active'
+GROUP BY p.project_id, u.industry, u.company_size
+ORDER BY total_mrr DESC
+```
+
+---
+
+### 5. Financial Operations Report
+
+**Audience**: CFO, Finance
+**Data Sources**: `expenses.csv`, `departments.json`, `revenue.csv`
+
+| Metric | Query Logic |
+| ------------------ | ------------------------------------------ |
+| Spend by Category | `SUM(amount)` grouped by `category` |
+| Budget Utilization | `SUM(expenses) / department.budget_annual` |
+| Cost per Employee | Total dept expenses / headcount |
+| Gross Margin | `(Revenue - COGS) / Revenue` |
+
+**Visualizations**:
+
+- Treemap: Expenses by department → category
+- Line chart: Cloud costs over time
+- Table: Top 10 vendors by spend
+
+**Cross-Domain Insight**: Join with `projects.json` and `revenue.csv` for **Product Profitability**:
+
+```sql
+SELECT
+ p.name,
+ r.total_mrr * 12 as arr,
+ SUM(CASE WHEN e.category = 'cloud' THEN e.amount ELSE 0 END) as infra_cost,
+ (r.total_mrr * 12 - SUM(e.amount)) as gross_profit
+FROM projects p
+JOIN revenue r ON r.project_id = p.project_id
+JOIN expenses e ON e.description LIKE CONCAT('%', p.name, '%')
+GROUP BY p.project_id
+```
+
+---
+
+### 6. Sales Performance Dashboard
+
+**Audience**: VP Sales, Account Executives
+**Data Sources**: `subscriptions.csv`, `app-users.json`, `revenue.csv`, `employees.json`
+
+| Metric | Query Logic |
+| ------------- | --------------------------------- |
+| New MRR | `SUM(new_mrr)` from revenue |
+| Avg Deal Size | `AVG(mrr)` from new subscriptions |
+| Win Rate | Closed won / Total opportunities |
+| Sales Cycle | Days from signup to paid |
+
+**Visualizations**:
+
+- Leaderboard: Rep performance
+- Funnel: Pipeline stages
+- Map: Revenue by country
+
+---
+
+### 7. Cross-Functional OKR Tracker
+
+**Audience**: Leadership Team
+**Data Sources**: All files
+
+This report brings together metrics from across the organization:
+
+| Department | Key Result | Data Source | Target | Actual |
+| ----------- | ---------------------------- | ------------------- | -------- | ------- |
+| Engineering | Ship 100 story points/sprint | `sprints.csv` | 100 | 94 |
+| Product | Reach 50k MAU on CloudSync | `app-events.csv` | 50,000 | 45,000 |
+| Sales | Close $50k new MRR | `revenue.csv` | $50,000 | $47,200 |
+| Finance | Keep burn under $100k/mo | `expenses.csv` | $100,000 | $98,500 |
+| Success | Maintain <2% monthly churn | `subscriptions.csv` | 2% | 1.8% |
+
+---
+
+## Getting Started
+
+1. **Import the data**: Upload CSV and JSON files as separate data sources
+2. **Define joins**: Connect tables using the relationship diagram above
+3. **Build visualizations**: Start with the report ideas above
+4. **Explore**: Discover your own cross-domain insights!
+
+## Data Quality Notes
+
+- All data is fictional and generated for demonstration purposes
+- Dates range from 2022-2024
+- Financial figures are in USD
+- Some records include churned/inactive status for realistic churn analysis
diff --git a/samples/app-events.csv b/samples/app-events.csv
new file mode 100644
index 00000000..1845cdd4
--- /dev/null
+++ b/samples/app-events.csv
@@ -0,0 +1,41 @@
+event_id,user_id,project_id,event_type,timestamp,session_id,platform,country,duration_secs
+evt-001,usr-001,proj-001,login,2024-01-15T09:00:00Z,sess-001,web,USA,0
+evt-002,usr-001,proj-001,file_upload,2024-01-15T09:05:32Z,sess-001,web,USA,45
+evt-003,usr-001,proj-001,file_share,2024-01-15T09:08:15Z,sess-001,web,USA,12
+evt-004,usr-001,proj-001,logout,2024-01-15T10:30:00Z,sess-001,web,USA,0
+evt-005,usr-002,proj-003,login,2024-01-15T14:22:00Z,sess-002,desktop,Spain,0
+evt-006,usr-002,proj-003,task_create,2024-01-15T14:25:18Z,sess-002,desktop,Spain,28
+evt-007,usr-002,proj-003,comment_add,2024-01-15T14:32:45Z,sess-002,desktop,Spain,15
+evt-008,usr-003,proj-001,login,2024-01-15T02:15:00Z,sess-003,web,China,0
+evt-009,usr-003,proj-001,file_download,2024-01-15T02:18:30Z,sess-003,web,China,120
+evt-010,usr-003,proj-002,login,2024-01-15T02:45:00Z,sess-004,web,China,0
+evt-011,usr-003,proj-002,backup_create,2024-01-15T02:46:12Z,sess-004,web,China,180
+evt-012,usr-004,proj-003,login,2024-01-15T11:00:00Z,sess-005,mobile,Germany,0
+evt-013,usr-004,proj-003,task_complete,2024-01-15T11:05:22Z,sess-005,mobile,Germany,8
+evt-014,usr-005,proj-001,login,2024-01-15T05:30:00Z,sess-006,web,India,0
+evt-015,usr-005,proj-001,folder_create,2024-01-15T05:32:45Z,sess-006,web,India,5
+evt-016,usr-006,proj-002,login,2024-01-15T09:45:00Z,sess-007,web,UK,0
+evt-017,usr-006,proj-002,backup_restore,2024-01-15T09:50:18Z,sess-007,web,UK,240
+evt-018,usr-007,proj-003,login,2024-01-15T16:00:00Z,sess-008,desktop,Brazil,0
+evt-019,usr-007,proj-003,project_create,2024-01-15T16:02:30Z,sess-008,desktop,Brazil,22
+evt-020,usr-007,proj-004,login,2024-01-15T16:30:00Z,sess-009,web,Brazil,0
+evt-021,usr-007,proj-004,dashboard_view,2024-01-15T16:31:15Z,sess-009,web,Brazil,180
+evt-022,usr-008,proj-001,login,2024-01-16T01:00:00Z,sess-010,web,Japan,0
+evt-023,usr-008,proj-001,file_sync,2024-01-16T01:02:00Z,sess-010,web,Japan,300
+evt-024,usr-008,proj-002,login,2024-01-16T01:15:00Z,sess-011,web,Japan,0
+evt-025,usr-009,proj-001,login,2024-01-16T10:30:00Z,sess-012,desktop,Ireland,0
+evt-026,usr-009,proj-001,file_upload,2024-01-16T10:35:20Z,sess-012,desktop,Ireland,65
+evt-027,usr-010,proj-003,login,2024-01-16T08:00:00Z,sess-013,web,Russia,0
+evt-028,usr-010,proj-003,integration_setup,2024-01-16T08:05:00Z,sess-013,web,Russia,420
+evt-029,usr-011,proj-001,login,2024-01-16T07:15:00Z,sess-014,mobile,UAE,0
+evt-030,usr-011,proj-001,file_view,2024-01-16T07:16:30Z,sess-014,mobile,UAE,90
+evt-031,usr-012,proj-004,login,2024-01-16T15:00:00Z,sess-015,web,USA,0
+evt-032,usr-012,proj-004,report_generate,2024-01-16T15:02:45Z,sess-015,web,USA,35
+evt-033,usr-012,proj-004,report_export,2024-01-16T15:08:20Z,sess-015,web,USA,12
+evt-034,usr-001,proj-002,login,2024-01-16T09:30:00Z,sess-016,web,USA,0
+evt-035,usr-001,proj-002,backup_schedule,2024-01-16T09:32:15Z,sess-016,web,USA,25
+evt-036,usr-002,proj-001,login,2024-01-16T14:00:00Z,sess-017,desktop,Spain,0
+evt-037,usr-002,proj-001,file_upload,2024-01-16T14:03:40Z,sess-017,desktop,Spain,55
+evt-038,usr-003,proj-004,login,2024-01-16T03:00:00Z,sess-018,web,China,0
+evt-039,usr-003,proj-004,dashboard_create,2024-01-16T03:05:30Z,sess-018,web,China,120
+evt-040,usr-005,proj-003,login,2024-01-16T06:00:00Z,sess-019,mobile,India,0
diff --git a/samples/app-users.json b/samples/app-users.json
new file mode 100644
index 00000000..82acc256
--- /dev/null
+++ b/samples/app-users.json
@@ -0,0 +1,146 @@
+[
+ {
+ "user_id": "usr-001",
+ "email": "john.smith@techcorp.com",
+ "company": "TechCorp Inc",
+ "company_size": "enterprise",
+ "industry": "technology",
+ "country": "USA",
+ "signup_date": "2023-02-15",
+ "signup_source": "google_ads",
+ "plan": "enterprise",
+ "projects_using": ["proj-001", "proj-002"]
+ },
+ {
+ "user_id": "usr-002",
+ "email": "maria.garcia@designstudio.io",
+ "company": "Design Studio",
+ "company_size": "smb",
+ "industry": "creative",
+ "country": "Spain",
+ "signup_date": "2023-04-22",
+ "signup_source": "organic",
+ "plan": "professional",
+ "projects_using": ["proj-001", "proj-003"]
+ },
+ {
+ "user_id": "usr-003",
+ "email": "chen.wei@globalfinance.cn",
+ "company": "Global Finance Ltd",
+ "company_size": "enterprise",
+ "industry": "finance",
+ "country": "China",
+ "signup_date": "2023-01-10",
+ "signup_source": "referral",
+ "plan": "enterprise",
+ "projects_using": ["proj-001", "proj-002", "proj-004"]
+ },
+ {
+ "user_id": "usr-004",
+ "email": "anna.mueller@startupberlin.de",
+ "company": "Startup Berlin",
+ "company_size": "startup",
+ "industry": "technology",
+ "country": "Germany",
+ "signup_date": "2023-08-05",
+ "signup_source": "product_hunt",
+ "plan": "starter",
+ "projects_using": ["proj-003"]
+ },
+ {
+ "user_id": "usr-005",
+ "email": "raj.patel@consulting.in",
+ "company": "Patel Consulting",
+ "company_size": "smb",
+ "industry": "consulting",
+ "country": "India",
+ "signup_date": "2023-06-18",
+ "signup_source": "linkedin",
+ "plan": "professional",
+ "projects_using": ["proj-001", "proj-003"]
+ },
+ {
+ "user_id": "usr-006",
+ "email": "emily.jones@healthcare.nhs.uk",
+ "company": "NHS Trust",
+ "company_size": "enterprise",
+ "industry": "healthcare",
+ "country": "UK",
+ "signup_date": "2022-11-30",
+ "signup_source": "direct",
+ "plan": "enterprise",
+ "projects_using": ["proj-002"]
+ },
+ {
+ "user_id": "usr-007",
+ "email": "lucas.silva@agencia.br",
+ "company": "Agencia Digital",
+ "company_size": "smb",
+ "industry": "marketing",
+ "country": "Brazil",
+ "signup_date": "2023-09-12",
+ "signup_source": "google_ads",
+ "plan": "professional",
+ "projects_using": ["proj-003", "proj-004"]
+ },
+ {
+ "user_id": "usr-008",
+ "email": "yuki.tanaka@enterprise.jp",
+ "company": "Enterprise Japan",
+ "company_size": "enterprise",
+ "industry": "manufacturing",
+ "country": "Japan",
+ "signup_date": "2023-03-28",
+ "signup_source": "partner",
+ "plan": "enterprise",
+ "projects_using": ["proj-001", "proj-002"]
+ },
+ {
+ "user_id": "usr-009",
+ "email": "sarah.oconnor@lawfirm.ie",
+ "company": "O'Connor Legal",
+ "company_size": "smb",
+ "industry": "legal",
+ "country": "Ireland",
+ "signup_date": "2023-07-20",
+ "signup_source": "organic",
+ "plan": "professional",
+ "projects_using": ["proj-001"]
+ },
+ {
+ "user_id": "usr-010",
+ "email": "alex.petrov@devshop.ru",
+ "company": "DevShop",
+ "company_size": "startup",
+ "industry": "technology",
+ "country": "Russia",
+ "signup_date": "2023-10-05",
+ "signup_source": "product_hunt",
+ "plan": "starter",
+ "projects_using": ["proj-003", "proj-005"]
+ },
+ {
+ "user_id": "usr-011",
+ "email": "fatima.hassan@ngo.org",
+ "company": "Global Aid Foundation",
+ "company_size": "smb",
+ "industry": "nonprofit",
+ "country": "UAE",
+ "signup_date": "2023-05-14",
+ "signup_source": "referral",
+ "plan": "nonprofit",
+ "projects_using": ["proj-001", "proj-003"]
+ },
+ {
+ "user_id": "usr-012",
+ "email": "mike.johnson@retailco.com",
+ "company": "RetailCo",
+ "company_size": "enterprise",
+ "industry": "retail",
+ "country": "USA",
+ "signup_date": "2022-09-08",
+ "signup_source": "direct",
+ "plan": "enterprise",
+ "projects_using": ["proj-001", "proj-002", "proj-004"]
+ }
+]
diff --git a/samples/departments.json b/samples/departments.json
new file mode 100644
index 00000000..0e682e4a
--- /dev/null
+++ b/samples/departments.json
@@ -0,0 +1,47 @@
+[
+ {
+ "department_id": "dept-eng",
+ "name": "Engineering",
+ "budget_annual": 2500000,
+ "headcount": 6,
+ "headcount_target": 8,
+ "cost_center": "CC-100",
+ "location": "San Francisco"
+ },
+ {
+ "department_id": "dept-product",
+ "name": "Product",
+ "budget_annual": 800000,
+ "headcount": 2,
+ "headcount_target": 3,
+ "cost_center": "CC-200",
+ "location": "San Francisco"
+ },
+ {
+ "department_id": "dept-design",
+ "name": "Design",
+ "budget_annual": 600000,
+ "headcount": 2,
+ "headcount_target": 3,
+ "cost_center": "CC-300",
+ "location": "San Francisco"
+ },
+ {
+ "department_id": "dept-sales",
+ "name": "Sales",
+ "budget_annual": 1200000,
+ "headcount": 3,
+ "headcount_target": 5,
+ "cost_center": "CC-400",
+ "location": "New York"
+ },
+ {
+ "department_id": "dept-ops",
+ "name": "Operations",
+ "budget_annual": 900000,
+ "headcount": 2,
+ "headcount_target": 2,
+ "cost_center": "CC-500",
+ "location": "San Francisco"
+ }
+]
diff --git a/samples/employees.json b/samples/employees.json
new file mode 100644
index 00000000..6015f4bd
--- /dev/null
+++ b/samples/employees.json
@@ -0,0 +1,182 @@
+[
+ {
+ "employee_id": "emp-001",
+ "name": "Sarah Chen",
+ "email": "sarah.chen@acmesoftware.com",
+ "department_id": "dept-eng",
+ "role": "Engineering Manager",
+ "level": "L6",
+ "hire_date": "2020-03-15",
+ "salary": 185000,
+ "reports_to": "emp-010",
+ "location": "San Francisco"
+ },
+ {
+ "employee_id": "emp-002",
+ "name": "Marcus Johnson",
+ "email": "marcus.j@acmesoftware.com",
+ "department_id": "dept-eng",
+ "role": "Senior Software Engineer",
+ "level": "L5",
+ "hire_date": "2021-06-01",
+ "salary": 165000,
+ "reports_to": "emp-001",
+ "location": "San Francisco"
+ },
+ {
+ "employee_id": "emp-003",
+ "name": "Priya Patel",
+ "email": "priya.p@acmesoftware.com",
+ "department_id": "dept-eng",
+ "role": "Software Engineer",
+ "level": "L4",
+ "hire_date": "2022-01-10",
+ "salary": 140000,
+ "reports_to": "emp-001",
+ "location": "Remote"
+ },
+ {
+ "employee_id": "emp-004",
+ "name": "James Wilson",
+ "email": "james.w@acmesoftware.com",
+ "department_id": "dept-eng",
+ "role": "Senior Software Engineer",
+ "level": "L5",
+ "hire_date": "2021-09-15",
+ "salary": 160000,
+ "reports_to": "emp-001",
+ "location": "New York"
+ },
+ {
+ "employee_id": "emp-005",
+ "name": "Lisa Zhang",
+ "email": "lisa.z@acmesoftware.com",
+ "department_id": "dept-product",
+ "role": "Product Manager",
+ "level": "L5",
+ "hire_date": "2021-02-20",
+ "salary": 155000,
+ "reports_to": "emp-011",
+ "location": "San Francisco"
+ },
+ {
+ "employee_id": "emp-006",
+ "name": "David Kim",
+ "email": "david.k@acmesoftware.com",
+ "department_id": "dept-design",
+ "role": "Senior Designer",
+ "level": "L5",
+ "hire_date": "2021-04-05",
+ "salary": 145000,
+ "reports_to": "emp-012",
+ "location": "San Francisco"
+ },
+ {
+ "employee_id": "emp-007",
+ "name": "Emma Rodriguez",
+ "email": "emma.r@acmesoftware.com",
+ "department_id": "dept-sales",
+ "role": "Account Executive",
+ "level": "L4",
+ "hire_date": "2022-03-01",
+ "salary": 95000,
+ "reports_to": "emp-013",
+ "location": "New York"
+ },
+ {
+ "employee_id": "emp-008",
+ "name": "Alex Thompson",
+ "email": "alex.t@acmesoftware.com",
+ "department_id": "dept-sales",
+ "role": "Sales Manager",
+ "level": "L5",
+ "hire_date": "2020-08-15",
+ "salary": 130000,
+ "reports_to": "emp-013",
+ "location": "New York"
+ },
+ {
+ "employee_id": "emp-009",
+ "name": "Nina Kowalski",
+ "email": "nina.k@acmesoftware.com",
+ "department_id": "dept-ops",
+ "role": "Operations Manager",
+ "level": "L5",
+ "hire_date": "2020-11-10",
+ "salary": 125000,
+ "reports_to": "emp-014",
+ "location": "Remote"
+ },
+ {
+ "employee_id": "emp-010",
+ "name": "Michael Foster",
+ "email": "michael.f@acmesoftware.com",
+ "department_id": "dept-eng",
+ "role": "VP of Engineering",
+ "level": "L7",
+ "hire_date": "2019-06-01",
+ "salary": 250000,
+ "reports_to": null,
+ "location": "San Francisco"
+ },
+ {
+ "employee_id": "emp-011",
+ "name": "Rachel Green",
+ "email": "rachel.g@acmesoftware.com",
+ "department_id": "dept-product",
+ "role": "Head of Product",
+ "level": "L7",
+ "hire_date": "2019-09-15",
+ "salary": 230000,
+ "reports_to": null,
+ "location": "San Francisco"
+ },
+ {
+ "employee_id": "emp-012",
+ "name": "Chris Martinez",
+ "email": "chris.m@acmesoftware.com",
+ "department_id": "dept-design",
+ "role": "Design Lead",
+ "level": "L6",
+ "hire_date": "2020-01-20",
+ "salary": 175000,
+ "reports_to": "emp-011",
+ "location": "San Francisco"
+ },
+ {
+ "employee_id": "emp-013",
+ "name": "Jennifer Lee",
+ "email": "jennifer.l@acmesoftware.com",
+ "department_id": "dept-sales",
+ "role": "VP of Sales",
+ "level": "L7",
+ "hire_date": "2019-11-01",
+ "salary": 220000,
+ "reports_to": null,
+ "location": "New York"
+ },
+ {
+ "employee_id": "emp-014",
+ "name": "Robert Taylor",
+ "email": "robert.t@acmesoftware.com",
+ "department_id": "dept-ops",
+ "role": "COO",
+ "level": "L8",
+ "hire_date": "2019-03-01",
+ "salary": 280000,
+ "reports_to": null,
+ "location": "San Francisco"
+ },
+ {
+ "employee_id": "emp-015",
+ "name": "Amy Nguyen",
+ "email": "amy.n@acmesoftware.com",
+ "department_id": "dept-eng",
+ "role": "Software Engineer",
+ "level": "L3",
+ "hire_date": "2023-06-15",
+ "salary": 115000,
+ "reports_to": "emp-001",
+ "location": "Remote"
+ }
+]
diff --git a/samples/expenses.csv b/samples/expenses.csv
new file mode 100644
index 00000000..5f4eb2f9
--- /dev/null
+++ b/samples/expenses.csv
@@ -0,0 +1,21 @@
+expense_id,department_id,category,vendor,amount,date,description,approved_by
+exp-001,dept-eng,cloud,AWS,28500,2024-01-15,Monthly AWS infrastructure,emp-010
+exp-002,dept-eng,cloud,Vercel,1200,2024-01-15,TeamFlow hosting,emp-001
+exp-003,dept-eng,tools,GitHub,450,2024-01-01,GitHub Enterprise seats,emp-010
+exp-004,dept-eng,tools,Datadog,890,2024-01-01,Monitoring and observability,emp-001
+exp-005,dept-design,tools,Figma,720,2024-01-01,Design tool licenses,emp-012
+exp-006,dept-sales,tools,Salesforce,2400,2024-01-01,CRM platform,emp-013
+exp-007,dept-sales,tools,HubSpot,850,2024-01-01,Marketing automation,emp-013
+exp-008,dept-ops,tools,Slack,680,2024-01-01,Team communication,emp-014
+exp-009,dept-ops,tools,Notion,340,2024-01-01,Documentation platform,emp-009
+exp-010,dept-eng,cloud,AWS,31200,2024-02-15,Monthly AWS infrastructure,emp-010
+exp-011,dept-sales,travel,United Airlines,1850,2024-01-22,Client visit - Chicago,emp-008
+exp-012,dept-sales,travel,Marriott,720,2024-01-22,Hotel - Chicago trip,emp-008
+exp-013,dept-eng,training,Coursera,1200,2024-01-10,Team learning subscriptions,emp-001
+exp-014,dept-product,tools,Amplitude,1500,2024-01-01,Product analytics,emp-011
+exp-015,dept-eng,cloud,GCP,8900,2024-01-15,DataVault infrastructure,emp-004
+exp-016,dept-ops,office,WeWork,12500,2024-01-01,SF office space,emp-014
+exp-017,dept-ops,office,WeWork,8500,2024-01-01,NY office space,emp-014
+exp-018,dept-eng,hardware,Apple,4500,2024-01-18,New engineer laptop,emp-001
+exp-019,dept-design,hardware,Apple,3200,2024-02-01,Designer workstation upgrade,emp-012
+exp-020,dept-eng,cloud,AWS,29800,2024-03-15,Monthly AWS infrastructure,emp-010
diff --git a/samples/orders.csv b/samples/orders.csv
deleted file mode 100644
index 9c481d11..00000000
--- a/samples/orders.csv
+++ /dev/null
@@ -1,8 +0,0 @@
-order_id,user_id,amount,order_date,status
-101,1,50.00,2023-01-05,completed
-102,1,75.50,2023-01-10,completed
-103,2,25.00,2023-01-20,pending
-104,3,100.00,2023-02-05,completed
-105,3,150.00,2023-02-15,refunded
-106,5,200.00,2023-03-10,completed
-107,99,30.00,2023-03-15,completed
diff --git a/samples/projects.json b/samples/projects.json
new file mode 100644
index 00000000..6f7cd985
--- /dev/null
+++ b/samples/projects.json
@@ -0,0 +1,67 @@
+[
+ {
+ "project_id": "proj-001",
+ "name": "CloudSync Pro",
+ "description": "Enterprise file synchronization and collaboration platform",
+ "status": "active",
+ "type": "saas",
+ "launch_date": "2021-03-01",
+ "tech_stack": ["React", "Node.js", "PostgreSQL", "AWS"],
+ "team_lead": "emp-001",
+ "product_manager": "emp-005",
+ "monthly_active_users": 45000,
+ "mrr": 125000
+ },
+ {
+ "project_id": "proj-002",
+ "name": "DataVault",
+ "description": "Secure data backup and recovery solution",
+ "status": "active",
+ "type": "saas",
+ "launch_date": "2022-01-15",
+ "tech_stack": ["Go", "PostgreSQL", "GCP", "Redis"],
+ "team_lead": "emp-004",
+ "product_manager": "emp-005",
+ "monthly_active_users": 28000,
+ "mrr": 89000
+ },
+ {
+ "project_id": "proj-003",
+ "name": "TeamFlow",
+ "description": "Real-time team communication and project management",
+ "status": "active",
+ "type": "saas",
+ "launch_date": "2023-06-01",
+ "tech_stack": ["TypeScript", "Next.js", "Supabase", "Vercel"],
+ "team_lead": "emp-002",
+ "product_manager": "emp-005",
+ "monthly_active_users": 12000,
+ "mrr": 34000
+ },
+ {
+ "project_id": "proj-004",
+ "name": "AnalyticsHub",
+ "description": "Business intelligence and analytics dashboard",
+ "status": "beta",
+ "type": "saas",
+ "launch_date": "2024-01-10",
+ "tech_stack": ["Python", "React", "ClickHouse", "AWS"],
+ "team_lead": "emp-003",
+ "product_manager": "emp-005",
+ "monthly_active_users": 850,
+ "mrr": 5200
+ },
+ {
+ "project_id": "proj-005",
+ "name": "Mobile SDK",
+ "description": "Cross-platform mobile development toolkit",
+ "status": "maintenance",
+ "type": "sdk",
+ "launch_date": "2020-09-01",
+ "tech_stack": ["Swift", "Kotlin", "React Native"],
+ "team_lead": "emp-004",
+ "product_manager": "emp-005",
+ "monthly_active_users": 3200,
+ "mrr": 18000
+ }
+]
diff --git a/samples/revenue.csv b/samples/revenue.csv
new file mode 100644
index 00000000..bbdb980f
--- /dev/null
+++ b/samples/revenue.csv
@@ -0,0 +1,21 @@
+revenue_id,project_id,month,channel,new_mrr,churned_mrr,expansion_mrr,total_mrr,new_customers,churned_customers
+rev-001,proj-001,2024-01,direct,8500,2200,3400,125000,12,3
+rev-002,proj-001,2024-02,direct,9200,1800,4100,134500,15,2
+rev-003,proj-001,2024-03,direct,7800,2500,2900,142700,11,4
+rev-004,proj-002,2024-01,direct,5200,1500,2100,89000,8,2
+rev-005,proj-002,2024-02,direct,6100,1200,1800,95700,10,1
+rev-006,proj-002,2024-03,direct,5800,2000,2400,101900,9,3
+rev-007,proj-003,2024-01,direct,4500,800,1200,34000,18,2
+rev-008,proj-003,2024-02,direct,5200,600,900,39500,22,1
+rev-009,proj-003,2024-03,direct,4800,1100,1400,44600,20,3
+rev-010,proj-004,2024-01,beta,1200,0,0,5200,15,0
+rev-011,proj-004,2024-02,beta,1800,200,300,7100,20,1
+rev-012,proj-004,2024-03,beta,2100,400,500,9300,25,2
+rev-013,proj-005,2024-01,direct,1500,800,400,18000,3,2
+rev-014,proj-005,2024-02,direct,1200,600,300,18900,2,1
+rev-015,proj-005,2024-03,direct,900,1000,200,19000,1,2
+rev-016,proj-001,2024-01,partner,3200,500,800,125000,5,1
+rev-017,proj-001,2024-02,partner,4100,400,1200,134500,7,0
+rev-018,proj-002,2024-01,partner,1800,300,400,89000,3,0
+rev-019,proj-003,2024-01,product_hunt,2800,0,0,34000,35,0
+rev-020,proj-003,2024-02,product_hunt,1200,200,0,39500,15,1
diff --git a/samples/sample-sales.csv b/samples/sample-sales.csv
deleted file mode 100644
index d3b6b08a..00000000
--- a/samples/sample-sales.csv
+++ /dev/null
@@ -1,19 +0,0 @@
-Region,Product,Date,Revenue,Units
-North,Widget A,2024-01-01,12500,250
-North,Widget B,2024-01-01,9800,180
-South,Widget A,2024-01-01,14350,310
-South,Widget C,2024-01-01,11200,205
-West,Widget A,2024-01-01,8700,165
-West,Widget B,2024-01-01,9200,170
-North,Widget A,2024-02-01,13100,260
-North,Widget B,2024-02-01,10050,190
-South,Widget A,2024-02-01,14920,320
-South,Widget C,2024-02-01,11880,215
-West,Widget A,2024-02-01,9050,172
-West,Widget B,2024-02-01,9475,178
-North,Widget A,2024-03-01,13780,272
-North,Widget B,2024-03-01,10420,196
-South,Widget A,2024-03-01,15460,330
-South,Widget C,2024-03-01,12340,222
-West,Widget A,2024-03-01,9340,178
-West,Widget B,2024-03-01,9690,181
diff --git a/samples/sprints.csv b/samples/sprints.csv
new file mode 100644
index 00000000..fcd7c685
--- /dev/null
+++ b/samples/sprints.csv
@@ -0,0 +1,16 @@
+sprint_id,project_id,sprint_number,start_date,end_date,planned_points,completed_points,bugs_found,bugs_fixed,team_size
+spr-001,proj-001,45,2024-01-01,2024-01-14,34,31,8,6,4
+spr-002,proj-001,46,2024-01-15,2024-01-28,36,35,5,7,4
+spr-003,proj-002,32,2024-01-01,2024-01-14,28,28,3,4,3
+spr-004,proj-002,33,2024-01-15,2024-01-28,30,27,6,5,3
+spr-005,proj-003,12,2024-01-01,2024-01-14,22,20,4,3,2
+spr-006,proj-003,13,2024-01-15,2024-01-28,24,24,2,4,2
+spr-007,proj-004,4,2024-01-01,2024-01-14,18,15,7,4,2
+spr-008,proj-004,5,2024-01-15,2024-01-28,16,14,5,6,2
+spr-009,proj-001,47,2024-01-29,2024-02-11,38,36,4,5,4
+spr-010,proj-002,34,2024-01-29,2024-02-11,32,30,4,3,3
+spr-011,proj-003,14,2024-01-29,2024-02-11,26,25,3,2,2
+spr-012,proj-004,6,2024-01-29,2024-02-11,20,18,4,5,2
+spr-013,proj-001,48,2024-02-12,2024-02-25,35,34,6,6,4
+spr-014,proj-002,35,2024-02-12,2024-02-25,29,29,2,3,3
+spr-015,proj-003,15,2024-02-12,2024-02-25,25,23,5,4,2
diff --git a/samples/subscriptions.csv b/samples/subscriptions.csv
new file mode 100644
index 00000000..af20fad8
--- /dev/null
+++ b/samples/subscriptions.csv
@@ -0,0 +1,26 @@
+subscription_id,user_id,project_id,plan,status,mrr,start_date,billing_cycle,payment_method,seats
+sub-001,usr-001,proj-001,enterprise,active,850,2023-02-15,annual,invoice,25
+sub-002,usr-001,proj-002,enterprise,active,420,2023-03-01,annual,invoice,25
+sub-003,usr-002,proj-001,professional,active,79,2023-04-22,monthly,card,5
+sub-004,usr-002,proj-003,professional,active,49,2023-05-01,monthly,card,5
+sub-005,usr-003,proj-001,enterprise,active,1200,2023-01-10,annual,wire,50
+sub-006,usr-003,proj-002,enterprise,active,680,2023-01-10,annual,wire,50
+sub-007,usr-003,proj-004,enterprise,active,320,2024-01-15,monthly,wire,50
+sub-008,usr-004,proj-003,starter,active,19,2023-08-05,monthly,card,2
+sub-009,usr-005,proj-001,professional,active,79,2023-06-18,monthly,card,8
+sub-010,usr-005,proj-003,professional,active,49,2023-07-01,monthly,card,8
+sub-011,usr-006,proj-002,enterprise,active,950,2022-11-30,annual,invoice,100
+sub-012,usr-007,proj-003,professional,active,49,2023-09-12,monthly,card,4
+sub-013,usr-007,proj-004,professional,active,29,2023-10-01,monthly,card,4
+sub-014,usr-008,proj-001,enterprise,active,1500,2023-03-28,annual,wire,80
+sub-015,usr-008,proj-002,enterprise,active,720,2023-04-15,annual,wire,80
+sub-016,usr-009,proj-001,professional,active,79,2023-07-20,monthly,card,6
+sub-017,usr-010,proj-003,starter,active,19,2023-10-05,monthly,card,3
+sub-018,usr-010,proj-005,developer,active,99,2023-10-15,monthly,card,1
+sub-019,usr-011,proj-001,nonprofit,active,39,2023-05-14,monthly,card,10
+sub-020,usr-011,proj-003,nonprofit,active,24,2023-06-01,monthly,card,10
+sub-021,usr-012,proj-001,enterprise,active,2200,2022-09-08,annual,invoice,150
+sub-022,usr-012,proj-002,enterprise,active,1100,2022-10-01,annual,invoice,150
+sub-023,usr-012,proj-004,enterprise,active,450,2024-01-20,monthly,invoice,150
+sub-024,usr-004,proj-003,starter,churned,19,2023-08-05,monthly,card,2
+sub-025,usr-010,proj-003,starter,churned,19,2023-10-05,monthly,card,3
diff --git a/samples/users.csv b/samples/users.csv
deleted file mode 100644
index c828b5d1..00000000
--- a/samples/users.csv
+++ /dev/null
@@ -1,6 +0,0 @@
-id,name,email,signup_date
-1,Alice,alice@example.com,2023-01-01
-2,Bob,bob@example.com,2023-01-15
-3,Charlie,charlie@example.com,2023-02-01
-4,David,david@example.com,2023-02-20
-5,Eve,eve@example.com,2023-03-05
diff --git a/scripts/ai-changeset.mjs b/scripts/ai-changeset.mjs
index bf9ba7a9..fb067ea3 100644
--- a/scripts/ai-changeset.mjs
+++ b/scripts/ai-changeset.mjs
@@ -39,7 +39,9 @@ const PACKAGE_PATHS = {
"packages/core-store": "@dashframe/core-store",
"packages/engine": "@dashframe/engine",
"packages/engine-browser": "@dashframe/engine-browser",
- "packages/connector-csv": "@dashframe/connector-csv",
+ "packages/csv": "@dashframe/csv",
+ "packages/json": "@dashframe/json",
+ "packages/connector-local": "@dashframe/connector-local",
"packages/connector-notion": "@dashframe/connector-notion",
"packages/visualization": "@dashframe/visualization",
"packages/ui": "@dashframe/ui",
diff --git a/turbo.json b/turbo.json
index a81746da..c91a0855 100644
--- a/turbo.json
+++ b/turbo.json
@@ -1,5 +1,7 @@
{
"$schema": "https://turbo.build/schema.json",
+ "ui": "stream",
+ "concurrency": "15",
"globalEnv": [
"NEXT_PUBLIC_CONVEX_URL",
"NEXT_PUBLIC_CONVEX_DEPLOYMENT",