{"$schema":"https://json-schema.org/draft/2020-12/schema","title":"PSI Contract v0.3.0","version":"0.3.0","description":"Product Success Index v0.3 — input and output contract for POST /api/v0.3/score. PSI = ((G_log − P_avg) / P_avg) × (1 + E_sum/10) × decay, where G_log = 10·log₁₀(1 + G_raw).","definitions":{"GainInputs":{"type":"object","required":["revenue","productivity","cost_takeout","confidence"],"additionalProperties":false,"properties":{"revenue":{"type":"number","description":"Annualized revenue uplift (monetary-equivalent units)."},"productivity":{"type":"number","description":"Annualized productivity capture."},"cost_takeout":{"type":"number","description":"Annualized cost take-out."},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"Section confidence — owned by Sales. Drives Monte Carlo resampling."}}},"PainInputs":{"type":"object","required":["technical","organizational","risk","change_mgmt","confidence"],"additionalProperties":false,"properties":{"technical":{"type":"number","minimum":1,"maximum":10,"description":"Technical complexity — 1–10."},"organizational":{"type":"number","minimum":1,"maximum":10,"description":"Organizational disruption — 1–10."},"risk":{"type":"number","minimum":1,"maximum":10,"description":"Risk exposure — 1–10."},"change_mgmt":{"type":"number","minimum":1,"maximum":10,"description":"Change management burden — 1–10."},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"Section confidence — owned by SE/Ops."}}},"ExperienceInputs":{"type":"object","required":["ttv","training","adoption","tickets","champion","confidence"],"additionalProperties":false,"properties":{"ttv":{"type":"number","minimum":-1,"maximum":1,"description":"Time-to-value signal, −1..+1."},"training":{"type":"number","minimum":-1,"maximum":1,"description":"Training load / coverage, −1..+1."},"adoption":{"type":"number","minimum":-1,"maximum":1,"description":"Adoption curve, −1..+1."},"tickets":{"type":"number","minimum":-1,"maximum":1,"description":"Support-ticket signal, −1..+1."},"champion":{"type":"number","minimum":-1,"maximum":1,"description":"Champion strength, −1..+1."},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"Section confidence — owned by CS."}}},"TimeInputs":{"type":"object","required":["days_since_advance","expected_cycle_days"],"additionalProperties":false,"properties":{"days_since_advance":{"type":"number","minimum":0,"description":"Days since the last stage advance."},"expected_cycle_days":{"type":"number","minimum":1,"description":"Segment-specific expected cycle length (days). See SPEC.md §8."}}},"PSIInputs":{"type":"object","required":["gain","pain","experience","time"],"additionalProperties":false,"properties":{"gain":{"$ref":"#/definitions/GainInputs"},"pain":{"$ref":"#/definitions/PainInputs"},"experience":{"$ref":"#/definitions/ExperienceInputs"},"time":{"$ref":"#/definitions/TimeInputs"}}},"Deal":{"allOf":[{"$ref":"#/definitions/PSIInputs"}],"description":"PSIInputs + optional display metadata (id, name, seg) that is echoed back in the response.","properties":{"id":{"type":"string"},"name":{"type":"string"},"seg":{"type":"string","description":"ICP segment tag."}}},"Zone":{"type":"string","enum":["upside-down","marginal","solid","strong","exceptional"]},"PSIBand":{"type":"object","required":["p10","p25","p50","p75","p90","width"],"properties":{"p10":{"type":"number"},"p25":{"type":"number"},"p50":{"type":"number","description":"Median Monte Carlo result."},"p75":{"type":"number"},"p90":{"type":"number"},"width":{"type":"number","description":"p90 − p10; advisory when > 1.5."}}},"PSIResult":{"type":"object","required":["point","decay","effective","zone","band","dispersion","intermediate","flags","levers"],"properties":{"point":{"type":"number","description":"Point PSI before time decay."},"decay":{"type":"number","description":"Time-decay multiplier applied to point PSI."},"effective":{"type":"number","description":"Final PSI — point × decay. The forecasting number."},"zone":{"$ref":"#/definitions/Zone"},"band":{"$ref":"#/definitions/PSIBand"},"dispersion":{"type":"object","required":["pain","experience"],"properties":{"pain":{"type":"number","description":"Internal disagreement across Pain sub-scores."},"experience":{"type":"number","description":"Internal disagreement across Experience sub-scores."}}},"intermediate":{"type":"object","description":"Transparency values — the components the point PSI was computed from.","properties":{"G_log":{"type":"number"},"P_avg":{"type":"number"},"E_sum":{"type":"number"},"raw_gain":{"type":"number"},"staleness":{"type":"number","description":"0..1; 1.0 = fully stale under the cycle."}}},"flags":{"type":"object","description":"Advisory flags consumed by the UI for badges and guardrails.","properties":{"wide_band":{"type":"boolean","description":"band.width > 1.5 — inputs are soft."},"high_pain_dispersion":{"type":"boolean","description":"Pain dispersion > 4 — one friction dominates."},"high_exp_dispersion":{"type":"boolean","description":"Experience dispersion > 1.5."},"dormant":{"type":"boolean","description":"Staleness > 0.7; do not commit to forecast."}}},"levers":{"type":"object","description":"Partial derivatives — coaching signal for which input moves PSI most.","required":["gain","pain","experience","dominant"],"properties":{"gain":{"type":"number","description":"∂PSI/∂G (raw dollars)."},"pain":{"type":"number","description":"PSI change per 1-unit Pain decrease."},"experience":{"type":"number","description":"∂PSI/∂E."},"dominant":{"type":"string","enum":["gain","pain","experience"]}}},"id":{"type":"string","description":"Echoed back from request if present."},"name":{"type":"string","description":"Echoed back from request if present."},"seg":{"type":"string","description":"Echoed back from request if present."}}},"ScoreResponse":{"oneOf":[{"type":"object","required":["version","result"],"properties":{"version":{"const":"0.3.0"},"result":{"$ref":"#/definitions/PSIResult"}}},{"type":"object","required":["version","results"],"properties":{"version":{"const":"0.3.0"},"results":{"type":"array","items":{"$ref":"#/definitions/PSIResult"}}}}]},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable error code."},"detail":{"type":"string","description":"Human-readable detail."}}}},"endpoints":{"POST /api/v0.3/score":{"auth":"Bearer <PSI_API_KEY>","queryParams":{"iterations":"Optional. Monte Carlo resample count, 1..10000. Default 500."},"stratificationHeaders":{"X-PSI-Deal-Type":"Optional. 'rcm' | 'voice_ai' | 'credentialing' | 'enrollment' | 'epic_migration' | 'consulting' | 'other'.","X-PSI-Scorer-Role":"Optional. 'seller' | 'buyer' | 'buyer_rep' | 'joint' | 'system'.","X-PSI-Scorer-Id":"Optional UUID identifying who scored.","X-PSI-Scorer-Email":"Optional email of scorer (used for buyer-side lead-magnet flow).","X-PSI-Consumer":"Optional. 'lab' | 'ae_form_quick' | 'ae_form_detailed' | 'public_form' | 'cli' | 'crm' | 'api'.","X-PSI-Rubric-Version":"Optional. Rubric multiplier set version. Default 'v0.3.0'.","X-PSI-Opportunity-Id":"Optional correlation key with the consumer's deal record. Used to join with /outcome later.","X-PSI-Org-Name":"Optional buyer org name. Hashed (SHA-256, server-side) before storage — never persisted in the clear."},"requestBody":{"description":"Either a single Deal or an array of Deals (max 100).","oneOf":[{"$ref":"#/definitions/Deal"},{"type":"array","items":{"$ref":"#/definitions/Deal"},"maxItems":100}]},"response":{"$ref":"#/definitions/ScoreResponse"},"errors":{"$ref":"#/definitions/ErrorResponse"}},"POST /api/v0.3/outcome":{"auth":"Bearer <PSI_API_KEY>","description":"Record the actual outcome for a previously scored deal. Idempotent on opportunity_id.","requestBody":{"type":"object","required":["opportunity_id","outcome"],"properties":{"opportunity_id":{"type":"string","description":"Same value sent as X-PSI-Opportunity-Id when scoring."},"outcome":{"type":"string","enum":["closed_won","closed_lost","no_decision","churned","renewed","active"]},"months_to_outcome":{"type":"number","minimum":0},"value_realized_pct":{"type":"number","minimum":0,"maximum":500,"description":"100 = matched projected value."},"actual_zone_implied":{"$ref":"#/definitions/Zone"},"context_notes":{"type":"string"},"organization_hash":{"type":"string"},"logged_by":{"type":"string"}}},"response":{"type":"object","properties":{"version":{"const":"0.3.0"},"ok":{"type":"boolean"}}},"errors":{"$ref":"#/definitions/ErrorResponse"}},"GET /api/v0.3/schema":{"auth":"none","response":"This document."}}}