@foundrynorth/compass-schema 1.0.20 → 1.0.21

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/dist/schema.js CHANGED
@@ -86,6 +86,7 @@ import { sql } from "drizzle-orm";
86
86
  import { pgTable, text, varchar, integer, jsonb, timestamp, doublePrecision, serial, numeric, uuid, date, index, boolean, pgEnum, unique, uniqueIndex, } from "drizzle-orm/pg-core";
87
87
  import { createInsertSchema } from "drizzle-zod";
88
88
  import { z } from "zod";
89
+ import { ACTIVITY_EVENT_TYPES, ACTIVITY_SEVERITIES, ACTIVITY_SOURCE_SYSTEMS, PENDING_ACTION_TYPES, } from "./types/activity-feed.js";
89
90
  export const users = pgTable("users", {
90
91
  id: varchar("id")
91
92
  .primaryKey()
@@ -98,6 +99,21 @@ export const insertUserSchema = createInsertSchema(users).pick({
98
99
  password: true,
99
100
  });
100
101
  export const avatarRequestStatusEnum = pgEnum("avatar_request_status", ["pending", "processed", "rejected"]);
102
+ // Activity feed enums (order activity feed + pending actions)
103
+ export const activityEventTypeEnum = pgEnum("activity_event_type", ACTIVITY_EVENT_TYPES);
104
+ export const activitySeverityEnum = pgEnum("activity_severity", ACTIVITY_SEVERITIES);
105
+ export const activitySourceSystemEnum = pgEnum("activity_source_system", ACTIVITY_SOURCE_SYSTEMS);
106
+ export const pendingActionTypeEnum = pgEnum("pending_action_type", PENDING_ACTION_TYPES);
107
+ export const amendmentReasonEnum = pgEnum("amendment_reason", [
108
+ "budget_adjustment",
109
+ "flight_date_change",
110
+ "product_swap",
111
+ "add_line_items",
112
+ "remove_line_items",
113
+ "rate_renegotiation",
114
+ "client_request",
115
+ "other",
116
+ ]);
101
117
  // Vendor enums (must be declared before clerkUsers which references them)
102
118
  export const vendorFulfillmentStatusEnum = pgEnum("vendor_fulfillment_status", [
103
119
  "assigned",
@@ -167,6 +183,19 @@ export const userProfiles = pgTable("user_profiles", {
167
183
  photoUrl: text("photo_url"), // Profile photo (can sync from Clerk or custom upload)
168
184
  agencyAvatarUrl: text("agency_avatar_url"), // Hand-sketched avatar for proposals (Fal.ai)
169
185
  lastAvatarGeneratedAt: timestamp("last_avatar_generated_at"), // Rate limiting: non-admins can only generate 1/day
186
+ proposalPortraitRenderUrl: text("proposal_portrait_render_url"),
187
+ proposalPortraitCutoutUrl: text("proposal_portrait_cutout_url"),
188
+ proposalPortraitStatus: text("proposal_portrait_status"),
189
+ proposalPortraitPromptVersion: text("proposal_portrait_prompt_version"),
190
+ proposalPortraitSeed: integer("proposal_portrait_seed"),
191
+ proposalPortraitGeneratedAt: timestamp("proposal_portrait_generated_at"),
192
+ proposalPortraitSourcePhotoHash: text("proposal_portrait_source_photo_hash"),
193
+ proposalPortraitPreviewRenderUrl: text("proposal_portrait_preview_render_url"),
194
+ proposalPortraitPreviewCutoutUrl: text("proposal_portrait_preview_cutout_url"),
195
+ proposalPortraitPreviewPromptVersion: text("proposal_portrait_preview_prompt_version"),
196
+ proposalPortraitPreviewSeed: integer("proposal_portrait_preview_seed"),
197
+ proposalPortraitPreviewGeneratedAt: timestamp("proposal_portrait_preview_generated_at"),
198
+ proposalPortraitPreviewSourcePhotoHash: text("proposal_portrait_preview_source_photo_hash"),
170
199
  // Active Market Preference (overrides partner default)
171
200
  preferredMarketId: varchar("preferred_market_id"), // User's preferred market for planning
172
201
  // Digital Business Card
@@ -195,6 +224,19 @@ export const externalTeamMembers = pgTable("external_team_members", {
195
224
  title: text("title"),
196
225
  photoUrl: text("photo_url"),
197
226
  agencyAvatarUrl: text("agency_avatar_url"),
227
+ proposalPortraitRenderUrl: text("proposal_portrait_render_url"),
228
+ proposalPortraitCutoutUrl: text("proposal_portrait_cutout_url"),
229
+ proposalPortraitStatus: text("proposal_portrait_status"),
230
+ proposalPortraitPromptVersion: text("proposal_portrait_prompt_version"),
231
+ proposalPortraitSeed: integer("proposal_portrait_seed"),
232
+ proposalPortraitGeneratedAt: timestamp("proposal_portrait_generated_at"),
233
+ proposalPortraitSourcePhotoHash: text("proposal_portrait_source_photo_hash"),
234
+ proposalPortraitPreviewRenderUrl: text("proposal_portrait_preview_render_url"),
235
+ proposalPortraitPreviewCutoutUrl: text("proposal_portrait_preview_cutout_url"),
236
+ proposalPortraitPreviewPromptVersion: text("proposal_portrait_preview_prompt_version"),
237
+ proposalPortraitPreviewSeed: integer("proposal_portrait_preview_seed"),
238
+ proposalPortraitPreviewGeneratedAt: timestamp("proposal_portrait_preview_generated_at"),
239
+ proposalPortraitPreviewSourcePhotoHash: text("proposal_portrait_preview_source_photo_hash"),
198
240
  linkedinUrl: text("linkedin_url"),
199
241
  displayOrder: integer("display_order").default(0),
200
242
  createdAt: timestamp("created_at").notNull().defaultNow(),
@@ -855,7 +897,7 @@ export const plans = pgTable("plans", {
855
897
  selectedOptionId: varchar("selected_option_id"), // FK → plan_options.id (nullable during migration)
856
898
  contractId: uuid("contract_id"), // FK → contracts.id (nullable — links to annual contract)
857
899
  // Workflow mode: determines which UI sections and features are available
858
- // 'research_backed' = full AnalyzeAndPlan flow (default, all existing plans)
900
+ // 'research_backed' = discovery mode creates plan, runs analysis in background
859
901
  // 'direct_config' = configure products directly without research
860
902
  // 'aor_pitch' = agency-of-record pitch with proposal focus
861
903
  // 'io_entry' = direct insertion order entry from client PDF/XLSX
@@ -1414,6 +1456,7 @@ export const clerkUsers = pgTable("clerk_users", {
1414
1456
  createdAt: timestamp("created_at").notNull().defaultNow(),
1415
1457
  approvedAt: timestamp("approved_at"),
1416
1458
  approvedBy: text("approved_by"), // Admin who approved
1459
+ compassVoiceWarmth: numeric("compass_voice_warmth").default("0.6"),
1417
1460
  });
1418
1461
  export const insertClerkUserSchema = createInsertSchema(clerkUsers).omit({
1419
1462
  createdAt: true,
@@ -3268,6 +3311,28 @@ export const mediaOrderStatusEnum = pgEnum("media_order_status", [
3268
3311
  "active",
3269
3312
  "completed",
3270
3313
  ]);
3314
+ export const salesInitiatives = pgTable("sales_initiatives", {
3315
+ id: uuid("id").primaryKey().defaultRandom(),
3316
+ code: varchar("code").notNull().unique(),
3317
+ name: varchar("name").notNull(),
3318
+ description: text("description"),
3319
+ hubspotValue: varchar("hubspot_value").notNull().unique(),
3320
+ displayOrder: integer("display_order").notNull().default(0),
3321
+ startsAt: date("starts_at"),
3322
+ endsAt: date("ends_at"),
3323
+ isActive: boolean("is_active").notNull().default(true),
3324
+ createdAt: timestamp("created_at").notNull().defaultNow(),
3325
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
3326
+ }, (table) => [
3327
+ index("sales_initiatives_active_idx").on(table.isActive),
3328
+ index("sales_initiatives_display_order_idx").on(table.displayOrder),
3329
+ ]);
3330
+ export const insertSalesInitiativeSchema = createInsertSchema(salesInitiatives).omit({
3331
+ id: true,
3332
+ createdAt: true,
3333
+ updatedAt: true,
3334
+ });
3335
+ export const updateSalesInitiativeSchema = insertSalesInitiativeSchema.partial();
3271
3336
  export const mediaOrders = pgTable("media_orders", {
3272
3337
  id: varchar("id")
3273
3338
  .primaryKey()
@@ -3281,6 +3346,7 @@ export const mediaOrders = pgTable("media_orders", {
3281
3346
  hubspotDealId: text("hubspot_deal_id"),
3282
3347
  hubspotCompanyId: text("hubspot_company_id"),
3283
3348
  parentHubspotCompanyId: text("parent_hubspot_company_id"),
3349
+ initiativeCode: varchar("initiative_code"),
3284
3350
  // Client header
3285
3351
  clientName: text("client_name").notNull(),
3286
3352
  clientContactName: text("client_contact_name"),
@@ -3304,6 +3370,8 @@ export const mediaOrders = pgTable("media_orders", {
3304
3370
  activatedAt: timestamp("activated_at"),
3305
3371
  completedAt: timestamp("completed_at"),
3306
3372
  returnReason: text("return_reason"),
3373
+ amendmentReason: amendmentReasonEnum("amendment_reason"),
3374
+ amendmentNotes: text("amendment_notes"),
3307
3375
  slaHold: boolean("sla_hold").default(false),
3308
3376
  clientTier: text("client_tier"),
3309
3377
  // Share link
@@ -3329,8 +3397,6 @@ export const mediaOrders = pgTable("media_orders", {
3329
3397
  defaultPromoting: text("default_promoting"),
3330
3398
  // Order-level notes
3331
3399
  orderNotes: text("order_notes"),
3332
- // Sales initiative (links to sales_initiatives table)
3333
- initiativeCode: varchar("initiative_code"),
3334
3400
  // Billing fields
3335
3401
  navigaAdvertiserId: text("naviga_advertiser_id"),
3336
3402
  purchaseOrderNumber: text("purchase_order_number"),
@@ -3378,6 +3444,7 @@ export const mediaOrders = pgTable("media_orders", {
3378
3444
  index("media_orders_engagement_id_idx").on(table.engagementId),
3379
3445
  index("media_orders_partner_id_idx").on(table.partnerId),
3380
3446
  index("media_orders_hubspot_company_id_idx").on(table.hubspotCompanyId),
3447
+ index("media_orders_hubspot_deal_id_idx").on(table.hubspotDealId),
3381
3448
  index("media_orders_status_idx").on(table.status),
3382
3449
  index("media_orders_vendor_id_idx").on(table.vendorId),
3383
3450
  index("media_orders_vendor_fulfillment_status_idx").on(table.vendorFulfillmentStatus),
@@ -3476,6 +3543,7 @@ export const mediaOrderLineItems = pgTable("media_order_line_items", {
3476
3543
  // Fulfillment details (product-specific fields for HubSpot sync)
3477
3544
  fulfillmentDetails: jsonb("fulfillment_details"),
3478
3545
  creativeSource: varchar("creative_source"),
3546
+ initiativeCode: varchar("initiative_code"),
3479
3547
  // Ad trafficking reference key (auto-generated naming convention)
3480
3548
  trafficKey: text("traffic_key"),
3481
3549
  // Canonical tracking fields for DSP/reporting consistency
@@ -3485,9 +3553,11 @@ export const mediaOrderLineItems = pgTable("media_order_line_items", {
3485
3553
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
3486
3554
  }, (table) => [
3487
3555
  index("media_order_line_items_section_id_idx").on(table.sectionId),
3556
+ index("media_order_line_items_sync_status_idx").on(table.syncStatus),
3488
3557
  index("idx_line_items_parent").on(table.parentLineItemId),
3489
3558
  index("media_order_line_items_location_id_idx").on(table.locationId),
3490
3559
  index("media_order_line_items_tracking_key_idx").on(table.trackingKey),
3560
+ index("media_order_line_items_initiative_code_idx").on(table.initiativeCode),
3491
3561
  ]);
3492
3562
  export const insertMediaOrderLineItemSchema = createInsertSchema(mediaOrderLineItems).omit({
3493
3563
  id: true,
@@ -3652,6 +3722,20 @@ export const insertStribProductSchema = createInsertSchema(stribProducts).omit({
3652
3722
  updatedAt: true,
3653
3723
  });
3654
3724
  export const updateStribProductSchema = insertStribProductSchema.partial();
3725
+ export const salesInitiativeProducts = pgTable("sales_initiative_products", {
3726
+ id: uuid("id").primaryKey().defaultRandom(),
3727
+ initiativeCode: varchar("initiative_code").notNull(),
3728
+ stribProductId: uuid("strib_product_id")
3729
+ .notNull()
3730
+ .references(() => stribProducts.id, { onDelete: "cascade" }),
3731
+ displayOrder: integer("display_order").notNull().default(0),
3732
+ createdAt: timestamp("created_at").notNull().defaultNow(),
3733
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
3734
+ }, (table) => [
3735
+ uniqueIndex("sales_initiative_products_unique_idx").on(table.initiativeCode, table.stribProductId),
3736
+ index("sales_initiative_products_code_idx").on(table.initiativeCode),
3737
+ index("sales_initiative_products_product_idx").on(table.stribProductId),
3738
+ ]);
3655
3739
  /**
3656
3740
  * strib_product_rates — tier-based pricing per product.
3657
3741
  * Different tiers depending on family: Digital uses open/advocacy/tier_1/tier_2/tier_3;
@@ -3915,6 +3999,7 @@ export const rateExceptionRequests = pgTable("rate_exception_requests", {
3915
3999
  escalationHistory: jsonb("escalation_history"), // Array of {level, at, by, reason}
3916
4000
  expiresAt: timestamp("expires_at"),
3917
4001
  approvalScope: varchar("approval_scope").notNull().default("request"), // "request" = affects this exception only; "catalog" = also updates global product rate
4002
+ batchId: uuid("batch_id"), // Groups bulk rate exception requests submitted together
3918
4003
  createdAt: timestamp("created_at").notNull().defaultNow(),
3919
4004
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
3920
4005
  }, (table) => [
@@ -3922,6 +4007,8 @@ export const rateExceptionRequests = pgTable("rate_exception_requests", {
3922
4007
  index("rate_exception_status_idx").on(table.status),
3923
4008
  index("rate_exception_requester_idx").on(table.requestedByUserId),
3924
4009
  index("rate_exception_plan_idx").on(table.planId),
4010
+ index("rate_exception_batch_idx").on(table.batchId),
4011
+ index("rate_exception_order_status_idx").on(table.mediaOrderId, table.status),
3925
4012
  ]);
3926
4013
  // ─── Lead Time Rules ──────────────────────────────────────────────────────────
3927
4014
  export const leadTimeRules = pgTable("lead_time_rules", {
@@ -3968,6 +4055,31 @@ export const slaConfig = pgTable("sla_config", {
3968
4055
  createdAt: timestamp("created_at").defaultNow(),
3969
4056
  updatedAt: timestamp("updated_at").defaultNow(),
3970
4057
  });
4058
+ // ─── SLA Snapshots ───────────────────────────────────────────────────────────
4059
+ /**
4060
+ * sla_snapshots — Point-in-time SLA state from fn-flux evaluations.
4061
+ * Each row captures the hold status, deadline, milestones, and profile
4062
+ * for a given media order + fulfillment ticket pair.
4063
+ */
4064
+ export const slaHoldStatusEnum = pgEnum("sla_hold_status", ["clear", "hold", "override"]);
4065
+ export const slaSnapshots = pgTable("sla_snapshots", {
4066
+ id: serial("id").primaryKey(),
4067
+ mediaOrderId: text("media_order_id").notNull().references(() => mediaOrders.id),
4068
+ ticketId: text("ticket_id").notNull(),
4069
+ holdStatus: slaHoldStatusEnum("hold_status").notNull().default("clear"),
4070
+ holdReason: text("hold_reason"),
4071
+ slaDays: integer("sla_days").notNull(),
4072
+ availableDays: numeric("available_days"),
4073
+ slaDeadline: timestamp("sla_deadline"),
4074
+ campaignStartDate: date("campaign_start_date"),
4075
+ milestones: jsonb("milestones"),
4076
+ profileName: text("profile_name"),
4077
+ lastUpdatedAt: timestamp("last_updated_at").defaultNow(),
4078
+ }, (table) => [
4079
+ index("sla_snapshots_media_order_id_idx").on(table.mediaOrderId),
4080
+ index("sla_snapshots_ticket_id_idx").on(table.ticketId),
4081
+ uniqueIndex("sla_snapshots_order_ticket_uniq").on(table.mediaOrderId, table.ticketId),
4082
+ ]);
3971
4083
  // ─── Inventory Sync Audit Trail ─────────────────────────────────────────────
3972
4084
  /**
3973
4085
  * inventory_sync_log — Audit trail for external inventory syncs (GAM, Sailthru, Naviga).
@@ -5967,4 +6079,71 @@ export const creativeDeliveries = pgTable("creative_deliveries", {
5967
6079
  assetCount: integer("asset_count").notNull().default(0),
5968
6080
  receivedAt: timestamp("received_at").defaultNow().notNull(),
5969
6081
  });
6082
+ // ─── Order Activity Feed ────────────────────────────────────────────────────
6083
+ export const orderActivityFeed = pgTable("order_activity_feed", {
6084
+ id: uuid("id").primaryKey().defaultRandom(),
6085
+ mediaOrderId: varchar("media_order_id")
6086
+ .notNull()
6087
+ .references(() => mediaOrders.id),
6088
+ lineItemId: varchar("line_item_id").references(() => mediaOrderLineItems.id),
6089
+ eventType: activityEventTypeEnum("event_type").notNull(),
6090
+ title: text("title").notNull(),
6091
+ detail: text("detail"),
6092
+ severity: activitySeverityEnum("severity").notNull(),
6093
+ sourceSystem: activitySourceSystemEnum("source_system").notNull(),
6094
+ externalId: text("external_id").notNull().unique(), // idempotency key — prevents duplicate event ingestion
6095
+ status: text("status"), // intentionally untyped — values vary by eventType (creative vs fulfillment vs contract)
6096
+ occurredAt: timestamp("occurred_at").notNull(),
6097
+ createdAt: timestamp("created_at").notNull().defaultNow(),
6098
+ metadata: jsonb("metadata").$type(),
6099
+ }, (table) => [
6100
+ index("order_activity_feed_order_occurred_idx").on(table.mediaOrderId, table.occurredAt),
6101
+ index("order_activity_feed_line_item_idx").on(table.lineItemId),
6102
+ ]);
6103
+ // ─── Pending Actions ────────────────────────────────────────────────────────
6104
+ export const pendingActions = pgTable("pending_actions", {
6105
+ id: uuid("id").primaryKey().defaultRandom(),
6106
+ mediaOrderId: varchar("media_order_id")
6107
+ .notNull()
6108
+ .references(() => mediaOrders.id),
6109
+ lineItemId: varchar("line_item_id").references(() => mediaOrderLineItems.id),
6110
+ actionType: pendingActionTypeEnum("action_type").notNull(),
6111
+ message: text("message").notNull(),
6112
+ activityFeedId: uuid("activity_feed_id")
6113
+ .notNull()
6114
+ .references(() => orderActivityFeed.id),
6115
+ resolvedAt: timestamp("resolved_at"),
6116
+ createdAt: timestamp("created_at").notNull().defaultNow(),
6117
+ }, (table) => [
6118
+ index("pending_actions_order_idx").on(table.mediaOrderId),
6119
+ index("pending_actions_unresolved_idx").on(table.mediaOrderId).where(sql `resolved_at IS NULL`),
6120
+ ]);
6121
+ // ─── Order Version Snapshots ────────────────────────────────────────────────
6122
+ export const orderVersionSnapshots = pgTable("order_version_snapshots", {
6123
+ id: uuid("id").primaryKey().defaultRandom(),
6124
+ mediaOrderId: varchar("media_order_id")
6125
+ .notNull()
6126
+ .references(() => mediaOrders.id),
6127
+ version: integer("version").notNull(),
6128
+ amendmentReason: text("amendment_reason"),
6129
+ amendmentNotes: text("amendment_notes"),
6130
+ amendedBy: varchar("amended_by"),
6131
+ snapshot: jsonb("snapshot").notNull().$type(),
6132
+ netInvestment: numeric("net_investment", { precision: 12, scale: 2 }),
6133
+ lineItemCount: integer("line_item_count"),
6134
+ createdAt: timestamp("created_at").notNull().defaultNow(),
6135
+ }, (table) => [
6136
+ index("order_version_snapshots_order_version_idx").on(table.mediaOrderId, table.version),
6137
+ ]);
6138
+ // ─── User Milestones ────────────────────────────────────────────────────────
6139
+ export const userMilestones = pgTable("user_milestones", {
6140
+ id: uuid("id").primaryKey().defaultRandom(),
6141
+ userId: varchar("user_id").notNull(),
6142
+ milestoneKey: text("milestone_key").notNull(),
6143
+ shownAt: timestamp("shown_at").notNull().defaultNow(),
6144
+ createdAt: timestamp("created_at").notNull().defaultNow(),
6145
+ }, (table) => [
6146
+ index("user_milestones_user_id_idx").on(table.userId),
6147
+ unique("user_milestones_user_key_unique").on(table.userId, table.milestoneKey),
6148
+ ]);
5970
6149
  //# sourceMappingURL=schema.js.map