@hailer/mcp 1.1.12 → 1.1.13
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/CHANGELOG.md +0 -7
- package/{.claude → dist}/CLAUDE.md +2 -2
- package/dist/app.js +18 -5
- package/dist/bot/bot-config.d.ts +10 -1
- package/dist/bot/bot-config.js +64 -3
- package/dist/bot/bot-manager.d.ts +2 -0
- package/dist/bot/bot-manager.js +9 -2
- package/dist/bot/bot.d.ts +33 -0
- package/dist/bot/bot.js +461 -160
- package/dist/bot/services/message-classifier.js +17 -0
- package/dist/bot/services/permission-guard.d.ts +52 -0
- package/dist/bot/services/permission-guard.js +149 -0
- package/dist/bot/services/types.d.ts +5 -0
- package/dist/bot/services/typing-indicator.d.ts +6 -1
- package/dist/bot/services/typing-indicator.js +19 -3
- package/dist/cli.js +0 -0
- package/dist/config.d.ts +6 -1
- package/dist/config.js +43 -0
- package/dist/core.js +3 -6
- package/dist/lib/discussion-lock.d.ts +42 -0
- package/dist/lib/discussion-lock.js +110 -0
- package/dist/mcp/UserContextCache.d.ts +5 -0
- package/dist/mcp/UserContextCache.js +51 -19
- package/dist/mcp/hailer-clients.d.ts +19 -1
- package/dist/mcp/hailer-clients.js +158 -24
- package/dist/mcp/session-store.d.ts +68 -0
- package/dist/mcp/session-store.js +169 -0
- package/dist/mcp/signal-handler.js +2 -0
- package/dist/mcp/tool-registry.d.ts +17 -4
- package/dist/mcp/tool-registry.js +37 -7
- package/dist/mcp/tools/activity.js +99 -7
- package/dist/mcp/tools/app-scaffold.js +304 -336
- package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
- package/dist/mcp/tools/bot-config/constants.js +94 -0
- package/dist/mcp/tools/bot-config/core.d.ts +253 -0
- package/dist/mcp/tools/bot-config/core.js +2456 -0
- package/dist/mcp/tools/bot-config/index.d.ts +10 -0
- package/dist/mcp/tools/bot-config/index.js +59 -0
- package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
- package/dist/mcp/tools/bot-config/tools.js +15 -0
- package/dist/mcp/tools/bot-config/types.d.ts +50 -0
- package/dist/mcp/tools/bot-config/types.js +6 -0
- package/dist/mcp/tools/bug-fixer-tools.d.ts +45 -0
- package/dist/mcp/tools/bug-fixer-tools.js +1096 -0
- package/dist/mcp/tools/company.d.ts +9 -0
- package/dist/mcp/tools/company.js +88 -0
- package/dist/mcp/tools/discussion.js +68 -0
- package/dist/mcp/tools/document.d.ts +11 -0
- package/dist/mcp/tools/document.js +741 -0
- package/dist/mcp/tools/investigate.d.ts +9 -0
- package/dist/mcp/tools/investigate.js +254 -0
- package/dist/mcp/tools/workflow-permissions.d.ts +15 -0
- package/dist/mcp/tools/workflow-permissions.js +204 -0
- package/dist/mcp/tools/workflow.js +57 -18
- package/dist/mcp/utils/index.d.ts +2 -0
- package/dist/mcp/utils/index.js +12 -1
- package/dist/mcp/utils/role-utils.d.ts +74 -0
- package/dist/mcp/utils/role-utils.js +151 -0
- package/dist/mcp/utils/types.d.ts +43 -1
- package/dist/mcp/utils/types.js +14 -0
- package/dist/mcp/webhook-handler.d.ts +4 -0
- package/dist/mcp/webhook-handler.js +8 -0
- package/dist/mcp-server.d.ts +23 -2
- package/dist/mcp-server.js +639 -127
- package/dist/plugins/vipunen/client.d.ts +150 -0
- package/dist/plugins/vipunen/client.js +535 -0
- package/dist/plugins/vipunen/config/schema-config.json +19 -0
- package/dist/plugins/vipunen/config/schema-doc.json +22 -0
- package/dist/plugins/vipunen/index.d.ts +41 -0
- package/dist/plugins/vipunen/index.js +88 -0
- package/dist/plugins/vipunen/tools.d.ts +26 -0
- package/dist/plugins/vipunen/tools.js +501 -0
- package/dist/stdio-server.d.ts +14 -0
- package/dist/stdio-server.js +101 -0
- package/package.json +2 -1
- package/.claude/agents/agent-ada-skill-builder.md +0 -94
- package/.claude/agents/agent-alejandro-function-fields.md +0 -342
- package/.claude/agents/agent-bjorn-config-audit.md +0 -103
- package/.claude/agents/agent-builder-agent-creator.md +0 -130
- package/.claude/agents/agent-code-simplifier.md +0 -53
- package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
- package/.claude/agents/agent-giuseppe-app-builder.md +0 -247
- package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
- package/.claude/agents/agent-helga-workflow-config.md +0 -204
- package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
- package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
- package/.claude/agents/agent-ivan-monolith.md +0 -154
- package/.claude/agents/agent-kenji-data-reader.md +0 -86
- package/.claude/agents/agent-lars-code-inspector.md +0 -102
- package/.claude/agents/agent-marco-mockup-builder.md +0 -110
- package/.claude/agents/agent-marcus-api-documenter.md +0 -323
- package/.claude/agents/agent-marketplace-publisher.md +0 -280
- package/.claude/agents/agent-marketplace-reviewer.md +0 -309
- package/.claude/agents/agent-permissions-handler.md +0 -208
- package/.claude/agents/agent-simple-writer.md +0 -48
- package/.claude/agents/agent-svetlana-code-review.md +0 -171
- package/.claude/agents/agent-tanya-test-runner.md +0 -333
- package/.claude/agents/agent-ui-designer.md +0 -100
- package/.claude/agents/agent-viktor-sql-insights.md +0 -212
- package/.claude/agents/agent-web-search.md +0 -55
- package/.claude/agents/agent-yevgeni-discussions.md +0 -45
- package/.claude/agents/agent-zara-zapier.md +0 -159
- package/.claude/commands/app-squad.md +0 -135
- package/.claude/commands/audit-squad.md +0 -158
- package/.claude/commands/autoplan.md +0 -563
- package/.claude/commands/cleanup-squad.md +0 -98
- package/.claude/commands/config-squad.md +0 -106
- package/.claude/commands/crud-squad.md +0 -87
- package/.claude/commands/data-squad.md +0 -97
- package/.claude/commands/debug-squad.md +0 -303
- package/.claude/commands/doc-squad.md +0 -65
- package/.claude/commands/handoff.md +0 -137
- package/.claude/commands/health.md +0 -49
- package/.claude/commands/help.md +0 -29
- package/.claude/commands/help:agents.md +0 -151
- package/.claude/commands/help:commands.md +0 -78
- package/.claude/commands/help:faq.md +0 -79
- package/.claude/commands/help:plugins.md +0 -50
- package/.claude/commands/help:skills.md +0 -93
- package/.claude/commands/help:tools.md +0 -75
- package/.claude/commands/hotfix-squad.md +0 -112
- package/.claude/commands/integration-squad.md +0 -82
- package/.claude/commands/janitor-squad.md +0 -167
- package/.claude/commands/learn-auto.md +0 -120
- package/.claude/commands/learn.md +0 -120
- package/.claude/commands/mcp-list.md +0 -27
- package/.claude/commands/onboard-squad.md +0 -140
- package/.claude/commands/plan-workspace.md +0 -732
- package/.claude/commands/prd.md +0 -130
- package/.claude/commands/project-status.md +0 -82
- package/.claude/commands/publish.md +0 -138
- package/.claude/commands/recap.md +0 -69
- package/.claude/commands/restore.md +0 -64
- package/.claude/commands/review-squad.md +0 -152
- package/.claude/commands/save.md +0 -24
- package/.claude/commands/stats.md +0 -19
- package/.claude/commands/swarm.md +0 -210
- package/.claude/commands/tool-builder.md +0 -39
- package/.claude/commands/ws-pull.md +0 -44
- package/.claude/hooks/_shared-memory.cjs +0 -305
- package/.claude/hooks/_utils.cjs +0 -108
- package/.claude/hooks/agent-failure-detector.cjs +0 -383
- package/.claude/hooks/agent-usage-logger.cjs +0 -204
- package/.claude/hooks/app-edit-guard.cjs +0 -494
- package/.claude/hooks/auto-learn.cjs +0 -304
- package/.claude/hooks/bash-guard.cjs +0 -272
- package/.claude/hooks/builder-mode-manager.cjs +0 -354
- package/.claude/hooks/bulk-activity-guard.cjs +0 -271
- package/.claude/hooks/context-watchdog.cjs +0 -230
- package/.claude/hooks/delegation-reminder.cjs +0 -465
- package/.claude/hooks/design-system-lint.cjs +0 -271
- package/.claude/hooks/post-scaffold-hook.cjs +0 -181
- package/.claude/hooks/prompt-guard.cjs +0 -354
- package/.claude/hooks/publish-template-guard.cjs +0 -147
- package/.claude/hooks/session-start.cjs +0 -35
- package/.claude/hooks/shared-memory-writer.cjs +0 -147
- package/.claude/hooks/skill-injector.cjs +0 -140
- package/.claude/hooks/skill-usage-logger.cjs +0 -258
- package/.claude/hooks/src-edit-guard.cjs +0 -240
- package/.claude/hooks/sync-marketplace-agents.cjs +0 -346
- package/.claude/settings.json +0 -257
- package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
- package/.claude/skills/SDK-document-templates/SKILL.md +0 -1033
- package/.claude/skills/SDK-function-fields/SKILL.md +0 -542
- package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
- package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
- package/.claude/skills/SDK-insight-queries/SKILL.md +0 -787
- package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -1139
- package/.claude/skills/agent-structure/SKILL.md +0 -98
- package/.claude/skills/api-documentation-patterns/SKILL.md +0 -474
- package/.claude/skills/chrome-mcp-reference/SKILL.md +0 -370
- package/.claude/skills/delegation-routing/SKILL.md +0 -202
- package/.claude/skills/frontend-design/SKILL.md +0 -254
- package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
- package/.claude/skills/hailer-api-client/SKILL.md +0 -518
- package/.claude/skills/hailer-app-builder/SKILL.md +0 -1434
- package/.claude/skills/hailer-apps-pictures/SKILL.md +0 -269
- package/.claude/skills/hailer-design-system/SKILL.md +0 -235
- package/.claude/skills/hailer-monolith-automations/SKILL.md +0 -686
- package/.claude/skills/hailer-permissions-system/SKILL.md +0 -121
- package/.claude/skills/hailer-project-protocol/SKILL.md +0 -488
- package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
- package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
- package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
- package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
- package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
- package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
- package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
- package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
- package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
- package/.claude/skills/integration-patterns/SKILL.md +0 -421
- package/.claude/skills/json-only-output/SKILL.md +0 -72
- package/.claude/skills/lsp-setup/SKILL.md +0 -160
- package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
- package/.claude/skills/optional-parameters/SKILL.md +0 -72
- package/.claude/skills/publish-hailer-app/SKILL.md +0 -244
- package/.claude/skills/testing-patterns/SKILL.md +0 -630
- package/.claude/skills/tool-builder/SKILL.md +0 -250
- package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
- package/.claude/skills/tool-response-verification/SKILL.md +0 -92
- package/.claude/skills/zapier-hailer-patterns/SKILL.md +0 -581
- package/.mcp.json +0 -13
- package/.opencode/agent/agent-ada-skill-builder.md +0 -35
- package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
- package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
- package/.opencode/agent/agent-builder-agent-creator.md +0 -39
- package/.opencode/agent/agent-code-simplifier.md +0 -31
- package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
- package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
- package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
- package/.opencode/agent/agent-helga-workflow-config.md +0 -203
- package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
- package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
- package/.opencode/agent/agent-ivan-monolith.md +0 -46
- package/.opencode/agent/agent-kenji-data-reader.md +0 -53
- package/.opencode/agent/agent-lars-code-inspector.md +0 -28
- package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
- package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
- package/.opencode/agent/agent-marketplace-publisher.md +0 -44
- package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
- package/.opencode/agent/agent-permissions-handler.md +0 -50
- package/.opencode/agent/agent-simple-writer.md +0 -45
- package/.opencode/agent/agent-svetlana-code-review.md +0 -39
- package/.opencode/agent/agent-tanya-test-runner.md +0 -57
- package/.opencode/agent/agent-ui-designer.md +0 -56
- package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
- package/.opencode/agent/agent-web-search.md +0 -42
- package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
- package/.opencode/agent/agent-zara-zapier.md +0 -53
- package/.opencode/commands/app-squad.md +0 -135
- package/.opencode/commands/audit-squad.md +0 -158
- package/.opencode/commands/autoplan.md +0 -563
- package/.opencode/commands/cleanup-squad.md +0 -98
- package/.opencode/commands/config-squad.md +0 -106
- package/.opencode/commands/crud-squad.md +0 -87
- package/.opencode/commands/data-squad.md +0 -97
- package/.opencode/commands/debug-squad.md +0 -303
- package/.opencode/commands/doc-squad.md +0 -65
- package/.opencode/commands/handoff.md +0 -137
- package/.opencode/commands/health.md +0 -49
- package/.opencode/commands/help-agents.md +0 -151
- package/.opencode/commands/help-commands.md +0 -32
- package/.opencode/commands/help-faq.md +0 -29
- package/.opencode/commands/help-plugins.md +0 -28
- package/.opencode/commands/help-skills.md +0 -7
- package/.opencode/commands/help-tools.md +0 -40
- package/.opencode/commands/help.md +0 -28
- package/.opencode/commands/hotfix-squad.md +0 -112
- package/.opencode/commands/integration-squad.md +0 -82
- package/.opencode/commands/janitor-squad.md +0 -167
- package/.opencode/commands/learn-auto.md +0 -120
- package/.opencode/commands/learn.md +0 -120
- package/.opencode/commands/mcp-list.md +0 -27
- package/.opencode/commands/onboard-squad.md +0 -140
- package/.opencode/commands/plan-workspace.md +0 -732
- package/.opencode/commands/prd.md +0 -131
- package/.opencode/commands/project-status.md +0 -82
- package/.opencode/commands/publish.md +0 -138
- package/.opencode/commands/recap.md +0 -69
- package/.opencode/commands/restore.md +0 -64
- package/.opencode/commands/review-squad.md +0 -152
- package/.opencode/commands/save.md +0 -24
- package/.opencode/commands/stats.md +0 -19
- package/.opencode/commands/swarm.md +0 -210
- package/.opencode/commands/tool-builder.md +0 -39
- package/.opencode/commands/ws-pull.md +0 -44
- package/.opencode/opencode.json +0 -28
- package/SESSION-HANDOFF.md +0 -68
- package/inbox/2026-03-04-bot-config-patterns.md +0 -24
- package/scripts/postinstall.cjs +0 -64
- package/scripts/test-hal-tools.ts +0 -154
|
@@ -1,787 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: SDK-insight-queries
|
|
3
|
-
description: SQL query patterns for Hailer insights - aggregations, filtering, dates, CASE statements
|
|
4
|
-
version: 1.4.1
|
|
5
|
-
triggers: Insight queries, GROUP BY, COUNT, SUM, date formatting, CASE WHEN
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Insight Query Patterns
|
|
9
|
-
|
|
10
|
-
<critical-rules>
|
|
11
|
-
## ⚠️ TIMESTAMPS IN INSIGHTS ARE SECONDS (NOT MILLISECONDS!)
|
|
12
|
-
|
|
13
|
-
**IMPORTANT:** While the Hailer API uses milliseconds, **insights expose datetime fields as SECONDS** (10-digit numbers like `1768291200`).
|
|
14
|
-
|
|
15
|
-
**For date comparisons in WHERE clauses:**
|
|
16
|
-
```sql
|
|
17
|
-
-- CORRECT (comparing seconds to seconds)
|
|
18
|
-
WHERE ajankohtaEnd > strftime('%s', 'now')
|
|
19
|
-
|
|
20
|
-
-- WRONG (comparing seconds to milliseconds - will fail!)
|
|
21
|
-
WHERE ajankohtaEnd > strftime('%s', 'now') * 1000
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**For formatting dates with strftime:**
|
|
25
|
-
```sql
|
|
26
|
-
-- CORRECT (field is already in seconds)
|
|
27
|
-
strftime('%Y-%m-%d', dateField, 'unixepoch')
|
|
28
|
-
|
|
29
|
-
-- WRONG (double-dividing - will show wrong dates)
|
|
30
|
-
strftime('%Y-%m-%d', dateField / 1000, 'unixepoch')
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**Quick rules for insights:**
|
|
34
|
-
- `dateField` → already in seconds, use directly with strftime
|
|
35
|
-
- `strftime('%s', 'now')` → returns seconds, compare directly with dateField
|
|
36
|
-
- Do NOT multiply or divide by 1000!
|
|
37
|
-
|
|
38
|
-
**Test before deploying:** Create a diagnostic query to verify:
|
|
39
|
-
```sql
|
|
40
|
-
SELECT dateField, strftime('%s', 'now') as now_sec,
|
|
41
|
-
(dateField > strftime('%s', 'now')) as is_future
|
|
42
|
-
FROM source LIMIT 5
|
|
43
|
-
```
|
|
44
|
-
</critical-rules>
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## Production Insight Guidelines
|
|
49
|
-
|
|
50
|
-
**If an insight is used by an app, document it:**
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
{
|
|
54
|
-
_id: "...",
|
|
55
|
-
name: "Tilavaraukset - Public", // Add (APP) suffix if used by app
|
|
56
|
-
// Or: name: "Tilavaraukset - Public (tilavaraus-app)",
|
|
57
|
-
query: `SELECT /* Used by tilavaraus-app - do not change field names! */
|
|
58
|
-
id, name, varattu_tila, ajankohtaStart, ajankohtaEnd
|
|
59
|
-
FROM varaukset
|
|
60
|
-
WHERE phaseId = '...'`
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
**Why:** Prevents breaking production apps when modifying insights.
|
|
65
|
-
|
|
66
|
-
**Before modifying production insights:**
|
|
67
|
-
1. Create a TEST duplicate with same sources
|
|
68
|
-
2. Test changes on the duplicate
|
|
69
|
-
3. Apply working changes to production
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## Query Basics
|
|
74
|
-
|
|
75
|
-
Hailer insights use **SQLite syntax**. Queries run over sources you define.
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
{
|
|
79
|
-
sources: [
|
|
80
|
-
{
|
|
81
|
-
workflowId: WorkflowIds.tasks_abc,
|
|
82
|
-
name: 't', // Alias for queries
|
|
83
|
-
fields: [
|
|
84
|
-
{ name: 'title', meta: 'name' },
|
|
85
|
-
{ name: 'id', meta: '_id' },
|
|
86
|
-
{ name: 'priority', fieldId: Tasks_FieldIds.priority_def }
|
|
87
|
-
]
|
|
88
|
-
}
|
|
89
|
-
],
|
|
90
|
-
query: 'SELECT title, priority FROM t WHERE priority = "High"'
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Source Field Naming Convention
|
|
95
|
-
|
|
96
|
-
**Use real field names** in source definitions, not generic names:
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
// GOOD - Real field names, matches Hailer UI
|
|
100
|
-
fields: [
|
|
101
|
-
{ name: 'nro', fieldId: FieldIds.invoiceNumber },
|
|
102
|
-
{ name: 'pvm', fieldId: FieldIds.invoiceDate },
|
|
103
|
-
{ name: 'asiakas', fieldId: FieldIds.customer }
|
|
104
|
-
]
|
|
105
|
-
// Query: SELECT nro, pvm, asiakas FROM invoices
|
|
106
|
-
|
|
107
|
-
// BAD - Generic names, confusing
|
|
108
|
-
fields: [
|
|
109
|
-
{ name: 'activityId', fieldId: FieldIds.invoiceNumber },
|
|
110
|
-
{ name: 'date', fieldId: FieldIds.invoiceDate },
|
|
111
|
-
{ name: 'link', fieldId: FieldIds.customer }
|
|
112
|
-
]
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
Benefits of real names:
|
|
116
|
-
- Queries are readable and self-documenting
|
|
117
|
-
- Field names match Hailer UI labels
|
|
118
|
-
- Easier debugging and maintenance
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## Available Meta Fields
|
|
123
|
-
|
|
124
|
-
| Meta | Description | Example |
|
|
125
|
-
|------|-------------|---------|
|
|
126
|
-
| `_id` | Activity ID | Required for JOINs |
|
|
127
|
-
| `name` | Activity name | Display name |
|
|
128
|
-
| `created` | Created timestamp | Unix seconds |
|
|
129
|
-
| `updated` | Updated timestamp | Unix seconds |
|
|
130
|
-
| `phaseId` | Current phase ID | For phase filtering |
|
|
131
|
-
| `phaseName` | Current phase name | Human readable |
|
|
132
|
-
| `workflowId` | Workflow ID | |
|
|
133
|
-
| `workflowName` | Workflow name | |
|
|
134
|
-
| `createdBy` | Creator user ID | |
|
|
135
|
-
| `uid` | User ID | |
|
|
136
|
-
| `team` | Team ID | |
|
|
137
|
-
| `priority` | Priority value | |
|
|
138
|
-
| `phaseLastMove` | Last phase change | Unix seconds |
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## Aggregations
|
|
143
|
-
|
|
144
|
-
### COUNT
|
|
145
|
-
```sql
|
|
146
|
-
SELECT
|
|
147
|
-
phaseName,
|
|
148
|
-
COUNT(*) as count
|
|
149
|
-
FROM tasks
|
|
150
|
-
GROUP BY phaseName
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### SUM
|
|
154
|
-
```sql
|
|
155
|
-
SELECT
|
|
156
|
-
projectName,
|
|
157
|
-
SUM(hours) as total_hours
|
|
158
|
-
FROM timesheets
|
|
159
|
-
GROUP BY projectName
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### AVG, MIN, MAX
|
|
163
|
-
```sql
|
|
164
|
-
SELECT
|
|
165
|
-
AVG(amount) as avg_amount,
|
|
166
|
-
MIN(amount) as min_amount,
|
|
167
|
-
MAX(amount) as max_amount
|
|
168
|
-
FROM orders
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### COUNT with DISTINCT
|
|
172
|
-
```sql
|
|
173
|
-
SELECT
|
|
174
|
-
COUNT(DISTINCT customerId) as unique_customers
|
|
175
|
-
FROM orders
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## Filtering
|
|
181
|
-
|
|
182
|
-
### WHERE Clauses
|
|
183
|
-
```sql
|
|
184
|
-
-- Text match
|
|
185
|
-
WHERE priority = "High"
|
|
186
|
-
|
|
187
|
-
-- Numeric comparison
|
|
188
|
-
WHERE amount > 1000
|
|
189
|
-
|
|
190
|
-
-- NULL check
|
|
191
|
-
WHERE assignee IS NOT NULL
|
|
192
|
-
|
|
193
|
-
-- Multiple conditions
|
|
194
|
-
WHERE priority = "High" AND status != "Done"
|
|
195
|
-
|
|
196
|
-
-- IN list
|
|
197
|
-
WHERE phaseName IN ("Todo", "In Progress")
|
|
198
|
-
|
|
199
|
-
-- LIKE pattern
|
|
200
|
-
WHERE title LIKE "%urgent%"
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### HAVING (filter after GROUP BY)
|
|
204
|
-
```sql
|
|
205
|
-
SELECT
|
|
206
|
-
customerId,
|
|
207
|
-
COUNT(*) as order_count
|
|
208
|
-
FROM orders
|
|
209
|
-
GROUP BY customerId
|
|
210
|
-
HAVING COUNT(*) > 5
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## Date Handling
|
|
216
|
-
|
|
217
|
-
**CRITICAL:** In Hailer insights, all date/time fields are exposed as **Unix timestamps in SECONDS** (10-digit numbers like `1770050954`).
|
|
218
|
-
|
|
219
|
-
This is different from the Hailer API which uses milliseconds! The insight layer converts automatically.
|
|
220
|
-
|
|
221
|
-
```sql
|
|
222
|
-
-- CORRECT - field is already in seconds, use directly
|
|
223
|
-
strftime('%Y-%m-%d', dateField, 'unixepoch') AS date_formatted
|
|
224
|
-
|
|
225
|
-
-- WRONG - do NOT divide by 1000 (will show wrong dates!)
|
|
226
|
-
strftime('%Y-%m-%d', dateField / 1000, 'unixepoch') AS date_formatted
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### Range Fields (daterange, datetimerange, timerange)
|
|
230
|
-
|
|
231
|
-
For range field types, Hailer automatically exposes **Start** and **End** suffixes.
|
|
232
|
-
|
|
233
|
-
| Field Type | In Insight SQL |
|
|
234
|
-
|------------|----------------|
|
|
235
|
-
| `date` | Seconds (10-digit) |
|
|
236
|
-
| `datetime` | Seconds (10-digit) |
|
|
237
|
-
| `time` | Seconds (10-digit) |
|
|
238
|
-
| `daterange` | Start/End in seconds |
|
|
239
|
-
| `datetimerange` | Start/End in seconds |
|
|
240
|
-
| `timerange` | Start/End in seconds |
|
|
241
|
-
|
|
242
|
-
**Note:** `timerange` stores full timestamps, not just time. To extract just the time portion, use SQLite time functions.
|
|
243
|
-
|
|
244
|
-
**In sources:** Use just the field name:
|
|
245
|
-
```typescript
|
|
246
|
-
fields: [
|
|
247
|
-
{ name: 'ajankohta', fieldId: Event_FieldIds.ajankohta_abc }, // daterange/datetimerange
|
|
248
|
-
{ name: 'tyoaika', fieldId: Event_FieldIds.tyoaika_def } // timerange
|
|
249
|
-
]
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
**In queries:** Access with Start/End suffixes (already in seconds):
|
|
253
|
-
```sql
|
|
254
|
-
SELECT
|
|
255
|
-
-- All fields are already in seconds - use directly with strftime
|
|
256
|
-
ajankohtaStart,
|
|
257
|
-
ajankohtaEnd,
|
|
258
|
-
strftime('%d.%m.%Y', ajankohtaStart, 'unixepoch') as start_date,
|
|
259
|
-
strftime('%d.%m.%Y', ajankohtaEnd, 'unixepoch') as end_date,
|
|
260
|
-
|
|
261
|
-
-- Timerange: extract just the time portion
|
|
262
|
-
tyoaikaStart,
|
|
263
|
-
tyoaikaEnd,
|
|
264
|
-
strftime('%H:%M', tyoaikaStart, 'unixepoch') as start_time,
|
|
265
|
-
strftime('%H:%M', tyoaikaEnd, 'unixepoch') as end_time,
|
|
266
|
-
|
|
267
|
-
-- Duration in seconds (both are already seconds)
|
|
268
|
-
(tyoaikaEnd - tyoaikaStart) as duration_seconds,
|
|
269
|
-
(tyoaikaEnd - tyoaikaStart) / 60 as duration_minutes
|
|
270
|
-
FROM events
|
|
271
|
-
WHERE ajankohtaStart >= strftime('%s', 'now')
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
**Naming convention:** `{fieldName}Start` and `{fieldName}End` are auto-generated.
|
|
275
|
-
|
|
276
|
-
**IMPORTANT: When to convert vs keep raw:**
|
|
277
|
-
- **Display only** (human-readable reports) → Use `strftime()` to format
|
|
278
|
-
- **Data for Hailer apps** → App receives seconds from insight, may need to multiply by 1000 for API calls
|
|
279
|
-
- **Comparisons** → Compare directly with `strftime('%s', ...)` which also returns seconds
|
|
280
|
-
|
|
281
|
-
### Format Date (Finnish)
|
|
282
|
-
```sql
|
|
283
|
-
strftime('%d.%m.%Y', dateField, 'unixepoch') AS date_formatted
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
### Format DateTime
|
|
287
|
-
```sql
|
|
288
|
-
strftime('%d.%m.%Y %H:%M', created, 'unixepoch') AS datetime_formatted
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### Timezone Handling (Finland: +2/+3)
|
|
292
|
-
```sql
|
|
293
|
-
strftime(
|
|
294
|
-
'%d.%m.%Y %H:%M',
|
|
295
|
-
created,
|
|
296
|
-
'unixepoch',
|
|
297
|
-
CASE
|
|
298
|
-
WHEN created >= (
|
|
299
|
-
SELECT strftime('%s', date(strftime('%Y', created, 'unixepoch') || '-03-31', 'weekday 0', '-7 days', '03:00'))
|
|
300
|
-
)
|
|
301
|
-
AND created < (
|
|
302
|
-
SELECT strftime('%s', date(strftime('%Y', created, 'unixepoch') || '-10-31', 'weekday 0', '-7 days', '04:00'))
|
|
303
|
-
)
|
|
304
|
-
THEN '+3 hours'
|
|
305
|
-
ELSE '+2 hours'
|
|
306
|
-
END
|
|
307
|
-
) AS local_time
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### Filter by Date Range
|
|
311
|
-
```sql
|
|
312
|
-
-- Last 30 days (both sides are seconds)
|
|
313
|
-
WHERE dateField >= strftime('%s', 'now', '-30 days')
|
|
314
|
-
|
|
315
|
-
-- This year
|
|
316
|
-
WHERE strftime('%Y', dateField, 'unixepoch') = strftime('%Y', 'now')
|
|
317
|
-
|
|
318
|
-
-- Specific month
|
|
319
|
-
WHERE strftime('%Y-%m', dateField, 'unixepoch') = '2024-01'
|
|
320
|
-
|
|
321
|
-
-- Between two dates
|
|
322
|
-
WHERE dateField >= strftime('%s', '2024-01-01')
|
|
323
|
-
AND dateField < strftime('%s', '2024-02-01')
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
### Group by Month
|
|
327
|
-
```sql
|
|
328
|
-
SELECT
|
|
329
|
-
strftime('%Y-%m', created, 'unixepoch') as month,
|
|
330
|
-
COUNT(*) as count
|
|
331
|
-
FROM orders
|
|
332
|
-
GROUP BY strftime('%Y-%m', created, 'unixepoch')
|
|
333
|
-
ORDER BY month
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
---
|
|
337
|
-
|
|
338
|
-
## CASE Statements
|
|
339
|
-
|
|
340
|
-
### Simple CASE
|
|
341
|
-
```sql
|
|
342
|
-
SELECT
|
|
343
|
-
title,
|
|
344
|
-
CASE priority
|
|
345
|
-
WHEN 'High' THEN '🔴'
|
|
346
|
-
WHEN 'Medium' THEN '🟡'
|
|
347
|
-
WHEN 'Low' THEN '🟢'
|
|
348
|
-
ELSE '⚪'
|
|
349
|
-
END as priority_icon
|
|
350
|
-
FROM tasks
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
### Searched CASE
|
|
354
|
-
```sql
|
|
355
|
-
SELECT
|
|
356
|
-
title,
|
|
357
|
-
CASE
|
|
358
|
-
WHEN amount > 10000 THEN 'Large'
|
|
359
|
-
WHEN amount > 1000 THEN 'Medium'
|
|
360
|
-
ELSE 'Small'
|
|
361
|
-
END as size_category
|
|
362
|
-
FROM orders
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
### CASE in Aggregation
|
|
366
|
-
```sql
|
|
367
|
-
SELECT
|
|
368
|
-
SUM(CASE WHEN phaseName = 'Done' THEN 1 ELSE 0 END) as completed,
|
|
369
|
-
SUM(CASE WHEN phaseName != 'Done' THEN 1 ELSE 0 END) as pending,
|
|
370
|
-
COUNT(*) as total
|
|
371
|
-
FROM tasks
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
---
|
|
375
|
-
|
|
376
|
-
## JOINs
|
|
377
|
-
|
|
378
|
-
### LEFT JOIN (recommended)
|
|
379
|
-
```sql
|
|
380
|
-
SELECT
|
|
381
|
-
t.title,
|
|
382
|
-
p.name as project_name
|
|
383
|
-
FROM tasks t
|
|
384
|
-
LEFT JOIN projects p ON t.projectId = p.id
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
**Requirements:**
|
|
388
|
-
- Both sources need `{ name: 'id', meta: '_id' }`
|
|
389
|
-
- Join on activitylink field = target id
|
|
390
|
-
|
|
391
|
-
### Multiple JOINs
|
|
392
|
-
```sql
|
|
393
|
-
SELECT
|
|
394
|
-
t.title,
|
|
395
|
-
p.name as project_name,
|
|
396
|
-
c.name as customer_name
|
|
397
|
-
FROM tasks t
|
|
398
|
-
LEFT JOIN projects p ON t.projectId = p.id
|
|
399
|
-
LEFT JOIN customers c ON p.customerId = c.id
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
### User ID → Username Conversion
|
|
403
|
-
|
|
404
|
-
**IMPORTANT:** The `user` table is a **pseudo-table** in Hailer insights. You don't need to declare it as a source - just JOIN directly.
|
|
405
|
-
|
|
406
|
-
```sql
|
|
407
|
-
-- Convert user ID fields to display names
|
|
408
|
-
SELECT
|
|
409
|
-
t.title,
|
|
410
|
-
COALESCE(u.name, '') AS Assignee_Name,
|
|
411
|
-
COALESCE(m.name, '') AS Manager_Name
|
|
412
|
-
FROM tasks t
|
|
413
|
-
LEFT JOIN user AS u ON u._id = t.assignee
|
|
414
|
-
LEFT JOIN user AS m ON m._id = t.manager
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
**Pattern for multiple user fields:**
|
|
418
|
-
```sql
|
|
419
|
-
-- Each user field needs its own JOIN with unique alias
|
|
420
|
-
LEFT JOIN user AS em ON em._id = t.event_manager
|
|
421
|
-
LEFT JOIN user AS pm ON pm._id = t.production_manager
|
|
422
|
-
LEFT JOIN user AS tm ON tm._id = t.ticketing_manager
|
|
423
|
-
|
|
424
|
-
-- Then select with COALESCE for clean output
|
|
425
|
-
COALESCE(em.name, '') AS Event_Manager,
|
|
426
|
-
COALESCE(pm.name, '') AS Production_Manager,
|
|
427
|
-
COALESCE(tm.name, '') AS Ticketing_Manager
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
**Why COALESCE?** User fields can be empty - COALESCE returns empty string instead of NULL for cleaner display.
|
|
431
|
-
|
|
432
|
-
**Common mistake:** Trying to add `user` as a source - don't do this! It's built-in.
|
|
433
|
-
|
|
434
|
-
See `insight-join-patterns` skill for detailed JOIN patterns.
|
|
435
|
-
|
|
436
|
-
---
|
|
437
|
-
|
|
438
|
-
## Sorting & Limiting
|
|
439
|
-
|
|
440
|
-
### ORDER BY
|
|
441
|
-
```sql
|
|
442
|
-
SELECT * FROM tasks
|
|
443
|
-
ORDER BY created DESC
|
|
444
|
-
|
|
445
|
-
-- Multiple columns
|
|
446
|
-
ORDER BY priority ASC, created DESC
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
### LIMIT
|
|
450
|
-
```sql
|
|
451
|
-
SELECT * FROM tasks
|
|
452
|
-
ORDER BY created DESC
|
|
453
|
-
LIMIT 10
|
|
454
|
-
|
|
455
|
-
-- With offset (pagination)
|
|
456
|
-
LIMIT 10 OFFSET 20
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
---
|
|
460
|
-
|
|
461
|
-
## String Functions
|
|
462
|
-
|
|
463
|
-
```sql
|
|
464
|
-
-- Concatenate
|
|
465
|
-
SELECT title || ' - ' || phaseName as combined FROM tasks
|
|
466
|
-
|
|
467
|
-
-- Upper/Lower
|
|
468
|
-
SELECT UPPER(title), LOWER(status) FROM tasks
|
|
469
|
-
|
|
470
|
-
-- Substring
|
|
471
|
-
SELECT SUBSTR(title, 1, 50) as short_title FROM tasks
|
|
472
|
-
|
|
473
|
-
-- Replace
|
|
474
|
-
SELECT REPLACE(title, 'OLD', 'NEW') FROM tasks
|
|
475
|
-
|
|
476
|
-
-- Trim
|
|
477
|
-
SELECT TRIM(title) FROM tasks
|
|
478
|
-
|
|
479
|
-
-- Length
|
|
480
|
-
SELECT title, LENGTH(title) as title_length FROM tasks
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
---
|
|
484
|
-
|
|
485
|
-
## NULL Handling
|
|
486
|
-
|
|
487
|
-
```sql
|
|
488
|
-
-- COALESCE (first non-null)
|
|
489
|
-
SELECT COALESCE(assignee, 'Unassigned') as assignee FROM tasks
|
|
490
|
-
|
|
491
|
-
-- IFNULL (SQLite specific)
|
|
492
|
-
SELECT IFNULL(amount, 0) as amount FROM orders
|
|
493
|
-
|
|
494
|
-
-- NULLIF (return NULL if equal)
|
|
495
|
-
SELECT NULLIF(status, 'None') as status FROM tasks
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
---
|
|
499
|
-
|
|
500
|
-
## Subqueries
|
|
501
|
-
|
|
502
|
-
```sql
|
|
503
|
-
-- In WHERE
|
|
504
|
-
SELECT * FROM tasks
|
|
505
|
-
WHERE projectId IN (
|
|
506
|
-
SELECT id FROM projects WHERE status = 'Active'
|
|
507
|
-
)
|
|
508
|
-
|
|
509
|
-
-- As column
|
|
510
|
-
SELECT
|
|
511
|
-
title,
|
|
512
|
-
(SELECT COUNT(*) FROM subtasks s WHERE s.parentId = t.id) as subtask_count
|
|
513
|
-
FROM tasks t
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
---
|
|
517
|
-
|
|
518
|
-
## Common Patterns
|
|
519
|
-
|
|
520
|
-
### Count by Phase
|
|
521
|
-
```sql
|
|
522
|
-
SELECT
|
|
523
|
-
phaseName,
|
|
524
|
-
COUNT(*) as count
|
|
525
|
-
FROM tasks
|
|
526
|
-
GROUP BY phaseName
|
|
527
|
-
ORDER BY count DESC
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
### Sum by Customer
|
|
531
|
-
```sql
|
|
532
|
-
SELECT
|
|
533
|
-
c.name as customer,
|
|
534
|
-
SUM(o.amount) as total
|
|
535
|
-
FROM orders o
|
|
536
|
-
LEFT JOIN customers c ON o.customerId = c.id
|
|
537
|
-
GROUP BY c.name
|
|
538
|
-
ORDER BY total DESC
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
### Monthly Trend
|
|
542
|
-
```sql
|
|
543
|
-
SELECT
|
|
544
|
-
strftime('%Y-%m', created, 'unixepoch') as month,
|
|
545
|
-
COUNT(*) as count,
|
|
546
|
-
SUM(amount) as total
|
|
547
|
-
FROM orders
|
|
548
|
-
WHERE created > strftime('%s', 'now', '-12 months')
|
|
549
|
-
GROUP BY month
|
|
550
|
-
ORDER BY month
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
### Completion Rate
|
|
554
|
-
```sql
|
|
555
|
-
SELECT
|
|
556
|
-
ROUND(
|
|
557
|
-
100.0 * SUM(CASE WHEN phaseName = 'Done' THEN 1 ELSE 0 END) / COUNT(*),
|
|
558
|
-
1
|
|
559
|
-
) as completion_percent
|
|
560
|
-
FROM tasks
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
---
|
|
564
|
-
|
|
565
|
-
## Advanced Patterns
|
|
566
|
-
|
|
567
|
-
### Window Functions
|
|
568
|
-
|
|
569
|
-
Window functions perform calculations across rows without collapsing them (unlike GROUP BY).
|
|
570
|
-
|
|
571
|
-
```sql
|
|
572
|
-
-- Row numbers (ranking)
|
|
573
|
-
SELECT
|
|
574
|
-
name,
|
|
575
|
-
amount,
|
|
576
|
-
ROW_NUMBER() OVER (ORDER BY amount DESC) as rank
|
|
577
|
-
FROM orders
|
|
578
|
-
|
|
579
|
-
-- Running total
|
|
580
|
-
SELECT
|
|
581
|
-
name,
|
|
582
|
-
amount,
|
|
583
|
-
SUM(amount) OVER (ORDER BY created) as running_total
|
|
584
|
-
FROM orders
|
|
585
|
-
|
|
586
|
-
-- Rank within groups (top 3 per customer)
|
|
587
|
-
SELECT * FROM (
|
|
588
|
-
SELECT
|
|
589
|
-
customerName,
|
|
590
|
-
name,
|
|
591
|
-
amount,
|
|
592
|
-
ROW_NUMBER() OVER (PARTITION BY customerName ORDER BY amount DESC) as rank
|
|
593
|
-
FROM orders
|
|
594
|
-
)
|
|
595
|
-
WHERE rank <= 3
|
|
596
|
-
|
|
597
|
-
-- Compare to previous row
|
|
598
|
-
SELECT
|
|
599
|
-
name,
|
|
600
|
-
amount,
|
|
601
|
-
LAG(amount) OVER (ORDER BY created) as previous_amount,
|
|
602
|
-
amount - LAG(amount) OVER (ORDER BY created) as change
|
|
603
|
-
FROM orders
|
|
604
|
-
|
|
605
|
-
-- Percentage of total
|
|
606
|
-
SELECT
|
|
607
|
-
phaseName,
|
|
608
|
-
COUNT(*) as count,
|
|
609
|
-
ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 1) as percent
|
|
610
|
-
FROM tasks
|
|
611
|
-
GROUP BY phaseName
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
**Available window functions:**
|
|
615
|
-
- `ROW_NUMBER()`, `RANK()`, `DENSE_RANK()` - rankings
|
|
616
|
-
- `LAG(col)`, `LEAD(col)` - previous/next row values
|
|
617
|
-
- `SUM() OVER`, `COUNT() OVER`, `AVG() OVER` - running aggregates
|
|
618
|
-
- `FIRST_VALUE()`, `LAST_VALUE()` - boundary values
|
|
619
|
-
- `NTILE(n)` - divide into n buckets
|
|
620
|
-
|
|
621
|
-
### CTEs (WITH Clauses)
|
|
622
|
-
|
|
623
|
-
CTEs make complex queries readable by breaking them into named steps.
|
|
624
|
-
|
|
625
|
-
```sql
|
|
626
|
-
-- Simple CTE
|
|
627
|
-
WITH active_orders AS (
|
|
628
|
-
SELECT * FROM orders
|
|
629
|
-
WHERE phaseName != 'Cancelled'
|
|
630
|
-
)
|
|
631
|
-
SELECT customerName, SUM(amount) as total
|
|
632
|
-
FROM active_orders
|
|
633
|
-
GROUP BY customerName
|
|
634
|
-
|
|
635
|
-
-- Multiple CTEs
|
|
636
|
-
WITH
|
|
637
|
-
monthly_sales AS (
|
|
638
|
-
SELECT
|
|
639
|
-
strftime('%Y-%m', created, 'unixepoch') as month,
|
|
640
|
-
SUM(amount) as total
|
|
641
|
-
FROM orders
|
|
642
|
-
GROUP BY month
|
|
643
|
-
),
|
|
644
|
-
monthly_avg AS (
|
|
645
|
-
SELECT AVG(total) as avg_total FROM monthly_sales
|
|
646
|
-
)
|
|
647
|
-
SELECT
|
|
648
|
-
ms.month,
|
|
649
|
-
ms.total,
|
|
650
|
-
ma.avg_total,
|
|
651
|
-
CASE WHEN ms.total > ma.avg_total THEN 'Above' ELSE 'Below' END as vs_avg
|
|
652
|
-
FROM monthly_sales ms, monthly_avg ma
|
|
653
|
-
ORDER BY ms.month
|
|
654
|
-
|
|
655
|
-
-- CTE for cleaner JOINs
|
|
656
|
-
WITH
|
|
657
|
-
customer_totals AS (
|
|
658
|
-
SELECT customerId, SUM(amount) as total_spent
|
|
659
|
-
FROM orders
|
|
660
|
-
GROUP BY customerId
|
|
661
|
-
)
|
|
662
|
-
SELECT
|
|
663
|
-
c.name,
|
|
664
|
-
ct.total_spent
|
|
665
|
-
FROM customers c
|
|
666
|
-
LEFT JOIN customer_totals ct ON c.id = ct.customerId
|
|
667
|
-
ORDER BY ct.total_spent DESC
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
### UNION (Combining Results)
|
|
671
|
-
|
|
672
|
-
Combine results from multiple queries.
|
|
673
|
-
|
|
674
|
-
```sql
|
|
675
|
-
-- UNION (removes duplicates)
|
|
676
|
-
SELECT name, 'Task' as type FROM tasks
|
|
677
|
-
UNION
|
|
678
|
-
SELECT name, 'Project' as type FROM projects
|
|
679
|
-
|
|
680
|
-
-- UNION ALL (keeps duplicates, faster)
|
|
681
|
-
SELECT name, amount, 'Invoice' as type FROM invoices
|
|
682
|
-
UNION ALL
|
|
683
|
-
SELECT name, amount, 'Quote' as type FROM quotes
|
|
684
|
-
|
|
685
|
-
-- Combined report from multiple workflows
|
|
686
|
-
SELECT
|
|
687
|
-
'This Month' as period,
|
|
688
|
-
COUNT(*) as count,
|
|
689
|
-
SUM(amount) as total
|
|
690
|
-
FROM orders
|
|
691
|
-
WHERE created >= strftime('%s', 'now', 'start of month')
|
|
692
|
-
UNION ALL
|
|
693
|
-
SELECT
|
|
694
|
-
'Last Month' as period,
|
|
695
|
-
COUNT(*) as count,
|
|
696
|
-
SUM(amount) as total
|
|
697
|
-
FROM orders
|
|
698
|
-
WHERE created >= strftime('%s', 'now', 'start of month', '-1 month')
|
|
699
|
-
AND created < strftime('%s', 'now', 'start of month')
|
|
700
|
-
```
|
|
701
|
-
|
|
702
|
-
**Note:** UNION requires same number of columns with compatible types.
|
|
703
|
-
|
|
704
|
-
---
|
|
705
|
-
|
|
706
|
-
## Testing Queries
|
|
707
|
-
|
|
708
|
-
Always preview before adding to insights.ts:
|
|
709
|
-
|
|
710
|
-
```javascript
|
|
711
|
-
mcp__hailer__preview_insight({
|
|
712
|
-
sources: [...],
|
|
713
|
-
query: "SELECT ..."
|
|
714
|
-
})
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
If preview returns data without errors, add to `workspace/insights.ts`.
|
|
718
|
-
|
|
719
|
-
---
|
|
720
|
-
|
|
721
|
-
## Common Mistakes
|
|
722
|
-
|
|
723
|
-
| Wrong | Right |
|
|
724
|
-
|-------|-------|
|
|
725
|
-
| Using `"` for identifiers | Use `"` for strings, backticks for identifiers if needed |
|
|
726
|
-
| Forgetting `_id` meta for JOINs | Always include `{ name: 'id', meta: '_id' }` |
|
|
727
|
-
| INNER JOIN for optional links | Use LEFT JOIN (links can be null) |
|
|
728
|
-
| Raw timestamps in output | Format with `strftime()` |
|
|
729
|
-
| Skipping preview | Always test with `preview_insight` first |
|
|
730
|
-
| Hardcoding workflow IDs | Use `WorkflowIds` enum |
|
|
731
|
-
| Generic field names (`date`, `id`) | Use real names (`pvm`, `nro`) that match Hailer UI |
|
|
732
|
-
| Using `/ 1000` for dates | Insight fields are already SECONDS - use directly with `strftime()` |
|
|
733
|
-
| Using `* 1000` in WHERE | Both sides are seconds - compare directly |
|
|
734
|
-
| `t.ajankohta` for range fields | Use `t.ajankohtaStart` and `t.ajankohtaEnd` (auto-suffixed for daterange, datetimerange, timerange) |
|
|
735
|
-
|
|
736
|
-
---
|
|
737
|
-
|
|
738
|
-
## Date Mistakes & What They Look Like
|
|
739
|
-
|
|
740
|
-
### Mistake 1: Using `/ 1000` (OLD HABIT - DON'T DO THIS!)
|
|
741
|
-
```sql
|
|
742
|
-
-- WRONG (insight fields are already in seconds!)
|
|
743
|
-
strftime('%Y-%m-%d', created / 1000, 'unixepoch')
|
|
744
|
-
-- Result: "1970-01-21" (wrong - divided seconds by 1000!)
|
|
745
|
-
|
|
746
|
-
-- CORRECT (use field directly)
|
|
747
|
-
strftime('%Y-%m-%d', created, 'unixepoch')
|
|
748
|
-
-- Result: "2024-01-15"
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
### Mistake 2: Using `* 1000` in WHERE
|
|
752
|
-
```sql
|
|
753
|
-
-- WRONG (multiplying seconds by 1000 makes comparison fail)
|
|
754
|
-
WHERE created >= strftime('%s', 'now', '-30 days') * 1000
|
|
755
|
-
-- 1705312800 >= 1705312800000 → always false!
|
|
756
|
-
|
|
757
|
-
-- CORRECT (both sides are seconds)
|
|
758
|
-
WHERE created >= strftime('%s', 'now', '-30 days')
|
|
759
|
-
-- 1705312800 >= 1705312800 → correct comparison
|
|
760
|
-
```
|
|
761
|
-
|
|
762
|
-
### Mistake 3: timerange is NOT minutes
|
|
763
|
-
```sql
|
|
764
|
-
-- WRONG (thinking timerange is minutes from midnight)
|
|
765
|
-
WHERE tyoaikaStart < 480 -- "before 8am"
|
|
766
|
-
|
|
767
|
-
-- CORRECT (timerange is timestamp in seconds - extract hour)
|
|
768
|
-
WHERE strftime('%H', tyoaikaStart, 'unixepoch') < '08'
|
|
769
|
-
```
|
|
770
|
-
|
|
771
|
-
### Mistake 4: Timezone confusion
|
|
772
|
-
```sql
|
|
773
|
-
-- WRONG (UTC time, Finland is +2/+3)
|
|
774
|
-
strftime('%H:%M', created, 'unixepoch')
|
|
775
|
-
-- Shows "08:30" when local time is "10:30"
|
|
776
|
-
|
|
777
|
-
-- CORRECT (add timezone offset)
|
|
778
|
-
strftime('%H:%M', created, 'unixepoch', '+2 hours')
|
|
779
|
-
-- Shows "10:30" (or use +3 during summer time)
|
|
780
|
-
```
|
|
781
|
-
|
|
782
|
-
### Quick Debug
|
|
783
|
-
If your dates look wrong:
|
|
784
|
-
1. **Years like 1970** → using `/ 1000` when you shouldn't (field is already seconds)
|
|
785
|
-
2. **No rows returned** → using `* 1000` in WHERE (comparing seconds to huge number)
|
|
786
|
-
3. **Times off by 2-3 hours** → timezone not applied
|
|
787
|
-
4. **Duration calculations** → subtract seconds directly: `(endField - startField) / 60` for minutes
|