Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3d7d6e5
Refactoring and much better individual phase type handling for appeal…
jmgasper Sep 27, 2025
3ec1403
Lots of cleanup, simplification, and refactoring
jmgasper Sep 27, 2025
8e3d5db
Cleanup
jmgasper Sep 27, 2025
a93bc1e
Cleanup
jmgasper Sep 27, 2025
7cbe1c8
Lint
jmgasper Sep 27, 2025
cdbeb67
End of review phase short circuit fixes
jmgasper Sep 27, 2025
092efc9
Build fix
jmgasper Sep 27, 2025
7deb722
Clean up logging a bit
jmgasper Sep 27, 2025
b003f21
Enforce review uniqueness for resource ID / submissionId / scorecardI…
jmgasper Sep 27, 2025
bf11ca5
Handle edge case related to review creation
jmgasper Sep 27, 2025
44abfc5
Make sure end date is set when transitioning to COMPLETED or CANCELLED*
jmgasper Sep 27, 2025
63275da
Additional tweak for First2Finish sequential iterative reviews
jmgasper Sep 27, 2025
2c5f52a
Handle minimumPassingScore for scorecards
jmgasper Sep 28, 2025
4467ebb
More robust phase matching for review completed events
jmgasper Sep 28, 2025
768805a
Iterative review phase fixes
jmgasper Sep 28, 2025
fe7245b
Add pending review to reviewer for each iterative review phase
jmgasper Sep 28, 2025
1f4acad
Don't handle a second iterative reviewer duplicate message at the wro…
jmgasper Sep 28, 2025
f0b78fc
Iterative review tweaks
jmgasper Sep 28, 2025
01a3a98
Additional iterative review fixes to process each submission sequenti…
jmgasper Sep 28, 2025
0eb453d
Iterative review updates
jmgasper Sep 28, 2025
ebfecb8
Fix this query to use updated schema
jmgasper Sep 29, 2025
2ada351
Remove challenge log files
jmgasper Sep 29, 2025
6d441c7
Challenge track consistency
jmgasper Sep 29, 2025
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
853 changes: 853 additions & 0 deletions 812522dc-eabb-4875-bc44-ea2ba9b90494.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ ENV NODE_ENV production
COPY --from=build /usr/src/app/dist ./dist
COPY --from=deps /usr/src/app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/main.js"]
CMD ["node", "dist/src/main.js"]
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import tseslint from 'typescript-eslint';

