Skip to content

feat(analytics): CPM critical-path engine — pure + tested (slice 6a, stacked on #217)#218

Merged
seonghobae merged 2 commits into
feat/saas-billingfrom
feat/saas-cpm
Jul 6, 2026
Merged

feat(analytics): CPM critical-path engine — pure + tested (slice 6a, stacked on #217)#218
seonghobae merged 2 commits into
feat/saas-billingfrom
feat/saas-cpm

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

SaaS pivot — slice 6a of N (stacked on #217)

The deepest 공정관리 (schedule-control) moat: a Critical Path Method engine. This tick is the pure engine + tests (low risk); table/gantt critical-path highlighting is the next tick.

analytics.jscomputeCpm(tasks, { calcDuration? })

  • Dependency graph from an optional task.predecessors (array or comma-separated ids referencing task.id).
  • Duration from task.duration or plannedStart/End day-count (mirrors calculateDurationDays; or an injected calcDuration).
  • Kahn topological sort → forward pass (ES/EF) → backward pass (LS/LF) → slack = LS−ES, critical = slack≈0.
  • Returns perTask{es,ef,ls,lf,slack,critical,duration} + projectDurationDays + criticalPath (ordered) + cycleDetected.
  • Robust: cycles → cycleDetected:true (never throws); missing predecessor ids ignored.
  • Exposed on window.ScopeWeaveAnalytics.computeCpm for the upcoming UI.

Tests (tests/unit/cpm.test.mjs)

  • Classic AON example → critical path A→B→D→E, C has slack 2, project = 13d.
  • Date-based durations (no explicit duration).
  • Cycle X↔Y → no throw, cycleDetected true, no critical tasks.
  • Empty list / ghost predecessor handled.

Verified

npm run test:unit ✓ (analytics + cpm) · npm run test:api ✓ · app.js untouched/eval-safe ✓ · no new deps.

🤖 Generated with Claude Code

Slice 6a: the deepest 공정관리 moat. Pure engine + tests this tick; table/gantt
highlighting follows next.

- analytics.js computeCpm(tasks, {calcDuration?}) — dependency graph from an
  optional task.predecessors (array or comma-separated ids → task.id); duration
  from task.duration or plannedStart/End day-count (mirrors calculateDurationDays,
  or an injected calcDuration). Kahn topo-sort → forward pass (ES/EF), backward
  pass (LS/LF), slack=LS-ES, critical=slack≈0. Returns perTask{es,ef,ls,lf,
  slack,critical,duration} + projectDurationDays + criticalPath (ordered) +
  cycleDetected. Robust to cycles / missing predecessors — never throws.
- Exposed on window.ScopeWeaveAnalytics.computeCpm for the upcoming UI.
- tests/unit/cpm.test.mjs: classic AON example (critical path A→B→D→E, C slack 2,
  project 13d), date-based durations, cycle (no throw), empty/ghost-pred.
- package.json test:unit now runs analytics + cpm suites.

app.js untouched (still eval-safe). No new deps.

Stacked on #217.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018LMoqYsUY6usjNMBjvxCbU
@seonghobae seonghobae merged commit f59277f into feat/saas-billing Jul 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant