From a435371957d082f379ead305ddbf63825358fe7e Mon Sep 17 00:00:00 2001 From: Alden Hallak Date: Mon, 1 Dec 2025 15:06:48 -0500 Subject: [PATCH 1/2] Initialize public stage data if missing on participant answer submission and enhance ranking reveal view rendering. --- .../components/stages/ranking_reveal_view.ts | 9 ++-- functions/src/stages/flipcard.utils.ts | 42 +++++++++++-------- .../stages/multi_asset_allocation.utils.ts | 16 +++---- functions/src/stages/ranking.utils.ts | 13 ++++-- functions/src/stages/survey.utils.ts | 12 ++++-- 5 files changed, 57 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/stages/ranking_reveal_view.ts b/frontend/src/components/stages/ranking_reveal_view.ts index 33106884f..69d04bf4c 100644 --- a/frontend/src/components/stages/ranking_reveal_view.ts +++ b/frontend/src/components/stages/ranking_reveal_view.ts @@ -128,9 +128,11 @@ export class RankingReveal extends MobxLitElement { participantMap = {[currentId]: participantMap[currentId]}; } - const maxOptions = Math.max( - ...Object.values(participantMap).map((lst) => lst.length), - ); + const participantAnswers = Object.values(participantMap); + const maxOptions = + participantAnswers.length > 0 + ? Math.max(...participantAnswers.map((lst) => lst.length)) + : 0; return html`
${headerText}
@@ -183,6 +185,7 @@ export class RankingReveal extends MobxLitElement { ${this.renderWinner()}
+ ${this.renderResultsTable()} `; } } diff --git a/functions/src/stages/flipcard.utils.ts b/functions/src/stages/flipcard.utils.ts index c0379c392..4daf0000f 100644 --- a/functions/src/stages/flipcard.utils.ts +++ b/functions/src/stages/flipcard.utils.ts @@ -3,6 +3,7 @@ import { FlipCardStageParticipantAnswer, FlipCardStagePublicData, ParticipantProfileExtended, + StageKind, } from '@deliberation-lab/utils'; import * as admin from 'firebase-admin'; @@ -30,24 +31,29 @@ export async function addParticipantAnswerToFlipCardStagePublicData( const publicDoc = await transaction.get(publicDocument); const publicData = publicDoc.data() as FlipCardStagePublicData | undefined; - if (publicData) { - // Update public data with participant's flip history and selections - const updatedPublicData: FlipCardStagePublicData = { - ...publicData, - participantFlipHistory: { - ...publicData.participantFlipHistory, - [participant.publicId]: answer.flipHistory, - }, - participantSelections: { - ...publicData.participantSelections, - [participant.publicId]: answer.selectedCardIds, - }, - }; + const currentPublicData = publicData || { + id: stage.id, + kind: StageKind.FLIPCARD, + participantFlipHistory: {}, + participantSelections: {}, + }; - transaction.set(publicDocument, { - ...updatedPublicData, - timestamp: admin.firestore.FieldValue.serverTimestamp(), - }); - } + // Update public data with participant's flip history and selections + const updatedPublicData: FlipCardStagePublicData = { + ...currentPublicData, + participantFlipHistory: { + ...currentPublicData.participantFlipHistory, + [participant.publicId]: answer.flipHistory, + }, + participantSelections: { + ...currentPublicData.participantSelections, + [participant.publicId]: answer.selectedCardIds, + }, + }; + + transaction.set(publicDocument, { + ...updatedPublicData, + timestamp: admin.firestore.FieldValue.serverTimestamp(), + }); }); } diff --git a/functions/src/stages/multi_asset_allocation.utils.ts b/functions/src/stages/multi_asset_allocation.utils.ts index 4cc3bfdc9..161e0a840 100644 --- a/functions/src/stages/multi_asset_allocation.utils.ts +++ b/functions/src/stages/multi_asset_allocation.utils.ts @@ -3,6 +3,7 @@ import { MultiAssetAllocationStageParticipantAnswer, MultiAssetAllocationStagePublicData, ParticipantProfile, + StageKind, } from '@deliberation-lab/utils'; import * as admin from 'firebase-admin'; import {app} from '../app'; @@ -41,17 +42,16 @@ export async function addParticipantAnswerToMultiAssetAllocationStagePublicData( | MultiAssetAllocationStagePublicData | undefined; - if (!publicData) { - console.error( - `Public data for stage ${stage.id} does not exist. Cannot update.`, - ); - return; - } + const currentPublicData = publicData || { + id: stage.id, + kind: StageKind.MULTI_ASSET_ALLOCATION, + participantAnswerMap: {}, + }; const updatedPublicData: MultiAssetAllocationStagePublicData = { - ...publicData, + ...currentPublicData, participantAnswerMap: { - ...publicData.participantAnswerMap, + ...currentPublicData.participantAnswerMap, [participant.publicId]: answer, }, }; diff --git a/functions/src/stages/ranking.utils.ts b/functions/src/stages/ranking.utils.ts index 97adedfdd..625f89630 100644 --- a/functions/src/stages/ranking.utils.ts +++ b/functions/src/stages/ranking.utils.ts @@ -9,6 +9,7 @@ import { getCondorcetElectionWinner, getRankingCandidatesFromWTL, LAS_WTL_STAGE_ID, + createRankingStagePublicData, } from '@deliberation-lab/utils'; import {app} from '../app'; @@ -41,9 +42,15 @@ export async function addParticipantAnswerToRankingStagePublicData( .collection('publicStageData') .doc(LAS_WTL_STAGE_ID); // Update public stage data (current participant rankings, current winner) - const publicStageData = ( - await publicDocument.get() - ).data() as RankingStagePublicData; + const publicDoc = await publicDocument.get(); + let publicStageData = publicDoc.data() as + | RankingStagePublicData + | undefined; + + if (!publicStageData) { + publicStageData = createRankingStagePublicData(stage.id); + } + publicStageData.participantAnswerMap[participant.publicId] = answer.rankingList; diff --git a/functions/src/stages/survey.utils.ts b/functions/src/stages/survey.utils.ts index 29eee027f..098ed6e8d 100644 --- a/functions/src/stages/survey.utils.ts +++ b/functions/src/stages/survey.utils.ts @@ -2,6 +2,8 @@ import { ParticipantProfileExtended, SurveyStageParticipantAnswer, SurveyStagePublicData, + SurveyStageConfig, + createSurveyStagePublicData, } from '@deliberation-lab/utils'; import {app} from '../app'; @@ -25,9 +27,13 @@ export async function addParticipantAnswerToSurveyStagePublicData( .doc(stage.id); // Update public stage data (current participant rankings, current winner) - const publicStageData = ( - await publicDocument.get() - ).data() as SurveyStagePublicData; + const publicDoc = await transaction.get(publicDocument); + let publicStageData = publicDoc.data() as SurveyStagePublicData | undefined; + + if (!publicStageData) { + publicStageData = createSurveyStagePublicData(stage.id); + } + publicStageData.participantAnswerMap[participant.publicId] = answer.answerMap; From c45fea49b6cc08ffc5ebeff87e8f0822e5c75e83 Mon Sep 17 00:00:00 2001 From: Alden Hallak Date: Wed, 10 Dec 2025 14:03:31 -0500 Subject: [PATCH 2/2] Remove on-the-fly public stage data creation and add warnings for missing public data. --- functions/src/stages/asset_allocation.utils.ts | 12 ++++++------ functions/src/stages/flipcard.utils.ts | 15 ++++++++------- .../src/stages/multi_asset_allocation.utils.ts | 16 +++++++++------- functions/src/stages/ranking.utils.ts | 8 +++++--- functions/src/stages/survey.utils.ts | 10 +++++++--- utils/src/utils/algebraic.utils.ts | 2 +- 6 files changed, 36 insertions(+), 27 deletions(-) diff --git a/functions/src/stages/asset_allocation.utils.ts b/functions/src/stages/asset_allocation.utils.ts index e569e403a..61dc2271e 100644 --- a/functions/src/stages/asset_allocation.utils.ts +++ b/functions/src/stages/asset_allocation.utils.ts @@ -3,7 +3,6 @@ import { AssetAllocationStageParticipantAnswer, AssetAllocationStagePublicData, ParticipantProfileExtended, - createAssetAllocationStagePublicData, } from '@deliberation-lab/utils'; import * as admin from 'firebase-admin'; @@ -27,15 +26,16 @@ export async function addParticipantAnswerToAssetAllocationStagePublicData( // Read current public data first (all reads must come before writes) const publicDoc = await transaction.get(publicDocument); - let publicData = publicDoc.data() as + const publicData = publicDoc.data() as | AssetAllocationStagePublicData | undefined; - // Create initial public data if it doesn't exist + // Public stage data should be initialized on cohort creation if (!publicData) { - publicData = createAssetAllocationStagePublicData({ - id: stage.id, - }); + console.warn( + `Public stage data not found for stage ${stage.id} in cohort ${participant.currentCohortId}. This should have been initialized on cohort creation.`, + ); + return; } // Update public data with participant's allocation diff --git a/functions/src/stages/flipcard.utils.ts b/functions/src/stages/flipcard.utils.ts index 4daf0000f..4e58504b0 100644 --- a/functions/src/stages/flipcard.utils.ts +++ b/functions/src/stages/flipcard.utils.ts @@ -3,7 +3,6 @@ import { FlipCardStageParticipantAnswer, FlipCardStagePublicData, ParticipantProfileExtended, - StageKind, } from '@deliberation-lab/utils'; import * as admin from 'firebase-admin'; @@ -31,12 +30,14 @@ export async function addParticipantAnswerToFlipCardStagePublicData( const publicDoc = await transaction.get(publicDocument); const publicData = publicDoc.data() as FlipCardStagePublicData | undefined; - const currentPublicData = publicData || { - id: stage.id, - kind: StageKind.FLIPCARD, - participantFlipHistory: {}, - participantSelections: {}, - }; + if (!publicData) { + console.warn( + `Public stage data not found for stage ${stage.id} in cohort ${participant.currentCohortId}. This should have been initialized on cohort creation.`, + ); + return; + } + + const currentPublicData = publicData; // Update public data with participant's flip history and selections const updatedPublicData: FlipCardStagePublicData = { diff --git a/functions/src/stages/multi_asset_allocation.utils.ts b/functions/src/stages/multi_asset_allocation.utils.ts index 161e0a840..c5c0a8010 100644 --- a/functions/src/stages/multi_asset_allocation.utils.ts +++ b/functions/src/stages/multi_asset_allocation.utils.ts @@ -3,9 +3,8 @@ import { MultiAssetAllocationStageParticipantAnswer, MultiAssetAllocationStagePublicData, ParticipantProfile, - StageKind, } from '@deliberation-lab/utils'; -import * as admin from 'firebase-admin'; + import {app} from '../app'; /** @@ -42,11 +41,14 @@ export async function addParticipantAnswerToMultiAssetAllocationStagePublicData( | MultiAssetAllocationStagePublicData | undefined; - const currentPublicData = publicData || { - id: stage.id, - kind: StageKind.MULTI_ASSET_ALLOCATION, - participantAnswerMap: {}, - }; + if (!publicData) { + console.warn( + `Public stage data not found for stage ${stage.id} in cohort ${participant.currentCohortId}. This should have been initialized on cohort creation.`, + ); + return; + } + + const currentPublicData = publicData; const updatedPublicData: MultiAssetAllocationStagePublicData = { ...currentPublicData, diff --git a/functions/src/stages/ranking.utils.ts b/functions/src/stages/ranking.utils.ts index 625f89630..14ab48e87 100644 --- a/functions/src/stages/ranking.utils.ts +++ b/functions/src/stages/ranking.utils.ts @@ -9,7 +9,6 @@ import { getCondorcetElectionWinner, getRankingCandidatesFromWTL, LAS_WTL_STAGE_ID, - createRankingStagePublicData, } from '@deliberation-lab/utils'; import {app} from '../app'; @@ -43,12 +42,15 @@ export async function addParticipantAnswerToRankingStagePublicData( .doc(LAS_WTL_STAGE_ID); // Update public stage data (current participant rankings, current winner) const publicDoc = await publicDocument.get(); - let publicStageData = publicDoc.data() as + const publicStageData = publicDoc.data() as | RankingStagePublicData | undefined; if (!publicStageData) { - publicStageData = createRankingStagePublicData(stage.id); + console.warn( + `Public stage data not found for stage ${stage.id} in cohort ${participant.currentCohortId}. This should have been initialized on cohort creation.`, + ); + return; } publicStageData.participantAnswerMap[participant.publicId] = diff --git a/functions/src/stages/survey.utils.ts b/functions/src/stages/survey.utils.ts index 098ed6e8d..984e55c74 100644 --- a/functions/src/stages/survey.utils.ts +++ b/functions/src/stages/survey.utils.ts @@ -3,7 +3,6 @@ import { SurveyStageParticipantAnswer, SurveyStagePublicData, SurveyStageConfig, - createSurveyStagePublicData, } from '@deliberation-lab/utils'; import {app} from '../app'; @@ -28,10 +27,15 @@ export async function addParticipantAnswerToSurveyStagePublicData( // Update public stage data (current participant rankings, current winner) const publicDoc = await transaction.get(publicDocument); - let publicStageData = publicDoc.data() as SurveyStagePublicData | undefined; + const publicStageData = publicDoc.data() as + | SurveyStagePublicData + | undefined; if (!publicStageData) { - publicStageData = createSurveyStagePublicData(stage.id); + console.warn( + `Public stage data not found for stage ${stage.id} in cohort ${participant.currentCohortId}. This should have been initialized on cohort creation.`, + ); + return; } publicStageData.participantAnswerMap[participant.publicId] = diff --git a/utils/src/utils/algebraic.utils.ts b/utils/src/utils/algebraic.utils.ts index be97251f5..381c025c2 100644 --- a/utils/src/utils/algebraic.utils.ts +++ b/utils/src/utils/algebraic.utils.ts @@ -194,7 +194,7 @@ export function getRankingCandidatesFromWTL( * filter rankings so that they only include those candidates. */ export function filterRankingsByCandidates( participantRankings: Record, - candidateList = [], + candidateList: string[] = [], ) { Object.keys(participantRankings).forEach((id) => { participantRankings[id] = participantRankings[id].filter(