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
5 changes: 5 additions & 0 deletions .changeset/worker-code-quality-phase-3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@bilbomd/worker': minor
---

Enhance worker code quality with centralized configuration and comprehensive test coverage. Extract all magic numbers to config/constants.ts (worker concurrency, polling intervals, retry settings, progress calculation). Consolidate duplicated error handling into shared helpers/errors.ts utility. Add 100% test coverage for mongo-utils.ts and workerControl.ts, plus 63% coverage for job-utils.ts (39 new tests total). Improve runPythonStep.ts coverage from 88% to 92%. Remove dead/commented code across worker files.
7 changes: 5 additions & 2 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
"Bash(pnpm -C apps/ui run test:*)",
"Bash(pnpm -C apps/worker run test:*)",
"Bash(gh run view:*)",
"Bash(gh pr checks:*)"
"Bash(gh pr checks:*)",
"Bash(pnpm -F @bilbomd/worker test:*)",
"Bash(gh pr view:*)",
"Bash(git fetch:*)"
]
}
}
}
49 changes: 49 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,55 @@ pnpm changeset # Select packages, choose semver bump, write summary

Changesets are applied on merge to `main`, producing git tags and Docker image semver tags. `updateInternalDependencies: "patch"` is enabled — bumping an internal package auto-bumps dependents.

### Manual Changeset Creation

**IMPORTANT**: The `pnpm changeset` command is interactive and won't work in non-TTY environments (like Claude Code CLI). When this happens, create changeset files manually:

1. **Create a new file** in `.changeset/` with a descriptive kebab-case name:
- Pattern: `.changeset/descriptive-name.md`
- Examples: `worker-code-quality-improvements.md`, `backend-security-fixes.md`

2. **File format** (YAML front matter + description):
```markdown
---
'@bilbomd/package-name': patch|minor|major
---

Brief description of changes. Focus on user/developer impact, not implementation details.
```

3. **Semver guidelines**:
- `patch` - Bug fixes, minor improvements, internal refactoring
- `minor` - New features, significant improvements (backwards compatible)
- `major` - Breaking changes

4. **Examples**:
```markdown
---
'@bilbomd/worker': patch
---

Improve worker reliability with graceful shutdown handling and MongoDB connection retry logic.
```

```markdown
---
'@bilbomd/worker': minor
---

Add comprehensive test coverage for critical infrastructure (mongo-utils, job-utils, workerControl). Extract magic numbers to centralized config/constants.ts. Consolidate error handling utilities.
```

5. **Multiple packages** (if changes affect multiple):
```markdown
---
'@bilbomd/backend': patch
'@bilbomd/mongodb-schema': patch
---

Fix user authentication schema validation and update backend handlers.
```

## Git Branch Naming Convention

Use standardized branch prefixes to indicate the type of work. Branch names should use kebab-case (lowercase with hyphens).
Expand Down
76 changes: 76 additions & 0 deletions apps/worker/src/config/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Worker configuration constants
*
* This file centralizes all magic numbers and hardcoded values used throughout
* the worker application to improve maintainability and configuration flexibility.
*/

// Worker concurrency settings
export const WORKER_CONCURRENCY = {
NERSC: 50,
LOCAL: 1,
MOVIE: 1,
MULTI_MD: 1
} as const

// BullMQ lock settings (in milliseconds)
export const LOCK_SETTINGS = {
DURATION: 60_000, // 1 minute
RENEW_TIME: 30_000 // 30 seconds
} as const

// Polling and monitoring intervals (in milliseconds)
export const INTERVALS = {
TOKEN_CHECK: 300_000, // 5 minutes
JOB_MONITORING: 60_000, // 1 minute
NERSC_TASK_POLL: 2_000, // 2 seconds
NERSC_JOB_POLL: 60_000 // 1 minute
} as const

// NERSC API retry configuration
export const NERSC_RETRY = {
MAX_ATTEMPTS: 11,
MAX_JOB_RETRIES: 10,
MAX_ITERATIONS: 1_440, // 1440 x 60s = 24 hours
RETRY_DELAY: 60_000 // 1 minute
} as const

// Progress calculation constants
export const PROGRESS = {
MIN: 20, // Minimum progress percentage
MAX: 90, // Maximum progress percentage
SCALE_FACTOR: 70 // Scale factor for progress calculation (20% to 90%)
} as const

// Step weights for progress calculation
export const STEP_WEIGHTS: Record<string, number> = {
alphafold: 20,
pdb2crd: 5,
pae: 5,
autorg: 5,
minimize: 10,
initfoxs: 5,
heat: 10,
md: 30,
dcd2pdb: 10,
foxs: 10,
multifoxs: 10,
copy_results_to_cfs: 5,
results: 3,
email: 1,
nersc_prepare_slurm_batch: 5,
nersc_submit_slurm_batch: 5,
nersc_job_status: 5,
nersc_copy_results_to_cfs: 5
} as const

// Server configuration
export const SERVER = {
PORT: 3000
} as const

// NERSC paths
// TODO: Make this configurable via environment variable
export const NERSC_PATHS = {
SCRIPT_LOGS_DIR: '/global/homes/s/sclassen/script-logs'
} as const
6 changes: 6 additions & 0 deletions apps/worker/src/helpers/__tests__/runPythonStep.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,10 @@ describe('runPythonStep', () => {
const result = await runPythonStep('a.py', 'b.yaml')
expect(result).toEqual({ code: 42, signal: 'SIGUSR1' })
})

it('throws error when spawn fails', async () => {
const spawnError = new Error('ENOENT: python binary not found')
setTimeout(() => mockChild.emit('error', spawnError), 10)
await expect(runPythonStep('a.py', 'b.yaml')).rejects.toThrow('ENOENT: python binary not found')
})
})
14 changes: 14 additions & 0 deletions apps/worker/src/helpers/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Error handling utilities
*
* Shared error handling functions used throughout the worker application
*/

/**
* Extracts a readable error message from various error types
*
* @param e - The error to extract a message from
* @returns A string representation of the error
*/
export const getErrorMessage = (e: unknown): string =>
e instanceof Error ? e.message : typeof e === 'string' ? e : JSON.stringify(e)
Loading
Loading