OKR — Objectives & Key Results
BSC-aligned OKR platform with cascading objectives, goal state machine, INSERT-only track history, and real-time dashboards
ONLY
Vision
OKR replaces legacy GB4's basic goal/objective system with a full BSC-aligned platform. Objectives cascade from organization → team → personal with many-to-many alignment links. Goals (Key Results) carry INSERT-only progress history for audit integrity. Check-ins replace static annual reviews with continuous manager-employee dialogue. Templates seed common objectives per BSC perspective, reducing blank-slate friction.
Business Value
Real-time objective progress across org/team/personal from one dashboard — aligned to BSC perspectives
Employees can see exactly which org objective their personal goal supports via MOKRALIGNMENT cascade view
TGOALTRACK is INSERT-only — every progress update is preserved immutably, enabling trend charts and dispute resolution
Dual-side check-in (employee self + manager assessment) replaces once-a-year appraisal-only review
Key Concepts Glossary
| Term | Definition |
|---|---|
| OKR Cycle | A time-bound period (Annual/Half-Yearly/Quarterly/Monthly) during which objectives and goals are tracked. Status: Draft → Active → Closed → Archived. |
| Objective | A qualitative goal statement (what we want to achieve). Belongs to a BSC perspective and an ObjectiveType. Can have parent objectives (hierarchy) and alignment links (MOKRALIGNMENT). |
| Key Result / Goal (TGOAL) | A measurable key result under an objective. Has TargetValue, CurrentProgress (denormalized from latest track), and a Status state machine. |
| ObjectiveType | 0=Personal, 1=Team, 2=Organization, 3=Overall — drives the 3-column dashboard layout. |
| BSC Perspective | Balanced Scorecard quadrant. Stored in MOKRPERSPECTIVE master (not a hardcoded enum) — clients can add custom perspectives (e.g., Innovation, ESG). |
| GoalTrack (TGOALTRACK) | INSERT-ONLY table of progress entries. TGOAL.CurrentProgress is denormalized from the latest entry. Never UPDATE or DELETE. |
| Check-in (TCHECKIN) | Dual-side meeting note: employee fills SelfProgress/SelfNotes; manager fills ManagerProgress/ManagerNotes. Status: Draft → Submitted → Reviewed. |
| OverallProgress | Denormalized weighted average of child goal progress on MOBJECTIVE. Updated automatically after every UpdateGoalProgress call. |
| MOKRALIGNMENT | Many-to-many table linking objectives (Cascade/Support/Blocks). Replaces legacy single LinkedObjectiveId FK. |
| Confidence | Snapshot field on TGOAL (0=Low, 1=Medium, 2=High) — updated with each track entry; avoids JOIN to history for current confidence display. |
| ForLevel / ForObjectId | Organizational scope of an objective: 0=Employee, 1=Department, 2=OU, 3=Company. ForObjectId is the ID at that level. |
Database Schema — Master Tables
MOKRCYCLE
| Column | Type | Notes |
|---|---|---|
| OKRCycleId | int PK | |
| Code / Name | nvarchar | Unique Code per tenant |
| CycleStart / CycleEnd | date | |
| Frequency | smallint | 0=Annual, 1=HalfYearly, 2=Quarterly, 3=Monthly |
| Status | smallint | 0=Draft 1=Active 2=Closed 3=Archived |
| TenantId | int | Index: (TenantId, Status, CycleStart, CycleEnd) |
MOKRPERSPECTIVE — BSC Perspectives (Client-Extensible)
| Column | Notes |
|---|---|
| PerspectiveId, Code, Name, DisplayOrder, Status | Seed rows: Financial, Customer, Internal Process, Learning & Growth. Clients can add Innovation, ESG, etc. |
MOBJECTIVE — Enhanced Objectives
| Column | Type | Notes |
|---|---|---|
| ObjectiveId, Code, Name, Description | int / nvarchar | |
| OKRCycleId | int FK | |
| PerspectiveId | int FK | BSC quadrant |
| ObjectiveType | smallint | 0=Personal, 1=Team, 2=Organization, 3=Overall |
| ForLevel / ForObjectId | smallint / int | Org scope: 0=Employee, 1=Dept, 2=OU, 3=Company |
| OwnerId / ParentObjectiveId | int | ParentObjectiveId = -1 for root; self-referencing FK |
| TemplateId | int | -1 = manually created; FK MOKRTEMPLATE |
| Priority | smallint | 0=Low, 1=Medium, 2=High, 3=Critical |
| OverallProgress | decimal(5,2) | Denormalized weighted avg of child goals — updated after every UpdateGoalProgress |
| Status | smallint | 0=NotStarted 1=OnTrack 2=OffTrack 3=NeedsWork 4=Achieved 5=Dropped |
MOKRALIGNMENT — Many-to-Many Objective Links
| Column | Notes |
|---|---|
| SourceObjectiveId → TargetObjectiveId | Child/supporting → Parent/supported objective |
| AlignmentType | 0=Cascade, 1=Support, 2=Blocks |
Transactional Tables
TGOAL — Key Results (Enhanced)
| Column | Type | Notes |
|---|---|---|
| GoalId, Code, Name | int / nvarchar | |
| OKRCycleId / ObjectiveId / KRAId | int FK | KRAId links to PERM; -1 = no KRA linkage |
| EmployeeId | int | Goal owner |
| GoalType | smallint | 0=Quantitative, 1=Qualitative |
| TargetValue / CurrentValue | decimal | CurrentValue = denormalized from latest TGOALTRACK |
| CurrentProgress | decimal(5,2) | Denormalized from latest TGOALTRACK.ProgressPercent — avoids JOIN on list queries |
| Weightage | decimal(5,2) | % contribution to parent objective's OverallProgress |
| GoalSourceType | smallint | 0=Manual, 1=Cascaded, 2=Template, 3=AppraisalLinked |
| ParentGoalId | int | -1 = top-level; sub-goal nesting |
| Confidence | smallint | Snapshot on goal record: 0=Low, 1=Medium, 2=High — avoids track history JOIN |
| Status | smallint | 0=NotStarted 1=InProgress 2=OnTrack 3=AtRisk 4=Completed 5=Cancelled |
TGOALTRACK — Progress History (INSERT-ONLY)
| Column | Notes |
|---|---|
| GoalTrackId, GoalId, TrackedOn | Append-only; never UPDATE or DELETE any row |
| ProgressPercent, CurrentValue, Remarks | State at time of tracking |
| TrackedById / EvaluatorId | Who logged; EvaluatorId=-1 if self-tracked |
| ConfidenceLevel | 0=Low, 1=Medium, 2=High — synced to TGOAL.Confidence |
| LovId | Qualitative LOV selection; -1 if N/A |
TCHECKIN — Dual-Side Check-ins
| Column | Notes |
|---|---|
| CheckInId, GoalId, EmployeeId, ManagerId | |
| SelfProgress / ManagerProgress | Each side fills independently |
| SelfNotes / ManagerNotes / Topics (JSON) | Topics: JSON array of agenda items |
| NextCheckInDate | Schedule next meeting from within this record |
| Status | 0=Draft, 1=Submitted (employee), 2=Reviewed (manager) |
MOKRTEMPLATE / MOKRGOALTEMPLATE — Objective Templates
| Column | Notes |
|---|---|
| TemplateId, Code, Name, PerspectiveId, ObjectiveType | HR-created templates per BSC quadrant |
| GoalTemplateId, TemplateId, GoalName, GoalType, SortOrder | Default key results per template |
Seed templates: "Grow Revenue by X%", "Improve NPS Score", "Reduce Cycle Time", "Upskill Team in [Technology]", "Achieve Compliance Certification"
OKR Cycle Lifecycle
Objective Hierarchy & Alignment
MOKRALIGNMENT stores these relationships as rows. A personal goal can align to multiple team objectives (many-to-many). The GetObjectiveAlignment endpoint returns the full cascade graph for visualization.
Goal Status Machine
State Machine SQL (GoalQB)
-- Derive new status from progress percentage
UPDATE TGOAL
SET CurrentProgress = @ProgressPercent,
CurrentValue = @CurrentValue,
Confidence = @ConfidenceLevel,
Status = CASE
WHEN @ProgressPercent = 0 THEN 0 -- NotStarted
WHEN @ProgressPercent < 25 THEN 1 -- InProgress
WHEN @ProgressPercent < 100 THEN 2 -- OnTrack
WHEN @ProgressPercent = 100 THEN 4 -- Completed
END
WHERE GoalId = @GoalId AND TenantId = @TenantId AND Status != 5 -- guard: never update Cancelled
Objective Progress Rollup
Called automatically inside GoalBLL.UpdateGoalProgress after every track entry — never exposed as a public endpoint.
OverallProgress = SUM(CurrentProgress × Weightage) / SUM(Weightage)
Simple average (if all Weightage = 0):
OverallProgress = AVG(CurrentProgress) — excluding Cancelled goals (Status != 5)
Objective Status derivation:
0% → NotStarted (0)
1–49% → OnTrack (1)
50–74% → NeedsWork (3)
75–99% → OnTrack (1)
100% → Achieved (4)
CloseOKRCycle Cascade (Transaction)
① Auto-complete 100% goals
SET Status=4 WHERE CurrentProgress=100 AND Status!=5 for all goals in cycle
② Final objective rollup
Run UpdateObjectiveOverallProgress for all objectives in the cycle
③ PERM linkage check
If cycle is linked to active MAPPRAISALPLAN: GoalEvaluatorPercentage already live on TGOAL — no extra action needed. PERM reads via GetGoalProgressForEmployee at scoring time.
④ SET MOKRCYCLE.Status = 2 (Closed)
Committed with all above in single transaction
⑤ Publish okr.cycle.closed
Outside transaction; Analytics subscriber ingests into TPERFKPIFEED
Recursive CTE — Objective Tree
-- ObjectiveQB.GET_OBJECTIVE_TREE
WITH ObjectiveTree AS (
-- Anchor: root objectives (no parent)
SELECT o.*, 0 AS TreeLevel
FROM MOBJECTIVE o
WHERE o.ParentObjectiveId = -1
AND o.OKRCycleId = @OKRCycleId
AND o.TenantId = @TenantId
UNION ALL
-- Recursive: children
SELECT o.*, ot.TreeLevel + 1
FROM MOBJECTIVE o
INNER JOIN ObjectiveTree ot ON o.ParentObjectiveId = ot.ObjectiveId
WHERE o.TenantId = @TenantId
)
SELECT * FROM ObjectiveTree
ORDER BY TreeLevel, ObjectiveId
OPTION (MAXRECURSION 10) -- guard against runaway hierarchy; 10 levels is ample
API Endpoints Reference
OKR Cycle
| Method | Route | Params | Cache |
|---|---|---|---|
| GET | /OKR/Cycle/GetOKRCycleList | Status? | CLIENT_LEVEL 5 min |
| GET | /OKR/Cycle/GetSelectListOKRCycle | — | CLIENT_LEVEL |
| POST | /OKR/Cycle/SaveOKRCycle | OKRCycleDTO | null |
| POST | /OKR/Cycle/CloseOKRCycle | {OKRCycleId} | null — triggers cascade + Dapr |
Objectives
| Method | Route | Cache |
|---|---|---|
| GET | /OKR/Objective/GetObjectiveList — {OKRCycleId, ObjectiveType?, OwnerId?, ForLevel?, ForObjectId?} | USER_LEVEL 2 min |
| GET | /OKR/Objective/GetObjectiveTree — {OKRCycleId} | NOT_REQUIRED (flat list, recursive CTE) |
| GET | /OKR/Objective/GetObjectiveAlignment — {OKRCycleId} | NOT_REQUIRED |
| POST | /OKR/Objective/SaveObjective / SaveAlignment | null |
| DELETE | /OKR/Objective/DeleteObjective / DeleteAlignment | null |
Goals (Key Results)
| Method | Route | Notes | Cache |
|---|---|---|---|
| GET | /OKR/Goal/GetGoalList | ObjectiveId?, EmployeeId?, OKRCycleId, Status? | USER_LEVEL 2 min |
| GET | /OKR/Goal/GetMyGoals | ESS — EmployeeId from login.UserId only | USER_LEVEL 2 min |
| POST | /OKR/Goal/UpdateGoalProgress | GoalId, ProgressPercent, CurrentValue, Remarks, ConfidenceLevel | null — triggers state machine + rollup |
| POST | /OKR/Goal/UpdateGoalConfidence | GoalId, ConfidenceLevel — quick update | null |
| POST | /OKR/Goal/LinkGoalToTask | GoalId, TaskId → MOKRTASKLINK junction | null |
Check-ins, Dashboard & Analytics
| Method | Route | Cache |
|---|---|---|
| GET | /OKR/CheckIn/GetMyCheckIns — {OKRCycleId?} ESS | USER_LEVEL 3 min |
| POST | /OKR/CheckIn/SubmitCheckIn / ReviewCheckIn | null |
| GET | /OKR/Dashboard/GetOKRDashboard — {OKRCycleId, OUID?} | NOT_REQUIRED |
| GET | /OKR/Dashboard/GetMyOKRDashboard — {OKRCycleId} ESS | USER_LEVEL 2 min |
| GET | /OKR/Analytics/GetOKRStatusDistribution — {OKRCycleId} | NOT_REQUIRED |
| GET | /OKR/Analytics/GetPerspectiveBreakdown — {OKRCycleId} | NOT_REQUIRED |
| GET | /OKR/Trend/GetGoalProgressTrend — {GoalId} | NOT_REQUIRED |
| GET | /OKR/Trend/GetOKRCycleTrend — {EmployeeId, OKRCycleId} | NOT_REQUIRED |
Sample Scenarios
Scenario A — Q1 FY2026 OKR Setup
Org Objective — Financial
Grow ARR by 20%
63% progress · OnTrack · 3 team objectives aligned
Team Objective — Sales
Close 15 Enterprise Deals
53% progress · NeedsWork · 8/15 deals closed
Personal Goal — Alice (SDR)
Qualify 30 Enterprise Leads
80% · 24/30 leads · Confidence: High
Scenario B — Goal Progress Update (INSERT-only TGOALTRACK)
POST /OKR/Goal/UpdateGoalProgress
{
"GoalId": 501,
"ProgressPercent": 75.0,
"CurrentValue": 22.5,
"Remarks": "Strong pipeline entering Q3 — 22 of 30 leads qualified",
"ConfidenceLevel": 2
}
-- Result:
-- 1. INSERT TGOALTRACK (GoalTrackId: NEW, GoalId: 501, ProgressPercent: 75, CurrentValue: 22.5,
-- ConfidenceLevel: 2, TrackedOn: 2026-03-15, TrackedById: 101)
-- 2. UPDATE TGOAL SET CurrentProgress=75, CurrentValue=22.5, Confidence=2, Status=2 (OnTrack)
-- 3. ObjectiveId 200 → OverallProgress recalculated from weighted avg of all goals
-- 4. Publish Dapr: okr.goal.progress.updated
-- Previous TGOALTRACK rows for GoalId 501 are NEVER updated or deleted
Scenario C — BSC Dashboard View
💰 Financial
72% — 3 objectives, 12 goals
🤝 Customer
58% — 2 objectives, 8 goals
⚙️ Internal Process
85% — 4 objectives, 16 goals
📚 Learning & Growth
45% — 2 objectives, 6 goals · ⚠️ NeedsWork
Scenario D — Goal Progress Trend (TGOALTRACK history)
| Date | Progress % | Current Value | Confidence | Remarks |
|---|---|---|---|---|
| 2026-01-10 | 10% | 3 | Low | Just started, pipeline thin |
| 2026-01-31 | 27% | 8 | Medium | First enterprise meetings scheduled |
| 2026-02-15 | 50% | 15 | High | Good momentum from partner referrals |
| 2026-03-01 | 67% | 20 | High | 2 large deals about to close |
| 2026-03-15 | 75% | 22.5 | High | Strong pipeline entering Q3 |
OKR Templates
| BSC Perspective | Template Name | Default Key Results |
|---|---|---|
| Financial | Grow Revenue by X% | Increase MRR, Reduce CAC, Improve NRR |
| Customer | Improve NPS Score | Increase NPS by 10pts, Reduce support tickets, Improve CSAT |
| Internal Process | Reduce Cycle Time | Automate X manual steps, Reduce lead time, Deploy faster |
| Learning & Growth | Upskill Team | Complete certification, Hours of training, New skills adopted |
| Learning & Growth | Improve Retention | eNPS target, Manager score, Development plan coverage % |
Integration Map
/okr/ proxy prefix
Dapr Events
| Topic | Published By | Subscribers |
|---|---|---|
| okr.goal.progress.updated | GoalBLL.UpdateGoalProgress | ESS push, manager dashboard refresh |
| okr.checkin.reviewed | CheckInBLL.ReviewCheckIn | ESS push to employee |
| okr.cycle.closed | OKRCycleBLL.CloseOKRCycle | Analytics KPI feed |
Caching Strategy
| Data | Level | TTL | Invalidation |
|---|---|---|---|
| MOKRCYCLE list | CLIENT_LEVEL | 5 min | Save, Close |
| MOKRPERSPECTIVE | CLIENT_LEVEL | 30 min | Save |
| MOBJECTIVE tree/list | NOT_REQUIRED | — | Changes frequently during active cycle |
| TGOAL per employee | USER_LEVEL | 2 min | Save, UpdateGoalProgress |
| TGOALTRACK history | NOT_REQUIRED | — | INSERT-only; always live |
| TCHECKIN lists | USER_LEVEL | 3 min | Save, Submit, Review |
| OKR Dashboard | NOT_REQUIRED | — | Live data |
| Templates | CLIENT_LEVEL | 10 min | SaveTemplate |
Legacy GB4 vs GB5 OKR
| Feature | Legacy GB4 | GB5 OKR |
|---|---|---|
| BSC Perspective | Hardcoded int enum (1-4) | MOKRPERSPECTIVE master — client-extensible, translateable |
| Objective cascading | LinkedObjectiveId (one parent only) | MOKRALIGNMENT table — many-to-many Cascade/Support/Blocks |
| Dashboard grouping | No explicit ObjectiveType label | ObjectiveType field drives 3-column Org/Team/Personal dashboard |
| Goal progress queries | JOIN to TGOALTRACK on every list query | CurrentProgress + CurrentValue denormalized on TGOAL |
| Confidence display | Only in track history (expensive lookup) | Confidence snapshot on TGOAL — zero JOIN for current state |
| Objective templates | None | MOKRTEMPLATE + MOKRGOALTEMPLATE per BSC perspective |
| Task linking | Single TaskId FK on TGOAL | MOKRTASKLINK junction — one goal → many tasks |
| Progress history | Basic numeric track | ConfidenceLevel + EvaluatorId per track entry; INSERT-only audit |