Most scheduling tools assume the schedule they are handed is sound. Most are not. BBSchedule computes the critical path properly — topologically ordered passes, all four relationship types, total and free float — then grades the network against the DCMA 14-point assessment and refuses to optimise one that fails.
Live — computed in your browser
This is the demo project shipped with the repository: a 25-activity commercial fit-out, deliberately built with the defects real schedules carry. Tick a repair and the forward and backward passes rerun, float redistributes, and the assessment updates. Nothing is precomputed.
core/ — pure Python, no Flask, no database
The forward and backward passes run in topological order. The implementation this replaced walked activities in list order, so a predecessor declared after its successor was read before it had been computed — and the critical path silently depended on database row ordering.
Finish-to-Start, Start-to-Start, Finish-to-Finish and Start-to-Finish, each with positive lag or negative lead. Overlapping work is modelled as overlapping, not flattened into a sequence.
Total float is how far an activity can slip before the project moves. Free float is how far it can slip before any successor moves. They differ, they mean different things on site, and both are computed.
A circular dependency raises an error identifying the specific loop, rather than producing a plausible-looking schedule built on infinite float.
Configurable working weekdays and holidays. Finish dates are the last day worked — a five-day activity starting Monday finishes Friday, which is how planners read it.
Four DCMA checks need recorded baseline and actual dates the schema does not yet hold. They report as skipped with a reason and are excluded from the score — never passed vacuously.
Positioning
Handing a language model a list of task rows and asking for "critical path analysis" invites it to invent one. Here, schedule analysis and forecasting run CPM and the quality assessment first, then pass the results into the prompt as given facts. The system prompt forbids claiming a saving on an activity that is not on the critical path, and the optimiser refuses to run at all against a network whose logic fails the blocking checks.
Optimising an unsound schedule produces confident nonsense. Treating the deterministic engine as the product — and the model as a presentation layer over it — is the difference between AI that helps and AI that launders guesswork.
HTTP
Every endpoint is company-scoped. A request for another tenant's project is byte-for-byte indistinguishable from a request for one that does not exist.
| Endpoint | Returns |
|---|---|
GET /api/schedule/projects/<id>/cpm |
Every activity with early and late dates, total float, free float, criticality |
GET /api/schedule/projects/<id>/health |
DCMA 14-point assessment, grade, and the offending activities per check |
GET /api/schedule/projects/<id>/critical-path |
The driving path only, for chart overlays |
from core import Activity, Relationship, RelationType, calculate_cpm, assess_schedule activities = [ Activity("A", "Excavate", 5), Activity("B", "Pour footings", 8), Activity("C", "Cure", 7), ] links = [ Relationship("A", "B"), Relationship("B", "C", RelationType.FS, lag=2), ] result = calculate_cpm(activities, links) result.project_duration # 22 result.critical_path # ['A', 'B', 'C'] result.activities["B"].total_float # 0 report = assess_schedule(activities, links) report.grade, report.is_optimisable
Where it stands
Being specific about which is which matters more than claiming everything works.