dotdo 0.0.1 → 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/LICENSE +1 -1
- package/README.md +446 -315
- package/cli/README.md +238 -0
- package/cli/agent.ts +72 -0
- package/cli/bin.js +44 -0
- package/cli/bin.ts +38 -0
- package/cli/build.ts +157 -0
- package/cli/commands/auth/login.ts +14 -0
- package/cli/commands/auth/logout.ts +6 -0
- package/cli/commands/auth/whoami.ts +16 -0
- package/cli/commands/deploy-multi.ts +245 -0
- package/cli/commands/dev/deploy.ts +100 -0
- package/cli/commands/dev/dev.ts +95 -0
- package/cli/commands/dev/logs.ts +91 -0
- package/cli/commands/dev-local.ts +88 -0
- package/cli/commands/do-ops.ts +314 -0
- package/cli/commands/index.ts +100 -0
- package/cli/commands/init.ts +247 -0
- package/cli/commands/introspect/emitter.ts +315 -0
- package/cli/commands/introspect/index.ts +193 -0
- package/cli/commands/link.ts +598 -0
- package/cli/commands/snippets.ts +415 -0
- package/cli/commands/tunnel.ts +239 -0
- package/cli/device-auth.ts +289 -0
- package/cli/fallback.ts +12 -0
- package/cli/index.ts +121 -0
- package/cli/main.ts +246 -0
- package/cli/mcp-stdio.ts +790 -0
- package/cli/package.json +62 -0
- package/cli/runtime/do-registry.ts +193 -0
- package/cli/runtime/embedded-db.ts +344 -0
- package/cli/runtime/index.ts +9 -0
- package/cli/runtime/miniflare-adapter.ts +162 -0
- package/cli/sandbox.ts +82 -0
- package/cli/src/args.ts +174 -0
- package/cli/src/auth.ts +55 -0
- package/cli/src/commands/call.ts +84 -0
- package/cli/src/commands/charge.ts +96 -0
- package/cli/src/commands/config.ts +115 -0
- package/cli/src/commands/email.ts +112 -0
- package/cli/src/commands/llm.ts +115 -0
- package/cli/src/commands/queue.ts +134 -0
- package/cli/src/commands/text.ts +86 -0
- package/cli/src/config.ts +185 -0
- package/cli/src/output.ts +246 -0
- package/cli/src/rpc.ts +192 -0
- package/cli/utils/config.ts +282 -0
- package/cli/utils/detect.ts +73 -0
- package/cli/utils/index.ts +15 -0
- package/cli/utils/logger.ts +232 -0
- package/dist/ai/index.js +19 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/template-literals.js +852 -0
- package/dist/ai/template-literals.js.map +1 -0
- package/dist/api/middleware/auth-federation.js +573 -0
- package/dist/api/middleware/auth-federation.js.map +1 -0
- package/dist/api/middleware/auth.js +545 -0
- package/dist/api/middleware/auth.js.map +1 -0
- package/dist/db/actions.js +212 -0
- package/dist/db/actions.js.map +1 -0
- package/dist/db/auth.js +506 -0
- package/dist/db/auth.js.map +1 -0
- package/dist/db/branches.js +65 -0
- package/dist/db/branches.js.map +1 -0
- package/dist/db/clickhouse.js +1074 -0
- package/dist/db/clickhouse.js.map +1 -0
- package/dist/db/dlq.js +39 -0
- package/dist/db/dlq.js.map +1 -0
- package/dist/db/events.js +28 -0
- package/dist/db/events.js.map +1 -0
- package/dist/db/exec.js +64 -0
- package/dist/db/exec.js.map +1 -0
- package/dist/db/files.js +85 -0
- package/dist/db/files.js.map +1 -0
- package/dist/db/flags.js +24 -0
- package/dist/db/flags.js.map +1 -0
- package/dist/db/git.js +116 -0
- package/dist/db/git.js.map +1 -0
- package/dist/db/iceberg/inverted-index.js +862 -0
- package/dist/db/iceberg/inverted-index.js.map +1 -0
- package/dist/db/iceberg/puffin.js +878 -0
- package/dist/db/iceberg/puffin.js.map +1 -0
- package/dist/db/iceberg/search-manifest.js +422 -0
- package/dist/db/iceberg/search-manifest.js.map +1 -0
- package/dist/db/iceberg/types.js +8 -0
- package/dist/db/iceberg/types.js.map +1 -0
- package/dist/db/index.js +121 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/integrations.js +368 -0
- package/dist/db/integrations.js.map +1 -0
- package/dist/db/json-indexes.js +332 -0
- package/dist/db/json-indexes.js.map +1 -0
- package/dist/db/linked-accounts.js +287 -0
- package/dist/db/linked-accounts.js.map +1 -0
- package/dist/db/nouns.js +183 -0
- package/dist/db/nouns.js.map +1 -0
- package/dist/db/objects.js +170 -0
- package/dist/db/objects.js.map +1 -0
- package/dist/db/primitives/dag-scheduler/index.js +869 -0
- package/dist/db/primitives/dag-scheduler/index.js.map +1 -0
- package/dist/db/primitives/exactly-once-context.js +237 -0
- package/dist/db/primitives/exactly-once-context.js.map +1 -0
- package/dist/db/primitives/index.js +62 -0
- package/dist/db/primitives/index.js.map +1 -0
- package/dist/db/primitives/keyed-router.js +145 -0
- package/dist/db/primitives/keyed-router.js.map +1 -0
- package/dist/db/primitives/observability.js +162 -0
- package/dist/db/primitives/observability.js.map +1 -0
- package/dist/db/primitives/schema-evolution.js +643 -0
- package/dist/db/primitives/schema-evolution.js.map +1 -0
- package/dist/db/primitives/stateful-operator/index.js +770 -0
- package/dist/db/primitives/stateful-operator/index.js.map +1 -0
- package/dist/db/primitives/temporal-store.js +306 -0
- package/dist/db/primitives/temporal-store.js.map +1 -0
- package/dist/db/primitives/typed-column-store.js +1229 -0
- package/dist/db/primitives/typed-column-store.js.map +1 -0
- package/dist/db/primitives/utils/duration.js +162 -0
- package/dist/db/primitives/utils/duration.js.map +1 -0
- package/dist/db/primitives/utils/murmur3.js +116 -0
- package/dist/db/primitives/utils/murmur3.js.map +1 -0
- package/dist/db/primitives/watermark-service.js +136 -0
- package/dist/db/primitives/watermark-service.js.map +1 -0
- package/dist/db/primitives/window-manager.js +764 -0
- package/dist/db/primitives/window-manager.js.map +1 -0
- package/dist/db/relationships.js +66 -0
- package/dist/db/relationships.js.map +1 -0
- package/dist/db/schema-minimal.js +61 -0
- package/dist/db/schema-minimal.js.map +1 -0
- package/dist/db/search.js +28 -0
- package/dist/db/search.js.map +1 -0
- package/dist/db/stores.js +1665 -0
- package/dist/db/stores.js.map +1 -0
- package/dist/db/things.js +297 -0
- package/dist/db/things.js.map +1 -0
- package/dist/db/vault.js +171 -0
- package/dist/db/vault.js.map +1 -0
- package/dist/db/verbs.js +102 -0
- package/dist/db/verbs.js.map +1 -0
- package/dist/do/base.js +48 -0
- package/dist/do/base.js.map +1 -0
- package/dist/do/tiny.js +31 -0
- package/dist/do/tiny.js.map +1 -0
- package/dist/lib/DOAuth.js +261 -0
- package/dist/lib/DOAuth.js.map +1 -0
- package/dist/lib/DODispatcher.js +72 -0
- package/dist/lib/DODispatcher.js.map +1 -0
- package/dist/lib/Modifier.js +189 -0
- package/dist/lib/Modifier.js.map +1 -0
- package/dist/lib/StateStorage.js +403 -0
- package/dist/lib/StateStorage.js.map +1 -0
- package/dist/lib/TypeRegistry.js +122 -0
- package/dist/lib/TypeRegistry.js.map +1 -0
- package/dist/lib/ai/gateway.js +247 -0
- package/dist/lib/ai/gateway.js.map +1 -0
- package/dist/lib/ai/tool-loop-agent.js +591 -0
- package/dist/lib/ai/tool-loop-agent.js.map +1 -0
- package/dist/lib/auto-wiring.js +439 -0
- package/dist/lib/auto-wiring.js.map +1 -0
- package/dist/lib/browse/browserbase.js +163 -0
- package/dist/lib/browse/browserbase.js.map +1 -0
- package/dist/lib/browse/cloudflare.js +144 -0
- package/dist/lib/browse/cloudflare.js.map +1 -0
- package/dist/lib/browse/index.js +62 -0
- package/dist/lib/browse/index.js.map +1 -0
- package/dist/lib/browse/types.js +13 -0
- package/dist/lib/browse/types.js.map +1 -0
- package/dist/lib/cache/index.js +37 -0
- package/dist/lib/cache/index.js.map +1 -0
- package/dist/lib/cache/visibility.js +638 -0
- package/dist/lib/cache/visibility.js.map +1 -0
- package/dist/lib/capabilities.js +268 -0
- package/dist/lib/capabilities.js.map +1 -0
- package/dist/lib/channels/base.js +106 -0
- package/dist/lib/channels/base.js.map +1 -0
- package/dist/lib/channels/discord.js +94 -0
- package/dist/lib/channels/discord.js.map +1 -0
- package/dist/lib/channels/email.js +204 -0
- package/dist/lib/channels/email.js.map +1 -0
- package/dist/lib/channels/index.js +90 -0
- package/dist/lib/channels/index.js.map +1 -0
- package/dist/lib/channels/mdxui-chat.js +95 -0
- package/dist/lib/channels/mdxui-chat.js.map +1 -0
- package/dist/lib/channels/slack-blockkit.js +121 -0
- package/dist/lib/channels/slack-blockkit.js.map +1 -0
- package/dist/lib/channels/types.js +7 -0
- package/dist/lib/channels/types.js.map +1 -0
- package/dist/lib/cloudflare/ai.js +654 -0
- package/dist/lib/cloudflare/ai.js.map +1 -0
- package/dist/lib/cloudflare/index.js +88 -0
- package/dist/lib/cloudflare/index.js.map +1 -0
- package/dist/lib/cloudflare/kv.js +342 -0
- package/dist/lib/cloudflare/kv.js.map +1 -0
- package/dist/lib/cloudflare/queues.js +434 -0
- package/dist/lib/cloudflare/queues.js.map +1 -0
- package/dist/lib/cloudflare/r2.js +604 -0
- package/dist/lib/cloudflare/r2.js.map +1 -0
- package/dist/lib/cloudflare/vectorize.js +494 -0
- package/dist/lib/cloudflare/vectorize.js.map +1 -0
- package/dist/lib/cloudflare/workflows.js +569 -0
- package/dist/lib/cloudflare/workflows.js.map +1 -0
- package/dist/lib/colo/caching.js +196 -0
- package/dist/lib/colo/caching.js.map +1 -0
- package/dist/lib/colo/detection.js +194 -0
- package/dist/lib/colo/detection.js.map +1 -0
- package/dist/lib/colo/external-data.js +219 -0
- package/dist/lib/colo/external-data.js.map +1 -0
- package/dist/lib/colo/globe-data.js +179 -0
- package/dist/lib/colo/globe-data.js.map +1 -0
- package/dist/lib/colo/index.js +16 -0
- package/dist/lib/colo/index.js.map +1 -0
- package/dist/lib/decorators.js +37 -0
- package/dist/lib/decorators.js.map +1 -0
- package/dist/lib/discovery.js +81 -0
- package/dist/lib/discovery.js.map +1 -0
- package/dist/lib/executors/AgenticFunctionExecutor.js +619 -0
- package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/BaseFunctionExecutor.js +328 -0
- package/dist/lib/executors/BaseFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/CascadeExecutor.js +418 -0
- package/dist/lib/executors/CascadeExecutor.js.map +1 -0
- package/dist/lib/executors/CodeFunctionExecutor.js +904 -0
- package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/GenerativeFunctionExecutor.js +904 -0
- package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/HumanFunctionExecutor.js +884 -0
- package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/ParallelStepExecutor.js +308 -0
- package/dist/lib/executors/ParallelStepExecutor.js.map +1 -0
- package/dist/lib/executors/types.js +12 -0
- package/dist/lib/executors/types.js.map +1 -0
- package/dist/lib/experiments.js +89 -0
- package/dist/lib/experiments.js.map +1 -0
- package/dist/lib/flags/store.js +262 -0
- package/dist/lib/flags/store.js.map +1 -0
- package/dist/lib/functions/FunctionComposition.js +467 -0
- package/dist/lib/functions/FunctionComposition.js.map +1 -0
- package/dist/lib/functions/FunctionMiddleware.js +457 -0
- package/dist/lib/functions/FunctionMiddleware.js.map +1 -0
- package/dist/lib/functions/FunctionRegistry.js +426 -0
- package/dist/lib/functions/FunctionRegistry.js.map +1 -0
- package/dist/lib/functions/createFunction.js +1048 -0
- package/dist/lib/functions/createFunction.js.map +1 -0
- package/dist/lib/humans/index.js +68 -0
- package/dist/lib/humans/index.js.map +1 -0
- package/dist/lib/humans/templates.js +117 -0
- package/dist/lib/humans/templates.js.map +1 -0
- package/dist/lib/identity.js +98 -0
- package/dist/lib/identity.js.map +1 -0
- package/dist/lib/index.js +9 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logging/error-logger.js +163 -0
- package/dist/lib/logging/error-logger.js.map +1 -0
- package/dist/lib/logging/index.js +160 -0
- package/dist/lib/logging/index.js.map +1 -0
- package/dist/lib/mixins/bash.js +753 -0
- package/dist/lib/mixins/bash.js.map +1 -0
- package/dist/lib/mixins/fs.js +648 -0
- package/dist/lib/mixins/fs.js.map +1 -0
- package/dist/lib/mixins/git.js +1006 -0
- package/dist/lib/mixins/git.js.map +1 -0
- package/dist/lib/mixins/npm.js +662 -0
- package/dist/lib/mixins/npm.js.map +1 -0
- package/dist/lib/noun-id.js +278 -0
- package/dist/lib/noun-id.js.map +1 -0
- package/dist/lib/rate-limit/sliding-window.js +148 -0
- package/dist/lib/rate-limit/sliding-window.js.map +1 -0
- package/dist/lib/rate-limit.js +110 -0
- package/dist/lib/rate-limit.js.map +1 -0
- package/dist/lib/rpc/bindings.js +548 -0
- package/dist/lib/rpc/bindings.js.map +1 -0
- package/dist/lib/rpc/index.js +64 -0
- package/dist/lib/rpc/index.js.map +1 -0
- package/dist/lib/safe-stringify.js +223 -0
- package/dist/lib/safe-stringify.js.map +1 -0
- package/dist/lib/sandbox/miniflare-sandbox.js +1007 -0
- package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -0
- package/dist/lib/sqids.js +110 -0
- package/dist/lib/sqids.js.map +1 -0
- package/dist/lib/sql/adapters/index.js +10 -0
- package/dist/lib/sql/adapters/index.js.map +1 -0
- package/dist/lib/sql/adapters/node-sql-parser.js +552 -0
- package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -0
- package/dist/lib/sql/adapters/pgsql-parser.js +1190 -0
- package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -0
- package/dist/lib/sql/index.js +277 -0
- package/dist/lib/sql/index.js.map +1 -0
- package/dist/lib/sql/types.js +56 -0
- package/dist/lib/sql/types.js.map +1 -0
- package/dist/lib/type-classifier.js +126 -0
- package/dist/lib/type-classifier.js.map +1 -0
- package/dist/lib/utils/html.js +47 -0
- package/dist/lib/utils/html.js.map +1 -0
- package/dist/lib/validation.js +48 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/lib/vault/store.js +411 -0
- package/dist/lib/vault/store.js.map +1 -0
- package/dist/metrics/hunch.js +739 -0
- package/dist/metrics/hunch.js.map +1 -0
- package/dist/objects/API.js +302 -0
- package/dist/objects/API.js.map +1 -0
- package/dist/objects/Agent.js +179 -0
- package/dist/objects/Agent.js.map +1 -0
- package/dist/objects/AgenticFunctionExecutor.js +8 -0
- package/dist/objects/AgenticFunctionExecutor.js.map +1 -0
- package/dist/objects/App.js +83 -0
- package/dist/objects/App.js.map +1 -0
- package/dist/objects/Browser.js +884 -0
- package/dist/objects/Browser.js.map +1 -0
- package/dist/objects/Business.js +107 -0
- package/dist/objects/Business.js.map +1 -0
- package/dist/objects/CLI.js +221 -0
- package/dist/objects/CLI.js.map +1 -0
- package/dist/objects/CodeFunctionExecutor.js +8 -0
- package/dist/objects/CodeFunctionExecutor.js.map +1 -0
- package/dist/objects/Collection.js +161 -0
- package/dist/objects/Collection.js.map +1 -0
- package/dist/objects/DO.js +41 -0
- package/dist/objects/DO.js.map +1 -0
- package/dist/objects/DOBase.js +2309 -0
- package/dist/objects/DOBase.js.map +1 -0
- package/dist/objects/DOCache.js +153 -0
- package/dist/objects/DOCache.js.map +1 -0
- package/dist/objects/DOFull.js +1676 -0
- package/dist/objects/DOFull.js.map +1 -0
- package/dist/objects/DOTiny.js +207 -0
- package/dist/objects/DOTiny.js.map +1 -0
- package/dist/objects/Directory.js +199 -0
- package/dist/objects/Directory.js.map +1 -0
- package/dist/objects/Entity.js +413 -0
- package/dist/objects/Entity.js.map +1 -0
- package/dist/objects/Function.js +116 -0
- package/dist/objects/Function.js.map +1 -0
- package/dist/objects/Human.js +231 -0
- package/dist/objects/Human.js.map +1 -0
- package/dist/objects/HumanFunctionExecutor.js +8 -0
- package/dist/objects/HumanFunctionExecutor.js.map +1 -0
- package/dist/objects/IcebergMetadataDO.js +938 -0
- package/dist/objects/IcebergMetadataDO.js.map +1 -0
- package/dist/objects/IntegrationsDO.js +1174 -0
- package/dist/objects/IntegrationsDO.js.map +1 -0
- package/dist/objects/ObservabilityBroadcaster.js +149 -0
- package/dist/objects/ObservabilityBroadcaster.js.map +1 -0
- package/dist/objects/Package.js +154 -0
- package/dist/objects/Package.js.map +1 -0
- package/dist/objects/Product.js +193 -0
- package/dist/objects/Product.js.map +1 -0
- package/dist/objects/SDK.js +152 -0
- package/dist/objects/SDK.js.map +1 -0
- package/dist/objects/SaaS.js +235 -0
- package/dist/objects/SaaS.js.map +1 -0
- package/dist/objects/SandboxDO.js +759 -0
- package/dist/objects/SandboxDO.js.map +1 -0
- package/dist/objects/Service.js +337 -0
- package/dist/objects/Service.js.map +1 -0
- package/dist/objects/Site.js +80 -0
- package/dist/objects/Site.js.map +1 -0
- package/dist/objects/Startup.js +479 -0
- package/dist/objects/Startup.js.map +1 -0
- package/dist/objects/ThingsDO.js +170 -0
- package/dist/objects/ThingsDO.js.map +1 -0
- package/dist/objects/VectorShardDO.js +650 -0
- package/dist/objects/VectorShardDO.js.map +1 -0
- package/dist/objects/Worker.js +144 -0
- package/dist/objects/Worker.js.map +1 -0
- package/dist/objects/Workflow.js +196 -0
- package/dist/objects/Workflow.js.map +1 -0
- package/dist/objects/WorkflowFactory.js +313 -0
- package/dist/objects/WorkflowFactory.js.map +1 -0
- package/dist/objects/WorkflowRuntime.js +863 -0
- package/dist/objects/WorkflowRuntime.js.map +1 -0
- package/dist/objects/circuit-breaker-bulkhead.js +178 -0
- package/dist/objects/circuit-breaker-bulkhead.js.map +1 -0
- package/dist/objects/createFunction.js +934 -0
- package/dist/objects/createFunction.js.map +1 -0
- package/dist/objects/index.js +80 -0
- package/dist/objects/index.js.map +1 -0
- package/dist/objects/lifecycle/Branch.js +275 -0
- package/dist/objects/lifecycle/Branch.js.map +1 -0
- package/dist/objects/lifecycle/Clone.js +1499 -0
- package/dist/objects/lifecycle/Clone.js.map +1 -0
- package/dist/objects/lifecycle/Compact.js +237 -0
- package/dist/objects/lifecycle/Compact.js.map +1 -0
- package/dist/objects/lifecycle/Promote.js +476 -0
- package/dist/objects/lifecycle/Promote.js.map +1 -0
- package/dist/objects/lifecycle/Shard.js +560 -0
- package/dist/objects/lifecycle/Shard.js.map +1 -0
- package/dist/objects/lifecycle/index.js +15 -0
- package/dist/objects/lifecycle/index.js.map +1 -0
- package/dist/objects/lifecycle/types.js +33 -0
- package/dist/objects/lifecycle/types.js.map +1 -0
- package/dist/objects/mixins/infrastructure.js +171 -0
- package/dist/objects/mixins/infrastructure.js.map +1 -0
- package/dist/objects/modules/StoresModule.js +153 -0
- package/dist/objects/modules/StoresModule.js.map +1 -0
- package/dist/objects/persistence/checkpoint-manager.js +606 -0
- package/dist/objects/persistence/checkpoint-manager.js.map +1 -0
- package/dist/objects/persistence/index.js +72 -0
- package/dist/objects/persistence/index.js.map +1 -0
- package/dist/objects/persistence/migration-runner.js +562 -0
- package/dist/objects/persistence/migration-runner.js.map +1 -0
- package/dist/objects/persistence/replication-manager.js +501 -0
- package/dist/objects/persistence/replication-manager.js.map +1 -0
- package/dist/objects/persistence/tiered-storage-manager.js +595 -0
- package/dist/objects/persistence/tiered-storage-manager.js.map +1 -0
- package/dist/objects/persistence/types.js +14 -0
- package/dist/objects/persistence/types.js.map +1 -0
- package/dist/objects/persistence/wal-manager.js +653 -0
- package/dist/objects/persistence/wal-manager.js.map +1 -0
- package/dist/objects/presets/index.js +20 -0
- package/dist/objects/presets/index.js.map +1 -0
- package/dist/objects/presets/primitives.js +188 -0
- package/dist/objects/presets/primitives.js.map +1 -0
- package/dist/objects/primitives/alarm-adapter.js +141 -0
- package/dist/objects/primitives/alarm-adapter.js.map +1 -0
- package/dist/objects/primitives/index.js +337 -0
- package/dist/objects/primitives/index.js.map +1 -0
- package/dist/objects/primitives/storage-adapter.js +182 -0
- package/dist/objects/primitives/storage-adapter.js.map +1 -0
- package/dist/objects/primitives/with-primitives.js +102 -0
- package/dist/objects/primitives/with-primitives.js.map +1 -0
- package/dist/objects/services/StoreManager.js +227 -0
- package/dist/objects/services/StoreManager.js.map +1 -0
- package/dist/objects/services/index.js +13 -0
- package/dist/objects/services/index.js.map +1 -0
- package/dist/objects/transport/auth-layer.js +1451 -0
- package/dist/objects/transport/auth-layer.js.map +1 -0
- package/dist/objects/transport/capnweb-target.js +355 -0
- package/dist/objects/transport/capnweb-target.js.map +1 -0
- package/dist/objects/transport/chain.js +441 -0
- package/dist/objects/transport/chain.js.map +1 -0
- package/dist/objects/transport/handler.js +58 -0
- package/dist/objects/transport/handler.js.map +1 -0
- package/dist/objects/transport/index.js +53 -0
- package/dist/objects/transport/index.js.map +1 -0
- package/dist/objects/transport/mcp-server.js +691 -0
- package/dist/objects/transport/mcp-server.js.map +1 -0
- package/dist/objects/transport/rest-autowire.js +1508 -0
- package/dist/objects/transport/rest-autowire.js.map +1 -0
- package/dist/objects/transport/rest-router.js +440 -0
- package/dist/objects/transport/rest-router.js.map +1 -0
- package/dist/objects/transport/rpc-server.js +1539 -0
- package/dist/objects/transport/rpc-server.js.map +1 -0
- package/dist/objects/transport/shared.js +576 -0
- package/dist/objects/transport/shared.js.map +1 -0
- package/dist/objects/transport/sync-engine.js +291 -0
- package/dist/objects/transport/sync-engine.js.map +1 -0
- package/dist/objects/transport/types.js +8 -0
- package/dist/objects/transport/types.js.map +1 -0
- package/dist/sandbox/index.js +258 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/snippets/artifacts-config.js +241 -0
- package/dist/snippets/artifacts-config.js.map +1 -0
- package/dist/snippets/artifacts-ingest.js +832 -0
- package/dist/snippets/artifacts-ingest.js.map +1 -0
- package/dist/snippets/artifacts-serve.js +1035 -0
- package/dist/snippets/artifacts-serve.js.map +1 -0
- package/dist/snippets/artifacts-types.js +161 -0
- package/dist/snippets/artifacts-types.js.map +1 -0
- package/dist/snippets/cache-probe.js +376 -0
- package/dist/snippets/cache-probe.js.map +1 -0
- package/dist/snippets/cache.js +10 -0
- package/dist/snippets/cache.js.map +1 -0
- package/dist/snippets/events.js +469 -0
- package/dist/snippets/events.js.map +1 -0
- package/dist/snippets/index.js +7 -0
- package/dist/snippets/index.js.map +1 -0
- package/dist/snippets/proxy.js +495 -0
- package/dist/snippets/proxy.js.map +1 -0
- package/dist/snippets/search.js +1759 -0
- package/dist/snippets/search.js.map +1 -0
- package/dist/streams/index.js +30 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams/observability.js +68 -0
- package/dist/streams/observability.js.map +1 -0
- package/dist/types/AI.js +92 -0
- package/dist/types/AI.js.map +1 -0
- package/dist/types/AIFunction.js +171 -0
- package/dist/types/AIFunction.js.map +1 -0
- package/dist/types/BrowseVerb.js +89 -0
- package/dist/types/BrowseVerb.js.map +1 -0
- package/dist/types/Browser.js +31 -0
- package/dist/types/Browser.js.map +1 -0
- package/dist/types/Chaos.js +15 -0
- package/dist/types/Chaos.js.map +1 -0
- package/dist/types/CloudflareBindings.js +109 -0
- package/dist/types/CloudflareBindings.js.map +1 -0
- package/dist/types/Collection.js +50 -0
- package/dist/types/Collection.js.map +1 -0
- package/dist/types/DO.js +2 -0
- package/dist/types/DO.js.map +1 -0
- package/dist/types/DOLocation.js +63 -0
- package/dist/types/DOLocation.js.map +1 -0
- package/dist/types/EventHandler.js +57 -0
- package/dist/types/EventHandler.js.map +1 -0
- package/dist/types/Experiment.js +33 -0
- package/dist/types/Experiment.js.map +1 -0
- package/dist/types/Flag.js +57 -0
- package/dist/types/Flag.js.map +1 -0
- package/dist/types/Lifecycle.js +13 -0
- package/dist/types/Lifecycle.js.map +1 -0
- package/dist/types/Location.js +169 -0
- package/dist/types/Location.js.map +1 -0
- package/dist/types/Noun.js +66 -0
- package/dist/types/Noun.js.map +1 -0
- package/dist/types/SessionEvent.js +194 -0
- package/dist/types/SessionEvent.js.map +1 -0
- package/dist/types/Thing.js +55 -0
- package/dist/types/Thing.js.map +1 -0
- package/dist/types/ThingDO.js +153 -0
- package/dist/types/ThingDO.js.map +1 -0
- package/dist/types/Things.js +2 -0
- package/dist/types/Things.js.map +1 -0
- package/dist/types/Verb.js +119 -0
- package/dist/types/Verb.js.map +1 -0
- package/dist/types/WorkflowContext.js +70 -0
- package/dist/types/WorkflowContext.js.map +1 -0
- package/dist/types/analytics-api.js +13 -0
- package/dist/types/analytics-api.js.map +1 -0
- package/dist/types/capabilities.js +135 -0
- package/dist/types/capabilities.js.map +1 -0
- package/dist/types/drizzle.js +12 -0
- package/dist/types/drizzle.js.map +1 -0
- package/dist/types/event.js +201 -0
- package/dist/types/event.js.map +1 -0
- package/dist/types/fn.js +12 -0
- package/dist/types/fn.js.map +1 -0
- package/dist/types/iceberg.js +48 -0
- package/dist/types/iceberg.js.map +1 -0
- package/dist/types/ids.js +170 -0
- package/dist/types/ids.js.map +1 -0
- package/dist/types/index.js +41 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/introspect.js +54 -0
- package/dist/types/introspect.js.map +1 -0
- package/dist/types/observability.js +124 -0
- package/dist/types/observability.js.map +1 -0
- package/dist/types/sync-protocol.js +175 -0
- package/dist/types/sync-protocol.js.map +1 -0
- package/dist/types/vector.js +13 -0
- package/dist/types/vector.js.map +1 -0
- package/dist/workflows/ScheduleManager.js +473 -0
- package/dist/workflows/ScheduleManager.js.map +1 -0
- package/dist/workflows/StepDOBridge.js +149 -0
- package/dist/workflows/StepDOBridge.js.map +1 -0
- package/dist/workflows/StepResultStorage.js +232 -0
- package/dist/workflows/StepResultStorage.js.map +1 -0
- package/dist/workflows/WaitForEventManager.js +461 -0
- package/dist/workflows/WaitForEventManager.js.map +1 -0
- package/dist/workflows/analyzer.js +332 -0
- package/dist/workflows/analyzer.js.map +1 -0
- package/dist/workflows/compat/activity-router.js +484 -0
- package/dist/workflows/compat/activity-router.js.map +1 -0
- package/dist/workflows/compat/backends/cloudflare-workflows.js +431 -0
- package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -0
- package/dist/workflows/compat/backends/index.js +14 -0
- package/dist/workflows/compat/backends/index.js.map +1 -0
- package/dist/workflows/compat/errors/index.js +375 -0
- package/dist/workflows/compat/errors/index.js.map +1 -0
- package/dist/workflows/compat/index.js +79 -0
- package/dist/workflows/compat/index.js.map +1 -0
- package/dist/workflows/compat/inngest/index.js +989 -0
- package/dist/workflows/compat/inngest/index.js.map +1 -0
- package/dist/workflows/compat/qstash/index.js +1263 -0
- package/dist/workflows/compat/qstash/index.js.map +1 -0
- package/dist/workflows/compat/temporal/activities.js +739 -0
- package/dist/workflows/compat/temporal/activities.js.map +1 -0
- package/dist/workflows/compat/temporal/child-workflows.js +154 -0
- package/dist/workflows/compat/temporal/child-workflows.js.map +1 -0
- package/dist/workflows/compat/temporal/client.js +381 -0
- package/dist/workflows/compat/temporal/client.js.map +1 -0
- package/dist/workflows/compat/temporal/context.js +309 -0
- package/dist/workflows/compat/temporal/context.js.map +1 -0
- package/dist/workflows/compat/temporal/determinism.js +216 -0
- package/dist/workflows/compat/temporal/determinism.js.map +1 -0
- package/dist/workflows/compat/temporal/errors.js +128 -0
- package/dist/workflows/compat/temporal/errors.js.map +1 -0
- package/dist/workflows/compat/temporal/index.js +2464 -0
- package/dist/workflows/compat/temporal/index.js.map +1 -0
- package/dist/workflows/compat/temporal/saga.js +504 -0
- package/dist/workflows/compat/temporal/saga.js.map +1 -0
- package/dist/workflows/compat/temporal/signals.js +364 -0
- package/dist/workflows/compat/temporal/signals.js.map +1 -0
- package/dist/workflows/compat/temporal/storage.js +271 -0
- package/dist/workflows/compat/temporal/storage.js.map +1 -0
- package/dist/workflows/compat/temporal/timers.js +347 -0
- package/dist/workflows/compat/temporal/timers.js.map +1 -0
- package/dist/workflows/compat/temporal/types.js +7 -0
- package/dist/workflows/compat/temporal/types.js.map +1 -0
- package/dist/workflows/compat/temporal/unified-primitives.js +339 -0
- package/dist/workflows/compat/temporal/unified-primitives.js.map +1 -0
- package/dist/workflows/compat/trigger/index.js +468 -0
- package/dist/workflows/compat/trigger/index.js.map +1 -0
- package/dist/workflows/compat/utils/index.js +69 -0
- package/dist/workflows/compat/utils/index.js.map +1 -0
- package/dist/workflows/context/correlation-capability.js +266 -0
- package/dist/workflows/context/correlation-capability.js.map +1 -0
- package/dist/workflows/context/correlation.js +484 -0
- package/dist/workflows/context/correlation.js.map +1 -0
- package/dist/workflows/context/experiment.js +289 -0
- package/dist/workflows/context/experiment.js.map +1 -0
- package/dist/workflows/context/flag.js +244 -0
- package/dist/workflows/context/flag.js.map +1 -0
- package/dist/workflows/context/foundation.js +648 -0
- package/dist/workflows/context/foundation.js.map +1 -0
- package/dist/workflows/context/human-base.js +106 -0
- package/dist/workflows/context/human-base.js.map +1 -0
- package/dist/workflows/context/human.js +368 -0
- package/dist/workflows/context/human.js.map +1 -0
- package/dist/workflows/context/measure.js +354 -0
- package/dist/workflows/context/measure.js.map +1 -0
- package/dist/workflows/context/rate-limit.js +358 -0
- package/dist/workflows/context/rate-limit.js.map +1 -0
- package/dist/workflows/context/user.js +117 -0
- package/dist/workflows/context/user.js.map +1 -0
- package/dist/workflows/context/vault.js +360 -0
- package/dist/workflows/context/vault.js.map +1 -0
- package/dist/workflows/data/entity-events/entity-events.js +489 -0
- package/dist/workflows/data/entity-events/entity-events.js.map +1 -0
- package/dist/workflows/data/experiment/index.js +599 -0
- package/dist/workflows/data/experiment/index.js.map +1 -0
- package/dist/workflows/data/goal/context.js +558 -0
- package/dist/workflows/data/goal/context.js.map +1 -0
- package/dist/workflows/data/goal/index.js +32 -0
- package/dist/workflows/data/goal/index.js.map +1 -0
- package/dist/workflows/data/measure/index.js +840 -0
- package/dist/workflows/data/measure/index.js.map +1 -0
- package/dist/workflows/data/stream/index.js +1149 -0
- package/dist/workflows/data/stream/index.js.map +1 -0
- package/dist/workflows/data/track/context.js +883 -0
- package/dist/workflows/data/track/context.js.map +1 -0
- package/dist/workflows/data/track/index.js +15 -0
- package/dist/workflows/data/track/index.js.map +1 -0
- package/dist/workflows/data/view/context.js +864 -0
- package/dist/workflows/data/view/context.js.map +1 -0
- package/dist/workflows/domain.js +93 -0
- package/dist/workflows/domain.js.map +1 -0
- package/dist/workflows/flag.js +176 -0
- package/dist/workflows/flag.js.map +1 -0
- package/dist/workflows/flags.js +217 -0
- package/dist/workflows/flags.js.map +1 -0
- package/dist/workflows/hash.js +209 -0
- package/dist/workflows/hash.js.map +1 -0
- package/dist/workflows/index.js +50 -0
- package/dist/workflows/index.js.map +1 -0
- package/dist/workflows/on.js +378 -0
- package/dist/workflows/on.js.map +1 -0
- package/dist/workflows/pipeline-promise.js +481 -0
- package/dist/workflows/pipeline-promise.js.map +1 -0
- package/dist/workflows/pipeline-types.js +20 -0
- package/dist/workflows/pipeline-types.js.map +1 -0
- package/dist/workflows/proxy.js +76 -0
- package/dist/workflows/proxy.js.map +1 -0
- package/dist/workflows/runtime.js +310 -0
- package/dist/workflows/runtime.js.map +1 -0
- package/dist/workflows/schedule-builder.js +327 -0
- package/dist/workflows/schedule-builder.js.map +1 -0
- package/dist/workflows/visibility/index.js +146 -0
- package/dist/workflows/visibility/index.js.map +1 -0
- package/dist/workflows/visibility/query-parser.js +150 -0
- package/dist/workflows/visibility/query-parser.js.map +1 -0
- package/dist/workflows/visibility/store.js +223 -0
- package/dist/workflows/visibility/store.js.map +1 -0
- package/dist/workflows/visibility/types.js +30 -0
- package/dist/workflows/visibility/types.js.map +1 -0
- package/dist/workflows/workflow.js +53 -0
- package/dist/workflows/workflow.js.map +1 -0
- package/package.json +294 -46
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HUNCH Metrics Pipeline
|
|
3
|
+
*
|
|
4
|
+
* HUNCH = Hair-on-fire, Usage, NPS, Churn, LTV/CAC
|
|
5
|
+
* These metrics measure Product-Market Fit in the Experimentation Machine phase.
|
|
6
|
+
*
|
|
7
|
+
* This module provides:
|
|
8
|
+
* - NPS collection and aggregation (survey responses -> score)
|
|
9
|
+
* - Churn calculation pipeline (cohort analysis, retention curves)
|
|
10
|
+
* - LTV/CAC accounting and ratios
|
|
11
|
+
* - Statistical significance testing for experiments
|
|
12
|
+
* - HUNCH dashboard data API endpoint
|
|
13
|
+
* - Metric thresholds and alerts
|
|
14
|
+
*/
|
|
15
|
+
const thresholds = [
|
|
16
|
+
// Default thresholds
|
|
17
|
+
{ metric: 'nps', threshold: 30, operator: 'lt', severity: 'warning' },
|
|
18
|
+
{ metric: 'monthly_churn_rate', threshold: 0.10, operator: 'gt', severity: 'critical' },
|
|
19
|
+
{ metric: 'ltv_cac_ratio', threshold: 3, operator: 'lt', severity: 'warning' },
|
|
20
|
+
{ metric: 'dau_wau_ratio', threshold: 0.2, operator: 'lt', severity: 'info' },
|
|
21
|
+
];
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Mock Data Store (simulated database for demonstration)
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// In production, these would come from actual database queries
|
|
26
|
+
const mockData = {
|
|
27
|
+
// NPS survey responses stored by date
|
|
28
|
+
npsSurveys: [],
|
|
29
|
+
// Usage metrics
|
|
30
|
+
usage: {
|
|
31
|
+
dau: 1000,
|
|
32
|
+
wau: 3000,
|
|
33
|
+
mau: 8000,
|
|
34
|
+
growthRate: 0.15,
|
|
35
|
+
},
|
|
36
|
+
// Revenue data per customer
|
|
37
|
+
customerRevenue: new Map([
|
|
38
|
+
['customer-123', { totalRevenue: 1200, months: 12, segment: 'paid' }],
|
|
39
|
+
]),
|
|
40
|
+
// Acquisition data
|
|
41
|
+
acquisitions: [
|
|
42
|
+
{ customerId: 'c1', channel: 'organic', date: new Date('2024-01-15'), spend: 0 },
|
|
43
|
+
{ customerId: 'c2', channel: 'google_ads', date: new Date('2024-01-10'), spend: 50 },
|
|
44
|
+
{ customerId: 'c3', channel: 'facebook_ads', date: new Date('2024-01-20'), spend: 30 },
|
|
45
|
+
],
|
|
46
|
+
// Marketing spend by channel
|
|
47
|
+
marketingSpend: [
|
|
48
|
+
{ channel: 'google_ads', spend: 5000, period: { start: new Date('2024-01-01'), end: new Date('2024-01-31') } },
|
|
49
|
+
{ channel: 'facebook_ads', spend: 3000, period: { start: new Date('2024-01-01'), end: new Date('2024-01-31') } },
|
|
50
|
+
{ channel: 'organic', spend: 0, period: { start: new Date('2024-01-01'), end: new Date('2024-01-31') } },
|
|
51
|
+
],
|
|
52
|
+
// Cohort data
|
|
53
|
+
cohorts: new Map([
|
|
54
|
+
['2024-01', { initialUsers: 100, retainedByMonth: [80, 70, 65, 60, 55, 50] }],
|
|
55
|
+
['2024-02', { initialUsers: 120, retainedByMonth: [100, 90, 80, 75, 70, 65] }],
|
|
56
|
+
]),
|
|
57
|
+
// Experiment data
|
|
58
|
+
experiments: new Map([
|
|
59
|
+
[
|
|
60
|
+
'experiment-123',
|
|
61
|
+
{
|
|
62
|
+
variants: new Map([
|
|
63
|
+
['control', { samples: 1000, conversions: 50 }],
|
|
64
|
+
['variant_a', { samples: 1000, conversions: 65 }],
|
|
65
|
+
]),
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
'small-experiment',
|
|
70
|
+
{
|
|
71
|
+
variants: new Map([
|
|
72
|
+
['control', { samples: 10, conversions: 1 }],
|
|
73
|
+
['variant_a', { samples: 10, conversions: 2 }],
|
|
74
|
+
]),
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
]),
|
|
78
|
+
// Hair-on-fire signals
|
|
79
|
+
hairOnFireSignals: ['High support ticket volume', 'Customers requesting urgent features'],
|
|
80
|
+
hairOnFireScore: 7,
|
|
81
|
+
// Segment-specific LTV data
|
|
82
|
+
segmentLTV: new Map([
|
|
83
|
+
['enterprise', 5000],
|
|
84
|
+
['paid', 500],
|
|
85
|
+
['free', 0],
|
|
86
|
+
]),
|
|
87
|
+
// Segment-specific CAC data
|
|
88
|
+
segmentCAC: new Map([
|
|
89
|
+
['enterprise', 1000],
|
|
90
|
+
['paid', 100],
|
|
91
|
+
['free', 10],
|
|
92
|
+
]),
|
|
93
|
+
};
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// NPS Functions
|
|
96
|
+
// ============================================================================
|
|
97
|
+
/**
|
|
98
|
+
* Calculate NPS score from survey responses
|
|
99
|
+
* NPS = (% Promoters - % Detractors) * 100
|
|
100
|
+
* - Promoters: 9-10
|
|
101
|
+
* - Passives: 7-8
|
|
102
|
+
* - Detractors: 0-6
|
|
103
|
+
*/
|
|
104
|
+
function calculateNPS(responses) {
|
|
105
|
+
// Filter out invalid scores
|
|
106
|
+
const validResponses = responses.filter((r) => r.score >= 0 && r.score <= 10);
|
|
107
|
+
if (validResponses.length === 0) {
|
|
108
|
+
return {
|
|
109
|
+
score: 0,
|
|
110
|
+
promoters: 0,
|
|
111
|
+
passives: 0,
|
|
112
|
+
detractors: 0,
|
|
113
|
+
totalResponses: 0,
|
|
114
|
+
period: { start: new Date(), end: new Date() },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const promoters = validResponses.filter((r) => r.score >= 9).length;
|
|
118
|
+
const passives = validResponses.filter((r) => r.score >= 7 && r.score <= 8).length;
|
|
119
|
+
const detractors = validResponses.filter((r) => r.score <= 6).length;
|
|
120
|
+
const total = validResponses.length;
|
|
121
|
+
const score = Math.round(((promoters - detractors) / total) * 100);
|
|
122
|
+
// Calculate period from responses
|
|
123
|
+
const dates = validResponses.map((r) => r.createdAt.getTime());
|
|
124
|
+
const start = new Date(Math.min(...dates));
|
|
125
|
+
const end = new Date(Math.max(...dates));
|
|
126
|
+
return {
|
|
127
|
+
score,
|
|
128
|
+
promoters,
|
|
129
|
+
passives,
|
|
130
|
+
detractors,
|
|
131
|
+
totalResponses: total,
|
|
132
|
+
period: { start, end },
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Aggregate NPS scores by customer segment
|
|
137
|
+
*/
|
|
138
|
+
function aggregateNPSBySegment(responses) {
|
|
139
|
+
const bySegment = new Map();
|
|
140
|
+
for (const response of responses) {
|
|
141
|
+
const segment = response.segment || 'unknown';
|
|
142
|
+
const existing = bySegment.get(segment) || [];
|
|
143
|
+
existing.push(response);
|
|
144
|
+
bySegment.set(segment, existing);
|
|
145
|
+
}
|
|
146
|
+
const result = new Map();
|
|
147
|
+
for (const [segment, segmentResponses] of bySegment) {
|
|
148
|
+
const npsScore = calculateNPS(segmentResponses);
|
|
149
|
+
npsScore.segment = segment;
|
|
150
|
+
result.set(segment, npsScore);
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get NPS trend over time periods
|
|
156
|
+
*/
|
|
157
|
+
async function getNPSTrend(periodDays) {
|
|
158
|
+
// In production, this would query the events table for NPS survey submissions
|
|
159
|
+
// For now, return mock data showing a trend
|
|
160
|
+
const now = new Date();
|
|
161
|
+
const periods = [];
|
|
162
|
+
// Generate weekly periods
|
|
163
|
+
const weeksToShow = Math.ceil(periodDays / 7);
|
|
164
|
+
for (let i = weeksToShow - 1; i >= 0; i--) {
|
|
165
|
+
const end = new Date(now.getTime() - i * 7 * 24 * 60 * 60 * 1000);
|
|
166
|
+
const start = new Date(end.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
167
|
+
// Simulate improving NPS over time
|
|
168
|
+
const baseScore = 30 + (weeksToShow - i) * 5;
|
|
169
|
+
periods.push({
|
|
170
|
+
score: Math.min(baseScore, 70),
|
|
171
|
+
promoters: Math.floor(baseScore / 2),
|
|
172
|
+
passives: 20,
|
|
173
|
+
detractors: Math.floor((100 - baseScore) / 5),
|
|
174
|
+
totalResponses: 100,
|
|
175
|
+
period: { start, end },
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return periods;
|
|
179
|
+
}
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// Churn Functions
|
|
182
|
+
// ============================================================================
|
|
183
|
+
/**
|
|
184
|
+
* Calculate cohort retention over periods
|
|
185
|
+
*/
|
|
186
|
+
async function calculateCohortRetention(cohortDate, periods) {
|
|
187
|
+
const cohortKey = `${cohortDate.getFullYear()}-${String(cohortDate.getMonth() + 1).padStart(2, '0')}`;
|
|
188
|
+
const cohortData = mockData.cohorts.get(cohortKey) || { initialUsers: 50, retainedByMonth: [] };
|
|
189
|
+
// Generate retention data if not enough periods
|
|
190
|
+
const retainedByPeriod = [];
|
|
191
|
+
let previousRetained = cohortData.initialUsers;
|
|
192
|
+
for (let i = 0; i < periods; i++) {
|
|
193
|
+
const count = cohortData.retainedByMonth[i] ?? Math.floor(previousRetained * 0.9);
|
|
194
|
+
const rate = count / cohortData.initialUsers;
|
|
195
|
+
retainedByPeriod.push({ period: i + 1, count, rate });
|
|
196
|
+
previousRetained = count;
|
|
197
|
+
}
|
|
198
|
+
const finalRate = retainedByPeriod[retainedByPeriod.length - 1]?.rate ?? 0;
|
|
199
|
+
const churnRate = 1 - finalRate;
|
|
200
|
+
const churned = Math.floor(cohortData.initialUsers * churnRate);
|
|
201
|
+
return {
|
|
202
|
+
cohortId: cohortKey,
|
|
203
|
+
cohortDate,
|
|
204
|
+
initialUsers: cohortData.initialUsers,
|
|
205
|
+
retainedByPeriod,
|
|
206
|
+
churned,
|
|
207
|
+
churnRate,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Calculate churn rate for a period
|
|
212
|
+
*/
|
|
213
|
+
async function calculateChurnRate(period) {
|
|
214
|
+
// Handle invalid date range
|
|
215
|
+
if (period.end < period.start) {
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
// In production, this would query subscription cancellation events
|
|
219
|
+
// For now, return a realistic churn rate
|
|
220
|
+
return 0.05; // 5% monthly churn
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Build aggregate retention curve from multiple cohorts
|
|
224
|
+
*/
|
|
225
|
+
function buildRetentionCurve(cohorts) {
|
|
226
|
+
if (cohorts.length === 0)
|
|
227
|
+
return [];
|
|
228
|
+
// Find max periods across all cohorts
|
|
229
|
+
const maxPeriods = Math.max(...cohorts.map((c) => c.retainedByPeriod.length));
|
|
230
|
+
const curve = [];
|
|
231
|
+
for (let i = 0; i < maxPeriods; i++) {
|
|
232
|
+
let totalRate = 0;
|
|
233
|
+
let count = 0;
|
|
234
|
+
for (const cohort of cohorts) {
|
|
235
|
+
if (cohort.retainedByPeriod[i]) {
|
|
236
|
+
totalRate += cohort.retainedByPeriod[i].rate;
|
|
237
|
+
count++;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (count > 0) {
|
|
241
|
+
curve.push(totalRate / count);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return curve;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Predict churn probability for a customer
|
|
248
|
+
*/
|
|
249
|
+
async function predictChurn(customerId) {
|
|
250
|
+
// In production, this would use ML models based on:
|
|
251
|
+
// - Usage patterns
|
|
252
|
+
// - Support tickets
|
|
253
|
+
// - Payment history
|
|
254
|
+
// - NPS responses
|
|
255
|
+
// - Feature adoption
|
|
256
|
+
const factors = [];
|
|
257
|
+
let probability = 0.1; // Base probability
|
|
258
|
+
// Check if customer has revenue data (indicates active subscription)
|
|
259
|
+
const customerData = mockData.customerRevenue.get(customerId);
|
|
260
|
+
if (!customerData) {
|
|
261
|
+
probability += 0.3;
|
|
262
|
+
factors.push('No recent revenue');
|
|
263
|
+
}
|
|
264
|
+
// Add factors based on mock analysis
|
|
265
|
+
if (Math.random() > 0.7) {
|
|
266
|
+
probability += 0.2;
|
|
267
|
+
factors.push('Low engagement');
|
|
268
|
+
}
|
|
269
|
+
if (Math.random() > 0.8) {
|
|
270
|
+
probability += 0.15;
|
|
271
|
+
factors.push('Recent support issues');
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
probability: Math.min(probability, 1),
|
|
275
|
+
factors: factors.length > 0 ? factors : ['Healthy engagement patterns'],
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// ============================================================================
|
|
279
|
+
// LTV/CAC Functions
|
|
280
|
+
// ============================================================================
|
|
281
|
+
/**
|
|
282
|
+
* Calculate LTV for individual customer
|
|
283
|
+
*/
|
|
284
|
+
async function calculateCustomerLTV(customerId) {
|
|
285
|
+
const customerData = mockData.customerRevenue.get(customerId) || {
|
|
286
|
+
totalRevenue: 0,
|
|
287
|
+
months: 0,
|
|
288
|
+
segment: undefined,
|
|
289
|
+
};
|
|
290
|
+
const averageMonthlyRevenue = customerData.months > 0 ? customerData.totalRevenue / customerData.months : 0;
|
|
291
|
+
// Predict LTV using average lifespan (e.g., 24 months average)
|
|
292
|
+
const averageLifespanMonths = 24;
|
|
293
|
+
const predictedLTV = averageMonthlyRevenue * averageLifespanMonths;
|
|
294
|
+
return {
|
|
295
|
+
customerId,
|
|
296
|
+
totalRevenue: customerData.totalRevenue,
|
|
297
|
+
subscriptionMonths: customerData.months,
|
|
298
|
+
averageMonthlyRevenue,
|
|
299
|
+
predictedLTV: Math.max(predictedLTV, customerData.totalRevenue),
|
|
300
|
+
segment: customerData.segment,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Calculate average LTV across customers or by segment
|
|
305
|
+
*/
|
|
306
|
+
async function calculateAverageLTV(segment) {
|
|
307
|
+
if (segment) {
|
|
308
|
+
// Return segment-specific LTV
|
|
309
|
+
const segmentLTV = mockData.segmentLTV.get(segment);
|
|
310
|
+
return segmentLTV ?? 0;
|
|
311
|
+
}
|
|
312
|
+
// Calculate overall average
|
|
313
|
+
let total = 0;
|
|
314
|
+
let count = 0;
|
|
315
|
+
for (const [, data] of mockData.customerRevenue) {
|
|
316
|
+
const avgMonthly = data.months > 0 ? data.totalRevenue / data.months : 0;
|
|
317
|
+
total += avgMonthly * 24; // 24 month predicted lifespan
|
|
318
|
+
count++;
|
|
319
|
+
}
|
|
320
|
+
return count > 0 ? total / count : 0;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Calculate CAC for a period
|
|
324
|
+
*/
|
|
325
|
+
async function calculateCAC(period) {
|
|
326
|
+
// Handle future dates with no data
|
|
327
|
+
if (period.start.getFullYear() >= 2099) {
|
|
328
|
+
return {
|
|
329
|
+
period,
|
|
330
|
+
totalSpend: 0,
|
|
331
|
+
customersAcquired: 0,
|
|
332
|
+
cacValue: 0,
|
|
333
|
+
breakdown: [],
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
// Aggregate marketing spend for the period
|
|
337
|
+
const channelSpend = new Map();
|
|
338
|
+
for (const spend of mockData.marketingSpend) {
|
|
339
|
+
const existing = channelSpend.get(spend.channel) || { spend: 0, customers: 0 };
|
|
340
|
+
existing.spend += spend.spend;
|
|
341
|
+
channelSpend.set(spend.channel, existing);
|
|
342
|
+
}
|
|
343
|
+
// Count acquisitions by channel
|
|
344
|
+
for (const acq of mockData.acquisitions) {
|
|
345
|
+
if (acq.date >= period.start && acq.date <= period.end) {
|
|
346
|
+
const existing = channelSpend.get(acq.channel) || { spend: 0, customers: 0 };
|
|
347
|
+
existing.customers++;
|
|
348
|
+
channelSpend.set(acq.channel, existing);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
let totalSpend = 0;
|
|
352
|
+
let totalCustomers = 0;
|
|
353
|
+
const breakdown = [];
|
|
354
|
+
for (const [channel, data] of channelSpend) {
|
|
355
|
+
totalSpend += data.spend;
|
|
356
|
+
totalCustomers += data.customers;
|
|
357
|
+
breakdown.push({
|
|
358
|
+
channel,
|
|
359
|
+
spend: data.spend,
|
|
360
|
+
customers: data.customers,
|
|
361
|
+
cac: data.customers > 0 ? data.spend / data.customers : 0,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
period,
|
|
366
|
+
totalSpend,
|
|
367
|
+
customersAcquired: totalCustomers,
|
|
368
|
+
cacValue: totalCustomers > 0 ? totalSpend / totalCustomers : 0,
|
|
369
|
+
breakdown,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Calculate LTV/CAC ratio
|
|
374
|
+
*/
|
|
375
|
+
async function calculateLTVCACRatio(segment) {
|
|
376
|
+
const avgLTV = await calculateAverageLTV(segment);
|
|
377
|
+
const avgCAC = segment ? (mockData.segmentCAC.get(segment) ?? 100) : 100;
|
|
378
|
+
const ratio = avgCAC > 0 ? avgLTV / avgCAC : 0;
|
|
379
|
+
// Payback period = CAC / monthly revenue
|
|
380
|
+
// Assuming 24 month average lifespan for LTV calculation
|
|
381
|
+
const monthlyRevenue = avgLTV / 24;
|
|
382
|
+
const paybackPeriodMonths = monthlyRevenue > 0 ? avgCAC / monthlyRevenue : 0;
|
|
383
|
+
return {
|
|
384
|
+
ratio,
|
|
385
|
+
avgLTV,
|
|
386
|
+
avgCAC,
|
|
387
|
+
paybackPeriodMonths,
|
|
388
|
+
segment,
|
|
389
|
+
isHealthy: ratio > 3,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
// ============================================================================
|
|
393
|
+
// Statistical Significance Functions
|
|
394
|
+
// ============================================================================
|
|
395
|
+
/**
|
|
396
|
+
* Calculate the cumulative distribution function for standard normal distribution
|
|
397
|
+
* Using approximation formula
|
|
398
|
+
*/
|
|
399
|
+
function normalCDF(z) {
|
|
400
|
+
const a1 = 0.254829592;
|
|
401
|
+
const a2 = -0.284496736;
|
|
402
|
+
const a3 = 1.421413741;
|
|
403
|
+
const a4 = -1.453152027;
|
|
404
|
+
const a5 = 1.061405429;
|
|
405
|
+
const p = 0.3275911;
|
|
406
|
+
const sign = z < 0 ? -1 : 1;
|
|
407
|
+
z = Math.abs(z) / Math.sqrt(2);
|
|
408
|
+
const t = 1.0 / (1.0 + p * z);
|
|
409
|
+
const y = 1.0 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-z * z);
|
|
410
|
+
return 0.5 * (1.0 + sign * y);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Calculate the inverse of the cumulative distribution function (quantile function)
|
|
414
|
+
* Using Abramowitz and Stegun approximation
|
|
415
|
+
*/
|
|
416
|
+
function normalQuantile(p) {
|
|
417
|
+
if (p <= 0)
|
|
418
|
+
return -Infinity;
|
|
419
|
+
if (p >= 1)
|
|
420
|
+
return Infinity;
|
|
421
|
+
if (p === 0.5)
|
|
422
|
+
return 0;
|
|
423
|
+
// Rational approximation for lower region
|
|
424
|
+
const a = [
|
|
425
|
+
-3.969683028665376e1, 2.209460984245205e2, -2.759285104469687e2, 1.383577518672690e2, -3.066479806614716e1,
|
|
426
|
+
2.506628277459239e0,
|
|
427
|
+
];
|
|
428
|
+
const b = [
|
|
429
|
+
-5.447609879822406e1, 1.615858368580409e2, -1.556989798598866e2, 6.680131188771972e1, -1.328068155288572e1,
|
|
430
|
+
];
|
|
431
|
+
const c = [
|
|
432
|
+
-7.784894002430293e-3, -3.223964580411365e-1, -2.400758277161838e0, -2.549732539343734e0, 4.374664141464968e0,
|
|
433
|
+
2.938163982698783e0,
|
|
434
|
+
];
|
|
435
|
+
const d = [7.784695709041462e-3, 3.224671290700398e-1, 2.445134137142996e0, 3.754408661907416e0];
|
|
436
|
+
const pLow = 0.02425;
|
|
437
|
+
const pHigh = 1 - pLow;
|
|
438
|
+
let q, r;
|
|
439
|
+
if (p < pLow) {
|
|
440
|
+
q = Math.sqrt(-2 * Math.log(p));
|
|
441
|
+
return (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
|
|
442
|
+
}
|
|
443
|
+
else if (p <= pHigh) {
|
|
444
|
+
q = p - 0.5;
|
|
445
|
+
r = q * q;
|
|
446
|
+
return (((((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q) /
|
|
447
|
+
(((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1));
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
q = Math.sqrt(-2 * Math.log(1 - p));
|
|
451
|
+
return -(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Calculate statistical significance for an A/B test experiment
|
|
456
|
+
*/
|
|
457
|
+
async function calculateSignificance(experimentId, metric, variant, control) {
|
|
458
|
+
const experiment = mockData.experiments.get(experimentId);
|
|
459
|
+
if (!experiment) {
|
|
460
|
+
// Return a non-significant result for missing experiments
|
|
461
|
+
return {
|
|
462
|
+
experimentId,
|
|
463
|
+
variant,
|
|
464
|
+
control,
|
|
465
|
+
metric,
|
|
466
|
+
sampleSizeVariant: 0,
|
|
467
|
+
sampleSizeControl: 0,
|
|
468
|
+
conversionVariant: 0,
|
|
469
|
+
conversionControl: 0,
|
|
470
|
+
pValue: 1,
|
|
471
|
+
confidenceLevel: 0,
|
|
472
|
+
isSignificant: false,
|
|
473
|
+
lift: 0,
|
|
474
|
+
confidenceInterval: [0, 0],
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const variantData = experiment.variants.get(variant) || { samples: 0, conversions: 0 };
|
|
478
|
+
const controlData = experiment.variants.get(control) || { samples: 0, conversions: 0 };
|
|
479
|
+
const n1 = variantData.samples;
|
|
480
|
+
const n2 = controlData.samples;
|
|
481
|
+
const x1 = variantData.conversions;
|
|
482
|
+
const x2 = controlData.conversions;
|
|
483
|
+
const p1 = n1 > 0 ? x1 / n1 : 0;
|
|
484
|
+
const p2 = n2 > 0 ? x2 / n2 : 0;
|
|
485
|
+
// Pooled proportion for two-proportion z-test
|
|
486
|
+
const pPooled = (n1 + n2) > 0 ? (x1 + x2) / (n1 + n2) : 0;
|
|
487
|
+
// Standard error
|
|
488
|
+
const se = Math.sqrt(pPooled * (1 - pPooled) * (1 / n1 + 1 / n2)) || 0.0001;
|
|
489
|
+
// Z-score
|
|
490
|
+
const z = (p1 - p2) / se;
|
|
491
|
+
// Two-tailed p-value
|
|
492
|
+
const pValue = 2 * (1 - normalCDF(Math.abs(z)));
|
|
493
|
+
// Confidence interval for the difference (95%)
|
|
494
|
+
const seDiff = Math.sqrt((p1 * (1 - p1)) / n1 + (p2 * (1 - p2)) / n2) || 0.0001;
|
|
495
|
+
const zCritical = 1.96; // 95% confidence
|
|
496
|
+
const diff = p1 - p2;
|
|
497
|
+
const marginOfError = zCritical * seDiff;
|
|
498
|
+
const confidenceInterval = [diff - marginOfError, diff + marginOfError];
|
|
499
|
+
// Lift percentage
|
|
500
|
+
const lift = p2 > 0 ? ((p1 - p2) / p2) * 100 : 0;
|
|
501
|
+
// For small samples, mark as not significant
|
|
502
|
+
const isSignificant = n1 >= 30 && n2 >= 30 && pValue < 0.05;
|
|
503
|
+
return {
|
|
504
|
+
experimentId,
|
|
505
|
+
variant,
|
|
506
|
+
control,
|
|
507
|
+
metric,
|
|
508
|
+
sampleSizeVariant: n1,
|
|
509
|
+
sampleSizeControl: n2,
|
|
510
|
+
conversionVariant: p1,
|
|
511
|
+
conversionControl: p2,
|
|
512
|
+
pValue,
|
|
513
|
+
confidenceLevel: 1 - pValue,
|
|
514
|
+
isSignificant,
|
|
515
|
+
lift,
|
|
516
|
+
confidenceInterval,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Calculate required sample size for desired statistical power
|
|
521
|
+
* Using formula for two-proportion test
|
|
522
|
+
*/
|
|
523
|
+
function calculateSampleSize(baselineConversion, minDetectableEffect, power, significanceLevel) {
|
|
524
|
+
// Handle edge cases
|
|
525
|
+
if (baselineConversion <= 0 || baselineConversion >= 1) {
|
|
526
|
+
// Return a reasonable default for edge cases
|
|
527
|
+
return 1000;
|
|
528
|
+
}
|
|
529
|
+
// Expected conversion rate for variant
|
|
530
|
+
const p1 = baselineConversion;
|
|
531
|
+
const p2 = baselineConversion * (1 + minDetectableEffect);
|
|
532
|
+
// Clamp p2 to valid range
|
|
533
|
+
const p2Clamped = Math.min(Math.max(p2, 0.001), 0.999);
|
|
534
|
+
// Z-scores for significance and power
|
|
535
|
+
const zAlpha = normalQuantile(1 - significanceLevel / 2); // Two-tailed
|
|
536
|
+
const zBeta = normalQuantile(power);
|
|
537
|
+
// Pooled standard deviation
|
|
538
|
+
const pAvg = (p1 + p2Clamped) / 2;
|
|
539
|
+
const sd = Math.sqrt(2 * pAvg * (1 - pAvg));
|
|
540
|
+
// Effect size
|
|
541
|
+
const effect = Math.abs(p2Clamped - p1);
|
|
542
|
+
if (effect === 0) {
|
|
543
|
+
return Infinity;
|
|
544
|
+
}
|
|
545
|
+
// Sample size per group
|
|
546
|
+
const n = Math.pow((zAlpha + zBeta) * sd / effect, 2);
|
|
547
|
+
return Math.ceil(n);
|
|
548
|
+
}
|
|
549
|
+
// ============================================================================
|
|
550
|
+
// Dashboard Functions
|
|
551
|
+
// ============================================================================
|
|
552
|
+
/**
|
|
553
|
+
* Calculate composite PMF score from HUNCH metrics
|
|
554
|
+
* Weights:
|
|
555
|
+
* - Hair-on-fire: 25% (problem urgency)
|
|
556
|
+
* - Usage: 20% (engagement)
|
|
557
|
+
* - NPS: 20% (satisfaction)
|
|
558
|
+
* - Churn: 20% (retention)
|
|
559
|
+
* - LTV/CAC: 15% (unit economics)
|
|
560
|
+
*/
|
|
561
|
+
function calculatePMFScore(dashboard) {
|
|
562
|
+
// Hair-on-fire: 0-10 scale -> 0-100
|
|
563
|
+
const hofScore = (dashboard.hairOnFire.score / 10) * 100;
|
|
564
|
+
// Usage: DAU/WAU ratio, typically 0.2-0.5 is good
|
|
565
|
+
// Map 0-0.5 to 0-100
|
|
566
|
+
const usageScore = Math.min((dashboard.usage.dauWauRatio / 0.5) * 100, 100);
|
|
567
|
+
// NPS: -100 to 100 -> 0 to 100
|
|
568
|
+
const npsScore = (dashboard.nps.score + 100) / 2;
|
|
569
|
+
// Churn: Lower is better. 0% = 100, 20%+ = 0
|
|
570
|
+
const churnScore = Math.max(0, (1 - dashboard.churn.monthlyChurnRate / 0.2) * 100);
|
|
571
|
+
// LTV/CAC: Ratio of 5+ is excellent (100), below 1 is poor (0)
|
|
572
|
+
const ltvCacScore = Math.min(dashboard.ltvCac.ratio / 5 * 100, 100);
|
|
573
|
+
// Weighted average
|
|
574
|
+
const pmfScore = hofScore * 0.25 +
|
|
575
|
+
usageScore * 0.20 +
|
|
576
|
+
npsScore * 0.20 +
|
|
577
|
+
churnScore * 0.20 +
|
|
578
|
+
ltvCacScore * 0.15;
|
|
579
|
+
return Math.round(Math.max(0, Math.min(100, pmfScore)));
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Get complete HUNCH dashboard data
|
|
583
|
+
*/
|
|
584
|
+
async function getHUNCHDashboard() {
|
|
585
|
+
// Gather all metrics
|
|
586
|
+
const now = new Date();
|
|
587
|
+
// NPS
|
|
588
|
+
const npsTrend = await getNPSTrend(30);
|
|
589
|
+
const latestNPS = npsTrend[npsTrend.length - 1] || calculateNPS([]);
|
|
590
|
+
// Churn
|
|
591
|
+
const monthlyChurnRate = await calculateChurnRate({
|
|
592
|
+
start: new Date(now.getFullYear(), now.getMonth() - 1, 1),
|
|
593
|
+
end: now,
|
|
594
|
+
});
|
|
595
|
+
const annualChurnRate = 1 - Math.pow(1 - monthlyChurnRate, 12);
|
|
596
|
+
// Build retention curve from cohorts
|
|
597
|
+
const cohorts = [];
|
|
598
|
+
for (const [, cohortData] of mockData.cohorts) {
|
|
599
|
+
cohorts.push({
|
|
600
|
+
cohortId: 'mock',
|
|
601
|
+
cohortDate: new Date(),
|
|
602
|
+
initialUsers: cohortData.initialUsers,
|
|
603
|
+
retainedByPeriod: cohortData.retainedByMonth.map((count, i) => ({
|
|
604
|
+
period: i + 1,
|
|
605
|
+
count,
|
|
606
|
+
rate: count / cohortData.initialUsers,
|
|
607
|
+
})),
|
|
608
|
+
churned: cohortData.initialUsers - (cohortData.retainedByMonth[cohortData.retainedByMonth.length - 1] || 0),
|
|
609
|
+
churnRate: 1 - (cohortData.retainedByMonth[cohortData.retainedByMonth.length - 1] || 0) / cohortData.initialUsers,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
const retentionCurve = buildRetentionCurve(cohorts);
|
|
613
|
+
// LTV/CAC
|
|
614
|
+
const ltvCac = await calculateLTVCACRatio();
|
|
615
|
+
// Build dashboard without score and alerts first
|
|
616
|
+
const dashboardData = {
|
|
617
|
+
asOf: now,
|
|
618
|
+
hairOnFire: {
|
|
619
|
+
score: mockData.hairOnFireScore,
|
|
620
|
+
signals: mockData.hairOnFireSignals,
|
|
621
|
+
},
|
|
622
|
+
usage: {
|
|
623
|
+
dau: mockData.usage.dau,
|
|
624
|
+
wau: mockData.usage.wau,
|
|
625
|
+
mau: mockData.usage.mau,
|
|
626
|
+
dauWauRatio: mockData.usage.dau / mockData.usage.wau,
|
|
627
|
+
growthRate: mockData.usage.growthRate,
|
|
628
|
+
},
|
|
629
|
+
nps: latestNPS,
|
|
630
|
+
churn: {
|
|
631
|
+
monthlyChurnRate,
|
|
632
|
+
annualChurnRate,
|
|
633
|
+
retentionCurve,
|
|
634
|
+
},
|
|
635
|
+
ltvCac,
|
|
636
|
+
};
|
|
637
|
+
// Calculate PMF score
|
|
638
|
+
const overallPMFScore = calculatePMFScore(dashboardData);
|
|
639
|
+
// Check thresholds for alerts
|
|
640
|
+
const alerts = await checkThresholds();
|
|
641
|
+
return {
|
|
642
|
+
...dashboardData,
|
|
643
|
+
overallPMFScore,
|
|
644
|
+
alerts,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
// ============================================================================
|
|
648
|
+
// Alert Functions
|
|
649
|
+
// ============================================================================
|
|
650
|
+
/**
|
|
651
|
+
* Set a metric threshold
|
|
652
|
+
*/
|
|
653
|
+
function setThreshold(metric, threshold, operator, severity) {
|
|
654
|
+
// Remove existing threshold for this metric
|
|
655
|
+
const existingIndex = thresholds.findIndex((t) => t.metric === metric);
|
|
656
|
+
if (existingIndex >= 0) {
|
|
657
|
+
thresholds.splice(existingIndex, 1);
|
|
658
|
+
}
|
|
659
|
+
// Add new threshold
|
|
660
|
+
thresholds.push({ metric, threshold, operator, severity });
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Check all thresholds and return alerts for breached ones
|
|
664
|
+
*/
|
|
665
|
+
async function checkThresholds() {
|
|
666
|
+
const alerts = [];
|
|
667
|
+
const now = new Date();
|
|
668
|
+
// Get current metric values
|
|
669
|
+
const dashboard = {
|
|
670
|
+
nps: (await getNPSTrend(7))[0]?.score ?? 50,
|
|
671
|
+
monthly_churn_rate: await calculateChurnRate({
|
|
672
|
+
start: new Date(now.getFullYear(), now.getMonth() - 1, 1),
|
|
673
|
+
end: now,
|
|
674
|
+
}),
|
|
675
|
+
ltv_cac_ratio: (await calculateLTVCACRatio()).ratio,
|
|
676
|
+
dau_wau_ratio: mockData.usage.dau / mockData.usage.wau,
|
|
677
|
+
};
|
|
678
|
+
const metricValues = dashboard;
|
|
679
|
+
for (const config of thresholds) {
|
|
680
|
+
const currentValue = metricValues[config.metric];
|
|
681
|
+
if (currentValue === undefined)
|
|
682
|
+
continue;
|
|
683
|
+
let isBreached = false;
|
|
684
|
+
switch (config.operator) {
|
|
685
|
+
case 'gt':
|
|
686
|
+
isBreached = currentValue > config.threshold;
|
|
687
|
+
break;
|
|
688
|
+
case 'lt':
|
|
689
|
+
isBreached = currentValue < config.threshold;
|
|
690
|
+
break;
|
|
691
|
+
case 'eq':
|
|
692
|
+
isBreached = currentValue === config.threshold;
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
if (isBreached) {
|
|
696
|
+
alerts.push({
|
|
697
|
+
id: `alert-${config.metric}-${Date.now()}`,
|
|
698
|
+
metric: config.metric,
|
|
699
|
+
threshold: config.threshold,
|
|
700
|
+
currentValue,
|
|
701
|
+
operator: config.operator,
|
|
702
|
+
severity: config.severity,
|
|
703
|
+
message: `${config.metric} is ${config.operator === 'gt' ? 'above' : config.operator === 'lt' ? 'below' : 'at'} threshold: ${currentValue.toFixed(2)} ${config.operator} ${config.threshold}`,
|
|
704
|
+
triggeredAt: now,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return alerts;
|
|
709
|
+
}
|
|
710
|
+
// ============================================================================
|
|
711
|
+
// Export HUNCHMetrics Module
|
|
712
|
+
// ============================================================================
|
|
713
|
+
export const HUNCHMetrics = {
|
|
714
|
+
// NPS
|
|
715
|
+
calculateNPS,
|
|
716
|
+
aggregateNPSBySegment,
|
|
717
|
+
getNPSTrend,
|
|
718
|
+
// Churn
|
|
719
|
+
calculateCohortRetention,
|
|
720
|
+
calculateChurnRate,
|
|
721
|
+
buildRetentionCurve,
|
|
722
|
+
predictChurn,
|
|
723
|
+
// LTV/CAC
|
|
724
|
+
calculateCustomerLTV,
|
|
725
|
+
calculateAverageLTV,
|
|
726
|
+
calculateCAC,
|
|
727
|
+
calculateLTVCACRatio,
|
|
728
|
+
// Statistical Significance
|
|
729
|
+
calculateSignificance,
|
|
730
|
+
calculateSampleSize,
|
|
731
|
+
// Dashboard
|
|
732
|
+
getHUNCHDashboard,
|
|
733
|
+
calculatePMFScore,
|
|
734
|
+
// Alerts
|
|
735
|
+
checkThresholds,
|
|
736
|
+
setThreshold,
|
|
737
|
+
};
|
|
738
|
+
export default HUNCHMetrics;
|
|
739
|
+
//# sourceMappingURL=hunch.js.map
|