javi-forge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore.template +105 -0
- package/.releaserc +44 -0
- package/README.md +45 -0
- package/ai-config/.skillignore +15 -0
- package/ai-config/AUTO_INVOKE.md +300 -0
- package/ai-config/agents/_TEMPLATE.md +93 -0
- package/ai-config/agents/business/api-designer.md +1657 -0
- package/ai-config/agents/business/business-analyst.md +1331 -0
- package/ai-config/agents/business/product-strategist.md +206 -0
- package/ai-config/agents/business/project-manager.md +178 -0
- package/ai-config/agents/business/requirements-analyst.md +1277 -0
- package/ai-config/agents/business/technical-writer.md +1679 -0
- package/ai-config/agents/creative/ux-designer.md +205 -0
- package/ai-config/agents/data-ai/ai-engineer.md +487 -0
- package/ai-config/agents/data-ai/analytics-engineer.md +953 -0
- package/ai-config/agents/data-ai/data-engineer.md +173 -0
- package/ai-config/agents/data-ai/data-scientist.md +672 -0
- package/ai-config/agents/data-ai/mlops-engineer.md +814 -0
- package/ai-config/agents/data-ai/prompt-engineer.md +772 -0
- package/ai-config/agents/development/angular-expert.md +620 -0
- package/ai-config/agents/development/backend-architect.md +795 -0
- package/ai-config/agents/development/database-specialist.md +212 -0
- package/ai-config/agents/development/frontend-specialist.md +686 -0
- package/ai-config/agents/development/fullstack-engineer.md +668 -0
- package/ai-config/agents/development/golang-pro.md +338 -0
- package/ai-config/agents/development/java-enterprise.md +400 -0
- package/ai-config/agents/development/javascript-pro.md +422 -0
- package/ai-config/agents/development/nextjs-pro.md +474 -0
- package/ai-config/agents/development/python-pro.md +570 -0
- package/ai-config/agents/development/react-pro.md +487 -0
- package/ai-config/agents/development/rust-pro.md +246 -0
- package/ai-config/agents/development/spring-boot-4-expert.md +326 -0
- package/ai-config/agents/development/typescript-pro.md +336 -0
- package/ai-config/agents/development/vue-specialist.md +605 -0
- package/ai-config/agents/infrastructure/cloud-architect.md +472 -0
- package/ai-config/agents/infrastructure/deployment-manager.md +358 -0
- package/ai-config/agents/infrastructure/devops-engineer.md +455 -0
- package/ai-config/agents/infrastructure/incident-responder.md +519 -0
- package/ai-config/agents/infrastructure/kubernetes-expert.md +705 -0
- package/ai-config/agents/infrastructure/monitoring-specialist.md +674 -0
- package/ai-config/agents/infrastructure/performance-engineer.md +658 -0
- package/ai-config/agents/orchestrator.md +241 -0
- package/ai-config/agents/quality/accessibility-auditor.md +1204 -0
- package/ai-config/agents/quality/code-reviewer-compact.md +123 -0
- package/ai-config/agents/quality/code-reviewer.md +363 -0
- package/ai-config/agents/quality/dependency-manager.md +743 -0
- package/ai-config/agents/quality/e2e-test-specialist.md +1005 -0
- package/ai-config/agents/quality/performance-tester.md +1086 -0
- package/ai-config/agents/quality/security-auditor.md +133 -0
- package/ai-config/agents/quality/test-engineer.md +453 -0
- package/ai-config/agents/specialists/api-designer.md +87 -0
- package/ai-config/agents/specialists/backend-architect.md +73 -0
- package/ai-config/agents/specialists/code-reviewer.md +77 -0
- package/ai-config/agents/specialists/db-optimizer.md +75 -0
- package/ai-config/agents/specialists/devops-engineer.md +83 -0
- package/ai-config/agents/specialists/documentation-writer.md +78 -0
- package/ai-config/agents/specialists/frontend-developer.md +75 -0
- package/ai-config/agents/specialists/performance-analyst.md +82 -0
- package/ai-config/agents/specialists/refactor-specialist.md +74 -0
- package/ai-config/agents/specialists/security-auditor.md +74 -0
- package/ai-config/agents/specialists/test-engineer.md +81 -0
- package/ai-config/agents/specialists/ux-consultant.md +76 -0
- package/ai-config/agents/specialized/agent-generator.md +1190 -0
- package/ai-config/agents/specialized/blockchain-developer.md +149 -0
- package/ai-config/agents/specialized/code-migrator.md +892 -0
- package/ai-config/agents/specialized/context-manager.md +978 -0
- package/ai-config/agents/specialized/documentation-writer.md +1078 -0
- package/ai-config/agents/specialized/ecommerce-expert.md +1756 -0
- package/ai-config/agents/specialized/embedded-engineer.md +1714 -0
- package/ai-config/agents/specialized/error-detective.md +1034 -0
- package/ai-config/agents/specialized/fintech-specialist.md +1659 -0
- package/ai-config/agents/specialized/freelance-project-planner-v2.md +1988 -0
- package/ai-config/agents/specialized/freelance-project-planner-v3.md +2136 -0
- package/ai-config/agents/specialized/freelance-project-planner-v4.md +4503 -0
- package/ai-config/agents/specialized/freelance-project-planner.md +722 -0
- package/ai-config/agents/specialized/game-developer.md +1963 -0
- package/ai-config/agents/specialized/healthcare-dev.md +1620 -0
- package/ai-config/agents/specialized/mobile-developer.md +188 -0
- package/ai-config/agents/specialized/parallel-plan-executor.md +506 -0
- package/ai-config/agents/specialized/plan-executor.md +485 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/00-INDEX.md +485 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/01-CORE.md +3493 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/02-SELF-CORRECTION.md +778 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/03-PROGRESSIVE-SETUP.md +918 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/04-DEPLOYMENT.md +1537 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/05-TESTING.md +2633 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/06-OPERATIONS.md +5610 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/INSTALL.md +335 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/QUICK-REFERENCE.txt +215 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/README.md +260 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/START-HERE.md +379 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/WORKFLOW-DIAGRAM.md +355 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/solo-dev-planner.md +279 -0
- package/ai-config/agents/specialized/template-writer.md +347 -0
- package/ai-config/agents/specialized/test-runner.md +99 -0
- package/ai-config/agents/specialized/vibekanban-smart-worker.md +244 -0
- package/ai-config/agents/specialized/wave-executor.md +138 -0
- package/ai-config/agents/specialized/workflow-optimizer.md +1114 -0
- package/ai-config/commands/git/changelog.md +32 -0
- package/ai-config/commands/git/ci-local.md +70 -0
- package/ai-config/commands/git/commit.md +35 -0
- package/ai-config/commands/git/fix-issue.md +23 -0
- package/ai-config/commands/git/pr-create.md +42 -0
- package/ai-config/commands/git/pr-review.md +50 -0
- package/ai-config/commands/git/worktree.md +39 -0
- package/ai-config/commands/refactoring/cleanup.md +24 -0
- package/ai-config/commands/refactoring/dead-code.md +40 -0
- package/ai-config/commands/refactoring/extract.md +31 -0
- package/ai-config/commands/testing/e2e.md +30 -0
- package/ai-config/commands/testing/tdd.md +36 -0
- package/ai-config/commands/testing/test-coverage.md +30 -0
- package/ai-config/commands/testing/test-fix.md +24 -0
- package/ai-config/commands/workflow/generate-agents-md.md +85 -0
- package/ai-config/commands/workflow/planning.md +47 -0
- package/ai-config/commands/workflows/compound.md +89 -0
- package/ai-config/commands/workflows/plan.md +77 -0
- package/ai-config/commands/workflows/review.md +78 -0
- package/ai-config/commands/workflows/work.md +75 -0
- package/ai-config/config.yaml +18 -0
- package/ai-config/hooks/_TEMPLATE.md +96 -0
- package/ai-config/hooks/block-dangerous-commands.md +75 -0
- package/ai-config/hooks/commit-guard.md +90 -0
- package/ai-config/hooks/context-loader.md +73 -0
- package/ai-config/hooks/improve-prompt.md +91 -0
- package/ai-config/hooks/learning-log.md +72 -0
- package/ai-config/hooks/model-router.md +86 -0
- package/ai-config/hooks/secret-scanner.md +64 -0
- package/ai-config/hooks/skill-validator.md +102 -0
- package/ai-config/hooks/task-artifact.md +114 -0
- package/ai-config/hooks/validate-workflow.md +100 -0
- package/ai-config/prompts/base.md +71 -0
- package/ai-config/prompts/modes/debug.md +34 -0
- package/ai-config/prompts/modes/deploy.md +40 -0
- package/ai-config/prompts/modes/research.md +32 -0
- package/ai-config/prompts/modes/review.md +33 -0
- package/ai-config/prompts/review-policy.md +79 -0
- package/ai-config/skills/_TEMPLATE.md +157 -0
- package/ai-config/skills/backend/api-gateway/SKILL.md +254 -0
- package/ai-config/skills/backend/bff-concepts/SKILL.md +239 -0
- package/ai-config/skills/backend/bff-spring/SKILL.md +364 -0
- package/ai-config/skills/backend/chi-router/SKILL.md +396 -0
- package/ai-config/skills/backend/error-handling/SKILL.md +255 -0
- package/ai-config/skills/backend/exceptions-spring/SKILL.md +323 -0
- package/ai-config/skills/backend/fastapi/SKILL.md +302 -0
- package/ai-config/skills/backend/gateway-spring/SKILL.md +390 -0
- package/ai-config/skills/backend/go-backend/SKILL.md +457 -0
- package/ai-config/skills/backend/gradle-multimodule/SKILL.md +274 -0
- package/ai-config/skills/backend/graphql-concepts/SKILL.md +352 -0
- package/ai-config/skills/backend/graphql-spring/SKILL.md +398 -0
- package/ai-config/skills/backend/grpc-concepts/SKILL.md +283 -0
- package/ai-config/skills/backend/grpc-spring/SKILL.md +445 -0
- package/ai-config/skills/backend/jwt-auth/SKILL.md +412 -0
- package/ai-config/skills/backend/notifications-concepts/SKILL.md +259 -0
- package/ai-config/skills/backend/recommendations-concepts/SKILL.md +261 -0
- package/ai-config/skills/backend/search-concepts/SKILL.md +263 -0
- package/ai-config/skills/backend/search-spring/SKILL.md +375 -0
- package/ai-config/skills/backend/spring-boot-4/SKILL.md +172 -0
- package/ai-config/skills/backend/websockets/SKILL.md +532 -0
- package/ai-config/skills/data-ai/ai-ml/SKILL.md +423 -0
- package/ai-config/skills/data-ai/analytics-concepts/SKILL.md +195 -0
- package/ai-config/skills/data-ai/analytics-spring/SKILL.md +340 -0
- package/ai-config/skills/data-ai/duckdb-analytics/SKILL.md +440 -0
- package/ai-config/skills/data-ai/langchain/SKILL.md +238 -0
- package/ai-config/skills/data-ai/mlflow/SKILL.md +302 -0
- package/ai-config/skills/data-ai/onnx-inference/SKILL.md +290 -0
- package/ai-config/skills/data-ai/powerbi/SKILL.md +352 -0
- package/ai-config/skills/data-ai/pytorch/SKILL.md +274 -0
- package/ai-config/skills/data-ai/scikit-learn/SKILL.md +321 -0
- package/ai-config/skills/data-ai/vector-db/SKILL.md +301 -0
- package/ai-config/skills/database/graph-databases/SKILL.md +218 -0
- package/ai-config/skills/database/graph-spring/SKILL.md +361 -0
- package/ai-config/skills/database/pgx-postgres/SKILL.md +512 -0
- package/ai-config/skills/database/redis-cache/SKILL.md +343 -0
- package/ai-config/skills/database/sqlite-embedded/SKILL.md +388 -0
- package/ai-config/skills/database/timescaledb/SKILL.md +320 -0
- package/ai-config/skills/docs/api-documentation/SKILL.md +293 -0
- package/ai-config/skills/docs/docs-spring/SKILL.md +377 -0
- package/ai-config/skills/docs/mustache-templates/SKILL.md +190 -0
- package/ai-config/skills/docs/technical-docs/SKILL.md +447 -0
- package/ai-config/skills/frontend/astro-ssr/SKILL.md +441 -0
- package/ai-config/skills/frontend/frontend-design/SKILL.md +54 -0
- package/ai-config/skills/frontend/frontend-web/SKILL.md +368 -0
- package/ai-config/skills/frontend/mantine-ui/SKILL.md +396 -0
- package/ai-config/skills/frontend/tanstack-query/SKILL.md +439 -0
- package/ai-config/skills/frontend/zod-validation/SKILL.md +417 -0
- package/ai-config/skills/frontend/zustand-state/SKILL.md +350 -0
- package/ai-config/skills/infrastructure/chaos-engineering/SKILL.md +244 -0
- package/ai-config/skills/infrastructure/chaos-spring/SKILL.md +378 -0
- package/ai-config/skills/infrastructure/devops-infra/SKILL.md +435 -0
- package/ai-config/skills/infrastructure/docker-containers/SKILL.md +420 -0
- package/ai-config/skills/infrastructure/kubernetes/SKILL.md +456 -0
- package/ai-config/skills/infrastructure/opentelemetry/SKILL.md +546 -0
- package/ai-config/skills/infrastructure/traefik-proxy/SKILL.md +474 -0
- package/ai-config/skills/infrastructure/woodpecker-ci/SKILL.md +315 -0
- package/ai-config/skills/mobile/ionic-capacitor/SKILL.md +504 -0
- package/ai-config/skills/mobile/mobile-ionic/SKILL.md +448 -0
- package/ai-config/skills/prompt-improver/SKILL.md +125 -0
- package/ai-config/skills/quality/ghagga-review/SKILL.md +216 -0
- package/ai-config/skills/references/hooks-patterns/SKILL.md +238 -0
- package/ai-config/skills/references/mcp-servers/SKILL.md +275 -0
- package/ai-config/skills/references/plugins-reference/SKILL.md +110 -0
- package/ai-config/skills/references/skills-reference/SKILL.md +420 -0
- package/ai-config/skills/references/subagent-templates/SKILL.md +193 -0
- package/ai-config/skills/systems-iot/modbus-protocol/SKILL.md +410 -0
- package/ai-config/skills/systems-iot/mqtt-rumqttc/SKILL.md +408 -0
- package/ai-config/skills/systems-iot/rust-systems/SKILL.md +386 -0
- package/ai-config/skills/systems-iot/tokio-async/SKILL.md +324 -0
- package/ai-config/skills/testing/playwright-e2e/SKILL.md +289 -0
- package/ai-config/skills/testing/testcontainers/SKILL.md +299 -0
- package/ai-config/skills/testing/vitest-testing/SKILL.md +381 -0
- package/ai-config/skills/workflow/ci-local-guide/SKILL.md +118 -0
- package/ai-config/skills/workflow/claude-automation-recommender/SKILL.md +299 -0
- package/ai-config/skills/workflow/claude-md-improver/SKILL.md +158 -0
- package/ai-config/skills/workflow/finishing-a-development-branch/SKILL.md +117 -0
- package/ai-config/skills/workflow/git-github/SKILL.md +334 -0
- package/ai-config/skills/workflow/git-github/references/examples.md +160 -0
- package/ai-config/skills/workflow/git-workflow/SKILL.md +214 -0
- package/ai-config/skills/workflow/ide-plugins/SKILL.md +277 -0
- package/ai-config/skills/workflow/ide-plugins-intellij/SKILL.md +401 -0
- package/ai-config/skills/workflow/obsidian-brain-workflow/SKILL.md +199 -0
- package/ai-config/skills/workflow/using-git-worktrees/SKILL.md +100 -0
- package/ai-config/skills/workflow/verification-before-completion/SKILL.md +73 -0
- package/ai-config/skills/workflow/wave-workflow/SKILL.md +178 -0
- package/ci-local/README.md +170 -0
- package/ci-local/ci-local.sh +297 -0
- package/ci-local/hooks/commit-msg +74 -0
- package/ci-local/hooks/pre-commit +162 -0
- package/ci-local/hooks/pre-push +41 -0
- package/ci-local/install.sh +49 -0
- package/ci-local/semgrep.yml +214 -0
- package/dist/commands/analyze.d.ts +9 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +55 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/analyze.test.d.ts +2 -0
- package/dist/commands/analyze.test.d.ts.map +1 -0
- package/dist/commands/analyze.test.js +145 -0
- package/dist/commands/analyze.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +7 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +158 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/doctor.test.d.ts +2 -0
- package/dist/commands/doctor.test.d.ts.map +1 -0
- package/dist/commands/doctor.test.js +200 -0
- package/dist/commands/doctor.test.js.map +1 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +283 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +2 -0
- package/dist/commands/init.test.d.ts.map +1 -0
- package/dist/commands/init.test.js +271 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +201 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +57 -0
- package/dist/constants.js.map +1 -0
- package/dist/e2e/aggressive.e2e.test.d.ts +2 -0
- package/dist/e2e/aggressive.e2e.test.d.ts.map +1 -0
- package/dist/e2e/aggressive.e2e.test.js +350 -0
- package/dist/e2e/aggressive.e2e.test.js.map +1 -0
- package/dist/e2e/commands.e2e.test.d.ts +2 -0
- package/dist/e2e/commands.e2e.test.d.ts.map +1 -0
- package/dist/e2e/commands.e2e.test.js +213 -0
- package/dist/e2e/commands.e2e.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/common.d.ts +17 -0
- package/dist/lib/common.d.ts.map +1 -0
- package/dist/lib/common.js +111 -0
- package/dist/lib/common.js.map +1 -0
- package/dist/lib/common.test.d.ts +2 -0
- package/dist/lib/common.test.d.ts.map +1 -0
- package/dist/lib/common.test.js +316 -0
- package/dist/lib/common.test.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +18 -0
- package/dist/lib/frontmatter.d.ts.map +1 -0
- package/dist/lib/frontmatter.js +61 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/frontmatter.test.d.ts +2 -0
- package/dist/lib/frontmatter.test.d.ts.map +1 -0
- package/dist/lib/frontmatter.test.js +257 -0
- package/dist/lib/frontmatter.test.js.map +1 -0
- package/dist/lib/template.d.ts +24 -0
- package/dist/lib/template.d.ts.map +1 -0
- package/dist/lib/template.js +78 -0
- package/dist/lib/template.js.map +1 -0
- package/dist/lib/template.test.d.ts +2 -0
- package/dist/lib/template.test.d.ts.map +1 -0
- package/dist/lib/template.test.js +201 -0
- package/dist/lib/template.test.js.map +1 -0
- package/dist/types/index.d.ts +48 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/AnalyzeUI.d.ts +7 -0
- package/dist/ui/AnalyzeUI.d.ts.map +1 -0
- package/dist/ui/AnalyzeUI.js +100 -0
- package/dist/ui/AnalyzeUI.js.map +1 -0
- package/dist/ui/App.d.ts +13 -0
- package/dist/ui/App.d.ts.map +1 -0
- package/dist/ui/App.js +100 -0
- package/dist/ui/App.js.map +1 -0
- package/dist/ui/CIContext.d.ts +9 -0
- package/dist/ui/CIContext.d.ts.map +1 -0
- package/dist/ui/CIContext.js +9 -0
- package/dist/ui/CIContext.js.map +1 -0
- package/dist/ui/CISelector.d.ts +8 -0
- package/dist/ui/CISelector.d.ts.map +1 -0
- package/dist/ui/CISelector.js +45 -0
- package/dist/ui/CISelector.js.map +1 -0
- package/dist/ui/Doctor.d.ts +3 -0
- package/dist/ui/Doctor.d.ts.map +1 -0
- package/dist/ui/Doctor.js +89 -0
- package/dist/ui/Doctor.js.map +1 -0
- package/dist/ui/Header.d.ts +8 -0
- package/dist/ui/Header.d.ts.map +1 -0
- package/dist/ui/Header.js +30 -0
- package/dist/ui/Header.js.map +1 -0
- package/dist/ui/MemorySelector.d.ts +8 -0
- package/dist/ui/MemorySelector.d.ts.map +1 -0
- package/dist/ui/MemorySelector.js +46 -0
- package/dist/ui/MemorySelector.js.map +1 -0
- package/dist/ui/NameInput.d.ts +8 -0
- package/dist/ui/NameInput.d.ts.map +1 -0
- package/dist/ui/NameInput.js +69 -0
- package/dist/ui/NameInput.js.map +1 -0
- package/dist/ui/OptionSelector.d.ts +12 -0
- package/dist/ui/OptionSelector.d.ts.map +1 -0
- package/dist/ui/OptionSelector.js +69 -0
- package/dist/ui/OptionSelector.js.map +1 -0
- package/dist/ui/Progress.d.ts +11 -0
- package/dist/ui/Progress.d.ts.map +1 -0
- package/dist/ui/Progress.js +58 -0
- package/dist/ui/Progress.js.map +1 -0
- package/dist/ui/StackSelector.d.ts +9 -0
- package/dist/ui/StackSelector.d.ts.map +1 -0
- package/dist/ui/StackSelector.js +65 -0
- package/dist/ui/StackSelector.js.map +1 -0
- package/dist/ui/Summary.d.ts +12 -0
- package/dist/ui/Summary.d.ts.map +1 -0
- package/dist/ui/Summary.js +114 -0
- package/dist/ui/Summary.js.map +1 -0
- package/dist/ui/SyncUI.d.ts +10 -0
- package/dist/ui/SyncUI.d.ts.map +1 -0
- package/dist/ui/SyncUI.js +64 -0
- package/dist/ui/SyncUI.js.map +1 -0
- package/dist/ui/Welcome.d.ts +7 -0
- package/dist/ui/Welcome.d.ts.map +1 -0
- package/dist/ui/Welcome.js +45 -0
- package/dist/ui/Welcome.js.map +1 -0
- package/dist/ui/theme.d.ts +10 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +9 -0
- package/dist/ui/theme.js.map +1 -0
- package/modules/engram/.gitignore-snippet.txt +6 -0
- package/modules/engram/.mcp-config-snippet.json +11 -0
- package/modules/engram/README.md +146 -0
- package/modules/engram/install-engram.sh +216 -0
- package/modules/ghagga/.env.example +43 -0
- package/modules/ghagga/README.md +153 -0
- package/modules/ghagga/docker-compose.yml +80 -0
- package/modules/ghagga/setup-ghagga.sh +139 -0
- package/modules/memory-simple/.project/NOTES.md +22 -0
- package/modules/memory-simple/README.md +23 -0
- package/modules/obsidian-brain/.obsidian/app.json +23 -0
- package/modules/obsidian-brain/.obsidian/appearance.json +5 -0
- package/modules/obsidian-brain/.obsidian/bookmarks.json +34 -0
- package/modules/obsidian-brain/.obsidian/community-plugins.json +1 -0
- package/modules/obsidian-brain/.obsidian/core-plugins-migration.json +21 -0
- package/modules/obsidian-brain/.obsidian/core-plugins.json +18 -0
- package/modules/obsidian-brain/.obsidian/daily-notes.json +5 -0
- package/modules/obsidian-brain/.obsidian/graph.json +37 -0
- package/modules/obsidian-brain/.obsidian/hotkeys.json +14 -0
- package/modules/obsidian-brain/.obsidian/plugins/dataview/data.json +25 -0
- package/modules/obsidian-brain/.obsidian/plugins/obsidian-kanban/data.json +29 -0
- package/modules/obsidian-brain/.obsidian/plugins/templater-obsidian/data.json +18 -0
- package/modules/obsidian-brain/.obsidian/snippets/project-memory.css +71 -0
- package/modules/obsidian-brain/.obsidian-gitignore-snippet.txt +8 -0
- package/modules/obsidian-brain/.project/Attachments/.gitkeep +0 -0
- package/modules/obsidian-brain/.project/Memory/BLOCKERS.md +78 -0
- package/modules/obsidian-brain/.project/Memory/CONTEXT.md +102 -0
- package/modules/obsidian-brain/.project/Memory/DASHBOARD.md +73 -0
- package/modules/obsidian-brain/.project/Memory/DECISIONS.md +87 -0
- package/modules/obsidian-brain/.project/Memory/KANBAN.md +15 -0
- package/modules/obsidian-brain/.project/Memory/README.md +61 -0
- package/modules/obsidian-brain/.project/Memory/WAVES.md +78 -0
- package/modules/obsidian-brain/.project/Sessions/TEMPLATE.md +99 -0
- package/modules/obsidian-brain/.project/Templates/ADR.md +33 -0
- package/modules/obsidian-brain/.project/Templates/Blocker.md +21 -0
- package/modules/obsidian-brain/.project/Templates/Session.md +88 -0
- package/modules/obsidian-brain/README.md +268 -0
- package/modules/obsidian-brain/new-wave.sh +182 -0
- package/package.json +51 -0
- package/schemas/agent.schema.json +34 -0
- package/schemas/ai-config.schema.json +28 -0
- package/schemas/skill.schema.json +44 -0
- package/src/commands/analyze.test.ts +145 -0
- package/src/commands/analyze.ts +69 -0
- package/src/commands/doctor.test.ts +208 -0
- package/src/commands/doctor.ts +163 -0
- package/src/commands/init.test.ts +298 -0
- package/src/commands/init.ts +285 -0
- package/src/constants.ts +69 -0
- package/src/e2e/aggressive.e2e.test.ts +557 -0
- package/src/e2e/commands.e2e.test.ts +298 -0
- package/src/index.tsx +106 -0
- package/src/lib/common.test.ts +318 -0
- package/src/lib/common.ts +127 -0
- package/src/lib/frontmatter.test.ts +291 -0
- package/src/lib/frontmatter.ts +77 -0
- package/src/lib/template.test.ts +226 -0
- package/src/lib/template.ts +99 -0
- package/src/types/index.ts +53 -0
- package/src/ui/AnalyzeUI.tsx +133 -0
- package/src/ui/App.tsx +175 -0
- package/src/ui/CIContext.tsx +25 -0
- package/src/ui/CISelector.tsx +72 -0
- package/src/ui/Doctor.tsx +122 -0
- package/src/ui/Header.tsx +48 -0
- package/src/ui/MemorySelector.tsx +73 -0
- package/src/ui/NameInput.tsx +82 -0
- package/src/ui/OptionSelector.tsx +100 -0
- package/src/ui/Progress.tsx +88 -0
- package/src/ui/StackSelector.tsx +101 -0
- package/src/ui/Summary.tsx +134 -0
- package/src/ui/Welcome.tsx +54 -0
- package/src/ui/theme.ts +10 -0
- package/stryker.config.json +19 -0
- package/tasks/_TEMPLATE/files-edited.md +3 -0
- package/tasks/_TEMPLATE/plan.md +3 -0
- package/tasks/_TEMPLATE/research.md +3 -0
- package/tasks/_TEMPLATE/verification.md +5 -0
- package/templates/common/dependabot/cargo.yml +11 -0
- package/templates/common/dependabot/github-actions.yml +16 -0
- package/templates/common/dependabot/gomod.yml +15 -0
- package/templates/common/dependabot/gradle.yml +15 -0
- package/templates/common/dependabot/header.yml +3 -0
- package/templates/common/dependabot/maven.yml +15 -0
- package/templates/common/dependabot/npm.yml +20 -0
- package/templates/common/dependabot/pip.yml +11 -0
- package/templates/dependabot.yml +162 -0
- package/templates/github/ci-go.yml +41 -0
- package/templates/github/ci-java.yml +45 -0
- package/templates/github/ci-monorepo.yml +150 -0
- package/templates/github/ci-node.yml +42 -0
- package/templates/github/ci-python.yml +42 -0
- package/templates/github/ci-rust.yml +42 -0
- package/templates/github/dependabot-automerge.yml +40 -0
- package/templates/gitlab/gitlab-ci-go.yml +88 -0
- package/templates/gitlab/gitlab-ci-java.yml +79 -0
- package/templates/gitlab/gitlab-ci-monorepo.yml +126 -0
- package/templates/gitlab/gitlab-ci-node.yml +63 -0
- package/templates/gitlab/gitlab-ci-python.yml +147 -0
- package/templates/gitlab/gitlab-ci-rust.yml +67 -0
- package/templates/global/claude-settings.json +98 -0
- package/templates/global/codex-config.toml +8 -0
- package/templates/global/copilot-instructions/base-rules.instructions.md +13 -0
- package/templates/global/copilot-instructions/sdd-orchestrator.instructions.md +37 -0
- package/templates/global/gemini-commands/cleanup.toml +20 -0
- package/templates/global/gemini-commands/commit.toml +15 -0
- package/templates/global/gemini-commands/dead-code.toml +22 -0
- package/templates/global/gemini-commands/plan.toml +30 -0
- package/templates/global/gemini-commands/review.toml +17 -0
- package/templates/global/gemini-commands/sdd-apply.toml +22 -0
- package/templates/global/gemini-commands/sdd-ff.toml +14 -0
- package/templates/global/gemini-commands/sdd-new.toml +21 -0
- package/templates/global/gemini-commands/sdd-verify.toml +21 -0
- package/templates/global/gemini-commands/tdd.toml +26 -0
- package/templates/global/gemini-settings.json +8 -0
- package/templates/global/opencode-config.json +44 -0
- package/templates/global/sdd-instructions.md +47 -0
- package/templates/global/sdd-orchestrator-claude.md +46 -0
- package/templates/global/sdd-orchestrator-copilot.md +34 -0
- package/templates/renovate.json +69 -0
- package/templates/woodpecker/monorepo/backend.yml +34 -0
- package/templates/woodpecker/monorepo/frontend.yml +34 -0
- package/templates/woodpecker/monorepo/summary.yml +25 -0
- package/templates/woodpecker/woodpecker-go.yml +51 -0
- package/templates/woodpecker/woodpecker-java.yml +67 -0
- package/templates/woodpecker/woodpecker-node.yml +47 -0
- package/templates/woodpecker/woodpecker-python.yml +108 -0
- package/templates/woodpecker/woodpecker-rust.yml +57 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +16 -0
- package/workflows/reusable-build-go.yml +111 -0
- package/workflows/reusable-build-java.yml +120 -0
- package/workflows/reusable-build-node.yml +145 -0
- package/workflows/reusable-build-python.yml +159 -0
- package/workflows/reusable-build-rust.yml +135 -0
- package/workflows/reusable-docker.yml +120 -0
- package/workflows/reusable-ghagga-review.yml +165 -0
- package/workflows/reusable-release.yml +91 -0
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: analytics-engineer
|
|
3
|
+
description: Analytics engineering expert specializing in dbt, data modeling, BI tools, and modern data stack architecture
|
|
4
|
+
trigger: >
|
|
5
|
+
analytics engineering, dbt, data modeling, dimensional modeling, BI tools,
|
|
6
|
+
Tableau, Looker, Power BI, data warehouse, data mart, SQL transformation,
|
|
7
|
+
modern data stack, data quality, metrics layer
|
|
8
|
+
category: data-ai
|
|
9
|
+
color: indigo
|
|
10
|
+
tools: Write, Read, MultiEdit, Bash, Grep, Glob
|
|
11
|
+
config:
|
|
12
|
+
model: sonnet
|
|
13
|
+
metadata:
|
|
14
|
+
version: "2.0"
|
|
15
|
+
updated: "2026-02"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
You are an analytics engineer with expertise in data transformation, modeling, business intelligence, and modern data stack architecture.
|
|
19
|
+
|
|
20
|
+
## Core Expertise
|
|
21
|
+
- Data modeling and transformation with dbt
|
|
22
|
+
- Data warehouse design and optimization
|
|
23
|
+
- Business intelligence and visualization
|
|
24
|
+
- Data pipeline orchestration and automation
|
|
25
|
+
- Data quality and testing frameworks
|
|
26
|
+
- Modern data stack architecture
|
|
27
|
+
- Dimensional modeling and data marts
|
|
28
|
+
- Self-service analytics and governance
|
|
29
|
+
|
|
30
|
+
## Technical Stack
|
|
31
|
+
- **Transformation**: dbt (Data Build Tool), SQL, Python
|
|
32
|
+
- **Data Warehouses**: Snowflake, BigQuery, Redshift, Databricks
|
|
33
|
+
- **BI Tools**: Tableau, Looker, Power BI, Metabase, Superset
|
|
34
|
+
- **Orchestration**: Airflow, Prefect, Dagster, dbt Cloud
|
|
35
|
+
- **Data Quality**: Great Expectations, dbt tests, Monte Carlo
|
|
36
|
+
- **Version Control**: Git, dbt Cloud IDE, VS Code
|
|
37
|
+
- **Monitoring**: dbt docs, Lightdash, DataHub
|
|
38
|
+
|
|
39
|
+
## dbt Project Structure and Best Practices
|
|
40
|
+
```yaml
|
|
41
|
+
# dbt_project.yml
|
|
42
|
+
name: 'analytics_project'
|
|
43
|
+
version: '1.0.0'
|
|
44
|
+
config-version: 2
|
|
45
|
+
|
|
46
|
+
profile: 'analytics_project'
|
|
47
|
+
|
|
48
|
+
model-paths: ["models"]
|
|
49
|
+
analysis-paths: ["analyses"]
|
|
50
|
+
test-paths: ["tests"]
|
|
51
|
+
seed-paths: ["seeds"]
|
|
52
|
+
macro-paths: ["macros"]
|
|
53
|
+
snapshot-paths: ["snapshots"]
|
|
54
|
+
|
|
55
|
+
target-path: "target"
|
|
56
|
+
clean-targets:
|
|
57
|
+
- "target"
|
|
58
|
+
- "dbt_packages"
|
|
59
|
+
|
|
60
|
+
models:
|
|
61
|
+
analytics_project:
|
|
62
|
+
+materialized: table
|
|
63
|
+
staging:
|
|
64
|
+
+materialized: view
|
|
65
|
+
+docs:
|
|
66
|
+
node_color: "lightblue"
|
|
67
|
+
intermediate:
|
|
68
|
+
+materialized: ephemeral
|
|
69
|
+
+docs:
|
|
70
|
+
node_color: "orange"
|
|
71
|
+
marts:
|
|
72
|
+
+materialized: table
|
|
73
|
+
+docs:
|
|
74
|
+
node_color: "lightgreen"
|
|
75
|
+
core:
|
|
76
|
+
+materialized: table
|
|
77
|
+
finance:
|
|
78
|
+
+materialized: table
|
|
79
|
+
marketing:
|
|
80
|
+
+materialized: table
|
|
81
|
+
|
|
82
|
+
vars:
|
|
83
|
+
start_date: '2020-01-01'
|
|
84
|
+
timezone: 'UTC'
|
|
85
|
+
|
|
86
|
+
seeds:
|
|
87
|
+
analytics_project:
|
|
88
|
+
+column_types:
|
|
89
|
+
id: varchar(50)
|
|
90
|
+
created_at: timestamp
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Advanced Data Modeling Framework
|
|
94
|
+
```sql
|
|
95
|
+
-- models/staging/stg_customers.sql
|
|
96
|
+
{{
|
|
97
|
+
config(
|
|
98
|
+
materialized='view',
|
|
99
|
+
tags=['staging', 'customers']
|
|
100
|
+
)
|
|
101
|
+
}}
|
|
102
|
+
|
|
103
|
+
with source_data as (
|
|
104
|
+
select * from {{ source('raw_data', 'customers') }}
|
|
105
|
+
),
|
|
106
|
+
|
|
107
|
+
cleaned_data as (
|
|
108
|
+
select
|
|
109
|
+
customer_id::varchar as customer_id,
|
|
110
|
+
lower(trim(email)) as email,
|
|
111
|
+
lower(trim(first_name)) as first_name,
|
|
112
|
+
lower(trim(last_name)) as last_name,
|
|
113
|
+
phone_number,
|
|
114
|
+
created_at::timestamp as created_at,
|
|
115
|
+
updated_at::timestamp as updated_at,
|
|
116
|
+
is_active::boolean as is_active,
|
|
117
|
+
|
|
118
|
+
-- Data quality flags
|
|
119
|
+
case
|
|
120
|
+
when email is null or email = '' then false
|
|
121
|
+
when email not like '%@%' then false
|
|
122
|
+
else true
|
|
123
|
+
end as has_valid_email,
|
|
124
|
+
|
|
125
|
+
case
|
|
126
|
+
when first_name is null or first_name = '' then false
|
|
127
|
+
when last_name is null or last_name = '' then false
|
|
128
|
+
else true
|
|
129
|
+
end as has_valid_name
|
|
130
|
+
|
|
131
|
+
from source_data
|
|
132
|
+
where customer_id is not null
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
select * from cleaned_data
|
|
136
|
+
|
|
137
|
+
-- Generic tests in schema.yml
|
|
138
|
+
version: 2
|
|
139
|
+
|
|
140
|
+
sources:
|
|
141
|
+
- name: raw_data
|
|
142
|
+
description: Raw data from operational systems
|
|
143
|
+
tables:
|
|
144
|
+
- name: customers
|
|
145
|
+
description: Customer data from CRM
|
|
146
|
+
columns:
|
|
147
|
+
- name: customer_id
|
|
148
|
+
description: Unique customer identifier
|
|
149
|
+
tests:
|
|
150
|
+
- not_null
|
|
151
|
+
- unique
|
|
152
|
+
|
|
153
|
+
models:
|
|
154
|
+
- name: stg_customers
|
|
155
|
+
description: Cleaned and standardized customer data
|
|
156
|
+
columns:
|
|
157
|
+
- name: customer_id
|
|
158
|
+
description: Unique customer identifier
|
|
159
|
+
tests:
|
|
160
|
+
- not_null
|
|
161
|
+
- unique
|
|
162
|
+
- name: email
|
|
163
|
+
description: Customer email address
|
|
164
|
+
tests:
|
|
165
|
+
- not_null
|
|
166
|
+
- name: has_valid_email
|
|
167
|
+
description: Flag indicating if email format is valid
|
|
168
|
+
tests:
|
|
169
|
+
- accepted_values:
|
|
170
|
+
values: [true, false]
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Dimensional Modeling Implementation
|
|
174
|
+
```sql
|
|
175
|
+
-- models/marts/core/dim_customers.sql
|
|
176
|
+
{{
|
|
177
|
+
config(
|
|
178
|
+
materialized='table',
|
|
179
|
+
indexes=[
|
|
180
|
+
{'columns': ['customer_key'], 'unique': True},
|
|
181
|
+
{'columns': ['customer_id'], 'unique': True},
|
|
182
|
+
{'columns': ['email']},
|
|
183
|
+
]
|
|
184
|
+
)
|
|
185
|
+
}}
|
|
186
|
+
|
|
187
|
+
with customers as (
|
|
188
|
+
select * from {{ ref('stg_customers') }}
|
|
189
|
+
),
|
|
190
|
+
|
|
191
|
+
customer_metrics as (
|
|
192
|
+
select
|
|
193
|
+
customer_id,
|
|
194
|
+
count(*) as total_orders,
|
|
195
|
+
sum(order_amount) as lifetime_value,
|
|
196
|
+
max(order_date) as last_order_date,
|
|
197
|
+
min(order_date) as first_order_date
|
|
198
|
+
from {{ ref('stg_orders') }}
|
|
199
|
+
group by customer_id
|
|
200
|
+
),
|
|
201
|
+
|
|
202
|
+
final as (
|
|
203
|
+
select
|
|
204
|
+
{{ dbt_utils.generate_surrogate_key(['c.customer_id']) }} as customer_key,
|
|
205
|
+
c.customer_id,
|
|
206
|
+
c.email,
|
|
207
|
+
c.first_name,
|
|
208
|
+
c.last_name,
|
|
209
|
+
c.phone_number,
|
|
210
|
+
c.created_at,
|
|
211
|
+
c.is_active,
|
|
212
|
+
|
|
213
|
+
-- Customer segmentation
|
|
214
|
+
case
|
|
215
|
+
when cm.lifetime_value >= 1000 then 'High Value'
|
|
216
|
+
when cm.lifetime_value >= 500 then 'Medium Value'
|
|
217
|
+
when cm.lifetime_value >= 100 then 'Low Value'
|
|
218
|
+
else 'New Customer'
|
|
219
|
+
end as customer_segment,
|
|
220
|
+
|
|
221
|
+
case
|
|
222
|
+
when cm.last_order_date >= current_date - interval '30 days' then 'Active'
|
|
223
|
+
when cm.last_order_date >= current_date - interval '90 days' then 'At Risk'
|
|
224
|
+
when cm.last_order_date >= current_date - interval '365 days' then 'Dormant'
|
|
225
|
+
else 'Churned'
|
|
226
|
+
end as customer_status,
|
|
227
|
+
|
|
228
|
+
coalesce(cm.total_orders, 0) as total_orders,
|
|
229
|
+
coalesce(cm.lifetime_value, 0) as lifetime_value,
|
|
230
|
+
cm.first_order_date,
|
|
231
|
+
cm.last_order_date,
|
|
232
|
+
|
|
233
|
+
current_timestamp as updated_at
|
|
234
|
+
|
|
235
|
+
from customers c
|
|
236
|
+
left join customer_metrics cm
|
|
237
|
+
on c.customer_id = cm.customer_id
|
|
238
|
+
where c.has_valid_email = true
|
|
239
|
+
and c.has_valid_name = true
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
select * from final
|
|
243
|
+
|
|
244
|
+
-- models/marts/core/fct_orders.sql
|
|
245
|
+
{{
|
|
246
|
+
config(
|
|
247
|
+
materialized='table',
|
|
248
|
+
partition_by={
|
|
249
|
+
"field": "order_date",
|
|
250
|
+
"data_type": "date",
|
|
251
|
+
"granularity": "month"
|
|
252
|
+
},
|
|
253
|
+
cluster_by=["customer_id", "order_date"]
|
|
254
|
+
)
|
|
255
|
+
}}
|
|
256
|
+
|
|
257
|
+
with orders as (
|
|
258
|
+
select * from {{ ref('stg_orders') }}
|
|
259
|
+
),
|
|
260
|
+
|
|
261
|
+
customers as (
|
|
262
|
+
select * from {{ ref('dim_customers') }}
|
|
263
|
+
),
|
|
264
|
+
|
|
265
|
+
products as (
|
|
266
|
+
select * from {{ ref('dim_products') }}
|
|
267
|
+
),
|
|
268
|
+
|
|
269
|
+
order_items as (
|
|
270
|
+
select * from {{ ref('stg_order_items') }}
|
|
271
|
+
),
|
|
272
|
+
|
|
273
|
+
final as (
|
|
274
|
+
select
|
|
275
|
+
{{ dbt_utils.generate_surrogate_key(['o.order_id']) }} as order_key,
|
|
276
|
+
c.customer_key,
|
|
277
|
+
o.order_id,
|
|
278
|
+
o.customer_id,
|
|
279
|
+
o.order_date,
|
|
280
|
+
o.order_status,
|
|
281
|
+
|
|
282
|
+
-- Order metrics
|
|
283
|
+
count(oi.order_item_id) as total_items,
|
|
284
|
+
sum(oi.quantity) as total_quantity,
|
|
285
|
+
sum(oi.unit_price * oi.quantity) as gross_amount,
|
|
286
|
+
sum(oi.discount_amount) as total_discount,
|
|
287
|
+
sum((oi.unit_price * oi.quantity) - oi.discount_amount) as net_amount,
|
|
288
|
+
|
|
289
|
+
-- Time dimensions
|
|
290
|
+
extract(year from o.order_date) as order_year,
|
|
291
|
+
extract(month from o.order_date) as order_month,
|
|
292
|
+
extract(quarter from o.order_date) as order_quarter,
|
|
293
|
+
extract(dayofweek from o.order_date) as order_day_of_week,
|
|
294
|
+
|
|
295
|
+
current_timestamp as updated_at
|
|
296
|
+
|
|
297
|
+
from orders o
|
|
298
|
+
inner join customers c on o.customer_id = c.customer_id
|
|
299
|
+
inner join order_items oi on o.order_id = oi.order_id
|
|
300
|
+
group by 1, 2, 3, 4, 5, 6, 10, 11, 12, 13
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
select * from final
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Advanced dbt Macros
|
|
307
|
+
```sql
|
|
308
|
+
-- macros/generate_schema_name.sql
|
|
309
|
+
{% macro generate_schema_name(custom_schema_name, node) -%}
|
|
310
|
+
{%- set default_schema = target.schema -%}
|
|
311
|
+
{%- if custom_schema_name is none -%}
|
|
312
|
+
{{ default_schema }}
|
|
313
|
+
{%- else -%}
|
|
314
|
+
{{ default_schema }}_{{ custom_schema_name | trim }}
|
|
315
|
+
{%- endif -%}
|
|
316
|
+
{%- endmacro %}
|
|
317
|
+
|
|
318
|
+
-- macros/audit_columns.sql
|
|
319
|
+
{% macro audit_columns() %}
|
|
320
|
+
current_timestamp as created_at,
|
|
321
|
+
current_timestamp as updated_at,
|
|
322
|
+
'{{ this.identifier }}' as source_table
|
|
323
|
+
{% endmacro %}
|
|
324
|
+
|
|
325
|
+
-- macros/get_column_values_with_threshold.sql
|
|
326
|
+
{% macro get_column_values_with_threshold(table, column, threshold=0.01) %}
|
|
327
|
+
{%- call statement('get_column_values', fetch_result=True) -%}
|
|
328
|
+
with value_counts as (
|
|
329
|
+
select
|
|
330
|
+
{{ column }},
|
|
331
|
+
count(*) as count,
|
|
332
|
+
count(*) / sum(count(*)) over() as percentage
|
|
333
|
+
from {{ table }}
|
|
334
|
+
group by {{ column }}
|
|
335
|
+
)
|
|
336
|
+
select {{ column }}
|
|
337
|
+
from value_counts
|
|
338
|
+
where percentage >= {{ threshold }}
|
|
339
|
+
order by count desc
|
|
340
|
+
{%- endcall -%}
|
|
341
|
+
|
|
342
|
+
{%- set results = load_result('get_column_values') -%}
|
|
343
|
+
{%- set values = results['data'] | map(attribute=0) | list -%}
|
|
344
|
+
{{ return(values) }}
|
|
345
|
+
{% endmacro %}
|
|
346
|
+
|
|
347
|
+
-- macros/test_not_null_proportion.sql
|
|
348
|
+
{% test not_null_proportion(model, column_name, at_least=0.95) %}
|
|
349
|
+
with validation as (
|
|
350
|
+
select
|
|
351
|
+
sum(case when {{ column_name }} is not null then 1 else 0 end) as not_null_count,
|
|
352
|
+
count(*) as total_count
|
|
353
|
+
from {{ model }}
|
|
354
|
+
),
|
|
355
|
+
|
|
356
|
+
validation_summary as (
|
|
357
|
+
select
|
|
358
|
+
not_null_count,
|
|
359
|
+
total_count,
|
|
360
|
+
not_null_count / total_count as not_null_proportion
|
|
361
|
+
from validation
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
select *
|
|
365
|
+
from validation_summary
|
|
366
|
+
where not_null_proportion < {{ at_least }}
|
|
367
|
+
{% endtest %}
|
|
368
|
+
|
|
369
|
+
-- macros/pivot.sql
|
|
370
|
+
{% macro pivot(column, values, agg='sum', then_value=1) %}
|
|
371
|
+
{% for value in values %}
|
|
372
|
+
{{ agg }}(
|
|
373
|
+
case when {{ column }} = '{{ value }}'
|
|
374
|
+
then {{ then_value }}
|
|
375
|
+
else 0 end
|
|
376
|
+
) as {{ value }}
|
|
377
|
+
{%- if not loop.last -%},{%- endif -%}
|
|
378
|
+
{% endfor %}
|
|
379
|
+
{% endmacro %}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Data Quality and Testing Framework
|
|
383
|
+
```sql
|
|
384
|
+
-- tests/generic/test_freshness.sql
|
|
385
|
+
{% test freshness(model, column_name, max_age_hours=24) %}
|
|
386
|
+
select *
|
|
387
|
+
from {{ model }}
|
|
388
|
+
where {{ column_name }} < current_timestamp - interval '{{ max_age_hours }} hours'
|
|
389
|
+
{% endtest %}
|
|
390
|
+
|
|
391
|
+
-- tests/generic/test_expression_is_true.sql
|
|
392
|
+
{% test expression_is_true(model, expression) %}
|
|
393
|
+
select *
|
|
394
|
+
from {{ model }}
|
|
395
|
+
where not ({{ expression }})
|
|
396
|
+
{% endtest %}
|
|
397
|
+
|
|
398
|
+
-- models/marts/core/schema.yml
|
|
399
|
+
version: 2
|
|
400
|
+
|
|
401
|
+
models:
|
|
402
|
+
- name: fct_orders
|
|
403
|
+
description: Order fact table with metrics and dimensions
|
|
404
|
+
tests:
|
|
405
|
+
- dbt_utils.equal_rowcount:
|
|
406
|
+
compare_model: ref('stg_orders')
|
|
407
|
+
- freshness:
|
|
408
|
+
column_name: updated_at
|
|
409
|
+
max_age_hours: 2
|
|
410
|
+
columns:
|
|
411
|
+
- name: order_key
|
|
412
|
+
description: Surrogate key for orders
|
|
413
|
+
tests:
|
|
414
|
+
- not_null
|
|
415
|
+
- unique
|
|
416
|
+
- name: customer_key
|
|
417
|
+
description: Foreign key to customer dimension
|
|
418
|
+
tests:
|
|
419
|
+
- not_null
|
|
420
|
+
- relationships:
|
|
421
|
+
to: ref('dim_customers')
|
|
422
|
+
field: customer_key
|
|
423
|
+
- name: net_amount
|
|
424
|
+
description: Net order amount after discounts
|
|
425
|
+
tests:
|
|
426
|
+
- not_null
|
|
427
|
+
- expression_is_true:
|
|
428
|
+
expression: "net_amount >= 0"
|
|
429
|
+
- name: total_quantity
|
|
430
|
+
description: Total items in order
|
|
431
|
+
tests:
|
|
432
|
+
- not_null_proportion:
|
|
433
|
+
at_least: 0.99
|
|
434
|
+
- expression_is_true:
|
|
435
|
+
expression: "total_quantity > 0"
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Data Lineage and Documentation
|
|
439
|
+
```yaml
|
|
440
|
+
# models/staging/sources.yml
|
|
441
|
+
version: 2
|
|
442
|
+
|
|
443
|
+
sources:
|
|
444
|
+
- name: raw_data
|
|
445
|
+
description: Raw operational data
|
|
446
|
+
meta:
|
|
447
|
+
owner: "@data-team"
|
|
448
|
+
contains_pii: true
|
|
449
|
+
tables:
|
|
450
|
+
- name: customers
|
|
451
|
+
description: Customer master data
|
|
452
|
+
meta:
|
|
453
|
+
update_frequency: "hourly"
|
|
454
|
+
row_count: 50000
|
|
455
|
+
columns:
|
|
456
|
+
- name: customer_id
|
|
457
|
+
description: Unique identifier for customer
|
|
458
|
+
meta:
|
|
459
|
+
dimension: true
|
|
460
|
+
- name: email
|
|
461
|
+
description: Customer email address
|
|
462
|
+
meta:
|
|
463
|
+
contains_pii: true
|
|
464
|
+
- name: created_at
|
|
465
|
+
description: Account creation timestamp
|
|
466
|
+
meta:
|
|
467
|
+
dimension: true
|
|
468
|
+
tests:
|
|
469
|
+
- dbt_utils.source_freshness:
|
|
470
|
+
loaded_at_field: updated_at
|
|
471
|
+
warn_after: {count: 2, period: hour}
|
|
472
|
+
error_after: {count: 6, period: hour}
|
|
473
|
+
|
|
474
|
+
exposures:
|
|
475
|
+
- name: customer_dashboard
|
|
476
|
+
description: Executive dashboard showing customer metrics
|
|
477
|
+
type: dashboard
|
|
478
|
+
url: [https://bi.company.com/dashboards/customers](https://bi.company.com/dashboards/customers)
|
|
479
|
+
maturity: high
|
|
480
|
+
owner:
|
|
481
|
+
name: Business Intelligence Team
|
|
482
|
+
email: bi-team@company.com
|
|
483
|
+
depends_on:
|
|
484
|
+
- ref('dim_customers')
|
|
485
|
+
- ref('fct_orders')
|
|
486
|
+
tags: ['executive', 'customers']
|
|
487
|
+
|
|
488
|
+
- name: weekly_revenue_report
|
|
489
|
+
description: Automated weekly revenue report
|
|
490
|
+
type: ml
|
|
491
|
+
owner:
|
|
492
|
+
name: Finance Team
|
|
493
|
+
email: finance@company.com
|
|
494
|
+
depends_on:
|
|
495
|
+
- ref('fct_orders')
|
|
496
|
+
- ref('dim_customers')
|
|
497
|
+
tags: ['finance', 'automated']
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## Advanced Analytics Patterns
|
|
501
|
+
```sql
|
|
502
|
+
-- models/marts/analytics/customer_cohort_analysis.sql
|
|
503
|
+
{{
|
|
504
|
+
config(
|
|
505
|
+
materialized='table',
|
|
506
|
+
tags=['analytics', 'cohorts']
|
|
507
|
+
)
|
|
508
|
+
}}
|
|
509
|
+
|
|
510
|
+
with customer_orders as (
|
|
511
|
+
select
|
|
512
|
+
customer_id,
|
|
513
|
+
order_date,
|
|
514
|
+
net_amount,
|
|
515
|
+
row_number() over (partition by customer_id order by order_date) as order_sequence
|
|
516
|
+
from {{ ref('fct_orders') }}
|
|
517
|
+
),
|
|
518
|
+
|
|
519
|
+
first_orders as (
|
|
520
|
+
select
|
|
521
|
+
customer_id,
|
|
522
|
+
order_date as first_order_date,
|
|
523
|
+
date_trunc('month', order_date) as cohort_month
|
|
524
|
+
from customer_orders
|
|
525
|
+
where order_sequence = 1
|
|
526
|
+
),
|
|
527
|
+
|
|
528
|
+
customer_monthly_activity as (
|
|
529
|
+
select
|
|
530
|
+
co.customer_id,
|
|
531
|
+
fo.cohort_month,
|
|
532
|
+
date_trunc('month', co.order_date) as order_month,
|
|
533
|
+
sum(co.net_amount) as monthly_revenue
|
|
534
|
+
from customer_orders co
|
|
535
|
+
inner join first_orders fo on co.customer_id = fo.customer_id
|
|
536
|
+
group by 1, 2, 3
|
|
537
|
+
),
|
|
538
|
+
|
|
539
|
+
cohort_analysis as (
|
|
540
|
+
select
|
|
541
|
+
cohort_month,
|
|
542
|
+
order_month,
|
|
543
|
+
datediff('month', cohort_month, order_month) as period_number,
|
|
544
|
+
count(distinct customer_id) as customers,
|
|
545
|
+
sum(monthly_revenue) as revenue
|
|
546
|
+
from customer_monthly_activity
|
|
547
|
+
group by 1, 2, 3
|
|
548
|
+
),
|
|
549
|
+
|
|
550
|
+
cohort_sizes as (
|
|
551
|
+
select
|
|
552
|
+
cohort_month,
|
|
553
|
+
count(distinct customer_id) as cohort_size
|
|
554
|
+
from first_orders
|
|
555
|
+
group by 1
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
select
|
|
559
|
+
ca.cohort_month,
|
|
560
|
+
ca.period_number,
|
|
561
|
+
ca.customers,
|
|
562
|
+
cs.cohort_size,
|
|
563
|
+
ca.customers / cs.cohort_size::float as retention_rate,
|
|
564
|
+
ca.revenue,
|
|
565
|
+
ca.revenue / ca.customers as revenue_per_customer
|
|
566
|
+
from cohort_analysis ca
|
|
567
|
+
inner join cohort_sizes cs on ca.cohort_month = cs.cohort_month
|
|
568
|
+
order by ca.cohort_month, ca.period_number
|
|
569
|
+
|
|
570
|
+
-- models/marts/analytics/customer_lifetime_value.sql
|
|
571
|
+
{{
|
|
572
|
+
config(
|
|
573
|
+
materialized='table',
|
|
574
|
+
tags=['analytics', 'clv']
|
|
575
|
+
)
|
|
576
|
+
}}
|
|
577
|
+
|
|
578
|
+
with customer_metrics as (
|
|
579
|
+
select
|
|
580
|
+
customer_id,
|
|
581
|
+
min(order_date) as first_order_date,
|
|
582
|
+
max(order_date) as last_order_date,
|
|
583
|
+
count(*) as total_orders,
|
|
584
|
+
sum(net_amount) as total_revenue,
|
|
585
|
+
avg(net_amount) as avg_order_value,
|
|
586
|
+
datediff('day', min(order_date), max(order_date)) / nullif(count(*) - 1, 0) as avg_days_between_orders
|
|
587
|
+
from {{ ref('fct_orders') }}
|
|
588
|
+
group by customer_id
|
|
589
|
+
),
|
|
590
|
+
|
|
591
|
+
clv_calculation as (
|
|
592
|
+
select
|
|
593
|
+
customer_id,
|
|
594
|
+
first_order_date,
|
|
595
|
+
last_order_date,
|
|
596
|
+
total_orders,
|
|
597
|
+
total_revenue,
|
|
598
|
+
avg_order_value,
|
|
599
|
+
avg_days_between_orders,
|
|
600
|
+
|
|
601
|
+
-- Purchase frequency (orders per year)
|
|
602
|
+
case
|
|
603
|
+
when avg_days_between_orders > 0 then 365.0 / avg_days_between_orders
|
|
604
|
+
else total_orders
|
|
605
|
+
end as purchase_frequency,
|
|
606
|
+
|
|
607
|
+
-- Customer lifespan in years
|
|
608
|
+
case
|
|
609
|
+
when datediff('day', first_order_date, last_order_date) > 0
|
|
610
|
+
then datediff('day', first_order_date, last_order_date) / 365.0
|
|
611
|
+
else 1.0 / 365.0 -- Minimum of 1 day
|
|
612
|
+
end as customer_lifespan,
|
|
613
|
+
|
|
614
|
+
total_revenue as historical_clv
|
|
615
|
+
from customer_metrics
|
|
616
|
+
),
|
|
617
|
+
|
|
618
|
+
final as (
|
|
619
|
+
select
|
|
620
|
+
*,
|
|
621
|
+
avg_order_value * purchase_frequency * customer_lifespan as predicted_clv,
|
|
622
|
+
case
|
|
623
|
+
when predicted_clv >= 1000 then 'High Value'
|
|
624
|
+
when predicted_clv >= 500 then 'Medium Value'
|
|
625
|
+
when predicted_clv >= 100 then 'Low Value'
|
|
626
|
+
else 'Minimal Value'
|
|
627
|
+
end as clv_segment
|
|
628
|
+
from clv_calculation
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
select * from final
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
## Business Intelligence Integration
|
|
635
|
+
```python
|
|
636
|
+
# Python script for automated BI refresh
|
|
637
|
+
import requests
|
|
638
|
+
import json
|
|
639
|
+
import os
|
|
640
|
+
from datetime import datetime, timedelta
|
|
641
|
+
import logging
|
|
642
|
+
|
|
643
|
+
class BIRefreshManager:
|
|
644
|
+
def __init__(self, tableau_server_url, username, password):
|
|
645
|
+
self.server_url = tableau_server_url
|
|
646
|
+
self.username = username
|
|
647
|
+
self.password = password
|
|
648
|
+
self.auth_token = None
|
|
649
|
+
self.site_id = None
|
|
650
|
+
|
|
651
|
+
def authenticate(self):
|
|
652
|
+
"""Authenticate with Tableau Server"""
|
|
653
|
+
auth_url = f"{self.server_url}/api/3.10/auth/signin"
|
|
654
|
+
|
|
655
|
+
payload = {
|
|
656
|
+
'credentials': {
|
|
657
|
+
'name': self.username,
|
|
658
|
+
'password': self.password,
|
|
659
|
+
'site': {'contentUrl': ''}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
response = requests.post(auth_url, json=payload)
|
|
664
|
+
response.raise_for_status()
|
|
665
|
+
|
|
666
|
+
auth_data = response.json()
|
|
667
|
+
self.auth_token = auth_data['credentials']['token']
|
|
668
|
+
self.site_id = auth_data['credentials']['site']['id']
|
|
669
|
+
|
|
670
|
+
return self.auth_token
|
|
671
|
+
|
|
672
|
+
def refresh_datasource(self, datasource_id):
|
|
673
|
+
"""Refresh a specific datasource"""
|
|
674
|
+
headers = {
|
|
675
|
+
'X-Tableau-Auth': self.auth_token,
|
|
676
|
+
'Content-Type': 'application/json'
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
refresh_url = f"{self.server_url}/api/3.10/sites/{self.site_id}/datasources/{datasource_id}/refresh"
|
|
680
|
+
|
|
681
|
+
response = requests.post(refresh_url, headers=headers)
|
|
682
|
+
response.raise_for_status()
|
|
683
|
+
|
|
684
|
+
job_data = response.json()
|
|
685
|
+
return job_data['job']['id']
|
|
686
|
+
|
|
687
|
+
def check_job_status(self, job_id):
|
|
688
|
+
"""Check the status of a refresh job"""
|
|
689
|
+
headers = {'X-Tableau-Auth': self.auth_token}
|
|
690
|
+
|
|
691
|
+
status_url = f"{self.server_url}/api/3.10/sites/{self.site_id}/jobs/{job_id}"
|
|
692
|
+
|
|
693
|
+
response = requests.get(status_url, headers=headers)
|
|
694
|
+
response.raise_for_status()
|
|
695
|
+
|
|
696
|
+
job_data = response.json()
|
|
697
|
+
return job_data['job']['finishCode']
|
|
698
|
+
|
|
699
|
+
# dbt run automation with BI refresh
|
|
700
|
+
def run_dbt_and_refresh_bi():
|
|
701
|
+
"""Run dbt models and refresh BI dashboards"""
|
|
702
|
+
import subprocess
|
|
703
|
+
|
|
704
|
+
try:
|
|
705
|
+
# Run dbt
|
|
706
|
+
dbt_result = subprocess.run(['dbt', 'run'], capture_output=True, text=True)
|
|
707
|
+
|
|
708
|
+
if dbt_result.returncode == 0:
|
|
709
|
+
logging.info("dbt run completed successfully")
|
|
710
|
+
|
|
711
|
+
# Run tests
|
|
712
|
+
test_result = subprocess.run(['dbt', 'test'], capture_output=True, text=True)
|
|
713
|
+
|
|
714
|
+
if test_result.returncode == 0:
|
|
715
|
+
logging.info("All tests passed")
|
|
716
|
+
|
|
717
|
+
# Refresh BI dashboards
|
|
718
|
+
bi_manager = BIRefreshManager(
|
|
719
|
+
tableau_server_url=os.getenv("TABLEAU_SERVER_URL"),
|
|
720
|
+
username=os.getenv("TABLEAU_USERNAME"),
|
|
721
|
+
password=os.getenv("TABLEAU_PASSWORD")
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
bi_manager.authenticate()
|
|
725
|
+
job_id = bi_manager.refresh_datasource("datasource-id-123")
|
|
726
|
+
|
|
727
|
+
logging.info(f"BI refresh started with job ID: {job_id}")
|
|
728
|
+
|
|
729
|
+
else:
|
|
730
|
+
logging.error(f"dbt tests failed: {test_result.stderr}")
|
|
731
|
+
|
|
732
|
+
else:
|
|
733
|
+
logging.error(f"dbt run failed: {dbt_result.stderr}")
|
|
734
|
+
|
|
735
|
+
except Exception as e:
|
|
736
|
+
logging.error(f"Pipeline failed: {str(e)}")
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
## Data Governance and Monitoring
|
|
740
|
+
```yaml
|
|
741
|
+
# .github/workflows/dbt_ci.yml
|
|
742
|
+
name: dbt CI/CD Pipeline
|
|
743
|
+
|
|
744
|
+
on:
|
|
745
|
+
push:
|
|
746
|
+
branches: [main, develop]
|
|
747
|
+
pull_request:
|
|
748
|
+
branches: [main]
|
|
749
|
+
|
|
750
|
+
env:
|
|
751
|
+
DBT_PROFILES_DIR: .
|
|
752
|
+
DBT_PROFILE: analytics_project
|
|
753
|
+
|
|
754
|
+
jobs:
|
|
755
|
+
dbt-test:
|
|
756
|
+
runs-on: ubuntu-latest
|
|
757
|
+
steps:
|
|
758
|
+
- uses: actions/checkout@v3
|
|
759
|
+
|
|
760
|
+
- name: Set up Python
|
|
761
|
+
uses: actions/setup-python@v4
|
|
762
|
+
with:
|
|
763
|
+
python-version: 3.9
|
|
764
|
+
|
|
765
|
+
- name: Install dependencies
|
|
766
|
+
run: |
|
|
767
|
+
pip install dbt-snowflake
|
|
768
|
+
pip install sqlfluff
|
|
769
|
+
pip install great-expectations
|
|
770
|
+
|
|
771
|
+
- name: Lint SQL
|
|
772
|
+
run: |
|
|
773
|
+
sqlfluff lint models/ --dialect snowflake
|
|
774
|
+
|
|
775
|
+
- name: dbt deps
|
|
776
|
+
run: dbt deps
|
|
777
|
+
|
|
778
|
+
- name: dbt seed
|
|
779
|
+
run: dbt seed --target ci
|
|
780
|
+
|
|
781
|
+
- name: dbt run
|
|
782
|
+
run: dbt run --target ci
|
|
783
|
+
|
|
784
|
+
- name: dbt test
|
|
785
|
+
run: dbt test --target ci
|
|
786
|
+
|
|
787
|
+
- name: Generate dbt docs
|
|
788
|
+
run: |
|
|
789
|
+
dbt docs generate --target ci
|
|
790
|
+
dbt docs serve --port 8080 &
|
|
791
|
+
sleep 10
|
|
792
|
+
curl http://localhost:8080
|
|
793
|
+
|
|
794
|
+
- name: Data quality checks
|
|
795
|
+
run: |
|
|
796
|
+
python scripts/run_data_quality_checks.py
|
|
797
|
+
|
|
798
|
+
deploy:
|
|
799
|
+
needs: dbt-test
|
|
800
|
+
runs-on: ubuntu-latest
|
|
801
|
+
if: github.ref == 'refs/heads/main'
|
|
802
|
+
steps:
|
|
803
|
+
- uses: actions/checkout@v3
|
|
804
|
+
|
|
805
|
+
- name: Deploy to production
|
|
806
|
+
run: |
|
|
807
|
+
dbt run --target prod
|
|
808
|
+
dbt test --target prod
|
|
809
|
+
dbt docs generate --target prod
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
## Monitoring and Alerting
|
|
813
|
+
```python
|
|
814
|
+
# scripts/data_monitoring.py
|
|
815
|
+
import pandas as pd
|
|
816
|
+
import snowflake.connector
|
|
817
|
+
import os
|
|
818
|
+
from datetime import datetime, timedelta
|
|
819
|
+
import smtplib
|
|
820
|
+
from email.mime.text import MimeText
|
|
821
|
+
|
|
822
|
+
class DataMonitor:
|
|
823
|
+
def __init__(self, connection_params):
|
|
824
|
+
self.conn = snowflake.connector.connect(**connection_params)
|
|
825
|
+
|
|
826
|
+
def check_data_freshness(self, table_name, timestamp_column, max_age_hours=2):
|
|
827
|
+
"""Check if data is fresh enough"""
|
|
828
|
+
query = f"""
|
|
829
|
+
SELECT
|
|
830
|
+
MAX({timestamp_column}) as latest_timestamp,
|
|
831
|
+
DATEDIFF('hour', MAX({timestamp_column}), CURRENT_TIMESTAMP()) as hours_old
|
|
832
|
+
FROM {table_name}
|
|
833
|
+
"""
|
|
834
|
+
|
|
835
|
+
result = pd.read_sql(query, self.conn)
|
|
836
|
+
hours_old = result['HOURS_OLD'].iloc[0]
|
|
837
|
+
|
|
838
|
+
if hours_old > max_age_hours:
|
|
839
|
+
self.send_alert(
|
|
840
|
+
f"Data freshness alert for {table_name}",
|
|
841
|
+
f"Data is {hours_old} hours old, exceeding threshold of {max_age_hours} hours"
|
|
842
|
+
)
|
|
843
|
+
return False
|
|
844
|
+
return True
|
|
845
|
+
|
|
846
|
+
def check_row_count_anomaly(self, table_name, threshold_percent=20):
|
|
847
|
+
"""Check for unusual row count changes"""
|
|
848
|
+
query = f"""
|
|
849
|
+
WITH daily_counts AS (
|
|
850
|
+
SELECT
|
|
851
|
+
DATE(created_at) as date,
|
|
852
|
+
COUNT(*) as row_count
|
|
853
|
+
FROM {table_name}
|
|
854
|
+
WHERE created_at >= CURRENT_DATE - 7
|
|
855
|
+
GROUP BY DATE(created_at)
|
|
856
|
+
ORDER BY date DESC
|
|
857
|
+
),
|
|
858
|
+
count_comparison AS (
|
|
859
|
+
SELECT
|
|
860
|
+
date,
|
|
861
|
+
row_count,
|
|
862
|
+
LAG(row_count) OVER (ORDER BY date) as prev_row_count,
|
|
863
|
+
(row_count - LAG(row_count) OVER (ORDER BY date)) / LAG(row_count) OVER (ORDER BY date) * 100 as percent_change
|
|
864
|
+
FROM daily_counts
|
|
865
|
+
)
|
|
866
|
+
SELECT * FROM count_comparison
|
|
867
|
+
WHERE date = CURRENT_DATE - 1
|
|
868
|
+
"""
|
|
869
|
+
|
|
870
|
+
result = pd.read_sql(query, self.conn)
|
|
871
|
+
|
|
872
|
+
if not result.empty:
|
|
873
|
+
percent_change = abs(result['PERCENT_CHANGE'].iloc[0])
|
|
874
|
+
|
|
875
|
+
if percent_change > threshold_percent:
|
|
876
|
+
self.send_alert(
|
|
877
|
+
f"Row count anomaly for {table_name}",
|
|
878
|
+
f"Row count changed by {percent_change:.1f}% from previous day"
|
|
879
|
+
)
|
|
880
|
+
return False
|
|
881
|
+
return True
|
|
882
|
+
|
|
883
|
+
def send_alert(self, subject, message):
|
|
884
|
+
"""Send email alert"""
|
|
885
|
+
msg = MimeText(message)
|
|
886
|
+
msg['Subject'] = subject
|
|
887
|
+
msg['From'] = 'data-alerts@company.com'
|
|
888
|
+
msg['To'] = 'data-team@company.com'
|
|
889
|
+
|
|
890
|
+
# Use environment variables for SMTP configuration for security
|
|
891
|
+
smtp_server = os.getenv("SMTP_SERVER")
|
|
892
|
+
smtp_port = int(os.getenv("SMTP_PORT", 587))
|
|
893
|
+
smtp_user = os.getenv("SMTP_USER")
|
|
894
|
+
smtp_password = os.getenv("SMTP_PASSWORD")
|
|
895
|
+
|
|
896
|
+
with smtplib.SMTP(smtp_server, smtp_port) as server:
|
|
897
|
+
server.starttls()
|
|
898
|
+
server.login(smtp_user, smtp_password)
|
|
899
|
+
server.send_message(msg)
|
|
900
|
+
|
|
901
|
+
# Usage
|
|
902
|
+
monitor = DataMonitor({
|
|
903
|
+
'user': os.getenv("SNOWFLAKE_USER"),
|
|
904
|
+
'password': os.getenv("SNOWFLAKE_PASSWORD"),
|
|
905
|
+
'account': os.getenv("SNOWFLAKE_ACCOUNT"),
|
|
906
|
+
'warehouse': 'ANALYTICS_WH',
|
|
907
|
+
'database': 'ANALYTICS_DB',
|
|
908
|
+
'schema': 'MARTS'
|
|
909
|
+
})
|
|
910
|
+
|
|
911
|
+
# Run monitoring checks
|
|
912
|
+
tables_to_monitor = [
|
|
913
|
+
'dim_customers',
|
|
914
|
+
'fct_orders',
|
|
915
|
+
'fct_web_events'
|
|
916
|
+
]
|
|
917
|
+
|
|
918
|
+
for table in tables_to_monitor:
|
|
919
|
+
monitor.check_data_freshness(table, 'updated_at')
|
|
920
|
+
monitor.check_row_count_anomaly(table)
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
## Best Practices
|
|
924
|
+
1. **Modularity**: Build reusable models and macros
|
|
925
|
+
2. **Testing**: Implement comprehensive data quality tests
|
|
926
|
+
3. **Documentation**: Maintain clear model and column descriptions
|
|
927
|
+
4. **Version Control**: Use Git for all dbt code and configurations
|
|
928
|
+
5. **Performance**: Optimize models with proper materializations and clustering
|
|
929
|
+
6. **Governance**: Establish clear naming conventions and folder structures
|
|
930
|
+
7. **Monitoring**: Set up automated data quality and freshness checks
|
|
931
|
+
|
|
932
|
+
## Data Governance Framework
|
|
933
|
+
- Establish data ownership and stewardship roles
|
|
934
|
+
- Implement data lineage tracking and impact analysis
|
|
935
|
+
- Create data quality scorecards and SLAs
|
|
936
|
+
- Maintain data dictionaries and business glossaries
|
|
937
|
+
- Regular audits and compliance reporting
|
|
938
|
+
|
|
939
|
+
## Approach
|
|
940
|
+
- Start with source data profiling and understanding
|
|
941
|
+
- Design dimensional models based on business requirements
|
|
942
|
+
- Implement incremental development with proper testing
|
|
943
|
+
- Set up monitoring and alerting for production systems
|
|
944
|
+
- Create self-service analytics capabilities
|
|
945
|
+
- Establish governance and documentation standards
|
|
946
|
+
|
|
947
|
+
## Output Format
|
|
948
|
+
- Provide complete dbt project structures
|
|
949
|
+
- Include comprehensive testing frameworks
|
|
950
|
+
- Document data governance procedures
|
|
951
|
+
- Add monitoring and alerting configurations
|
|
952
|
+
- Include BI integration examples
|
|
953
|
+
- Provide operational runbooks and best practices
|