@useatlas/create 0.0.1

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 (515) hide show
  1. package/README.md +231 -0
  2. package/index.ts +829 -0
  3. package/package.json +38 -0
  4. package/templates/docker/.env.example +67 -0
  5. package/templates/docker/Dockerfile +52 -0
  6. package/templates/docker/bin/__tests__/benchmark.test.ts +598 -0
  7. package/templates/docker/bin/__tests__/duckdb-ingest.test.ts +171 -0
  8. package/templates/docker/bin/__tests__/eval.test.ts +434 -0
  9. package/templates/docker/bin/__tests__/matview-partition.test.ts +615 -0
  10. package/templates/docker/bin/__tests__/multi-source.test.ts +113 -0
  11. package/templates/docker/bin/__tests__/plugin-cli.test.ts +322 -0
  12. package/templates/docker/bin/__tests__/profiler-heuristics.test.ts +608 -0
  13. package/templates/docker/bin/__tests__/query.test.ts +240 -0
  14. package/templates/docker/bin/__tests__/schema-drift.test.ts +542 -0
  15. package/templates/docker/bin/__tests__/view-yaml-generation.test.ts +146 -0
  16. package/templates/docker/bin/atlas.ts +5044 -0
  17. package/templates/docker/bin/benchmark.ts +695 -0
  18. package/templates/docker/bin/enrich.ts +559 -0
  19. package/templates/docker/bin/eval.ts +770 -0
  20. package/templates/docker/bin/smoke.ts +438 -0
  21. package/templates/docker/data/.gitkeep +0 -0
  22. package/templates/docker/data/cybersec.sql +1961 -0
  23. package/templates/docker/data/demo-semantic/catalog.yml +40 -0
  24. package/templates/docker/data/demo-semantic/entities/accounts.yml +170 -0
  25. package/templates/docker/data/demo-semantic/entities/companies.yml +207 -0
  26. package/templates/docker/data/demo-semantic/entities/people.yml +145 -0
  27. package/templates/docker/data/demo-semantic/glossary.yml +22 -0
  28. package/templates/docker/data/demo-semantic/metrics/accounts.yml +38 -0
  29. package/templates/docker/data/demo-semantic/metrics/companies.yml +89 -0
  30. package/templates/docker/data/demo.sql +373 -0
  31. package/templates/docker/data/ecommerce.sql +1690 -0
  32. package/templates/docker/data/init-demo-db.sql +8 -0
  33. package/templates/docker/docker-compose.yml +34 -0
  34. package/templates/docker/docs/deploy.md +390 -0
  35. package/templates/docker/eslint.config.mjs +18 -0
  36. package/templates/docker/gitignore +5 -0
  37. package/templates/docker/next.config.ts +9 -0
  38. package/templates/docker/package.json +59 -0
  39. package/templates/docker/postcss.config.mjs +8 -0
  40. package/templates/docker/public/.gitkeep +0 -0
  41. package/templates/docker/public/favicon.svg +4 -0
  42. package/templates/docker/railway.json +13 -0
  43. package/templates/docker/render.yaml +34 -0
  44. package/templates/docker/semantic/catalog.yml +5 -0
  45. package/templates/docker/semantic/entities/.gitkeep +0 -0
  46. package/templates/docker/semantic/glossary.yml +6 -0
  47. package/templates/docker/semantic/metrics/.gitkeep +0 -0
  48. package/templates/docker/sidecar/Dockerfile +28 -0
  49. package/templates/docker/sidecar/railway.json +14 -0
  50. package/templates/docker/sidecar/server.ts +188 -0
  51. package/templates/docker/src/api/__tests__/actions.test.ts +683 -0
  52. package/templates/docker/src/api/__tests__/admin.test.ts +820 -0
  53. package/templates/docker/src/api/__tests__/auth.test.ts +165 -0
  54. package/templates/docker/src/api/__tests__/chat.test.ts +376 -0
  55. package/templates/docker/src/api/__tests__/conversations.test.ts +555 -0
  56. package/templates/docker/src/api/__tests__/cors.test.ts +135 -0
  57. package/templates/docker/src/api/__tests__/health-plugin.test.ts +169 -0
  58. package/templates/docker/src/api/__tests__/health.test.ts +261 -0
  59. package/templates/docker/src/api/__tests__/query.test.ts +891 -0
  60. package/templates/docker/src/api/__tests__/scheduled-tasks.test.ts +601 -0
  61. package/templates/docker/src/api/__tests__/slack.test.ts +847 -0
  62. package/templates/docker/src/api/index.ts +117 -0
  63. package/templates/docker/src/api/routes/actions.ts +274 -0
  64. package/templates/docker/src/api/routes/admin.ts +757 -0
  65. package/templates/docker/src/api/routes/auth.ts +48 -0
  66. package/templates/docker/src/api/routes/chat.ts +465 -0
  67. package/templates/docker/src/api/routes/conversations.ts +266 -0
  68. package/templates/docker/src/api/routes/health.ts +287 -0
  69. package/templates/docker/src/api/routes/openapi.ts +390 -0
  70. package/templates/docker/src/api/routes/query.ts +318 -0
  71. package/templates/docker/src/api/routes/scheduled-tasks.ts +467 -0
  72. package/templates/docker/src/api/routes/slack.ts +611 -0
  73. package/templates/docker/src/api/server.ts +226 -0
  74. package/templates/docker/src/app/api/[...route]/route.ts +33 -0
  75. package/templates/docker/src/app/error.tsx +24 -0
  76. package/templates/docker/src/app/globals.css +126 -0
  77. package/templates/docker/src/app/layout.tsx +19 -0
  78. package/templates/docker/src/app/page.tsx +14 -0
  79. package/templates/docker/src/global.d.ts +1 -0
  80. package/templates/docker/src/lib/__tests__/agent-cache.test.ts +437 -0
  81. package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +114 -0
  82. package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
  83. package/templates/docker/src/lib/__tests__/agent-integration.test.ts +514 -0
  84. package/templates/docker/src/lib/__tests__/config-actions.test.ts +166 -0
  85. package/templates/docker/src/lib/__tests__/config.test.ts +1063 -0
  86. package/templates/docker/src/lib/__tests__/conversations.test.ts +589 -0
  87. package/templates/docker/src/lib/__tests__/errors.test.ts +256 -0
  88. package/templates/docker/src/lib/__tests__/logger.test.ts +200 -0
  89. package/templates/docker/src/lib/__tests__/providers.test.ts +99 -0
  90. package/templates/docker/src/lib/__tests__/rls.test.ts +435 -0
  91. package/templates/docker/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
  92. package/templates/docker/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
  93. package/templates/docker/src/lib/__tests__/semantic-index.test.ts +547 -0
  94. package/templates/docker/src/lib/__tests__/semantic-multisource.test.ts +544 -0
  95. package/templates/docker/src/lib/__tests__/semantic.test.ts +363 -0
  96. package/templates/docker/src/lib/__tests__/startup-actions.test.ts +452 -0
  97. package/templates/docker/src/lib/__tests__/startup.test.ts +465 -0
  98. package/templates/docker/src/lib/__tests__/tracing.test.ts +28 -0
  99. package/templates/docker/src/lib/action-types.ts +95 -0
  100. package/templates/docker/src/lib/agent-query.ts +178 -0
  101. package/templates/docker/src/lib/agent.ts +505 -0
  102. package/templates/docker/src/lib/api-url.ts +2 -0
  103. package/templates/docker/src/lib/auth/__tests__/audit.test.ts +418 -0
  104. package/templates/docker/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
  105. package/templates/docker/src/lib/auth/__tests__/byot.test.ts +366 -0
  106. package/templates/docker/src/lib/auth/__tests__/detect.test.ts +190 -0
  107. package/templates/docker/src/lib/auth/__tests__/managed.test.ts +173 -0
  108. package/templates/docker/src/lib/auth/__tests__/middleware.test.ts +456 -0
  109. package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +201 -0
  110. package/templates/docker/src/lib/auth/__tests__/permissions.test.ts +225 -0
  111. package/templates/docker/src/lib/auth/__tests__/server.test.ts +34 -0
  112. package/templates/docker/src/lib/auth/__tests__/simple-key.test.ts +176 -0
  113. package/templates/docker/src/lib/auth/__tests__/types.test.ts +44 -0
  114. package/templates/docker/src/lib/auth/audit.ts +89 -0
  115. package/templates/docker/src/lib/auth/byot.ts +158 -0
  116. package/templates/docker/src/lib/auth/client.ts +35 -0
  117. package/templates/docker/src/lib/auth/detect.ts +83 -0
  118. package/templates/docker/src/lib/auth/managed.ts +73 -0
  119. package/templates/docker/src/lib/auth/middleware.ts +208 -0
  120. package/templates/docker/src/lib/auth/migrate.ts +111 -0
  121. package/templates/docker/src/lib/auth/permissions.ts +156 -0
  122. package/templates/docker/src/lib/auth/server.ts +142 -0
  123. package/templates/docker/src/lib/auth/simple-key.ts +92 -0
  124. package/templates/docker/src/lib/auth/types.ts +49 -0
  125. package/templates/docker/src/lib/config.ts +704 -0
  126. package/templates/docker/src/lib/conversation-types.ts +29 -0
  127. package/templates/docker/src/lib/conversations.ts +270 -0
  128. package/templates/docker/src/lib/db/__tests__/connection.test.ts +69 -0
  129. package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +141 -0
  130. package/templates/docker/src/lib/db/__tests__/internal.test.ts +387 -0
  131. package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +207 -0
  132. package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
  133. package/templates/docker/src/lib/db/__tests__/registry.test.ts +595 -0
  134. package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +339 -0
  135. package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +217 -0
  136. package/templates/docker/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
  137. package/templates/docker/src/lib/db/connection.ts +753 -0
  138. package/templates/docker/src/lib/db/duckdb.ts +122 -0
  139. package/templates/docker/src/lib/db/internal.ts +273 -0
  140. package/templates/docker/src/lib/db/salesforce.ts +342 -0
  141. package/templates/docker/src/lib/db/source-rate-limit.ts +191 -0
  142. package/templates/docker/src/lib/errors.ts +154 -0
  143. package/templates/docker/src/lib/logger.ts +98 -0
  144. package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
  145. package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +529 -0
  146. package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +521 -0
  147. package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +346 -0
  148. package/templates/docker/src/lib/plugins/__tests__/tools.test.ts +49 -0
  149. package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +585 -0
  150. package/templates/docker/src/lib/plugins/hooks.ts +162 -0
  151. package/templates/docker/src/lib/plugins/index.ts +9 -0
  152. package/templates/docker/src/lib/plugins/migrate.ts +309 -0
  153. package/templates/docker/src/lib/plugins/registry.ts +231 -0
  154. package/templates/docker/src/lib/plugins/tools.ts +39 -0
  155. package/templates/docker/src/lib/plugins/wiring.ts +291 -0
  156. package/templates/docker/src/lib/providers.ts +102 -0
  157. package/templates/docker/src/lib/rls.ts +321 -0
  158. package/templates/docker/src/lib/scheduled-task-types.ts +132 -0
  159. package/templates/docker/src/lib/scheduled-tasks.ts +475 -0
  160. package/templates/docker/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
  161. package/templates/docker/src/lib/scheduler/__tests__/engine.test.ts +248 -0
  162. package/templates/docker/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
  163. package/templates/docker/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
  164. package/templates/docker/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
  165. package/templates/docker/src/lib/scheduler/delivery.ts +248 -0
  166. package/templates/docker/src/lib/scheduler/engine.ts +317 -0
  167. package/templates/docker/src/lib/scheduler/executor.ts +73 -0
  168. package/templates/docker/src/lib/scheduler/format-email.ts +109 -0
  169. package/templates/docker/src/lib/scheduler/format-slack.ts +35 -0
  170. package/templates/docker/src/lib/scheduler/format-webhook.ts +37 -0
  171. package/templates/docker/src/lib/scheduler/index.ts +7 -0
  172. package/templates/docker/src/lib/security.ts +11 -0
  173. package/templates/docker/src/lib/semantic-index.ts +503 -0
  174. package/templates/docker/src/lib/semantic.ts +387 -0
  175. package/templates/docker/src/lib/sidecar-types.ts +16 -0
  176. package/templates/docker/src/lib/slack/__tests__/api.test.ts +160 -0
  177. package/templates/docker/src/lib/slack/__tests__/format.test.ts +237 -0
  178. package/templates/docker/src/lib/slack/__tests__/store.test.ts +188 -0
  179. package/templates/docker/src/lib/slack/__tests__/threads.test.ts +112 -0
  180. package/templates/docker/src/lib/slack/__tests__/verify.test.ts +111 -0
  181. package/templates/docker/src/lib/slack/api.ts +102 -0
  182. package/templates/docker/src/lib/slack/format.ts +209 -0
  183. package/templates/docker/src/lib/slack/store.ts +107 -0
  184. package/templates/docker/src/lib/slack/threads.ts +64 -0
  185. package/templates/docker/src/lib/slack/verify.ts +71 -0
  186. package/templates/docker/src/lib/startup.ts +730 -0
  187. package/templates/docker/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
  188. package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
  189. package/templates/docker/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
  190. package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
  191. package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
  192. package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
  193. package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
  194. package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
  195. package/templates/docker/src/lib/tools/__tests__/registry.test.ts +235 -0
  196. package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
  197. package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
  198. package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
  199. package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
  200. package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
  201. package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
  202. package/templates/docker/src/lib/tools/__tests__/sql.test.ts +1012 -0
  203. package/templates/docker/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
  204. package/templates/docker/src/lib/tools/actions/__tests__/email.test.ts +378 -0
  205. package/templates/docker/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
  206. package/templates/docker/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
  207. package/templates/docker/src/lib/tools/actions/audit.ts +47 -0
  208. package/templates/docker/src/lib/tools/actions/email.ts +191 -0
  209. package/templates/docker/src/lib/tools/actions/handler.ts +591 -0
  210. package/templates/docker/src/lib/tools/actions/index.ts +23 -0
  211. package/templates/docker/src/lib/tools/actions/jira.ts +220 -0
  212. package/templates/docker/src/lib/tools/explore-nsjail.ts +343 -0
  213. package/templates/docker/src/lib/tools/explore-sandbox.ts +264 -0
  214. package/templates/docker/src/lib/tools/explore-sidecar.ts +163 -0
  215. package/templates/docker/src/lib/tools/explore.ts +379 -0
  216. package/templates/docker/src/lib/tools/registry.ts +221 -0
  217. package/templates/docker/src/lib/tools/salesforce.ts +138 -0
  218. package/templates/docker/src/lib/tools/soql-validation.ts +172 -0
  219. package/templates/docker/src/lib/tools/sql.ts +680 -0
  220. package/templates/docker/src/lib/tracing.ts +40 -0
  221. package/templates/docker/src/lib/utils.ts +6 -0
  222. package/templates/docker/src/test-setup.ts +38 -0
  223. package/templates/docker/src/types/vercel-sandbox.d.ts +54 -0
  224. package/templates/docker/src/ui/components/actions/action-approval-card.tsx +295 -0
  225. package/templates/docker/src/ui/components/actions/action-status-badge.tsx +50 -0
  226. package/templates/docker/src/ui/components/admin/admin-layout.tsx +26 -0
  227. package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +96 -0
  228. package/templates/docker/src/ui/components/admin/empty-state.tsx +24 -0
  229. package/templates/docker/src/ui/components/admin/entity-detail.tsx +233 -0
  230. package/templates/docker/src/ui/components/admin/entity-list.tsx +96 -0
  231. package/templates/docker/src/ui/components/admin/error-banner.tsx +22 -0
  232. package/templates/docker/src/ui/components/admin/feature-disabled.tsx +44 -0
  233. package/templates/docker/src/ui/components/admin/health-badge.tsx +30 -0
  234. package/templates/docker/src/ui/components/admin/loading-state.tsx +14 -0
  235. package/templates/docker/src/ui/components/admin/stat-card.tsx +32 -0
  236. package/templates/docker/src/ui/components/atlas-chat.tsx +370 -0
  237. package/templates/docker/src/ui/components/chart/chart-detection.ts +261 -0
  238. package/templates/docker/src/ui/components/chart/result-chart.tsx +375 -0
  239. package/templates/docker/src/ui/components/chat/api-key-bar.tsx +66 -0
  240. package/templates/docker/src/ui/components/chat/copy-button.tsx +25 -0
  241. package/templates/docker/src/ui/components/chat/data-table.tsx +102 -0
  242. package/templates/docker/src/ui/components/chat/error-banner.tsx +32 -0
  243. package/templates/docker/src/ui/components/chat/explore-card.tsx +41 -0
  244. package/templates/docker/src/ui/components/chat/loading-card.tsx +10 -0
  245. package/templates/docker/src/ui/components/chat/managed-auth-card.tsx +116 -0
  246. package/templates/docker/src/ui/components/chat/markdown.tsx +72 -0
  247. package/templates/docker/src/ui/components/chat/sql-block.tsx +30 -0
  248. package/templates/docker/src/ui/components/chat/sql-result-card.tsx +144 -0
  249. package/templates/docker/src/ui/components/chat/starter-prompts.ts +6 -0
  250. package/templates/docker/src/ui/components/chat/tool-part.tsx +40 -0
  251. package/templates/docker/src/ui/components/chat/typing-indicator.tsx +19 -0
  252. package/templates/docker/src/ui/components/conversations/conversation-item.tsx +120 -0
  253. package/templates/docker/src/ui/components/conversations/conversation-list.tsx +66 -0
  254. package/templates/docker/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
  255. package/templates/docker/src/ui/components/conversations/delete-confirmation.tsx +27 -0
  256. package/templates/docker/src/ui/context.tsx +78 -0
  257. package/templates/docker/src/ui/hooks/use-admin-fetch.ts +104 -0
  258. package/templates/docker/src/ui/hooks/use-conversations.ts +184 -0
  259. package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -0
  260. package/templates/docker/src/ui/lib/action-types.ts +63 -0
  261. package/templates/docker/src/ui/lib/helpers.ts +104 -0
  262. package/templates/docker/src/ui/lib/types.ts +145 -0
  263. package/templates/docker/tsconfig.json +41 -0
  264. package/templates/docker/vercel.json +3 -0
  265. package/templates/nextjs-standalone/.env.example +68 -0
  266. package/templates/nextjs-standalone/bin/__tests__/benchmark.test.ts +598 -0
  267. package/templates/nextjs-standalone/bin/__tests__/duckdb-ingest.test.ts +171 -0
  268. package/templates/nextjs-standalone/bin/__tests__/eval.test.ts +434 -0
  269. package/templates/nextjs-standalone/bin/__tests__/matview-partition.test.ts +615 -0
  270. package/templates/nextjs-standalone/bin/__tests__/multi-source.test.ts +113 -0
  271. package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +322 -0
  272. package/templates/nextjs-standalone/bin/__tests__/profiler-heuristics.test.ts +608 -0
  273. package/templates/nextjs-standalone/bin/__tests__/query.test.ts +240 -0
  274. package/templates/nextjs-standalone/bin/__tests__/schema-drift.test.ts +542 -0
  275. package/templates/nextjs-standalone/bin/__tests__/view-yaml-generation.test.ts +146 -0
  276. package/templates/nextjs-standalone/bin/atlas.ts +5044 -0
  277. package/templates/nextjs-standalone/bin/benchmark.ts +695 -0
  278. package/templates/nextjs-standalone/bin/enrich.ts +559 -0
  279. package/templates/nextjs-standalone/bin/eval.ts +770 -0
  280. package/templates/nextjs-standalone/bin/smoke.ts +438 -0
  281. package/templates/nextjs-standalone/data/.gitkeep +0 -0
  282. package/templates/nextjs-standalone/data/cybersec.sql +1961 -0
  283. package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +40 -0
  284. package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +170 -0
  285. package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +207 -0
  286. package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +145 -0
  287. package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +22 -0
  288. package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +38 -0
  289. package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +89 -0
  290. package/templates/nextjs-standalone/data/demo.sql +373 -0
  291. package/templates/nextjs-standalone/data/ecommerce.sql +1690 -0
  292. package/templates/nextjs-standalone/data/init-demo-db.sql +8 -0
  293. package/templates/nextjs-standalone/docs/deploy.md +390 -0
  294. package/templates/nextjs-standalone/eslint.config.mjs +18 -0
  295. package/templates/nextjs-standalone/gitignore +5 -0
  296. package/templates/nextjs-standalone/next.config.ts +10 -0
  297. package/templates/nextjs-standalone/package.json +63 -0
  298. package/templates/nextjs-standalone/postcss.config.mjs +8 -0
  299. package/templates/nextjs-standalone/semantic/catalog.yml +5 -0
  300. package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
  301. package/templates/nextjs-standalone/semantic/glossary.yml +6 -0
  302. package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
  303. package/templates/nextjs-standalone/src/api/__tests__/actions.test.ts +683 -0
  304. package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +820 -0
  305. package/templates/nextjs-standalone/src/api/__tests__/auth.test.ts +165 -0
  306. package/templates/nextjs-standalone/src/api/__tests__/chat.test.ts +376 -0
  307. package/templates/nextjs-standalone/src/api/__tests__/conversations.test.ts +555 -0
  308. package/templates/nextjs-standalone/src/api/__tests__/cors.test.ts +135 -0
  309. package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +169 -0
  310. package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +261 -0
  311. package/templates/nextjs-standalone/src/api/__tests__/query.test.ts +891 -0
  312. package/templates/nextjs-standalone/src/api/__tests__/scheduled-tasks.test.ts +601 -0
  313. package/templates/nextjs-standalone/src/api/__tests__/slack.test.ts +847 -0
  314. package/templates/nextjs-standalone/src/api/index.ts +117 -0
  315. package/templates/nextjs-standalone/src/api/routes/actions.ts +274 -0
  316. package/templates/nextjs-standalone/src/api/routes/admin.ts +757 -0
  317. package/templates/nextjs-standalone/src/api/routes/auth.ts +48 -0
  318. package/templates/nextjs-standalone/src/api/routes/chat.ts +465 -0
  319. package/templates/nextjs-standalone/src/api/routes/conversations.ts +266 -0
  320. package/templates/nextjs-standalone/src/api/routes/health.ts +287 -0
  321. package/templates/nextjs-standalone/src/api/routes/openapi.ts +390 -0
  322. package/templates/nextjs-standalone/src/api/routes/query.ts +318 -0
  323. package/templates/nextjs-standalone/src/api/routes/scheduled-tasks.ts +467 -0
  324. package/templates/nextjs-standalone/src/api/routes/slack.ts +611 -0
  325. package/templates/nextjs-standalone/src/api/server.ts +226 -0
  326. package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +33 -0
  327. package/templates/nextjs-standalone/src/app/error.tsx +24 -0
  328. package/templates/nextjs-standalone/src/app/global-error.tsx +68 -0
  329. package/templates/nextjs-standalone/src/app/globals.css +126 -0
  330. package/templates/nextjs-standalone/src/app/layout.tsx +19 -0
  331. package/templates/nextjs-standalone/src/app/page.tsx +14 -0
  332. package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +437 -0
  333. package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +114 -0
  334. package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
  335. package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +514 -0
  336. package/templates/nextjs-standalone/src/lib/__tests__/config-actions.test.ts +166 -0
  337. package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +1063 -0
  338. package/templates/nextjs-standalone/src/lib/__tests__/conversations.test.ts +589 -0
  339. package/templates/nextjs-standalone/src/lib/__tests__/errors.test.ts +256 -0
  340. package/templates/nextjs-standalone/src/lib/__tests__/logger.test.ts +200 -0
  341. package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +99 -0
  342. package/templates/nextjs-standalone/src/lib/__tests__/rls.test.ts +435 -0
  343. package/templates/nextjs-standalone/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
  344. package/templates/nextjs-standalone/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
  345. package/templates/nextjs-standalone/src/lib/__tests__/semantic-index.test.ts +547 -0
  346. package/templates/nextjs-standalone/src/lib/__tests__/semantic-multisource.test.ts +544 -0
  347. package/templates/nextjs-standalone/src/lib/__tests__/semantic.test.ts +363 -0
  348. package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +452 -0
  349. package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +465 -0
  350. package/templates/nextjs-standalone/src/lib/__tests__/tracing.test.ts +28 -0
  351. package/templates/nextjs-standalone/src/lib/action-types.ts +95 -0
  352. package/templates/nextjs-standalone/src/lib/agent-query.ts +178 -0
  353. package/templates/nextjs-standalone/src/lib/agent.ts +505 -0
  354. package/templates/nextjs-standalone/src/lib/api-url.ts +3 -0
  355. package/templates/nextjs-standalone/src/lib/auth/__tests__/audit.test.ts +418 -0
  356. package/templates/nextjs-standalone/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
  357. package/templates/nextjs-standalone/src/lib/auth/__tests__/byot.test.ts +366 -0
  358. package/templates/nextjs-standalone/src/lib/auth/__tests__/detect.test.ts +190 -0
  359. package/templates/nextjs-standalone/src/lib/auth/__tests__/managed.test.ts +173 -0
  360. package/templates/nextjs-standalone/src/lib/auth/__tests__/middleware.test.ts +456 -0
  361. package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +201 -0
  362. package/templates/nextjs-standalone/src/lib/auth/__tests__/permissions.test.ts +225 -0
  363. package/templates/nextjs-standalone/src/lib/auth/__tests__/server.test.ts +34 -0
  364. package/templates/nextjs-standalone/src/lib/auth/__tests__/simple-key.test.ts +176 -0
  365. package/templates/nextjs-standalone/src/lib/auth/__tests__/types.test.ts +44 -0
  366. package/templates/nextjs-standalone/src/lib/auth/audit.ts +89 -0
  367. package/templates/nextjs-standalone/src/lib/auth/byot.ts +158 -0
  368. package/templates/nextjs-standalone/src/lib/auth/client.ts +23 -0
  369. package/templates/nextjs-standalone/src/lib/auth/detect.ts +83 -0
  370. package/templates/nextjs-standalone/src/lib/auth/managed.ts +73 -0
  371. package/templates/nextjs-standalone/src/lib/auth/middleware.ts +208 -0
  372. package/templates/nextjs-standalone/src/lib/auth/migrate.ts +111 -0
  373. package/templates/nextjs-standalone/src/lib/auth/permissions.ts +156 -0
  374. package/templates/nextjs-standalone/src/lib/auth/server.ts +142 -0
  375. package/templates/nextjs-standalone/src/lib/auth/simple-key.ts +92 -0
  376. package/templates/nextjs-standalone/src/lib/auth/types.ts +49 -0
  377. package/templates/nextjs-standalone/src/lib/config.ts +704 -0
  378. package/templates/nextjs-standalone/src/lib/conversation-types.ts +29 -0
  379. package/templates/nextjs-standalone/src/lib/conversations.ts +270 -0
  380. package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +69 -0
  381. package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +141 -0
  382. package/templates/nextjs-standalone/src/lib/db/__tests__/internal.test.ts +387 -0
  383. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +207 -0
  384. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
  385. package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +595 -0
  386. package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +339 -0
  387. package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +217 -0
  388. package/templates/nextjs-standalone/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
  389. package/templates/nextjs-standalone/src/lib/db/connection.ts +753 -0
  390. package/templates/nextjs-standalone/src/lib/db/duckdb.ts +122 -0
  391. package/templates/nextjs-standalone/src/lib/db/internal.ts +273 -0
  392. package/templates/nextjs-standalone/src/lib/db/salesforce.ts +342 -0
  393. package/templates/nextjs-standalone/src/lib/db/source-rate-limit.ts +191 -0
  394. package/templates/nextjs-standalone/src/lib/errors.ts +154 -0
  395. package/templates/nextjs-standalone/src/lib/logger.ts +98 -0
  396. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
  397. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +529 -0
  398. package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +521 -0
  399. package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +346 -0
  400. package/templates/nextjs-standalone/src/lib/plugins/__tests__/tools.test.ts +49 -0
  401. package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +585 -0
  402. package/templates/nextjs-standalone/src/lib/plugins/hooks.ts +162 -0
  403. package/templates/nextjs-standalone/src/lib/plugins/index.ts +9 -0
  404. package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +309 -0
  405. package/templates/nextjs-standalone/src/lib/plugins/registry.ts +231 -0
  406. package/templates/nextjs-standalone/src/lib/plugins/tools.ts +39 -0
  407. package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +291 -0
  408. package/templates/nextjs-standalone/src/lib/providers.ts +102 -0
  409. package/templates/nextjs-standalone/src/lib/rls.ts +321 -0
  410. package/templates/nextjs-standalone/src/lib/scheduled-task-types.ts +132 -0
  411. package/templates/nextjs-standalone/src/lib/scheduled-tasks.ts +475 -0
  412. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
  413. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/engine.test.ts +248 -0
  414. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
  415. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
  416. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
  417. package/templates/nextjs-standalone/src/lib/scheduler/delivery.ts +248 -0
  418. package/templates/nextjs-standalone/src/lib/scheduler/engine.ts +317 -0
  419. package/templates/nextjs-standalone/src/lib/scheduler/executor.ts +73 -0
  420. package/templates/nextjs-standalone/src/lib/scheduler/format-email.ts +109 -0
  421. package/templates/nextjs-standalone/src/lib/scheduler/format-slack.ts +35 -0
  422. package/templates/nextjs-standalone/src/lib/scheduler/format-webhook.ts +37 -0
  423. package/templates/nextjs-standalone/src/lib/scheduler/index.ts +7 -0
  424. package/templates/nextjs-standalone/src/lib/security.ts +11 -0
  425. package/templates/nextjs-standalone/src/lib/semantic-index.ts +503 -0
  426. package/templates/nextjs-standalone/src/lib/semantic.ts +387 -0
  427. package/templates/nextjs-standalone/src/lib/sidecar-types.ts +16 -0
  428. package/templates/nextjs-standalone/src/lib/slack/__tests__/api.test.ts +160 -0
  429. package/templates/nextjs-standalone/src/lib/slack/__tests__/format.test.ts +237 -0
  430. package/templates/nextjs-standalone/src/lib/slack/__tests__/store.test.ts +188 -0
  431. package/templates/nextjs-standalone/src/lib/slack/__tests__/threads.test.ts +112 -0
  432. package/templates/nextjs-standalone/src/lib/slack/__tests__/verify.test.ts +111 -0
  433. package/templates/nextjs-standalone/src/lib/slack/api.ts +102 -0
  434. package/templates/nextjs-standalone/src/lib/slack/format.ts +209 -0
  435. package/templates/nextjs-standalone/src/lib/slack/store.ts +107 -0
  436. package/templates/nextjs-standalone/src/lib/slack/threads.ts +64 -0
  437. package/templates/nextjs-standalone/src/lib/slack/verify.ts +71 -0
  438. package/templates/nextjs-standalone/src/lib/startup.ts +730 -0
  439. package/templates/nextjs-standalone/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
  440. package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
  441. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
  442. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
  443. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
  444. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
  445. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
  446. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
  447. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +235 -0
  448. package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
  449. package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
  450. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
  451. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
  452. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
  453. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
  454. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +1012 -0
  455. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
  456. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/email.test.ts +378 -0
  457. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
  458. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
  459. package/templates/nextjs-standalone/src/lib/tools/actions/audit.ts +47 -0
  460. package/templates/nextjs-standalone/src/lib/tools/actions/email.ts +191 -0
  461. package/templates/nextjs-standalone/src/lib/tools/actions/handler.ts +591 -0
  462. package/templates/nextjs-standalone/src/lib/tools/actions/index.ts +23 -0
  463. package/templates/nextjs-standalone/src/lib/tools/actions/jira.ts +220 -0
  464. package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +343 -0
  465. package/templates/nextjs-standalone/src/lib/tools/explore-sandbox.ts +264 -0
  466. package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +163 -0
  467. package/templates/nextjs-standalone/src/lib/tools/explore.ts +379 -0
  468. package/templates/nextjs-standalone/src/lib/tools/registry.ts +221 -0
  469. package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +138 -0
  470. package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +172 -0
  471. package/templates/nextjs-standalone/src/lib/tools/sql.ts +680 -0
  472. package/templates/nextjs-standalone/src/lib/tracing.ts +40 -0
  473. package/templates/nextjs-standalone/src/lib/utils.ts +6 -0
  474. package/templates/nextjs-standalone/src/test-setup.ts +38 -0
  475. package/templates/nextjs-standalone/src/ui/components/actions/action-approval-card.tsx +295 -0
  476. package/templates/nextjs-standalone/src/ui/components/actions/action-status-badge.tsx +50 -0
  477. package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +26 -0
  478. package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +96 -0
  479. package/templates/nextjs-standalone/src/ui/components/admin/empty-state.tsx +24 -0
  480. package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +233 -0
  481. package/templates/nextjs-standalone/src/ui/components/admin/entity-list.tsx +96 -0
  482. package/templates/nextjs-standalone/src/ui/components/admin/error-banner.tsx +22 -0
  483. package/templates/nextjs-standalone/src/ui/components/admin/feature-disabled.tsx +44 -0
  484. package/templates/nextjs-standalone/src/ui/components/admin/health-badge.tsx +30 -0
  485. package/templates/nextjs-standalone/src/ui/components/admin/loading-state.tsx +14 -0
  486. package/templates/nextjs-standalone/src/ui/components/admin/stat-card.tsx +32 -0
  487. package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +370 -0
  488. package/templates/nextjs-standalone/src/ui/components/chart/chart-detection.ts +261 -0
  489. package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +375 -0
  490. package/templates/nextjs-standalone/src/ui/components/chat/api-key-bar.tsx +66 -0
  491. package/templates/nextjs-standalone/src/ui/components/chat/copy-button.tsx +25 -0
  492. package/templates/nextjs-standalone/src/ui/components/chat/data-table.tsx +102 -0
  493. package/templates/nextjs-standalone/src/ui/components/chat/error-banner.tsx +32 -0
  494. package/templates/nextjs-standalone/src/ui/components/chat/explore-card.tsx +41 -0
  495. package/templates/nextjs-standalone/src/ui/components/chat/loading-card.tsx +10 -0
  496. package/templates/nextjs-standalone/src/ui/components/chat/managed-auth-card.tsx +116 -0
  497. package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +72 -0
  498. package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +30 -0
  499. package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +144 -0
  500. package/templates/nextjs-standalone/src/ui/components/chat/starter-prompts.ts +6 -0
  501. package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +40 -0
  502. package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +19 -0
  503. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +120 -0
  504. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-list.tsx +66 -0
  505. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
  506. package/templates/nextjs-standalone/src/ui/components/conversations/delete-confirmation.tsx +27 -0
  507. package/templates/nextjs-standalone/src/ui/context.tsx +78 -0
  508. package/templates/nextjs-standalone/src/ui/hooks/use-admin-fetch.ts +104 -0
  509. package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +184 -0
  510. package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -0
  511. package/templates/nextjs-standalone/src/ui/lib/action-types.ts +63 -0
  512. package/templates/nextjs-standalone/src/ui/lib/helpers.ts +104 -0
  513. package/templates/nextjs-standalone/src/ui/lib/types.ts +145 -0
  514. package/templates/nextjs-standalone/tsconfig.json +32 -0
  515. package/templates/nextjs-standalone/vercel.json +4 -0
