OKR Objectives & Key Results Module

OKR — Objectives & Key Results

BSC-aligned OKR platform with cascading objectives, goal state machine, INSERT-only track history, and real-time dashboards

10
Database Tables
38+
API Endpoints
M:N
Objective Alignment
INSERT
ONLY
Goal Track History

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

Strategy Execution Visibility

Real-time objective progress across org/team/personal from one dashboard — aligned to BSC perspectives

Alignment Transparency

Employees can see exactly which org objective their personal goal supports via MOKRALIGNMENT cascade view

Audit-Safe History

TGOALTRACK is INSERT-only — every progress update is preserved immutably, enabling trend charts and dispute resolution

Continuous Check-ins

Dual-side check-in (employee self + manager assessment) replaces once-a-year appraisal-only review

Key Concepts Glossary

TermDefinition
OKR CycleA time-bound period (Annual/Half-Yearly/Quarterly/Monthly) during which objectives and goals are tracked. Status: Draft → Active → Closed → Archived.
ObjectiveA 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.
ObjectiveType0=Personal, 1=Team, 2=Organization, 3=Overall — drives the 3-column dashboard layout.
BSC PerspectiveBalanced 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.
OverallProgressDenormalized weighted average of child goal progress on MOBJECTIVE. Updated automatically after every UpdateGoalProgress call.
MOKRALIGNMENTMany-to-many table linking objectives (Cascade/Support/Blocks). Replaces legacy single LinkedObjectiveId FK.
ConfidenceSnapshot field on TGOAL (0=Low, 1=Medium, 2=High) — updated with each track entry; avoids JOIN to history for current confidence display.
ForLevel / ForObjectIdOrganizational scope of an objective: 0=Employee, 1=Department, 2=OU, 3=Company. ForObjectId is the ID at that level.

Database Schema — Master Tables

MOKRCYCLE

ColumnTypeNotes
OKRCycleIdint PK
Code / NamenvarcharUnique Code per tenant
CycleStart / CycleEnddate
Frequencysmallint0=Annual, 1=HalfYearly, 2=Quarterly, 3=Monthly
Statussmallint0=Draft 1=Active 2=Closed 3=Archived
TenantIdintIndex: (TenantId, Status, CycleStart, CycleEnd)

MOKRPERSPECTIVE — BSC Perspectives (Client-Extensible)

ColumnNotes
PerspectiveId, Code, Name, DisplayOrder, StatusSeed rows: Financial, Customer, Internal Process, Learning & Growth. Clients can add Innovation, ESG, etc.
Key improvement over legacy: Legacy had BSC perspective as a hardcoded int enum. Master table allows client-customised perspectives and translated names without code changes.

MOBJECTIVE — Enhanced Objectives

ColumnTypeNotes
ObjectiveId, Code, Name, Descriptionint / nvarchar
OKRCycleIdint FK
PerspectiveIdint FKBSC quadrant
ObjectiveTypesmallint0=Personal, 1=Team, 2=Organization, 3=Overall
ForLevel / ForObjectIdsmallint / intOrg scope: 0=Employee, 1=Dept, 2=OU, 3=Company
OwnerId / ParentObjectiveIdintParentObjectiveId = -1 for root; self-referencing FK
TemplateIdint-1 = manually created; FK MOKRTEMPLATE
Prioritysmallint0=Low, 1=Medium, 2=High, 3=Critical
OverallProgressdecimal(5,2)Denormalized weighted avg of child goals — updated after every UpdateGoalProgress
Statussmallint0=NotStarted 1=OnTrack 2=OffTrack 3=NeedsWork 4=Achieved 5=Dropped
Indexes: (OKRCycleId, TenantId, ParentObjectiveId) for tree traversal; (OKRCycleId, ObjectiveType, TenantId) for dashboard column filter; (OKRCycleId, ForLevel, ForObjectId, TenantId) for scoped queries.

MOKRALIGNMENT — Many-to-Many Objective Links

ColumnNotes
SourceObjectiveId → TargetObjectiveIdChild/supporting → Parent/supported objective
AlignmentType0=Cascade, 1=Support, 2=Blocks
Why a separate table? An objective can align to multiple parents (e.g., a personal goal supports both a team OKR and a cross-functional initiative). The legacy single LinkedObjectiveId could not express this. Unique: (SourceObjectiveId, TargetObjectiveId, TenantId).

Transactional Tables

TGOAL — Key Results (Enhanced)

