# PerformanceReviews Module Performance review cycles: cycle creation and management, form templates per evaluation type, reviewer assignment, form reviews (self, peer, subordinate, others), scoring, nine-box, calibration, external reviewers, statistics, and reports. **After finishing any implementation task, evaluate:** - New service added or responsibility shifted → update Service Map + Services — Detailed Reference - New or modified critical flow (fill, calibration claim, cycle state transition) → update Key Patterns or add to Dangerous Zones - New race condition or silent failure risk identified → add to Dangerous Zones - New endpoint or auth context → update HTTP Endpoints table - New integration test file → update Integration Tests table If any apply → **ask the user** if they want to update AGENTS.md (never update automatically). If none apply, skip silently. Skip for routine bug fixes. --- ## Domain Model ``` PerformanceReviewCycle (instanceId, status, goalCycleId) └── PerformanceReviewFormTemplate (evaluationType, coreFormId, scoring config, withCalibration) └── PerformanceReviewTargetUser (reviewedId, responsibleId, shareStatus) └── PerformanceReviewFormReview (type, status, reviewerId, coreFormAnswerId) ├── PerformanceReviewGoalQuestionWithAnswer └── PerformanceReviewCompetencyQuestionWithAnswer └── PerformanceReviewReviewer (reviewedId, reviewerId, type) └── PerformanceReviewExternalReviewer (cycleId, instanceId, accessLink) └── PerformanceReviewFormReviewEditRequest (formReviewId, status) PerformanceReviewCalibrationRule (cycleId, calibratorsAudience, calibratedAudience) [UUID v7 PK] └── PerformanceReviewCalibrationFormReviewRule (formReviewId ↔ calibrationRuleId) [junction] PerformanceReviewTemplate (reusable backoffice template) PerformanceReviewUser (module-local user projection for CDC sync) ``` > Calibration model detail → see [./AGENTS-calibration.md](./AGENTS-calibration.md) ### Key enums | Concept | Values | |---------|--------| | Cycle status | `DRAFT`, `PENDING`, `SELECTION`, `IN_PROGRESS`, `FINISHED` | | Evaluation types | `SELF_REVIEW`, `PEER_REVIEW`, `SUBORDINATE_REVIEW`, `OTHERS_REVIEW`, `CALIBRATION_REVIEW` | | FormReview status | `PENDING`, `FINISHED`, `EDIT_REQUESTED`, `EDIT_PENDING` | | Share status | `SHARE_DISABLED`, `SHARED`, `CONFIRMED` | --- ## Dangerous Zones 1. **`PerformanceReviewsService` ~4300 lines — god service.** Search the exact method before touching. Many flows live there (fill, scoring, nine-box, CDC delegation, external reviewers). Don't add to it unless the logic genuinely crosses multiple sub-domains — check if `PerformanceReviewCalibrationService` or a new focused service is a better home first. 2. **Circular dependency between `PerformanceReviewsService` and `PerformanceReviewCalibrationService`.** Each calls methods on the other. Resolved with TypeDI's lazy `@Inject(() => PerformanceReviewsService)` setter in the calibration service (not constructor injection). If you add a new cross-service call, make sure you don't create a true circular constructor dependency — always use the lazy setter pattern on the calibration side. > Calibration-specific dangerous zones (atomic claim, orphaned FormReviews, queue stubs, `withCalibration` access, cycle status guard) → see [./AGENTS-calibration.md](./AGENTS-calibration.md) --- ## Directory Structure ``` performanceReviews/ ├── business/ │ ├── interfaces/ # 24 input interfaces for services (cycles, reviewers, fill, duplication, etc.) │ ├── models/ # 16 domain models (ForCreation + entity; incl. calibration) │ ├── ports/ # 11 abstract ports (dependency inversion) │ ├── services/ # 5 services │ └── types/ # 37 files: enums, form config, scopes, stats ├── infrastructure/ │ ├── adapters/ # 10 adapters (8 standalone + 2 under repositories/) │ ├── dataAccessObjects/ # 13 Sequelize DAOs │ └── mappers/ # 13 pure mappers DAO ↔ domain ├── presentation/ │ ├── controllers/ # 2 controllers (main + calibration) │ ├── routers/ # 6 router functions (app, backoffice, public, devops, calibration rules, calibration review) │ ├── validationClasses/ # 37 VCs + decorators/ │ ├── serializationClasses/ # 43 SCs │ ├── dataTransferObjects/ # 23 DTOs │ ├── types/response.ts │ └── interfaces/locals.ts └── performanceReviewsPortsDI.ts # Port → Adapter DI (10 entries) ``` ## Service Map — Which Service Owns Each Feature | Feature Area | Service | |---|---| | Cycle CRUD (create, edit, draft → start → finish) | `PerformanceReviewsService` | | Form templates per evaluation type | `PerformanceReviewsService` | | Target users (reviewees): add, remove, list | `PerformanceReviewsService` | | Reviewer assignment (including peers) | `PerformanceReviewsService` | | FormReviews: create, list, fill, confirm, edit requests | `PerformanceReviewsService` | | Scoring, weights, nine-box | `PerformanceReviewsService` | | Cycle, team, and reviewer statistics | `PerformanceReviewsService` | | Sharing reviews with reviewees | `PerformanceReviewsService` | | External reviewers (invite, external fill) | `PerformanceReviewsService` | | Cycle duplication | `PerformanceReviewsService` | | Goals / competencies / forms / notifications integration | `PerformanceReviewsService` via ports | | Calibration rules CRUD | `PerformanceReviewCalibrationService` (detail → [AGENTS-calibration.md](./AGENTS-calibration.md)) | | Create calibration FormReviews when adding a target user | `PerformanceReviewCalibrationService` (detail → [AGENTS-calibration.md](./AGENTS-calibration.md)) | | List calibrated users visible to a calibrator (app) | `PerformanceReviewCalibrationService` (detail → [AGENTS-calibration.md](./AGENTS-calibration.md)) | | Calibration detail (subordinate snapshot) | `PerformanceReviewCalibrationService` (detail → [AGENTS-calibration.md](./AGENTS-calibration.md)) | | Fill calibration (atomic claim + fill) | `PerformanceReviewCalibrationService` (detail → [AGENTS-calibration.md](./AGENTS-calibration.md)) | | Sync `PerformanceReviewUsers` (CDC Kafka) | `PerformanceReviewsCDCService` | | SQS consumer (`PERFORMANCE_REVIEW` queue) | `PerformanceReviewQueueConsumer` | | Queue message handlers (calibration membership sync for user/segmentation changes; other message types still stubs) | `PerformanceReviewQueueService` | --- ## Services — Detailed Reference ### `PerformanceReviewsService` Main service (~4300 lines). Orchestrates the full lifecycle of a performance review. **Cycles** - `findCycleByPk()` — Fetches cycle by PK and validates it belongs to the instance; `throwIfNotFound` controls whether it throws or returns null - `createCycle()` — Creates a cycle in DRAFT status with its `PerformanceReviewFormTemplates` per evaluation type - `editCycle()` — Edits cycle metadata; only allowed in DRAFT - `editCyclePartially()` — Partial metadata update - `deleteCycle()` — Deletes cycle; only allowed in DRAFT - `startCycle()` — Moves cycle to IN_PROGRESS; validates configuration and assigned reviewers - `finishCycle()` — Closes the cycle (FINISHED); validates all reviews are complete - `listCycles()` — Paginated list for backoffice/app with status filters - `duplicateCycle()` — Copies a cycle (templates, config) as a new draft **Form Templates** - `getFormTemplate()` — Gets the template for an evaluation type within the cycle - `findFormTemplateByEvaluationType()` — Finds template by type; throws `NotFoundError` if not found - `updateFormTemplate()` — Replaces template configuration (core form, scoring, share, withCalibration) - `updateFormTemplatePartially()` — Partial template update - `getFormTemplateScore()` — Gets scoring configuration for the template - `updateFormTemplateScore()` — Updates scoring weights **Target Users (reviewees)** - `addTargetUsers()` — Adds reviewees to the cycle; if cycle is IN_PROGRESS also creates the normal `FormReview` instances and calls `PerformanceReviewCalibrationService.createCalibrationFormReviewsForUser` - `removeTargetUser()` — Removes reviewee; only available in DRAFT or SELECTION - `listTargetUsers()` — Paginated list of reviewees with optional stats - `getTargetUser()` — Single reviewee detail - `patchTargetUser()` — Partial update of reviewee (responsible, etc.) - `shareTargetUser()` — Marks the review as shared with the reviewee - `confirmTargetUser()` — Reviewee confirms they have seen their review - `getTargetUserIds()` — Set of all reviewee IDs in the cycle (used internally by calibration) **Reviewers** - `listReviewers()` — Paginated list of reviewers with filters - `getTargetUserReviewers()` — Reviewers assigned to a specific reviewee - `putTargetUserReviewers()` — Replaces all reviewers for a reviewee - `patchTargetUserReviewers()` — Partial reviewer update - `listRevieweds()` — Reviewees accessible by the logged user (reviewer context) - `notifyReviewerUser()` / `notifyReviewerUsers()` — Sends reminder notifications to reviewers **FormReviews** - `createFormReviews()` — Creates `PerformanceReviewFormReview` instances (internal method also used by calibration) - `listFormReviews()` / `getFormReview()` — Read reviews with optional detail (goals, competencies, form answers) - `fillReview()` — Completes a FormReview; validates status, submits to core form, updates scoring - `addFormDetailsToReview()` — Enriches review with core form data (questions and answers) - `addGoalDetailsToReview()` — Enriches with goal data and goal scoring - `addCompetencyDetailsToReview()` — Enriches with competencies and scoring - `createFormReviewEditRequest()` — Creates a correction request after fill - `reviewEditRequest()` — Approves or rejects an edit request **Scoring / Nine-box / Stats** - `getCycleStats()` — Aggregated cycle stats (progress by evaluation type) - `getReviewsStats()` — Stats for a specific view (reviewer or reviewee) - `getNineBox()` — Nine-box calculation for all reviewees in the cycle - `getUserScores()` / `getBossScores()` — Scores by user / by manager - `getTargetUsersStats()` / `getTargetUsersShareStats()` — Target progress / sharing progress - `getReviewersSelectionStats()` — Reviewer selection progress **External Reviewers** - `createExternalReviewer()` — Creates an external reviewer with an access link per instance/cycle - `listExternalReviewers()` — Lists external reviewers - `fillExternalReview()` — Completes a review from the external context (without standard authentication) - `getExternalReviewDetail()` — External review detail --- ### `PerformanceReviewCalibrationService` > Full detail → see [./AGENTS-calibration.md](./AGENTS-calibration.md) --- ### `PerformanceReviewsCDCService` Kafka CDC consumer (users topic). Keeps `PerformanceReviewUsers` in sync with the actual user state and propagates user deletions to related tables (`PerformanceReviewReviewers`, `PerformanceReviewTargetUsers`). - **CREATE/UPDATE/READ** → upsert in `PerformanceReviewUsers` - **DELETE** → marks user as deleted; soft-deletes reviewer rows where the user was the reviewer (across all cycles); soft-deletes reviewer rows where the user was the reviewed and target user rows, only in cycles still in editable states (DRAFT, PENDING, SELECTION) --- ### `PerformanceReviewQueueConsumer` SQS consumer for `ALL_QUEUES.PERFORMANCE_REVIEW`. Dispatches each message to `DeferredExecutionPerformanceReviewsPort.handleMessage`. --- ### `PerformanceReviewQueueService` Queue message handlers for the `PERFORMANCE_REVIEW` SQS queue. - `handleUserItemsChange()` — Reconciles calibration membership for the user whose items changed. Opens one Sequelize transaction and delegates to `PerformanceReviewCalibrationService.syncCalibratedRulesForUser`. No-op when both `addedItems` and `removedItems` are empty. - `handleSegmentationItemUsersChange()` — Bulk version. Iterates sequentially over `users`, opening one transaction per user and delegating to `syncCalibratedRulesForUser`. No-op when `users` is empty. The `action` field (`ASSIGN` / `REMOVE`) is intentionally not consumed — reconciliation reads the post-change state of the audience index. - `handleUserCreation` / `handleUserDeletion` / `handleSegmentationItemDeletion` — Still stubs (`Promise.resolve()`). Out of scope for the calibration sync work. The dispatcher (`getHandler`, `getMessageBody`, `handleQueueMessage`) propagates errors — there is no wrapping `try/catch`. This allows SQS to retry failed messages and route them to the DLQ on unrecoverable errors. Unknown `messageType` values are logged at `warn` level and silently skipped (no retry triggered for those). --- ## Key Patterns ### Transaction Pattern Critical mutations run in a Sequelize transaction. The controller explicitly calls `getSequelize().transaction(...)`; services do not open their own transactions. ### Signed URLs Controllers sign profile picture URLs with `AttachmentsService.addSignedUrlToModelWithImages()` before serializing. Services never sign URLs. > Calibration-specific patterns (Calibration Prerequisite, Atomic Claim, Orphaned FormReviews, Audiences, Permissions, Calibration Score Override) → see [./AGENTS-calibration.md](./AGENTS-calibration.md) --- ## HTTP Endpoints All currently documented endpoints are calibration endpoints → see [./AGENTS-calibration.md](./AGENTS-calibration.md). Add non-calibration endpoint tables here when they are added to the module. --- ## Integration Tests ``` humand-packages/monolith/test-integration/api/performanceReviews/ ``` | File | Covers | |------|--------| | `common.ts` | Shared helpers: `createPerformanceReviewCommunity`, template setup and cycle contexts | | `calibrationRules.test.ts` | Rules CRUD, audience validation, cycle status constraints, permissions/capabilities (→ [AGENTS-calibration.md](./AGENTS-calibration.md)) | | `calibrationReview.test.ts` | List calibrated users, detail (subordinate snapshot), fill, claim conflict, flow with completed subordinate (→ [AGENTS-calibration.md](./AGENTS-calibration.md)) | | `calibrationFormSync.test.ts` | `syncCalibratedRulesForUser` end-to-end (→ [AGENTS-calibration.md](./AGENTS-calibration.md)) | | `performanceReviewsCalibration.test.ts` | `withCalibration` flag on subordinate template (→ [AGENTS-calibration.md](./AGENTS-calibration.md)) | | `performanceReviewsCycle.test.ts` | General cycle lifecycle | | `performanceReviewsCycleStats.test.ts` | Cycle statistics | | `performanceReviewsScore.test.ts` | Scoring — weights, score display types, formula chains, calibration score override | | `performanceReviewsCompetencies.test.ts` | Competencies in reviews | | `performanceReviewsSelf.test.ts` | Self-evaluation | | `performanceReviewsPeer.test.ts` | Peer evaluation | | `performanceReviewsSubordinate.test.ts` | Subordinate evaluation; `reviewedFormReviews` filtering when calibration is in play (→ [AGENTS-calibration.md](./AGENTS-calibration.md)) | | `performanceReviewsOthers.test.ts` | "Others" type | | `performanceReviewsAllDirections.test.ts` | Multiple evaluation directions | | `performanceReviewsExternal.test.ts` | External reviewers | | `performanceReviewsShareReview.test.ts` | Sharing review with target | | `performanceReviewsDevops.test.ts` | Devops recalculate endpoint | | `performanceReviewsCDCService.test.ts` | CDC / `PerformanceReviewUsers` sync and deletion | | `reviewTemplates.test.ts` | Form templates | Reusable HTTP commands live in `test-integration/commands/performanceReviews/`. --- ## Keeping This Document Up to Date - **New method added to a service** → add it to the relevant section under Services — Detailed Reference - **New non-calibration endpoint** → add a table to HTTP Endpoints here; **new calibration endpoint** → update AGENTS-calibration.md - **New integration test file** → add a row to the Integration Tests table The document is wrong if it describes code that no longer exists, or omits code that does. Keep it exact.