@fbdo/smart-agentic-calendar 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.
Files changed (154) hide show
  1. package/README.md +195 -0
  2. package/dist/analytics/allocation.d.ts +9 -0
  3. package/dist/analytics/allocation.d.ts.map +1 -0
  4. package/dist/analytics/allocation.js +27 -0
  5. package/dist/analytics/allocation.js.map +1 -0
  6. package/dist/analytics/analytics-engine.d.ts +17 -0
  7. package/dist/analytics/analytics-engine.d.ts.map +1 -0
  8. package/dist/analytics/analytics-engine.js +33 -0
  9. package/dist/analytics/analytics-engine.js.map +1 -0
  10. package/dist/analytics/estimation.d.ts +9 -0
  11. package/dist/analytics/estimation.d.ts.map +1 -0
  12. package/dist/analytics/estimation.js +74 -0
  13. package/dist/analytics/estimation.js.map +1 -0
  14. package/dist/analytics/health.d.ts +14 -0
  15. package/dist/analytics/health.d.ts.map +1 -0
  16. package/dist/analytics/health.js +146 -0
  17. package/dist/analytics/health.js.map +1 -0
  18. package/dist/analytics/period.d.ts +9 -0
  19. package/dist/analytics/period.d.ts.map +1 -0
  20. package/dist/analytics/period.js +41 -0
  21. package/dist/analytics/period.js.map +1 -0
  22. package/dist/analytics/productivity.d.ts +9 -0
  23. package/dist/analytics/productivity.d.ts.map +1 -0
  24. package/dist/analytics/productivity.js +34 -0
  25. package/dist/analytics/productivity.js.map +1 -0
  26. package/dist/common/constants.d.ts +13 -0
  27. package/dist/common/constants.d.ts.map +1 -0
  28. package/dist/common/constants.js +19 -0
  29. package/dist/common/constants.js.map +1 -0
  30. package/dist/common/id.d.ts +2 -0
  31. package/dist/common/id.d.ts.map +1 -0
  32. package/dist/common/id.js +5 -0
  33. package/dist/common/id.js.map +1 -0
  34. package/dist/common/time.d.ts +11 -0
  35. package/dist/common/time.d.ts.map +1 -0
  36. package/dist/common/time.js +67 -0
  37. package/dist/common/time.js.map +1 -0
  38. package/dist/engine/conflict-detector.d.ts +22 -0
  39. package/dist/engine/conflict-detector.d.ts.map +1 -0
  40. package/dist/engine/conflict-detector.js +194 -0
  41. package/dist/engine/conflict-detector.js.map +1 -0
  42. package/dist/engine/dependency-resolver.d.ts +8 -0
  43. package/dist/engine/dependency-resolver.d.ts.map +1 -0
  44. package/dist/engine/dependency-resolver.js +160 -0
  45. package/dist/engine/dependency-resolver.js.map +1 -0
  46. package/dist/engine/recurrence-manager.d.ts +24 -0
  47. package/dist/engine/recurrence-manager.d.ts.map +1 -0
  48. package/dist/engine/recurrence-manager.js +140 -0
  49. package/dist/engine/recurrence-manager.js.map +1 -0
  50. package/dist/engine/replan-coordinator.d.ts +24 -0
  51. package/dist/engine/replan-coordinator.d.ts.map +1 -0
  52. package/dist/engine/replan-coordinator.js +107 -0
  53. package/dist/engine/replan-coordinator.js.map +1 -0
  54. package/dist/engine/scheduler.d.ts +65 -0
  55. package/dist/engine/scheduler.d.ts.map +1 -0
  56. package/dist/engine/scheduler.js +368 -0
  57. package/dist/engine/scheduler.js.map +1 -0
  58. package/dist/index.d.ts +19 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +69 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/mcp/server.d.ts +25 -0
  63. package/dist/mcp/server.d.ts.map +1 -0
  64. package/dist/mcp/server.js +421 -0
  65. package/dist/mcp/server.js.map +1 -0
  66. package/dist/mcp/tools/analytics-tools.d.ts +45 -0
  67. package/dist/mcp/tools/analytics-tools.d.ts.map +1 -0
  68. package/dist/mcp/tools/analytics-tools.js +27 -0
  69. package/dist/mcp/tools/analytics-tools.js.map +1 -0
  70. package/dist/mcp/tools/config-tools.d.ts +55 -0
  71. package/dist/mcp/tools/config-tools.d.ts.map +1 -0
  72. package/dist/mcp/tools/config-tools.js +47 -0
  73. package/dist/mcp/tools/config-tools.js.map +1 -0
  74. package/dist/mcp/tools/event-tools.d.ts +50 -0
  75. package/dist/mcp/tools/event-tools.d.ts.map +1 -0
  76. package/dist/mcp/tools/event-tools.js +48 -0
  77. package/dist/mcp/tools/event-tools.js.map +1 -0
  78. package/dist/mcp/tools/schedule-tools.d.ts +80 -0
  79. package/dist/mcp/tools/schedule-tools.d.ts.map +1 -0
  80. package/dist/mcp/tools/schedule-tools.js +103 -0
  81. package/dist/mcp/tools/schedule-tools.js.map +1 -0
  82. package/dist/mcp/tools/task-tools.d.ts +106 -0
  83. package/dist/mcp/tools/task-tools.d.ts.map +1 -0
  84. package/dist/mcp/tools/task-tools.js +161 -0
  85. package/dist/mcp/tools/task-tools.js.map +1 -0
  86. package/dist/mcp/validators.d.ts +231 -0
  87. package/dist/mcp/validators.d.ts.map +1 -0
  88. package/dist/mcp/validators.js +405 -0
  89. package/dist/mcp/validators.js.map +1 -0
  90. package/dist/models/analytics.d.ts +55 -0
  91. package/dist/models/analytics.d.ts.map +1 -0
  92. package/dist/models/analytics.js +2 -0
  93. package/dist/models/analytics.js.map +1 -0
  94. package/dist/models/config.d.ts +32 -0
  95. package/dist/models/config.d.ts.map +1 -0
  96. package/dist/models/config.js +2 -0
  97. package/dist/models/config.js.map +1 -0
  98. package/dist/models/conflict.d.ts +21 -0
  99. package/dist/models/conflict.d.ts.map +1 -0
  100. package/dist/models/conflict.js +2 -0
  101. package/dist/models/conflict.js.map +1 -0
  102. package/dist/models/dependency.d.ts +5 -0
  103. package/dist/models/dependency.d.ts.map +1 -0
  104. package/dist/models/dependency.js +2 -0
  105. package/dist/models/dependency.js.map +1 -0
  106. package/dist/models/errors.d.ts +19 -0
  107. package/dist/models/errors.d.ts.map +1 -0
  108. package/dist/models/errors.js +29 -0
  109. package/dist/models/errors.js.map +1 -0
  110. package/dist/models/event.d.ts +11 -0
  111. package/dist/models/event.d.ts.map +1 -0
  112. package/dist/models/event.js +2 -0
  113. package/dist/models/event.js.map +1 -0
  114. package/dist/models/recurrence.d.ts +22 -0
  115. package/dist/models/recurrence.d.ts.map +1 -0
  116. package/dist/models/recurrence.js +2 -0
  117. package/dist/models/recurrence.js.map +1 -0
  118. package/dist/models/schedule.d.ts +16 -0
  119. package/dist/models/schedule.d.ts.map +1 -0
  120. package/dist/models/schedule.js +2 -0
  121. package/dist/models/schedule.js.map +1 -0
  122. package/dist/models/task.d.ts +21 -0
  123. package/dist/models/task.d.ts.map +1 -0
  124. package/dist/models/task.js +9 -0
  125. package/dist/models/task.js.map +1 -0
  126. package/dist/storage/analytics-repository.d.ts +17 -0
  127. package/dist/storage/analytics-repository.d.ts.map +1 -0
  128. package/dist/storage/analytics-repository.js +99 -0
  129. package/dist/storage/analytics-repository.js.map +1 -0
  130. package/dist/storage/config-repository.d.ts +20 -0
  131. package/dist/storage/config-repository.d.ts.map +1 -0
  132. package/dist/storage/config-repository.js +145 -0
  133. package/dist/storage/config-repository.js.map +1 -0
  134. package/dist/storage/database.d.ts +6 -0
  135. package/dist/storage/database.d.ts.map +1 -0
  136. package/dist/storage/database.js +160 -0
  137. package/dist/storage/database.js.map +1 -0
  138. package/dist/storage/event-repository.d.ts +16 -0
  139. package/dist/storage/event-repository.d.ts.map +1 -0
  140. package/dist/storage/event-repository.js +120 -0
  141. package/dist/storage/event-repository.js.map +1 -0
  142. package/dist/storage/recurrence-repository.d.ts +18 -0
  143. package/dist/storage/recurrence-repository.d.ts.map +1 -0
  144. package/dist/storage/recurrence-repository.js +99 -0
  145. package/dist/storage/recurrence-repository.js.map +1 -0
  146. package/dist/storage/schedule-repository.d.ts +13 -0
  147. package/dist/storage/schedule-repository.d.ts.map +1 -0
  148. package/dist/storage/schedule-repository.js +53 -0
  149. package/dist/storage/schedule-repository.js.map +1 -0
  150. package/dist/storage/task-repository.d.ts +29 -0
  151. package/dist/storage/task-repository.d.ts.map +1 -0
  152. package/dist/storage/task-repository.js +235 -0
  153. package/dist/storage/task-repository.js.map +1 -0
  154. package/package.json +77 -0
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # Smart Agentic Calendar
2
+
3
+ A local-first smart calendar MCP server for AI agents. Inspired by [Reclaim.ai](https://reclaim.ai/) and [Motion](https://www.usemotion.com/), it automatically re-plans tasks, recalculates the schedule, and helps AI agents act as personal scheduling assistants.
4
+
5
+ ## What It Does
6
+
7
+ - **Time-blocking scheduler** that assigns specific time slots to tasks based on deadlines, durations, and priorities (P1-P4)
8
+ - **Automatic replanning** triggered on any change (create, update, delete, complete) — runs in the background so tool responses are instant
9
+ - **Constraint satisfaction** engine that respects hard constraints (deadlines, availability, events, dependencies) and optimizes soft constraints (priority, focus time, buffer time)
10
+ - **Focus time protection** for deep work blocks with minimum uninterrupted duration enforcement
11
+ - **Task splitting** across multiple time slots when no single slot fits
12
+ - **Recurring tasks** with full RRULE support (daily, weekly, monthly, yearly, custom patterns)
13
+ - **Task dependencies** with cycle detection
14
+ - **Conflict detection** that identifies at-risk tasks and suggests deprioritizations
15
+ - **Analytics** — completion rates, schedule health, estimation accuracy, time allocation by category
16
+
17
+ ## Architecture
18
+
19
+ ```
20
+ src/
21
+ models/ Domain types (Task, Event, TimeBlock, Config, etc.)
22
+ common/ Shared utilities (ID generation, time functions, constants)
23
+ storage/ SQLite database layer (better-sqlite3)
24
+ engine/ Scheduling engine, replanning, conflict detection, recurrence
25
+ analytics/ Productivity stats, schedule health, estimation accuracy
26
+ mcp/ MCP server and tool handlers
27
+ index.ts Composition root and entry point
28
+ ```
29
+
30
+ Single-user, single-process, local-first. All data stored in a local SQLite database with no external service dependencies.
31
+
32
+ ## Tech Stack
33
+
34
+ | Component | Choice |
35
+ |-----------|--------|
36
+ | Language | TypeScript (strict mode) |
37
+ | Runtime | Node.js >= 20 |
38
+ | Storage | SQLite via [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) |
39
+ | MCP Transport | stdio |
40
+ | Test Runner | [Vitest](https://vitest.dev/) |
41
+ | Scheduling | Constraint satisfaction algorithm |
42
+
43
+ ## Getting Started
44
+
45
+ ### Prerequisites
46
+
47
+ - Node.js >= 20
48
+
49
+ ### Install and Build
50
+
51
+ ```bash
52
+ npm install
53
+ npm run build
54
+ ```
55
+
56
+ This compiles TypeScript to `dist/` and produces the runnable server at `dist/index.js`.
57
+
58
+ ### Test
59
+
60
+ ```bash
61
+ npm test # run all tests (578 tests)
62
+ npm run test:watch # watch mode
63
+ npm run test:coverage # with coverage
64
+ ```
65
+
66
+ ### Quality Checks
67
+
68
+ ```bash
69
+ npm run lint # eslint
70
+ npm run format:check # prettier
71
+ npm run quality # all checks (lint, format, duplication, unused code, dependency rules, security)
72
+ ```
73
+
74
+ ## Connecting to an MCP-Compatible Agent
75
+
76
+ This server communicates over **stdio** using the [Model Context Protocol](https://modelcontextprotocol.io/). Any MCP-compatible client can connect to it by spawning the server as a subprocess.
77
+
78
+ ### Configuration
79
+
80
+ The server accepts one environment variable:
81
+
82
+ | Variable | Default | Description |
83
+ |----------|---------|-------------|
84
+ | `CALENDAR_DB_PATH` | `./calendar.db` | Path to the SQLite database file |
85
+
86
+ ### Claude Desktop
87
+
88
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
89
+
90
+ ```json
91
+ {
92
+ "mcpServers": {
93
+ "smart-agentic-calendar": {
94
+ "command": "npx",
95
+ "args": ["-y", "smart-agentic-calendar"],
96
+ "env": {
97
+ "CALENDAR_DB_PATH": "/absolute/path/to/calendar.db"
98
+ }
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ ### Claude Code (CLI)
105
+
106
+ Add to your project's `.mcp.json` or global `~/.claude/settings.json`:
107
+
108
+ ```json
109
+ {
110
+ "mcpServers": {
111
+ "smart-agentic-calendar": {
112
+ "command": "npx",
113
+ "args": ["-y", "smart-agentic-calendar"],
114
+ "env": {
115
+ "CALENDAR_DB_PATH": "/absolute/path/to/calendar.db"
116
+ }
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ### Cursor
123
+
124
+ Add to `.cursor/mcp.json` in your project root:
125
+
126
+ ```json
127
+ {
128
+ "mcpServers": {
129
+ "smart-agentic-calendar": {
130
+ "command": "npx",
131
+ "args": ["-y", "smart-agentic-calendar"],
132
+ "env": {
133
+ "CALENDAR_DB_PATH": "/absolute/path/to/calendar.db"
134
+ }
135
+ }
136
+ }
137
+ }
138
+ ```
139
+
140
+ ### Any MCP Client (Generic)
141
+
142
+ Spawn the server as a child process with stdio transport:
143
+
144
+ ```bash
145
+ npx -y smart-agentic-calendar
146
+ ```
147
+
148
+ Or with a custom database path:
149
+
150
+ ```bash
151
+ CALENDAR_DB_PATH=/path/to/calendar.db npx -y smart-agentic-calendar
152
+ ```
153
+
154
+ The server reads JSON-RPC messages from stdin and writes responses to stdout. Diagnostic messages go to stderr.
155
+
156
+ ## MCP Tools
157
+
158
+ ### Task Management
159
+ - `create_task` — create a task with title, duration, deadline, priority, category, tags
160
+ - `get_task` — retrieve a task by ID
161
+ - `update_task` — update any task field (triggers background replan)
162
+ - `delete_task` — soft-delete a task (triggers background replan)
163
+ - `complete_task` — mark a task done, optionally record actual duration
164
+ - `list_tasks` — query tasks with filters (status, priority, deadline range, category)
165
+
166
+ ### Event Management
167
+ - `create_event` — create a fixed calendar event that blocks time
168
+ - `update_event` — modify an event
169
+ - `delete_event` — remove an event
170
+ - `list_events` — query events by date range
171
+
172
+ ### Schedule & Conflicts
173
+ - `get_schedule` — view scheduled time blocks for a date range (returns `schedule_status`: "up_to_date" or "replan_in_progress")
174
+ - `replan` — force a synchronous replan (the only blocking replan path)
175
+ - `get_conflicts` — view at-risk tasks and deadline conflicts
176
+
177
+ ### Analytics
178
+ - `get_productivity_stats` — completion rates, on-time rates by period
179
+ - `get_schedule_health` — composite health score, utilization, overdue/at-risk counts
180
+ - `get_time_allocation` — hours breakdown by category
181
+ - `get_estimation_accuracy` — estimated vs. actual duration analysis
182
+
183
+ ### Configuration
184
+ - `set_availability` — define work hours per day of week
185
+ - `set_focus_time` — define focus/deep-work blocks
186
+ - `set_preferences` — buffer time, default priority, scheduling horizon, minimum block size
187
+ - `get_preferences` — retrieve current configuration
188
+
189
+ ## Development
190
+
191
+ This project follows **test-driven development** (red-green-refactor) with a healthy test pyramid and dependency injection for testability. Property-based tests are used for pure scheduling functions and serialization round-trips.
192
+
193
+ ## License
194
+
195
+ MIT
@@ -0,0 +1,9 @@
1
+ import type { AnalyticsRepository } from "../storage/analytics-repository.js";
2
+ import type { TimeAllocation } from "../models/analytics.js";
3
+ import type { Period } from "./period.js";
4
+ export declare class AllocationCalculator {
5
+ private readonly analyticsRepo;
6
+ constructor(analyticsRepo: AnalyticsRepository);
7
+ compute(period: Period, referenceDate?: Date): TimeAllocation;
8
+ }
9
+ //# sourceMappingURL=allocation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allocation.d.ts","sourceRoot":"","sources":["../../src/analytics/allocation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAsB,MAAM,wBAAwB,CAAC;AACjF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;gBAExC,aAAa,EAAE,mBAAmB;IAI9C,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,IAAI,GAAG,cAAc;CAoB9D"}
@@ -0,0 +1,27 @@
1
+ import { resolvePeriod } from "./period.js";
2
+ export class AllocationCalculator {
3
+ analyticsRepo;
4
+ constructor(analyticsRepo) {
5
+ this.analyticsRepo = analyticsRepo;
6
+ }
7
+ compute(period, referenceDate) {
8
+ const range = resolvePeriod(period, referenceDate);
9
+ const summaries = this.analyticsRepo.getTasksByCategory(range.start, range.end);
10
+ if (summaries.length === 0) {
11
+ return { period, categories: [] };
12
+ }
13
+ const totalMinutes = summaries.reduce((sum, s) => sum + s.totalMinutes, 0);
14
+ const categories = summaries
15
+ .map((s) => ({
16
+ category: s.category,
17
+ hours: round2(s.totalMinutes / 60),
18
+ percentage: totalMinutes === 0 ? 0 : round2((s.totalMinutes / totalMinutes) * 100),
19
+ }))
20
+ .sort((a, b) => b.hours - a.hours);
21
+ return { period, categories };
22
+ }
23
+ }
24
+ function round2(value) {
25
+ return Math.round(value * 100) / 100;
26
+ }
27
+ //# sourceMappingURL=allocation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allocation.js","sourceRoot":"","sources":["../../src/analytics/allocation.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,oBAAoB;IACd,aAAa,CAAsB;IAEpD,YAAY,aAAkC;QAC5C,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACrC,CAAC;IAED,OAAO,CAAC,MAAc,EAAE,aAAoB;QAC1C,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QACnD,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,kBAAkB,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAEhF,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;QACpC,CAAC;QAED,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;QAE3E,MAAM,UAAU,GAAyB,SAAS;aAC/C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACX,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,GAAG,EAAE,CAAC;YAClC,UAAU,EAAE,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,YAAY,CAAC,GAAG,GAAG,CAAC;SACnF,CAAC,CAAC;aACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAErC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAChC,CAAC;CACF;AAED,SAAS,MAAM,CAAC,KAAa;IAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACvC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { AnalyticsRepository } from "../storage/analytics-repository.js";
2
+ import type { TaskRepository } from "../storage/task-repository.js";
3
+ import type { ScheduleRepository } from "../storage/schedule-repository.js";
4
+ import type { ConfigRepository } from "../storage/config-repository.js";
5
+ import type { ProductivityStats, ScheduleHealth, EstimationAccuracy, TimeAllocation } from "../models/analytics.js";
6
+ export declare class AnalyticsEngine {
7
+ private readonly productivity;
8
+ private readonly health;
9
+ private readonly estimation;
10
+ private readonly allocation;
11
+ constructor(analyticsRepo: AnalyticsRepository, taskRepo: TaskRepository, scheduleRepo: ScheduleRepository, configRepo: ConfigRepository);
12
+ getProductivityStats(period: string, referenceDate?: Date): ProductivityStats;
13
+ getScheduleHealth(referenceDate?: Date): ScheduleHealth;
14
+ getEstimationAccuracy(period: string, referenceDate?: Date): EstimationAccuracy;
15
+ getTimeAllocation(period: string, referenceDate?: Date): TimeAllocation;
16
+ }
17
+ //# sourceMappingURL=analytics-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics-engine.d.ts","sourceRoot":"","sources":["../../src/analytics/analytics-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,cAAc,EACf,MAAM,wBAAwB,CAAC;AAOhC,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyB;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuB;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuB;gBAGhD,aAAa,EAAE,mBAAmB,EAClC,QAAQ,EAAE,cAAc,EACxB,YAAY,EAAE,kBAAkB,EAChC,UAAU,EAAE,gBAAgB;IAQ9B,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,IAAI,GAAG,iBAAiB;IAK7E,iBAAiB,CAAC,aAAa,CAAC,EAAE,IAAI,GAAG,cAAc;IAIvD,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,IAAI,GAAG,kBAAkB;IAK/E,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,IAAI,GAAG,cAAc;CAIxE"}
@@ -0,0 +1,33 @@
1
+ import { validatePeriod } from "./period.js";
2
+ import { ProductivityCalculator } from "./productivity.js";
3
+ import { HealthCalculator } from "./health.js";
4
+ import { EstimationCalculator } from "./estimation.js";
5
+ import { AllocationCalculator } from "./allocation.js";
6
+ export class AnalyticsEngine {
7
+ productivity;
8
+ health;
9
+ estimation;
10
+ allocation;
11
+ constructor(analyticsRepo, taskRepo, scheduleRepo, configRepo) {
12
+ this.productivity = new ProductivityCalculator(analyticsRepo);
13
+ this.health = new HealthCalculator(analyticsRepo, taskRepo, scheduleRepo, configRepo);
14
+ this.estimation = new EstimationCalculator(analyticsRepo);
15
+ this.allocation = new AllocationCalculator(analyticsRepo);
16
+ }
17
+ getProductivityStats(period, referenceDate) {
18
+ validatePeriod(period);
19
+ return this.productivity.compute(period, referenceDate);
20
+ }
21
+ getScheduleHealth(referenceDate) {
22
+ return this.health.compute(referenceDate);
23
+ }
24
+ getEstimationAccuracy(period, referenceDate) {
25
+ validatePeriod(period);
26
+ return this.estimation.compute(period, referenceDate);
27
+ }
28
+ getTimeAllocation(period, referenceDate) {
29
+ validatePeriod(period);
30
+ return this.allocation.compute(period, referenceDate);
31
+ }
32
+ }
33
+ //# sourceMappingURL=analytics-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics-engine.js","sourceRoot":"","sources":["../../src/analytics/analytics-engine.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,OAAO,eAAe;IACT,YAAY,CAAyB;IACrC,MAAM,CAAmB;IACzB,UAAU,CAAuB;IACjC,UAAU,CAAuB;IAElD,YACE,aAAkC,EAClC,QAAwB,EACxB,YAAgC,EAChC,UAA4B;QAE5B,IAAI,CAAC,YAAY,GAAG,IAAI,sBAAsB,CAAC,aAAa,CAAC,CAAC;QAC9D,IAAI,CAAC,MAAM,GAAG,IAAI,gBAAgB,CAAC,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;QACtF,IAAI,CAAC,UAAU,GAAG,IAAI,oBAAoB,CAAC,aAAa,CAAC,CAAC;QAC1D,IAAI,CAAC,UAAU,GAAG,IAAI,oBAAoB,CAAC,aAAa,CAAC,CAAC;IAC5D,CAAC;IAED,oBAAoB,CAAC,MAAc,EAAE,aAAoB;QACvD,cAAc,CAAC,MAAM,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC1D,CAAC;IAED,iBAAiB,CAAC,aAAoB;QACpC,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5C,CAAC;IAED,qBAAqB,CAAC,MAAc,EAAE,aAAoB;QACxD,cAAc,CAAC,MAAM,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACxD,CAAC;IAED,iBAAiB,CAAC,MAAc,EAAE,aAAoB;QACpD,cAAc,CAAC,MAAM,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACxD,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ import type { AnalyticsRepository } from "../storage/analytics-repository.js";
2
+ import type { EstimationAccuracy } from "../models/analytics.js";
3
+ import type { Period } from "./period.js";
4
+ export declare class EstimationCalculator {
5
+ private readonly analyticsRepo;
6
+ constructor(analyticsRepo: AnalyticsRepository);
7
+ compute(period: Period, referenceDate?: Date): EstimationAccuracy;
8
+ }
9
+ //# sourceMappingURL=estimation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"estimation.d.ts","sourceRoot":"","sources":["../../src/analytics/estimation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAE,kBAAkB,EAAkB,MAAM,wBAAwB,CAAC;AACjF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;gBAExC,aAAa,EAAE,mBAAmB;IAI9C,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,IAAI,GAAG,kBAAkB;CAkDlE"}
@@ -0,0 +1,74 @@
1
+ import { resolvePeriod } from "./period.js";
2
+ export class EstimationCalculator {
3
+ analyticsRepo;
4
+ constructor(analyticsRepo) {
5
+ this.analyticsRepo = analyticsRepo;
6
+ }
7
+ compute(period, referenceDate) {
8
+ const range = resolvePeriod(period, referenceDate);
9
+ const records = this.analyticsRepo.getDurationRecords(range.start, range.end);
10
+ if (records.length === 0) {
11
+ return {
12
+ averageAccuracyPercentage: null,
13
+ overestimateCount: 0,
14
+ underestimateCount: 0,
15
+ averageOverestimateMinutes: null,
16
+ averageUnderestimateMinutes: null,
17
+ accuracyByCategory: null,
18
+ };
19
+ }
20
+ const accuracies = records.map((r) => perTaskAccuracy(r.estimatedMinutes, r.actualMinutes));
21
+ const averageAccuracyPercentage = round2(accuracies.reduce((sum, a) => sum + a, 0) / accuracies.length);
22
+ const overestimates = records.filter((r) => r.actualMinutes < r.estimatedMinutes);
23
+ const underestimates = records.filter((r) => r.actualMinutes > r.estimatedMinutes);
24
+ const averageOverestimateMinutes = overestimates.length === 0
25
+ ? null
26
+ : round1(overestimates.reduce((sum, r) => sum + (r.estimatedMinutes - r.actualMinutes), 0) /
27
+ overestimates.length);
28
+ const averageUnderestimateMinutes = underestimates.length === 0
29
+ ? null
30
+ : round1(underestimates.reduce((sum, r) => sum + (r.actualMinutes - r.estimatedMinutes), 0) /
31
+ underestimates.length);
32
+ const accuracyByCategory = computeCategoryAccuracy(records);
33
+ return {
34
+ averageAccuracyPercentage,
35
+ overestimateCount: overestimates.length,
36
+ underestimateCount: underestimates.length,
37
+ averageOverestimateMinutes,
38
+ averageUnderestimateMinutes,
39
+ accuracyByCategory,
40
+ };
41
+ }
42
+ }
43
+ function perTaskAccuracy(estimated, actual) {
44
+ if (estimated === 0)
45
+ return 0;
46
+ const deviation = (Math.abs(actual - estimated) / estimated) * 100;
47
+ return Math.max(0, 100 - deviation);
48
+ }
49
+ function computeCategoryAccuracy(records) {
50
+ const groups = new Map();
51
+ for (const record of records) {
52
+ const category = record.category ?? "uncategorized";
53
+ const accuracy = perTaskAccuracy(record.estimatedMinutes, record.actualMinutes);
54
+ if (!groups.has(category)) {
55
+ groups.set(category, []);
56
+ }
57
+ const group = groups.get(category);
58
+ if (group) {
59
+ group.push(accuracy);
60
+ }
61
+ }
62
+ const result = {};
63
+ for (const [category, accuracies] of groups) {
64
+ result[category] = round2(accuracies.reduce((sum, a) => sum + a, 0) / accuracies.length);
65
+ }
66
+ return result;
67
+ }
68
+ function round2(value) {
69
+ return Math.round(value * 100) / 100;
70
+ }
71
+ function round1(value) {
72
+ return Math.round(value * 10) / 10;
73
+ }
74
+ //# sourceMappingURL=estimation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"estimation.js","sourceRoot":"","sources":["../../src/analytics/estimation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { AnalyticsRepository } from "../storage/analytics-repository.js";
2
+ import type { TaskRepository } from "../storage/task-repository.js";
3
+ import type { ScheduleRepository } from "../storage/schedule-repository.js";
4
+ import type { ConfigRepository } from "../storage/config-repository.js";
5
+ import type { ScheduleHealth } from "../models/analytics.js";
6
+ export declare class HealthCalculator {
7
+ private readonly analyticsRepo;
8
+ private readonly taskRepo;
9
+ private readonly scheduleRepo;
10
+ private readonly configRepo;
11
+ constructor(analyticsRepo: AnalyticsRepository, taskRepo: TaskRepository, scheduleRepo: ScheduleRepository, configRepo: ConfigRepository);
12
+ compute(referenceDate?: Date): ScheduleHealth;
13
+ }
14
+ //# sourceMappingURL=health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/analytics/health.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAe7D,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmB;gBAG5C,aAAa,EAAE,mBAAmB,EAClC,QAAQ,EAAE,cAAc,EACxB,YAAY,EAAE,kBAAkB,EAChC,UAAU,EAAE,gBAAgB;IAQ9B,OAAO,CAAC,aAAa,CAAC,EAAE,IAAI,GAAG,cAAc;CAyC9C"}
@@ -0,0 +1,146 @@
1
+ import { resolvePeriod } from "./period.js";
2
+ import { diffMinutes } from "../common/time.js";
3
+ const DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
4
+ const OVERDUE_WEIGHT = 15;
5
+ const AT_RISK_WEIGHT = 10;
6
+ const OVER_UTILIZATION_THRESHOLD = 90;
7
+ const OVER_UTILIZATION_MULTIPLIER = 2;
8
+ const UNDER_UTILIZATION_THRESHOLD = 20;
9
+ const UNDER_UTILIZATION_MULTIPLIER = 1;
10
+ export class HealthCalculator {
11
+ analyticsRepo;
12
+ taskRepo;
13
+ scheduleRepo;
14
+ configRepo;
15
+ constructor(analyticsRepo, taskRepo, scheduleRepo, configRepo) {
16
+ this.analyticsRepo = analyticsRepo;
17
+ this.taskRepo = taskRepo;
18
+ this.scheduleRepo = scheduleRepo;
19
+ this.configRepo = configRepo;
20
+ }
21
+ compute(referenceDate) {
22
+ const ref = referenceDate ?? new Date();
23
+ const weekRange = resolvePeriod("week", ref);
24
+ const config = this.configRepo.getFullConfig();
25
+ const timeBlocks = this.scheduleRepo.getSchedule(weekRange.start, weekRange.end);
26
+ const overdueTasks = this.analyticsRepo.getOverdueTasks(ref.toISOString());
27
+ const atRiskTasks = this.taskRepo.findAll({ status: "at_risk" });
28
+ const overdueCount = overdueTasks.length;
29
+ const atRiskCount = atRiskTasks.length;
30
+ const availablePerDay = computeAvailableMinutesPerDay(config.availability, weekRange.start);
31
+ const scheduledPerDay = computeScheduledMinutesPerDay(timeBlocks, weekRange.start);
32
+ const totalAvailable = sumValues(availablePerDay);
33
+ const totalScheduled = sumValues(scheduledPerDay);
34
+ const utilizationPercentage = totalAvailable === 0 ? 0 : round2((totalScheduled / totalAvailable) * 100);
35
+ const freeHoursThisWeek = totalAvailable === 0 ? 0 : round1(Math.max(0, (totalAvailable - totalScheduled) / 60));
36
+ const { busiestDay, lightestDay } = findBusiestLightestDay(scheduledPerDay, availablePerDay, weekRange.start);
37
+ const healthScore = computeHealthScore(overdueCount, atRiskCount, utilizationPercentage);
38
+ return {
39
+ healthScore,
40
+ utilizationPercentage,
41
+ overdueCount,
42
+ atRiskCount,
43
+ freeHoursThisWeek,
44
+ busiestDay,
45
+ lightestDay,
46
+ };
47
+ }
48
+ }
49
+ function computeHealthScore(overdueCount, atRiskCount, utilizationPercentage) {
50
+ let score = 100;
51
+ score -= overdueCount * OVERDUE_WEIGHT;
52
+ score -= atRiskCount * AT_RISK_WEIGHT;
53
+ if (utilizationPercentage > OVER_UTILIZATION_THRESHOLD) {
54
+ score -= (utilizationPercentage - OVER_UTILIZATION_THRESHOLD) * OVER_UTILIZATION_MULTIPLIER;
55
+ }
56
+ else if (utilizationPercentage < UNDER_UTILIZATION_THRESHOLD) {
57
+ score -= (UNDER_UTILIZATION_THRESHOLD - utilizationPercentage) * UNDER_UTILIZATION_MULTIPLIER;
58
+ }
59
+ return Math.round(Math.max(0, Math.min(100, score)));
60
+ }
61
+ function computeAvailableMinutesPerDay(availability, weekStart) {
62
+ const result = new Map();
63
+ const startDate = new Date(weekStart);
64
+ for (let dayOffset = 0; dayOffset < 7; dayOffset++) {
65
+ const date = new Date(startDate);
66
+ date.setUTCDate(date.getUTCDate() + dayOffset);
67
+ const dayOfWeek = date.getUTCDay(); // 0=Sun..6=Sat
68
+ let minutes = 0;
69
+ for (const window of availability.windows) {
70
+ if (window.day === dayOfWeek) {
71
+ const [startH, startM] = window.startTime.split(":").map(Number);
72
+ const [endH, endM] = window.endTime.split(":").map(Number);
73
+ minutes += endH * 60 + endM - (startH * 60 + startM);
74
+ }
75
+ }
76
+ result.set(dayOffset, minutes);
77
+ }
78
+ return result;
79
+ }
80
+ function computeScheduledMinutesPerDay(timeBlocks, weekStart) {
81
+ const result = new Map();
82
+ const startMs = new Date(weekStart).getTime();
83
+ for (const block of timeBlocks) {
84
+ const blockDate = new Date(block.startTime);
85
+ const dayOffset = Math.floor((blockDate.getTime() - startMs) / (24 * 60 * 60 * 1000));
86
+ if (dayOffset >= 0 && dayOffset < 7) {
87
+ const minutes = diffMinutes(block.startTime, block.endTime);
88
+ result.set(dayOffset, (result.get(dayOffset) ?? 0) + minutes);
89
+ }
90
+ }
91
+ return result;
92
+ }
93
+ function findBusiestLightestDay(scheduledPerDay, availablePerDay, weekStart) {
94
+ // Check if any time blocks exist
95
+ let hasScheduled = false;
96
+ for (const minutes of scheduledPerDay.values()) {
97
+ if (minutes > 0) {
98
+ hasScheduled = true;
99
+ break;
100
+ }
101
+ }
102
+ if (!hasScheduled) {
103
+ return { busiestDay: null, lightestDay: null };
104
+ }
105
+ const startDate = new Date(weekStart);
106
+ let busiestOffset = -1;
107
+ let busiestMinutes = -1;
108
+ let lightestOffset = -1;
109
+ let lightestMinutes = Infinity;
110
+ for (let dayOffset = 0; dayOffset < 7; dayOffset++) {
111
+ const scheduled = scheduledPerDay.get(dayOffset) ?? 0;
112
+ const available = availablePerDay.get(dayOffset) ?? 0;
113
+ // Busiest: any day with scheduled time
114
+ if (scheduled > busiestMinutes) {
115
+ busiestMinutes = scheduled;
116
+ busiestOffset = dayOffset;
117
+ }
118
+ // Lightest: only days with availability > 0
119
+ if (available > 0 && scheduled < lightestMinutes) {
120
+ lightestMinutes = scheduled;
121
+ lightestOffset = dayOffset;
122
+ }
123
+ }
124
+ const busiestDay = busiestOffset >= 0 ? getDayName(startDate, busiestOffset) : null;
125
+ const lightestDay = lightestOffset >= 0 ? getDayName(startDate, lightestOffset) : null;
126
+ return { busiestDay, lightestDay };
127
+ }
128
+ function getDayName(weekStart, dayOffset) {
129
+ const date = new Date(weekStart);
130
+ date.setUTCDate(date.getUTCDate() + dayOffset);
131
+ return DAY_NAMES[date.getUTCDay()];
132
+ }
133
+ function sumValues(map) {
134
+ let sum = 0;
135
+ for (const v of map.values()) {
136
+ sum += v;
137
+ }
138
+ return sum;
139
+ }
140
+ function round2(value) {
141
+ return Math.round(value * 100) / 100;
142
+ }
143
+ function round1(value) {
144
+ return Math.round(value * 10) / 10;
145
+ }
146
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/analytics/health.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export type Period = "day" | "week" | "month";
2
+ interface DateRange {
3
+ start: string;
4
+ end: string;
5
+ }
6
+ export declare function validatePeriod(period: string): asserts period is Period;
7
+ export declare function resolvePeriod(period: Period, referenceDate?: Date): DateRange;
8
+ export {};
9
+ //# sourceMappingURL=period.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"period.d.ts","sourceRoot":"","sources":["../../src/analytics/period.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAE9C,UAAU,SAAS;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAID,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAIvE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,IAAI,GAAG,SAAS,CAW7E"}
@@ -0,0 +1,41 @@
1
+ import { ValidationError } from "../models/errors.js";
2
+ const VALID_PERIODS = new Set(["day", "week", "month"]);
3
+ export function validatePeriod(period) {
4
+ if (!VALID_PERIODS.has(period)) {
5
+ throw new ValidationError("invalid period: must be day, week, or month");
6
+ }
7
+ }
8
+ export function resolvePeriod(period, referenceDate) {
9
+ const ref = referenceDate ?? new Date();
10
+ switch (period) {
11
+ case "day":
12
+ return resolveDay(ref);
13
+ case "week":
14
+ return resolveWeek(ref);
15
+ case "month":
16
+ return resolveMonth(ref);
17
+ }
18
+ }
19
+ function resolveDay(ref) {
20
+ const start = new Date(Date.UTC(ref.getUTCFullYear(), ref.getUTCMonth(), ref.getUTCDate()));
21
+ const end = new Date(start);
22
+ end.setUTCDate(end.getUTCDate() + 1);
23
+ return { start: start.toISOString(), end: end.toISOString() };
24
+ }
25
+ function resolveWeek(ref) {
26
+ const start = new Date(Date.UTC(ref.getUTCFullYear(), ref.getUTCMonth(), ref.getUTCDate()));
27
+ // getUTCDay(): 0=Sunday, 1=Monday, ..., 6=Saturday
28
+ // We want Monday as start of week
29
+ const dayOfWeek = start.getUTCDay();
30
+ const daysFromMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
31
+ start.setUTCDate(start.getUTCDate() - daysFromMonday);
32
+ const end = new Date(start);
33
+ end.setUTCDate(end.getUTCDate() + 7);
34
+ return { start: start.toISOString(), end: end.toISOString() };
35
+ }
36
+ function resolveMonth(ref) {
37
+ const start = new Date(Date.UTC(ref.getUTCFullYear(), ref.getUTCMonth(), 1));
38
+ const end = new Date(Date.UTC(ref.getUTCFullYear(), ref.getUTCMonth() + 1, 1));
39
+ return { start: start.toISOString(), end: end.toISOString() };
40
+ }
41
+ //# sourceMappingURL=period.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"period.js","sourceRoot":"","sources":["../../src/analytics/period.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAStD,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7E,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,eAAe,CAAC,6CAA6C,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,aAAoB;IAChE,MAAM,GAAG,GAAG,aAAa,IAAI,IAAI,IAAI,EAAE,CAAC;IAExC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,KAAK;YACR,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC;QACzB,KAAK,MAAM;YACT,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;QAC1B,KAAK,OAAO;YACV,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,GAAS;IAC3B,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAC5F,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5B,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;IACrC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;AAChE,CAAC;AAED,SAAS,WAAW,CAAC,GAAS;IAC5B,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAC5F,mDAAmD;IACnD,kCAAkC;IAClC,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;IACpC,MAAM,cAAc,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC;IAC3D,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,cAAc,CAAC,CAAC;IAEtD,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5B,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;IACrC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;AAChE,CAAC;AAED,SAAS,YAAY,CAAC,GAAS;IAC7B,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7E,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/E,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;AAChE,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { AnalyticsRepository } from "../storage/analytics-repository.js";
2
+ import type { ProductivityStats } from "../models/analytics.js";
3
+ import type { Period } from "./period.js";
4
+ export declare class ProductivityCalculator {
5
+ private readonly analyticsRepo;
6
+ constructor(analyticsRepo: AnalyticsRepository);
7
+ compute(period: Period, referenceDate?: Date): ProductivityStats;
8
+ }
9
+ //# sourceMappingURL=productivity.d.ts.map