Capablio

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-identical steps (sorted where iteration order is unstable). No timestamps inside steps.
  • data.json / metrics.json are never modified by PDP code paths.
  • Timestamps appear only in top-level created_at (session) and finalized_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.

  • agreeaccepted
  • disagree + competency significance robustpushback with message restating self vs others scores and contested: true
  • disagree or partially + verdict directional or insufficientaccepted, contested: true, message notes small-sample caveat
  • partially + robustaccepted (not pushback; pushback applies only to disagree + robust)

Focus (focus) — answer {"selected": [names], "override_reason"?: str}.

  • selected must be a list of 2–3 competency names present in step options → else invalid
  • No selected name in top-3 CTA (top3: true) and blank override_reasonneeds_override
  • Non-blank override_reason when skipping top-3 → accepted_with_warning (records override_reason on validation)
  • Any selected competency with quadrant leverage or maintain → 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]}.

  • selected must be a list; custom if 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; any ungrounded review → accepted_with_warning (still advances)

Support (support) — answer {"manager_support": str, "resources"?: str, "checkpoints": {"30","60","90"}}.

  • Blank manager_supportinvalid
  • checkpoints must be an object with non-blank 30, 60, 90 strings → else invalid

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)

  1. Deterministic template always computed: baseline = rating_excluding_self, target = min(7.0, baseline + 0.5) (1 decimal).
  2. With provider: prompt reformulates user aspiration into one measurable sentence citing baseline verbatim.
  3. AI response validated: non-empty, 20–600 chars, at least one digit, no P120 banned phrases.
  4. On pass → origin: "ai". On fail/rejection/exception → template kept, "ai_note" records reason.

Custom action review (review_custom_action)

  • No provider → unreviewed for all custom actions.
  • With provider: YES/NO reply → grounded / ungrounded; unparseable or exception → unreviewed.
  • ungrounded actions 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_reasonneeds_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.