adserver-dashboard 1.0.0

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 (525) hide show
  1. package/.ci/staging.yml +191 -0
  2. package/.dockerignore +117 -0
  3. package/.env +40 -0
  4. package/.env.staging +38 -0
  5. package/.gitlab-ci.yml +16 -0
  6. package/DEMO_STATUS.md +579 -0
  7. package/Dockerfile +61 -0
  8. package/Influence-MW-AdServer-12-02-2026/client/index.html +17 -0
  9. package/Influence-MW-AdServer-12-02-2026/client/public/favicon.png +0 -0
  10. package/Influence-MW-AdServer-12-02-2026/client/src/App.tsx +91 -0
  11. package/Influence-MW-AdServer-12-02-2026/client/src/components/advanced-map-drawer.tsx +1131 -0
  12. package/Influence-MW-AdServer-12-02-2026/client/src/components/ai-recommendation-panel.tsx +379 -0
  13. package/Influence-MW-AdServer-12-02-2026/client/src/components/app-sidebar.tsx +183 -0
  14. package/Influence-MW-AdServer-12-02-2026/client/src/components/auto-optimize-button.tsx +184 -0
  15. package/Influence-MW-AdServer-12-02-2026/client/src/components/availability-drawer.tsx +385 -0
  16. package/Influence-MW-AdServer-12-02-2026/client/src/components/brand-insights-panel.tsx +87 -0
  17. package/Influence-MW-AdServer-12-02-2026/client/src/components/create-agency-drawer.tsx +198 -0
  18. package/Influence-MW-AdServer-12-02-2026/client/src/components/create-brand-drawer.tsx +275 -0
  19. package/Influence-MW-AdServer-12-02-2026/client/src/components/creative-assignment.tsx +526 -0
  20. package/Influence-MW-AdServer-12-02-2026/client/src/components/data-table-toolbar.tsx +148 -0
  21. package/Influence-MW-AdServer-12-02-2026/client/src/components/data-table.tsx +158 -0
  22. package/Influence-MW-AdServer-12-02-2026/client/src/components/filter-drawer.tsx +356 -0
  23. package/Influence-MW-AdServer-12-02-2026/client/src/components/form-insights-panel.tsx +82 -0
  24. package/Influence-MW-AdServer-12-02-2026/client/src/components/geography-selector.tsx +699 -0
  25. package/Influence-MW-AdServer-12-02-2026/client/src/components/header-user-menu.tsx +178 -0
  26. package/Influence-MW-AdServer-12-02-2026/client/src/components/history-drawer.tsx +313 -0
  27. package/Influence-MW-AdServer-12-02-2026/client/src/components/inventory-availability-section.tsx +176 -0
  28. package/Influence-MW-AdServer-12-02-2026/client/src/components/inventory-format-drawer.tsx +173 -0
  29. package/Influence-MW-AdServer-12-02-2026/client/src/components/inventory-selector.tsx +401 -0
  30. package/Influence-MW-AdServer-12-02-2026/client/src/components/manual-inventory-drawer.tsx +368 -0
  31. package/Influence-MW-AdServer-12-02-2026/client/src/components/mapbox-map.tsx +368 -0
  32. package/Influence-MW-AdServer-12-02-2026/client/src/components/market-insights-panel.tsx +202 -0
  33. package/Influence-MW-AdServer-12-02-2026/client/src/components/media-owner-drawer.tsx +217 -0
  34. package/Influence-MW-AdServer-12-02-2026/client/src/components/metric-card.tsx +58 -0
  35. package/Influence-MW-AdServer-12-02-2026/client/src/components/page-header.tsx +27 -0
  36. package/Influence-MW-AdServer-12-02-2026/client/src/components/player-status-indicator.tsx +137 -0
  37. package/Influence-MW-AdServer-12-02-2026/client/src/components/poi-targeting-drawer.tsx +298 -0
  38. package/Influence-MW-AdServer-12-02-2026/client/src/components/recommendation-score-badge.tsx +102 -0
  39. package/Influence-MW-AdServer-12-02-2026/client/src/components/recommended-inventories-panel.tsx +248 -0
  40. package/Influence-MW-AdServer-12-02-2026/client/src/components/searchable-combobox.tsx +134 -0
  41. package/Influence-MW-AdServer-12-02-2026/client/src/components/signal-visualizations.tsx +407 -0
  42. package/Influence-MW-AdServer-12-02-2026/client/src/components/status-badge.tsx +35 -0
  43. package/Influence-MW-AdServer-12-02-2026/client/src/components/theme-provider.tsx +73 -0
  44. package/Influence-MW-AdServer-12-02-2026/client/src/components/theme-toggle.tsx +37 -0
  45. package/Influence-MW-AdServer-12-02-2026/client/src/components/traffic-slider.tsx +75 -0
  46. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/accordion.tsx +56 -0
  47. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/alert-dialog.tsx +139 -0
  48. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/alert.tsx +59 -0
  49. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/aspect-ratio.tsx +5 -0
  50. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/avatar.tsx +51 -0
  51. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/badge.tsx +38 -0
  52. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/breadcrumb.tsx +115 -0
  53. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/button.tsx +62 -0
  54. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/calendar.tsx +68 -0
  55. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/card.tsx +85 -0
  56. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/carousel.tsx +260 -0
  57. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/chart.tsx +365 -0
  58. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/checkbox.tsx +28 -0
  59. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/collapsible.tsx +11 -0
  60. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/command.tsx +151 -0
  61. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/context-menu.tsx +198 -0
  62. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/dialog.tsx +122 -0
  63. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/drawer.tsx +118 -0
  64. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/dropdown-menu.tsx +198 -0
  65. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/form.tsx +178 -0
  66. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/hover-card.tsx +29 -0
  67. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/input-otp.tsx +69 -0
  68. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/input.tsx +23 -0
  69. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/label.tsx +24 -0
  70. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/menubar.tsx +256 -0
  71. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/navigation-menu.tsx +128 -0
  72. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/pagination.tsx +117 -0
  73. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/popover.tsx +29 -0
  74. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/progress.tsx +28 -0
  75. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/radio-group.tsx +42 -0
  76. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/resizable.tsx +45 -0
  77. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/scroll-area.tsx +46 -0
  78. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/select.tsx +160 -0
  79. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/separator.tsx +29 -0
  80. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/sheet.tsx +140 -0
  81. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/sidebar.tsx +727 -0
  82. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/skeleton.tsx +15 -0
  83. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/slider.tsx +26 -0
  84. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/switch.tsx +27 -0
  85. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/table.tsx +117 -0
  86. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/tabs.tsx +53 -0
  87. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/textarea.tsx +22 -0
  88. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toast.tsx +127 -0
  89. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toaster.tsx +33 -0
  90. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toggle-group.tsx +61 -0
  91. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toggle.tsx +43 -0
  92. package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/tooltip.tsx +30 -0
  93. package/Influence-MW-AdServer-12-02-2026/client/src/components/vendor-stores-modal.tsx +336 -0
  94. package/Influence-MW-AdServer-12-02-2026/client/src/components/venue-type-drawer.tsx +359 -0
  95. package/Influence-MW-AdServer-12-02-2026/client/src/components/venue-type-selector.tsx +436 -0
  96. package/Influence-MW-AdServer-12-02-2026/client/src/hooks/use-mobile.tsx +19 -0
  97. package/Influence-MW-AdServer-12-02-2026/client/src/hooks/use-toast.ts +191 -0
  98. package/Influence-MW-AdServer-12-02-2026/client/src/index.css +244 -0
  99. package/Influence-MW-AdServer-12-02-2026/client/src/lib/queryClient.ts +57 -0
  100. package/Influence-MW-AdServer-12-02-2026/client/src/lib/utils.ts +39 -0
  101. package/Influence-MW-AdServer-12-02-2026/client/src/lib/venue-taxonomy.ts +532 -0
  102. package/Influence-MW-AdServer-12-02-2026/client/src/main.tsx +5 -0
  103. package/Influence-MW-AdServer-12-02-2026/client/src/pages/assign-creative.tsx +781 -0
  104. package/Influence-MW-AdServer-12-02-2026/client/src/pages/content-hub.tsx +995 -0
  105. package/Influence-MW-AdServer-12-02-2026/client/src/pages/custom-pois.tsx +431 -0
  106. package/Influence-MW-AdServer-12-02-2026/client/src/pages/dashboard.tsx +620 -0
  107. package/Influence-MW-AdServer-12-02-2026/client/src/pages/deal-detail.tsx +1062 -0
  108. package/Influence-MW-AdServer-12-02-2026/client/src/pages/deal-form.tsx +1570 -0
  109. package/Influence-MW-AdServer-12-02-2026/client/src/pages/deals.tsx +716 -0
  110. package/Influence-MW-AdServer-12-02-2026/client/src/pages/edit-creative-assignment.tsx +1051 -0
  111. package/Influence-MW-AdServer-12-02-2026/client/src/pages/geotargeting.tsx +675 -0
  112. package/Influence-MW-AdServer-12-02-2026/client/src/pages/integrations.tsx +425 -0
  113. package/Influence-MW-AdServer-12-02-2026/client/src/pages/line-item-creatives.tsx +622 -0
  114. package/Influence-MW-AdServer-12-02-2026/client/src/pages/line-item-form.tsx +3132 -0
  115. package/Influence-MW-AdServer-12-02-2026/client/src/pages/line-items.tsx +530 -0
  116. package/Influence-MW-AdServer-12-02-2026/client/src/pages/not-found.tsx +21 -0
  117. package/Influence-MW-AdServer-12-02-2026/client/src/pages/proof-of-play-upload.tsx +479 -0
  118. package/Influence-MW-AdServer-12-02-2026/client/src/pages/proof-of-play.tsx +880 -0
  119. package/Influence-MW-AdServer-12-02-2026/client/src/pages/reports.tsx +235 -0
  120. package/Influence-MW-AdServer-12-02-2026/client/src/pages/settings.tsx +652 -0
  121. package/Influence-MW-AdServer-12-02-2026/client/src/pages/signal-form.tsx +1117 -0
  122. package/Influence-MW-AdServer-12-02-2026/client/src/pages/signals.tsx +366 -0
  123. package/Influence-MW-AdServer-12-02-2026/client/src/pages/tags.tsx +332 -0
  124. package/Influence-MW-AdServer-12-02-2026/client/src/pages/venues.tsx +381 -0
  125. package/Influence-MW-AdServer-12-02-2026/client/src/types/mapbox-gl-draw.d.ts +37 -0
  126. package/Influence-MW-AdServer-12-02-2026/client/src/types/react-simple-maps.d.ts +57 -0
  127. package/Influence-MW-AdServer-12-02-2026/components.json +20 -0
  128. package/Influence-MW-AdServer-12-02-2026/docs/PRD.md +3373 -0
  129. package/Influence-MW-AdServer-12-02-2026/docs/influence-feature-mapping.csv +498 -0
  130. package/Influence-MW-AdServer-12-02-2026/drizzle.config.ts +14 -0
  131. package/Influence-MW-AdServer-12-02-2026/package-lock.json +9672 -0
  132. package/Influence-MW-AdServer-12-02-2026/package.json +118 -0
  133. package/Influence-MW-AdServer-12-02-2026/postcss.config.js +6 -0
  134. package/Influence-MW-AdServer-12-02-2026/replit.md +91 -0
  135. package/Influence-MW-AdServer-12-02-2026/script/build.ts +67 -0
  136. package/Influence-MW-AdServer-12-02-2026/scripts/create-miro-diagrams.cjs +318 -0
  137. package/Influence-MW-AdServer-12-02-2026/scripts/create-remaining-diagrams.cjs +270 -0
  138. package/Influence-MW-AdServer-12-02-2026/server/index.ts +103 -0
  139. package/Influence-MW-AdServer-12-02-2026/server/recommendation-service.ts +319 -0
  140. package/Influence-MW-AdServer-12-02-2026/server/routes.ts +1890 -0
  141. package/Influence-MW-AdServer-12-02-2026/server/static.ts +19 -0
  142. package/Influence-MW-AdServer-12-02-2026/server/storage.ts +2058 -0
  143. package/Influence-MW-AdServer-12-02-2026/server/vite.ts +58 -0
  144. package/Influence-MW-AdServer-12-02-2026/shared/schema.ts +1595 -0
  145. package/Influence-MW-AdServer-12-02-2026/tailwind.config.ts +107 -0
  146. package/Influence-MW-AdServer-12-02-2026/tsconfig.json +23 -0
  147. package/Influence-MW-AdServer-12-02-2026/vite.config.ts +40 -0
  148. package/LINE_ITEM_BUDGET_FIELD_MAPPING.md +178 -0
  149. package/PCM/.env.example +92 -0
  150. package/PCM/README.md +558 -0
  151. package/PCM/docs/TEST_CASES.md +422 -0
  152. package/PCM/index.js +106 -0
  153. package/PCM/package-lock.json +3282 -0
  154. package/PCM/package.json +32 -0
  155. package/PCM/replit.md +64 -0
  156. package/PCM/schema.sql +495 -0
  157. package/PCM/scripts/export-schema.js +183 -0
  158. package/PCM/scripts/seed-comprehensive.js +631 -0
  159. package/PCM/scripts/seed-production.js +477 -0
  160. package/PCM/src/config/db.js +56 -0
  161. package/PCM/src/config/swagger.js +5975 -0
  162. package/PCM/src/dto/EmailRequestDTO.js +166 -0
  163. package/PCM/src/middleware/errorHandler.js +52 -0
  164. package/PCM/src/middleware/logger.js +26 -0
  165. package/PCM/src/migrations/001_add_campaign_mode_fields.sql +36 -0
  166. package/PCM/src/migrations/002_create_deal_id_counters.sql +22 -0
  167. package/PCM/src/migrations/003_update_publishers_column.sql +15 -0
  168. package/PCM/src/migrations/004_add_direct_dealtype_and_advertiser.sql +5 -0
  169. package/PCM/src/migrations/005_add_programmatic_fields_and_update_enums.sql +31 -0
  170. package/PCM/src/migrations/006_add_line_item_programmatic_fields.sql +12 -0
  171. package/PCM/src/migrations/007_add_line_item_direct_fields.sql +15 -0
  172. package/PCM/src/migrations/008_add_inventory_fields.sql +45 -0
  173. package/PCM/src/migrations/009_move_inventory_fields_to_metadata.sql +32 -0
  174. package/PCM/src/migrations/010_add_draft_status_and_line_items_count.sql +23 -0
  175. package/PCM/src/migrations/011_add_planning_field.sql +21 -0
  176. package/PCM/src/migrations/012_fix_inventory_composite_pk.sql +17 -0
  177. package/PCM/src/migrations/013_make_external_id_optional.sql +3 -0
  178. package/PCM/src/migrations/014_create_change_history.sql +38 -0
  179. package/PCM/src/migrations/016_create_publisher_insertion_orders.sql +33 -0
  180. package/PCM/src/migrations/017_fix_line_item_id_fk_reference.sql +86 -0
  181. package/PCM/src/migrations/018_create_approval_tables.sql +44 -0
  182. package/PCM/src/migrations/019_add_encrypted_token_column.sql +2 -0
  183. package/PCM/src/migrations/020_add_rejection_reason_to_deals.sql +10 -0
  184. package/PCM/src/migrations/021_add_publisher_external_id_to_inventories.sql +12 -0
  185. package/PCM/src/migrations/022_add_line_item_extended_fields.sql +24 -0
  186. package/PCM/src/migrations/023_add_base_price_fields.sql +8 -0
  187. package/PCM/src/migrations/run-migrations.js +46 -0
  188. package/PCM/src/models/ApprovalOTP.js +51 -0
  189. package/PCM/src/models/ApprovalToken.js +79 -0
  190. package/PCM/src/models/ChangeHistory.js +107 -0
  191. package/PCM/src/models/Deal.js +186 -0
  192. package/PCM/src/models/DealIdCounter.js +28 -0
  193. package/PCM/src/models/LineItem.js +227 -0
  194. package/PCM/src/models/LineItemCreative.js +89 -0
  195. package/PCM/src/models/LineItemInventory.js +115 -0
  196. package/PCM/src/models/PublisherInsertionOrder.js +93 -0
  197. package/PCM/src/models/TransactionHistory.js +34 -0
  198. package/PCM/src/models/associations.js +81 -0
  199. package/PCM/src/routes/approval.js +321 -0
  200. package/PCM/src/routes/creatives.js +437 -0
  201. package/PCM/src/routes/deals.js +1638 -0
  202. package/PCM/src/routes/digitalSignage.js +242 -0
  203. package/PCM/src/routes/insertionOrders.js +380 -0
  204. package/PCM/src/routes/lineItems.js +926 -0
  205. package/PCM/src/routes/system.js +384 -0
  206. package/PCM/src/services/ApprovalService.js +885 -0
  207. package/PCM/src/services/CampaignImportConverter.js +631 -0
  208. package/PCM/src/services/CampaignModeService.js +273 -0
  209. package/PCM/src/services/CampaignStatusService.js +395 -0
  210. package/PCM/src/services/ChangeHistoryService.js +316 -0
  211. package/PCM/src/services/DealIdService.js +94 -0
  212. package/PCM/src/services/DealResponseFormatter.js +90 -0
  213. package/PCM/src/services/EmailNotificationService.js +315 -0
  214. package/PCM/src/services/LineItemResponseFormatter.js +122 -0
  215. package/PCM/src/services/LineItemStatusService.js +380 -0
  216. package/PCM/src/tests/comprehensiveTestRunner.js +360 -0
  217. package/PCM/src/tests/comprehensiveTests.js +1277 -0
  218. package/PCM/src/tests/dealTypeUnitTests.js +1058 -0
  219. package/PCM/src/tests/testRunner.js +248 -0
  220. package/PCM/src/utils/caseConverter.js +92 -0
  221. package/PCM/src/utils/dealCalculations.js +206 -0
  222. package/PCM/src/utils/lineItemPayloadNormalizer.js +41 -0
  223. package/PCM/src/utils/payloadNormalizer.js +34 -0
  224. package/PCM/src/utils/sourceNormalizer.js +56 -0
  225. package/PCM/src/validators/creativeValidator.js +27 -0
  226. package/PCM/src/validators/dealValidator.js +203 -0
  227. package/PCM/src/validators/lineItemValidator.js +489 -0
  228. package/PCM/tests/approval-flows.test.js +238 -0
  229. package/PCM/tests/approval-workflow.test.js +291 -0
  230. package/PCM/tests/campaign-import-converter.test.js +543 -0
  231. package/PCM/tests/campaign-import-e2e.test.js +520 -0
  232. package/PCM/tests/campaign-status.test.js +539 -0
  233. package/PCM/tests/direct-publisher-split-reimport.test.js +460 -0
  234. package/PCM/tests/e2e/digital-signage.test.js +145 -0
  235. package/PCM/tests/e2e/search-filter-pagination.test.js +399 -0
  236. package/PCM/tests/e2e-comprehensive.test.js +3446 -0
  237. package/PCM/tests/edge-cases.test.js +340 -0
  238. package/PCM/tests/line-item-status.test.js +340 -0
  239. package/PCM/tests/seller-account-external-ids.test.js +877 -0
  240. package/PCM/tests/source-validation.test.js +324 -0
  241. package/PRD.md +3373 -0
  242. package/README.md +186 -0
  243. package/client/index.html +35 -0
  244. package/client/public/DEMO_STATUS.md +579 -0
  245. package/client/public/img/MW-logo-trans_1754045676555.png +0 -0
  246. package/client/public/locales/ar/approval.json +144 -0
  247. package/client/public/locales/ar/buyer.json +61 -0
  248. package/client/public/locales/ar/campaigns.json +1 -0
  249. package/client/public/locales/ar/common.json +218 -0
  250. package/client/public/locales/ar/contentHub.json +266 -0
  251. package/client/public/locales/ar/creatives.json +79 -0
  252. package/client/public/locales/ar/dashboard.json +57 -0
  253. package/client/public/locales/ar/deals.json +886 -0
  254. package/client/public/locales/ar/dsp.json +131 -0
  255. package/client/public/locales/ar/inventory.json +201 -0
  256. package/client/public/locales/ar/lineItems.json +553 -0
  257. package/client/public/locales/ar/navigation.json +48 -0
  258. package/client/public/locales/ar/wizard.json +1 -0
  259. package/client/public/locales/en/approval.json +144 -0
  260. package/client/public/locales/en/buyer.json +65 -0
  261. package/client/public/locales/en/campaigns.json +1 -0
  262. package/client/public/locales/en/common.json +218 -0
  263. package/client/public/locales/en/contentHub.json +266 -0
  264. package/client/public/locales/en/creatives.json +79 -0
  265. package/client/public/locales/en/dashboard.json +57 -0
  266. package/client/public/locales/en/deals.json +886 -0
  267. package/client/public/locales/en/dsp.json +131 -0
  268. package/client/public/locales/en/inventory.json +201 -0
  269. package/client/public/locales/en/lineItems.json +659 -0
  270. package/client/public/locales/en/navigation.json +48 -0
  271. package/client/public/locales/en/wizard.json +1 -0
  272. package/client/public/locales/ja/approval.json +144 -0
  273. package/client/public/locales/ja/buyer.json +61 -0
  274. package/client/public/locales/ja/campaigns.json +1 -0
  275. package/client/public/locales/ja/common.json +218 -0
  276. package/client/public/locales/ja/contentHub.json +266 -0
  277. package/client/public/locales/ja/creatives.json +79 -0
  278. package/client/public/locales/ja/dashboard.json +57 -0
  279. package/client/public/locales/ja/deals.json +886 -0
  280. package/client/public/locales/ja/dsp.json +131 -0
  281. package/client/public/locales/ja/inventory.json +201 -0
  282. package/client/public/locales/ja/lineItems.json +553 -0
  283. package/client/public/locales/ja/navigation.json +48 -0
  284. package/client/public/locales/ja/wizard.json +1 -0
  285. package/client/public/locales/zh/approval.json +144 -0
  286. package/client/public/locales/zh/buyer.json +61 -0
  287. package/client/public/locales/zh/campaigns.json +1 -0
  288. package/client/public/locales/zh/common.json +218 -0
  289. package/client/public/locales/zh/contentHub.json +266 -0
  290. package/client/public/locales/zh/creatives.json +79 -0
  291. package/client/public/locales/zh/dashboard.json +57 -0
  292. package/client/public/locales/zh/deals.json +886 -0
  293. package/client/public/locales/zh/dsp.json +131 -0
  294. package/client/public/locales/zh/inventory.json +201 -0
  295. package/client/public/locales/zh/lineItems.json +553 -0
  296. package/client/public/locales/zh/navigation.json +48 -0
  297. package/client/public/locales/zh/wizard.json +1 -0
  298. package/client/public/manifest.json +36 -0
  299. package/client/src/App.tsx +464 -0
  300. package/client/src/components/app-sidebar.tsx +312 -0
  301. package/client/src/components/approval/approval-decision-form.test.tsx +294 -0
  302. package/client/src/components/approval/approval-decision-form.tsx +326 -0
  303. package/client/src/components/approval/approval-sheet.tsx +631 -0
  304. package/client/src/components/approval/line-item-details-sheet.tsx +371 -0
  305. package/client/src/components/approval/otp-verification.test.tsx +337 -0
  306. package/client/src/components/approval/otp-verification.tsx +180 -0
  307. package/client/src/components/content-hub/bulk-transcode-dialog.tsx +379 -0
  308. package/client/src/components/content-hub/content-hub-manager-v2.tsx +574 -0
  309. package/client/src/components/content-hub/content-hub-manager.tsx +330 -0
  310. package/client/src/components/content-hub/creative-card.tsx +456 -0
  311. package/client/src/components/content-hub/creative-detail-sheet.tsx +685 -0
  312. package/client/src/components/content-hub/creative-filters.tsx +457 -0
  313. package/client/src/components/content-hub/creative-grid.tsx +329 -0
  314. package/client/src/components/content-hub/creative-selector.tsx +415 -0
  315. package/client/src/components/content-hub/creative-upload.tsx +547 -0
  316. package/client/src/components/content-hub/folder-dialogs.tsx +445 -0
  317. package/client/src/components/content-hub/folder-list.tsx +280 -0
  318. package/client/src/components/content-hub/review-dialogs.tsx +268 -0
  319. package/client/src/components/content-hub/transcode-dialog.tsx +226 -0
  320. package/client/src/components/creative-library/creative-details-view.tsx +446 -0
  321. package/client/src/components/creative-library/creative-filters-panel.tsx +203 -0
  322. package/client/src/components/creative-library/creative-list.tsx +360 -0
  323. package/client/src/components/creative-library/creative-status-badge.tsx +71 -0
  324. package/client/src/components/creative-library/folder-card.tsx +78 -0
  325. package/client/src/components/creative-library/index.ts +27 -0
  326. package/client/src/components/creative-library/new-creative-card.tsx +211 -0
  327. package/client/src/components/creative-library/upload-creative-dialog.tsx +261 -0
  328. package/client/src/components/dashboard-overview.tsx +109 -0
  329. package/client/src/components/deals/approval-history-panel.test.tsx +240 -0
  330. package/client/src/components/deals/approval-history-panel.tsx +156 -0
  331. package/client/src/components/deals/deal-status-badge.tsx +92 -0
  332. package/client/src/components/deals/import-from-planner-dialog.tsx +399 -0
  333. package/client/src/components/deals/market-insights-panel.tsx +237 -0
  334. package/client/src/components/deals/reopen-deal-sheet.tsx +191 -0
  335. package/client/src/components/deals/request-approval-sheet.test.tsx +323 -0
  336. package/client/src/components/deals/request-approval-sheet.tsx +136 -0
  337. package/client/src/components/deals/resend-approval-sheet.tsx +201 -0
  338. package/client/src/components/direct-campaigns/campaign-card.tsx +283 -0
  339. package/client/src/components/direct-campaigns/deal-filter-panel.tsx +325 -0
  340. package/client/src/components/inventory/advanced-filters-panel.tsx +273 -0
  341. package/client/src/components/inventory/csv-upload-modal.tsx +639 -0
  342. package/client/src/components/inventory/inventory-availability-view.tsx +486 -0
  343. package/client/src/components/inventory/inventory-details-sheet.tsx +376 -0
  344. package/client/src/components/inventory/inventory-map-view.tsx +596 -0
  345. package/client/src/components/inventory/inventory-settings-menu.tsx +52 -0
  346. package/client/src/components/language-switcher.tsx +53 -0
  347. package/client/src/components/line-items/campaign-forecast-panel.tsx +138 -0
  348. package/client/src/components/line-items/form-insights.tsx +89 -0
  349. package/client/src/components/line-items/geofencing/LocationCsvUploadDrawer.tsx +100 -0
  350. package/client/src/components/line-items/geofencing/POIDropdown.tsx +379 -0
  351. package/client/src/components/line-items/geofencing/SelectedLocationsSidebar.tsx +436 -0
  352. package/client/src/components/line-items/geofencing/ViewFileLocationDrawer.tsx +199 -0
  353. package/client/src/components/line-items/geofencing/components/ExistingFilesTab.tsx +268 -0
  354. package/client/src/components/line-items/geofencing/components/TemplateDownloadSection.tsx +59 -0
  355. package/client/src/components/line-items/geofencing/components/UploadTab.tsx +215 -0
  356. package/client/src/components/line-items/geofencing-map.tsx +1270 -0
  357. package/client/src/components/line-items/inventory-availability-section.tsx +178 -0
  358. package/client/src/components/line-items/line-item-schedule-manager.tsx +313 -0
  359. package/client/src/components/line-items/manual-inventory-drawer.tsx +346 -0
  360. package/client/src/components/line-items/planner-inventory-card.tsx +495 -0
  361. package/client/src/components/line-items/planner-schedule-grid.tsx +495 -0
  362. package/client/src/components/line-items/schedule-rule-editor.tsx +649 -0
  363. package/client/src/components/line-items/schedule-rule-types.ts +122 -0
  364. package/client/src/components/line-items/steps/creatives-step.tsx +681 -0
  365. package/client/src/components/line-items/steps/inventory-schedule-step.tsx +1596 -0
  366. package/client/src/components/line-items/steps/inventory-step.tsx +1533 -0
  367. package/client/src/components/line-items/steps/line-item-details-step.tsx +916 -0
  368. package/client/src/components/line-items/steps/schedule-step.tsx +273 -0
  369. package/client/src/components/line-items/steps/summary-step.tsx +680 -0
  370. package/client/src/components/line-items/steps/targeting-step.tsx +1708 -0
  371. package/client/src/components/product-switcher.tsx +105 -0
  372. package/client/src/components/protected-route.tsx +49 -0
  373. package/client/src/components/skip-link.tsx +22 -0
  374. package/client/src/components/stat-card.tsx +53 -0
  375. package/client/src/components/status-badge.tsx +96 -0
  376. package/client/src/components/ui/hierarchical-venue-selector.tsx +389 -0
  377. package/client/src/components/ui/toaster.tsx +111 -0
  378. package/client/src/contexts/auth-context.tsx +181 -0
  379. package/client/src/contexts/sidebar-state.tsx +50 -0
  380. package/client/src/contexts/theme-context.tsx +66 -0
  381. package/client/src/data/campaign-data.json +107 -0
  382. package/client/src/data/countries.json +22 -0
  383. package/client/src/hooks/use-approval.ts +366 -0
  384. package/client/src/hooks/use-keyboard-shortcuts.ts +74 -0
  385. package/client/src/hooks/use-media-query.ts +46 -0
  386. package/client/src/hooks/use-mobile.tsx +19 -0
  387. package/client/src/hooks/use-page-title.ts +21 -0
  388. package/client/src/hooks/use-toast.ts +195 -0
  389. package/client/src/index.css +694 -0
  390. package/client/src/lib/__tests__/accessibility.test.ts +104 -0
  391. package/client/src/lib/__tests__/date-utils.test.ts +199 -0
  392. package/client/src/lib/__tests__/dsp-buyer-api.test.ts +127 -0
  393. package/client/src/lib/__tests__/dsp-buyer-integration.test.ts +247 -0
  394. package/client/src/lib/__tests__/storage-utils.test.ts +167 -0
  395. package/client/src/lib/__tests__/utils.test.ts +57 -0
  396. package/client/src/lib/accessibility.ts +141 -0
  397. package/client/src/lib/api-config.ts +9 -0
  398. package/client/src/lib/auth-service.ts +209 -0
  399. package/client/src/lib/campaign-creative-api.ts +82 -0
  400. package/client/src/lib/company-api.ts +61 -0
  401. package/client/src/lib/content-hub-api.ts +407 -0
  402. package/client/src/lib/creative-mapper.ts +61 -0
  403. package/client/src/lib/date-utils.ts +119 -0
  404. package/client/src/lib/deal-helpers.ts +220 -0
  405. package/client/src/lib/dsp-buyer-api.ts +196 -0
  406. package/client/src/lib/geo-import-api.ts +151 -0
  407. package/client/src/lib/google-poi-api.ts +305 -0
  408. package/client/src/lib/i18n/__tests__/formatting.test.ts +202 -0
  409. package/client/src/lib/i18n/formatting.ts +130 -0
  410. package/client/src/lib/i18n/index.ts +8 -0
  411. package/client/src/lib/i18n-compat.ts +76 -0
  412. package/client/src/lib/influence-deals-api.ts +896 -0
  413. package/client/src/lib/inventory-api.ts +399 -0
  414. package/client/src/lib/oauth-service.ts +678 -0
  415. package/client/src/lib/poi-types.ts +75 -0
  416. package/client/src/lib/queryClient.ts +144 -0
  417. package/client/src/lib/recommendation-api.ts +380 -0
  418. package/client/src/lib/storage-utils.ts +104 -0
  419. package/client/src/lib/tolgee.ts +85 -0
  420. package/client/src/lib/utils.ts +0 -0
  421. package/client/src/main.tsx +67 -0
  422. package/client/src/mapbox-draw-modes.d.ts +32 -0
  423. package/client/src/pages/all-folders.tsx +203 -0
  424. package/client/src/pages/auth-callback.tsx +115 -0
  425. package/client/src/pages/buyer-form.tsx +339 -0
  426. package/client/src/pages/buyer-list.tsx +622 -0
  427. package/client/src/pages/content-hub.tsx +1358 -0
  428. package/client/src/pages/create-deal.tsx +2093 -0
  429. package/client/src/pages/creative-assignment-page.tsx +548 -0
  430. package/client/src/pages/creatives.tsx +5 -0
  431. package/client/src/pages/custom-pois.tsx +425 -0
  432. package/client/src/pages/dashboard.tsx +615 -0
  433. package/client/src/pages/deal-history.tsx +434 -0
  434. package/client/src/pages/deal-line-items.tsx +1703 -0
  435. package/client/src/pages/demo-status.tsx +113 -0
  436. package/client/src/pages/direct-campaign-details.tsx +361 -0
  437. package/client/src/pages/direct-campaigns-new.tsx +824 -0
  438. package/client/src/pages/dsp-form.tsx +803 -0
  439. package/client/src/pages/dsp-list.tsx +239 -0
  440. package/client/src/pages/folder-content.tsx +336 -0
  441. package/client/src/pages/integrations.tsx +429 -0
  442. package/client/src/pages/line-item-creatives.tsx +789 -0
  443. package/client/src/pages/line-item-detail-page.tsx +684 -0
  444. package/client/src/pages/line-item-form-page.tsx +3261 -0
  445. package/client/src/pages/line-item-wizard.tsx +1207 -0
  446. package/client/src/pages/login.tsx +154 -0
  447. package/client/src/pages/not-found.tsx +23 -0
  448. package/client/src/pages/proof-of-play.tsx +397 -0
  449. package/client/src/pages/public-approval.tsx +551 -0
  450. package/client/src/pages/reports.tsx +231 -0
  451. package/client/src/pages/settings.tsx +760 -0
  452. package/client/src/pages/signals.tsx +389 -0
  453. package/client/src/pages/tags.tsx +318 -0
  454. package/client/src/pages/test-results.tsx +328 -0
  455. package/client/src/store/hooks.ts +5 -0
  456. package/client/src/store/index.ts +15 -0
  457. package/client/src/store/mapMarkerLocationsSlice.ts +241 -0
  458. package/client/src/styles/design-tokens.css +324 -0
  459. package/client/src/test/setup.ts +261 -0
  460. package/client/src/test/test-utils.tsx +40 -0
  461. package/client/src/types/approval.ts +221 -0
  462. package/client/src/types/content-hub.ts +209 -0
  463. package/client/src/types/geofencing.ts +67 -0
  464. package/client/src/types/transcoding.ts +140 -0
  465. package/client/src/vite-env.d.ts +18 -0
  466. package/components.json +20 -0
  467. package/creative-api.json +1 -0
  468. package/docs/AI_REFERENCE.md +459 -0
  469. package/docs/MWDesign-Prompt.md +132 -0
  470. package/docs/MWDesign-System.md +344 -0
  471. package/docs/test-plan.md +277 -0
  472. package/e2e/AUTONOMOUS-TESTING.md +406 -0
  473. package/e2e/README.md +219 -0
  474. package/e2e/autonomous-flow.spec.ts +308 -0
  475. package/e2e/debug-sso.spec.ts +163 -0
  476. package/e2e/direct-campaigns.spec.ts +219 -0
  477. package/e2e/explore-sso.spec.ts +149 -0
  478. package/e2e/fixtures/auth.ts +26 -0
  479. package/e2e/fixtures/enhanced-test.ts +331 -0
  480. package/e2e/pagination.spec.ts +280 -0
  481. package/e2e/view-toggle.spec.ts +312 -0
  482. package/generated-icon.png +0 -0
  483. package/i18next-scanner.config.cjs +46 -0
  484. package/package.json +141 -0
  485. package/playwright.config.ts +93 -0
  486. package/postcss.config.js +6 -0
  487. package/replit.md +196 -0
  488. package/screenshot-after-login.png +0 -0
  489. package/screenshot-contenthub-grid.png +0 -0
  490. package/screenshot-contenthub-list-fixed.png +0 -0
  491. package/screenshot-contenthub-list.png +0 -0
  492. package/screenshot-create-deal.png +0 -0
  493. package/screenshot-dashboard.png +0 -0
  494. package/screenshot-deals.png +0 -0
  495. package/screenshot-login-filled.png +0 -0
  496. package/screenshot-login.png +0 -0
  497. package/screenshot.mjs +24 -0
  498. package/scripts/deploy-stg.sh +185 -0
  499. package/shared/direct-io-schema.ts +383 -0
  500. package/shared/schema.ts +439 -0
  501. package/shared/screen-types.ts +149 -0
  502. package/springdocDefault.json +1 -0
  503. package/swagger-ui-bundle.js +2 -0
  504. package/swagger-ui-init.js +10316 -0
  505. package/tailwind.config.ts +282 -0
  506. package/terraform/README.md +306 -0
  507. package/terraform/cloudfront.tf +289 -0
  508. package/terraform/ecs.tf +727 -0
  509. package/terraform/environments/dev.tfvars +59 -0
  510. package/terraform/environments/production.tfvars +60 -0
  511. package/terraform/main.tf +47 -0
  512. package/terraform/outputs.tf +145 -0
  513. package/terraform/s3.tf +192 -0
  514. package/terraform/variables.tf +226 -0
  515. package/terraform/waf.tf +165 -0
  516. package/terraform-frontend/.terraform.lock.hcl +25 -0
  517. package/terraform-frontend/README.md +85 -0
  518. package/terraform-frontend/cloudfront.tf +125 -0
  519. package/terraform-frontend/main.tf +31 -0
  520. package/terraform-frontend/outputs.tf +24 -0
  521. package/terraform-frontend/terraform.tfvars +12 -0
  522. package/terraform-frontend/variables.tf +53 -0
  523. package/tsconfig.json +23 -0
  524. package/vite.config.ts +226 -0
  525. package/vitest.config.ts +56 -0