export default tseslint.config(
{
ignores: ['eslint.config.mjs'],
ignores: ['eslint.config.mjs', 'src/autopilot/generated/**/*'],
},
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"test:e2e": "jest --config ./test/jest-e2e.json",
"prisma:generate": "prisma generate --schema prisma/challenge.schema.prisma && prisma generate --schema prisma/autopilot.schema.prisma",
"postinstall": "prisma generate --schema prisma/challenge.schema.prisma && prisma generate --schema prisma/autopilot.schema.prisma && patch-package",
"prisma:pushautopilot": "prisma db push --schema prisma/autopilot.schema.prisma"
"prisma:pushautopilot": "prisma db push --schema prisma/autopilot.schema.prisma",
"pull:logs": "ts-node scripts/fetch-autopilot-actions.ts"
},
"prisma": {
"schema": "prisma/challenge.schema.prisma"
Expand Down
4 changes: 2 additions & 2 deletions prisma/challenge.schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ generator client {

// Enum for allowed challenge track values (matches app-constants)
enum ChallengeTrackEnum {
DEVELOP
DEVELOPMENT
DESIGN
DATA_SCIENCE
QA
QUALITY_ASSURANCE
}

enum ReviewTypeEnum {
Expand Down
70 changes: 70 additions & 0 deletions scripts/fetch-autopilot-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Prisma, PrismaClient } from '@prisma/client';

interface AutopilotActionRecord {
id: string;
challengeId: string | null;
action: string;
status: string;
source: string | null;
details: Prisma.JsonValue | null;
createdAt: Date | string;
}

async function main(): Promise<void> {
const [, , challengeId] = process.argv;

if (!challengeId) {
console.error('Usage: ts-node scripts/fetch-autopilot-actions.ts <challengeId>');
process.exit(1);
}

const databaseUrl = process.env.AUTOPILOT_DB_URL;

if (!databaseUrl) {
console.error('AUTOPILOT_DB_URL environment variable is not set.');
process.exit(1);
}

const prisma = new PrismaClient({
datasources: {
db: {
url: databaseUrl,
},
},
});

try {
const rows = (await prisma.$queryRaw<AutopilotActionRecord[]>
(Prisma.sql`
SELECT
"id",
"challengeId",
"action",
"status",
"source",
"details",
"createdAt"
FROM "autopilot"."actions"
WHERE "challengeId" = ${challengeId}
ORDER BY "createdAt" ASC
`)) ?? [];

const normalizedRows = rows.map((row) => ({
...row,
createdAt:
row.createdAt instanceof Date
? row.createdAt.toISOString()
: row.createdAt,
}));

console.log(JSON.stringify(normalizedRows, null, 2));
} catch (error) {
const err = error as Error;
console.error(`Failed to fetch autopilot actions: ${err.message}`);
process.exitCode = 1;
} finally {
await prisma.$disconnect();
}
}

void main();
6 changes: 6 additions & 0 deletions src/autopilot/autopilot.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { ResourcesModule } from '../resources/resources.module';
import { PhaseReviewService } from './services/phase-review.service';
import { ReviewAssignmentService } from './services/review-assignment.service';
import { ChallengeCompletionService } from './services/challenge-completion.service';
import { PhaseScheduleManager } from './services/phase-schedule-manager.service';
import { ResourceEventHandler } from './services/resource-event-handler.service';
import { First2FinishService } from './services/first2finish.service';

@Module({
imports: [
Expand All @@ -23,6 +26,9 @@ import { ChallengeCompletionService } from './services/challenge-completion.serv
providers: [
AutopilotService,
SchedulerService,
PhaseScheduleManager,
ResourceEventHandler,
First2FinishService,
PhaseReviewService,
ReviewAssignmentService,
ChallengeCompletionService,
Expand Down
26 changes: 26 additions & 0 deletions src/autopilot/constants/challenge.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const FIRST2FINISH_TYPE = 'first2finish';
export const TOPGEAR_TASK_TYPE = 'topgear task';

export function normalizeChallengeType(type?: string): string {
return (type ?? '').toLowerCase();
}

export function isTopgearTaskChallenge(type?: string): boolean {
return normalizeChallengeType(type) === TOPGEAR_TASK_TYPE;
}

export function isFirst2FinishChallenge(type?: string): boolean {
const normalized = normalizeChallengeType(type);
return normalized === FIRST2FINISH_TYPE || normalized === TOPGEAR_TASK_TYPE;
}

export function describeChallengeType(type?: string): string {
const normalized = normalizeChallengeType(type);
if (normalized === TOPGEAR_TASK_TYPE) {
return 'Topgear Task';
}
if (normalized === FIRST2FINISH_TYPE) {
return 'First2Finish';
}
return type ?? 'Unknown';
}
27 changes: 26 additions & 1 deletion src/autopilot/constants/review.constants.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
export const REVIEW_PHASE_NAMES = new Set(['Review', 'Iterative Review']);
export const REVIEW_PHASE_NAMES = new Set([
'Review',
'Iterative Review',
'Post-Mortem',
]);

export const ITERATIVE_REVIEW_PHASE_NAME = 'Iterative Review';
export const POST_MORTEM_PHASE_NAME = 'Post-Mortem';
export const REGISTRATION_PHASE_NAME = 'Registration';
export const SUBMISSION_PHASE_NAME = 'Submission';
export const TOPGEAR_SUBMISSION_PHASE_NAME = 'Topgear Submission';

export const SUBMISSION_PHASE_NAMES = new Set<string>([
SUBMISSION_PHASE_NAME,
TOPGEAR_SUBMISSION_PHASE_NAME,
]);

export const DEFAULT_APPEALS_PHASE_NAMES = new Set(['Appeals']);
export const DEFAULT_APPEALS_RESPONSE_PHASE_NAMES = new Set([
'Appeals Response',
]);

const DEFAULT_PHASE_ROLES = ['Reviewer', 'Iterative Reviewer'];

export const PHASE_ROLE_MAP: Record<string, string[]> = {
Review: ['Reviewer'],
'Iterative Review': ['Iterative Reviewer'],
'Post-Mortem': ['Reviewer', 'Copilot'],
};

export function getRoleNamesForPhase(phaseName: string): string[] {
return PHASE_ROLE_MAP[phaseName] ?? DEFAULT_PHASE_ROLES;
}

export function isSubmissionPhaseName(phaseName: string): boolean {
return SUBMISSION_PHASE_NAMES.has(phaseName);
}
51 changes: 51 additions & 0 deletions src/autopilot/interfaces/autopilot.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,54 @@ export interface SubmissionAggregatePayload {
originalTopic?: string;
[key: string]: unknown;
}

export interface ResourceEventPayload {
id: string;
challengeId: string;
memberId: string;
memberHandle: string;
roleId: string;
created: string;
createdBy: string;
}

export interface ReviewCompletedPayload {
challengeId: string;
submissionId: string;
reviewId: string;
phaseId: string;
scorecardId: string;
reviewerResourceId: string;
reviewerHandle: string;
reviewerMemberId: string;
submitterHandle: string;
submitterMemberId: string;
completedAt: string;
initialScore: number;
}

export interface AppealRespondedPayload {
challengeId: string;
submissionId: string;
reviewId: string;
scorecardId: string;
appealId: string;
appealResponseId: string;
reviewerResourceId: string;
reviewerHandle: string;
reviewerMemberId: string;
submitterHandle: string;
submitterMemberId: string;
reviewCompletedAt: string;
finalScore: number;
}

export interface First2FinishSubmissionPayload {
challengeId: string;
submissionId: string;
memberId: string;
memberHandle: string;
submittedAt: string;
}

export type TopgearSubmissionPayload = First2FinishSubmissionPayload;
Loading