Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/features/pilots/pilot-page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ describe("PilotPage", () => {
name: /close, revise, or proceed/i,
});
expect(within(decisions).getByText(/^continue$/i)).toBeInTheDocument();
expect(decisions).toHaveTextContent(/5 qualified test participants/i);
expect(decisions).toHaveTextContent(
/5 reviewers ready for an authorized test/i,
);
expect(decisions).toHaveTextContent(/3 suitable authorized projects/i);
expect(within(decisions).getByText(/^revise$/i)).toBeInTheDocument();
expect(within(decisions).getByText(/^archive$/i)).toBeInTheDocument();
Expand Down
4 changes: 2 additions & 2 deletions src/features/pilots/pilot-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,12 @@ export function PilotPage() {
<DecisionCard
icon={CheckCircle2}
title="Continue"
description={`${pilot.continue_participant_threshold} qualified test participants and ${pilot.continue_project_threshold} suitable authorized projects support a capped authorized test run.`}
description={`${pilot.continue_participant_threshold} reviewers ready for an authorized test and ${pilot.continue_project_threshold} suitable authorized projects support a capped authorized test run.`}
/>
<DecisionCard
icon={RefreshCcw}
title="Revise"
description={`Meaningful interest exists, but fewer than ${pilot.continue_participant_threshold} people are prepared to participate or the proposed projects do not fit the safety boundary.`}
description={`Meaningful interest exists, but fewer than ${pilot.continue_participant_threshold} reviewers are ready for an authorized test or the proposed projects do not fit the safety boundary.`}
/>
<DecisionCard
icon={Archive}
Expand Down
2 changes: 2 additions & 0 deletions src/features/pilots/pilot-readiness-panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ describe("PilotReadinessPanel", () => {
expect(within(dashboard).getByText("9")).toBeInTheDocument();
expect(within(dashboard).getByText("5")).toBeInTheDocument();
expect(within(dashboard).getByText("3")).toBeInTheDocument();
expect(within(dashboard).getByText(/ready reviewers/i)).toBeInTheDocument();
expect(dashboard).not.toHaveTextContent(/participant responses/i);
expect(within(dashboard).getByText("2 active")).toBeInTheDocument();
expect(within(dashboard).getByText("1 accepted")).toBeInTheDocument();
expect(within(dashboard).getByText("2 spaces remain")).toBeInTheDocument();
Expand Down
4 changes: 2 additions & 2 deletions src/features/pilots/pilot-readiness-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const recommendationCopy: Record<
continue: {
label: "Continue threshold met",
description:
"Participant and suitable-project evidence support preparing the published authorized test run.",
"Ready-reviewer and suitable-project evidence support preparing the published authorized test run.",
},
revise: {
label: "Revise before advancing",
Expand Down Expand Up @@ -101,7 +101,7 @@ export function PilotReadinessPanel({ pilotId }: { pilotId: string }) {
/>
<Metric
value={readiness.participantResponseCount}
label="participant responses"
label="ready reviewers"
/>
<Metric
value={readiness.projectResponseCount}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
-- Separate current ready-reviewer evidence from preserved historical project
-- evidence. A member who answered both question generations must never count
-- twice toward the reviewer threshold.
create or replace function public.get_pilot_readiness_summary(target_pilot_id uuid)
returns table (
pilot_id uuid,
meaningful_signal_count bigint,
participant_response_count bigint,
project_response_count bigint,
active_application_count bigint,
accepted_application_count bigint,
remaining_capacity bigint,
recommendation text
)
language sql
stable
security definer
set search_path = ''
as $$
with authorized_pilot as (
select
pilots.id,
pilots.idea_id,
pilots.created_at,
pilots.evidence_window_days,
pilots.continue_participant_threshold,
pilots.continue_project_threshold,
pilots.archive_signal_ceiling,
pilots.project_capacity
from public.idea_pilots as pilots
join public.ideas as ideas
on ideas.id = pilots.idea_id
where pilots.id = target_pilot_id
and (
ideas.creator_id = (select auth.uid())
or (select public.is_ideascape_operator())
)
),
evidence as (
select
pilots.id as pilot_id,
(
select count(*)
from public.idea_interests as interests
where interests.idea_id = pilots.idea_id
and interests.participation_intent in ('use', 'build', 'pilot', 'expertise')
)::bigint as meaningful_signal_count,
(
select count(distinct responses.profile_id)
from public.idea_validation_responses as responses
join public.idea_validation_questions as questions
on questions.id = responses.question_id
join public.idea_validation_options as options
on options.question_id = responses.question_id
and options.id = responses.option_id
where questions.idea_id = pilots.idea_id
and questions.status = 'active'
and options.value = 'ready-for-authorized-test'
)::bigint as participant_response_count,
(
select count(distinct responses.profile_id)
from public.idea_validation_responses as responses
join public.idea_validation_questions as questions
on questions.id = responses.question_id
join public.idea_validation_options as options
on options.question_id = responses.question_id
and options.id = responses.option_id
where questions.id = '00000000-0000-4000-8000-000000000401'
and questions.idea_id = pilots.idea_id
and options.value in (
'open-source-project',
'coursework-research-tool',
'creative-software',
'civic-community-application'
)
)::bigint as project_response_count,
(
select count(*)
from public.idea_pilot_applications as applications
where applications.pilot_id = pilots.id
and applications.status in ('submitted', 'under_review', 'accepted', 'waitlisted')
)::bigint as active_application_count,
(
select count(*)
from public.idea_pilot_applications as applications
where applications.pilot_id = pilots.id
and applications.status = 'accepted'
)::bigint as accepted_application_count,
pilots.created_at,
pilots.evidence_window_days,
pilots.continue_participant_threshold,
pilots.continue_project_threshold,
pilots.archive_signal_ceiling,
pilots.project_capacity
from authorized_pilot as pilots
)
select
evidence.pilot_id,
evidence.meaningful_signal_count,
evidence.participant_response_count,
evidence.project_response_count,
evidence.active_application_count,
evidence.accepted_application_count,
greatest(
evidence.project_capacity::bigint - evidence.accepted_application_count,
0::bigint
) as remaining_capacity,
case
when evidence.participant_response_count >= evidence.continue_participant_threshold
and evidence.project_response_count >= evidence.continue_project_threshold
then 'continue'
when timezone('utc', now()) >= evidence.created_at + make_interval(days => evidence.evidence_window_days)
and evidence.meaningful_signal_count <= evidence.archive_signal_ceiling
then 'archive'
when timezone('utc', now()) >= evidence.created_at + make_interval(days => evidence.evidence_window_days)
then 'revise'
else 'pending'
end as recommendation
from evidence;
$$;

comment on function public.get_pilot_readiness_summary(uuid) is
'Returns creator/operator-only aggregate readiness: distinct current ready reviewers, explicitly scoped historical project evidence, private application totals, and a threshold preview without member identities.';
16 changes: 14 additions & 2 deletions supabase/tests/database/idea-pilots.test.sql
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,13 @@ values
('00000000-0000-4000-8000-000000000401', '00000000-0000-4000-8000-000000000411', '55555555-5555-4555-8555-555555555555'),
('00000000-0000-4000-8000-000000000401', '00000000-0000-4000-8000-000000000415', '66666666-6666-4666-8666-666666666666');

insert into public.idea_validation_responses (question_id, option_id, profile_id)
values (
'00000000-0000-4000-8000-000000000601',
'00000000-0000-4000-8000-000000000611',
'55555555-5555-4555-8555-555555555555'
);

set local role authenticated;
set local "request.jwt.claim.sub" = '55555555-5555-4555-8555-555555555555';
set local "request.jwt.claims" = '{"sub":"55555555-5555-4555-8555-555555555555","role":"authenticated","app_metadata":{}}';
Expand All @@ -613,8 +620,8 @@ select is(
)::text
from public.get_pilot_readiness_summary('00000000-0000-4000-8000-000000000501')
),
row(2, 2, 1, 1, 1, 2, 'pending')::text,
'the creator receives aggregate progress without applicant identities'
row(2, 1, 1, 1, 1, 2, 'pending')::text,
'the creator receives current ready-reviewer and historical project aggregates without double-counting one member'
);

set local "request.jwt.claim.sub" = '77777777-7777-4777-8777-777777777777';
Expand Down Expand Up @@ -655,6 +662,11 @@ select is(
);
reset role;

insert into public.idea_validation_responses (question_id, option_id, profile_id)
values
('00000000-0000-4000-8000-000000000601', '00000000-0000-4000-8000-000000000611', '66666666-6666-4666-8666-666666666666'),
('00000000-0000-4000-8000-000000000601', '00000000-0000-4000-8000-000000000611', '77777777-7777-4777-8777-777777777777');

update public.idea_pilots
set continue_participant_threshold = 3, continue_project_threshold = 2
where id = '00000000-0000-4000-8000-000000000501';
Expand Down
28 changes: 28 additions & 0 deletions supabase/verification/authorized-bounty-upgrade.test.sql
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ update public.idea_validation_options
set value = 'ready-in-range'
where id = '00000000-0000-4000-8000-000000000611';

insert into public.idea_validation_responses (question_id, option_id, profile_id)
values
(
'00000000-0000-4000-8000-000000000401',
'00000000-0000-4000-8000-000000000411',
'00000000-0000-4000-8000-000000000101'
),
(
'00000000-0000-4000-8000-000000000601',
'00000000-0000-4000-8000-000000000611',
'00000000-0000-4000-8000-000000000101'
);

create temporary table bounty_upgrade_fixture_before as
select
ideas.updated_at,
Expand All @@ -70,6 +83,8 @@ where ideas.id = '88888888-8888-4888-8888-888888888811'

\ir ../migrations/20260811193000_recast_as_authorized_bounty_network.sql
\ir ../migrations/20260811193000_recast_as_authorized_bounty_network.sql
\ir ../migrations/20260813203034_fix_pilot_readiness_generation_counts.sql
\ir ../migrations/20260813203034_fix_pilot_readiness_generation_counts.sql

do $$
begin
Expand Down Expand Up @@ -152,6 +167,19 @@ begin
) <> 0 then
raise exception 'unauthenticated validation summary unexpectedly returned rows';
end if;

perform set_config(
'request.jwt.claim.sub',
'00000000-0000-4000-8000-000000000101',
true
);

if (
select row(participant_response_count, project_response_count)::text
from public.get_pilot_readiness_summary('00000000-0000-4000-8000-000000000501')
) is distinct from row(1::bigint, 1::bigint)::text then
raise exception 'authorized bounty migration mixed historical and active pilot-readiness responses';
end if;
end
$$;

Expand Down
Loading