ColumnTypeNotes
GoalId, Code, Nameint / nvarchar
OKRCycleId / ObjectiveId / KRAIdint FKKRAId links to PERM; -1 = no KRA linkage
EmployeeIdintGoal owner
GoalTypesmallint0=Quantitative, 1=Qualitative
TargetValue / CurrentValuedecimalCurrentValue = denormalized from latest TGOALTRACK
CurrentProgressdecimal(5,2)Denormalized from latest TGOALTRACK.ProgressPercent — avoids JOIN on list queries
Weightagedecimal(5,2)% contribution to parent objective's OverallProgress
GoalSourceTypesmallint0=Manual, 1=Cascaded, 2=Template, 3=AppraisalLinked
ParentGoalIdint-1 = top-level; sub-goal nesting
ConfidencesmallintSnapshot on goal record: 0=Low, 1=Medium, 2=High — avoids track history JOIN
Statussmallint0=NotStarted 1=InProgress 2=OnTrack 3=AtRisk 4=Completed 5=Cancelled
Indexes: (OKRCycleId, EmployeeId, TenantId) for ESS "my goals"; (ObjectiveId, TenantId) for all goals in an objective; (OKRCycleId, KRAId, EmployeeId, TenantId) for PERM scoring lookup.

TGOALTRACK — Progress History (INSERT-ONLY)

ColumnNotes
GoalTrackId, GoalId, TrackedOnAppend-only; never UPDATE or DELETE any row
ProgressPercent, CurrentValue, RemarksState at time of tracking
TrackedById / EvaluatorIdWho logged; EvaluatorId=-1 if self-tracked
ConfidenceLevel0=Low, 1=Medium, 2=High — synced to TGOAL.Confidence
LovIdQualitative LOV selection; -1 if N/A
CRITICAL: TGOALTRACK is INSERT-ONLY. GoalTrackDAL must never contain UPDATE or DELETE statements. TGOAL.CurrentProgress and CurrentValue are denormalized from the latest entry for query performance.

TCHECKIN — Dual-Side Check-ins

ColumnNotes
CheckInId, GoalId, EmployeeId, ManagerId
SelfProgress / ManagerProgressEach side fills independently
SelfNotes / ManagerNotes / Topics (JSON)Topics: JSON array of agenda items
NextCheckInDateSchedule next meeting from within this record
Status0=Draft, 1=Submitted (employee), 2=Reviewed (manager)

MOKRTEMPLATE / MOKRGOALTEMPLATE — Objective Templates

ColumnNotes
TemplateId, Code, Name, PerspectiveId, ObjectiveTypeHR-created templates per BSC quadrant
GoalTemplateId, TemplateId, GoalName, GoalType, SortOrderDefault 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

Draft Created
→
Cycle Activated
→
Objectives Set
→
Goals Defined
→
Progress Updates
→
Check-ins
→
Close Cycle
→
Archived

Objective Hierarchy & Alignment

🏢 Organization ObjectiveGrow ARR by 20% (Financial Perspective)
👥 Team ObjectiveClose 15 Enterprise Deals (Sales Team)
👤 Personal GoalQualify 30 enterprise leads this quarter
👤 Personal GoalConduct 20 product demos to qualified leads
👥 Team ObjectiveReduce Churn Rate to <5% (CS Team)

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

0 NotStarted
→ 1-24%
1 InProgress
→ 25%+
2 OnTrack
→ 100%
4 Completed
2 OnTrack
↓ Confidence drop
3 AtRisk
→ re-track
2 OnTrack
5 Cancelled
← terminal; no further progress updates allowed
Correction rule: A Completed(4) goal can revert to OnTrack(2) if a corrected progress entry below 100% is submitted. This allows fixing accidental 100% entries.

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.

Weighted average (if any Weightage > 0):
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
Returns flat list with ParentObjectiveId and TreeLevel — the frontend builds the visual tree. Never serialize as a nested object graph (unbounded depth causes stack overflow / serialization explosion for deep hierarchies).

API Endpoints Reference

OKR Cycle

MethodRouteParamsCache
GET/OKR/Cycle/GetOKRCycleListStatus?CLIENT_LEVEL 5 min
GET/OKR/Cycle/GetSelectListOKRCycle—CLIENT_LEVEL
POST/OKR/Cycle/SaveOKRCycleOKRCycleDTOnull
POST/OKR/Cycle/CloseOKRCycle{OKRCycleId}null — triggers cascade + Dapr

Objectives

MethodRouteCache
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 / SaveAlignmentnull
DELETE/OKR/Objective/DeleteObjective / DeleteAlignmentnull

Goals (Key Results)

