Docs / Admin

Investment facilitation (Phase 4)

Summary — The Facilitation section (/facilitation/*) is a read-oriented reporting layer over the matching module: which investor profiles have been qualified, which B2B meetings have happened, which follow-ups are outstanding, and a metrics snapshot tying qualification through to deal flow.

Purpose — Understand the investor qualification workflow that feeds the Qualified tab, what each facilitation tab shows, and where the underlying data comes from.

Audience — Staff, reviewer, lead, and admin roles working Phase 4 (investment facilitation) of the program.

Prerequisites — Investor profiles and matches already exist — see Investor-opportunity matching and B2B meetings.


Overview

Facilitation has four tabs, all under src/app/(authenticated)/facilitation/:

RoutePurpose
/facilitation/qualifiedInvestor profiles that have reached a qualified state in the investor-qualification workflow.
/facilitation/follow-upFollow-up actions tracked from B2B meetings (backed by the generic Issues module).
/facilitation/meetings-logFull B2B meeting log across all opportunities, filterable by status.
/facilitation/metricsA five-card snapshot: qualified investors, matched, meetings completed, promoted to deal flow, successful matches.

Key concepts:

  • Investor qualification workflow — A workflow-states module workflow (slug investor-qualification, entity type investor_profile) with its own states and transition rules, separate from match status.
  • Qualified statesqualified, qualified_with_conditions, and matched are the states this reporting surface treats as "qualified" (see below — the two lists differ slightly).
  • Event follow-up — Not a new table; it is an Issue of type event-follow-up, linked to the originating B2B meeting.

Screenshot: Facilitation Metrics page showing five stat cards — Qualified Investors, Matched, Meetings Completed, Promoted to Deal Flow, Successful Matches.


The investor qualification workflow

Seeded per-tenant by migration 060_phase4_investor_facilitation.sql as workflow slug investor-qualification, entity type investor_profile.

States:

StateLabelFinal?
submittedSubmittedNo
incompleteIncompleteNo
validatedValidatedNo
qualifiedQualifiedNo
qualified_with_conditionsQualified (Conditions)No
not_qualifiedNot QualifiedYes
matchedMatchedYes

Transitions and required role:

FromToLabelRequired role
submittedvalidatedValidatestaff
submittedincompleteMark Incompletestaff
incompletevalidatedValidatestaff
validatedqualifiedQualifyreviewer
validatedqualified_with_conditionsQualify with Conditionsreviewer
validatednot_qualifiedDisqualifyreviewer
qualified_with_conditionsqualifiedClear Conditionslead
* (any state)not_qualifiedDisqualifyreviewer
qualifiedmatchedMark Matchedreviewer

One extra rule not expressible in the generic state machine: a profile cannot move into qualified or qualified_with_conditions unless it has at least one entry in sectorsOfInterest and a non-null counterpartyType. This is enforced by assertQualifiable() in src/lib/modules/matching/qualification.ts, with these verbatim errors:

Cannot qualify: profile has no sectors of interest.

Cannot qualify: profile is missing a counterparty type.

This check runs before every call to transitionQualification(), in addition to (not instead of) the workflow engine's own transition and role validation and its own audit logging (no duplicate audit call is made in the matching module wrapper).

There is no dedicated action route reviewed for triggering these transitions from the UI other than transitionQualificationAction (a server action taking organizationId + toState + optional notes) — the coarse gate on the action itself only requires staff; the workflow engine enforces the finer per-transition role shown above and throws if unmet.


Qualified Investors tab

Path: /facilitation/qualified

Calls listQualifiedProfiles(), which reads the latest state per investor_profile entity from state_transitions directly (there is no generic "latest state per entity" helper on the workflow-states module, so this reads the table with the same tenant-scoped pattern module repositories use) and filters to profiles whose latest state is one of:

  • qualified
  • qualified_with_conditions
  • matched

Each row links to the organization's stakeholder page and shows a StatusBadge for the qualification state. Empty state: "No qualified investors yet — Move investor profiles through qualification on their organization page."

Note: this "report" set of states (qualified, qualified_with_conditions, matched) is the same as QUALIFIED_STATES ∪ {matched} used internally — QUALIFIED_STATES (used only by the assertQualifiable gate) is {qualified, qualified_with_conditions}, while the reporting set additionally includes matched.


B2B Meetings Log tab

Path: /facilitation/meetings-log

Lists all B2B meetings (listB2bMeetings), filterable by status via the FilterBar (scheduled, completed, cancelled, no_show — same statuses as on /matching/meetings). Each row links through to /matching/meetings/[id] for the full meeting detail (status/outcome editing, participants, promote outcome) — see Investor-opportunity matching and B2B meetings for that detail page's full behavior. This tab is a cross-opportunity view; the matching module's own /matching/meetings page shows the same data split into Upcoming/Past sections.


Follow-up tab

Path: /facilitation/follow-up

Follow-ups are not a dedicated table — a follow-up is an Issue of type event-follow-up (seeded per-tenant by migration 060), created via createEventFollowUp() from a B2B meeting's detail page ("Create Follow-up" form). The follow-up issue carries:

  • title: "Follow-up: {meeting.title}"
  • description: the free-text note entered
  • optional assignedTo, dueDate
  • fieldData: { meetingId, organizationId }
  • linkedEntityType: "b2b_meeting", linkedEntityId: meeting.id

The issue type is seeded with default_status: "open" and allowed_statuses: [open, in_progress, resolved, closed].

The Facilitation Follow-up tab calls listIssues({ typeSlug: "event-follow-up" }) and lists title, due date, and status, with each row linking to the full issue detail at /issues/{id} and a top-level link to "View in Issues" for the generic issue tracker.

b2b_meetings.follow_up_action (a free-text field editable from the meeting's Status & Outcome form) is a separate, simpler note field on the meeting itself — it is not synced with the Issue created here.


Metrics tab

Path: /facilitation/metrics

Five cards, computed by:

CardSource
Qualified InvestorslistQualifiedProfiles().length (same set as the Qualified tab: qualified, qualified_with_conditions, matched)
MatchedOf the qualified rows, count where state === "matched"
Meetings CompletedlistB2bMeetings({ status: "completed" }).length
Promoted to Deal FlowOf completed meetings, count where metadata.promotedOpportunityId is set (i.e., outcome was promoted via the deal_flow decision)
Successful MatcheslistMatches({ status: "successful" }).length

This is a point-in-time snapshot with no date-range filter, export, or drill-down in the code reviewed.


Roles and permissions

ActionMinimum role
View any facilitation tabstaff (inherits the authenticated layout's default gate — no facilitation-specific role check found in the four page.tsx files themselves)
Transition investor qualification (coarse gate)staff
Qualify / Qualify with Conditions / Disqualifyreviewer (per workflow transition requiredRole)
Clear Conditions (qualified_with_conditionsqualified)lead
Create event follow-upstaff

Best practices

  1. Qualify profiles before they show up here. The Qualified tab and the Matched metric are both driven entirely by the investor-qualification workflow state — a profile with a great match score but no qualification transition applied will not appear.
  2. Use Promote Outcome (deal_flow) consistently on completed B2B meetings you want reflected in the "Promoted to Deal Flow" metric — the count only increments when promotedOpportunityId is set via that path.
  3. Route ad-hoc action items through Create Follow-up, not the meeting's followUpAction free-text field, if you want them tracked and assignable in Issues.

Warnings

not_qualified and matched are terminal in the investor-qualification workflow — once reached, no further transition is defined except the wildcard *not_qualified disqualify path (which does not apply once already not_qualified, since that would be a self-transition).

The Qualified tab and QUALIFIED_STATES guard use different state sets. The tab's report set includes matched; the qualification gate (assertQualifiable) does not check matched at all since matched profiles are already past the gate.


Troubleshooting

"Cannot qualify: profile has no sectors of interest." / "...missing a counterparty type."

Symptom: A qualification transition to qualified or qualified_with_conditions fails.

Cause: The investor profile is missing sectorsOfInterest or counterpartyType.

Fix: Update the investor profile (Upsert Investor Profile form on the organization page) to add at least one sector of interest and a counterparty type, then retry the transition.


A meeting doesn't count toward "Promoted to Deal Flow"

Symptom: A completed B2B meeting isn't reflected in the metrics card.

Cause: The metric only counts completed meetings with metadata.promotedOpportunityId set — which only happens via the "Deal flow — source an opportunity" decision on Promote Outcome, not by manually setting status to completed.

Fix: Use the Promote Outcome form with decision deal_flow on the meeting detail page.


FAQ

Is "Qualified" on this page the same as a match status? No. Match status (suggested/approved/etc.) lives on the Match record between an opportunity and organization. Qualification status lives on the investor profile itself, independent of any specific opportunity match.

Can a profile be qualified without ever being matched? Yes — qualified and qualified_with_conditions are non-final states; matched is a separate, later transition (qualified → matched, requires reviewer) typically applied once matchmaking has actually produced a result.

Where do event follow-ups show up outside of Facilitation? They are ordinary Issues of type event-follow-up and also appear in the generic /issues list and detail pages.


Related articles