Billing Data Model
Billing manages three stored collections (items, categories, sync jobs) and several computed report types. All stored entities use nanoid(12) identifiers. Amounts are stored in cents (integer).
BillingItem
A single cost entry.
interface BillingItem {
id: string; // nanoid(12)
name: string; // Item name
amount: number; // Amount in cents (integer)
currency: string; // Currency code (default "USD")
categoryId?: string; // Category ID
tags: string[]; // Freeform tags
date: string; // ISO 8601 date of the charge
description?: string;
provider?: string; // Cloud provider (e.g., "gcp", "aws", "openai")
providerRef?: string; // External ID for deduplication
adapter?: string; // Ingestion adapter key (e.g. "gcp-bigquery")
source?: string; // Configured source profile name
recurring: boolean; // Whether this is a recurring charge
period?: BillingPeriod; // Recurrence period
serviceId?: string; // Yellow Pages service link
featureKey?: string; // Internal feature attribution
stageAppId?: string; // Stage app ID
environment?: string; // local | stage | prod | custom
scope?: BillingScopeRef; // Generic account/workspace/project scope
usage?: BillingUsage; // Normalized usage dimensions
authorEmail?: string; // Who recorded this item
meta?: Record<string, string | number | boolean>;
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
}
type BillingPeriod = "daily" | "weekly" | "monthly" | "yearly";
interface BillingScopeRef {
kind: string; // e.g. "billing-account", "workspace", "subscription"
id: string;
name?: string;
}
interface BillingUsage {
amount?: number;
unit?: string;
skuId?: string;
skuDescription?: string;
}
Category
A hierarchical grouping for organizing costs.
interface Category {
id: string; // nanoid(12)
name: string; // Category name
description?: string;
color?: string; // Display color (hex)
parentId?: string; // Parent category (hierarchical)
stageAppId?: string;
authorEmail?: string;
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
}
SyncJob
An automated cost import operation.
interface SyncJob {
id: string; // nanoid(12)
provider: string; // Vendor family (e.g. "gcp", "aws", "openai")
adapter?: string; // Adapter name (e.g. "gcp-bigquery")
source?: string; // Configured source profile name
status: SyncJobStatus; // Job state
itemsCreated: number; // New items imported
itemsUpdated: number; // Existing items updated
startedAt: string; // ISO 8601
completedAt?: string; // ISO 8601
error?: string; // Error message on failure
stageAppId?: string;
authorEmail?: string;
}
type SyncJobStatus = "pending" | "running" | "completed" | "failed";
Report Types (computed, not stored)
BillingSummary
interface BillingSummary {
totalAmount: number; // Total in cents
itemCount: number;
currency: string;
from?: string; // ISO 8601
to?: string; // ISO 8601
}
BreakdownEntry
interface BreakdownEntry {
key: string; // Group key (provider name, category, etc.)
amount: number; // Total in cents
count: number; // Number of items
percentage: number; // Percentage of total
}
TrendBucket
interface TrendBucket {
date: string; // Bucket date (e.g., "2026-03")
amount: number; // Total in cents
count: number; // Number of items
}
StorageStats
interface StorageStats {
totalItems: number;
totalCategories: number;
totalSyncJobs: number;
totalAmount: number; // cents
currency: string;
oldestItem?: string; // ISO 8601
newestItem?: string; // ISO 8601
storageType: string;
}
BillingConfig
Billing sources are configured at the service layer, not hardcoded into the core data model.
interface BillingConfig {
version: number;
currency: string;
storage?: "file";
sources?: BillingSourceConfig[];
}
interface BillingSourceConfigBase {
name: string; // stable source/profile name
adapter: string; // adapter key, e.g. "gcp-bigquery"
provider: string; // vendor family, e.g. "gcp"
enabled?: boolean;
attribution?: BillingAttribution;
}
interface GcpBigQuerySourceConfig extends BillingSourceConfigBase {
adapter: "gcp-bigquery";
provider: "gcp";
authMode?: "auto" | "access-token" | "adc";
credentialsFile?: string; // optional service account path, or rely on ADC
datasetProjectId: string;
datasetId: string;
tableId: string;
queryProjectId?: string;
location?: string;
billingAccountId?: string;
currency?: string;
projects?: Record<string, BillingAttribution>;
}
interface BillingAttribution {
categoryId?: string;
serviceId?: string;
featureKey?: string;
stageAppId?: string;
environment?: string;
tags?: string[];
}
Storage
File-Based (SHIFT_STORAGE=file)
.billing/
config.json # BillingConfig
items/<id>.json # Individual billing items
categories/<id>.json # Category definitions
sync-jobs/<id>.json # Sync execution history
Convex (SHIFT_STORAGE=convex)
Tables use the billing_ prefix:
| Table | Description |
|---|---|
billing_items | Billing item records |
billing_categories | Category definitions |
billing_sync_jobs | Sync job history |
Each record includes a sid field (shift ID) that maps to the application-level id.