Pipeline funnel v2: 4-stage enum + migration 0007 + derived grid signals
Collapse the inherited 6-stage opportunity funnel to the locked 4-stage
per-investor funnel (lead -> engaged -> diligence -> commitment), terminal at
commitment. Migration 0007 remaps existing stage values (outreach/meeting ->
engaged, due_diligence -> diligence, committed/funded -> commitment) and
archives the stray 'lost' value (the grid row is left intact). Inject read-only
existing_investor (total_invested>0), last_activity_at, and staleness
(''/'aging'>=30d/'stale'>=60d) into the grid GET, stripped on write. Frontend:
4-stage chip tints + Pipeline board / opp-form / mock on the new enum.
The visible desktop existing-investor star + staleness recency column + the
Stale saved view are deferred to mobile Phase 3 (data is injected + test-locked
now, so that phase stays pure-frontend). Longshot was already retired by prior
cleanup -- no-op.
Tests: test_pipeline_stages_v2.py (migration remap + derivation boundaries) +
updated grid-pipeline-link / soft-delete / nl_query; 36/36 green, render-smoke
green, fresh-DB migrate clean.
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
-- Reversal of 0007_pipeline_stages_v2.sql (manual; .down files are never auto-applied).
|
||||
--
|
||||
-- BEST-EFFORT: the 6->4 stage collapse is lossy and cannot be perfectly inverted (the
|
||||
-- pattern other .down files here share -- e.g. 0005 cannot DROP COLUMN on old SQLite). It
|
||||
-- restores VALID legacy 6-stage values, choosing a representative for each collapsed pair:
|
||||
-- engaged was outreach OR meeting -> 'meeting' (representative)
|
||||
-- diligence -> 'due_diligence' (exact)
|
||||
-- commitment was committed OR funded -> 'committed' (representative)
|
||||
-- Opportunities archived from the stray 'lost' value still carry stage = 'lost' but cannot be
|
||||
-- re-identified as "archived by this migration" vs archived for other reasons, so they are
|
||||
-- left archived; un-archive (clear deleted_at) manually if a rollback truly needs them back.
|
||||
UPDATE opportunities SET stage = 'meeting' WHERE stage = 'engaged';
|
||||
UPDATE opportunities SET stage = 'due_diligence' WHERE stage = 'diligence';
|
||||
UPDATE opportunities SET stage = 'committed' WHERE stage = 'commitment';
|
||||
@@ -0,0 +1,25 @@
|
||||
-- Pipeline funnel v2 — collapse the inherited 6-stage opportunity funnel into the locked
|
||||
-- 4-stage per-investor funnel: lead -> engaged -> diligence -> commitment, terminal at
|
||||
-- commitment. See ROADMAP "Pipeline stages + investor flags/labels -- LOCKED SPEC" (2026-06-19)
|
||||
-- and server.PIPELINE_STAGES.
|
||||
--
|
||||
-- DATA-ONLY + DEPLOYMENT-STATE-INVARIANT (migrations guide): targets stage values
|
||||
-- structurally, so it is a no-op on a fresh DB (no opportunities) and remaps deterministically
|
||||
-- on a populated one.
|
||||
-- outreach, meeting -> engaged (a two-way conversation has begun; "meeting" was an
|
||||
-- activity, not a position, so it folds in here)
|
||||
-- due_diligence -> diligence
|
||||
-- committed, funded -> commitment (terminal; post-commit $ lives in the grid fund cell,
|
||||
-- and fund admin owns post-commitment -- no "funded" stage)
|
||||
UPDATE opportunities SET stage = 'engaged' WHERE stage IN ('outreach', 'meeting');
|
||||
UPDATE opportunities SET stage = 'diligence' WHERE stage = 'due_diligence';
|
||||
UPDATE opportunities SET stage = 'commitment' WHERE stage IN ('committed', 'funded');
|
||||
|
||||
-- The stray legacy 'lost' value is not in the new settable enum, and a lost deal is a dead
|
||||
-- deal: ARCHIVE (soft-delete) the opportunity rather than leave an un-settable stage on a live
|
||||
-- row. The grid investor row is left fully intact (the grid is canonical); graveyarding the
|
||||
-- investor stays a human action, never an auto-mutation (human-in-the-loop guardrail). The
|
||||
-- stage text is left as 'lost' on the archived row for provenance -- it is filtered out
|
||||
-- everywhere by deleted_at IS NULL.
|
||||
UPDATE opportunities SET deleted_at = datetime('now'), updated_at = datetime('now')
|
||||
WHERE stage = 'lost' AND deleted_at IS NULL;
|
||||
Reference in New Issue
Block a user