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
24 changes: 24 additions & 0 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@ pnpm start
For development with TypeScript watch mode, use `pnpm start:dev`. Production runs the
compiled NestJS entrypoint at `dist/main.js`.

## Copilot History Regression Tests

Copilot profile summaries, challenge history, and track totals include only
completed or cancelled challenges. Fulfillment is completed challenges divided
by completed plus cancelled challenges, excluding client-request cancellations
from the denominator. Client-request cancellations remain visible in history.
Drafts and other non-terminal challenges, including active challenges in post
mortem, are excluded from all Copilot counts. Reviewer status handling is unchanged.

The PostgreSQL regression suite executes the service's real SQL against minimal
fixtures in an empty test database. It verifies the PM-5966 clarification example,
all cancellation types, empty fulfillment denominators, visibility, and Reviewer
behavior. Both schemas and all fixtures are created in a transaction and rolled
back afterward; setup fails if either schema already exists.

```bash
nvm use
SPECIAL_ROLE_TEST_DB_URL=postgresql://user:password@localhost:5432/member_test \
pnpm exec mocha --require ts-node/register/transpile-only test/integration/SpecialRoleService.test.js
```

The suite is skipped when `SPECIAL_ROLE_TEST_DB_URL` is unset. The existing
isolated service tests remain available through `pnpm test`.

## Database Setup

Please setup PostgreSQL first. If you are using docker, you can run:
Expand Down
25 changes: 19 additions & 6 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1656,8 +1656,11 @@ paths:
Reviewer, Primary Reviewer, Reviewer, Failure Reviewer, Final Reviewer,
Aggregator, Stress Reviewer, Accuracy Reviewer, Primary Screener,
Checkpoint Screener, and Checkpoint Reviewer. Copilot assignments use
the Copilot resource role. The summary does not load challenge names or
Copilot track/fulfillment details.
the Copilot resource role and count only COMPLETED, CANCELLED, or
CANCELLED_* challenges, including client-request cancellations. Drafts
and other non-terminal challenges are excluded from Copilot counts;
Reviewer counts have no status restriction. The summary does not load
challenge names or Copilot track/fulfillment details.

Authorization:
- Public: Anonymous callers may retrieve the count-only summary.
Expand Down Expand Up @@ -1707,6 +1710,11 @@ paths:
no groups, no user-whitelist rows, and are not tasks. Restricted
challenge IDs and names are never returned.

Copilot history, detail totals, and track counts include only COMPLETED,
CANCELLED, or CANCELLED_* challenges. Drafts and other non-terminal
challenges, including active challenges in post mortem, are excluded.
Reviewer history and totals have no status restriction.

Copilot `trackCounts` includes only non-zero Development, Design,
Quality Assurance, and Data Science keys. Fulfillment is
`COMPLETED / (COMPLETED + qualifying CANCELLED*) * 100`.
Expand Down Expand Up @@ -2803,12 +2811,15 @@ definitions:
type: integer
format: int32
minimum: 1
description: Anonymous-visible distinct challenge count for this role family.
description: >
Anonymous-visible distinct challenge count for this role family.
Copilot counts include only completed or cancelled challenges.
SpecialRoleTrackCounts:
type: object
description: >
Non-zero Copilot challenge counts by challenge track. A zero-count track
is omitted.
Non-zero Copilot challenge counts by challenge track, including only
completed or cancelled challenges. Client-request cancellations are
included. A zero-count track is omitted.
properties:
DEVELOPMENT:
type: integer
Expand Down Expand Up @@ -2919,7 +2930,9 @@ definitions:
type: integer
format: int32
minimum: 0
description: Total anonymous-visible distinct challenges for this role.
description: >
Total anonymous-visible distinct challenges for this role. Copilot
totals include only completed or cancelled challenges.
trackCounts:
description: Present only for Copilot details.
$ref: '#/definitions/SpecialRoleTrackCounts'
Expand Down
29 changes: 21 additions & 8 deletions src/services/SpecialRoleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ function buildFulfillment (completed, cancelled) {
/**
* Build the reusable SQL common-table expressions for one role's anonymous-
* visible challenges. Resource assignments are de-duplicated per challenge.
* Copilot summaries, history, and metrics include only completed or cancelled
* challenges; Reviewer queries retain all statuses. Client-request cancellations
* remain visible but are excluded separately when calculating fulfillment.
* Visibility matches challenge-api's anonymous baseline: no groups, no user
* whitelist rows, and no tasks. This helper only constructs SQL and does not
* execute or raise database errors.
Expand Down Expand Up @@ -168,6 +171,12 @@ function buildVisibleRoleChallengesCte (userId, role) {
ON challenge."id" = roleAssignment."challengeId"
WHERE cardinality(challenge."groups") = 0
AND challenge."taskIsTask" = FALSE
${role === COPILOT_ROLE
? ChallengesPrisma.sql`AND (
challenge."status" = 'COMPLETED'
OR challenge."status"::text LIKE 'CANCELLED%'
)`
: ChallengesPrisma.empty}
AND NOT EXISTS (
SELECT 1
FROM challenges."ChallengeUserWhitelist" AS whitelist
Expand All @@ -180,7 +189,8 @@ function buildVisibleRoleChallengesCte (userId, role) {
/**
* Count distinct anonymous-visible challenges for one role. The profile
* summary uses this count so it matches the anonymous-visible detail set and
* never discloses restricted challenge assignments.
* never discloses restricted challenge assignments. Copilot counts include
* only completed or cancelled challenges.
* @param {BigInt|Number|String} userId member user ID from the members database
* @param {String} role `copilot` or `reviewer`
* @returns {Promise<Number>} visible distinct challenge count
Expand All @@ -198,8 +208,9 @@ async function countVisibleRoleChallenges (userId, role) {
}

/**
* Load every anonymous-visible challenge detail. PostgreSQL
* orders lifecycle end date, then start/creation fallback, newest first; latest
* Load every anonymous-visible challenge detail, limited to completed or
* cancelled challenges for Copilots. PostgreSQL orders lifecycle end date,
* then start/creation fallback, newest first; latest
* Resource assignment and challenge ID are deterministic tie-breakers. The
* Resource timestamp is assignment time, not a literal review-completion date.
* @param {BigInt|Number|String} userId member user ID from the members database
Expand Down Expand Up @@ -240,8 +251,8 @@ async function loadVisibleRoleChallenges (userId, role) {

/**
* Aggregate Copilot tracks and terminal statuses entirely in PostgreSQL over
* the anonymous-visible challenge set. Only the clicked Copilot detail route
* invokes this query; zero-count tracks are naturally omitted from the result.
* the anonymous-visible completed or cancelled challenge set. Only the Copilot
* detail route invokes this query; zero-count tracks are omitted from the result.
* @param {BigInt|Number|String} userId member user ID from the members database
* @returns {Promise<Object>} non-zero track counts and fulfillment statistics
* @throws {Error} propagates cross-schema PostgreSQL query failures
Expand Down Expand Up @@ -338,7 +349,8 @@ function formatChallenge (challenge) {
* Get public Copilot and Reviewer badge counts for a member profile. The
* summary reuses the detail view's anonymous-visible challenge set so its
* counts match the role details. Roles with no visible challenges are omitted,
* and challenges with multiple reviewer assignments count once.
* and challenges with multiple reviewer assignments count once. Copilot counts
* exclude drafts and other non-terminal challenges.
* @param {String} handle member handle resolved through the members table
* @returns {Promise<Object>} optional count-only Copilot/Reviewer summaries
* @throws {NotFoundError} when the member handle does not exist
Expand Down Expand Up @@ -372,8 +384,9 @@ getMemberRoleStats.schema = {

/**
* Get every newest-first anonymous-visible Copilot or Reviewer challenge for a
* member. Copilot responses also include public-set track counts and terminal
* fulfillment. Details never return restricted challenge IDs.
* member. Copilot history and track counts include only completed or cancelled
* challenges, with client-request cancellations excluded from fulfillment.
* Details never return restricted challenge IDs.
* @param {String} handle member handle resolved through the members table
* @param {String} role `copilot` or `reviewer`
* @returns {Promise<Object>} complete challenge list and aggregate role metrics
Expand Down
Loading
Loading