@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.
- package/AGENTS.md +79 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +67 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/collections/CampaignChannelCollection.d.ts +9 -0
- package/dist/collections/CampaignChannelCollection.d.ts.map +1 -0
- package/dist/collections/CampaignCollection.d.ts +10 -0
- package/dist/collections/CampaignCollection.d.ts.map +1 -0
- package/dist/collections/CampaignMetricSnapshotCollection.d.ts +15 -0
- package/dist/collections/CampaignMetricSnapshotCollection.d.ts.map +1 -0
- package/dist/collections/index.d.ts +4 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +625 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +1173 -0
- package/dist/models/Campaign.d.ts +31 -0
- package/dist/models/Campaign.d.ts.map +1 -0
- package/dist/models/CampaignChannel.d.ts +22 -0
- package/dist/models/CampaignChannel.d.ts.map +1 -0
- package/dist/models/CampaignMetricSnapshot.d.ts +29 -0
- package/dist/models/CampaignMetricSnapshot.d.ts.map +1 -0
- package/dist/models/index.d.ts +4 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/services/BudgetPacingService.d.ts +25 -0
- package/dist/services/BudgetPacingService.d.ts.map +1 -0
- package/dist/services/CampaignLifecycleService.d.ts +19 -0
- package/dist/services/CampaignLifecycleService.d.ts.map +1 -0
- package/dist/services/MetricIngestionService.d.ts +16 -0
- package/dist/services/MetricIngestionService.d.ts.map +1 -0
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +875 -0
- package/dist/svelte/__tests__/components.test.js +115 -0
- package/dist/svelte/__tests__/helpers.test.js +94 -0
- package/dist/svelte/components/BudgetPacing.svelte +129 -0
- package/dist/svelte/components/BudgetPacing.svelte.d.ts +10 -0
- package/dist/svelte/components/BudgetPacing.svelte.d.ts.map +1 -0
- package/dist/svelte/components/CampaignDetail.svelte +208 -0
- package/dist/svelte/components/CampaignDetail.svelte.d.ts +14 -0
- package/dist/svelte/components/CampaignDetail.svelte.d.ts.map +1 -0
- package/dist/svelte/components/CampaignList.svelte +140 -0
- package/dist/svelte/components/CampaignList.svelte.d.ts +11 -0
- package/dist/svelte/components/CampaignList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ChannelMix.svelte +138 -0
- package/dist/svelte/components/ChannelMix.svelte.d.ts +10 -0
- package/dist/svelte/components/ChannelMix.svelte.d.ts.map +1 -0
- package/dist/svelte/components/MarketingDashboard.svelte +129 -0
- package/dist/svelte/components/MarketingDashboard.svelte.d.ts +13 -0
- package/dist/svelte/components/MarketingDashboard.svelte.d.ts.map +1 -0
- package/dist/svelte/format.d.ts +8 -0
- package/dist/svelte/format.d.ts.map +1 -0
- package/dist/svelte/format.js +42 -0
- package/dist/svelte/index.d.ts +8 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +7 -0
- package/dist/svelte/types.d.ts +73 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +80 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +29 -0
- package/dist/types.js.map +1 -0
- package/package.json +79 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @happyvertical/smrt-marketing
|
|
2
|
+
|
|
3
|
+
Cross-channel campaign coordination for SMRT. This package owns Campaign
|
|
4
|
+
identity, budgets, lifecycle, execution links, immutable performance evidence,
|
|
5
|
+
computed pacing, and props-driven Svelte surfaces. It does not execute ads,
|
|
6
|
+
publish social posts, send messages, or own sales attribution.
|
|
7
|
+
|
|
8
|
+
## Validation
|
|
9
|
+
|
|
10
|
+
Run `pnpm --filter @happyvertical/smrt-marketing test` and
|
|
11
|
+
`pnpm --filter @happyvertical/smrt-marketing typecheck`; use `test:coverage`
|
|
12
|
+
to exercise the model/service and component coverage configurations.
|
|
13
|
+
Natural-key changes must also run
|
|
14
|
+
`pnpm --filter @happyvertical/smrt-marketing test:postgres` when
|
|
15
|
+
the disposable PostgreSQL harness is available. Use
|
|
16
|
+
`pnpm --filter @happyvertical/smrt-marketing build` and `verify:pack` for
|
|
17
|
+
publish-surface changes.
|
|
18
|
+
|
|
19
|
+
## Models
|
|
20
|
+
|
|
21
|
+
- **Campaign**: optional-tenant umbrella with stable natural key
|
|
22
|
+
`(tenant_id, campaign_key)`, open objective, integer-cent budget, currency,
|
|
23
|
+
schedule, guarded metadata helpers, and lifecycle
|
|
24
|
+
`draft → scheduled → active ↔ paused → completed → archived`. Raw saves are
|
|
25
|
+
protected by an authoritative prior-status re-read; use
|
|
26
|
+
`CampaignLifecycleService` for lifecycle writes.
|
|
27
|
+
- **CampaignChannel**: one generic execution link per
|
|
28
|
+
`(campaign_id, channel_kind, channel_ref)`. `channelKind`/`channelRef` are
|
|
29
|
+
intentionally plain strings so ads, social, messages, content, events, and
|
|
30
|
+
referral programs remain peer packages. Allocation and schedule overrides
|
|
31
|
+
use integer cents and dates.
|
|
32
|
+
- **CampaignMetricSnapshot**: immutable period evidence with a global
|
|
33
|
+
`dedupe_key` natural key. A snapshot always carries `campaignId`; a null
|
|
34
|
+
`campaignChannelId` means campaign-level rollup evidence; when present, the
|
|
35
|
+
channel must belong to that same campaign. API/MCP/CLI expose create/list/get
|
|
36
|
+
only, and the save guard rejects hydrated edits, blind-id overwrites, and
|
|
37
|
+
natural-key overwrites. Programmatic deletion is rejected.
|
|
38
|
+
|
|
39
|
+
Every model is `@TenantScoped({ mode: 'optional' })` with nullable `tenantId`.
|
|
40
|
+
All generated surfaces are explicit; never omit `api`, `mcp`, or `cli` config.
|
|
41
|
+
|
|
42
|
+
## Services
|
|
43
|
+
|
|
44
|
+
- **CampaignLifecycleService** loads the current row before applying one legal
|
|
45
|
+
transition. Paused campaigns may resume; completed campaigns may only be
|
|
46
|
+
archived; archived campaigns are terminal.
|
|
47
|
+
- **MetricIngestionService** validates required scope/source/period fields and
|
|
48
|
+
delegates to `getOrCreateByDedupeKey()`. A replay returns the original row
|
|
49
|
+
unchanged even if the replay payload differs.
|
|
50
|
+
- **BudgetPacingService** computes campaign/channel pacing from snapshots and
|
|
51
|
+
never persists derived balances. For each exact reporting period, campaign
|
|
52
|
+
pacing prefers campaign rollups and otherwise sums channel snapshots,
|
|
53
|
+
preventing double counting without dropping channel-only periods. Status
|
|
54
|
+
uses a five-percent budget tolerance around schedule-derived expected spend.
|
|
55
|
+
|
|
56
|
+
## Svelte
|
|
57
|
+
|
|
58
|
+
`@happyvertical/smrt-marketing/svelte` exports `MarketingDashboard`,
|
|
59
|
+
`CampaignList`, `CampaignDetail`, `ChannelMix`, and `BudgetPacing`, plus view
|
|
60
|
+
interfaces and pure helpers. Components are props-only: no fetching, no model
|
|
61
|
+
imports, no providers. Use Provider-free `smrt-ui` primitives, `--smrt-*`
|
|
62
|
+
tokens, named `$props()` interfaces, and integer-cent inputs formatted only at
|
|
63
|
+
render time.
|
|
64
|
+
|
|
65
|
+
## Boundaries and gotchas
|
|
66
|
+
|
|
67
|
+
- Runtime dependencies stay limited to `smrt-core`, `smrt-tenancy`, and
|
|
68
|
+
`smrt-ui`. Never statically import sibling domain packages.
|
|
69
|
+
- Lead loop-closing is conventional: CRM stores `sourceKind: 'campaign'` and a
|
|
70
|
+
campaign key in `sourceId`; marketing does not import or mutate Lead.
|
|
71
|
+
- Attribution math remains in sales/referrals. Marketing stores performance
|
|
72
|
+
evidence only.
|
|
73
|
+
- Campaign rollups and channel snapshots may describe the same period. The
|
|
74
|
+
pacing service deliberately chooses one evidence level instead of summing
|
|
75
|
+
both.
|
|
76
|
+
- Corrections append a new metric snapshot with a new dedupe key; never edit
|
|
77
|
+
persisted evidence.
|
|
78
|
+
- Table names are global: `campaigns`, `campaign_channels`, and
|
|
79
|
+
`campaign_metric_snapshots`.
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright <2025> <Happy Vertical Corporation>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @happyvertical/smrt-marketing
|
|
2
|
+
|
|
3
|
+
Cross-channel Campaign models, immutable performance snapshots, computed
|
|
4
|
+
budget pacing, and reusable Svelte marketing surfaces for SMRT.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pnpm add @happyvertical/smrt-marketing
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import {
|
|
12
|
+
BudgetPacingService,
|
|
13
|
+
CampaignChannelCollection,
|
|
14
|
+
CampaignCollection,
|
|
15
|
+
MetricIngestionService,
|
|
16
|
+
} from '@happyvertical/smrt-marketing';
|
|
17
|
+
|
|
18
|
+
const campaigns = await CampaignCollection.create({ db });
|
|
19
|
+
const channels = await CampaignChannelCollection.create({ db });
|
|
20
|
+
const campaign = await campaigns.create({
|
|
21
|
+
tenantId,
|
|
22
|
+
campaignKey: 'summer-demand-2026',
|
|
23
|
+
name: 'Summer demand 2026',
|
|
24
|
+
objective: 'demand_generation',
|
|
25
|
+
budgetCents: 200_000,
|
|
26
|
+
currency: 'CAD',
|
|
27
|
+
});
|
|
28
|
+
if (!campaign.id) throw new Error('Campaign did not persist');
|
|
29
|
+
|
|
30
|
+
const adGroup = await channels.create({
|
|
31
|
+
tenantId,
|
|
32
|
+
campaignId: campaign.id,
|
|
33
|
+
channelKind: 'ad_group',
|
|
34
|
+
channelRef: 'ad-group-42',
|
|
35
|
+
allocatedBudgetCents: 150_000,
|
|
36
|
+
});
|
|
37
|
+
if (!adGroup.id) throw new Error('Campaign channel did not persist');
|
|
38
|
+
|
|
39
|
+
const ingestion = await MetricIngestionService.create({ db });
|
|
40
|
+
await ingestion.ingest({
|
|
41
|
+
tenantId,
|
|
42
|
+
campaignId: campaign.id,
|
|
43
|
+
campaignChannelId: adGroup.id,
|
|
44
|
+
periodStart: new Date('2026-07-01T00:00:00Z'),
|
|
45
|
+
periodEnd: new Date('2026-07-01T23:59:59Z'),
|
|
46
|
+
spendCents: 12_500,
|
|
47
|
+
impressions: 25_000,
|
|
48
|
+
clicks: 800,
|
|
49
|
+
conversions: 35,
|
|
50
|
+
leads: 20,
|
|
51
|
+
source: 'ad-platform',
|
|
52
|
+
dedupeKey: `${tenantId}:summer-demand-2026:ad-group-42:2026-07-01`,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Channel-scoped evidence is accepted only when the channel belongs to the
|
|
56
|
+
// supplied campaign. Reporting periods are required valid date-like values.
|
|
57
|
+
|
|
58
|
+
const pacing = await BudgetPacingService.create({ db });
|
|
59
|
+
console.log(await pacing.getCampaignPacing(campaign.id));
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Svelte components are exported from `@happyvertical/smrt-marketing/svelte`.
|
|
63
|
+
They are presentational and accept plain view models; consumers remain in
|
|
64
|
+
control of fetching and mutations.
|
|
65
|
+
|
|
66
|
+
See [AGENTS.md](./AGENTS.md) for lifecycle, evidence, and package-boundary
|
|
67
|
+
invariants.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"__smrt-register__.d.ts","sourceRoot":"","sources":["../src/__smrt-register__.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SmrtCollection } from '@happyvertical/smrt-core';
|
|
2
|
+
import { CampaignChannel } from '../models/CampaignChannel.js';
|
|
3
|
+
export declare class CampaignChannelCollection extends SmrtCollection<CampaignChannel> {
|
|
4
|
+
static readonly _itemClass: typeof CampaignChannel;
|
|
5
|
+
findByCampaign(campaignId: string): Promise<CampaignChannel[]>;
|
|
6
|
+
findByExecutionRef(channelKind: string, channelRef: string): Promise<CampaignChannel[]>;
|
|
7
|
+
}
|
|
8
|
+
export default CampaignChannelCollection;
|
|
9
|
+
//# sourceMappingURL=CampaignChannelCollection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CampaignChannelCollection.d.ts","sourceRoot":"","sources":["../../src/collections/CampaignChannelCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,qBAAa,yBAA0B,SAAQ,cAAc,CAAC,eAAe,CAAC;IAC5E,MAAM,CAAC,QAAQ,CAAC,UAAU,yBAAmB;IAEvC,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAO9D,kBAAkB,CACtB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,EAAE,CAAC;CAG9B;AAED,eAAe,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SmrtCollection } from '@happyvertical/smrt-core';
|
|
2
|
+
import { Campaign } from '../models/Campaign.js';
|
|
3
|
+
import { CampaignStatus } from '../types.js';
|
|
4
|
+
export declare class CampaignCollection extends SmrtCollection<Campaign> {
|
|
5
|
+
static readonly _itemClass: typeof Campaign;
|
|
6
|
+
findByCampaignKey(campaignKey: string, tenantId?: string | null): Promise<Campaign | null>;
|
|
7
|
+
findByStatus(status: CampaignStatus): Promise<Campaign[]>;
|
|
8
|
+
}
|
|
9
|
+
export default CampaignCollection;
|
|
10
|
+
//# sourceMappingURL=CampaignCollection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CampaignCollection.d.ts","sourceRoot":"","sources":["../../src/collections/CampaignCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,qBAAa,kBAAmB,SAAQ,cAAc,CAAC,QAAQ,CAAC;IAC9D,MAAM,CAAC,QAAQ,CAAC,UAAU,kBAAY;IAEhC,iBAAiB,CACrB,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAQrB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;CAMhE;AAED,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SmrtCollection } from '@happyvertical/smrt-core';
|
|
2
|
+
import { CampaignMetricSnapshot } from '../models/CampaignMetricSnapshot.js';
|
|
3
|
+
import { CampaignMetricSnapshotOptions } from '../types.js';
|
|
4
|
+
export declare class CampaignMetricSnapshotCollection extends SmrtCollection<CampaignMetricSnapshot> {
|
|
5
|
+
static readonly _itemClass: typeof CampaignMetricSnapshot;
|
|
6
|
+
findByDedupeKey(dedupeKey: string): Promise<CampaignMetricSnapshot | null>;
|
|
7
|
+
getOrCreateByDedupeKey(options: CampaignMetricSnapshotOptions): Promise<{
|
|
8
|
+
snapshot: CampaignMetricSnapshot;
|
|
9
|
+
created: boolean;
|
|
10
|
+
}>;
|
|
11
|
+
findByCampaign(campaignId: string): Promise<CampaignMetricSnapshot[]>;
|
|
12
|
+
findByChannel(campaignChannelId: string): Promise<CampaignMetricSnapshot[]>;
|
|
13
|
+
}
|
|
14
|
+
export default CampaignMetricSnapshotCollection;
|
|
15
|
+
//# sourceMappingURL=CampaignMetricSnapshotCollection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CampaignMetricSnapshotCollection.d.ts","sourceRoot":"","sources":["../../src/collections/CampaignMetricSnapshotCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAEjE,qBAAa,gCAAiC,SAAQ,cAAc,CAAC,sBAAsB,CAAC;IAC1F,MAAM,CAAC,QAAQ,CAAC,UAAU,gCAA0B;IAE9C,eAAe,CACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC;IAMnC,sBAAsB,CAC1B,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC;QAAE,QAAQ,EAAE,sBAAsB,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IA8B5D,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC;IAOrE,aAAa,CACjB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,sBAAsB,EAAE,CAAC;CAMrC;AAED,eAAe,gCAAgC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/collections/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,uCAAuC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,OAAO,wBAAwB,CAAC;AAEhC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC"}
|