@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.
- package/README.md +231 -0
- package/index.ts +829 -0
- package/package.json +38 -0
- package/templates/docker/.env.example +67 -0
- package/templates/docker/Dockerfile +52 -0
- package/templates/docker/bin/__tests__/benchmark.test.ts +598 -0
- package/templates/docker/bin/__tests__/duckdb-ingest.test.ts +171 -0
- package/templates/docker/bin/__tests__/eval.test.ts +434 -0
- package/templates/docker/bin/__tests__/matview-partition.test.ts +615 -0
- package/templates/docker/bin/__tests__/multi-source.test.ts +113 -0
- package/templates/docker/bin/__tests__/plugin-cli.test.ts +322 -0
- package/templates/docker/bin/__tests__/profiler-heuristics.test.ts +608 -0
- package/templates/docker/bin/__tests__/query.test.ts +240 -0
- package/templates/docker/bin/__tests__/schema-drift.test.ts +542 -0
- package/templates/docker/bin/__tests__/view-yaml-generation.test.ts +146 -0
- package/templates/docker/bin/atlas.ts +5044 -0
- package/templates/docker/bin/benchmark.ts +695 -0
- package/templates/docker/bin/enrich.ts +559 -0
- package/templates/docker/bin/eval.ts +770 -0
- package/templates/docker/bin/smoke.ts +438 -0
- package/templates/docker/data/.gitkeep +0 -0
- package/templates/docker/data/cybersec.sql +1961 -0
- package/templates/docker/data/demo-semantic/catalog.yml +40 -0
- package/templates/docker/data/demo-semantic/entities/accounts.yml +170 -0
- package/templates/docker/data/demo-semantic/entities/companies.yml +207 -0
- package/templates/docker/data/demo-semantic/entities/people.yml +145 -0
- package/templates/docker/data/demo-semantic/glossary.yml +22 -0
- package/templates/docker/data/demo-semantic/metrics/accounts.yml +38 -0
- package/templates/docker/data/demo-semantic/metrics/companies.yml +89 -0
- package/templates/docker/data/demo.sql +373 -0
- package/templates/docker/data/ecommerce.sql +1690 -0
- package/templates/docker/data/init-demo-db.sql +8 -0
- package/templates/docker/docker-compose.yml +34 -0
- package/templates/docker/docs/deploy.md +390 -0
- package/templates/docker/eslint.config.mjs +18 -0
- package/templates/docker/gitignore +5 -0
- package/templates/docker/next.config.ts +9 -0
- package/templates/docker/package.json +59 -0
- package/templates/docker/postcss.config.mjs +8 -0
- package/templates/docker/public/.gitkeep +0 -0
- package/templates/docker/public/favicon.svg +4 -0
- package/templates/docker/railway.json +13 -0
- package/templates/docker/render.yaml +34 -0
- package/templates/docker/semantic/catalog.yml +5 -0
- package/templates/docker/semantic/entities/.gitkeep +0 -0
- package/templates/docker/semantic/glossary.yml +6 -0
- package/templates/docker/semantic/metrics/.gitkeep +0 -0
- package/templates/docker/sidecar/Dockerfile +28 -0
- package/templates/docker/sidecar/railway.json +14 -0
- package/templates/docker/sidecar/server.ts +188 -0
- package/templates/docker/src/api/__tests__/actions.test.ts +683 -0
- package/templates/docker/src/api/__tests__/admin.test.ts +820 -0
- package/templates/docker/src/api/__tests__/auth.test.ts +165 -0
- package/templates/docker/src/api/__tests__/chat.test.ts +376 -0
- package/templates/docker/src/api/__tests__/conversations.test.ts +555 -0
- package/templates/docker/src/api/__tests__/cors.test.ts +135 -0
- package/templates/docker/src/api/__tests__/health-plugin.test.ts +169 -0
- package/templates/docker/src/api/__tests__/health.test.ts +261 -0
- package/templates/docker/src/api/__tests__/query.test.ts +891 -0
- package/templates/docker/src/api/__tests__/scheduled-tasks.test.ts +601 -0
- package/templates/docker/src/api/__tests__/slack.test.ts +847 -0
- package/templates/docker/src/api/index.ts +117 -0
- package/templates/docker/src/api/routes/actions.ts +274 -0
- package/templates/docker/src/api/routes/admin.ts +757 -0
- package/templates/docker/src/api/routes/auth.ts +48 -0
- package/templates/docker/src/api/routes/chat.ts +465 -0
- package/templates/docker/src/api/routes/conversations.ts +266 -0
- package/templates/docker/src/api/routes/health.ts +287 -0
- package/templates/docker/src/api/routes/openapi.ts +390 -0
- package/templates/docker/src/api/routes/query.ts +318 -0
- package/templates/docker/src/api/routes/scheduled-tasks.ts +467 -0
- package/templates/docker/src/api/routes/slack.ts +611 -0
- package/templates/docker/src/api/server.ts +226 -0
- package/templates/docker/src/app/api/[...route]/route.ts +33 -0
- package/templates/docker/src/app/error.tsx +24 -0
- package/templates/docker/src/app/globals.css +126 -0
- package/templates/docker/src/app/layout.tsx +19 -0
- package/templates/docker/src/app/page.tsx +14 -0
- package/templates/docker/src/global.d.ts +1 -0
- package/templates/docker/src/lib/__tests__/agent-cache.test.ts +437 -0
- package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +114 -0
- package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
- package/templates/docker/src/lib/__tests__/agent-integration.test.ts +514 -0
- package/templates/docker/src/lib/__tests__/config-actions.test.ts +166 -0
- package/templates/docker/src/lib/__tests__/config.test.ts +1063 -0
- package/templates/docker/src/lib/__tests__/conversations.test.ts +589 -0
- package/templates/docker/src/lib/__tests__/errors.test.ts +256 -0
- package/templates/docker/src/lib/__tests__/logger.test.ts +200 -0
- package/templates/docker/src/lib/__tests__/providers.test.ts +99 -0
- package/templates/docker/src/lib/__tests__/rls.test.ts +435 -0
- package/templates/docker/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
- package/templates/docker/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
- package/templates/docker/src/lib/__tests__/semantic-index.test.ts +547 -0
- package/templates/docker/src/lib/__tests__/semantic-multisource.test.ts +544 -0
- package/templates/docker/src/lib/__tests__/semantic.test.ts +363 -0
- package/templates/docker/src/lib/__tests__/startup-actions.test.ts +452 -0
- package/templates/docker/src/lib/__tests__/startup.test.ts +465 -0
- package/templates/docker/src/lib/__tests__/tracing.test.ts +28 -0
- package/templates/docker/src/lib/action-types.ts +95 -0
- package/templates/docker/src/lib/agent-query.ts +178 -0
- package/templates/docker/src/lib/agent.ts +505 -0
- package/templates/docker/src/lib/api-url.ts +2 -0
- package/templates/docker/src/lib/auth/__tests__/audit.test.ts +418 -0
- package/templates/docker/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
- package/templates/docker/src/lib/auth/__tests__/byot.test.ts +366 -0
- package/templates/docker/src/lib/auth/__tests__/detect.test.ts +190 -0
- package/templates/docker/src/lib/auth/__tests__/managed.test.ts +173 -0
- package/templates/docker/src/lib/auth/__tests__/middleware.test.ts +456 -0
- package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +201 -0
- package/templates/docker/src/lib/auth/__tests__/permissions.test.ts +225 -0
- package/templates/docker/src/lib/auth/__tests__/server.test.ts +34 -0
- package/templates/docker/src/lib/auth/__tests__/simple-key.test.ts +176 -0
- package/templates/docker/src/lib/auth/__tests__/types.test.ts +44 -0
- package/templates/docker/src/lib/auth/audit.ts +89 -0
- package/templates/docker/src/lib/auth/byot.ts +158 -0
- package/templates/docker/src/lib/auth/client.ts +35 -0
- package/templates/docker/src/lib/auth/detect.ts +83 -0
- package/templates/docker/src/lib/auth/managed.ts +73 -0
- package/templates/docker/src/lib/auth/middleware.ts +208 -0
- package/templates/docker/src/lib/auth/migrate.ts +111 -0
- package/templates/docker/src/lib/auth/permissions.ts +156 -0
- package/templates/docker/src/lib/auth/server.ts +142 -0
- package/templates/docker/src/lib/auth/simple-key.ts +92 -0
- package/templates/docker/src/lib/auth/types.ts +49 -0
- package/templates/docker/src/lib/config.ts +704 -0
- package/templates/docker/src/lib/conversation-types.ts +29 -0
- package/templates/docker/src/lib/conversations.ts +270 -0
- package/templates/docker/src/lib/db/__tests__/connection.test.ts +69 -0
- package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +141 -0
- package/templates/docker/src/lib/db/__tests__/internal.test.ts +387 -0
- package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +207 -0
- package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
- package/templates/docker/src/lib/db/__tests__/registry.test.ts +595 -0
- package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +339 -0
- package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +217 -0
- package/templates/docker/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
- package/templates/docker/src/lib/db/connection.ts +753 -0
- package/templates/docker/src/lib/db/duckdb.ts +122 -0
- package/templates/docker/src/lib/db/internal.ts +273 -0
- package/templates/docker/src/lib/db/salesforce.ts +342 -0
- package/templates/docker/src/lib/db/source-rate-limit.ts +191 -0
- package/templates/docker/src/lib/errors.ts +154 -0
- package/templates/docker/src/lib/logger.ts +98 -0
- package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
- package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +529 -0
- package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +521 -0
- package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +346 -0
- package/templates/docker/src/lib/plugins/__tests__/tools.test.ts +49 -0
- package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +585 -0
- package/templates/docker/src/lib/plugins/hooks.ts +162 -0
- package/templates/docker/src/lib/plugins/index.ts +9 -0
- package/templates/docker/src/lib/plugins/migrate.ts +309 -0
- package/templates/docker/src/lib/plugins/registry.ts +231 -0
- package/templates/docker/src/lib/plugins/tools.ts +39 -0
- package/templates/docker/src/lib/plugins/wiring.ts +291 -0
- package/templates/docker/src/lib/providers.ts +102 -0
- package/templates/docker/src/lib/rls.ts +321 -0
- package/templates/docker/src/lib/scheduled-task-types.ts +132 -0
- package/templates/docker/src/lib/scheduled-tasks.ts +475 -0
- package/templates/docker/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
- package/templates/docker/src/lib/scheduler/__tests__/engine.test.ts +248 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
- package/templates/docker/src/lib/scheduler/delivery.ts +248 -0
- package/templates/docker/src/lib/scheduler/engine.ts +317 -0
- package/templates/docker/src/lib/scheduler/executor.ts +73 -0
- package/templates/docker/src/lib/scheduler/format-email.ts +109 -0
- package/templates/docker/src/lib/scheduler/format-slack.ts +35 -0
- package/templates/docker/src/lib/scheduler/format-webhook.ts +37 -0
- package/templates/docker/src/lib/scheduler/index.ts +7 -0
- package/templates/docker/src/lib/security.ts +11 -0
- package/templates/docker/src/lib/semantic-index.ts +503 -0
- package/templates/docker/src/lib/semantic.ts +387 -0
- package/templates/docker/src/lib/sidecar-types.ts +16 -0
- package/templates/docker/src/lib/slack/__tests__/api.test.ts +160 -0
- package/templates/docker/src/lib/slack/__tests__/format.test.ts +237 -0
- package/templates/docker/src/lib/slack/__tests__/store.test.ts +188 -0
- package/templates/docker/src/lib/slack/__tests__/threads.test.ts +112 -0
- package/templates/docker/src/lib/slack/__tests__/verify.test.ts +111 -0
- package/templates/docker/src/lib/slack/api.ts +102 -0
- package/templates/docker/src/lib/slack/format.ts +209 -0
- package/templates/docker/src/lib/slack/store.ts +107 -0
- package/templates/docker/src/lib/slack/threads.ts +64 -0
- package/templates/docker/src/lib/slack/verify.ts +71 -0
- package/templates/docker/src/lib/startup.ts +730 -0
- package/templates/docker/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
- package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
- package/templates/docker/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
- package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
- package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
- package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
- package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
- package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
- package/templates/docker/src/lib/tools/__tests__/registry.test.ts +235 -0
- package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
- package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
- package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
- package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
- package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
- package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
- package/templates/docker/src/lib/tools/__tests__/sql.test.ts +1012 -0
- package/templates/docker/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
- package/templates/docker/src/lib/tools/actions/__tests__/email.test.ts +378 -0
- package/templates/docker/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
- package/templates/docker/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
- package/templates/docker/src/lib/tools/actions/audit.ts +47 -0
- package/templates/docker/src/lib/tools/actions/email.ts +191 -0
- package/templates/docker/src/lib/tools/actions/handler.ts +591 -0
- package/templates/docker/src/lib/tools/actions/index.ts +23 -0
- package/templates/docker/src/lib/tools/actions/jira.ts +220 -0
- package/templates/docker/src/lib/tools/explore-nsjail.ts +343 -0
- package/templates/docker/src/lib/tools/explore-sandbox.ts +264 -0
- package/templates/docker/src/lib/tools/explore-sidecar.ts +163 -0
- package/templates/docker/src/lib/tools/explore.ts +379 -0
- package/templates/docker/src/lib/tools/registry.ts +221 -0
- package/templates/docker/src/lib/tools/salesforce.ts +138 -0
- package/templates/docker/src/lib/tools/soql-validation.ts +172 -0
- package/templates/docker/src/lib/tools/sql.ts +680 -0
- package/templates/docker/src/lib/tracing.ts +40 -0
- package/templates/docker/src/lib/utils.ts +6 -0
- package/templates/docker/src/test-setup.ts +38 -0
- package/templates/docker/src/types/vercel-sandbox.d.ts +54 -0
- package/templates/docker/src/ui/components/actions/action-approval-card.tsx +295 -0
- package/templates/docker/src/ui/components/actions/action-status-badge.tsx +50 -0
- package/templates/docker/src/ui/components/admin/admin-layout.tsx +26 -0
- package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +96 -0
- package/templates/docker/src/ui/components/admin/empty-state.tsx +24 -0
- package/templates/docker/src/ui/components/admin/entity-detail.tsx +233 -0
- package/templates/docker/src/ui/components/admin/entity-list.tsx +96 -0
- package/templates/docker/src/ui/components/admin/error-banner.tsx +22 -0
- package/templates/docker/src/ui/components/admin/feature-disabled.tsx +44 -0
- package/templates/docker/src/ui/components/admin/health-badge.tsx +30 -0
- package/templates/docker/src/ui/components/admin/loading-state.tsx +14 -0
- package/templates/docker/src/ui/components/admin/stat-card.tsx +32 -0
- package/templates/docker/src/ui/components/atlas-chat.tsx +370 -0
- package/templates/docker/src/ui/components/chart/chart-detection.ts +261 -0
- package/templates/docker/src/ui/components/chart/result-chart.tsx +375 -0
- package/templates/docker/src/ui/components/chat/api-key-bar.tsx +66 -0
- package/templates/docker/src/ui/components/chat/copy-button.tsx +25 -0
- package/templates/docker/src/ui/components/chat/data-table.tsx +102 -0
- package/templates/docker/src/ui/components/chat/error-banner.tsx +32 -0
- package/templates/docker/src/ui/components/chat/explore-card.tsx +41 -0
- package/templates/docker/src/ui/components/chat/loading-card.tsx +10 -0
- package/templates/docker/src/ui/components/chat/managed-auth-card.tsx +116 -0
- package/templates/docker/src/ui/components/chat/markdown.tsx +72 -0
- package/templates/docker/src/ui/components/chat/sql-block.tsx +30 -0
- package/templates/docker/src/ui/components/chat/sql-result-card.tsx +144 -0
- package/templates/docker/src/ui/components/chat/starter-prompts.ts +6 -0
- package/templates/docker/src/ui/components/chat/tool-part.tsx +40 -0
- package/templates/docker/src/ui/components/chat/typing-indicator.tsx +19 -0
- package/templates/docker/src/ui/components/conversations/conversation-item.tsx +120 -0
- package/templates/docker/src/ui/components/conversations/conversation-list.tsx +66 -0
- package/templates/docker/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
- package/templates/docker/src/ui/components/conversations/delete-confirmation.tsx +27 -0
- package/templates/docker/src/ui/context.tsx +78 -0
- package/templates/docker/src/ui/hooks/use-admin-fetch.ts +104 -0
- package/templates/docker/src/ui/hooks/use-conversations.ts +184 -0
- package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -0
- package/templates/docker/src/ui/lib/action-types.ts +63 -0
- package/templates/docker/src/ui/lib/helpers.ts +104 -0
- package/templates/docker/src/ui/lib/types.ts +145 -0
- package/templates/docker/tsconfig.json +41 -0
- package/templates/docker/vercel.json +3 -0
- package/templates/nextjs-standalone/.env.example +68 -0
- package/templates/nextjs-standalone/bin/__tests__/benchmark.test.ts +598 -0
- package/templates/nextjs-standalone/bin/__tests__/duckdb-ingest.test.ts +171 -0
- package/templates/nextjs-standalone/bin/__tests__/eval.test.ts +434 -0
- package/templates/nextjs-standalone/bin/__tests__/matview-partition.test.ts +615 -0
- package/templates/nextjs-standalone/bin/__tests__/multi-source.test.ts +113 -0
- package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +322 -0
- package/templates/nextjs-standalone/bin/__tests__/profiler-heuristics.test.ts +608 -0
- package/templates/nextjs-standalone/bin/__tests__/query.test.ts +240 -0
- package/templates/nextjs-standalone/bin/__tests__/schema-drift.test.ts +542 -0
- package/templates/nextjs-standalone/bin/__tests__/view-yaml-generation.test.ts +146 -0
- package/templates/nextjs-standalone/bin/atlas.ts +5044 -0
- package/templates/nextjs-standalone/bin/benchmark.ts +695 -0
- package/templates/nextjs-standalone/bin/enrich.ts +559 -0
- package/templates/nextjs-standalone/bin/eval.ts +770 -0
- package/templates/nextjs-standalone/bin/smoke.ts +438 -0
- package/templates/nextjs-standalone/data/.gitkeep +0 -0
- package/templates/nextjs-standalone/data/cybersec.sql +1961 -0
- package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +40 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +170 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +207 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +145 -0
- package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +22 -0
- package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +38 -0
- package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +89 -0
- package/templates/nextjs-standalone/data/demo.sql +373 -0
- package/templates/nextjs-standalone/data/ecommerce.sql +1690 -0
- package/templates/nextjs-standalone/data/init-demo-db.sql +8 -0
- package/templates/nextjs-standalone/docs/deploy.md +390 -0
- package/templates/nextjs-standalone/eslint.config.mjs +18 -0
- package/templates/nextjs-standalone/gitignore +5 -0
- package/templates/nextjs-standalone/next.config.ts +10 -0
- package/templates/nextjs-standalone/package.json +63 -0
- package/templates/nextjs-standalone/postcss.config.mjs +8 -0
- package/templates/nextjs-standalone/semantic/catalog.yml +5 -0
- package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
- package/templates/nextjs-standalone/semantic/glossary.yml +6 -0
- package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
- package/templates/nextjs-standalone/src/api/__tests__/actions.test.ts +683 -0
- package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +820 -0
- package/templates/nextjs-standalone/src/api/__tests__/auth.test.ts +165 -0
- package/templates/nextjs-standalone/src/api/__tests__/chat.test.ts +376 -0
- package/templates/nextjs-standalone/src/api/__tests__/conversations.test.ts +555 -0
- package/templates/nextjs-standalone/src/api/__tests__/cors.test.ts +135 -0
- package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +169 -0
- package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +261 -0
- package/templates/nextjs-standalone/src/api/__tests__/query.test.ts +891 -0
- package/templates/nextjs-standalone/src/api/__tests__/scheduled-tasks.test.ts +601 -0
- package/templates/nextjs-standalone/src/api/__tests__/slack.test.ts +847 -0
- package/templates/nextjs-standalone/src/api/index.ts +117 -0
- package/templates/nextjs-standalone/src/api/routes/actions.ts +274 -0
- package/templates/nextjs-standalone/src/api/routes/admin.ts +757 -0
- package/templates/nextjs-standalone/src/api/routes/auth.ts +48 -0
- package/templates/nextjs-standalone/src/api/routes/chat.ts +465 -0
- package/templates/nextjs-standalone/src/api/routes/conversations.ts +266 -0
- package/templates/nextjs-standalone/src/api/routes/health.ts +287 -0
- package/templates/nextjs-standalone/src/api/routes/openapi.ts +390 -0
- package/templates/nextjs-standalone/src/api/routes/query.ts +318 -0
- package/templates/nextjs-standalone/src/api/routes/scheduled-tasks.ts +467 -0
- package/templates/nextjs-standalone/src/api/routes/slack.ts +611 -0
- package/templates/nextjs-standalone/src/api/server.ts +226 -0
- package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +33 -0
- package/templates/nextjs-standalone/src/app/error.tsx +24 -0
- package/templates/nextjs-standalone/src/app/global-error.tsx +68 -0
- package/templates/nextjs-standalone/src/app/globals.css +126 -0
- package/templates/nextjs-standalone/src/app/layout.tsx +19 -0
- package/templates/nextjs-standalone/src/app/page.tsx +14 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +437 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +114 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +514 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config-actions.test.ts +166 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +1063 -0
- package/templates/nextjs-standalone/src/lib/__tests__/conversations.test.ts +589 -0
- package/templates/nextjs-standalone/src/lib/__tests__/errors.test.ts +256 -0
- package/templates/nextjs-standalone/src/lib/__tests__/logger.test.ts +200 -0
- package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +99 -0
- package/templates/nextjs-standalone/src/lib/__tests__/rls.test.ts +435 -0
- package/templates/nextjs-standalone/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
- package/templates/nextjs-standalone/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic-index.test.ts +547 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic-multisource.test.ts +544 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic.test.ts +363 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +452 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +465 -0
- package/templates/nextjs-standalone/src/lib/__tests__/tracing.test.ts +28 -0
- package/templates/nextjs-standalone/src/lib/action-types.ts +95 -0
- package/templates/nextjs-standalone/src/lib/agent-query.ts +178 -0
- package/templates/nextjs-standalone/src/lib/agent.ts +505 -0
- package/templates/nextjs-standalone/src/lib/api-url.ts +3 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/audit.test.ts +418 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/byot.test.ts +366 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/detect.test.ts +190 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/managed.test.ts +173 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/middleware.test.ts +456 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +201 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/permissions.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/server.test.ts +34 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/simple-key.test.ts +176 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/types.test.ts +44 -0
- package/templates/nextjs-standalone/src/lib/auth/audit.ts +89 -0
- package/templates/nextjs-standalone/src/lib/auth/byot.ts +158 -0
- package/templates/nextjs-standalone/src/lib/auth/client.ts +23 -0
- package/templates/nextjs-standalone/src/lib/auth/detect.ts +83 -0
- package/templates/nextjs-standalone/src/lib/auth/managed.ts +73 -0
- package/templates/nextjs-standalone/src/lib/auth/middleware.ts +208 -0
- package/templates/nextjs-standalone/src/lib/auth/migrate.ts +111 -0
- package/templates/nextjs-standalone/src/lib/auth/permissions.ts +156 -0
- package/templates/nextjs-standalone/src/lib/auth/server.ts +142 -0
- package/templates/nextjs-standalone/src/lib/auth/simple-key.ts +92 -0
- package/templates/nextjs-standalone/src/lib/auth/types.ts +49 -0
- package/templates/nextjs-standalone/src/lib/config.ts +704 -0
- package/templates/nextjs-standalone/src/lib/conversation-types.ts +29 -0
- package/templates/nextjs-standalone/src/lib/conversations.ts +270 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +69 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +141 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/internal.test.ts +387 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +207 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +595 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +339 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +217 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
- package/templates/nextjs-standalone/src/lib/db/connection.ts +753 -0
- package/templates/nextjs-standalone/src/lib/db/duckdb.ts +122 -0
- package/templates/nextjs-standalone/src/lib/db/internal.ts +273 -0
- package/templates/nextjs-standalone/src/lib/db/salesforce.ts +342 -0
- package/templates/nextjs-standalone/src/lib/db/source-rate-limit.ts +191 -0
- package/templates/nextjs-standalone/src/lib/errors.ts +154 -0
- package/templates/nextjs-standalone/src/lib/logger.ts +98 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +529 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +521 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +346 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/tools.test.ts +49 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +585 -0
- package/templates/nextjs-standalone/src/lib/plugins/hooks.ts +162 -0
- package/templates/nextjs-standalone/src/lib/plugins/index.ts +9 -0
- package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +309 -0
- package/templates/nextjs-standalone/src/lib/plugins/registry.ts +231 -0
- package/templates/nextjs-standalone/src/lib/plugins/tools.ts +39 -0
- package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +291 -0
- package/templates/nextjs-standalone/src/lib/providers.ts +102 -0
- package/templates/nextjs-standalone/src/lib/rls.ts +321 -0
- package/templates/nextjs-standalone/src/lib/scheduled-task-types.ts +132 -0
- package/templates/nextjs-standalone/src/lib/scheduled-tasks.ts +475 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/engine.test.ts +248 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
- package/templates/nextjs-standalone/src/lib/scheduler/delivery.ts +248 -0
- package/templates/nextjs-standalone/src/lib/scheduler/engine.ts +317 -0
- package/templates/nextjs-standalone/src/lib/scheduler/executor.ts +73 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-email.ts +109 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-slack.ts +35 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-webhook.ts +37 -0
- package/templates/nextjs-standalone/src/lib/scheduler/index.ts +7 -0
- package/templates/nextjs-standalone/src/lib/security.ts +11 -0
- package/templates/nextjs-standalone/src/lib/semantic-index.ts +503 -0
- package/templates/nextjs-standalone/src/lib/semantic.ts +387 -0
- package/templates/nextjs-standalone/src/lib/sidecar-types.ts +16 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/api.test.ts +160 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/format.test.ts +237 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/store.test.ts +188 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/threads.test.ts +112 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/verify.test.ts +111 -0
- package/templates/nextjs-standalone/src/lib/slack/api.ts +102 -0
- package/templates/nextjs-standalone/src/lib/slack/format.ts +209 -0
- package/templates/nextjs-standalone/src/lib/slack/store.ts +107 -0
- package/templates/nextjs-standalone/src/lib/slack/threads.ts +64 -0
- package/templates/nextjs-standalone/src/lib/slack/verify.ts +71 -0
- package/templates/nextjs-standalone/src/lib/startup.ts +730 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +235 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +1012 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/email.test.ts +378 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/audit.ts +47 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/email.ts +191 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/handler.ts +591 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/index.ts +23 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/jira.ts +220 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +343 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-sandbox.ts +264 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +163 -0
- package/templates/nextjs-standalone/src/lib/tools/explore.ts +379 -0
- package/templates/nextjs-standalone/src/lib/tools/registry.ts +221 -0
- package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +138 -0
- package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +172 -0
- package/templates/nextjs-standalone/src/lib/tools/sql.ts +680 -0
- package/templates/nextjs-standalone/src/lib/tracing.ts +40 -0
- package/templates/nextjs-standalone/src/lib/utils.ts +6 -0
- package/templates/nextjs-standalone/src/test-setup.ts +38 -0
- package/templates/nextjs-standalone/src/ui/components/actions/action-approval-card.tsx +295 -0
- package/templates/nextjs-standalone/src/ui/components/actions/action-status-badge.tsx +50 -0
- package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +26 -0
- package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +96 -0
- package/templates/nextjs-standalone/src/ui/components/admin/empty-state.tsx +24 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +233 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-list.tsx +96 -0
- package/templates/nextjs-standalone/src/ui/components/admin/error-banner.tsx +22 -0
- package/templates/nextjs-standalone/src/ui/components/admin/feature-disabled.tsx +44 -0
- package/templates/nextjs-standalone/src/ui/components/admin/health-badge.tsx +30 -0
- package/templates/nextjs-standalone/src/ui/components/admin/loading-state.tsx +14 -0
- package/templates/nextjs-standalone/src/ui/components/admin/stat-card.tsx +32 -0
- package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +370 -0
- package/templates/nextjs-standalone/src/ui/components/chart/chart-detection.ts +261 -0
- package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +375 -0
- package/templates/nextjs-standalone/src/ui/components/chat/api-key-bar.tsx +66 -0
- package/templates/nextjs-standalone/src/ui/components/chat/copy-button.tsx +25 -0
- package/templates/nextjs-standalone/src/ui/components/chat/data-table.tsx +102 -0
- package/templates/nextjs-standalone/src/ui/components/chat/error-banner.tsx +32 -0
- package/templates/nextjs-standalone/src/ui/components/chat/explore-card.tsx +41 -0
- package/templates/nextjs-standalone/src/ui/components/chat/loading-card.tsx +10 -0
- package/templates/nextjs-standalone/src/ui/components/chat/managed-auth-card.tsx +116 -0
- package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +72 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +30 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +144 -0
- package/templates/nextjs-standalone/src/ui/components/chat/starter-prompts.ts +6 -0
- package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +40 -0
- package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +19 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +120 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-list.tsx +66 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/delete-confirmation.tsx +27 -0
- package/templates/nextjs-standalone/src/ui/context.tsx +78 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-admin-fetch.ts +104 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +184 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -0
- package/templates/nextjs-standalone/src/ui/lib/action-types.ts +63 -0
- package/templates/nextjs-standalone/src/ui/lib/helpers.ts +104 -0
- package/templates/nextjs-standalone/src/ui/lib/types.ts +145 -0
- package/templates/nextjs-standalone/tsconfig.json +32 -0
- 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;
|