Handbook
Personal Development Plan (PDP)
The PDP feature is a guided interview in 360 Insights Studio that turns deterministic report evidence into a structured development plan. It lives entirely outside the parse → analyze → render pipeline: data.json and…
Updated
Implementation: feedback360/pdp/engine.py (session + validation),
feedback360/pdp/compose.py (goal drafting, action review, final assembly).
Determinism policy
build_question_plan(data, metrics)is a pure function of the two JSON files. Same inputs produce byte-identicalsteps(sorted where iteration order is unstable). No timestamps insidesteps.data.json/metrics.jsonare never modified by PDP code paths.- Timestamps appear only in top-level
created_at(session) andfinalized_at(plan). - Optional AI calls never block: provider errors fall back to deterministic templates and are recorded in validation metadata, not raised to HTTP.
The feature is fully functional with no AI provider configured (template
goals, unreviewed custom actions).
Interview stages (7)
Stages 1–3 are built at session start; stages 4–6 are appended after the focus step is accepted; stage 7 is appended with support.
| Stage | Step id(s) | Kind | What it asks | Metrics / data inputs |
|---|---|---|---|---|
| 1 Orientation | orient |
confirm |
Do the headline KPIs match your self-view? | metrics.kpis: develop_count, lowest_statement, widest_inversion, overall_avg |
| 2 Blind spots | blindspot-1..N (up to 3) |
confirm |
Do you agree this competency is a blind spot (self ahead of others)? | metrics.johari.quadrants.blind_spot.items sorted by self_gap desc, tie-break name asc; per-item self/others/self_gap; metrics.significance verdict |
| 3 Focus | focus |
multi_select |
Pick 2–3 competencies for the plan | metrics.cta in rank order; competency_metrics for quadrant and rating_excluding_self; top-3 CTA flagged |
| 4 Goals | goal-<i> |
free_text |
Describe a development goal per selected competency | rating_excluding_self baseline; CTA title; worst flagged statement_metrics entry when present |
| 5 Actions | actions-<i> |
multi_select |
Select CTA actions and/or add custom actions | metrics.cta[].actions as options; custom actions grounding-checked |
| 6 Support | support |
form |
Manager support, resources, 30/60/90 checkpoints | User-supplied only |
| 7 Review | review |
confirm |
Confirm plan summary before finalize | Assembled from session answers |
Dynamic step order after focus: selected competencies in CTA rank order
(non-CTA picks last, name ascending), each with goal-<i> then actions-<i>,
then support, then review.
Validation statuses
Five statuses are returned in validation.status:
| Status | Advances cursor? | When triggered |
|---|---|---|
accepted |
Yes | Valid answer; no warnings |
accepted_with_warning |
Yes | Valid answer with caveats (see below) |
pushback |
Yes | Blind-spot disagree when significance verdict is robust — evidence restated, answer recorded as contested |
needs_override |
No | Focus step: no top-3 CTA competency selected and override_reason missing/blank |
invalid |
No | Malformed answer shape or failed field checks |
Only accepted, accepted_with_warning, and pushback advance the cursor.
pushback does not block — the user's answer stands with contested: true.
Per-step rules
Orientation (orient) — answer {"choice": "yes"|"partially"|"no", "comment"?: str}.
Invalid if choice is anything else.
Blind spots (blindspot-*) — answer {"choice": "agree"|"partially"|"disagree", "comment"?: str}.
Invalid if choice is not one of the three.
agree→accepteddisagree+ competency significancerobust→pushbackwith message restating self vs others scores andcontested: truedisagreeorpartially+ verdictdirectionalorinsufficient→accepted,contested: true, message notes small-sample caveatpartially+robust→accepted(not pushback; pushback applies only todisagree+ robust)
Focus (focus) — answer {"selected": [names], "override_reason"?: str}.
selectedmust be a list of 2–3 competency names present in stepoptions→ elseinvalid- No selected name in top-3 CTA (
top3: true) and blankoverride_reason→needs_override - Non-blank
override_reasonwhen skipping top-3 →accepted_with_warning(recordsoverride_reasonon validation) - Any selected competency with quadrant
leverageormaintain→ warning"already strong: rating X, verdict Y"→accepted_with_warning
Goals (goal-*) — answer {"text": str}. Fewer than 10 non-whitespace
characters → invalid. On success, validation includes drafted goal object
(see compose below).
Actions (actions-*) — answer {"selected": [indices], "custom"?: [str]}.
selectedmust be a list;customif present must be a list- At least one CTA index or non-empty custom string → else
invalid - Invalid action index when options exist →
invalid - Custom actions reviewed via
review_custom_action; anyungroundedreview →accepted_with_warning(still advances)
Support (support) — answer
{"manager_support": str, "resources"?: str, "checkpoints": {"30","60","90"}}.
- Blank
manager_support→invalid checkpointsmust be an object with non-blank30,60,90strings → elseinvalid
Review (review) — answer {"choice": "confirm"}. Any other choice
→ invalid. On accepted, done: true and cursor becomes null.
pdp_session.json schema
Written to <workspace>/report/pdp_session.json (sorted keys, 2-space indent).
{
"schema_version": 1,
"status": "active",
"created_at": "2026-07-10T12:00:00+00:00",
"steps": [
{
"id": "orient",
"stage": 1,
"kind": "confirm",
"prompt": "...",
"context": {
"evidence": [
{"competency": "", "metric": "develop_count", "value": 3, "verdict": "n/a"}
],
"kpis": {}
}
}
],
"answers": {"orient": {"choice": "yes"}},
"validations": {"orient": {"status": "accepted"}},
"cursor": "blindspot-1",
"provider_meta": {"provider": "openai"}
}
| Field | Type | Description |
|---|---|---|
schema_version |
1 |
Schema version |
status |
"active" | "finalized" |
Session lifecycle |
created_at |
ISO 8601 UTC | Session creation time |
steps |
array | Full question plan (initial + dynamically appended) |
answers |
object | Map step_id → answer payload |
validations |
object | Map step_id → validation result |
cursor |
string | null | Current step id; null when interview complete |
provider_meta |
object? | Optional {"provider": "<name>"} from settings at start |
Step object
| Field | Required | Description |
|---|---|---|
id |
yes | Stable step identifier |
stage |
yes | 1–7 |
kind |
yes | confirm, multi_select, free_text, form |
prompt |
yes | Question text |
context |
yes | Evidence chips and step-specific data |
options |
multi_select only | Focus: competency options; actions: {index, text} |
Evidence chip shape: {"competency", "metric", "value", "verdict"} where
verdict comes from metrics.significance.competencies (robust,
directional, insufficient, or "n/a").
pdp.json schema
Written on POST /api/pdp/finalize after review confirms.
{
"schema_version": 1,
"subject": "Leonid Zvyahintsev",
"finalized_at": "2026-07-10T12:30:00+00:00",
"calibration": {
"orientation": {"choice": "yes", "comment": "..."},
"blindspots": [
{
"competency": "PROVIDES VISION & DIRECTION",
"choice": "disagree",
"contested": true,
"self": 5.8,
"others": 5.6,
"verdict": "robust",
"comment": "..."
}
]
},
"focus_areas": [
{
"competency": "PROVIDES VISION & DIRECTION",
"priority_score": 0.975,
"goal": {
"text": "Raise '...' from 5.6 toward 6.1 on the 7-point scale: ...",
"origin": "template",
"baseline": 5.6,
"target": 6.1
},
"evidence": ["..."],
"actions": [
{"text": "...", "origin": "cta", "review": "grounded"},
{"text": "...", "origin": "custom", "review": "unreviewed"}
]
}
],
"support": {
"manager_support": "...",
"resources": "...",
"checkpoints": {"30": "...", "60": "...", "90": "..."}
},
"provider": null
}
| Field | Description |
|---|---|
calibration.orientation |
Orientation choice and optional comment |
calibration.blindspots |
One entry per blind-spot step answered |
focus_areas |
Ordered focus competencies with goals, evidence, actions |
focus_areas[].evidence |
Non-empty: CTA evidence bullets or generated citation |
focus_areas[].goal.origin |
template or ai |
focus_areas[].actions[].review |
grounded, ungrounded, or unreviewed |
provider |
AI provider name from session settings, or null |
AI involvement
Optional; uses the same build_provider layer as /api/ai/*.
Goal drafting (draft_goal)
- Deterministic template always computed: baseline =
rating_excluding_self, target =min(7.0, baseline + 0.5)(1 decimal). - With provider: prompt reformulates user aspiration into one measurable sentence citing baseline verbatim.
- AI response validated: non-empty, 20–600 chars, at least one digit, no P120 banned phrases.
- On pass →
origin: "ai". On fail/rejection/exception → template kept,"ai_note"records reason.
Custom action review (review_custom_action)
- No provider →
unreviewedfor all custom actions. - With provider: YES/NO reply →
grounded/ungrounded; unparseable or exception →unreviewed. ungroundedactions still appear in the plan (accepted_with_warning).
Training & assignment catalog
Implementation: feedback360/pdp/catalog.py (load, validate, matching),
catalog/pdp_catalog.json (bundled read-only input at project root).
The bundled catalog contains 10 development areas, 54 trainings,
40 assignments, and 4 pathways (pdp_pathways). Each development area
links one or more 360 competency names via its competencies array; the join is
case-insensitive (areas_for_competency). When multiple focus competencies map
to the same area, assign_areas gives each competency its first unused matching
area (sorted by PRIORITY_RANK then id).
Fixed ordering maps used for filtering and sorting:
| Map | Keys (ascending rank) |
|---|---|
COST_TIER_RANK |
free-low, free-low to mid, low-mid, mid, mid-premium, premium |
DIFFICULTY_RANK |
low, low-medium, medium, medium-high, high |
PRIORITY_RANK |
critical, high, medium-high, medium |
Pathway suggestion rule: suggest_pathway scores each pathway by
len(set(pathway.areas) ∩ set(assigned_area_ids)), returns the pathway with
the highest overlap (ties broken by pathway id ascending), or null when the
best overlap is 0.
filter_trainings keeps trainings whose cost_tier rank is ≤ the constraints
budget rank (budget any or omitted = no cost filter) and whose format
contains the requested format substring (case-insensitive). filter_assignments
optionally caps by max_difficulty rank. Both sort by rank then id.
Catalog API routes (GET /api/pdp/catalog, GET /api/pdp/catalog/suggest) do
not require an active workspace. See app-api.md.
Catalog mode interview
Opt in at session start with POST /api/pdp/session/start body
{"catalog": true}. When the body is {} or omits a truthy "catalog" key,
the interview is identical to plain mode (no constraints, pathway,
training-*, or sourcing-* steps; finalized pdp.json has no catalog
fields).
Catalog mode reuses stages 1–5 from plain mode, then appends catalog-specific
steps (all stage 6 except review, which stays stage 7):
| Step id | Kind | Answer shape | Validation |
|---|---|---|---|
constraints |
form |
{"budget", "fmt"?, "hours_per_week", "horizon"} |
budget must be a COST_TIER_RANK key or "any"; horizon one of 90d, 6m, 12m; hours_per_week int 1–40; optional fmt string → else invalid |
pathway |
confirm |
{"choice": "accept"\|"customize"} |
Choice must be accept or customize → else invalid; on success accepted (echoes pathway_id, choice). Omitted when no pathway overlaps assigned areas. |
training-<i> |
multi_select |
{"selected": [TRN ids]} |
Every id must be a step option → else invalid. 0 selected → accepted_with_warning (“no formal learning input”). >2 selected → accepted_with_warning (“limit to 1–2”). Otherwise accepted. Carries trainings list in validation. |
sourcing-<i> |
multi_select |
{"selected": [ASN ids], "override_reason"?: str} |
Invalid id → invalid. 0 selected on a critical area with blank override_reason → needs_override. 0 selected on critical with non-blank reason → accepted_with_warning. 0 on non-critical → accepted_with_warning. ≥1 selected → accepted. |
After focus is accepted, catalog mode appends goal-<i> / actions-<i> pairs
(as plain mode), then constraints — not support / review yet. On
constraints acceptance the engine resolves catalog_state (areas, pathway) and
either appends pathway first (when suggested) or jumps straight to
training-<i> / sourcing-<i> pairs. Training/sourcing steps are appended only
after pathway is answered when a pathway step exists. Then support and
review are appended together.
Finalized catalog-mode pdp.json adds top-level constraints, pathway, and
per focus area: area, trainings, catalog_assignments (full catalog
records with origin: "catalog").
Checklist
Implementation: feedback360/pdp/checklist.py. On finalize,
write_checklist_for_plan derives <workspace>/report/pdp_checklist.json.
pdp_checklist.json schema
{
"schema_version": 1,
"subject": "Leonid Zvyahintsev",
"derived_from_finalized_at": "2026-07-10T12:30:00+00:00",
"items": [
{
"id": "goal:provides-vision-direction",
"area": "PROVIDES VISION & DIRECTION",
"kind": "goal",
"label": "Raise vision from 5.6 toward 6.1 …",
"source_ref": "goal",
"status": "todo",
"updated_at": null
}
]
}
| Field | Description |
|---|---|
schema_version |
1 |
subject |
Copied from pdp.json |
derived_from_finalized_at |
Copied from pdp.json finalized_at |
items |
Ordered checklist rows (see id scheme below) |
Each item has kind one of goal, action, training, assignment,
deliverable, checkpoint; status one of todo, in_progress, done;
updated_at ISO 8601 UTC or null until the user changes status via the API.
Item id scheme
Per focus area (plan order), competency slug =
competency.lower() with non-alphanumeric runs replaced by -:
| Pattern | Kind | Example |
|---|---|---|
goal:<slug> |
goal |
goal:provides-vision-direction |
action:<slug>:<j> |
action |
action:provides-vision-direction:0 |
training:<slug>:<TRN_id>:book |
training |
training:…:TRN_001:book |
training:<slug>:<TRN_id>:start |
training |
training:…:TRN_001:start |
training:<slug>:<TRN_id>:complete |
training |
training:…:TRN_001:complete |
assignment:<slug>:<ASN_id> |
assignment |
assignment:…:ASN_001 |
deliverable:<slug>:<ASN_id>:<k> |
deliverable |
deliverable:…:ASN_001:0 |
checkpoint:30 | checkpoint:60 | checkpoint:90 |
checkpoint |
Plan-level (area is "") |
Status-preserving re-derivation
finalize_session always re-derives items from the current pdp.json, then
merge_preserved_statuses copies status and updated_at from the previous
checklist file for ids that still exist. New ids start todo; removed ids are
dropped. This lets goal text or catalog picks change without losing progress on
unchanged items.
Progress rollup
progress(doc) returns total, done, in_progress, todo, and by_area
(first-appearance order of competency names, with plan-level checkpoints under
area: ""). Studio exposes this via GET /api/pdp/checklist.
HTTP API
Session and checklist routes require an active workspace. Catalog read routes do not. See app-api.md for request/response examples and status codes.
Related docs
- Studio HTTP API — full endpoint reference including PDP
- Data contract —
data.jsonandmetrics.json - Engineering guide — CLI and output inventory