12 KiB
Calculation Reference
How every number in CapaKraken is derived. All monetary values are integer cents. All percentages are 0-100 integers unless noted.
1. Allocation Costs
Source: packages/engine/src/allocation/calculator.ts
| Metric | Formula | Notes |
|---|---|---|
| Daily Cost | round(hoursPerDay x lcrCents) |
LCR = Labor Cost Rate (cents/hour) |
| Total Cost | round(lcrCents x hoursPerDay x workingDays) |
Mon-Fri only, minus vacation days |
| Working Days | Calendar days minus weekends minus vacations | Sat included only if includeSaturday=true |
Inputs:
lcrCents— from Resource record (integer cents per hour)hoursPerDay— from Allocation recordstartDate/endDate— allocation period
Edge cases:
- Null LCR -> cost = 0
- Recurring allocations use
getRecurringHoursForDay()per-day override - Availability-aware: capped at resource's available hours for that weekday
- Vacation days block hours completely (reduce to 0 for that day)
2. Chargeability
2a. Allocation-level Chargeability
Source: packages/engine/src/allocation/chargeability.ts
availableHours = SUM(availability[weekday] for each working day in period)
bookedHours = SUM(min(allocation.hoursPerDay, availability[day]) for each overlapping day)
chargeability = availableHours > 0 ? min(100, round(bookedHours / availableHours x 100)) : 0
- Capped at 100 (overbooking not reflected)
- Only counts days where resource has availability > 0
2b. Forecast Chargeability (Chargeability Report)
Source: packages/engine/src/chargeability/calculator.ts
Per resource per month:
For each assignment overlapping the month:
hours = hoursPerDay x workingDays(in overlap)
categoryHours[categoryCode] += hours
chg = min(1, chargeableHours / SAH)
bd = min(1, bdHours / SAH)
mdi = min(1, mdiHours / SAH)
...
unassigned = min(1, max(0, SAH - totalAssigned) / SAH)
Categories come from the project's utilizationCategory.code. All ratios sum to <= 1.0.
2c. Group Chargeability (FTE-weighted)
groupChg = SUM(fte_i x chg_i) / SUM(fte_i)
Returns 0-1 ratio. Used for chapter/team aggregations.
3. Standard Available Hours (SAH)
Source: packages/engine/src/sah/calculator.ts
SAH is the denominator for all chargeability ratios.
For each day in period:
if weekend -> weekendDays++
else if holiday -> publicHolidayDays++
else if absent -> absenceDays++
else:
hoursForDay = getDailyHours(day, baseHours, scheduleRules) x FTE
totalHours += hoursForDay
netWorkingDays++
SAH = round(totalHours x 100) / 100
Schedule rules (e.g. Spain):
- Friday: reduced hours (
fridayHours) - Summer period: reduced hours (
summerHours) - Default:
regularHours
Components returned:
| Field | Meaning |
|---|---|
| calendarDays | Total days (inclusive) |
| weekendDays | Sat + Sun |
| grossWorkingDays | calendarDays - weekendDays |
| publicHolidayDays | Holidays falling on working days |
| absenceDays | Vacations/absences on working days |
| netWorkingDays | Gross - holidays - absences |
| effectiveHoursPerDay | Weighted average including FTE |
| standardAvailableHours | SAH (total for period) |
4. Budget
Source: packages/engine/src/budget/monitor.ts
For each allocation:
totalCents = dailyCostCents x workingDays(Mon-Fri)
CONFIRMED/ACTIVE -> confirmedCents += totalCents
PROPOSED -> proposedCents += totalCents
allocatedCents = confirmedCents + proposedCents
remainingCents = budgetCents - allocatedCents
utilizationPct = budgetCents > 0 ? (allocatedCents / budgetCents) x 100 : 0
winWeightedCents = round(allocatedCents x winProbability / 100)
Warning thresholds (packages/shared/src/constants/index.ts):
| Level | Threshold |
|---|---|
| INFO | >= 70% |
| WARNING | >= 85% |
| CRITICAL | >= 95% or exceeded |
5. Vacation Balance
Source: packages/api/src/router/entitlement.ts
Day counting
countDays(start, end, isHalfDay) =
isHalfDay ? 0.5 : round((end - start) / 86400000) + 1
Calendar days, inclusive. Half-day always = 0.5.
Carryover (lazy, on first access)
carryover = max(0, prev.entitledDays - prev.usedDays - prev.pendingDays)
new.entitledDays = defaultDays + carryover
Balance sync
usedDays = SUM(countDays(...)) where status=APPROVED and type in [ANNUAL, OTHER]
pendingDays = SUM(countDays(...)) where status=PENDING and type in [ANNUAL, OTHER]
remaining = max(0, entitledDays - usedDays - pendingDays)
sickDays = SUM(countDays(...)) where status=APPROVED and type=SICK (informational, does not consume balance)
6. Estimates
Source: packages/engine/src/estimate/metrics.ts
Per demand line
costTotalCents = round(hours x costRateCents)
priceTotalCents = round(hours x billRateCents)
Estimate summary
totalHours = SUM(line.hours)
totalCostCents = SUM(line.costTotalCents)
totalPriceCents = SUM(line.priceTotalCents)
marginCents = totalPriceCents - totalCostCents
marginPercent = totalPriceCents > 0 ? round(marginCents / totalPriceCents x 100) : 0
7. Effort Rules
Source: packages/engine/src/estimate/effort-rules.ts
Converts scope items into demand lines:
unitCount = case unitMode:
"per_frame" -> frameCount ?? 1
"per_item" -> itemCount ?? 1
"flat" -> 1
hours = round(unitCount x hoursPerUnit x 100) / 100
Rules match by scopeType (case-insensitive). Sorted by sortOrder. Unmatched scope items generate warnings.
Lines are aggregated by discipline + "::" + chapter.
8. Experience Multipliers
Source: packages/engine/src/estimate/experience-multiplier.ts
Rule matching (hierarchical specificity)
| Specificity | Filters present | Score |
|---|---|---|
| Most specific | chapter + location + level | 7 |
| chapter + location | 6 | |
| chapter + level | 5 | |
| chapter only | 4 | |
| location + level | 3 | |
| location only | 2 | |
| level only | 1 | |
| Least specific | none (global fallback) | 0 |
Highest score wins. Ties broken by order.
Rate adjustment
adjustedCostRate = round(costRateCents x costMultiplier)
adjustedBillRate = round(billRateCents x billMultiplier)
Shoring ratio
if shoringRatio > 0:
onsiteHours = hours x (1 - shoringRatio)
offshoreHours = hours x shoringRatio x (1 + additionalEffortRatio)
adjustedHours = round((onsiteHours + offshoreHours) x 100) / 100
9. Staffing Suggestions
Source: packages/staffing/src/skill-matcher.ts
Component scores (each 0-100)
Skill Score:
if no required skills: 100
else:
requiredScore = SUM((proficiency/5) x (70/requiredCount)) for matched skills
if preferred skills exist:
preferredScore = SUM((proficiency/5) x (30/preferredCount)) for matched
score = min(100, round(requiredScore + preferredScore))
else:
score = min(100, round(requiredScore / 70 x 100))
Availability Score:
no conflicts: 100
else: max(0, 100 - conflictDays x 10)
Cost Score:
no budget: 50 (neutral)
within budget: 100
over budget: max(0, round(100 - (ratio - 1) x 100))
where ratio = resourceLCR / budgetLCR
Utilization Score:
gap = target - current
gap >= 20: 100
gap >= 0: 60 + gap x 2
gap >= -20: max(0, 60 + gap x 2)
gap < -20: 0
Combined score (weights)
| Dimension | Weight |
|---|---|
| Skill | 40% |
| Availability | 30% |
| Cost | 20% |
| Utilization | 10% |
total = round(skill x 0.4 + availability x 0.3 + cost x 0.2 + utilization x 0.1)
Results sorted descending by total score.
10. Resource Value Score
Source: packages/staffing/src/value-scorer.ts
Five dimensions, each normalized to 0-100:
| Dimension | Formula | Weight |
|---|---|---|
| Skill Depth | round(avgProficiency / 5 x 100) |
30% |
| Skill Breadth | min(100, skillCount x 10) |
15% |
| Cost Efficiency | round((1 - lcrCents/maxLcrCents) x 100) |
25% |
| Chargeability | max(0, 100 - abs(target - current) x 2) |
15% |
| Experience | min(100, round(avgYears x 10)) |
15% |
total = clamp(0, 100, round(
depth x 0.30 + breadth x 0.15 + costEff x 0.25 + chg x 0.15 + exp x 0.15
))
Recomputed on demand with 90-day lookback for current chargeability.
11. Dashboard Metrics
Source: packages/application/src/use-cases/dashboard/
Overview
totalCostCents = SUM(dailyCostCents x inclusiveDays) for budget bookings
avgUtilizationPct = totalBudgetCents > 0 ? round(totalCost / totalBudget x 100) : 0
Peak Times
For each allocation, for each day in period:
bucket[weekKey or monthKey][group] += hoursPerDay
capacityHours = SUM(avgDailyAvailability) x (week=5, month=22)
Groups: by project (shortCode), chapter, or resource name.
Demand Analysis
demandFteFactor = percentage > 0 ? percentage/100 : (hoursPerDay/8)
allocatedHours = SUM(hoursPerDay x inclusiveDays)
requiredFTEs = SUM(headcount x demandFteFactor)
12. Column Definitions
Source: packages/shared/src/constants/columns.ts
Allocation Columns
| Key | Label | Default | Hideable | Derivation |
|---|---|---|---|---|
| resource | Resource | visible | no | assignment.resource.displayName |
| project | Project | visible | no | project.shortCode + project.name |
| role | Role | visible | yes | allocation.role (text label) |
| dates | Dates | visible | yes | startDate -> endDate formatted |
| hoursPerDay | h/day | visible | yes | allocation.hoursPerDay |
| cost | Daily Cost | hidden | yes | round(hoursPerDay x lcrCents) / 100 in EUR |
| status | Status | visible | yes | PROPOSED / CONFIRMED / ACTIVE / COMPLETED / CANCELLED |
Resource Columns
| Key | Label | Default | Derivation |
|---|---|---|---|
| displayName | Name | visible | Direct from DB |
| eid | EID | visible | Employee ID (anonymized when enabled) |
| chapter | Chapter | visible | Organizational unit |
| roles | Roles | visible | From ResourceRole join table |
| chargeability | Chargeability | visible | See Section 2a |
| lcr | LCR | hidden | Labor Cost Rate (cents/hour), requires VIEW_COSTS |
| valueScore | Score | hidden | See Section 10 |
| isActive | Status | visible | Boolean flag |
Project Columns
| Key | Label | Default | Derivation |
|---|---|---|---|
| shortCode | Code | visible | Direct from DB |
| name | Name | visible | Direct from DB |
| status | Status | visible | DRAFT / ACTIVE / COMPLETED / CANCELLED |
| orderType | Type | visible | CHARGEABLE / INTERNAL / INVESTMENT |
| dates | Dates | visible | startDate -> endDate |
| budget | Budget | hidden | project.budgetCents / 100 in EUR |
| allocations | Allocations | visible | Count of linked assignments + demands |
13. Rounding & Precision Rules
| Domain | Rule |
|---|---|
| Money | Integer cents everywhere. round(value) at calculation boundary |
| Hours | 2 decimal places: round(x * 100) / 100 |
| Percentages | Integer 0-100: round(ratio * 100) |
| Scores | Integer 0-100: round(weighted_sum), clamped |
| Days | Integer (or 0.5 for half-day vacations) |
| FTE | Decimal (e.g. 0.8), used as multiplier on SAH |
14. Constants
Source: packages/shared/src/constants/index.ts
DEFAULT_WORKING_HOURS_PER_DAY = 8
DEFAULT_AVAILABILITY = {
monday-friday: 8h each
saturday/sunday: 0h
}
BUDGET_WARNING_THRESHOLDS = { INFO: 70, WARNING: 85, CRITICAL: 95 }
STAFFING_SCORE_WEIGHTS = { SKILL: 0.4, AVAILABILITY: 0.3, COST: 0.2, UTILIZATION: 0.1 }
VALUE_SCORE_WEIGHTS = { SKILL_DEPTH: 0.30, BREADTH: 0.15, COST_EFF: 0.25, CHG: 0.15, EXP: 0.15 }