@happyvertical/smrt-marketing 0.39.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/AGENTS.md +79 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +67 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/collections/CampaignChannelCollection.d.ts +9 -0
  8. package/dist/collections/CampaignChannelCollection.d.ts.map +1 -0
  9. package/dist/collections/CampaignCollection.d.ts +10 -0
  10. package/dist/collections/CampaignCollection.d.ts.map +1 -0
  11. package/dist/collections/CampaignMetricSnapshotCollection.d.ts +15 -0
  12. package/dist/collections/CampaignMetricSnapshotCollection.d.ts.map +1 -0
  13. package/dist/collections/index.d.ts +4 -0
  14. package/dist/collections/index.d.ts.map +1 -0
  15. package/dist/index.d.ts +5 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +625 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/manifest.json +1173 -0
  20. package/dist/models/Campaign.d.ts +31 -0
  21. package/dist/models/Campaign.d.ts.map +1 -0
  22. package/dist/models/CampaignChannel.d.ts +22 -0
  23. package/dist/models/CampaignChannel.d.ts.map +1 -0
  24. package/dist/models/CampaignMetricSnapshot.d.ts +29 -0
  25. package/dist/models/CampaignMetricSnapshot.d.ts.map +1 -0
  26. package/dist/models/index.d.ts +4 -0
  27. package/dist/models/index.d.ts.map +1 -0
  28. package/dist/services/BudgetPacingService.d.ts +25 -0
  29. package/dist/services/BudgetPacingService.d.ts.map +1 -0
  30. package/dist/services/CampaignLifecycleService.d.ts +19 -0
  31. package/dist/services/CampaignLifecycleService.d.ts.map +1 -0
  32. package/dist/services/MetricIngestionService.d.ts +16 -0
  33. package/dist/services/MetricIngestionService.d.ts.map +1 -0
  34. package/dist/services/index.d.ts +4 -0
  35. package/dist/services/index.d.ts.map +1 -0
  36. package/dist/smrt-knowledge.json +875 -0
  37. package/dist/svelte/__tests__/components.test.js +115 -0
  38. package/dist/svelte/__tests__/helpers.test.js +94 -0
  39. package/dist/svelte/components/BudgetPacing.svelte +129 -0
  40. package/dist/svelte/components/BudgetPacing.svelte.d.ts +10 -0
  41. package/dist/svelte/components/BudgetPacing.svelte.d.ts.map +1 -0
  42. package/dist/svelte/components/CampaignDetail.svelte +208 -0
  43. package/dist/svelte/components/CampaignDetail.svelte.d.ts +14 -0
  44. package/dist/svelte/components/CampaignDetail.svelte.d.ts.map +1 -0
  45. package/dist/svelte/components/CampaignList.svelte +140 -0
  46. package/dist/svelte/components/CampaignList.svelte.d.ts +11 -0
  47. package/dist/svelte/components/CampaignList.svelte.d.ts.map +1 -0
  48. package/dist/svelte/components/ChannelMix.svelte +138 -0
  49. package/dist/svelte/components/ChannelMix.svelte.d.ts +10 -0
  50. package/dist/svelte/components/ChannelMix.svelte.d.ts.map +1 -0
  51. package/dist/svelte/components/MarketingDashboard.svelte +129 -0
  52. package/dist/svelte/components/MarketingDashboard.svelte.d.ts +13 -0
  53. package/dist/svelte/components/MarketingDashboard.svelte.d.ts.map +1 -0
  54. package/dist/svelte/format.d.ts +8 -0
  55. package/dist/svelte/format.d.ts.map +1 -0
  56. package/dist/svelte/format.js +42 -0
  57. package/dist/svelte/index.d.ts +8 -0
  58. package/dist/svelte/index.d.ts.map +1 -0
  59. package/dist/svelte/index.js +7 -0
  60. package/dist/svelte/types.d.ts +73 -0
  61. package/dist/svelte/types.d.ts.map +1 -0
  62. package/dist/svelte/types.js +80 -0
  63. package/dist/types.d.ts +61 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/types.js +29 -0
  66. package/dist/types.js.map +1 -0
  67. package/package.json +79 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/__smrt-register__.ts","../src/models/Campaign.ts","../src/models/CampaignChannel.ts","../src/collections/CampaignChannelCollection.ts","../src/collections/CampaignCollection.ts","../src/models/CampaignMetricSnapshot.ts","../src/collections/CampaignMetricSnapshotCollection.ts","../src/services/BudgetPacingService.ts","../src/services/CampaignLifecycleService.ts","../src/services/MetricIngestionService.ts"],"sourcesContent":["import { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/** Campaign identity, budget, schedule, and guarded lifecycle. */\n\nimport { field, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n CAMPAIGN_STATUSES,\n type CampaignOptions,\n type CampaignStatus,\n} from '../types.js';\n\nconst CAMPAIGN_STATUS_TRANSITIONS: Record<CampaignStatus, CampaignStatus[]> = {\n draft: ['scheduled'],\n scheduled: ['active'],\n active: ['paused', 'completed'],\n paused: ['active', 'completed'],\n completed: ['archived'],\n archived: [],\n};\n\nconst loadedCampaignStatus = new WeakMap<Campaign, CampaignStatus>();\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'campaigns',\n conflictColumns: ['tenant_id', 'campaign_key'],\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get', 'create', 'update'] },\n cli: { include: ['list', 'get', 'create', 'update'] },\n})\nexport class Campaign extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /** Stable caller-defined key used by CRM/referral systems. */\n @field({ required: true })\n campaignKey: string = '';\n\n @field({ required: true })\n name: string = '';\n\n /** Open objective key such as `awareness` or `demand_generation`. */\n objective: string = '';\n\n status: CampaignStatus = 'draft';\n\n startAt: Date | null = null;\n\n endAt: Date | null = null;\n\n /** Total campaign budget in integer cents. */\n budgetCents: number = 0;\n\n /** ISO 4217 currency for every campaign/channel amount. */\n currency: string = 'USD';\n\n metadata: string = '{}';\n\n constructor(options: CampaignOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.campaignKey !== undefined)\n this.campaignKey = options.campaignKey;\n if (options.name !== undefined) this.name = options.name;\n if (options.objective !== undefined) this.objective = options.objective;\n if (options.status !== undefined) this.status = options.status;\n if (options.startAt !== undefined)\n this.startAt = Campaign.coerceDate(options.startAt);\n if (options.endAt !== undefined)\n this.endAt = Campaign.coerceDate(options.endAt);\n if (options.budgetCents !== undefined)\n this.budgetCents = options.budgetCents;\n if (options.currency !== undefined) this.currency = options.currency;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n\n override async initialize(): Promise<this> {\n await super.initialize();\n this.startAt = Campaign.coerceDate(this.startAt);\n this.endAt = Campaign.coerceDate(this.endAt);\n if (await this.isSaved()) {\n loadedCampaignStatus.set(this, this.status);\n }\n return this;\n }\n\n override async save(): Promise<this> {\n const prior = await this.resolvePriorStatus();\n this.assertStatusTransition(prior);\n this.assertSchedule();\n const result = (await super.save()) as this;\n loadedCampaignStatus.set(this, this.status);\n return result;\n }\n\n /** Apply one legal transition in memory; callers choose the save boundary. */\n transitionTo(next: CampaignStatus): this {\n if (next === this.status) return this;\n const allowed = CAMPAIGN_STATUS_TRANSITIONS[this.status] ?? [];\n if (!allowed.includes(next)) {\n throw new Error(\n `Campaign ${this.campaignKey || this.id}: cannot transition ` +\n `'${this.status}' → '${next}'.`,\n );\n }\n this.status = next;\n return this;\n }\n\n getMetadata(): Record<string, unknown> {\n if (!this.metadata) return {};\n try {\n const parsed = JSON.parse(this.metadata) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n setMetadata(metadata: Record<string, unknown>): void {\n this.metadata = JSON.stringify(metadata ?? {});\n }\n\n private assertSchedule(): void {\n if (\n this.startAt &&\n this.endAt &&\n this.endAt.getTime() < this.startAt.getTime()\n ) {\n throw new Error('Campaign endAt must be on or after startAt.');\n }\n if (!Number.isInteger(this.budgetCents) || this.budgetCents < 0) {\n throw new Error('Campaign budgetCents must be a non-negative integer.');\n }\n }\n\n private async resolvePriorStatus(): Promise<CampaignStatus | undefined> {\n if (this.id) {\n try {\n const row = await this.db.get(this.tableName, { id: this.id });\n if (row && row.status != null) return row.status as CampaignStatus;\n } catch {\n // DB not ready / table absent; fall through to the hydrated state.\n }\n }\n if (this.campaignKey) {\n try {\n const row = await this.db.get(this.tableName, {\n tenant_id: this.tenantId,\n campaign_key: this.campaignKey,\n });\n if (row && row.status != null) return row.status as CampaignStatus;\n } catch {\n // DB not ready / table absent; fall through to the hydrated state.\n }\n }\n return loadedCampaignStatus.get(this);\n }\n\n private assertStatusTransition(prior: CampaignStatus | undefined): void {\n if (!CAMPAIGN_STATUSES.includes(this.status)) {\n throw new Error(\n `Campaign ${this.campaignKey || this.id}: unknown status '${this.status}'.`,\n );\n }\n if (prior === undefined || prior === this.status) return;\n const allowed = CAMPAIGN_STATUS_TRANSITIONS[prior] ?? [];\n if (!allowed.includes(this.status)) {\n throw new Error(\n `Campaign ${this.campaignKey || this.id}: illegal status transition ` +\n `'${prior}' → '${this.status}'.`,\n );\n }\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n return null;\n }\n}\n\nexport default Campaign;\n","/** Generic links from a campaign to cross-package channel execution rows. */\n\nimport { field, foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type {\n CampaignChannelOptions,\n CampaignChannelStatus,\n} from '../types.js';\nimport { CAMPAIGN_CHANNEL_STATUSES } from '../types.js';\nimport { Campaign } from './Campaign.js';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'campaign_channels',\n conflictColumns: ['campaign_id', 'channel_kind', 'channel_ref'],\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create', 'update'] },\n cli: { include: ['list', 'get', 'create', 'update', 'delete'] },\n})\nexport class CampaignChannel extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey(Campaign, { required: true })\n campaignId: string = '';\n\n /** Open kind vocabulary; recommended values live in CAMPAIGN_CHANNEL_KINDS. */\n @field({ required: true })\n channelKind: string = '';\n\n /** Opaque identifier in the namespace named by channelKind. */\n @field({ required: true })\n channelRef: string = '';\n\n allocatedBudgetCents: number = 0;\n\n /** Optional execution-specific override of the Campaign start. */\n startAt: Date | null = null;\n\n /** Optional execution-specific override of the Campaign end. */\n endAt: Date | null = null;\n\n status: CampaignChannelStatus = 'planned';\n\n constructor(options: CampaignChannelOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.campaignId !== undefined) this.campaignId = options.campaignId;\n if (options.channelKind !== undefined)\n this.channelKind = options.channelKind;\n if (options.channelRef !== undefined) this.channelRef = options.channelRef;\n if (options.allocatedBudgetCents !== undefined)\n this.allocatedBudgetCents = options.allocatedBudgetCents;\n if (options.startAt !== undefined)\n this.startAt = CampaignChannel.coerceDate(options.startAt);\n if (options.endAt !== undefined)\n this.endAt = CampaignChannel.coerceDate(options.endAt);\n if (options.status !== undefined) this.status = options.status;\n }\n\n override async initialize(): Promise<this> {\n await super.initialize();\n this.startAt = CampaignChannel.coerceDate(this.startAt);\n this.endAt = CampaignChannel.coerceDate(this.endAt);\n return this;\n }\n\n override async save(): Promise<this> {\n if (!CAMPAIGN_CHANNEL_STATUSES.includes(this.status)) {\n throw new Error(\n `CampaignChannel ${this.channelRef || this.id}: unknown status '${this.status}'.`,\n );\n }\n if (\n !Number.isInteger(this.allocatedBudgetCents) ||\n this.allocatedBudgetCents < 0\n ) {\n throw new Error(\n 'CampaignChannel allocatedBudgetCents must be a non-negative integer.',\n );\n }\n if (\n this.startAt &&\n this.endAt &&\n this.endAt.getTime() < this.startAt.getTime()\n ) {\n throw new Error('CampaignChannel endAt must be on or after startAt.');\n }\n return (await super.save()) as this;\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return value;\n if (typeof value === 'number' || typeof value === 'string') {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n return null;\n }\n}\n\nexport default CampaignChannel;\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { CampaignChannel } from '../models/CampaignChannel.js';\n\nexport class CampaignChannelCollection extends SmrtCollection<CampaignChannel> {\n static readonly _itemClass = CampaignChannel;\n\n async findByCampaign(campaignId: string): Promise<CampaignChannel[]> {\n return await this.list({\n where: { campaignId },\n orderBy: 'start_at ASC',\n });\n }\n\n async findByExecutionRef(\n channelKind: string,\n channelRef: string,\n ): Promise<CampaignChannel[]> {\n return await this.list({ where: { channelKind, channelRef } });\n }\n}\n\nexport default CampaignChannelCollection;\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Campaign } from '../models/Campaign.js';\nimport type { CampaignStatus } from '../types.js';\n\nexport class CampaignCollection extends SmrtCollection<Campaign> {\n static readonly _itemClass = Campaign;\n\n async findByCampaignKey(\n campaignKey: string,\n tenantId?: string | null,\n ): Promise<Campaign | null> {\n if (!campaignKey) return null;\n const where: Record<string, unknown> = { campaignKey };\n if (tenantId !== undefined) where.tenantId = tenantId;\n const rows = await this.list({ where, limit: 1 });\n return rows[0] ?? null;\n }\n\n async findByStatus(status: CampaignStatus): Promise<Campaign[]> {\n return await this.list({\n where: { status },\n orderBy: 'start_at ASC',\n });\n }\n}\n\nexport default CampaignCollection;\n","/** Immutable period performance evidence for a campaign or campaign channel. */\n\nimport { field, foreignKey, SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { CampaignMetricSnapshotOptions } from '../types.js';\nimport { Campaign } from './Campaign.js';\nimport { CampaignChannel } from './CampaignChannel.js';\n\nconst persistedSnapshotState = new WeakMap<CampaignMetricSnapshot, string>();\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'campaign_metric_snapshots',\n conflictColumns: ['dedupe_key'],\n api: { include: ['list', 'get', 'create'] },\n mcp: { include: ['list', 'get', 'create'] },\n cli: { include: ['list', 'get', 'create'] },\n})\nexport class CampaignMetricSnapshot extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey(Campaign, { required: true })\n campaignId: string = '';\n\n /** Null for campaign rollups; set for channel-scoped evidence. */\n @foreignKey(CampaignChannel, { nullable: true })\n campaignChannelId: string | null = null;\n\n @field({ required: true })\n periodStart: Date = new Date();\n\n @field({ required: true })\n periodEnd: Date = new Date();\n\n spendCents: number = 0;\n\n impressions: number = 0;\n\n clicks: number = 0;\n\n conversions: number = 0;\n\n leads: number = 0;\n\n @field({ type: 'integer', nullable: true })\n revenueCents: number | null = null;\n\n /** Ingestion source such as `google_ads`, `meta`, or `rollup_job`. */\n @field({ required: true })\n source: string = '';\n\n @field({ required: true })\n dedupeKey: string = '';\n\n constructor(options: CampaignMetricSnapshotOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.campaignId !== undefined) this.campaignId = options.campaignId;\n if (options.campaignChannelId !== undefined)\n this.campaignChannelId = options.campaignChannelId;\n if (options.periodStart !== undefined)\n this.periodStart =\n CampaignMetricSnapshot.coerceDate(options.periodStart) ??\n new Date(Number.NaN);\n if (options.periodEnd !== undefined)\n this.periodEnd =\n CampaignMetricSnapshot.coerceDate(options.periodEnd) ??\n new Date(Number.NaN);\n if (options.spendCents !== undefined) this.spendCents = options.spendCents;\n if (options.impressions !== undefined)\n this.impressions = options.impressions;\n if (options.clicks !== undefined) this.clicks = options.clicks;\n if (options.conversions !== undefined)\n this.conversions = options.conversions;\n if (options.leads !== undefined) this.leads = options.leads;\n if (options.revenueCents !== undefined)\n this.revenueCents = options.revenueCents;\n if (options.source !== undefined) this.source = options.source;\n if (options.dedupeKey !== undefined) this.dedupeKey = options.dedupeKey;\n }\n\n override async initialize(): Promise<this> {\n await super.initialize();\n this.periodStart =\n CampaignMetricSnapshot.coerceDate(this.periodStart) ??\n new Date(Number.NaN);\n this.periodEnd =\n CampaignMetricSnapshot.coerceDate(this.periodEnd) ?? new Date(Number.NaN);\n if (this.isPersisted) {\n persistedSnapshotState.set(this, this.serializeState());\n }\n return this;\n }\n\n override async save(): Promise<this> {\n this.assertMetrics();\n await this.assertChannelMatchesCampaign();\n const captured = persistedSnapshotState.get(this);\n if (captured !== undefined) {\n if (captured !== this.serializeState()) {\n throw new Error(\n `CampaignMetricSnapshot ${this.id ?? '<new>'}: metric snapshots ` +\n 'are immutable evidence; ingest a correcting snapshot instead.',\n );\n }\n } else if (this.id && (await this.isSaved())) {\n throw new Error(\n `CampaignMetricSnapshot ${this.id}: refusing to overwrite an existing ` +\n 'snapshot from a non-hydrated instance; snapshots are immutable evidence.',\n );\n } else if (this.dedupeKey) {\n try {\n const row = await this.db.get(this.tableName, {\n dedupe_key: this.dedupeKey,\n });\n if (row && row.id !== this.id) {\n throw new Error(\n `CampaignMetricSnapshot (dedupeKey '${this.dedupeKey}'): a ` +\n 'snapshot with this dedupe key already exists; use ' +\n 'MetricIngestionService for idempotent ingestion.',\n );\n }\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes('idempotent ingestion')\n ) {\n throw error;\n }\n // DB not ready / table absent; there is no persisted row to protect.\n }\n }\n\n const result = (await super.save()) as this;\n persistedSnapshotState.set(this, this.serializeState());\n return result;\n }\n\n override async delete(): Promise<void> {\n throw new Error(\n `CampaignMetricSnapshot ${this.id ?? '<new>'}: metric snapshots are ` +\n 'immutable evidence and cannot be deleted.',\n );\n }\n\n private assertMetrics(): void {\n if (\n Number.isNaN(this.periodStart.getTime()) ||\n Number.isNaN(this.periodEnd.getTime())\n ) {\n throw new Error(\n 'CampaignMetricSnapshot periodStart and periodEnd must be valid dates.',\n );\n }\n if (this.periodEnd.getTime() < this.periodStart.getTime()) {\n throw new Error(\n 'CampaignMetricSnapshot periodEnd must be on or after periodStart.',\n );\n }\n const integerMetrics: Array<[string, number]> = [\n ['spendCents', this.spendCents],\n ['impressions', this.impressions],\n ['clicks', this.clicks],\n ['conversions', this.conversions],\n ['leads', this.leads],\n ];\n for (const [name, value] of integerMetrics) {\n if (!Number.isInteger(value) || value < 0) {\n throw new Error(`${name} must be a non-negative integer.`);\n }\n }\n if (\n this.revenueCents !== null &&\n (!Number.isInteger(this.revenueCents) || this.revenueCents < 0)\n ) {\n throw new Error('revenueCents must be null or a non-negative integer.');\n }\n }\n\n private async assertChannelMatchesCampaign(): Promise<void> {\n if (!this.campaignChannelId) return;\n const channel = (await this.loadRelated(\n 'campaignChannelId',\n )) as CampaignChannel;\n if (channel.campaignId !== this.campaignId) {\n throw new Error(\n `CampaignMetricSnapshot campaignChannelId '${this.campaignChannelId}' ` +\n `does not belong to campaignId '${this.campaignId}'.`,\n );\n }\n }\n\n private serializeState(): string {\n return JSON.stringify({\n tenantId: this.tenantId,\n campaignId: this.campaignId,\n campaignChannelId: this.campaignChannelId,\n periodStart: this.periodStart.toISOString(),\n periodEnd: this.periodEnd.toISOString(),\n spendCents: this.spendCents,\n impressions: this.impressions,\n clicks: this.clicks,\n conversions: this.conversions,\n leads: this.leads,\n revenueCents: this.revenueCents,\n source: this.source,\n dedupeKey: this.dedupeKey,\n });\n }\n\n private static coerceDate(value: unknown): Date | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value;\n }\n if (typeof value === 'number' || typeof value === 'string') {\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n return null;\n }\n}\n\nexport default CampaignMetricSnapshot;\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport { CampaignMetricSnapshot } from '../models/CampaignMetricSnapshot.js';\nimport type { CampaignMetricSnapshotOptions } from '../types.js';\n\nexport class CampaignMetricSnapshotCollection extends SmrtCollection<CampaignMetricSnapshot> {\n static readonly _itemClass = CampaignMetricSnapshot;\n\n async findByDedupeKey(\n dedupeKey: string,\n ): Promise<CampaignMetricSnapshot | null> {\n if (!dedupeKey) return null;\n const rows = await this.list({ where: { dedupeKey }, limit: 1 });\n return rows[0] ?? null;\n }\n\n async getOrCreateByDedupeKey(\n options: CampaignMetricSnapshotOptions,\n ): Promise<{ snapshot: CampaignMetricSnapshot; created: boolean }> {\n const dedupeKey = options.dedupeKey ?? '';\n if (!dedupeKey) {\n throw new Error(\n 'CampaignMetricSnapshotCollection.getOrCreateByDedupeKey requires a dedupeKey.',\n );\n }\n const existing = await this.findByDedupeKey(dedupeKey);\n if (existing) return { snapshot: existing, created: false };\n\n try {\n const snapshot = await this.create({\n ...options,\n // Keep runtime values uncoerced so the model rejects null/invalid\n // periods instead of allowing Date(null) to become the Unix epoch.\n periodStart: options.periodStart as Date | undefined,\n periodEnd: options.periodEnd as Date | undefined,\n _insertOnly: true,\n });\n return { snapshot, created: true };\n } catch (error) {\n // A concurrent ingest may have won the unique dedupe-key insert after\n // our initial read. Re-read the immutable winner instead of allowing a\n // natural-key upsert to replace its evidence in place.\n const concurrent = await this.findByDedupeKey(dedupeKey);\n if (concurrent) return { snapshot: concurrent, created: false };\n throw error;\n }\n }\n\n async findByCampaign(campaignId: string): Promise<CampaignMetricSnapshot[]> {\n return await this.list({\n where: { campaignId },\n orderBy: 'period_start ASC',\n });\n }\n\n async findByChannel(\n campaignChannelId: string,\n ): Promise<CampaignMetricSnapshot[]> {\n return await this.list({\n where: { campaignChannelId },\n orderBy: 'period_start ASC',\n });\n }\n}\n\nexport default CampaignMetricSnapshotCollection;\n","import type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport {\n CampaignChannelCollection,\n CampaignCollection,\n CampaignMetricSnapshotCollection,\n} from '../collections/index.js';\nimport type { Campaign } from '../models/Campaign.js';\nimport type { CampaignMetricSnapshot } from '../models/CampaignMetricSnapshot.js';\nimport type { BudgetPacingResult, BudgetPacingStatus } from '../types.js';\n\n/** Computes spend-to-budget pacing from immutable snapshots; stores nothing. */\nexport class BudgetPacingService {\n constructor(\n private readonly campaigns: CampaignCollection,\n private readonly channels: CampaignChannelCollection,\n private readonly snapshots: CampaignMetricSnapshotCollection,\n ) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<BudgetPacingService> {\n return new BudgetPacingService(\n await CampaignCollection.create(classOptions),\n await CampaignChannelCollection.create(classOptions),\n await CampaignMetricSnapshotCollection.create(classOptions),\n );\n }\n\n async getCampaignPacing(\n campaignId: string,\n at = new Date(),\n ): Promise<BudgetPacingResult> {\n const campaign = await this.campaigns.get({ id: campaignId });\n if (!campaign) throw new Error(`Campaign '${campaignId}' was not found.`);\n\n const all = await this.snapshots.findByCampaign(campaignId);\n const rollups = all.filter((snapshot) => !snapshot.campaignChannelId);\n const used = this.selectCampaignEvidence(all);\n return this.calculate({\n campaign,\n snapshots: used,\n budgetCents: campaign.budgetCents,\n startAt: campaign.startAt,\n endAt: campaign.endAt,\n at,\n usedCampaignRollups: rollups.length > 0,\n });\n }\n\n async getChannelPacing(\n campaignChannelId: string,\n at = new Date(),\n ): Promise<BudgetPacingResult> {\n const channel = await this.channels.get({ id: campaignChannelId });\n if (!channel) {\n throw new Error(`CampaignChannel '${campaignChannelId}' was not found.`);\n }\n const campaign = await this.campaigns.get({ id: channel.campaignId });\n if (!campaign) {\n throw new Error(`Campaign '${channel.campaignId}' was not found.`);\n }\n const snapshots = await this.snapshots.findByChannel(campaignChannelId);\n return {\n ...this.calculate({\n campaign,\n snapshots,\n budgetCents: channel.allocatedBudgetCents,\n startAt: channel.startAt ?? campaign.startAt,\n endAt: channel.endAt ?? campaign.endAt,\n at,\n usedCampaignRollups: false,\n }),\n campaignChannelId,\n };\n }\n\n private calculate({\n campaign,\n snapshots,\n budgetCents,\n startAt,\n endAt,\n at,\n usedCampaignRollups,\n }: {\n campaign: Campaign;\n snapshots: CampaignMetricSnapshot[];\n budgetCents: number;\n startAt: Date | null;\n endAt: Date | null;\n at: Date;\n usedCampaignRollups: boolean;\n }): BudgetPacingResult {\n const spendCents = snapshots.reduce(\n (sum, snapshot) => sum + snapshot.spendCents,\n 0,\n );\n const elapsedFraction = this.elapsedFraction(startAt, endAt, at);\n const expectedSpendCents =\n elapsedFraction === null\n ? null\n : Math.round(budgetCents * elapsedFraction);\n const varianceCents =\n expectedSpendCents === null ? null : spendCents - expectedSpendCents;\n const budgetFraction = budgetCents > 0 ? spendCents / budgetCents : null;\n\n return {\n campaignId: campaign.id ?? '',\n currency: campaign.currency,\n budgetCents,\n spendCents,\n remainingCents: budgetCents - spendCents,\n expectedSpendCents,\n varianceCents,\n budgetFraction,\n elapsedFraction,\n status: this.pacingStatus({\n campaign,\n budgetCents,\n spendCents,\n elapsedFraction,\n varianceCents,\n startAt,\n at,\n }),\n snapshotCount: snapshots.length,\n usedCampaignRollups,\n };\n }\n\n private pacingStatus({\n campaign,\n budgetCents,\n spendCents,\n elapsedFraction,\n varianceCents,\n startAt,\n at,\n }: {\n campaign: Campaign;\n budgetCents: number;\n spendCents: number;\n elapsedFraction: number | null;\n varianceCents: number | null;\n startAt: Date | null;\n at: Date;\n }): BudgetPacingStatus {\n if (budgetCents <= 0) return 'unbudgeted';\n if (spendCents > budgetCents) return 'over_budget';\n if (startAt && at.getTime() < startAt.getTime()) {\n return 'not_started';\n }\n if (\n campaign.status === 'completed' ||\n campaign.status === 'archived' ||\n elapsedFraction === 1\n ) {\n return 'complete';\n }\n if (varianceCents === null) return 'on_track';\n\n // A five-percent budget band avoids noisy status flips from minor timing\n // differences between spend collection and scheduled pacing.\n const toleranceCents = Math.max(1, Math.round(budgetCents * 0.05));\n if (varianceCents > toleranceCents) return 'ahead';\n if (varianceCents < -toleranceCents) return 'behind';\n return 'on_track';\n }\n\n /**\n * Prefer a campaign rollup for each exact reporting period, while retaining\n * channel evidence for periods that do not yet have a rollup. This prevents\n * double counting without dropping earlier/later channel-only periods.\n */\n private selectCampaignEvidence(\n snapshots: CampaignMetricSnapshot[],\n ): CampaignMetricSnapshot[] {\n const rollupPeriods = new Set(\n snapshots\n .filter((snapshot) => !snapshot.campaignChannelId)\n .map((snapshot) => this.periodKey(snapshot)),\n );\n return snapshots.filter(\n (snapshot) =>\n !snapshot.campaignChannelId ||\n !rollupPeriods.has(this.periodKey(snapshot)),\n );\n }\n\n private periodKey(snapshot: CampaignMetricSnapshot): string {\n return `${snapshot.periodStart.toISOString()}:${snapshot.periodEnd.toISOString()}`;\n }\n\n private elapsedFraction(\n startAt: Date | null,\n endAt: Date | null,\n at: Date,\n ): number | null {\n if (!startAt || !endAt) return null;\n const start = startAt.getTime();\n const end = endAt.getTime();\n if (end <= start) return at.getTime() < start ? 0 : 1;\n return Math.min(1, Math.max(0, (at.getTime() - start) / (end - start)));\n }\n}\n\nexport default BudgetPacingService;\n","import type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { CampaignCollection } from '../collections/CampaignCollection.js';\nimport type { Campaign } from '../models/Campaign.js';\nimport type { CampaignStatus } from '../types.js';\n\n/** Single authoritative write path for Campaign lifecycle changes. */\nexport class CampaignLifecycleService {\n constructor(private readonly campaigns: CampaignCollection) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<CampaignLifecycleService> {\n return new CampaignLifecycleService(\n await CampaignCollection.create(classOptions),\n );\n }\n\n async transition(\n campaignId: string,\n next: CampaignStatus,\n ): Promise<Campaign> {\n const campaign = await this.campaigns.get({ id: campaignId });\n if (!campaign) throw new Error(`Campaign '${campaignId}' was not found.`);\n campaign.transitionTo(next);\n await campaign.save();\n return campaign;\n }\n\n async schedule(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'scheduled');\n }\n\n async activate(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'active');\n }\n\n async pause(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'paused');\n }\n\n async resume(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'active');\n }\n\n async complete(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'completed');\n }\n\n async archive(campaignId: string): Promise<Campaign> {\n return await this.transition(campaignId, 'archived');\n }\n}\n\nexport default CampaignLifecycleService;\n","import type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport { CampaignMetricSnapshotCollection } from '../collections/CampaignMetricSnapshotCollection.js';\nimport type { CampaignMetricSnapshot } from '../models/CampaignMetricSnapshot.js';\nimport type { CampaignMetricSnapshotOptions } from '../types.js';\n\n/** Idempotent, validation-first ingestion for immutable metric evidence. */\nexport class MetricIngestionService {\n constructor(private readonly snapshots: CampaignMetricSnapshotCollection) {}\n\n static async create(\n classOptions: SmrtClassOptions = {},\n ): Promise<MetricIngestionService> {\n return new MetricIngestionService(\n await CampaignMetricSnapshotCollection.create(classOptions),\n );\n }\n\n async ingest(\n options: CampaignMetricSnapshotOptions,\n ): Promise<{ snapshot: CampaignMetricSnapshot; created: boolean }> {\n if (!options.campaignId) {\n throw new Error('Metric ingestion requires campaignId.');\n }\n if (!options.source?.trim()) {\n throw new Error('Metric ingestion requires source.');\n }\n if (!options.dedupeKey?.trim()) {\n throw new Error('Metric ingestion requires dedupeKey.');\n }\n if (options.periodStart == null || options.periodEnd == null) {\n throw new Error('Metric ingestion requires periodStart and periodEnd.');\n }\n return await this.snapshots.getOrCreateByDedupeKey(options);\n }\n}\n\nexport default MetricIngestionService;\n"],"mappings":"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"}