diff --git a/src/common/utils.js b/src/common/utils.js index 7eef74b..b2e7c3a 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -434,17 +434,9 @@ export const getFHIRServerConfigFromCurrentContext = () => { return getLocalFHIRServerConfig() || getDefaultFHIRServerConfig(); } -export const canSwitchServer = () => { - const user = getCurrentUser(); - - return Boolean( - getSelectedServerConfig() || - get(user, 'is_superuser') || - hasAuthGroup(user, 'server') - ); -} - -export const hasAuthGroup = (user, groupName) => Boolean(find(user?.auth_groups, group => group.includes(groupName))) +// Matches the group's exact name, never a group whose name contains it +// (OpenConceptLab/ocl_online#517). +export const hasAuthGroup = (user, groupName) => Boolean(user?.auth_groups?.includes(groupName)) export const canViewOperationsPanel = () => { const user = getCurrentUser() diff --git a/src/components/map-projects/MapProject.jsx b/src/components/map-projects/MapProject.jsx index f7fbf80..de3e798 100644 --- a/src/components/map-projects/MapProject.jsx +++ b/src/components/map-projects/MapProject.jsx @@ -91,7 +91,7 @@ import ConfigurationForm from './ConfigurationForm' import Controls from './Controls' import DataGridControls from './DataGridControls' import { getPreviewEligibleRowIndexes, getRowsToProcess, countAIOnlyRows, getAutoMatchAIOnOpen, takeRunRows, clearRunRowStatuses, canRunAlgorithm, spendsMatchQuota, shouldStopAIStep, getAIRequestIdempotencyKey, getCandidatePoolFingerprint, hasCurrentAnalysis, getScispacyRowResults, getPendingRowLookups, waitForLookups, AI_LOOKUP_WAIT_MS, RERANK_LOOKUP_WAIT_MS, RERANK_MAX_IN_FLIGHT, isScispacyWarmingUp } from './autoMatchRows' -import { aiOnByDefault, getMaxRunRows } from '@openconceptlab/oclmap-extensions' +import { aiOnByDefault, getMaxRunRows, getMapperSettings } from '@openconceptlab/oclmap-extensions' import { createAutosaveScheduler, createLatestSender, saveOnLeave, trackSave, whenSaved, installUnloadGuard } from './autosave' import MatchSummaryCard from './MatchSummaryCard' import MappingDecisionResult from './MappingDecisionResult' @@ -628,16 +628,14 @@ const MapProject = () => { const OCL_ONLINE_API_URL = window.OCL_ONLINE_API_URL || process.env.OCL_ONLINE_API_URL const inAIAssistantGroup = Boolean(hasCapability(user, 'users.mapper_ai_assistant') && AI_ASSISTANT_API_URL) const isCoreUser = hasAuthGroup(user, 'core_user') - // Batch size and concurrent requests per algorithm, by the user's tier - // (ocl_online#274, ocl_issues#2849) - const requestLimits = getRequestLimits(user) + // Batch size and concurrent requests per algorithm the user can set + // (ocl_online#274, ocl_issues#2849, ocl_online#517) + const requestLimits = getRequestLimits(getMapperSettings(user)) const mapperPreview = getMapperPreview() // Only staff choose the AI model and prompt template (canSelectAIModel gates - // both pickers). Core, early-access and unlimited-AI users keep the output - // language; preview users get the project's template and its default model. - const { canSelectAIModel, canSetAIOutputLocale } = getAIAssistantChoices( - user, {unlimitedAICalls: mapperPreview.aiAssistantCalls.unlimited} - ) + // both pickers). Users granted it keep the output language; everyone else + // gets the project's template and its default model. + const { canSelectAIModel, canSetAIOutputLocale } = getAIAssistantChoices(user) const CANDIDATES_LIMIT = 15 const canBridge = bridgeRef?.current?.canBridge() const canScispacy = Boolean( diff --git a/src/components/map-projects/__tests__/aiVisibility.test.js b/src/components/map-projects/__tests__/aiVisibility.test.js index db30a11..2e7d5d5 100644 --- a/src/components/map-projects/__tests__/aiVisibility.test.js +++ b/src/components/map-projects/__tests__/aiVisibility.test.js @@ -1,7 +1,7 @@ import test from 'node:test' import assert from 'node:assert/strict' -import { canSeeAIInternals, getAIAssistantChoices, getModelUsed, getPromptTemplateKey, createLatestRequestGate } from '../aiVisibility.js' +import { AI_OUTPUT_LOCALE_PERMISSION, canSeeAIInternals, getAIAssistantChoices, getModelUsed, getPromptTemplateKey, createLatestRequestGate } from '../aiVisibility.js' test('canSeeAIInternals: staff and superusers see the model, prompt template, URI and raw JSON', () => { assert.equal(canSeeAIInternals({ is_staff: true }), true) @@ -13,9 +13,8 @@ test('canSeeAIInternals: a non-staff core user does not', () => { assert.equal(canSeeAIInternals({ is_staff: false, is_superuser: false, auth_groups: ['core_user'] }), false) }) -test('canSeeAIInternals: early-access, preview and signed-out users do not', () => { - assert.equal(canSeeAIInternals({ auth_groups: ['early_access'] }), false) - assert.equal(canSeeAIInternals({ auth_groups: ['preview'] }), false) +test('canSeeAIInternals: other and signed-out users do not', () => { + assert.equal(canSeeAIInternals({ auth_groups: ['mapper_ai_assistant'] }), false) assert.equal(canSeeAIInternals(null), false) assert.equal(canSeeAIInternals(undefined), false) }) @@ -26,17 +25,17 @@ test('getAIAssistantChoices: staff and superusers choose the model, the prompt t assert.deepEqual(getAIAssistantChoices({ is_superuser: true }), both) }) -test('getAIAssistantChoices: non-staff core, early-access and unlimited-AI users choose the output language only', () => { +test('getAIAssistantChoices: non-staff users granted the output language choose it only', () => { const localeOnly = { canSelectAIModel: false, canSetAIOutputLocale: true } - assert.deepEqual(getAIAssistantChoices({ is_staff: false, auth_groups: ['core_user'] }), localeOnly) - assert.deepEqual(getAIAssistantChoices({ auth_groups: ['early_access'] }), localeOnly) - assert.deepEqual(getAIAssistantChoices({ auth_groups: ['preview'] }, { unlimitedAICalls: true }), localeOnly) + assert.deepEqual(getAIAssistantChoices({ permissions: ['users.mapper_ai_assistant', AI_OUTPUT_LOCALE_PERMISSION] }), localeOnly) + assert.equal(AI_OUTPUT_LOCALE_PERMISSION, 'users.mapper_ai_output_locale') }) -test('getAIAssistantChoices: preview and signed-out users choose nothing', () => { +test('getAIAssistantChoices: other users choose nothing, whatever their groups', () => { const none = { canSelectAIModel: false, canSetAIOutputLocale: false } - assert.deepEqual(getAIAssistantChoices({ auth_groups: ['preview'] }), none) - assert.deepEqual(getAIAssistantChoices({ auth_groups: ['preview'] }, { unlimitedAICalls: false }), none) + assert.deepEqual(getAIAssistantChoices({ permissions: ['users.mapper_ai_assistant'] }), none) + assert.deepEqual(getAIAssistantChoices({ auth_groups: ['mapper-approved'], permissions: [] }), none) + assert.deepEqual(getAIAssistantChoices({ auth_groups: ['mapper_ai_output_locale'] }), none) assert.deepEqual(getAIAssistantChoices(null), none) assert.deepEqual(getAIAssistantChoices(undefined), none) }) diff --git a/src/components/map-projects/__tests__/requestLimits.test.js b/src/components/map-projects/__tests__/requestLimits.test.js index 795ee03..80a6817 100644 --- a/src/components/map-projects/__tests__/requestLimits.test.js +++ b/src/components/map-projects/__tests__/requestLimits.test.js @@ -1,12 +1,10 @@ /** - * Auto Match request limits per algorithm (ocl_online#274). + * Auto Match request settings per algorithm (ocl_online#274, #517). * - * Non-core users can't run batches of more than 10 rows, whether they type it - * in the form or a project saved it earlier. Early-access users keep up to 5 - * requests in flight; preview users 2 (ocl_issues#2849), as many previews at - * once could fill the server's matching capacity. A preview project has at - * most 25 rows (3 batches of 10), so 2 costs one more batch's time. Core users - * and staff keep the form's full range. + * The getMapperSettings checkpoint sets the most rows per request and + * requests in flight a user can choose. The open build allows the form's full + * range, and a project's saved settings run as they are. Below that range, + * saved settings run within it. * * Run with: npm test */ @@ -14,74 +12,52 @@ import test from 'node:test' import assert from 'node:assert/strict' -import { hasFullRequestLimits, getRequestLimits, getRequestSettings, applyRequestSettings } from '../requestLimits.js' +import { getRequestLimits, getRequestSettings, applyRequestSettings } from '../requestLimits.js' +import { getMapperSettings } from '../autoMatchCheck.js' -const FULL = getRequestLimits({ is_staff: true }) -const EARLY_ACCESS = getRequestLimits({ auth_groups: ['early_access'] }) -const PREVIEW = getRequestLimits({ auth_groups: ['preview'] }) +const OPEN = getRequestLimits(getMapperSettings(null)) +const NARROW = getRequestLimits({ batchSize: 10, concurrentRequests: 5 }) -test('hasFullRequestLimits: core users, staff and superusers', () => { - assert.equal(hasFullRequestLimits({ auth_groups: ['core_user'] }), true) - assert.equal(hasFullRequestLimits({ is_staff: true }), true) - assert.equal(hasFullRequestLimits({ is_superuser: true }), true) +test('getRequestLimits: the open build allows the form\'s full range', () => { + assert.deepEqual(OPEN, { batchSize: 1000, concurrentRequests: 50, capped: false }) + assert.deepEqual(getRequestLimits(), OPEN) + assert.deepEqual(getRequestLimits(null), OPEN) }) -test('hasFullRequestLimits: preview, early-access and signed-out users are capped', () => { - assert.equal(hasFullRequestLimits({ auth_groups: ['early_access'] }), false) - assert.equal(hasFullRequestLimits({ auth_groups: [] }), false) - assert.equal(hasFullRequestLimits({ is_staff: false, auth_groups: ['preview'] }), false) - assert.equal(hasFullRequestLimits(null), false) - assert.equal(hasFullRequestLimits(undefined), false) +test('getRequestLimits: settings below the form\'s range cap it', () => { + assert.deepEqual(NARROW, { batchSize: 10, concurrentRequests: 5, capped: true }) + assert.deepEqual(getRequestLimits({ batchSize: 1000, concurrentRequests: 2 }), { batchSize: 1000, concurrentRequests: 2, capped: true }) }) -test('getRequestLimits: the form range for core users and staff', () => { - assert.deepEqual(getRequestLimits({ auth_groups: ['core_user'] }), { batchSize: 1000, concurrentRequests: 50, capped: false }) - assert.deepEqual(FULL, { batchSize: 1000, concurrentRequests: 50, capped: false }) +test('getRequestLimits: settings stay within the form\'s range', () => { + assert.deepEqual(getRequestLimits({ batchSize: 5000, concurrentRequests: 80 }), OPEN) }) -test('getRequestLimits: early access, 10 rows × 5 requests', () => { - assert.deepEqual(EARLY_ACCESS, { batchSize: 10, concurrentRequests: 5, capped: true }) +test('getRequestLimits: missing or invalid settings fall back to the form\'s range', () => { + assert.deepEqual(getRequestLimits({ batchSize: 0, concurrentRequests: 'abc' }), OPEN) + assert.deepEqual(getRequestLimits({ batchSize: '10' }), { batchSize: 10, concurrentRequests: 50, capped: true }) }) -test('getRequestLimits: preview, 10 rows × 2 requests', () => { - assert.deepEqual(PREVIEW, { batchSize: 10, concurrentRequests: 2, capped: true }) -}) - -test('getRequestLimits: a user in no tier, or signed out, gets the preview limits', () => { - assert.deepEqual(getRequestLimits({ auth_groups: [] }), PREVIEW) - assert.deepEqual(getRequestLimits({ auth_groups: ['mapper_ai_assistant'] }), PREVIEW) - assert.deepEqual(getRequestLimits(null), PREVIEW) -}) - -test('getRequestLimits: the highest tier wins', () => { - assert.deepEqual(getRequestLimits({ auth_groups: ['preview', 'early_access'] }), EARLY_ACCESS) - assert.deepEqual(getRequestLimits({ auth_groups: ['early_access', 'core_user'] }), FULL) -}) - -test('getRequestSettings: a capped user\'s saved project runs within the cap', () => { +test('getRequestSettings: a saved project runs within capped settings', () => { // the ticket's case: batch 1 × 25 concurrent semantic requests - assert.deepEqual(getRequestSettings({ batch_size: 1, concurrent_requests: 25 }, EARLY_ACCESS), { batchSize: 1, concurrentRequests: 5 }) - assert.deepEqual(getRequestSettings({ batch_size: 1, concurrent_requests: 25 }, PREVIEW), { batchSize: 1, concurrentRequests: 2 }) - assert.deepEqual(getRequestSettings({ batch_size: 1000, concurrent_requests: 50 }, EARLY_ACCESS), { batchSize: 10, concurrentRequests: 5 }) - assert.deepEqual(getRequestSettings({ batch_size: 1000, concurrent_requests: 50 }, PREVIEW), { batchSize: 10, concurrentRequests: 2 }) + assert.deepEqual(getRequestSettings({ batch_size: 1, concurrent_requests: 25 }, NARROW), { batchSize: 1, concurrentRequests: 5 }) + assert.deepEqual(getRequestSettings({ batch_size: 1000, concurrent_requests: 50 }, NARROW), { batchSize: 10, concurrentRequests: 5 }) // ocl-search's shipped default is 50 rows per batch - assert.deepEqual(getRequestSettings({ batch_size: 50, concurrent_requests: 2 }, EARLY_ACCESS), { batchSize: 10, concurrentRequests: 2 }) + assert.deepEqual(getRequestSettings({ batch_size: 50, concurrent_requests: 2 }, NARROW), { batchSize: 10, concurrentRequests: 2 }) }) -test('getRequestSettings: values within the cap are kept', () => { - assert.deepEqual(getRequestSettings({ batch_size: 10, concurrent_requests: 2 }, EARLY_ACCESS), { batchSize: 10, concurrentRequests: 2 }) - assert.deepEqual(getRequestSettings({ batch_size: 3, concurrent_requests: 5 }, EARLY_ACCESS), { batchSize: 3, concurrentRequests: 5 }) - assert.deepEqual(getRequestSettings({ batch_size: 3, concurrent_requests: 1 }, PREVIEW), { batchSize: 3, concurrentRequests: 1 }) +test('getRequestSettings: values within capped settings are kept', () => { + assert.deepEqual(getRequestSettings({ batch_size: 3, concurrent_requests: 5 }, NARROW), { batchSize: 3, concurrentRequests: 5 }) }) -test('getRequestSettings: core users and staff run what the project saved, even past the form\'s range', () => { - assert.deepEqual(getRequestSettings({ batch_size: 1, concurrent_requests: 25 }, FULL), { batchSize: 1, concurrentRequests: 25 }) - assert.deepEqual(getRequestSettings({ batch_size: 50, concurrent_requests: 2 }, FULL), { batchSize: 50, concurrentRequests: 2 }) - assert.deepEqual(getRequestSettings({ batch_size: 2000, concurrent_requests: 75 }, FULL), { batchSize: 2000, concurrentRequests: 75 }) +test('getRequestSettings: uncapped, a project runs what it saved, even past the form\'s range', () => { + assert.deepEqual(getRequestSettings({ batch_size: 1, concurrent_requests: 25 }, OPEN), { batchSize: 1, concurrentRequests: 25 }) + assert.deepEqual(getRequestSettings({ batch_size: 2000, concurrent_requests: 75 }, OPEN), { batchSize: 2000, concurrentRequests: 75 }) + assert.deepEqual(getRequestSettings({ batch_size: 2000, concurrent_requests: 75 }), { batchSize: 2000, concurrentRequests: 75 }) }) test('getRequestSettings: missing or invalid values fall back to 10 rows × 1 request, as before', () => { - for (const limits of [FULL, EARLY_ACCESS, PREVIEW]) { + for (const limits of [OPEN, NARROW]) { assert.deepEqual(getRequestSettings({}, limits), { batchSize: 10, concurrentRequests: 1 }) assert.deepEqual(getRequestSettings(undefined, limits), { batchSize: 10, concurrentRequests: 1 }) assert.deepEqual(getRequestSettings({ batch_size: 0, concurrent_requests: 0 }, limits), { batchSize: 10, concurrentRequests: 1 }) @@ -89,24 +65,20 @@ test('getRequestSettings: missing or invalid values fall back to 10 rows × 1 re } }) -test('getRequestSettings: without limits, the preview cap applies', () => { - assert.deepEqual(getRequestSettings({ batch_size: 50, concurrent_requests: 25 }), { batchSize: 10, concurrentRequests: 2 }) -}) - test('getRequestSettings: numeric strings are read as numbers', () => { - assert.deepEqual(getRequestSettings({ batch_size: '8', concurrent_requests: '9' }, EARLY_ACCESS), { batchSize: 8, concurrentRequests: 5 }) + assert.deepEqual(getRequestSettings({ batch_size: '8', concurrent_requests: '9' }, NARROW), { batchSize: 8, concurrentRequests: 5 }) }) -test('applyRequestSettings: a capped user\'s algorithm carries the settings the run uses, for the run and its record', () => { +test('applyRequestSettings: capped, the algorithm carries the settings the run uses, for the run and its record', () => { const algo = { id: 'ocl-semantic', type: 'ocl-semantic', batch_size: 1, concurrent_requests: 25, query_params: { semantic: true } } - assert.deepEqual(applyRequestSettings(algo, EARLY_ACCESS), { ...algo, batch_size: 1, concurrent_requests: 5 }) - assert.deepEqual(applyRequestSettings(algo, PREVIEW), { ...algo, batch_size: 1, concurrent_requests: 2 }) - assert.deepEqual(applyRequestSettings({ id: 'ocl-search' }, EARLY_ACCESS), { id: 'ocl-search', batch_size: 10, concurrent_requests: 1 }) + assert.deepEqual(applyRequestSettings(algo, NARROW), { ...algo, batch_size: 1, concurrent_requests: 5 }) + assert.deepEqual(applyRequestSettings({ id: 'ocl-search' }, NARROW), { id: 'ocl-search', batch_size: 10, concurrent_requests: 1 }) // the saved project is not changed assert.equal(algo.concurrent_requests, 25) }) -test('applyRequestSettings: core users and staff get the algorithm back untouched', () => { +test('applyRequestSettings: uncapped, the algorithm comes back untouched', () => { const algo = { id: 'ocl-semantic', batch_size: 2000, concurrent_requests: 75 } - assert.equal(applyRequestSettings(algo, FULL), algo) + assert.equal(applyRequestSettings(algo, OPEN), algo) + assert.equal(applyRequestSettings(algo), algo) }) diff --git a/src/components/map-projects/aiVisibility.js b/src/components/map-projects/aiVisibility.js index 8d082ce..de19dab 100644 --- a/src/components/map-projects/aiVisibility.js +++ b/src/components/map-projects/aiVisibility.js @@ -1,20 +1,20 @@ // An AI recommendation's internals are staff-only (ocl_online#254): the model, -// the prompt template and its URI, and the raw JSON that carries them. Core, -// early-access and preview users see the assessment and the candidates only. +// the prompt template and its URI, and the raw JSON that carries them. +// Everyone else sees the assessment and the candidates only. export const canSeeAIInternals = user => Boolean(user?.is_staff || user?.is_superuser) -const inAuthGroup = (user, group) => Boolean(user?.auth_groups?.some(name => name.includes(group))) - // What a user can choose for the OCL AI Assistant (ocl_online#259): // - the model and the prompt template: staff only, like seeing them. The AI // Assistant honours a model override from staff only, so everyone else runs // the project's template on that template's default model. -// - the output language: staff, core, early-access and unlimited-AI users. -export const getAIAssistantChoices = (user, { unlimitedAICalls = false } = {}) => ({ +// - the output language: staff, and users an admin has granted it to +// (OpenConceptLab/ocl_online#517). +export const AI_OUTPUT_LOCALE_PERMISSION = 'users.mapper_ai_output_locale' + +export const getAIAssistantChoices = user => ({ canSelectAIModel: canSeeAIInternals(user), canSetAIOutputLocale: Boolean( - canSeeAIInternals(user) || unlimitedAICalls || - inAuthGroup(user, 'core_user') || inAuthGroup(user, 'early_access') + canSeeAIInternals(user) || user?.permissions?.includes(AI_OUTPUT_LOCALE_PERMISSION) ), }) diff --git a/src/components/map-projects/autoMatchCheck.js b/src/components/map-projects/autoMatchCheck.js index 2fbc043..cd5dee8 100644 --- a/src/components/map-projects/autoMatchCheck.js +++ b/src/components/map-projects/autoMatchCheck.js @@ -1,6 +1,6 @@ -// Checkpoints for AutoMatch (OpenConceptLab/ocl_online#465). The app imports -// them as '@openconceptlab/oclmap-extensions'; webpack resolves that name to an installed -// package when there is one, and to this file otherwise. +// Checkpoints for the Mapper (OpenConceptLab/ocl_online#465, #517). The app +// imports them as '@openconceptlab/oclmap-extensions'; webpack resolves that +// name to an installed package when there is one, and to this file otherwise. // // useAutoMatchCheck receives what the user has selected in the dialog: // {open, autoMatchScope, rowsToMatchCount, totalRows, retrieveCandidates, @@ -20,4 +20,10 @@ export const aiOnByDefault = () => true // eslint-disable-next-line no-unused-vars export const getMaxRunRows = run => null +// The most rows per matching request, and requests in flight per algorithm, +// that the user can set in the form: {batchSize, concurrentRequests}. A saved +// project runs within them too. Here, the form's full range. +// eslint-disable-next-line no-unused-vars +export const getMapperSettings = user => ({batchSize: 1000, concurrentRequests: 50}) + export default useAutoMatchCheck diff --git a/src/components/map-projects/requestLimits.js b/src/components/map-projects/requestLimits.js index c3ede85..ab59f84 100644 --- a/src/components/map-projects/requestLimits.js +++ b/src/components/map-projects/requestLimits.js @@ -1,47 +1,43 @@ /** * How many rows go in one matching request, and how many requests an - * algorithm keeps in flight, during Auto Match (ocl_online#274). + * algorithm keeps in flight, during Auto Match (ocl_online#274, #517). * - * $match is throttled per minute, not per request in flight, so a run at - * batch 1 × 25 concurrent requests puts 25 semantic searches on the search - * cluster at once. Everyone but core users and staff gets at most 10 rows per - * request, in the form and at run time, where the cap also holds a project's - * earlier saved settings: early-access users up to 5 requests in flight, - * preview users 2 (ocl_issues#2849), since many previews at once could fill - * the server's matching capacity. Core users and staff are unchanged: the - * form's range, and whatever the project saved at run time. + * The most a user can set in the form comes from the getMapperSettings + * checkpoint ('@openconceptlab/oclmap-extensions'); the open build allows the + * form's full range. Below that range, a project's earlier saved settings run + * within it too. Following the server's suggested concurrency is + * OpenConceptLab/ocl_online#340. */ export const DEFAULT_BATCH_SIZE = 10 export const DEFAULT_CONCURRENT_REQUESTS = 1 -const FULL_LIMITS = { batchSize: 1000, concurrentRequests: 50, capped: false } -const EARLY_ACCESS_LIMITS = { batchSize: 10, concurrentRequests: 5, capped: true } -const PREVIEW_LIMITS = { batchSize: 10, concurrentRequests: 2, capped: true } - -const inAuthGroup = (user, group) => Boolean(user?.auth_groups?.some(name => name.includes(group))) - -export const hasFullRequestLimits = user => Boolean(user?.is_staff || user?.is_superuser || inAuthGroup(user, 'core_user')) - -// The most rows per request, and requests in flight, a user may type in the -// form, by their highest tier. capped: false for core users and staff, whose -// saved settings run as they are. -export const getRequestLimits = user => { - if(hasFullRequestLimits(user)) - return FULL_LIMITS - if(inAuthGroup(user, 'early_access')) - return EARLY_ACCESS_LIMITS - return PREVIEW_LIMITS -} +const FORM_RANGE = { batchSize: 1000, concurrentRequests: 50 } const toCount = value => { const n = Number.parseInt(value, 10) return n >= 1 ? n : null } +// The form's maximums from the checkpoint's {batchSize, concurrentRequests}, +// within the form's range. capped: true when either is below the range, so the +// form shows its maximum and saved settings run within it; false runs saved +// settings as they are. +export const getRequestLimits = settings => { + const batchSize = Math.min(toCount(settings?.batchSize) || FORM_RANGE.batchSize, FORM_RANGE.batchSize) + const concurrentRequests = Math.min(toCount(settings?.concurrentRequests) || FORM_RANGE.concurrentRequests, FORM_RANGE.concurrentRequests) + return { + batchSize, + concurrentRequests, + capped: batchSize < FORM_RANGE.batchSize || concurrentRequests < FORM_RANGE.concurrentRequests, + } +} + +const OPEN_LIMITS = getRequestLimits() + // The batch size and concurrency an algorithm runs with: its saved values, or // the defaults, within the user's limits (getRequestLimits). -export const getRequestSettings = (algo, limits = PREVIEW_LIMITS) => { +export const getRequestSettings = (algo, limits = OPEN_LIMITS) => { const batchSize = toCount(algo?.batch_size) || DEFAULT_BATCH_SIZE const concurrentRequests = toCount(algo?.concurrent_requests) || DEFAULT_CONCURRENT_REQUESTS if(!limits.capped) @@ -53,8 +49,8 @@ export const getRequestSettings = (algo, limits = PREVIEW_LIMITS) => { } // A copy of a run's algorithm carrying the settings it runs with, so the -// AutomatchRun record matches what ran. Core users and staff get it back as is. -export const applyRequestSettings = (algo, limits = PREVIEW_LIMITS) => { +// AutomatchRun record matches what ran. Uncapped, it comes back as is. +export const applyRequestSettings = (algo, limits = OPEN_LIMITS) => { if(!limits.capped) return algo const { batchSize, concurrentRequests } = getRequestSettings(algo, limits)