MethodRouteNotesCache
GET/OKR/Goal/GetGoalListObjectiveId?, EmployeeId?, OKRCycleId, Status?USER_LEVEL 2 min
GET/OKR/Goal/GetMyGoalsESS — EmployeeId from login.UserId onlyUSER_LEVEL 2 min
POST/OKR/Goal/UpdateGoalProgressGoalId, ProgressPercent, CurrentValue, Remarks, ConfidenceLevelnull — triggers state machine + rollup
POST/OKR/Goal/UpdateGoalConfidenceGoalId, ConfidenceLevel — quick updatenull
POST/OKR/Goal/LinkGoalToTaskGoalId, TaskId → MOKRTASKLINK junctionnull

Check-ins, Dashboard & Analytics

MethodRouteCache
GET/OKR/CheckIn/GetMyCheckIns — {OKRCycleId?} ESSUSER_LEVEL 3 min
POST/OKR/CheckIn/SubmitCheckIn / ReviewCheckInnull
GET/OKR/Dashboard/GetOKRDashboard — {OKRCycleId, OUID?}NOT_REQUIRED
GET/OKR/Dashboard/GetMyOKRDashboard — {OKRCycleId} ESSUSER_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)

DateProgress %Current ValueConfidenceRemarks
2026-01-1010%3LowJust started, pipeline thin
2026-01-3127%8MediumFirst enterprise meetings scheduled
2026-02-1550%15HighGood momentum from partner referrals
2026-03-0167%20High2 large deals about to close
2026-03-1575%22.5HighStrong pipeline entering Q3
All 5 rows are separate TGOALTRACK inserts — none were updated. TGOAL.CurrentProgress = 75 is denormalized from the latest row.

OKR Templates

BSC PerspectiveTemplate NameDefault Key Results
FinancialGrow Revenue by X%Increase MRR, Reduce CAC, Improve NRR
CustomerImprove NPS ScoreIncrease NPS by 10pts, Reduce support tickets, Improve CSAT
Internal ProcessReduce Cycle TimeAutomate X manual steps, Reduce lead time, Deploy faster
Learning & GrowthUpskill TeamComplete certification, Hours of training, New skills adopted
Learning & GrowthImprove RetentioneNPS target, Manager score, Development plan coverage %

Integration Map

PERM Module
Reads TGOAL via IGoalDAL.GetGoalProgressForEmployee(employeeId, okrCycleId, kraId) — DAL-level ref only, no BLL-BLL circular dep
OKR
Port 5122
/okr/ proxy prefix
Analytics Module
Dapr: okr.cycle.closed → PerformanceKPISubscriber ingests cycle KPI data into TPERFKPIFEED

Dapr Events

TopicPublished BySubscribers
okr.goal.progress.updatedGoalBLL.UpdateGoalProgressESS push, manager dashboard refresh
okr.checkin.reviewedCheckInBLL.ReviewCheckInESS push to employee
okr.cycle.closedOKRCycleBLL.CloseOKRCycleAnalytics KPI feed

Caching Strategy

DataLevelTTLInvalidation
MOKRCYCLE listCLIENT_LEVEL5 minSave, Close
MOKRPERSPECTIVECLIENT_LEVEL30 minSave
MOBJECTIVE tree/listNOT_REQUIRED—Changes frequently during active cycle
TGOAL per employeeUSER_LEVEL2 minSave, UpdateGoalProgress
TGOALTRACK historyNOT_REQUIRED—INSERT-only; always live
TCHECKIN listsUSER_LEVEL3 minSave, Submit, Review
OKR DashboardNOT_REQUIRED—Live data
TemplatesCLIENT_LEVEL10 minSaveTemplate

Legacy GB4 vs GB5 OKR

FeatureLegacy GB4GB5 OKR
BSC PerspectiveHardcoded int enum (1-4)MOKRPERSPECTIVE master — client-extensible, translateable
Objective cascadingLinkedObjectiveId (one parent only)MOKRALIGNMENT table — many-to-many Cascade/Support/Blocks
Dashboard groupingNo explicit ObjectiveType labelObjectiveType field drives 3-column Org/Team/Personal dashboard
Goal progress queriesJOIN to TGOALTRACK on every list queryCurrentProgress + CurrentValue denormalized on TGOAL
Confidence displayOnly in track history (expensive lookup)Confidence snapshot on TGOAL — zero JOIN for current state
Objective templatesNoneMOKRTEMPLATE + MOKRGOALTEMPLATE per BSC perspective
Task linkingSingle TaskId FK on TGOALMOKRTASKLINK junction — one goal → many tasks
Progress historyBasic numeric trackConfidenceLevel + EvaluatorId per track entry; INSERT-only audit