Skip to content

Question answer baseline and rebuild

The phase-3 question-answer calculator previously served parity tests but was not wired into runtime rebuilds. The runtime calculator now uses the same authoritative question tally pipeline as manual refresh, with Project definitions, answer counts and control revisions read through one pinned MongoDB snapshot. Project-question grain is preserved; no stage or version dimension is invented for counts.

Administrators with the existing BatchAdminProjects permission can invoke:

  • POST api/admin/project-statistics/{id}/question-answers/backfill
  • POST api/admin/project-statistics/{id}/question-answers/rebuild

The shared bounded backfill service supplies admission, leases, publication guards, source revision checks, bootstrap checkpoints and typed 202/409/404 outcomes. The force route retains the existing configuration reconciliation rules. Scope enumeration intersects authoritative answered question IDs with the Project's current definitions, including synthesized system definitions. Unanswered or removed definitions do not become confident zero rows. An existing project with no answered scopes returns an empty success without fabricating a checkpoint; a missing project is distinguished by the shared admission/existence checks.

Because an ordinary answer save can introduce a new scope, the bootstrap revalidates the enumerated scope set and source revision together before capture. Final publication retains the existing CAS and complete-scope verification. A concurrent change must result in a retry/refusal rather than claiming completion at a different boundary. The project-wide configuration digest is shared with other families; this implementation introduces no family-specific control digest. Historical display metadata retains the question identity, text, type and schema from the same source snapshot.

Flag decision: no new flag. The existing writes, question-family and project-allowlist gates control this operational route; default configuration remains off/empty. Registration does not enable reads, schedule work, mutate runtime flags, or replace the existing question editor's tally source. Consumer cutover and activation remain separate programme work. The existing authoritative aggregation remains an administrative rebuild cost, not a query added to ordinary per-study writes.

Validation covers real-replica-set baseline publication and bootstrap, repeat idempotence, absent unanswered scopes, pinned enumeration across a concurrent answer insert, writes/family/allowlist refusals, and equality with the legacy tally. Existing question parity cases exercise repeated answers on one study and distinct study counts. API routing/authorization and both host registry contracts are verified separately. No staging or production activation is claimed.

Question designer read cutover

The independently default-off materializedProjectStatisticsQuestionCounts consumer enables GET api/projects/{projectId}/question-answer-counts and a read-only count overlay in the question designer. It also requires Pages, writes, serving, the QuestionAnswers family and the project allowlist. Disabled or non-pilot requests return an explicit enabled: false response before source queries; these projects retain legacy editing behavior. The endpoint requires active project membership and Project.View, with membership checked again inside the read snapshot. Requests above 100 current definitions return 413 rather than issuing an unbounded projection bundle.

The endpoint returns one fully validated current materialized bundle or one authoritative tally from a pinned source snapshot. Counts are never partially combined. Source fallback includes the live question IDs covered by that snapshot; a deleted or uncovered definition is not a verified zero. Missing or invalid projection rows trigger fallback, including unanswered questions whose absence has not been verified. This preserves correctness but means an unanswered question can require the source tally; this slice makes no universal read-performance claim. Guarded source refusals return 403, 404 or 503 instead of cached counts.

The question-management parent route provides one read-only NgRx SignalStore for the count snapshot. Design and Assign acquire demand from it, share one HTTP request and signal across navigation, and release demand on exit; Preview does not fetch. The store exposes its active scope, permission gate, request phase, accepted and last-good responses, error, and a local accepted-response sequence. The endpoint has no server revision, so this sequence is not a server monotonicity proof. QuestionAnswers SignalR invalidations and reconnect invalidations refresh the counts, with 100 ms burst coalescing and a 30-second recovery refresh. Changing project or user, losing View or Design permission, or leaving the route cancels pending requests. Count refreshes do not replace question drafts or form references. Existing questions remain locked while evidence is loading, unavailable or does not cover their identity; new unsaved questions remain editable. Before the first live evidence, turning off the consumer retains stored-count behavior. After live evidence, rollback preserves conservative edit locks as described below. The existing project DTO and manual tally action retain their current behavior.

Acceptance covers source-definition removal and membership revocation during reads, projection validation and fallback, endpoint capacity/gates, SignalR burst coalescing, cancellation on project switch or disable, conservative edit locks after failure, and preservation of dirty form state. Deployment, activation, elapsed soak evidence and production performance acceptance remain separate.

Question assignment lock cutover

The assignment page shares the parent route's QuestionCountsState with the stage view and keeps AssignStore page-scoped. Its lock indicators and unlocked-assignment warning use the same current question-count coverage as the designer, but fall back in the opposite direction. On this page a lock blocks no edit; it only hides the "adding to an active stage" warning, the per-question warning icons and the active-question border. So only ready evidence that covers a question may override its stored count: a positive live count locks it and a covered unanswered definition unlocks it. Loading, unavailable, refused (401/403/404) and uncovered evidence fall back to the stored count, so a question the stored count calls unanswered keeps its warning. Missing evidence never hides the warning. Count refreshes only recompute lock metadata: selected question IDs, tree state and unsaved assignment choices are not reset or persisted. Because the assignment state is now scoped to the assignment page, pending selections and tree state last for one page visit: switching to the Design or Preview tab and back starts with an empty selection.

Flag decision: reuse the independently default-off materializedProjectStatisticsQuestionCounts consumer and its existing dependencies. This is the assignment half of the approved question-count consumer, not a new endpoint or scope. Disabled and non-pilot responses retain stored-count behavior. The existing SignalR coalescing, recovery refresh and project/user cancellation are reused without another subscription protocol. Focused tests cover lock transitions, missing evidence, legacy behavior, selection preservation, refused (401/403/404) and other-project evidence, the stored-count fallback that keeps the unlocked-assignment warning visible and a project switch that discards the previous scope's late response; shared state tests cover refresh failures, terminal refusals and cancellation.

Safe edit locks during consumer rollback

If the current authenticated project has already received live count evidence, disabling the consumer locally or receiving a disabled response clears displayed counts but preserves conservative locks for existing questions. An old cached zero is not evidence that a question with newly recorded answers is editable. Pending requests and polling stop when the local flag is off. New unsaved question drafts retain their existing behavior; no question entities or form drafts are replaced.

This protection belongs only to the component's current project and authenticated user. Changing identity, signing out or disposing the view clears it. Fresh count evidence after reactivation can release locks normally. Initial flag-off and initially non-pilot views continue using legacy behavior. A legacy ProjectDetails reload cannot establish fresh counts because its stored question tally is refreshed manually; this change does not invoke that mutation or silently replace the user's draft.