Reviews¶
Overview¶
The Reviews system handles peer reviews, self-reviews, and continuous feedback. It supports:
- Cycle-based reviews: fixed-date cycles (e.g. biannual performance reviews) or lifecycle cycles (e.g. newcomer reviews triggered by tenure)
- Ad-hoc reviews: one-off reviews not tied to a cycle
- Continuous feedback: lightweight single-question reviews, imported from Lattice or Slack
- Self-reviews: optional self-assessment as part of a review cycle
- ABAC-gated access: fine-grained policies controlling who can view, submit, approve, and release reviews
- PDF export & Google Drive release: completed reviews are exported to PDF and uploaded to the alaner's Drive folder
Data model¶
ReviewTemplate¶
Defines the set of questions used in a review cycle.
| Field | Type | Description |
|---|---|---|
name |
Text |
Template name |
description |
Text |
Template description |
questions |
JSONB[list[ReviewQuestion]] |
Array of question dataclasses |
Question types (ReviewQuestionType): text, select, checkbox, callout
Each ReviewQuestion has:
| Field | Type | Description |
|---|---|---|
id |
str |
Unique question identifier |
type |
ReviewQuestionType |
One of the four types above |
title |
str? |
Question title |
description |
str? |
Question description / helper text |
options |
dict? |
Type-specific options (e.g. values: list[OptionValue] for select) |
Relationships: review_cycles (cycles using this as main template), self_review_cycles (cycles using this as self-review template)
ReviewCycle¶
Groups reviews into a campaign. Determines who gets reviewed, when, and with what template.
| Field | Type | Description |
|---|---|---|
name |
Text |
Cycle name |
description |
Text |
Cycle description |
type |
ReviewCycleType |
fixed_date or lifecycle |
template_id |
UUID (FK) |
Main review template |
self_review_template_id |
UUID? (FK) |
Optional self-review template |
start_date |
Date? |
Start date (None for lifecycle cycles) |
duration |
Integer |
Duration in weeks |
is_cancelled |
Boolean |
Whether the cycle has been cancelled |
reviewees_smart_group_id |
UUID (FK) |
Smart group determining who gets reviewed |
min_reviewers |
Integer |
Minimum number of reviewers required |
max_reviewers |
Integer |
Maximum number of reviewers allowed |
Hybrid property: is_active - True when not cancelled AND (lifecycle OR start_date <= today < start_date + duration weeks)
Relationships: template, self_review_template, reviewees_smart_group, smart_reviewees (alaners in the smart group on cycle start date), reviews
Review¶
A single review for one alaner. Extends Historizable (provides start_date, end_date).
| Field | Type | Description |
|---|---|---|
name |
Text |
Review name |
alaner_id |
Integer |
The reviewee |
type |
ReviewType |
feedback, review_cycle, or ad_hoc |
review_cycle_id |
UUID? (FK) |
Associated cycle (nullable for feedback/ad-hoc) |
questions |
JSONB[list[ReviewQuestion]] |
Questions for reviewers (copied from template) |
self_review_questions |
JSONB[list[ReviewQuestion]] |
Questions for self-review (copied from template) |
min_reviewers |
Integer |
Minimum reviewers required |
max_reviewers |
Integer |
Maximum reviewers allowed |
status |
ReviewStatus |
Computed status (see state machine below) |
released_on |
Date? |
Date when review was released to the alaner |
Relationships: alaner, review_cycle, review_submissions
ReviewSubmission¶
A single reviewer's submission for a review. Extends Historizable (provides start_date, end_date).
| Field | Type | Description |
|---|---|---|
review_id |
UUID (FK) |
Parent review |
reviewer_id |
Integer |
The reviewer |
answers |
JSONB[dict]? |
Reviewer's answers (data-privacy protected via released_on) |
type |
ReviewSubmissionType |
self, coach, coachee, or peer |
approved_at |
DateTime? |
When the submission was approved by the coach |
approver_id |
Integer? |
Who approved |
rejected_at |
DateTime? |
When the submission was rejected |
rejection_reason |
Text? |
Reason for rejection |
rejecter_id |
Integer? |
Who rejected |
published_at |
DateTime? |
When the submission was published |
permalink |
Text? |
Permanent link to the submission |
released_on |
Date? |
Date when released (controls data privacy retention) |
Relationships: review, reviewer, approver, rejecter
Review status machine¶
Review status is stored on the Review model and recomputed by compute_review_status() after each status-changing operation (submission created, approved, rejected, published).
flowchart TD
A[Start] --> B{Non-self submissions<br>< min_reviewers?}
B -->|Yes| C[ready_for_reviewers_selection]
B -->|No| D{Approved non-rejected<br>< min_reviewers?}
D -->|Yes| E[ready_for_reviewers_approval]
D -->|No| F{review_cycle or ad_hoc<br>AND no published self-review?}
F -->|Yes| G[ready_for_self_review]
F -->|No| H{All non-rejected<br>submissions published?}
H -->|Yes| I[completed]
H -->|No| J[ready_for_submissions]
| Status | Condition |
|---|---|
ready_for_reviewers_selection |
Fewer non-self submissions than min_reviewers |
ready_for_reviewers_approval |
Not enough approved (non-rejected) submissions to meet min_reviewers |
ready_for_self_review |
Review is review_cycle or ad_hoc type and no self-review has been published yet |
ready_for_submissions |
Waiting for remaining reviewers to publish |
completed |
All non-rejected submissions are published |
Review cycle types¶
fixed_date¶
Has a concrete start_date. Active while start_date <= today < start_date + duration weeks. Used for scheduled campaigns like biannual performance reviews.
lifecycle¶
start_date is None, making the cycle always active. Used for reviews triggered by tenure milestones (e.g. newcomer reviews at 2.5 months and 5 months).
Submission lifecycle¶
sequenceDiagram
participant Reviewee as Reviewee
participant Coach as Coach / Manager
participant Reviewer as Reviewer
participant System as System
Note over System: Review created<br>(status: ready_for_reviewers_selection)
Reviewee->>System: POST submission (add reviewer)
Note over System: ReviewSubmissionCreated event
Note over Reviewee,Coach: Coach can also add reviewers<br>as backup
alt Enough reviewers added
Note over System: status: ready_for_reviewers_approval
end
Coach->>System: PUT validation (approve)
Note over System: ReviewSubmissionApproved event
alt Enough approved
Note over System: status: ready_for_self_review<br>(if self-review needed)
end
alt Submission rejected
Coach->>System: DELETE validation (reject)
Note over System: ReviewSubmissionRejected event
end
Reviewer->>System: PATCH answers
Reviewer->>System: PUT publication
Note over System: ReviewSubmissionPublished event
alt All published
Note over System: status: completed
end
Continuous feedback¶
Continuous feedback is stored as Review with type=feedback, a single question, and auto-completed status.
Sources¶
- Lattice import:
flask reviews import_feedback_from_latticeimports historical feedback from Lattice via Turing - Slack import:
flask reviews import_feedback_from_slackconvertsSlackMessagerecords (typecontinuous_feedback) into feedback reviews
Slack workflow¶
Messages posted in the #continuous-feedback Slack channel are parsed by flask slack import_feedback and stored as SlackMessage records. These are then imported as feedback reviews via the command above.
When feedback is imported, a thread reply is posted pinging the receiver's coach for visibility.
AI summary¶
POST /alaners/<id>/feedback-summary aggregates received feedback over a configurable period (6-12 months) and sends it to Dust for AI-powered analysis. Returns a 204 with a Location header pointing to the Dust conversation.
User lifecycle integration¶
ReviewsProvider and ReviewStartTask handle automatic review creation:
ReviewsProvider.get_all_users()returns active review cycles per alaner email (cached 20 min)ReviewStartTask.should_run()checks if the alaner is active and has at least one active cycle without a reviewReviewsProvider.provision_user():- For each active
ReviewCycle, checks if alaner is inreviewees_smart_groupbut has noReviewyet - Creates
Reviewwith questions copied from the cycle's template - Auto-creates a self-review
ReviewSubmission - Publishes
ReviewCreatedwebhook event
- For each active
ABAC policies¶
Access control is defined in access_policies/people.py:
| Policy | ID | Description |
|---|---|---|
RevieweePolicy |
reviewee |
Allows actions on your own review (review.alaner_id == principal.id) |
ReviewerPolicy |
reviewer |
Allows actions on your own submissions (submission.reviewer_id == principal.id) |
PeerReviewerPolicy |
peer-reviewer |
Allows viewing published self-review if you're a reviewer on the same review |
RevieweeCoachPolicy |
reviewee-coach |
Allows coach to manage coachee's review (review.alaner_id in principal.coachees) |
ReviewConsultationPolicy |
review-consultation |
Allows viewing completed + released review, or if ManageReviewPolicy applies |
ManageReviewPolicy |
(composite) | RevieweeCoachPolicy OR eu_tools_manage_hr permission |
PDF export and release¶
export_to_pdf() generates a PDF from all published, non-rejected submissions:
- Submissions are sorted by type (
self>coach>coachee>peer), then by reviewer last name - Markdown answers are converted to HTML
- HTML is rendered to PDF with custom CSS (
css/reviews.css)
Release flow (PUT /reviews/<id>/release):
- Validates status is
completedand type is notfeedback - Exports to PDF
- Uploads to Google Drive in the alaner's directory under
Reviews/folder - Sets
released_onon the review and all submissions
Webhook events¶
All events extend WebhookMessage and include review + review_cycle (nullable) schemas:
| Event | Additional fields | Trigger |
|---|---|---|
ReviewCreated |
- | New review provisioned by user lifecycle |
ReviewStatusUpdated |
- | Status changes (submission added, approved, published, etc.) |
ReviewExtended |
previous_start_date, previous_end_date, start_date, end_date |
Review dates extended via PATCH |
ReviewSubmissionCreated |
review_submission |
New submission added |
ReviewSubmissionApproved |
review_submission |
Submission approved by coach |
ReviewSubmissionRejected |
review_submission |
Submission rejected by coach |
ReviewSubmissionPublished |
review_submission |
Submission published by reviewer |