@@ -0,0 +1,1690 @@
1
+ -- ==========================================================================
2
+ -- NovaMart — E-commerce DTC Brand Demo Database (PostgreSQL)
3
+ -- ==========================================================================
4
+ -- ~52 tables, ~480K rows. Realistic tech debt patterns:
5
+ -- 1. Abandoned/legacy tables (4 tables nobody reads)
6
+ -- 2. Schema evolution artifacts (columns that changed meaning)
7
+ -- 3. Missing/wrong constraints (logical FKs without DB constraints)
8
+ -- 4. Denormalization & duplication (reporting tables, copied columns)
9
+ --
10
+ -- Company: NovaMart — DTC home goods brand (bedding, kitchen, bath, outdoor)
11
+ -- Founded 2020 (pandemic), launched marketplace 2022.
12
+ -- Time span: 2020–2025 (pandemic boom → normalization)
13
+ --
14
+ -- Usage: psql $ATLAS_DATASOURCE_URL -f data/ecommerce.sql
15
+ -- Reset: bun run db:reset (nukes volume, re-seeds)
16
+ -- ==========================================================================
17
+
18
+ BEGIN;
19
+ SELECT setseed(0.57); -- reproducible random data (different from cybersec 0.42)
20
+
21
+ -- ==========================================================================
22
+ -- DROP (safe re-run)
23
+ -- ==========================================================================
24
+ DROP TABLE IF EXISTS payment_methods_backup CASCADE;
25
+ DROP TABLE IF EXISTS legacy_analytics_events CASCADE;
26
+ DROP TABLE IF EXISTS temp_product_import_2023 CASCADE;
27
+ DROP TABLE IF EXISTS old_orders_v1 CASCADE;
28
+ DROP TABLE IF EXISTS system_settings CASCADE;
29
+ DROP TABLE IF EXISTS admin_audit_log CASCADE;
30
+ DROP TABLE IF EXISTS admin_users CASCADE;
31
+ DROP TABLE IF EXISTS search_queries CASCADE;
32
+ DROP TABLE IF EXISTS cart_events CASCADE;
33
+ DROP TABLE IF EXISTS page_views CASCADE;
34
+ DROP TABLE IF EXISTS customer_ltv_cache CASCADE;
35
+ DROP TABLE IF EXISTS product_performance_cache CASCADE;
36
+ DROP TABLE IF EXISTS orders_denormalized CASCADE;
37
+ DROP TABLE IF EXISTS monthly_revenue_summary CASCADE;
38
+ DROP TABLE IF EXISTS daily_sales_summary CASCADE;
39
+ DROP TABLE IF EXISTS review_helpfulness CASCADE;
40
+ DROP TABLE IF EXISTS review_responses CASCADE;
41
+ DROP TABLE IF EXISTS product_reviews CASCADE;
42
+ DROP TABLE IF EXISTS utm_tracking CASCADE;
43
+ DROP TABLE IF EXISTS email_sends CASCADE;
44
+ DROP TABLE IF EXISTS email_campaigns CASCADE;
45
+ DROP TABLE IF EXISTS promotion_usages CASCADE;
46
+ DROP TABLE IF EXISTS promotions CASCADE;
47
+ DROP TABLE IF EXISTS return_items CASCADE;
48
+ DROP TABLE IF EXISTS returns CASCADE;
49
+ DROP TABLE IF EXISTS shipping_carriers CASCADE;
50
+ DROP TABLE IF EXISTS shipment_items CASCADE;
51
+ DROP TABLE IF EXISTS shipments CASCADE;
52
+ DROP TABLE IF EXISTS gift_card_transactions CASCADE;
53
+ DROP TABLE IF EXISTS gift_cards CASCADE;
54
+ DROP TABLE IF EXISTS refunds CASCADE;
55
+ DROP TABLE IF EXISTS payments CASCADE;
56
+ DROP TABLE IF EXISTS order_events CASCADE;
57
+ DROP TABLE IF EXISTS order_items CASCADE;
58
+ DROP TABLE IF EXISTS orders CASCADE;
59
+ DROP TABLE IF EXISTS seller_performance CASCADE;
60
+ DROP TABLE IF EXISTS seller_payouts CASCADE;
61
+ DROP TABLE IF EXISTS seller_applications CASCADE;
62
+ DROP TABLE IF EXISTS sellers CASCADE;
63
+ DROP TABLE IF EXISTS inventory_levels CASCADE;
64
+ DROP TABLE IF EXISTS product_tags CASCADE;
65
+ DROP TABLE IF EXISTS product_images CASCADE;
66
+ DROP TABLE IF EXISTS product_variants CASCADE;
67
+ DROP TABLE IF EXISTS products CASCADE;
68
+ DROP TABLE IF EXISTS categories CASCADE;
69
+ DROP TABLE IF EXISTS warehouses CASCADE;
70
+ DROP TABLE IF EXISTS loyalty_transactions CASCADE;
71
+ DROP TABLE IF EXISTS loyalty_accounts CASCADE;
72
+ DROP TABLE IF EXISTS customer_segment_assignments CASCADE;
73
+ DROP TABLE IF EXISTS customer_segments CASCADE;
74
+ DROP TABLE IF EXISTS customer_addresses CASCADE;
75
+ DROP TABLE IF EXISTS customers CASCADE;
76
+
77
+ -- ==========================================================================
78
+ -- 1. SCHEMA
79
+ -- ==========================================================================
80
+
81
+ -- ---------- 1.1 Core Commerce ----------
82
+
83
+ CREATE TABLE customers (
84
+ id SERIAL PRIMARY KEY,
85
+ email TEXT NOT NULL,
86
+ full_name TEXT NOT NULL,
87
+ phone TEXT, -- TECH DEBT: original phone column
88
+ mobile_phone TEXT, -- TECH DEBT: added 2022, preferred. App uses COALESCE(mobile_phone, phone)
89
+ acquisition_source TEXT, -- TECH DEBT: case-inconsistent ('Google', 'google', 'GOOGLE', 'organic', 'Organic')
90
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
91
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
92
+ is_active BOOLEAN NOT NULL DEFAULT true
93
+ );
94
+
95
+ CREATE TABLE customer_addresses (
96
+ id SERIAL PRIMARY KEY,
97
+ customer_id INTEGER, -- TECH DEBT: NO FK to customers
98
+ label TEXT DEFAULT 'home',
99
+ street TEXT NOT NULL,
100
+ city TEXT NOT NULL,
101
+ state TEXT,
102
+ zip TEXT,
103
+ country TEXT NOT NULL DEFAULT 'US',
104
+ is_default BOOLEAN DEFAULT false,
105
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
106
+ );
107
+
108
+ CREATE TABLE customer_segments (
109
+ id SERIAL PRIMARY KEY,
110
+ name TEXT NOT NULL UNIQUE,
111
+ description TEXT,
112
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
113
+ );
114
+
115
+ CREATE TABLE customer_segment_assignments (
116
+ id SERIAL PRIMARY KEY,
117
+ customer_id INTEGER NOT NULL REFERENCES customers(id),
118
+ segment_id INTEGER NOT NULL REFERENCES customer_segments(id),
119
+ segment_name TEXT, -- TECH DEBT: denormalized from customer_segments.name, sometimes out of sync
120
+ assigned_at TIMESTAMPTZ NOT NULL DEFAULT now()
121
+ );
122
+
123
+ CREATE TABLE loyalty_accounts (
124
+ id SERIAL PRIMARY KEY,
125
+ customer_id INTEGER NOT NULL REFERENCES customers(id),
126
+ points INTEGER NOT NULL DEFAULT 0,
127
+ tier TEXT, -- TECH DEBT: case-inconsistent ('Gold','gold','GOLD','Silver','silver')
128
+ enrolled_at TIMESTAMPTZ NOT NULL DEFAULT now(),
129
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
130
+ );
131
+
132
+ CREATE TABLE loyalty_transactions (
133
+ id SERIAL PRIMARY KEY,
134
+ loyalty_account_id INTEGER NOT NULL REFERENCES loyalty_accounts(id),
135
+ type TEXT NOT NULL, -- 'earn', 'redeem', 'adjust', 'expire'
136
+ points INTEGER NOT NULL,
137
+ description TEXT,
138
+ order_id INTEGER, -- reference only, no FK
139
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
140
+ );
141
+
142
+ -- ---------- 1.2 Product Catalog ----------
143
+
144
+ CREATE TABLE categories (
145
+ id SERIAL PRIMARY KEY,
146
+ name TEXT NOT NULL,
147
+ parent_id INTEGER REFERENCES categories(id),
148
+ slug TEXT NOT NULL,
149
+ description TEXT,
150
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
151
+ );
152
+
153
+ CREATE TABLE warehouses (
154
+ id SERIAL PRIMARY KEY,
155
+ name TEXT NOT NULL,
156
+ code TEXT NOT NULL UNIQUE,
157
+ city TEXT NOT NULL,
158
+ state TEXT NOT NULL,
159
+ country TEXT NOT NULL DEFAULT 'US',
160
+ is_active BOOLEAN DEFAULT true
161
+ );
162
+
163
+ CREATE TABLE products (
164
+ id SERIAL PRIMARY KEY,
165
+ name TEXT NOT NULL,
166
+ slug TEXT NOT NULL,
167
+ category_id INTEGER NOT NULL REFERENCES categories(id),
168
+ seller_id INTEGER, -- TECH DEBT: NO FK to sellers (~20% are marketplace products)
169
+ price NUMERIC(10,2), -- TECH DEBT: original, in dollars
170
+ price_cents INTEGER, -- TECH DEBT: added 2023, in cents. NULL for ~40% of products
171
+ description TEXT,
172
+ status TEXT NOT NULL DEFAULT 'active',
173
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
174
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
175
+ );
176
+
177
+ CREATE TABLE product_variants (
178
+ id SERIAL PRIMARY KEY,
179
+ product_id INTEGER NOT NULL REFERENCES products(id),
180
+ sku TEXT NOT NULL,
181
+ name TEXT NOT NULL, -- e.g. 'Queen / White / Cotton'
182
+ price_cents INTEGER,
183
+ weight_oz INTEGER,
184
+ is_active BOOLEAN DEFAULT true,
185
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
186
+ );
187
+
188
+ CREATE TABLE product_images (
189
+ id SERIAL PRIMARY KEY,
190
+ product_id INTEGER NOT NULL REFERENCES products(id),
191
+ url TEXT NOT NULL,
192
+ alt_text TEXT,
193
+ position INTEGER DEFAULT 0,
194
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
195
+ );
196
+
197
+ CREATE TABLE product_tags (
198
+ id SERIAL PRIMARY KEY,
199
+ product_id INTEGER NOT NULL REFERENCES products(id),
200
+ tag TEXT NOT NULL,
201
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
202
+ );
203
+
204
+ CREATE TABLE inventory_levels (
205
+ id SERIAL PRIMARY KEY,
206
+ variant_id INTEGER, -- TECH DEBT: NO FK to product_variants
207
+ warehouse_id INTEGER NOT NULL REFERENCES warehouses(id),
208
+ quantity INTEGER NOT NULL DEFAULT 0,
209
+ reorder_point INTEGER DEFAULT 10,
210
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
211
+ );
212
+
213
+ -- ---------- 1.3 Marketplace ----------
214
+
215
+ CREATE TABLE sellers (
216
+ id SERIAL PRIMARY KEY,
217
+ company_name TEXT NOT NULL,
218
+ contact_email TEXT NOT NULL,
219
+ commission_rate NUMERIC(4,2) NOT NULL DEFAULT 15.00,
220
+ status TEXT NOT NULL DEFAULT 'active',
221
+ joined_at TIMESTAMPTZ NOT NULL DEFAULT now(),
222
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
223
+ );
224
+
225
+ CREATE TABLE seller_applications (
226
+ id SERIAL PRIMARY KEY,
227
+ company_name TEXT NOT NULL,
228
+ contact_email TEXT NOT NULL,
229
+ status TEXT NOT NULL DEFAULT 'pending',
230
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT now(),
231
+ reviewed_at TIMESTAMPTZ,
232
+ notes TEXT
233
+ );
234
+
235
+ CREATE TABLE seller_payouts (
236
+ id SERIAL PRIMARY KEY,
237
+ seller_id INTEGER NOT NULL REFERENCES sellers(id),
238
+ amount_cents INTEGER NOT NULL,
239
+ period_start DATE NOT NULL,
240
+ period_end DATE NOT NULL,
241
+ status TEXT NOT NULL DEFAULT 'pending',
242
+ paid_at TIMESTAMPTZ,
243
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
244
+ );
245
+
246
+ CREATE TABLE seller_performance (
247
+ id SERIAL PRIMARY KEY,
248
+ seller_id INTEGER NOT NULL REFERENCES sellers(id),
249
+ month DATE NOT NULL,
250
+ total_orders INTEGER DEFAULT 0,
251
+ total_revenue_cents INTEGER DEFAULT 0,
252
+ return_rate NUMERIC(5,2),
253
+ avg_rating NUMERIC(3,2),
254
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
255
+ );
256
+
257
+ -- ---------- 1.4 Orders & Transactions ----------
258
+
259
+ CREATE TABLE orders (
260
+ id SERIAL PRIMARY KEY,
261
+ customer_id INTEGER NOT NULL REFERENCES customers(id),
262
+ customer_email TEXT, -- TECH DEBT: denormalized from customers.email
263
+ status TEXT NOT NULL DEFAULT 'pending',
264
+ subtotal_cents INTEGER NOT NULL,
265
+ shipping_cost NUMERIC(10,2), -- TECH DEBT: was dollars, now stores cents for orders after 2023-06. Old data NOT migrated
266
+ tax_cents INTEGER DEFAULT 0,
267
+ total_cents INTEGER NOT NULL,
268
+ shipping_address_id INTEGER,
269
+ promotion_id INTEGER,
270
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
271
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
272
+ );
273
+
274
+ CREATE TABLE order_items (
275
+ id SERIAL PRIMARY KEY,
276
+ order_id INTEGER NOT NULL REFERENCES orders(id),
277
+ product_variant_id INTEGER, -- TECH DEBT: NO FK to product_variants
278
+ product_name TEXT NOT NULL,
279
+ quantity INTEGER NOT NULL DEFAULT 1,
280
+ unit_price_cents INTEGER NOT NULL,
281
+ total_cents INTEGER NOT NULL,
282
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
283
+ );
284
+
285
+ CREATE TABLE order_events (
286
+ id SERIAL PRIMARY KEY,
287
+ order_id INTEGER, -- TECH DEBT: NO FK to orders
288
+ event_type TEXT NOT NULL, -- 'placed','confirmed','processing','shipped','delivered','canceled','returned'
289
+ description TEXT,
290
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
291
+ );
292
+
293
+ CREATE TABLE payments (
294
+ id SERIAL PRIMARY KEY,
295
+ order_id INTEGER, -- TECH DEBT: NO FK to orders. ~1.5% reference nonexistent orders (orphaned from deleted test orders)
296
+ method TEXT NOT NULL,
297
+ status TEXT NOT NULL DEFAULT 'pending',
298
+ amount_cents INTEGER NOT NULL,
299
+ provider_ref TEXT,
300
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
301
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
302
+ );
303
+
304
+ CREATE TABLE refunds (
305
+ id SERIAL PRIMARY KEY,
306
+ payment_id INTEGER NOT NULL REFERENCES payments(id),
307
+ amount_cents INTEGER NOT NULL,
308
+ reason TEXT,
309
+ status TEXT NOT NULL DEFAULT 'pending',
310
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
311
+ );
312
+
313
+ CREATE TABLE gift_cards (
314
+ id SERIAL PRIMARY KEY,
315
+ code TEXT NOT NULL UNIQUE,
316
+ initial_cents INTEGER NOT NULL,
317
+ balance_cents INTEGER NOT NULL,
318
+ issued_to INTEGER REFERENCES customers(id),
319
+ status TEXT NOT NULL DEFAULT 'active',
320
+ expires_at TIMESTAMPTZ,
321
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
322
+ );
323
+
324
+ CREATE TABLE gift_card_transactions (
325
+ id SERIAL PRIMARY KEY,
326
+ gift_card_id INTEGER NOT NULL REFERENCES gift_cards(id),
327
+ order_id INTEGER,
328
+ amount_cents INTEGER NOT NULL,
329
+ type TEXT NOT NULL, -- 'issue', 'redeem', 'refund'
330
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
331
+ );
332
+
333
+ -- ---------- 1.5 Shipping & Fulfillment ----------
334
+
335
+ CREATE TABLE shipping_carriers (
336
+ id SERIAL PRIMARY KEY,
337
+ name TEXT NOT NULL,
338
+ code TEXT NOT NULL UNIQUE,
339
+ tracking_url_template TEXT,
340
+ is_active BOOLEAN DEFAULT true
341
+ );
342
+
343
+ CREATE TABLE shipments (
344
+ id SERIAL PRIMARY KEY,
345
+ order_id INTEGER NOT NULL REFERENCES orders(id),
346
+ warehouse_id INTEGER REFERENCES warehouses(id),
347
+ carrier TEXT, -- TECH DEBT: original text field ('UPS','FedEx',etc.)
348
+ carrier_id INTEGER, -- TECH DEBT: added 2024, logical FK to shipping_carriers (NO CONSTRAINT). NULL for ~60% of older shipments
349
+ tracking_number TEXT,
350
+ status TEXT NOT NULL DEFAULT 'pending',
351
+ shipped_at TIMESTAMPTZ,
352
+ delivered_at TIMESTAMPTZ,
353
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
354
+ );
355
+
356
+ CREATE TABLE shipment_items (
357
+ id SERIAL PRIMARY KEY,
358
+ shipment_id INTEGER, -- TECH DEBT: NO FK to shipments
359
+ order_item_id INTEGER NOT NULL,
360
+ quantity INTEGER NOT NULL DEFAULT 1
361
+ );
362
+
363
+ CREATE TABLE returns (
364
+ id SERIAL PRIMARY KEY,
365
+ order_id INTEGER NOT NULL REFERENCES orders(id),
366
+ customer_id INTEGER NOT NULL REFERENCES customers(id),
367
+ reason TEXT, -- TECH DEBT: case-inconsistent ('Defective','defective','DEFECTIVE','Wrong Item','wrong_item')
368
+ status TEXT NOT NULL DEFAULT 'requested',
369
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
370
+ resolved_at TIMESTAMPTZ
371
+ );
372
+
373
+ CREATE TABLE return_items (
374
+ id SERIAL PRIMARY KEY,
375
+ return_id INTEGER NOT NULL REFERENCES returns(id),
376
+ order_item_id INTEGER NOT NULL,
377
+ quantity INTEGER NOT NULL DEFAULT 1,
378
+ condition TEXT DEFAULT 'unopened'
379
+ );
380
+
381
+ -- ---------- 1.6 Marketing & Promotions ----------
382
+
383
+ CREATE TABLE promotions (
384
+ id SERIAL PRIMARY KEY,
385
+ code TEXT NOT NULL,
386
+ name TEXT NOT NULL,
387
+ type TEXT NOT NULL, -- 'percentage', 'fixed_amount', 'free_shipping', 'bogo'
388
+ value NUMERIC(10,2),
389
+ min_order_cents INTEGER,
390
+ max_uses INTEGER,
391
+ times_used INTEGER DEFAULT 0,
392
+ starts_at TIMESTAMPTZ NOT NULL,
393
+ ends_at TIMESTAMPTZ,
394
+ is_active BOOLEAN DEFAULT true,
395
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
396
+ );
397
+
398
+ CREATE TABLE promotion_usages (
399
+ id SERIAL PRIMARY KEY,
400
+ promotion_id INTEGER, -- TECH DEBT: NO FK to promotions
401
+ order_id INTEGER, -- TECH DEBT: NO FK to orders
402
+ customer_id INTEGER NOT NULL REFERENCES customers(id),
403
+ discount_cents INTEGER NOT NULL,
404
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
405
+ );
406
+
407
+ CREATE TABLE email_campaigns (
408
+ id SERIAL PRIMARY KEY,
409
+ name TEXT NOT NULL,
410
+ subject TEXT NOT NULL,
411
+ type TEXT NOT NULL, -- 'promotional','transactional','retention','winback'
412
+ status TEXT NOT NULL DEFAULT 'draft',
413
+ sent_at TIMESTAMPTZ,
414
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
415
+ );
416
+
417
+ CREATE TABLE email_sends (
418
+ id SERIAL PRIMARY KEY,
419
+ campaign_id INTEGER NOT NULL REFERENCES email_campaigns(id),
420
+ customer_id INTEGER NOT NULL REFERENCES customers(id),
421
+ status TEXT NOT NULL DEFAULT 'sent',
422
+ opened_at TIMESTAMPTZ,
423
+ clicked_at TIMESTAMPTZ,
424
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
425
+ );
426
+
427
+ CREATE TABLE utm_tracking (
428
+ id SERIAL PRIMARY KEY,
429
+ customer_id INTEGER, -- TECH DEBT: NO FK to customers
430
+ utm_source TEXT,
431
+ utm_medium TEXT,
432
+ utm_campaign TEXT,
433
+ utm_content TEXT,
434
+ landing_page TEXT,
435
+ order_id INTEGER, -- reference only
436
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
437
+ );
438
+
439
+ -- ---------- 1.7 Reviews ----------
440
+
441
+ CREATE TABLE product_reviews (
442
+ id SERIAL PRIMARY KEY,
443
+ product_id INTEGER NOT NULL REFERENCES products(id),
444
+ customer_id INTEGER NOT NULL REFERENCES customers(id),
445
+ rating INTEGER NOT NULL, -- TECH DEBT: original INTEGER 1-5
446
+ rating_decimal NUMERIC(3,1), -- TECH DEBT: added 2024, NULL for ~70% of older reviews
447
+ title TEXT,
448
+ body TEXT,
449
+ is_verified BOOLEAN DEFAULT false,
450
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
451
+ );
452
+
453
+ CREATE TABLE review_responses (
454
+ id SERIAL PRIMARY KEY,
455
+ review_id INTEGER NOT NULL REFERENCES product_reviews(id),
456
+ responder TEXT NOT NULL, -- 'NovaMart Team' or seller name
457
+ body TEXT NOT NULL,
458
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
459
+ );
460
+
461
+ CREATE TABLE review_helpfulness (
462
+ id SERIAL PRIMARY KEY,
463
+ review_id INTEGER, -- TECH DEBT: NO FK to product_reviews
464
+ customer_id INTEGER, -- TECH DEBT: NO FK to customers
465
+ helpful BOOLEAN NOT NULL,
466
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
467
+ );
468
+
469
+ -- ---------- 1.8 Reporting / Denormalized ----------
470
+
471
+ CREATE TABLE daily_sales_summary (
472
+ id SERIAL PRIMARY KEY,
473
+ sale_date DATE NOT NULL,
474
+ total_orders INTEGER DEFAULT 0,
475
+ total_revenue_cents INTEGER DEFAULT 0,
476
+ total_items INTEGER DEFAULT 0,
477
+ avg_order_value_cents INTEGER DEFAULT 0,
478
+ return_count INTEGER DEFAULT 0
479
+ );
480
+
481
+ CREATE TABLE monthly_revenue_summary (
482
+ id SERIAL PRIMARY KEY,
483
+ month DATE NOT NULL,
484
+ revenue_cents INTEGER DEFAULT 0,
485
+ order_count INTEGER DEFAULT 0,
486
+ new_customers INTEGER DEFAULT 0,
487
+ returning_customers INTEGER DEFAULT 0,
488
+ avg_order_value_cents INTEGER DEFAULT 0
489
+ );
490
+
491
+ CREATE TABLE orders_denormalized (
492
+ id SERIAL PRIMARY KEY,
493
+ order_id INTEGER,
494
+ customer_id INTEGER,
495
+ customer_name TEXT,
496
+ customer_email TEXT,
497
+ order_status TEXT,
498
+ total_cents INTEGER,
499
+ item_count INTEGER,
500
+ first_item_name TEXT,
501
+ shipping_city TEXT,
502
+ shipping_state TEXT,
503
+ created_at TIMESTAMPTZ
504
+ );
505
+
506
+ CREATE TABLE product_performance_cache (
507
+ id SERIAL PRIMARY KEY,
508
+ product_id INTEGER,
509
+ product_name TEXT,
510
+ category_name TEXT,
511
+ total_sold INTEGER DEFAULT 0,
512
+ total_revenue_cents INTEGER DEFAULT 0,
513
+ avg_rating NUMERIC(3,2),
514
+ review_count INTEGER DEFAULT 0,
515
+ return_rate NUMERIC(5,2),
516
+ calculated_at TIMESTAMPTZ NOT NULL DEFAULT now()
517
+ );
518
+
519
+ CREATE TABLE customer_ltv_cache (
520
+ id SERIAL PRIMARY KEY,
521
+ customer_id INTEGER,
522
+ total_orders INTEGER DEFAULT 0,
523
+ total_spent_cents INTEGER DEFAULT 0,
524
+ first_order_at TIMESTAMPTZ,
525
+ last_order_at TIMESTAMPTZ,
526
+ avg_order_value_cents INTEGER DEFAULT 0,
527
+ predicted_ltv_cents INTEGER,
528
+ segment TEXT,
529
+ calculated_at TIMESTAMPTZ NOT NULL DEFAULT now()
530
+ );
531
+
532
+ -- ---------- 1.9 Site Analytics ----------
533
+
534
+ CREATE TABLE page_views (
535
+ id SERIAL PRIMARY KEY,
536
+ customer_id INTEGER, -- TECH DEBT: NO FK to customers (nullable for anonymous)
537
+ session_id TEXT,
538
+ page_url TEXT NOT NULL,
539
+ referrer TEXT,
540
+ device_type TEXT,
541
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
542
+ );
543
+
544
+ CREATE TABLE cart_events (
545
+ id SERIAL PRIMARY KEY,
546
+ customer_id INTEGER, -- TECH DEBT: NO FK to customers
547
+ session_id TEXT,
548
+ event_type TEXT NOT NULL, -- 'add', 'remove', 'update_qty', 'abandon'
549
+ product_id INTEGER,
550
+ variant_id INTEGER,
551
+ quantity INTEGER,
552
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
553
+ );
554
+
555
+ CREATE TABLE search_queries (
556
+ id SERIAL PRIMARY KEY,
557
+ customer_id INTEGER,
558
+ session_id TEXT,
559
+ query TEXT NOT NULL,
560
+ results_count INTEGER,
561
+ clicked_product_id INTEGER,
562
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
563
+ );
564
+
565
+ -- ---------- 1.10 Internal / Ops ----------
566
+
567
+ CREATE TABLE admin_users (
568
+ id SERIAL PRIMARY KEY,
569
+ email TEXT NOT NULL UNIQUE,
570
+ full_name TEXT NOT NULL,
571
+ role TEXT NOT NULL DEFAULT 'support',
572
+ is_active BOOLEAN DEFAULT true,
573
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
574
+ );
575
+
576
+ CREATE TABLE admin_audit_log (
577
+ id SERIAL PRIMARY KEY,
578
+ admin_user_id INTEGER NOT NULL REFERENCES admin_users(id),
579
+ action TEXT NOT NULL,
580
+ resource_type TEXT,
581
+ resource_id TEXT,
582
+ details TEXT,
583
+ ip_address TEXT,
584
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
585
+ );
586
+
587
+ CREATE TABLE system_settings (
588
+ id SERIAL PRIMARY KEY,
589
+ key TEXT NOT NULL UNIQUE,
590
+ value TEXT NOT NULL,
591
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
592
+ );
593
+
594
+ -- ---------- 1.11 Legacy & Abandoned ----------
595
+
596
+ CREATE TABLE old_orders_v1 (
597
+ id SERIAL PRIMARY KEY,
598
+ cust_email TEXT, -- different name than customer_id
599
+ order_total NUMERIC(10,2), -- different: dollars, not cents
600
+ order_status TEXT, -- different values than current orders.status
601
+ item_list TEXT, -- CSV of items in a single text field
602
+ placed_date TIMESTAMPTZ, -- different name than created_at
603
+ shipped_date TIMESTAMPTZ
604
+ );
605
+
606
+ CREATE TABLE temp_product_import_2023 (
607
+ id SERIAL PRIMARY KEY,
608
+ import_name TEXT,
609
+ import_sku TEXT,
610
+ import_price TEXT, -- stored as text, not numeric
611
+ import_category TEXT,
612
+ raw_csv_line TEXT,
613
+ imported_at TIMESTAMPTZ DEFAULT '2023-09-15'::timestamptz
614
+ );
615
+
616
+ CREATE TABLE legacy_analytics_events (
617
+ id SERIAL PRIMARY KEY,
618
+ event_name TEXT, -- different from page_views/cart_events structure
619
+ event_data TEXT, -- JSON-as-text blob
620
+ user_ref TEXT, -- string user reference, not integer FK
621
+ page_url TEXT,
622
+ timestamp TIMESTAMPTZ -- different column name than created_at
623
+ );
624
+
625
+ CREATE TABLE payment_methods_backup (
626
+ id SERIAL PRIMARY KEY,
627
+ cust_id INTEGER, -- old column name, doesn't match customers.id range
628
+ card_type TEXT, -- 'visa','mastercard','amex' — different from payments.method
629
+ last_four TEXT,
630
+ exp_month INTEGER,
631
+ exp_year INTEGER,
632
+ is_primary BOOLEAN DEFAULT false,
633
+ created_date TIMESTAMPTZ -- different name than created_at
634
+ );
635
+
636
+
637
+ -- ==========================================================================
638
+ -- 2. INDEXES
639
+ -- ==========================================================================
640
+
641
+ CREATE INDEX idx_customers_email ON customers(email);
642
+ CREATE INDEX idx_customers_created ON customers(created_at);
643
+ CREATE INDEX idx_customer_addresses_customer ON customer_addresses(customer_id);
644
+ CREATE INDEX idx_customer_segment_assignments_customer ON customer_segment_assignments(customer_id);
645
+ CREATE INDEX idx_loyalty_accounts_customer ON loyalty_accounts(customer_id);
646
+ CREATE INDEX idx_loyalty_transactions_account ON loyalty_transactions(loyalty_account_id);
647
+ CREATE INDEX idx_products_category ON products(category_id);
648
+ CREATE INDEX idx_products_seller ON products(seller_id);
649
+ CREATE INDEX idx_product_variants_product ON product_variants(product_id);
650
+ CREATE INDEX idx_inventory_levels_variant ON inventory_levels(variant_id);
651
+ CREATE INDEX idx_orders_customer ON orders(customer_id);
652
+ CREATE INDEX idx_orders_created ON orders(created_at);
653
+ CREATE INDEX idx_orders_status ON orders(status);
654
+ CREATE INDEX idx_order_items_order ON order_items(order_id);
655
+ CREATE INDEX idx_order_events_order ON order_events(order_id);
656
+ CREATE INDEX idx_order_events_created ON order_events(created_at);
657
+ CREATE INDEX idx_payments_order ON payments(order_id);
658
+ CREATE INDEX idx_shipments_order ON shipments(order_id);
659
+ CREATE INDEX idx_shipment_items_shipment ON shipment_items(shipment_id);
660
+ CREATE INDEX idx_returns_order ON returns(order_id);
661
+ CREATE INDEX idx_promotions_code ON promotions(code);
662
+ CREATE INDEX idx_email_sends_campaign ON email_sends(campaign_id);
663
+ CREATE INDEX idx_email_sends_customer ON email_sends(customer_id);
664
+ CREATE INDEX idx_utm_tracking_customer ON utm_tracking(customer_id);
665
+ CREATE INDEX idx_product_reviews_product ON product_reviews(product_id);
666
+ CREATE INDEX idx_review_helpfulness_review ON review_helpfulness(review_id);
667
+ CREATE INDEX idx_page_views_customer ON page_views(customer_id);
668
+ CREATE INDEX idx_page_views_created ON page_views(created_at);
669
+ CREATE INDEX idx_cart_events_customer ON cart_events(customer_id);
670
+ CREATE INDEX idx_search_queries_created ON search_queries(created_at);
671
+ CREATE INDEX idx_admin_audit_log_admin ON admin_audit_log(admin_user_id);
672
+ CREATE INDEX idx_admin_audit_log_created ON admin_audit_log(created_at);
673
+ CREATE INDEX idx_orders_denormalized_order ON orders_denormalized(order_id);
674
+ CREATE INDEX idx_orders_denormalized_created ON orders_denormalized(created_at);
675
+
676
+
677
+ -- ==========================================================================
678
+ -- 3. REFERENCE DATA
679
+ -- ==========================================================================
680
+
681
+ -- ---------- Categories (25, hierarchical) ----------
682
+ INSERT INTO categories (name, parent_id, slug, description) VALUES
683
+ ('Bedding', NULL, 'bedding', 'Sheets, duvets, pillows, mattress toppers'),
684
+ ('Kitchen', NULL, 'kitchen', 'Cookware, utensils, storage, appliances'),
685
+ ('Bath', NULL, 'bath', 'Towels, shower curtains, bath accessories'),
686
+ ('Outdoor', NULL, 'outdoor', 'Patio furniture, planters, outdoor decor'),
687
+ ('Home Decor', NULL, 'home-decor', 'Candles, art, mirrors, rugs');
688
+
689
+ INSERT INTO categories (name, parent_id, slug, description) VALUES
690
+ ('Sheets', 1, 'bedding-sheets', 'Flat sheets, fitted sheets, sheet sets'),
691
+ ('Duvets & Covers', 1, 'bedding-duvets', 'Duvet inserts and covers'),
692
+ ('Pillows', 1, 'bedding-pillows', 'Sleeping pillows and shams'),
693
+ ('Mattress Toppers', 1, 'bedding-toppers', 'Memory foam and down toppers'),
694
+ ('Cookware', 2, 'kitchen-cookware', 'Pots, pans, skillets'),
695
+ ('Utensils', 2, 'kitchen-utensils', 'Spatulas, tongs, whisks'),
696
+ ('Storage', 2, 'kitchen-storage', 'Containers, organizers, pantry'),
697
+ ('Small Appliances', 2, 'kitchen-appliances', 'Blenders, toasters, coffee makers'),
698
+ ('Towels', 3, 'bath-towels', 'Bath towels, hand towels, washcloths'),
699
+ ('Shower', 3, 'bath-shower', 'Curtains, caddies, mats'),
700
+ ('Accessories', 3, 'bath-accessories', 'Soap dispensers, mirrors, organizers'),
701
+ ('Patio Furniture', 4, 'outdoor-patio', 'Tables, chairs, loungers'),
702
+ ('Planters', 4, 'outdoor-planters', 'Pots, window boxes, stands'),
703
+ ('Outdoor Decor', 4, 'outdoor-decor', 'Lights, rugs, cushions'),
704
+ ('Candles', 5, 'decor-candles', 'Scented, decorative, candle holders'),
705
+ ('Wall Art', 5, 'decor-wall-art', 'Prints, frames, tapestries'),
706
+ ('Rugs', 5, 'decor-rugs', 'Area rugs, runners, mats'),
707
+ ('Mirrors', 5, 'decor-mirrors', 'Wall mirrors, floor mirrors, vanity'),
708
+ ('Throws & Blankets',1, 'bedding-throws', 'Throw blankets and weighted blankets'),
709
+ ('Knife Sets', 2, 'kitchen-knives', 'Chef knives, knife blocks, sharpeners');
710
+
711
+ -- ---------- Warehouses (5) ----------
712
+ INSERT INTO warehouses (name, code, city, state, country) VALUES
713
+ ('East Coast Hub', 'EC1', 'Edison', 'NJ', 'US'),
714
+ ('West Coast Hub', 'WC1', 'Ontario', 'CA', 'US'),
715
+ ('Central Warehouse', 'CW1', 'Louisville', 'KY', 'US'),
716
+ ('Southeast Fulfillment','SE1', 'Atlanta', 'GA', 'US'),
717
+ ('Pacific Northwest', 'PNW', 'Portland', 'OR', 'US');
718
+
719
+ -- ---------- Shipping Carriers (8) ----------
720
+ INSERT INTO shipping_carriers (name, code, tracking_url_template, is_active) VALUES
721
+ ('UPS', 'UPS', 'https://www.ups.com/track?tracknum={tracking}', true),
722
+ ('FedEx', 'FEDEX', 'https://www.fedex.com/fedextrack/?tracknumbers={tracking}', true),
723
+ ('USPS', 'USPS', 'https://tools.usps.com/go/TrackConfirmAction?tLabels={tracking}', true),
724
+ ('DHL', 'DHL', 'https://www.dhl.com/us-en/home/tracking.html?tracking-id={tracking}', true),
725
+ ('OnTrac', 'ONTRC', 'https://www.ontrac.com/tracking.asp?tracking={tracking}', true),
726
+ ('Amazon Logistics', 'AMZL', NULL, true),
727
+ ('LaserShip', 'LASER', NULL, true),
728
+ ('Veho', 'VEHO', NULL, false);
729
+
730
+ -- ---------- Customer Segments (10) ----------
731
+ INSERT INTO customer_segments (name, description) VALUES
732
+ ('VIP', 'Top spenders, >$1000 lifetime value'),
733
+ ('Regular', 'Active customers with 3+ orders'),
734
+ ('New', 'First order within last 90 days'),
735
+ ('At-Risk', 'No orders in last 180 days'),
736
+ ('Churned', 'No orders in last 365 days'),
737
+ ('High-Value', 'Average order value >$150'),
738
+ ('Bargain', 'Primarily uses promo codes'),
739
+ ('Marketplace', 'Primarily buys from marketplace sellers'),
740
+ ('Loyal', 'Enrolled in loyalty program, active'),
741
+ ('Dormant', 'Has account but never ordered');
742
+
743
+
744
+ -- ==========================================================================
745
+ -- 4. CORE ENTITY DATA
746
+ -- ==========================================================================
747
+
748
+ -- ---------- Customers (8,000) ----------
749
+ INSERT INTO customers (email, full_name, phone, mobile_phone, acquisition_source, created_at, is_active)
750
+ SELECT
751
+ lower(first) || '.' || lower(last) || floor(random() * 1000)::int || '@' ||
752
+ (ARRAY['gmail.com','yahoo.com','outlook.com','icloud.com','hotmail.com','protonmail.com','aol.com','mail.com'])[1 + floor(random() * 8)::int],
753
+ first || ' ' || last,
754
+ -- TECH DEBT: phone is original column, always populated
755
+ '(' || (200 + floor(random() * 800)::int) || ') ' || lpad(floor(random() * 1000)::int::text, 3, '0') || '-' || lpad(floor(random() * 10000)::int::text, 4, '0'),
756
+ -- TECH DEBT: mobile_phone added 2022, NULL for ~15% of all customers (~79% are post-2022)
757
+ CASE WHEN created_ts > '2022-01-01'::timestamptz OR random() < 0.3
758
+ THEN '+1' || (200 + floor(random() * 800)::int) || lpad(floor(random() * 10000000)::int::text, 7, '0')
759
+ ELSE NULL
760
+ END,
761
+ -- TECH DEBT: case-inconsistent acquisition_source
762
+ (ARRAY[
763
+ 'Google','Google','Google','google','GOOGLE',
764
+ 'Facebook','Facebook','facebook',
765
+ 'Instagram','instagram',
766
+ 'Organic','organic','ORGANIC',
767
+ 'Referral','referral',
768
+ 'Email','email',
769
+ 'TikTok','tiktok',
770
+ 'Direct','direct'
771
+ ])[1 + floor(random() * 21)::int],
772
+ created_ts,
773
+ CASE WHEN random() < 0.92 THEN true ELSE false END
774
+ FROM (
775
+ SELECT
776
+ g,
777
+ (ARRAY['Emma','Liam','Olivia','Noah','Ava','William','Sophia','James','Isabella','Oliver',
778
+ 'Mia','Benjamin','Charlotte','Elijah','Amelia','Lucas','Harper','Mason','Evelyn','Logan',
779
+ 'Luna','Alexander','Ella','Daniel','Chloe','Henry','Penelope','Sebastian','Layla','Jack',
780
+ 'Riley','Aiden','Zoey','Owen','Nora','Samuel','Lily','Jacob','Eleanor','David'])[1 + floor(random() * 40)::int] AS first,
781
+ (ARRAY['Smith','Johnson','Williams','Brown','Jones','Garcia','Miller','Davis','Rodriguez','Martinez',
782
+ 'Hernandez','Lopez','Gonzalez','Wilson','Anderson','Thomas','Taylor','Moore','Jackson','Martin',
783
+ 'Lee','Perez','Thompson','White','Harris','Sanchez','Clark','Ramirez','Lewis','Robinson',
784
+ 'Walker','Young','Allen','King','Wright','Scott','Torres','Nguyen','Hill','Flores'])[1 + floor(random() * 40)::int] AS last,
785
+ -- Pandemic-era founding: more customers in 2020-2021
786
+ '2020-01-15'::timestamptz + (power(random(), 0.6) * interval '1825 days') AS created_ts
787
+ FROM generate_series(1, 8000) AS g
788
+ ) AS src;
789
+
790
+ -- ---------- Customer Addresses (12,000) ----------
791
+ INSERT INTO customer_addresses (customer_id, label, street, city, state, zip, country, is_default, created_at)
792
+ SELECT
793
+ -- TECH DEBT: ~2% reference nonexistent customer IDs
794
+ CASE
795
+ WHEN g <= 11760 THEN 1 + floor(random() * 8000)::int
796
+ ELSE 8001 + floor(random() * 500)::int
797
+ END,
798
+ (ARRAY['home','home','home','work','work','other'])[1 + floor(random() * 6)::int],
799
+ floor(random() * 9999 + 1)::int || ' ' ||
800
+ (ARRAY['Oak','Maple','Cedar','Pine','Elm','Willow','Birch','Walnut','Main','Park',
801
+ 'Lake','River','Hill','Meadow','Spring','Valley','Sunset','Harbor','Forest','Garden'])[1 + floor(random() * 20)::int]
802
+ || ' ' ||
803
+ (ARRAY['St','Ave','Blvd','Dr','Ln','Way','Ct','Pl','Rd','Cir'])[1 + floor(random() * 10)::int],
804
+ (ARRAY['New York','Los Angeles','Chicago','Houston','Phoenix','Philadelphia','San Antonio','San Diego',
805
+ 'Dallas','Austin','Jacksonville','San Francisco','Columbus','Charlotte','Indianapolis',
806
+ 'Seattle','Denver','Nashville','Portland','Las Vegas'])[1 + floor(random() * 20)::int],
807
+ (ARRAY['NY','CA','IL','TX','AZ','PA','TX','CA','TX','TX','FL','CA','OH','NC','IN',
808
+ 'WA','CO','TN','OR','NV'])[1 + floor(random() * 20)::int],
809
+ lpad(floor(random() * 90000 + 10000)::int::text, 5, '0'),
810
+ 'US',
811
+ CASE WHEN g % 3 = 0 THEN true ELSE false END,
812
+ '2020-01-15'::timestamptz + (random() * interval '1825 days')
813
+ FROM generate_series(1, 12000) AS g;
814
+
815
+ -- ---------- Customer Segment Assignments (9,000) ----------
816
+ INSERT INTO customer_segment_assignments (customer_id, segment_id, segment_name, assigned_at)
817
+ SELECT
818
+ 1 + floor(random() * 8000)::int,
819
+ seg_id,
820
+ -- TECH DEBT: segment_name denormalized, ~12% out of sync with customer_segments.name
821
+ CASE
822
+ WHEN random() < 0.12 THEN (ARRAY['vip','VIP Customer','regular_customer','new_user','at-risk','high_value','bargain_hunter'])[1 + floor(random() * 7)::int]
823
+ ELSE (SELECT name FROM customer_segments WHERE id = seg_id)
824
+ END,
825
+ '2020-06-01'::timestamptz + (random() * interval '1700 days')
826
+ FROM (
827
+ SELECT g, 1 + floor(random() * 10)::int AS seg_id
828
+ FROM generate_series(1, 9000) AS g
829
+ ) AS src;
830
+
831
+ -- ---------- Loyalty Accounts (5,500) ----------
832
+ INSERT INTO loyalty_accounts (customer_id, points, tier, enrolled_at, updated_at)
833
+ SELECT
834
+ 1 + floor(random() * 8000)::int,
835
+ floor(random() * 5000)::int,
836
+ -- TECH DEBT: tier with case-inconsistent values
837
+ (ARRAY[
838
+ 'Bronze','Bronze','Bronze','Bronze',
839
+ 'Silver','Silver','Silver','silver','SILVER',
840
+ 'Gold','Gold','gold','GOLD',
841
+ 'Platinum','Platinum'
842
+ ])[1 + floor(random() * 15)::int],
843
+ enrolled_ts,
844
+ enrolled_ts + (random() * interval '365 days')
845
+ FROM (
846
+ SELECT g, '2020-06-01'::timestamptz + (random() * interval '1700 days') AS enrolled_ts
847
+ FROM generate_series(1, 5500) AS g
848
+ ) AS src;
849
+
850
+ -- ---------- Loyalty Transactions (18,000) ----------
851
+ INSERT INTO loyalty_transactions (loyalty_account_id, type, points, description, order_id, created_at)
852
+ SELECT
853
+ 1 + floor(random() * 5500)::int,
854
+ txn_type,
855
+ CASE txn_type
856
+ WHEN 'earn' THEN floor(random() * 200 + 10)::int
857
+ WHEN 'redeem' THEN -floor(random() * 500 + 50)::int
858
+ WHEN 'adjust' THEN floor(random() * 100 - 50)::int
859
+ WHEN 'expire' THEN -floor(random() * 300 + 100)::int
860
+ END,
861
+ CASE txn_type
862
+ WHEN 'earn' THEN 'Points earned from order'
863
+ WHEN 'redeem' THEN 'Points redeemed for discount'
864
+ WHEN 'adjust' THEN 'Manual adjustment by admin'
865
+ WHEN 'expire' THEN 'Points expired (12-month policy)'
866
+ END,
867
+ CASE WHEN txn_type IN ('earn','redeem') THEN 1 + floor(random() * 25000)::int ELSE NULL END,
868
+ '2020-09-01'::timestamptz + (power(random(), 0.5) * interval '1700 days')
869
+ FROM (
870
+ SELECT g,
871
+ (ARRAY['earn','earn','earn','earn','earn','earn','redeem','redeem','redeem','adjust','expire'])[1 + floor(random() * 11)::int] AS txn_type
872
+ FROM generate_series(1, 18000) AS g
873
+ ) AS src;
874
+
875
+
876
+ -- ==========================================================================
877
+ -- 5. PRODUCT DATA
878
+ -- ==========================================================================
879
+
880
+ -- ---------- Products (800) ----------
881
+ INSERT INTO products (name, slug, category_id, seller_id, price, price_cents, description, status, created_at)
882
+ SELECT
883
+ product_name,
884
+ lower(replace(replace(product_name, ' ', '-'), '''', '')) || '-' || g,
885
+ 1 + floor(random() * 25)::int,
886
+ -- ~20% are marketplace products (seller_id set), TECH DEBT: no FK
887
+ CASE WHEN random() < 0.20 THEN 1 + floor(random() * 80)::int ELSE NULL END,
888
+ price_val,
889
+ -- TECH DEBT: price_cents added 2023, NULL for ~40% of older products
890
+ CASE
891
+ WHEN created_ts > '2023-01-01'::timestamptz THEN (price_val * 100)::int
892
+ WHEN random() < 0.3 THEN (price_val * 100)::int
893
+ ELSE NULL
894
+ END,
895
+ 'Premium quality ' || lower(product_name) || ' for your home. Made with sustainable materials.',
896
+ (ARRAY['active','active','active','active','active','active','active','discontinued','draft'])[1 + floor(random() * 9)::int],
897
+ created_ts
898
+ FROM (
899
+ SELECT
900
+ g,
901
+ (ARRAY['Egyptian Cotton Sheet Set','Bamboo Pillowcase','Memory Foam Pillow','Linen Duvet Cover',
902
+ 'Waffle Weave Blanket','Down Alternative Comforter','Silk Pillowcase','Percale Sheet Set',
903
+ 'Cast Iron Skillet','Nonstick Pan Set','Chef Knife','Cutting Board Set',
904
+ 'Turkish Bath Towel','Waffle Bath Robe','Shower Caddy','Bath Mat Set',
905
+ 'Patio Lounge Chair','Outdoor Planter','Solar String Lights','Ceramic Vase',
906
+ 'Soy Candle Set','Wall Print','Area Rug','Throw Pillow',
907
+ 'Weighted Blanket','Mattress Topper','Knife Block Set','Spice Rack',
908
+ 'Hand Towel Set','Vanity Mirror','Outdoor Cushion','Table Runner'])[1 + floor(random() * 32)::int]
909
+ || ' ' ||
910
+ (ARRAY['Classic','Premium','Luxe','Essential','Heritage','Modern','Artisan','Coastal'])[1 + floor(random() * 8)::int]
911
+ AS product_name,
912
+ round((19.99 + random() * 280)::numeric, 2) AS price_val,
913
+ '2020-01-15'::timestamptz + (power(random(), 0.7) * interval '1825 days') AS created_ts
914
+ FROM generate_series(1, 800) AS g
915
+ ) AS src;
916
+
917
+ -- ---------- Product Variants (3,200) ----------
918
+ INSERT INTO product_variants (product_id, sku, name, price_cents, weight_oz, is_active, created_at)
919
+ SELECT
920
+ product_id,
921
+ 'NVM-' || lpad(product_id::text, 4, '0') || '-' || lpad(g::text, 3, '0'),
922
+ size_val || ' / ' || color_val,
923
+ (2999 + floor(random() * 20000))::int,
924
+ (8 + floor(random() * 120))::int,
925
+ CASE WHEN random() < 0.9 THEN true ELSE false END,
926
+ '2020-02-01'::timestamptz + (random() * interval '1800 days')
927
+ FROM (
928
+ SELECT
929
+ g,
930
+ ((g - 1) / 4) + 1 AS product_id,
931
+ (ARRAY['Twin','Full','Queen','King','One Size','Small','Medium','Large'])[1 + floor(random() * 8)::int] AS size_val,
932
+ (ARRAY['White','Ivory','Gray','Navy','Sage','Blush','Charcoal','Sand','Ocean','Terracotta'])[1 + floor(random() * 10)::int] AS color_val
933
+ FROM generate_series(1, 3200) AS g
934
+ ) AS src;
935
+
936
+ -- ---------- Product Images (4,000) ----------
937
+ INSERT INTO product_images (product_id, url, alt_text, position, created_at)
938
+ SELECT
939
+ 1 + floor(random() * 800)::int,
940
+ 'https://cdn.novamart.com/products/' || md5(random()::text) || '.jpg',
941
+ 'Product image',
942
+ (g % 5),
943
+ '2020-02-01'::timestamptz + (random() * interval '1800 days')
944
+ FROM generate_series(1, 4000) AS g;
945
+
946
+ -- ---------- Product Tags (2,500) ----------
947
+ INSERT INTO product_tags (product_id, tag, created_at)
948
+ SELECT
949
+ 1 + floor(random() * 800)::int,
950
+ (ARRAY['organic','sustainable','bestseller','new-arrival','sale','eco-friendly','handmade',
951
+ 'luxury','trending','limited-edition','bundle','gift-idea','seasonal','clearance','exclusive'])[1 + floor(random() * 15)::int],
952
+ '2020-03-01'::timestamptz + (random() * interval '1800 days')
953
+ FROM generate_series(1, 2500) AS g;
954
+
955
+ -- ---------- Inventory Levels (3,200) ----------
956
+ INSERT INTO inventory_levels (variant_id, warehouse_id, quantity, reorder_point, updated_at)
957
+ SELECT
958
+ g, -- TECH DEBT: no FK to product_variants
959
+ 1 + floor(random() * 5)::int,
960
+ floor(random() * 200)::int,
961
+ (ARRAY[5, 10, 15, 20, 25])[1 + floor(random() * 5)::int],
962
+ now() - (random() * interval '30 days')
963
+ FROM generate_series(1, 3200) AS g;
964
+
965
+
966
+ -- ==========================================================================
967
+ -- 6. MARKETPLACE DATA
968
+ -- ==========================================================================
969
+
970
+ -- ---------- Sellers (80) ----------
971
+ INSERT INTO sellers (company_name, contact_email, commission_rate, status, joined_at)
972
+ SELECT
973
+ seller_name || ' ' || seller_suffix,
974
+ lower(seller_name) || '@' || lower(seller_suffix) || '.com',
975
+ (ARRAY[12.00, 15.00, 15.00, 15.00, 18.00, 20.00])[1 + floor(random() * 6)::int],
976
+ (ARRAY['active','active','active','active','active','active','suspended','pending'])[1 + floor(random() * 8)::int],
977
+ '2022-01-01'::timestamptz + (random() * interval '1095 days')
978
+ FROM (
979
+ SELECT
980
+ g,
981
+ (ARRAY['Artisan','Heritage','Pacific','Summit','Golden','Nordic','Urban','Coastal',
982
+ 'Evergreen','Sunset','Harvest','Alpine','Terra','Atlas','Bloom'])[((g-1) % 15) + 1] AS seller_name,
983
+ (ARRAY['Home Co','Living','Goods','Craft','Designs','Supply'])[((g-1) / 15) + 1] AS seller_suffix
984
+ FROM generate_series(1, 80) AS g
985
+ ) AS src;
986
+
987
+ -- ---------- Seller Applications (120) ----------
988
+ INSERT INTO seller_applications (company_name, contact_email, status, applied_at, reviewed_at, notes)
989
+ SELECT
990
+ 'Applicant Store ' || g,
991
+ 'apply' || g || '@seller-app.com',
992
+ (ARRAY['approved','approved','approved','rejected','rejected','pending','pending','pending'])[1 + floor(random() * 8)::int],
993
+ applied_ts,
994
+ CASE WHEN random() < 0.7 THEN applied_ts + (random() * interval '14 days') ELSE NULL END,
995
+ CASE WHEN random() < 0.3 THEN 'Reviewed by marketplace ops team' ELSE NULL END
996
+ FROM (
997
+ SELECT g, '2022-01-01'::timestamptz + (random() * interval '1095 days') AS applied_ts
998
+ FROM generate_series(1, 120) AS g
999
+ ) AS src;
1000
+
1001
+
1002
+ -- ==========================================================================
1003
+ -- 7. ORDER & TRANSACTION DATA
1004
+ -- ==========================================================================
1005
+
1006
+ -- ---------- Orders (25,000) ----------
1007
+ -- Pandemic growth curve: 2020 ~2K, 2021 ~6K peak, 2022-2025 normalization
1008
+ INSERT INTO orders (customer_id, customer_email, status, subtotal_cents, shipping_cost, tax_cents, total_cents, shipping_address_id, promotion_id, created_at)
1009
+ SELECT
1010
+ cust_id,
1011
+ -- TECH DEBT: denormalized customer_email
1012
+ (SELECT email FROM customers WHERE id = cust_id),
1013
+ (ARRAY['delivered','delivered','delivered','delivered','delivered','delivered',
1014
+ 'shipped','shipped','processing','confirmed','pending','canceled','returned'])[1 + floor(random() * 13)::int],
1015
+ subtotal,
1016
+ -- TECH DEBT: shipping_cost in dollars pre-2023-06, cents after. Old data NOT migrated
1017
+ CASE
1018
+ WHEN order_ts < '2023-06-01'::timestamptz THEN round((random() * 15 + 5)::numeric, 2)
1019
+ ELSE round((random() * 1500 + 500)::numeric, 2) -- cents (5.00-20.00 in cents = 500-2000)
1020
+ END,
1021
+ floor(subtotal * 0.08)::int,
1022
+ subtotal + floor(subtotal * 0.08)::int + floor(random() * 1500 + 500)::int, -- TECH DEBT: shipping component is independent of shipping_cost column (totals don't reconcile)
1023
+ CASE WHEN random() < 0.8 THEN 1 + floor(random() * 12000)::int ELSE NULL END,
1024
+ CASE WHEN random() < 0.15 THEN 1 + floor(random() * 200)::int ELSE NULL END,
1025
+ order_ts
1026
+ FROM (
1027
+ SELECT
1028
+ g,
1029
+ 1 + floor(random() * 8000)::int AS cust_id,
1030
+ (3000 + floor(random() * 25000))::int AS subtotal,
1031
+ -- Pandemic growth curve
1032
+ CASE
1033
+ WHEN g <= 2000 THEN '2020-03-01'::timestamptz + (random() * interval '305 days')
1034
+ WHEN g <= 8000 THEN '2021-01-01'::timestamptz + (random() * interval '365 days')
1035
+ WHEN g <= 13000 THEN '2022-01-01'::timestamptz + (random() * interval '365 days')
1036
+ WHEN g <= 18000 THEN '2023-01-01'::timestamptz + (random() * interval '365 days')
1037
+ WHEN g <= 22500 THEN '2024-01-01'::timestamptz + (random() * interval '365 days')
1038
+ ELSE '2025-01-01'::timestamptz + (random() * interval '56 days')
1039
+ END AS order_ts
1040
+ FROM generate_series(1, 25000) AS g
1041
+ ) AS src;
1042
+
1043
+ -- ---------- Order Items (55,000) ----------
1044
+ INSERT INTO order_items (order_id, product_variant_id, product_name, quantity, unit_price_cents, total_cents, created_at)
1045
+ SELECT
1046
+ order_id,
1047
+ -- TECH DEBT: no FK to product_variants
1048
+ 1 + floor(random() * 3200)::int,
1049
+ (ARRAY['Egyptian Cotton Sheet Set','Bamboo Pillowcase','Memory Foam Pillow','Linen Duvet Cover',
1050
+ 'Cast Iron Skillet','Nonstick Pan Set','Turkish Bath Towel','Waffle Bath Robe',
1051
+ 'Patio Lounge Chair','Soy Candle Set','Area Rug','Throw Pillow',
1052
+ 'Weighted Blanket','Mattress Topper','Chef Knife','Cutting Board Set',
1053
+ 'Solar String Lights','Ceramic Vase','Wall Print','Hand Towel Set'])[1 + floor(random() * 20)::int],
1054
+ qty,
1055
+ unit_price,
1056
+ unit_price * qty,
1057
+ '2020-03-01'::timestamptz + (power(g::float / 55000, 1.0) * interval '1795 days')
1058
+ FROM (
1059
+ SELECT
1060
+ g,
1061
+ 1 + floor(random() * 25000)::int AS order_id,
1062
+ (ARRAY[1,1,1,1,1,1,2,2,3])[1 + floor(random() * 9)::int] AS qty,
1063
+ (1999 + floor(random() * 15000))::int AS unit_price
1064
+ FROM generate_series(1, 55000) AS g
1065
+ ) AS src;
1066
+
1067
+ -- ---------- Order Events (60,000) ----------
1068
+ INSERT INTO order_events (order_id, event_type, description, created_at)
1069
+ SELECT
1070
+ -- TECH DEBT: no FK to orders
1071
+ 1 + floor(random() * 25000)::int,
1072
+ (ARRAY['placed','placed','confirmed','confirmed','processing','processing',
1073
+ 'shipped','shipped','shipped','delivered','delivered','delivered',
1074
+ 'canceled','returned'])[1 + floor(random() * 14)::int],
1075
+ (ARRAY['Order placed by customer','Payment confirmed','Order sent to fulfillment',
1076
+ 'Shipped via carrier','Out for delivery','Delivered to customer',
1077
+ 'Canceled by customer','Return initiated','Refund processed'])[1 + floor(random() * 9)::int],
1078
+ '2020-03-01'::timestamptz + (power(g::float / 60000, 1.0) * interval '1795 days')
1079
+ FROM generate_series(1, 60000) AS g;
1080
+
1081
+ -- ---------- Payments (26,000) ----------
1082
+ INSERT INTO payments (order_id, method, status, amount_cents, provider_ref, created_at)
1083
+ SELECT
1084
+ -- TECH DEBT: ~1.5% reference nonexistent orders (orphaned from deleted test orders)
1085
+ CASE
1086
+ WHEN g <= 25610 THEN 1 + floor(random() * 25000)::int
1087
+ ELSE 25001 + floor(random() * 500)::int
1088
+ END,
1089
+ (ARRAY['credit_card','credit_card','credit_card','credit_card','debit_card','debit_card',
1090
+ 'paypal','paypal','apple_pay','google_pay','gift_card','klarna'])[1 + floor(random() * 12)::int],
1091
+ (ARRAY['completed','completed','completed','completed','completed','completed','completed',
1092
+ 'pending','failed','refunded'])[1 + floor(random() * 10)::int],
1093
+ (3000 + floor(random() * 30000))::int,
1094
+ 'pay_' || md5(random()::text || g::text),
1095
+ '2020-03-01'::timestamptz + (power(g::float / 26000, 1.0) * interval '1795 days')
1096
+ FROM generate_series(1, 26000) AS g;
1097
+
1098
+ -- ---------- Refunds (2,500) ----------
1099
+ INSERT INTO refunds (payment_id, amount_cents, reason, status, created_at)
1100
+ SELECT
1101
+ 1 + floor(random() * 26000)::int,
1102
+ (1000 + floor(random() * 15000))::int,
1103
+ (ARRAY['Defective product','Wrong item shipped','Changed mind','Item not as described',
1104
+ 'Late delivery','Duplicate order','Quality issue','Better price found'])[1 + floor(random() * 8)::int],
1105
+ (ARRAY['completed','completed','completed','completed','pending','processing','denied'])[1 + floor(random() * 7)::int],
1106
+ '2020-06-01'::timestamptz + (power(random(), 0.5) * interval '1700 days')
1107
+ FROM generate_series(1, 2500) AS g;
1108
+
1109
+ -- ---------- Gift Cards (500) ----------
1110
+ INSERT INTO gift_cards (code, initial_cents, balance_cents, issued_to, status, expires_at, created_at)
1111
+ SELECT
1112
+ 'NVM-' || upper(substr(md5(random()::text), 1, 4)) || '-' || upper(substr(md5(random()::text), 1, 4)),
1113
+ initial,
1114
+ CASE WHEN random() < 0.3 THEN 0 ELSE floor(initial * random())::int END,
1115
+ CASE WHEN random() < 0.7 THEN 1 + floor(random() * 8000)::int ELSE NULL END,
1116
+ (ARRAY['active','active','active','active','redeemed','expired','disabled'])[1 + floor(random() * 7)::int],
1117
+ CASE WHEN random() < 0.8 THEN now() + (random() * interval '365 days') ELSE now() - (random() * interval '180 days') END,
1118
+ '2020-06-01'::timestamptz + (random() * interval '1700 days')
1119
+ FROM (
1120
+ SELECT g, (ARRAY[2500, 5000, 7500, 10000, 15000, 25000])[1 + floor(random() * 6)::int] AS initial
1121
+ FROM generate_series(1, 500) AS g
1122
+ ) AS src;
1123
+
1124
+ -- ---------- Gift Card Transactions (1,200) ----------
1125
+ INSERT INTO gift_card_transactions (gift_card_id, order_id, amount_cents, type, created_at)
1126
+ SELECT
1127
+ 1 + floor(random() * 500)::int,
1128
+ CASE WHEN txn_type IN ('redeem','refund') THEN 1 + floor(random() * 25000)::int ELSE NULL END,
1129
+ (ARRAY[2500, 5000, 1000, 3000, 7500])[1 + floor(random() * 5)::int],
1130
+ txn_type,
1131
+ '2020-09-01'::timestamptz + (random() * interval '1700 days')
1132
+ FROM (
1133
+ SELECT g, (ARRAY['issue','issue','redeem','redeem','redeem','refund'])[1 + floor(random() * 6)::int] AS txn_type
1134
+ FROM generate_series(1, 1200) AS g
1135
+ ) AS src;
1136
+
1137
+
1138
+ -- ==========================================================================
1139
+ -- 8. SHIPPING & FULFILLMENT DATA
1140
+ -- ==========================================================================
1141
+
1142
+ -- ---------- Shipments (22,000) ----------
1143
+ INSERT INTO shipments (order_id, warehouse_id, carrier, carrier_id, tracking_number, status, shipped_at, delivered_at, created_at)
1144
+ SELECT
1145
+ 1 + floor(random() * 25000)::int,
1146
+ 1 + floor(random() * 5)::int,
1147
+ -- TECH DEBT: carrier text always populated
1148
+ (ARRAY['UPS','UPS','FedEx','FedEx','USPS','USPS','DHL','OnTrac'])[1 + floor(random() * 8)::int],
1149
+ -- TECH DEBT: carrier_id added 2024, NULL for ~60% of older shipments
1150
+ CASE
1151
+ WHEN ship_ts > '2024-01-01'::timestamptz THEN 1 + floor(random() * 8)::int
1152
+ WHEN random() < 0.15 THEN 1 + floor(random() * 8)::int
1153
+ ELSE NULL
1154
+ END,
1155
+ 'NVM' || upper(substr(md5(random()::text), 1, 12)),
1156
+ (ARRAY['delivered','delivered','delivered','delivered','delivered','delivered',
1157
+ 'in_transit','in_transit','shipped','pending','returned'])[1 + floor(random() * 11)::int],
1158
+ ship_ts,
1159
+ CASE WHEN random() < 0.85 THEN ship_ts + ((2 + random() * 8) * interval '1 day') ELSE NULL END,
1160
+ ship_ts - (random() * interval '2 days')
1161
+ FROM (
1162
+ SELECT
1163
+ g,
1164
+ '2020-03-15'::timestamptz + (power(g::float / 22000, 1.0) * interval '1780 days') AS ship_ts
1165
+ FROM generate_series(1, 22000) AS g
1166
+ ) AS src;
1167
+
1168
+ -- ---------- Shipment Items (48,000) ----------
1169
+ INSERT INTO shipment_items (shipment_id, order_item_id, quantity)
1170
+ SELECT
1171
+ -- TECH DEBT: no FK to shipments
1172
+ 1 + floor(random() * 22000)::int,
1173
+ 1 + floor(random() * 55000)::int,
1174
+ (ARRAY[1,1,1,1,1,2,2,3])[1 + floor(random() * 8)::int]
1175
+ FROM generate_series(1, 48000) AS g;
1176
+
1177
+ -- ---------- Returns (3,000) ----------
1178
+ INSERT INTO returns (order_id, customer_id, reason, status, created_at, resolved_at)
1179
+ SELECT
1180
+ 1 + floor(random() * 25000)::int,
1181
+ 1 + floor(random() * 8000)::int,
1182
+ -- TECH DEBT: reason with case-inconsistent values
1183
+ (ARRAY[
1184
+ 'Defective','Defective','defective','DEFECTIVE',
1185
+ 'Wrong Item','Wrong Item','wrong_item','WRONG ITEM',
1186
+ 'Not as described','not_as_described',
1187
+ 'Changed mind','Changed Mind','changed_mind',
1188
+ 'Too small','Too large',
1189
+ 'Better price elsewhere','Arrived late'
1190
+ ])[1 + floor(random() * 17)::int],
1191
+ (ARRAY['requested','approved','approved','approved','completed','completed','completed','denied'])[1 + floor(random() * 8)::int],
1192
+ return_ts,
1193
+ CASE WHEN random() < 0.7 THEN return_ts + (random() * interval '14 days') ELSE NULL END
1194
+ FROM (
1195
+ SELECT g, '2020-06-01'::timestamptz + (power(random(), 0.5) * interval '1700 days') AS return_ts
1196
+ FROM generate_series(1, 3000) AS g
1197
+ ) AS src;
1198
+
1199
+ -- ---------- Return Items (4,500) ----------
1200
+ INSERT INTO return_items (return_id, order_item_id, quantity, condition)
1201
+ SELECT
1202
+ 1 + floor(random() * 3000)::int,
1203
+ 1 + floor(random() * 55000)::int,
1204
+ 1,
1205
+ (ARRAY['unopened','unopened','opened','opened','damaged','defective'])[1 + floor(random() * 6)::int]
1206
+ FROM generate_series(1, 4500) AS g;
1207
+
1208
+
1209
+ -- ==========================================================================
1210
+ -- 9. MARKETING & PROMOTIONS DATA
1211
+ -- ==========================================================================
1212
+
1213
+ -- ---------- Promotions (200) ----------
1214
+ INSERT INTO promotions (code, name, type, value, min_order_cents, max_uses, times_used, starts_at, ends_at, is_active, created_at)
1215
+ SELECT
1216
+ upper(promo_prefix || floor(random() * 100)::int),
1217
+ promo_prefix || ' ' || (ARRAY['Sale','Special','Savings','Deal','Offer'])[1 + floor(random() * 5)::int],
1218
+ promo_type,
1219
+ CASE promo_type
1220
+ WHEN 'percentage' THEN (ARRAY[10, 15, 20, 25, 30])[1 + floor(random() * 5)::int]
1221
+ WHEN 'fixed_amount' THEN (ARRAY[5, 10, 15, 20, 50])[1 + floor(random() * 5)::int]
1222
+ WHEN 'free_shipping' THEN 0
1223
+ WHEN 'bogo' THEN 50
1224
+ END,
1225
+ CASE WHEN random() < 0.6 THEN (ARRAY[2500, 5000, 7500, 10000])[1 + floor(random() * 4)::int] ELSE NULL END,
1226
+ CASE WHEN random() < 0.5 THEN (ARRAY[100, 500, 1000, 5000])[1 + floor(random() * 4)::int] ELSE NULL END,
1227
+ floor(random() * 500)::int,
1228
+ start_ts,
1229
+ CASE WHEN random() < 0.7 THEN start_ts + ((7 + floor(random() * 83)) * interval '1 day') ELSE NULL END,
1230
+ CASE WHEN random() < 0.3 THEN true ELSE false END,
1231
+ start_ts
1232
+ FROM (
1233
+ SELECT
1234
+ g,
1235
+ (ARRAY['WELCOME','SUMMER','WINTER','SPRING','FALL','FLASH','HOLIDAY','BDAY',
1236
+ 'VIP','SAVE','DEAL','CLEARANCE','NEW','THANKS','LOYALTY'])[1 + floor(random() * 15)::int] AS promo_prefix,
1237
+ (ARRAY['percentage','percentage','percentage','fixed_amount','fixed_amount','free_shipping','bogo'])[1 + floor(random() * 7)::int] AS promo_type,
1238
+ '2020-03-01'::timestamptz + (random() * interval '1795 days') AS start_ts
1239
+ FROM generate_series(1, 200) AS g
1240
+ ) AS src;
1241
+
1242
+ -- ---------- Promotion Usages (8,000) ----------
1243
+ INSERT INTO promotion_usages (promotion_id, order_id, customer_id, discount_cents, created_at)
1244
+ SELECT
1245
+ -- TECH DEBT: no FK to promotions or orders
1246
+ 1 + floor(random() * 200)::int,
1247
+ 1 + floor(random() * 25000)::int,
1248
+ 1 + floor(random() * 8000)::int,
1249
+ (ARRAY[500, 1000, 1500, 2000, 2500, 3000, 5000])[1 + floor(random() * 7)::int],
1250
+ '2020-06-01'::timestamptz + (power(random(), 0.5) * interval '1700 days')
1251
+ FROM generate_series(1, 8000) AS g;
1252
+
1253
+ -- ---------- Email Campaigns (50) ----------
1254
+ INSERT INTO email_campaigns (name, subject, type, status, sent_at, created_at)
1255
+ SELECT
1256
+ campaign_name || ' - ' || g,
1257
+ CASE campaign_type
1258
+ WHEN 'promotional' THEN (ARRAY['Dont miss our biggest sale!','New arrivals just dropped','Your exclusive deal inside'])[1 + floor(random() * 3)::int]
1259
+ WHEN 'transactional' THEN (ARRAY['Your order has shipped','Welcome to NovaMart','Your receipt'])[1 + floor(random() * 3)::int]
1260
+ WHEN 'retention' THEN (ARRAY['We miss you!','Time for a refresh?','Your points are expiring'])[1 + floor(random() * 3)::int]
1261
+ WHEN 'winback' THEN (ARRAY['Come back for 20% off','Its been a while...','Special offer just for you'])[1 + floor(random() * 3)::int]
1262
+ END,
1263
+ campaign_type,
1264
+ (ARRAY['sent','sent','sent','sent','draft','scheduled'])[1 + floor(random() * 6)::int],
1265
+ CASE WHEN random() < 0.8 THEN '2020-06-01'::timestamptz + (random() * interval '1700 days') ELSE NULL END,
1266
+ '2020-06-01'::timestamptz + (random() * interval '1700 days')
1267
+ FROM (
1268
+ SELECT
1269
+ g,
1270
+ (ARRAY['Summer Sale','Winter Clearance','New Arrivals','Flash Sale','Holiday Special',
1271
+ 'Welcome Series','Loyalty Reward','Re-engagement','Anniversary','VIP Preview'])[1 + floor(random() * 10)::int] AS campaign_name,
1272
+ (ARRAY['promotional','promotional','promotional','transactional','retention','winback'])[1 + floor(random() * 6)::int] AS campaign_type
1273
+ FROM generate_series(1, 50) AS g
1274
+ ) AS src;
1275
+
1276
+ -- ---------- Email Sends (30,000) ----------
1277
+ INSERT INTO email_sends (campaign_id, customer_id, status, opened_at, clicked_at, created_at)
1278
+ SELECT
1279
+ 1 + floor(random() * 50)::int,
1280
+ 1 + floor(random() * 8000)::int,
1281
+ (ARRAY['delivered','delivered','delivered','delivered','bounced','unsubscribed'])[1 + floor(random() * 6)::int],
1282
+ CASE WHEN random() < 0.35 THEN sent_ts + (random() * interval '3 days') ELSE NULL END,
1283
+ CASE WHEN random() < 0.12 THEN sent_ts + (random() * interval '3 days') ELSE NULL END,
1284
+ sent_ts
1285
+ FROM (
1286
+ SELECT g, '2020-06-01'::timestamptz + (power(random(), 0.5) * interval '1700 days') AS sent_ts
1287
+ FROM generate_series(1, 30000) AS g
1288
+ ) AS src;
1289
+
1290
+ -- ---------- UTM Tracking (15,000) ----------
1291
+ INSERT INTO utm_tracking (customer_id, utm_source, utm_medium, utm_campaign, utm_content, landing_page, order_id, created_at)
1292
+ SELECT
1293
+ -- TECH DEBT: no FK to customers
1294
+ CASE WHEN random() < 0.7 THEN 1 + floor(random() * 8000)::int ELSE NULL END,
1295
+ (ARRAY['google','google','google','facebook','facebook','instagram','instagram',
1296
+ 'tiktok','email','email','pinterest','bing','twitter'])[1 + floor(random() * 13)::int],
1297
+ (ARRAY['cpc','cpc','organic','social','social','email','display','referral'])[1 + floor(random() * 8)::int],
1298
+ (ARRAY['summer_sale','winter_clearance','new_arrivals','retargeting','brand_awareness',
1299
+ 'flash_sale','holiday_2024','spring_launch','loyalty_program','back_to_school'])[1 + floor(random() * 10)::int],
1300
+ CASE WHEN random() < 0.5 THEN (ARRAY['hero_banner','sidebar','carousel','popup','footer'])[1 + floor(random() * 5)::int] ELSE NULL END,
1301
+ (ARRAY['/','/','/collections/bedding','/collections/kitchen','/collections/bath',
1302
+ '/collections/outdoor','/collections/sale','/products/bestseller'])[1 + floor(random() * 8)::int],
1303
+ CASE WHEN random() < 0.2 THEN 1 + floor(random() * 25000)::int ELSE NULL END,
1304
+ '2020-06-01'::timestamptz + (power(random(), 0.4) * interval '1700 days')
1305
+ FROM generate_series(1, 15000) AS g;
1306
+
1307
+
1308
+ -- ==========================================================================
1309
+ -- 10. REVIEWS DATA
1310
+ -- ==========================================================================
1311
+
1312
+ -- ---------- Product Reviews (6,000) ----------
1313
+ INSERT INTO product_reviews (product_id, customer_id, rating, rating_decimal, title, body, is_verified, created_at)
1314
+ SELECT
1315
+ 1 + floor(random() * 800)::int,
1316
+ 1 + floor(random() * 8000)::int,
1317
+ rating_int,
1318
+ -- TECH DEBT: rating_decimal added 2024, NULL for ~70% of older reviews
1319
+ CASE
1320
+ WHEN review_ts > '2024-01-01'::timestamptz THEN rating_int + round((random() * 0.8 - 0.4)::numeric, 1)
1321
+ WHEN random() < 0.1 THEN rating_int + round((random() * 0.8 - 0.4)::numeric, 1)
1322
+ ELSE NULL
1323
+ END,
1324
+ (ARRAY['Love it!','Great quality','Just okay','Not what I expected','Perfect for our home',
1325
+ 'Highly recommend','Good value','Disappointed','Beautiful design','Exceeded expectations',
1326
+ 'Decent for the price','Amazing product','Would buy again','Meh','Exactly as described'])[1 + floor(random() * 15)::int],
1327
+ (ARRAY['This is exactly what I was looking for. Great quality and fast shipping.',
1328
+ 'The material feels premium and the color is accurate to the photos.',
1329
+ 'Its okay but not as soft as I expected for the price.',
1330
+ 'Arrived damaged. Customer service was helpful though.',
1331
+ 'We bought two of these and love them both. Highly recommend!',
1332
+ 'Perfect addition to our bedroom. Looks and feels luxurious.',
1333
+ 'Decent product but the stitching could be better.',
1334
+ 'Not worth the premium price. You can find similar quality elsewhere.',
1335
+ 'Bought this as a gift and the recipient loved it.',
1336
+ 'The color was slightly different from the photo but still nice.'])[1 + floor(random() * 10)::int],
1337
+ random() < 0.65,
1338
+ review_ts
1339
+ FROM (
1340
+ SELECT
1341
+ g,
1342
+ (ARRAY[1,2,3,3,4,4,4,5,5,5])[1 + floor(random() * 10)::int] AS rating_int,
1343
+ '2020-06-01'::timestamptz + (power(random(), 0.5) * interval '1700 days') AS review_ts
1344
+ FROM generate_series(1, 6000) AS g
1345
+ ) AS src;
1346
+
1347
+ -- ---------- Review Responses (1,500) ----------
1348
+ INSERT INTO review_responses (review_id, responder, body, created_at)
1349
+ SELECT
1350
+ 1 + floor(random() * 6000)::int,
1351
+ CASE WHEN random() < 0.7 THEN 'NovaMart Team' ELSE 'Seller: ' || (ARRAY['Artisan Home Co','Heritage Living','Pacific Goods','Summit Craft'])[1 + floor(random() * 4)::int] END,
1352
+ (ARRAY['Thank you for your feedback! We are glad you love it.',
1353
+ 'We are sorry to hear about your experience. Please reach out to our support team.',
1354
+ 'Thanks for the kind words! We hope you enjoy it for years to come.',
1355
+ 'We appreciate your honest review. We have forwarded your feedback to our quality team.',
1356
+ 'Sorry for the inconvenience. We have issued a replacement.'])[1 + floor(random() * 5)::int],
1357
+ '2020-09-01'::timestamptz + (power(random(), 0.5) * interval '1650 days')
1358
+ FROM generate_series(1, 1500) AS g;
1359
+
1360
+ -- ---------- Review Helpfulness (8,000) ----------
1361
+ INSERT INTO review_helpfulness (review_id, customer_id, helpful, created_at)
1362
+ SELECT
1363
+ -- TECH DEBT: no FK to product_reviews or customers
1364
+ 1 + floor(random() * 6000)::int,
1365
+ 1 + floor(random() * 8000)::int,
1366
+ random() < 0.75,
1367
+ '2020-09-01'::timestamptz + (power(random(), 0.5) * interval '1650 days')
1368
+ FROM generate_series(1, 8000) AS g;
1369
+
1370
+
1371
+ -- ==========================================================================
1372
+ -- 11. SITE ANALYTICS DATA
1373
+ -- ==========================================================================
1374
+
1375
+ -- ---------- Page Views (20,000) ----------
1376
+ INSERT INTO page_views (customer_id, session_id, page_url, referrer, device_type, created_at)
1377
+ SELECT
1378
+ -- TECH DEBT: no FK to customers, nullable for anonymous
1379
+ CASE WHEN random() < 0.6 THEN 1 + floor(random() * 8000)::int ELSE NULL END,
1380
+ 'sess_' || md5(random()::text || g::text),
1381
+ (ARRAY['/','/collections/bedding','/collections/kitchen','/collections/bath','/collections/outdoor',
1382
+ '/collections/sale','/products/' || floor(random() * 800 + 1)::int,'/cart','/checkout',
1383
+ '/account','/account/orders','/search','/about','/contact'])[1 + floor(random() * 14)::int],
1384
+ (ARRAY['https://google.com','https://facebook.com','https://instagram.com',
1385
+ 'https://pinterest.com','https://tiktok.com',NULL,NULL,NULL])[1 + floor(random() * 8)::int],
1386
+ (ARRAY['desktop','desktop','desktop','mobile','mobile','mobile','mobile','tablet'])[1 + floor(random() * 8)::int],
1387
+ '2021-01-01'::timestamptz + (power(random(), 0.35) * interval '1520 days')
1388
+ FROM generate_series(1, 20000) AS g;
1389
+
1390
+ -- ---------- Cart Events (15,000) ----------
1391
+ INSERT INTO cart_events (customer_id, session_id, event_type, product_id, variant_id, quantity, created_at)
1392
+ SELECT
1393
+ -- TECH DEBT: no FK to customers
1394
+ CASE WHEN random() < 0.7 THEN 1 + floor(random() * 8000)::int ELSE NULL END,
1395
+ 'sess_' || md5(random()::text || g::text),
1396
+ (ARRAY['add','add','add','add','remove','update_qty','abandon','abandon'])[1 + floor(random() * 8)::int],
1397
+ 1 + floor(random() * 800)::int,
1398
+ 1 + floor(random() * 3200)::int,
1399
+ (ARRAY[1,1,1,1,2,2,3])[1 + floor(random() * 7)::int],
1400
+ '2021-01-01'::timestamptz + (power(random(), 0.35) * interval '1520 days')
1401
+ FROM generate_series(1, 15000) AS g;
1402
+
1403
+ -- ---------- Search Queries (5,000) ----------
1404
+ INSERT INTO search_queries (customer_id, session_id, query, results_count, clicked_product_id, created_at)
1405
+ SELECT
1406
+ CASE WHEN random() < 0.6 THEN 1 + floor(random() * 8000)::int ELSE NULL END,
1407
+ 'sess_' || md5(random()::text || g::text),
1408
+ (ARRAY['sheets','pillow','towel','duvet','blanket','kitchen','bath mat','outdoor',
1409
+ 'rug','candle','gift','sale','queen sheets','king duvet','bath towel set',
1410
+ 'throw blanket','cast iron','knife set','planter','mirror'])[1 + floor(random() * 20)::int],
1411
+ floor(random() * 50)::int,
1412
+ CASE WHEN random() < 0.4 THEN 1 + floor(random() * 800)::int ELSE NULL END,
1413
+ '2021-01-01'::timestamptz + (power(random(), 0.35) * interval '1520 days')
1414
+ FROM generate_series(1, 5000) AS g;
1415
+
1416
+
1417
+ -- ==========================================================================
1418
+ -- 12. REPORTING / DENORMALIZED TABLES
1419
+ -- ==========================================================================
1420
+
1421
+ -- ---------- Daily Sales Summary (~1,800 days: 2020-03-01 to 2025-02-26) ----------
1422
+ INSERT INTO daily_sales_summary (sale_date, total_orders, total_revenue_cents, total_items, avg_order_value_cents, return_count)
1423
+ SELECT
1424
+ ('2020-03-01'::date + g),
1425
+ order_count,
1426
+ order_count * (5000 + floor(random() * 10000))::int,
1427
+ order_count * 2,
1428
+ (5000 + floor(random() * 10000))::int,
1429
+ CASE WHEN random() < 0.3 THEN floor(random() * 5)::int ELSE 0 END
1430
+ FROM (
1431
+ SELECT
1432
+ g,
1433
+ -- Pandemic curve in daily order count
1434
+ CASE
1435
+ WHEN g < 305 THEN 3 + floor(random() * 8)::int -- 2020: low ramp
1436
+ WHEN g < 670 THEN 10 + floor(random() * 20)::int -- 2021: peak
1437
+ WHEN g < 1035 THEN 8 + floor(random() * 15)::int -- 2022: normalization
1438
+ WHEN g < 1400 THEN 7 + floor(random() * 12)::int -- 2023: stable
1439
+ WHEN g < 1766 THEN 6 + floor(random() * 10)::int -- 2024: stable
1440
+ ELSE 5 + floor(random() * 8)::int -- 2025: partial
1441
+ END AS order_count
1442
+ FROM generate_series(0, 1799) AS g
1443
+ ) AS src;
1444
+
1445
+ -- ---------- Monthly Revenue Summary (60 months: 2020-03 to 2025-02) ----------
1446
+ INSERT INTO monthly_revenue_summary (month, revenue_cents, order_count, new_customers, returning_customers, avg_order_value_cents)
1447
+ SELECT
1448
+ ('2020-03-01'::date + (g * 30)),
1449
+ revenue,
1450
+ order_count,
1451
+ floor(order_count * 0.4)::int,
1452
+ floor(order_count * 0.6)::int,
1453
+ CASE WHEN order_count > 0 THEN revenue / order_count ELSE 0 END
1454
+ FROM (
1455
+ SELECT
1456
+ g,
1457
+ -- Pandemic curve
1458
+ CASE
1459
+ WHEN g < 10 THEN (100 + floor(random() * 200))::int * 10000 -- 2020: ramp
1460
+ WHEN g < 22 THEN (300 + floor(random() * 400))::int * 10000 -- 2021: peak
1461
+ WHEN g < 34 THEN (200 + floor(random() * 300))::int * 10000 -- 2022: normalize
1462
+ WHEN g < 46 THEN (180 + floor(random() * 250))::int * 10000 -- 2023: stable
1463
+ WHEN g < 58 THEN (160 + floor(random() * 200))::int * 10000 -- 2024: stable
1464
+ ELSE (140 + floor(random() * 180))::int * 10000 -- 2025: partial
1465
+ END AS revenue,
1466
+ CASE
1467
+ WHEN g < 10 THEN 100 + floor(random() * 200)::int
1468
+ WHEN g < 22 THEN 400 + floor(random() * 500)::int
1469
+ WHEN g < 34 THEN 300 + floor(random() * 350)::int
1470
+ WHEN g < 46 THEN 250 + floor(random() * 300)::int
1471
+ WHEN g < 58 THEN 220 + floor(random() * 280)::int
1472
+ ELSE 200 + floor(random() * 250)::int
1473
+ END AS order_count
1474
+ FROM generate_series(0, 59) AS g
1475
+ ) AS src;
1476
+
1477
+ -- ---------- Orders Denormalized (25,000) ----------
1478
+ INSERT INTO orders_denormalized (order_id, customer_id, customer_name, customer_email, order_status, total_cents, item_count, first_item_name, shipping_city, shipping_state, created_at)
1479
+ SELECT
1480
+ o.id,
1481
+ o.customer_id,
1482
+ c.full_name,
1483
+ c.email,
1484
+ o.status,
1485
+ o.total_cents,
1486
+ (SELECT count(*) FROM order_items oi WHERE oi.order_id = o.id),
1487
+ (SELECT product_name FROM order_items oi WHERE oi.order_id = o.id ORDER BY oi.id LIMIT 1),
1488
+ ca.city,
1489
+ ca.state,
1490
+ o.created_at
1491
+ FROM orders o
1492
+ LEFT JOIN customers c ON c.id = o.customer_id
1493
+ LEFT JOIN customer_addresses ca ON ca.id = o.shipping_address_id
1494
+ LIMIT 25000;
1495
+
1496
+ -- ---------- Product Performance Cache (800) ----------
1497
+ INSERT INTO product_performance_cache (product_id, product_name, category_name, total_sold, total_revenue_cents, avg_rating, review_count, return_rate, calculated_at)
1498
+ SELECT
1499
+ p.id,
1500
+ p.name,
1501
+ cat.name,
1502
+ COALESCE((SELECT sum(oi.quantity) FROM order_items oi WHERE oi.product_name = p.name), 0)::int,
1503
+ COALESCE((SELECT sum(oi.total_cents) FROM order_items oi WHERE oi.product_name = p.name), 0)::int,
1504
+ COALESCE((SELECT round(avg(pr.rating)::numeric, 2) FROM product_reviews pr WHERE pr.product_id = p.id), NULL),
1505
+ COALESCE((SELECT count(*) FROM product_reviews pr WHERE pr.product_id = p.id), 0)::int,
1506
+ round((random() * 8)::numeric, 2),
1507
+ now() - (random() * interval '7 days')
1508
+ FROM products p
1509
+ LEFT JOIN categories cat ON cat.id = p.category_id
1510
+ LIMIT 800;
1511
+
1512
+ -- ---------- Customer LTV Cache (8,000) ----------
1513
+ INSERT INTO customer_ltv_cache (customer_id, total_orders, total_spent_cents, first_order_at, last_order_at, avg_order_value_cents, predicted_ltv_cents, segment, calculated_at)
1514
+ SELECT
1515
+ c.id,
1516
+ COALESCE(order_stats.cnt, 0)::int,
1517
+ COALESCE(order_stats.total, 0)::int,
1518
+ order_stats.first_at,
1519
+ order_stats.last_at,
1520
+ CASE WHEN COALESCE(order_stats.cnt, 0) > 0 THEN (order_stats.total / order_stats.cnt)::int ELSE 0 END,
1521
+ CASE WHEN COALESCE(order_stats.cnt, 0) > 0 THEN (order_stats.total * (1.5 + random()))::int ELSE 0 END,
1522
+ (ARRAY['VIP','High-Value','Regular','New','At-Risk','Churned','Dormant'])[1 + floor(random() * 7)::int],
1523
+ now() - (random() * interval '7 days')
1524
+ FROM customers c
1525
+ LEFT JOIN (
1526
+ SELECT customer_id, count(*) AS cnt, sum(total_cents) AS total, min(created_at) AS first_at, max(created_at) AS last_at
1527
+ FROM orders
1528
+ GROUP BY customer_id
1529
+ ) order_stats ON order_stats.customer_id = c.id
1530
+ LIMIT 8000;
1531
+
1532
+
1533
+ -- ==========================================================================
1534
+ -- 13. MARKETPLACE PAYOUTS & PERFORMANCE
1535
+ -- ==========================================================================
1536
+
1537
+ -- ---------- Seller Payouts (2,000) ----------
1538
+ INSERT INTO seller_payouts (seller_id, amount_cents, period_start, period_end, status, paid_at, created_at)
1539
+ SELECT
1540
+ 1 + floor(random() * 80)::int,
1541
+ (5000 + floor(random() * 50000))::int,
1542
+ period_start,
1543
+ period_start + interval '1 month',
1544
+ (ARRAY['paid','paid','paid','paid','paid','pending','processing'])[1 + floor(random() * 7)::int],
1545
+ CASE WHEN random() < 0.8 THEN period_start + interval '1 month' + (random() * interval '5 days') ELSE NULL END,
1546
+ period_start
1547
+ FROM (
1548
+ SELECT g, ('2022-02-01'::date + ((g * 15) % 1095))::date AS period_start
1549
+ FROM generate_series(1, 2000) AS g
1550
+ ) AS src;
1551
+
1552
+ -- ---------- Seller Performance (400) ----------
1553
+ INSERT INTO seller_performance (seller_id, month, total_orders, total_revenue_cents, return_rate, avg_rating, created_at)
1554
+ SELECT
1555
+ ((g - 1) % 80) + 1,
1556
+ ('2022-02-01'::date + (((g - 1) / 80) * 30)),
1557
+ floor(random() * 50 + 5)::int,
1558
+ (10000 + floor(random() * 200000))::int,
1559
+ round((random() * 10)::numeric, 2),
1560
+ round((3.0 + random() * 2)::numeric, 2),
1561
+ ('2022-03-01'::date + (((g - 1) / 80) * 30))::timestamptz
1562
+ FROM generate_series(1, 400) AS g;
1563
+
1564
+
1565
+ -- ==========================================================================
1566
+ -- 14. INTERNAL / OPS DATA
1567
+ -- ==========================================================================
1568
+
1569
+ -- ---------- Admin Users (30) ----------
1570
+ INSERT INTO admin_users (email, full_name, role, is_active, created_at)
1571
+ SELECT
1572
+ lower(first) || '.' || lower(last) || '@novamart.com',
1573
+ first || ' ' || last,
1574
+ (ARRAY['admin','admin','manager','manager','support','support','support','support','support','warehouse'])[1 + floor(random() * 10)::int],
1575
+ CASE WHEN g <= 25 THEN true ELSE false END,
1576
+ '2020-01-01'::timestamptz + (random() * interval '365 days')
1577
+ FROM (
1578
+ SELECT
1579
+ g,
1580
+ (ARRAY['Sarah','Mike','Jessica','Tom','Emily','Chris','Rachel','David','Lisa','Kevin',
1581
+ 'Amanda','Brian','Megan','Ryan','Lauren','Nick','Tina','Josh','Diana','Mark',
1582
+ 'Kelly','Peter','Grace','Sam','Alex','Robin','Casey','Jordan','Morgan','Taylor'])[g] AS first,
1583
+ (ARRAY['Chen','Patel','Kim','Santos','Mueller','Johansson','O''Brien','Nakamura','Silva','Andersen',
1584
+ 'Ivanov','Kowalski','Tanaka','Svensson','Park','Dubois','Fernandez','Ali','Nguyen','Rossi',
1585
+ 'Müller','Sato','Costa','Berg','Larsen','Moreau','Reyes','Khan','Lee','Fischer'])[g] AS last
1586
+ FROM generate_series(1, 30) AS g
1587
+ ) AS src;
1588
+
1589
+ -- ---------- Admin Audit Log (10,000) ----------
1590
+ INSERT INTO admin_audit_log (admin_user_id, action, resource_type, resource_id, details, ip_address, created_at)
1591
+ SELECT
1592
+ 1 + floor(random() * 30)::int,
1593
+ (ARRAY['login','logout','view_order','update_order','cancel_order','issue_refund',
1594
+ 'update_product','create_promotion','update_customer','export_data',
1595
+ 'view_report','manage_seller','update_settings','manage_return','view_analytics'])[1 + floor(random() * 15)::int],
1596
+ (ARRAY['order','product','customer','promotion','seller','return','report','settings'])[1 + floor(random() * 8)::int],
1597
+ floor(random() * 25000)::int::text,
1598
+ CASE WHEN random() < 0.3 THEN '{"ip":"10.0.' || floor(random() * 255)::int || '.' || floor(random() * 255)::int || '"}' ELSE NULL END,
1599
+ '10.0.' || floor(random() * 255)::int || '.' || floor(random() * 255)::int,
1600
+ '2020-01-15'::timestamptz + (power(random(), 0.3) * interval '1870 days')
1601
+ FROM generate_series(1, 10000) AS g;
1602
+
1603
+ -- ---------- System Settings (20) ----------
1604
+ INSERT INTO system_settings (key, value, updated_at) VALUES
1605
+ ('site.name', 'NovaMart', '2020-01-15'::timestamptz),
1606
+ ('site.tagline', 'Premium Home Goods, Delivered', '2020-01-15'::timestamptz),
1607
+ ('shipping.free_threshold', '7500', '2023-06-01'::timestamptz),
1608
+ ('shipping.default_carrier', 'UPS', '2020-01-15'::timestamptz),
1609
+ ('tax.default_rate', '0.08', '2020-01-15'::timestamptz),
1610
+ ('loyalty.points_per_dollar','1', '2020-09-01'::timestamptz),
1611
+ ('loyalty.redeem_rate', '100', '2020-09-01'::timestamptz),
1612
+ ('return.window_days', '30', '2020-01-15'::timestamptz),
1613
+ ('marketplace.commission', '0.15', '2022-01-01'::timestamptz),
1614
+ ('marketplace.payout_day', '15', '2022-01-01'::timestamptz),
1615
+ ('email.from_address', 'hello@novamart.com', '2020-01-15'::timestamptz),
1616
+ ('email.support_address', 'support@novamart.com', '2020-01-15'::timestamptz),
1617
+ ('analytics.enabled', 'true', '2021-01-01'::timestamptz),
1618
+ ('review.moderation', 'auto', '2021-06-01'::timestamptz),
1619
+ ('inventory.low_stock_alert','true', '2020-06-01'::timestamptz),
1620
+ ('inventory.reorder_auto', 'false', '2023-01-01'::timestamptz),
1621
+ ('checkout.guest_allowed', 'true', '2020-01-15'::timestamptz),
1622
+ ('checkout.max_items', '50', '2020-01-15'::timestamptz),
1623
+ ('promo.stack_allowed', 'false', '2021-01-01'::timestamptz),
1624
+ ('maintenance_mode', 'false', '2024-01-15'::timestamptz);
1625
+
1626
+
1627
+ -- ==========================================================================
1628
+ -- 15. LEGACY & ABANDONED TABLE DATA
1629
+ -- ==========================================================================
1630
+
1631
+ -- ---------- old_orders_v1 (3,000) — pre-migration 2020 orders ----------
1632
+ INSERT INTO old_orders_v1 (cust_email, order_total, order_status, item_list, placed_date, shipped_date)
1633
+ SELECT
1634
+ 'customer' || floor(random() * 2000)::int || '@' || (ARRAY['gmail.com','yahoo.com','outlook.com'])[1 + floor(random() * 3)::int],
1635
+ round((29.99 + random() * 300)::numeric, 2), -- dollars, not cents
1636
+ (ARRAY['complete','shipped','processing','canceled','pending','refunded'])[1 + floor(random() * 6)::int],
1637
+ 'SKU-' || lpad(floor(random() * 999)::int::text, 3, '0') || ' x' || (1 + floor(random() * 3)::int)
1638
+ || CASE WHEN random() < 0.4 THEN ', SKU-' || lpad(floor(random() * 999)::int::text, 3, '0') || ' x1' ELSE '' END,
1639
+ placed_ts,
1640
+ CASE WHEN random() < 0.6 THEN placed_ts + (random() * interval '7 days') ELSE NULL END
1641
+ FROM (
1642
+ SELECT g, '2020-01-15'::timestamptz + (random() * interval '365 days') AS placed_ts
1643
+ FROM generate_series(1, 3000) AS g
1644
+ ) AS src;
1645
+
1646
+ -- ---------- temp_product_import_2023 (500) — CSV import artifact ----------
1647
+ INSERT INTO temp_product_import_2023 (import_name, import_sku, import_price, import_category, raw_csv_line, imported_at)
1648
+ SELECT
1649
+ 'Imported Product ' || g,
1650
+ 'IMP-' || lpad(g::text, 4, '0'),
1651
+ -- stored as text, not numeric
1652
+ '$' || round((9.99 + random() * 200)::numeric, 2)::text,
1653
+ (ARRAY['bedding','kitchen','bath','outdoor','decor','unknown',''])[1 + floor(random() * 7)::int],
1654
+ 'Imported Product ' || g || ',IMP-' || lpad(g::text, 4, '0') || ',$' || round((9.99 + random() * 200)::numeric, 2) || ',bedding',
1655
+ '2023-09-15'::timestamptz + (random() * interval '3 hours')
1656
+ FROM generate_series(1, 500) AS g;
1657
+
1658
+ -- ---------- legacy_analytics_events (8,000) — old tracking system ----------
1659
+ INSERT INTO legacy_analytics_events (event_name, event_data, user_ref, page_url, timestamp)
1660
+ SELECT
1661
+ (ARRAY['page_view','click','scroll','form_submit','purchase','add_to_cart','remove_from_cart',
1662
+ 'search','signup','login'])[1 + floor(random() * 10)::int],
1663
+ '{"session":"' || md5(random()::text) || '","ua":"Chrome"}',
1664
+ 'user_' || floor(random() * 5000)::int, -- string reference, not integer FK
1665
+ (ARRAY['/','/products','/cart','/checkout','/account','/collections','/search'])[1 + floor(random() * 7)::int],
1666
+ '2020-06-01'::timestamptz + (random() * interval '1095 days') -- stopped mid-2023
1667
+ FROM generate_series(1, 8000) AS g;
1668
+
1669
+ -- ---------- payment_methods_backup (2,000) — Stripe→Adyen migration backup ----------
1670
+ INSERT INTO payment_methods_backup (cust_id, card_type, last_four, exp_month, exp_year, is_primary, created_date)
1671
+ SELECT
1672
+ floor(random() * 10000 + 5000)::int, -- old customer IDs (5000-15000 range, don't match current customers)
1673
+ (ARRAY['visa','visa','visa','mastercard','mastercard','amex','discover'])[1 + floor(random() * 7)::int],
1674
+ lpad(floor(random() * 10000)::int::text, 4, '0'),
1675
+ 1 + floor(random() * 12)::int,
1676
+ 2023 + floor(random() * 4)::int,
1677
+ CASE WHEN g <= 1500 THEN true ELSE false END,
1678
+ '2020-01-15'::timestamptz + (random() * interval '1095 days')
1679
+ FROM generate_series(1, 2000) AS g;
1680
+
1681
+
1682
+ COMMIT;
1683
+
1684
+ -- ==========================================================================
1685
+ -- Verify row counts
1686
+ -- ==========================================================================
1687
+ SELECT 'Row counts:' AS info;
1688
+ SELECT schemaname, relname AS table_name, n_live_tup AS row_count
1689
+ FROM pg_stat_user_tables
1690
+ ORDER BY n_live_tup DESC;