@@ -0,0 +1,631 @@
1
+ import sequelize from '../src/config/db.js';
2
+ import Deal from '../src/models/Deal.js';
3
+ import LineItem from '../src/models/LineItem.js';
4
+ import LineItemInventory from '../src/models/LineItemInventory.js';
5
+ import LineItemCreative from '../src/models/LineItemCreative.js';
6
+ import { calculateDealMetrics } from '../src/utils/dealCalculations.js';
7
+ import { setupAssociations } from '../src/models/associations.js';
8
+
9
+ setupAssociations();
10
+
11
+ const SOURCE = 'influence';
12
+
13
+ const CAMPAIGNS = [
14
+ { advertiser: { id: 'adv-coca-cola', name: 'Coca-Cola Company' }, brand: 'Coca-Cola Zero Sugar', category: 'Beverages' },
15
+ { advertiser: { id: 'adv-nike', name: 'Nike Inc.' }, brand: 'Nike Air Max 2025', category: 'Sportswear' },
16
+ { advertiser: { id: 'adv-mcdonalds', name: "McDonald's Corporation" }, brand: "McDonald's McFlurry", category: 'Quick Service Restaurant' },
17
+ { advertiser: { id: 'adv-samsung', name: 'Samsung Electronics' }, brand: 'Samsung Galaxy S25', category: 'Consumer Electronics' },
18
+ { advertiser: { id: 'adv-amazon', name: 'Amazon.com Inc.' }, brand: 'Amazon Prime Video', category: 'Streaming Services' },
19
+ { advertiser: { id: 'adv-toyota', name: 'Toyota Motor Corporation' }, brand: 'Toyota Camry 2025', category: 'Automotive' },
20
+ { advertiser: { id: 'adv-apple', name: 'Apple Inc.' }, brand: 'iPhone 17 Pro', category: 'Consumer Electronics' },
21
+ { advertiser: { id: 'adv-pepsi', name: 'PepsiCo Inc.' }, brand: 'Pepsi Max Zero', category: 'Beverages' },
22
+ { advertiser: { id: 'adv-adidas', name: 'Adidas AG' }, brand: 'Adidas Ultraboost 2025', category: 'Sportswear' },
23
+ { advertiser: { id: 'adv-bmw', name: 'BMW Group' }, brand: 'BMW iX Electric SUV', category: 'Automotive' },
24
+ { advertiser: { id: 'adv-netflix', name: 'Netflix Inc.' }, brand: 'Netflix Originals 2025', category: 'Streaming Services' },
25
+ { advertiser: { id: 'adv-starbucks', name: 'Starbucks Corporation' }, brand: 'Starbucks Summer Blend', category: 'Quick Service Restaurant' },
26
+ { advertiser: { id: 'adv-loreal', name: "L'Oréal S.A." }, brand: "L'Oréal Paris Revitalift", category: 'Beauty & Personal Care' },
27
+ { advertiser: { id: 'adv-microsoft', name: 'Microsoft Corporation' }, brand: 'Microsoft Surface Pro 11', category: 'Consumer Electronics' },
28
+ { advertiser: { id: 'adv-honda', name: 'Honda Motor Co.' }, brand: 'Honda Accord Hybrid', category: 'Automotive' },
29
+ { advertiser: { id: 'adv-spotify', name: 'Spotify Technology' }, brand: 'Spotify Premium Family', category: 'Streaming Services' },
30
+ { advertiser: { id: 'adv-unilever', name: 'Unilever PLC' }, brand: 'Dove Deep Moisture', category: 'Beauty & Personal Care' },
31
+ { advertiser: { id: 'adv-intel', name: 'Intel Corporation' }, brand: 'Intel Core Ultra 300', category: 'Technology' },
32
+ { advertiser: { id: 'adv-verizon', name: 'Verizon Communications' }, brand: 'Verizon 5G Ultra', category: 'Telecommunications' },
33
+ { advertiser: { id: 'adv-disney', name: 'The Walt Disney Company' }, brand: 'Disney+ Premium', category: 'Streaming Services' }
34
+ ];
35
+
36
+ const SELLERS = [
37
+ { id: 'seller-vgi', name: 'VGI Global Media' },
38
+ { id: 'seller-hivestack', name: 'Hivestack' },
39
+ { id: 'seller-vistar', name: 'Vistar Media' },
40
+ { id: 'seller-broadsign', name: 'Broadsign' }
41
+ ];
42
+
43
+ const BUYERS = [
44
+ { id: 'buyer-ttd', name: 'The Trade Desk', dsp: 'TTD', seatId: 'SEAT-TTD-001' },
45
+ { id: 'buyer-dv360', name: 'Google DV360', dsp: 'DV360', seatId: 'SEAT-DV360-001' },
46
+ { id: 'buyer-xandr', name: 'Xandr', dsp: 'XANDR', seatId: 'SEAT-XANDR-001' },
47
+ { id: 'buyer-mediamath', name: 'MediaMath', dsp: 'MEDIAMATH', seatId: 'SEAT-MM-001' },
48
+ { id: 'buyer-yahoo', name: 'Yahoo DSP', dsp: 'YAHOO', seatId: 'SEAT-YAHOO-001' },
49
+ { id: 'buyer-amazon', name: 'Amazon DSP', dsp: 'AMAZON', seatId: 'SEAT-AMZN-001' }
50
+ ];
51
+
52
+ const PUBLISHERS = [
53
+ { id: 'pub-clear-channel', name: 'Clear Channel Outdoor', domain: 'clearchannel.com' },
54
+ { id: 'pub-jcdecaux', name: 'JCDecaux', domain: 'jcdecaux.com' },
55
+ { id: 'pub-lamar', name: 'Lamar Advertising', domain: 'lamar.com' },
56
+ { id: 'pub-outfront', name: 'Outfront Media', domain: 'outfrontmedia.com' },
57
+ { id: 'pub-stroer', name: 'Ströer SE', domain: 'stroeer.com' }
58
+ ];
59
+
60
+ const LOCATIONS = [
61
+ { name: 'Times Square NYC', lat: 40.758896, lng: -73.985130, city: 'New York', country: 'US', iso3: 'USA', tz: 'America/New_York', venue: 'RETAIL' },
62
+ { name: 'Piccadilly Circus', lat: 51.510067, lng: -0.133869, city: 'London', country: 'GB', iso3: 'GBR', tz: 'Europe/London', venue: 'OUTDOOR' },
63
+ { name: 'Shibuya Crossing', lat: 35.659517, lng: 139.700571, city: 'Tokyo', country: 'JP', iso3: 'JPN', tz: 'Asia/Tokyo', venue: 'TRANSIT' },
64
+ { name: 'Champs-Élysées', lat: 48.869966, lng: 2.307845, city: 'Paris', country: 'FR', iso3: 'FRA', tz: 'Europe/Paris', venue: 'OUTDOOR' },
65
+ { name: 'Las Vegas Strip', lat: 36.114647, lng: -115.172813, city: 'Las Vegas', country: 'US', iso3: 'USA', tz: 'America/Los_Angeles', venue: 'ENTERTAINMENT' },
66
+ { name: 'Dubai Mall', lat: 25.197525, lng: 55.279457, city: 'Dubai', country: 'AE', iso3: 'ARE', tz: 'Asia/Dubai', venue: 'RETAIL' },
67
+ { name: 'Sydney Opera House', lat: -33.856784, lng: 151.215297, city: 'Sydney', country: 'AU', iso3: 'AUS', tz: 'Australia/Sydney', venue: 'OUTDOOR' },
68
+ { name: 'Berlin Hauptbahnhof', lat: 52.525084, lng: 13.369402, city: 'Berlin', country: 'DE', iso3: 'DEU', tz: 'Europe/Berlin', venue: 'TRANSIT' },
69
+ { name: 'Toronto Union Station', lat: 43.645348, lng: -79.380718, city: 'Toronto', country: 'CA', iso3: 'CAN', tz: 'America/Toronto', venue: 'TRANSIT' },
70
+ { name: 'Singapore Orchard Road', lat: 1.304833, lng: 103.831833, city: 'Singapore', country: 'SG', iso3: 'SGP', tz: 'Asia/Singapore', venue: 'RETAIL' }
71
+ ];
72
+
73
+ const RESOLUTIONS = {
74
+ landscape: ['1920x1080', '3840x2160', '1280x720'],
75
+ portrait: ['1080x1920', '2160x3840'],
76
+ square: ['1080x1080']
77
+ };
78
+
79
+ const DEAL_CONFIGS = [
80
+ { dealType: 'GUARANTEED', mode: 'PROGRAMMATIC', prefix: 'GD', auctionType: 3, costTypes: ['CPM', 'CPD'] },
81
+ { dealType: 'PREFERRED_DEAL', mode: 'PROGRAMMATIC', prefix: 'PD', auctionType: 3, costTypes: ['CPM', 'CPD', 'CPS'] },
82
+ { dealType: 'PRIVATE_AUCTION', mode: 'PROGRAMMATIC', prefix: 'PA', auctionType: 1, costTypes: ['CPM', 'CPD', 'CPS'] },
83
+ { dealType: 'EVERGREEN_PMP', mode: 'PROGRAMMATIC', prefix: 'EG', auctionType: 2, costTypes: ['CPM', 'CPD', 'CPS'] },
84
+ { dealType: 'DIRECT', mode: 'DIRECT', prefix: 'DIR', auctionType: 0, costTypes: ['CPD'] }
85
+ ];
86
+
87
+ const STATUSES = {
88
+ editable: ['DRAFT', 'PENDING'],
89
+ approved: ['APPROVED', 'LIVE'],
90
+ completed: ['COMPLETED', 'ARCHIVED']
91
+ };
92
+
93
+ function randomItem(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
94
+ function randomItems(arr, count) { return [...arr].sort(() => 0.5 - Math.random()).slice(0, count); }
95
+ function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
96
+ function randomFloat(min, max, dec = 2) { return parseFloat((Math.random() * (max - min) + min).toFixed(dec)); }
97
+
98
+ function generateMongoObjectId() {
99
+ const timestamp = Math.floor(Date.now() / 1000).toString(16).padStart(8, '0');
100
+ const machineId = Math.floor(Math.random() * 16777216).toString(16).padStart(6, '0');
101
+ const processId = Math.floor(Math.random() * 65536).toString(16).padStart(4, '0');
102
+ const counter = Math.floor(Math.random() * 16777216).toString(16).padStart(6, '0');
103
+ return timestamp + machineId + processId + counter;
104
+ }
105
+
106
+ function generateDate(daysFromNow) {
107
+ const date = new Date();
108
+ date.setDate(date.getDate() + daysFromNow);
109
+ return date.toISOString().split('T')[0];
110
+ }
111
+
112
+ function generateScheduleArray(lineItemIndex, startDate, endDate) {
113
+ const schedules = [];
114
+ schedules.push({
115
+ type: 'DEFAULT',
116
+ validity: { startDate, endDate },
117
+ hours: [{ start: 8, end: 20 }]
118
+ });
119
+ schedules.push({
120
+ type: 'WEEKDAY',
121
+ validity: { startDate, endDate },
122
+ hours: [{ start: 7, end: 21 }]
123
+ });
124
+ schedules.push({
125
+ type: 'WEEKEND',
126
+ validity: { startDate, endDate },
127
+ hours: [{ start: 10, end: 22 }]
128
+ });
129
+ if (lineItemIndex % 3 === 0) {
130
+ schedules.push({
131
+ type: 'CUSTOM',
132
+ date: generateDate(randomInt(5, 30)),
133
+ validity: { startDate, endDate },
134
+ hours: [{ start: 9, end: 12 }, { start: 18, end: 21 }]
135
+ });
136
+ }
137
+ return schedules;
138
+ }
139
+
140
+ function generateInventoryPlanning(invIndex) {
141
+ const campaignDays = randomInt(14, 60);
142
+ const availableSlots = randomInt(50000, 200000);
143
+ const availablePlayTime = availableSlots * randomInt(10, 20);
144
+ const maxImpressions = randomInt(2000000, 20000000);
145
+ const allocatedSlots = Math.floor(availableSlots * randomFloat(0.01, 0.05));
146
+ const allocatedPlayTime = Math.floor(availablePlayTime * randomFloat(0.01, 0.05));
147
+ const sov = parseFloat((allocatedSlots / availableSlots).toFixed(5));
148
+ const sot = parseFloat((allocatedPlayTime / availablePlayTime).toFixed(5));
149
+ const impressions = randomInt(100000, 2000000);
150
+ const reach = Math.floor(impressions / randomFloat(3, 6));
151
+ const frequency = parseFloat((impressions / reach).toFixed(2));
152
+ const estimatedCost = randomFloat(10000, 100000);
153
+ const cpm = parseFloat(((estimatedCost / impressions) * 1000).toFixed(2));
154
+ return {
155
+ capacity: {
156
+ campaign_days: campaignDays,
157
+ available: { slots: availableSlots, play_time_sec: availablePlayTime, max_impressions: maxImpressions }
158
+ },
159
+ allocation: { slots: allocatedSlots, play_time_sec: allocatedPlayTime, sov, sot },
160
+ estimates: { impressions, reach, frequency },
161
+ pricing: { cpm, estimated_cost: estimatedCost }
162
+ };
163
+ }
164
+
165
+ async function cleanDatabase() {
166
+ console.log('Cleaning database...');
167
+ await LineItemCreative.destroy({ where: {}, truncate: true, cascade: true });
168
+ await LineItemInventory.destroy({ where: {}, truncate: true, cascade: true });
169
+ await LineItem.destroy({ where: {}, truncate: true, cascade: true });
170
+ await Deal.destroy({ where: {}, truncate: true, cascade: true });
171
+ console.log('Database cleaned successfully.\n');
172
+ }
173
+
174
+ async function seed() {
175
+ console.log('=== DOOH AdServer Comprehensive Seed ===\n');
176
+ console.log(`Default Source: ${SOURCE}\n`);
177
+
178
+ await cleanDatabase();
179
+
180
+ const deals = [];
181
+ const lineItems = [];
182
+ const inventories = [];
183
+ const creatives = [];
184
+
185
+ let dealIndex = 1;
186
+ let lineItemIndex = 1;
187
+ let inventoryIndex = 1;
188
+ let creativeIndex = 1;
189
+
190
+ for (let i = 0; i < CAMPAIGNS.length; i++) {
191
+ const campaign = CAMPAIGNS[i];
192
+ const configIndex = i % DEAL_CONFIGS.length;
193
+ const config = DEAL_CONFIGS[configIndex];
194
+ const seller = SELLERS[i % SELLERS.length];
195
+ const location = LOCATIONS[i % LOCATIONS.length];
196
+ const currency = ['US', 'CA'].includes(location.country) ? 'USD' : location.country === 'GB' ? 'GBP' : location.country === 'JP' ? 'JPY' : location.country === 'AE' ? 'AED' : location.country === 'AU' ? 'AUD' : location.country === 'SG' ? 'SGD' : 'EUR';
197
+
198
+ let status;
199
+ if (i < 4) status = STATUSES.editable[i % STATUSES.editable.length];
200
+ else if (i < 14) status = STATUSES.approved[(i - 4) % STATUSES.approved.length];
201
+ else status = STATUSES.completed[(i - 14) % STATUSES.completed.length];
202
+
203
+ const startDays = ['COMPLETED', 'ARCHIVED'].includes(status) ? -90 : status === 'LIVE' ? -5 : randomInt(5, 30);
204
+ const endDays = ['COMPLETED', 'ARCHIVED'].includes(status) ? -10 : startDays + randomInt(30, 120);
205
+
206
+ const dealId = `${config.prefix}-${SOURCE.toUpperCase()}-${String(dealIndex).padStart(5, '0')}-${String(randomInt(10000, 99999))}`;
207
+
208
+ const dealData = {
209
+ id: crypto.randomUUID(),
210
+ deal_id: dealId,
211
+ external_id: `${SOURCE}-deal-${dealIndex}`,
212
+ source: SOURCE,
213
+ name: `${campaign.brand} - ${config.dealType.replace(/_/g, ' ')} Campaign Q1 2025`,
214
+ deal_type: config.dealType,
215
+ mode: config.mode,
216
+ advertiser: campaign.advertiser,
217
+ seller: seller,
218
+ currency: currency,
219
+ timezone_id: location.tz,
220
+ start_date: generateDate(startDays),
221
+ end_date: generateDate(endDays),
222
+ status: status,
223
+ active: true,
224
+ is_playing: ['APPROVED', 'LIVE'].includes(status),
225
+ account: { id: `acc-${campaign.advertiser.id}`, name: campaign.advertiser.name },
226
+ country: location.city,
227
+ metadata: { category: campaign.category, source: SOURCE, version: '2.0', createdBy: 'seed-script' },
228
+ version: 1,
229
+ publishers: []
230
+ };
231
+
232
+ if (config.mode === 'PROGRAMMATIC') {
233
+ const costType = config.costTypes[i % config.costTypes.length];
234
+ dealData.buyers = randomItems(BUYERS, randomInt(1, 3));
235
+ dealData.auction_type = config.auctionType;
236
+ dealData.transaction_type = i % 2 === 0 ? 'SPOT' : 'AUDIENCE';
237
+ dealData.cost_type = costType;
238
+ dealData.delivery_goal = randomInt(500000, 5000000);
239
+ dealData.net_cost = randomFloat(10000, 250000);
240
+ dealData.net_cost_per_day = randomFloat(500, 5000);
241
+ dealData.impressions = randomInt(100000, 2000000);
242
+ dealData.published = status !== 'REQUESTED';
243
+ dealData.reopened = false;
244
+ dealData.non_billable = false;
245
+ dealData.stop_bid_requests = false;
246
+ dealData.imp_multiplier_type = 'MAD';
247
+ } else {
248
+ dealData.brand = campaign.brand;
249
+ dealData.client_type = i % 2 === 0 ? 'DIRECT_ADVERTISER' : 'AGENCY';
250
+ dealData.approval_emails = [`approvals@${campaign.advertiser.id.replace('adv-', '')}.com`, `media@${campaign.advertiser.id.replace('adv-', '')}.com`];
251
+ dealData.market_selection = { country: location.country, currency: currency, region: location.city };
252
+ dealData.budget_setup = { currency: currency, budgetAmount: randomFloat(50000, 500000), budgetType: 'FIXED' };
253
+ dealData.campaign_goal = { type: ['IMPRESSIONS', 'REACH', 'SHARE_OF_VOICE'][i % 3], targetValue: randomInt(500000, 5000000) };
254
+ }
255
+
256
+ deals.push(dealData);
257
+
258
+ const dealPublishersMap = new Map();
259
+ const creativeTypes = ['DISPLAY', 'VIDEO', 'AUDIO'];
260
+ const numLineItems = randomInt(2, 4);
261
+
262
+ for (let li = 0; li < numLineItems; li++) {
263
+ const creativeType = creativeTypes[li % creativeTypes.length];
264
+ const publisher = PUBLISHERS[(i + li) % PUBLISHERS.length];
265
+ const resolutionSet = creativeType === 'AUDIO' ? ['1920x1080'] :
266
+ creativeType === 'VIDEO' ? randomItems(RESOLUTIONS.landscape, 2) :
267
+ [...randomItems(RESOLUTIONS.landscape, 1), ...randomItems(RESOLUTIONS.portrait, 1)];
268
+
269
+ const liStartDays = startDays + randomInt(0, 5);
270
+ const liEndDays = liStartDays + randomInt(20, 60);
271
+
272
+ const lineItemData = {
273
+ id: crypto.randomUUID(),
274
+ source: SOURCE,
275
+ external_id: `${SOURCE}-li-${lineItemIndex}`,
276
+ deal_id: dealId,
277
+ name: `${campaign.brand} - ${creativeType} ${location.venue} Placement ${li + 1}`,
278
+ deal_type: config.dealType,
279
+ mode: config.mode,
280
+ creative_type: creativeType,
281
+ timezone_id: location.tz,
282
+ start_date: generateDate(liStartDays),
283
+ end_date: generateDate(liEndDays),
284
+ resolutions: resolutionSet,
285
+ creative_source: li % 2 === 0 ? 'PUBLISHER' : 'ADVERTISER',
286
+ total_inventories: 0,
287
+ schedule: generateScheduleArray(lineItemIndex, generateDate(liStartDays), generateDate(liEndDays)),
288
+ publisher_id: publisher.id,
289
+ status: status,
290
+ is_playing: ['APPROVED', 'LIVE'].includes(status),
291
+ metadata: { placement: location.venue, category: campaign.category, source: SOURCE },
292
+ version: 1
293
+ };
294
+
295
+ const pacingTypes = ['even', 'asap', 'front-loaded', 'back-loaded'];
296
+
297
+ if (config.mode === 'PROGRAMMATIC') {
298
+ lineItemData.auction_type = config.auctionType;
299
+ lineItemData.duration = creativeType !== 'DISPLAY' ? randomInt(15, 30) : 0;
300
+ lineItemData.impression_type = li % 2 === 0 ? 'SPOT' : 'AUDIENCE';
301
+ lineItemData.threshold_count_per_day = randomInt(5000, 50000);
302
+ lineItemData.delivery_goal = randomInt(50000, 500000);
303
+ lineItemData.cost_type = config.costTypes[li % config.costTypes.length];
304
+ lineItemData.bid_floor = randomFloat(2, 15);
305
+ lineItemData.net_cost = randomFloat(2000, 50000);
306
+ lineItemData.net_cost_per_day = randomFloat(100, 1500);
307
+ lineItemData.impressions = randomInt(20000, 200000);
308
+ lineItemData.adm_creative_sync = true;
309
+ lineItemData.non_billable = false;
310
+ lineItemData.stop_bid_requests = false;
311
+ lineItemData.imp_multiplier = { type: 'ALL_TIME', source: SOURCE };
312
+ lineItemData.pacing = config.dealType === 'GUARANTEED'
313
+ ? { type: 'even', dailyCap: randomInt(5000, 20000), hourlyCap: randomInt(200, 1000) }
314
+ : { type: pacingTypes[li % pacingTypes.length], dailyCap: randomInt(5000, 20000), hourlyCap: randomInt(200, 1000) };
315
+
316
+ const progAgeGroups = [['18-24', '25-34'], ['25-34', '35-44', '45-54'], ['18-24', '25-34', '35-44', '45-54', '55+']];
317
+ const progInterests = [['technology', 'automotive', 'business'], ['retail', 'travel', 'lifestyle'], ['entertainment', 'sports', 'gaming']];
318
+ const progVenueTypes = [
319
+ ['transit.train_stations', 'transit.airports', 'transit.bus_stops'],
320
+ ['retail.shopping_mall', 'retail.gas_stations', 'retail.convenience_stores'],
321
+ ['outdoor.billboards', 'outdoor.street_furniture', 'entertainment.cinema']
322
+ ];
323
+ lineItemData.targeting = {
324
+ demographics: {
325
+ ageGroups: progAgeGroups[li % progAgeGroups.length],
326
+ genders: ['male', 'female'],
327
+ incomeGroups: ['middle', 'upper-middle', 'high'],
328
+ interests: progInterests[li % progInterests.length],
329
+ audienceBehaviour: ['daily_commuter', 'frequent_shopper', 'business_traveler']
330
+ },
331
+ venueTypes: progVenueTypes[li % progVenueTypes.length],
332
+ geofencing: {
333
+ geometrics: [
334
+ {
335
+ type: 'Polygon',
336
+ coordinates: [[[location.lng - 0.03, location.lat - 0.03], [location.lng + 0.03, location.lat - 0.03], [location.lng + 0.03, location.lat + 0.03], [location.lng - 0.03, location.lat + 0.03], [location.lng - 0.03, location.lat - 0.03]]],
337
+ included: true
338
+ }
339
+ ],
340
+ locations: [
341
+ {
342
+ name: `${location.name} Target Area`,
343
+ lat: location.lat,
344
+ lng: location.lng,
345
+ radius: randomInt(1000, 5000),
346
+ address: `${location.name}, ${location.city}`,
347
+ metadata: { type: 'circle', source: SOURCE },
348
+ included: true
349
+ }
350
+ ]
351
+ }
352
+ };
353
+ lineItemData.metadata = {
354
+ placement: location.venue,
355
+ category: campaign.category,
356
+ source: SOURCE,
357
+ inventoryFilteredBy: ['demographics', 'venueTypes', 'geofencing'],
358
+ venueTaxonomyFormat: 'dot-notation',
359
+ version: '1.0'
360
+ };
361
+ } else {
362
+ lineItemData.budget_setup = { currency: currency, budgetAmount: randomFloat(5000, 50000), budgetType: li % 2 === 0 ? 'DAILY' : 'TOTAL' };
363
+ lineItemData.campaign_goal = { type: 'IMPRESSIONS', targetValue: randomInt(50000, 500000) };
364
+ lineItemData.pacing = { type: pacingTypes[li % pacingTypes.length], dailyCap: randomInt(5000, 20000), hourlyCap: randomInt(200, 1000) };
365
+
366
+ const ageGroupOptions = [['18-24', '25-34'], ['25-34', '35-44'], ['35-44', '45-54'], ['18-24', '25-34', '35-44']];
367
+ const interestOptions = [['retail', 'travel'], ['technology', 'automotive'], ['fashion', 'lifestyle'], ['entertainment', 'sports']];
368
+ const audienceBehaviourOptions = [['daily_commuter', 'frequent_shopper'], ['weekend_shopper', 'evening_viewer'], ['business_traveler', 'premium_consumer']];
369
+ const venueTypeOptions = [
370
+ ['transit.train_stations', 'transit.train_stations.platform', 'retail.shopping_mall'],
371
+ ['retail', 'retail.gas_stations', 'retail.gas_stations.pump'],
372
+ ['transit.airports', 'transit.airports.terminal', 'transit.airports.lounge'],
373
+ ['outdoor.billboards', 'outdoor.street_furniture', 'retail.shopping_mall']
374
+ ];
375
+
376
+ lineItemData.targeting = {
377
+ demographics: {
378
+ ageGroups: ageGroupOptions[li % ageGroupOptions.length],
379
+ genders: ['male', 'female'],
380
+ incomeGroups: ['middle', 'upper-middle'],
381
+ interests: interestOptions[li % interestOptions.length],
382
+ audienceBehaviour: audienceBehaviourOptions[li % audienceBehaviourOptions.length]
383
+ },
384
+ venueTypes: venueTypeOptions[li % venueTypeOptions.length],
385
+ geofencing: {
386
+ geometrics: [
387
+ {
388
+ type: 'Polygon',
389
+ coordinates: [[[location.lng - 0.02, location.lat - 0.02], [location.lng + 0.02, location.lat - 0.02], [location.lng + 0.02, location.lat + 0.02], [location.lng - 0.02, location.lat + 0.02], [location.lng - 0.02, location.lat - 0.02]]],
390
+ included: true
391
+ }
392
+ ],
393
+ locations: [
394
+ {
395
+ name: `${location.name} Centre`,
396
+ lat: location.lat,
397
+ lng: location.lng,
398
+ radius: randomInt(500, 3000),
399
+ address: `${location.name}, ${location.city}`,
400
+ metadata: { type: 'circle' },
401
+ included: true
402
+ }
403
+ ]
404
+ }
405
+ };
406
+
407
+ const weatherConditions = [['sunny', 'partly_cloudy'], ['rainy', 'cloudy'], ['sunny', 'clear'], ['any']];
408
+ lineItemData.delivery_targeting = {
409
+ signals: {
410
+ weather: li % 2 === 0 ? {
411
+ conditions: weatherConditions[li % weatherConditions.length],
412
+ temperature: { min: 20, max: 35, unit: 'celsius' }
413
+ } : {}
414
+ }
415
+ };
416
+
417
+ const filterTypes = [['demographics', 'venueTypes', 'geofencing'], ['demographics', 'venueTypes'], ['venueTypes', 'geofencing'], ['demographics']];
418
+ const triggerTypes = li % 2 === 0 ? ['weather'] : [];
419
+ lineItemData.metadata = {
420
+ placement: location.venue,
421
+ category: campaign.category,
422
+ source: SOURCE,
423
+ inventoryFilteredBy: filterTypes[li % filterTypes.length],
424
+ deliveryTriggeredBy: triggerTypes,
425
+ venueTaxonomyFormat: 'dot-notation',
426
+ version: '1.0'
427
+ };
428
+
429
+ const campaignDays = randomInt(14, 90);
430
+ const availableSlots = randomInt(100000, 500000);
431
+ const availablePlayTime = availableSlots * randomInt(10, 30);
432
+ const maxImpressions = randomInt(5000000, 50000000);
433
+ const allocatedSlots = Math.floor(availableSlots * randomFloat(0.02, 0.1));
434
+ const allocatedPlayTime = Math.floor(availablePlayTime * randomFloat(0.02, 0.1));
435
+ const impressions = randomInt(500000, 5000000);
436
+ const reach = Math.floor(impressions / randomFloat(3, 8));
437
+ const frequency = parseFloat((impressions / reach).toFixed(2));
438
+ const estimatedCost = randomFloat(50000, 300000);
439
+ const cpm = parseFloat(((estimatedCost / impressions) * 1000).toFixed(2));
440
+
441
+ lineItemData.planning = {
442
+ capacity: { campaign_days: campaignDays, available: { slots: availableSlots, play_time_sec: availablePlayTime, max_impressions: maxImpressions } },
443
+ allocation: { slots: allocatedSlots, play_time_sec: allocatedPlayTime, sov: parseFloat((allocatedSlots / availableSlots).toFixed(5)), sot: parseFloat((allocatedPlayTime / availablePlayTime).toFixed(5)) },
444
+ estimates: { impressions, reach, frequency },
445
+ pricing: { cpm, estimated_cost: estimatedCost }
446
+ };
447
+ }
448
+
449
+ lineItems.push(lineItemData);
450
+
451
+ const numInventories = randomInt(3, 6);
452
+ for (let inv = 0; inv < numInventories; inv++) {
453
+ const invLocation = LOCATIONS[(i + inv) % LOCATIONS.length];
454
+ const invResolution = resolutionSet[inv % resolutionSet.length];
455
+
456
+ const inventoryData = {
457
+ id: `INV-${SOURCE.toUpperCase()}-${String(inventoryIndex).padStart(6, '0')}`,
458
+ line_item_id: lineItemData.external_id,
459
+ deal_id: dealId,
460
+ ad_unit_code: `AU-${publisher.id.replace('pub-', '').toUpperCase()}-${String(inventoryIndex).padStart(4, '0')}`,
461
+ name: `${publisher.name} - ${invLocation.name} Screen ${inv + 1}`,
462
+ reference_id: `REF-${SOURCE}-${publisher.id}-${inventoryIndex}`,
463
+ device_id: `DEVICE-${SOURCE}-${publisher.id}-${inventoryIndex}`,
464
+ size: invResolution,
465
+ publisher_id: publisher.id,
466
+ publisher_name: publisher.name,
467
+ latitude: invLocation.lat + randomFloat(-0.01, 0.01, 6),
468
+ longitude: invLocation.lng + randomFloat(-0.01, 0.01, 6),
469
+ venue_type: invLocation.venue,
470
+ venue_type_ids: [String(randomInt(100, 199))],
471
+ thumbnail: `https://cdn.${SOURCE}.com/thumbnails/${publisher.id}/${inventoryIndex}.jpg`,
472
+ timezone: invLocation.tz,
473
+ country_iso2: invLocation.country,
474
+ country_iso3: invLocation.iso3,
475
+ bcat: [{ name: 'Alcohol', code: 'IAB8-5' }, { name: 'Gambling', code: 'IAB9-9' }],
476
+ publisher_domain: publisher.domain,
477
+ enable_aspect_ratio: true,
478
+ display_aspect_ratio: invResolution.includes('1920x1080') || invResolution.includes('1280x720') || invResolution.includes('3840x2160') ? '16:9' : invResolution.includes('1080x1920') || invResolution.includes('2160x3840') ? '9:16' : '1:1',
479
+ metadata: {
480
+ externalRefIds: [
481
+ { source: 'LMX', externalRefId: generateMongoObjectId() }
482
+ ],
483
+ spotsPerHour: randomInt(60, 180),
484
+ spotDuration: randomInt(10, 30),
485
+ clients: randomInt(1, 5),
486
+ group: `${invLocation.city} Network`,
487
+ networkId: `NET-${invLocation.country}-${SOURCE}`,
488
+ packageId: `PKG-${invLocation.venue}-${inv + 1}`,
489
+ source: SOURCE
490
+ }
491
+ };
492
+
493
+ if (config.mode === 'DIRECT') {
494
+ inventoryData.planning = generateInventoryPlanning(inventoryIndex);
495
+ }
496
+
497
+ inventories.push(inventoryData);
498
+ inventoryIndex++;
499
+ dealPublishersMap.set(publisher.id, { id: publisher.id, name: publisher.name });
500
+ }
501
+ lineItemData.total_inventories = numInventories;
502
+
503
+ const canAssignCreatives = config.mode === 'DIRECT' || ['APPROVED', 'LIVE', 'COMPLETED'].includes(status);
504
+
505
+ if (canAssignCreatives) {
506
+ const numCreatives = randomInt(2, 4);
507
+ for (let cr = 0; cr < numCreatives; cr++) {
508
+ const crResolution = resolutionSet[cr % resolutionSet.length];
509
+ let crStatus;
510
+ if (['APPROVED', 'LIVE'].includes(status)) {
511
+ crStatus = 'APPROVED';
512
+ } else {
513
+ const crStatuses = ['PENDING', 'APPROVED', 'REJECTED'];
514
+ crStatus = crStatuses[cr % crStatuses.length];
515
+ }
516
+
517
+ const matchingInvIds = inventories
518
+ .filter(inv => inv.line_item_id === lineItemData.external_id && inv.size === crResolution)
519
+ .map(inv => inv.id);
520
+
521
+ const fileExt = creativeType === 'VIDEO' ? 'mp4' : creativeType === 'AUDIO' ? 'mp3' : 'jpg';
522
+ const brandSlug = campaign.brand.toLowerCase().replace(/[^a-z0-9]+/g, '-');
523
+
524
+ const creativeData = {
525
+ id: crypto.randomUUID(),
526
+ creative_id: `CR-${SOURCE.toUpperCase()}-${String(creativeIndex).padStart(6, '0')}`,
527
+ creative_uri: `https://cdn.${SOURCE}.com/creatives/${campaign.advertiser.id}/${brandSlug}-${creativeType.toLowerCase()}-${cr + 1}.${fileExt}`,
528
+ mime_type: creativeType === 'VIDEO' ? 'video/mp4' : creativeType === 'AUDIO' ? 'audio/mpeg' : 'image/jpeg',
529
+ duration: creativeType !== 'DISPLAY' ? randomInt(15, 30) : 0,
530
+ creative_type: creativeType,
531
+ resolution: crResolution,
532
+ thumbnail: `https://cdn.${SOURCE}.com/thumbnails/creatives/${campaign.advertiser.id}/${creativeIndex}.jpg`,
533
+ status: crStatus,
534
+ reviewer_id: crStatus !== 'PENDING' ? `reviewer-${SOURCE}-${randomInt(1, 5)}` : 'pending',
535
+ reviewer_name: crStatus !== 'PENDING' ? `${SOURCE.charAt(0).toUpperCase() + SOURCE.slice(1)} Reviewer ${randomInt(1, 5)}` : 'Pending Review',
536
+ reviewed_at: crStatus !== 'PENDING' ? new Date() : null,
537
+ line_item_id: lineItemData.external_id,
538
+ deal_id: dealId,
539
+ inventory_ids: matchingInvIds.length > 0 ? matchingInvIds : ['all'],
540
+ metadata: crStatus === 'REJECTED'
541
+ ? { rejectionReason: 'Creative does not meet brand safety guidelines', source: SOURCE, reviewedBy: 'content-moderation-team' }
542
+ : { brandName: campaign.brand, category: campaign.category, source: SOURCE, approvedBy: 'creative-review-team' }
543
+ };
544
+ creatives.push(creativeData);
545
+ creativeIndex++;
546
+ }
547
+ }
548
+ lineItemIndex++;
549
+ }
550
+
551
+ dealData.publishers = Array.from(dealPublishersMap.values());
552
+ dealIndex++;
553
+ }
554
+
555
+ console.log(`Inserting ${deals.length} deals...`);
556
+ await Deal.bulkCreate(deals);
557
+
558
+ console.log(`Inserting ${lineItems.length} line items...`);
559
+ await LineItem.bulkCreate(lineItems);
560
+
561
+ console.log(`Inserting ${inventories.length} inventories...`);
562
+ await LineItemInventory.bulkCreate(inventories);
563
+
564
+ console.log(`Inserting ${creatives.length} creatives...`);
565
+ await LineItemCreative.bulkCreate(creatives);
566
+
567
+ console.log('\nRecalculating deal metrics from line items and inventories...');
568
+ const allDealsRecords = await Deal.findAll();
569
+ for (const dealRecord of allDealsRecords) {
570
+ const lineItemRecords = await LineItem.findAll({
571
+ where: { deal_id: dealRecord.deal_id },
572
+ include: [{ model: LineItemInventory, as: 'inventories' }]
573
+ });
574
+ const calculatedMetrics = calculateDealMetrics(
575
+ lineItemRecords.map(li => li.toJSON()),
576
+ dealRecord.mode || 'PROGRAMMATIC'
577
+ );
578
+ await dealRecord.update(calculatedMetrics);
579
+ }
580
+ console.log(`Updated metrics for ${allDealsRecords.length} deals`);
581
+
582
+ const dealsByMode = {};
583
+ deals.forEach(d => { dealsByMode[d.mode] = (dealsByMode[d.mode] || 0) + 1; });
584
+
585
+ const dealsByType = {};
586
+ deals.forEach(d => { dealsByType[d.deal_type] = (dealsByType[d.deal_type] || 0) + 1; });
587
+
588
+ const dealsByStatus = {};
589
+ deals.forEach(d => { dealsByStatus[d.status] = (dealsByStatus[d.status] || 0) + 1; });
590
+
591
+ const creativesByType = {};
592
+ creatives.forEach(c => { creativesByType[c.creative_type] = (creativesByType[c.creative_type] || 0) + 1; });
593
+
594
+ const creativesByStatus = {};
595
+ creatives.forEach(c => { creativesByStatus[c.status] = (creativesByStatus[c.status] || 0) + 1; });
596
+
597
+ const directLineItems = lineItems.filter(li => li.mode === 'DIRECT');
598
+ const directInventories = inventories.filter(inv => inv.planning);
599
+
600
+ console.log('\n========================================');
601
+ console.log('=== SEED COMPLETE - SUMMARY ===');
602
+ console.log('========================================');
603
+ console.log(`\nSource: ${SOURCE}`);
604
+ console.log(`\nTotal Deals: ${deals.length}`);
605
+ console.log(`Total Line Items: ${lineItems.length}`);
606
+ console.log(`Total Inventories: ${inventories.length}`);
607
+ console.log(`Total Creatives: ${creatives.length}`);
608
+ console.log(`\nDIRECT Mode Line Items with Planning: ${directLineItems.length}`);
609
+ console.log(`DIRECT Mode Inventories with Planning: ${directInventories.length}`);
610
+ console.log('\nDeals by Mode:');
611
+ Object.entries(dealsByMode).forEach(([key, count]) => console.log(` ${key}: ${count}`));
612
+ console.log('\nDeals by Type:');
613
+ Object.entries(dealsByType).forEach(([key, count]) => console.log(` ${key}: ${count}`));
614
+ console.log('\nDeals by Status:');
615
+ Object.entries(dealsByStatus).forEach(([key, count]) => console.log(` ${key}: ${count}`));
616
+ console.log('\nCreatives by Type:');
617
+ Object.entries(creativesByType).forEach(([key, count]) => console.log(` ${key}: ${count}`));
618
+ console.log('\nCreatives by Status:');
619
+ Object.entries(creativesByStatus).forEach(([key, count]) => console.log(` ${key}: ${count}`));
620
+ console.log('\n========================================');
621
+ }
622
+
623
+ seed()
624
+ .then(() => {
625
+ console.log(`\nDatabase seeded successfully with ${SOURCE} data!`);
626
+ process.exit(0);
627
+ })
628
+ .catch(err => {
629
+ console.error('Seed failed:', err);
630
+ process.exit(1);
631
+ });