From a0a1809b2bca7ad3f73186e47e98f089352e7491 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 18 Oct 2025 12:28:04 +0100 Subject: [PATCH 01/36] cookies not proxy --- CLAUDE-ai-provider-comparison.md | 119 +++++++++++++++++++++++++++---- 1 file changed, 106 insertions(+), 13 deletions(-) diff --git a/CLAUDE-ai-provider-comparison.md b/CLAUDE-ai-provider-comparison.md index 0b38f84..c21877c 100644 --- a/CLAUDE-ai-provider-comparison.md +++ b/CLAUDE-ai-provider-comparison.md @@ -15,8 +15,7 @@ This work will be done within the existing `expression-shepherd` repository, lev ### Access Details: - API endpoint: `/wdk-service/record-types/gene/searches/single_record_question_GeneRecordClasses_GeneRecordClass/reports/aiExpression` -- Proxy setup: Available for bmaccallum* sites (details TBD from user) -- Local development: Proxy endpoint is `http://localhost:8080/wdk-service/...` when using webpack local server +- Authentication: Cookie-based authentication using `auth_tkt` cookie (see Appendix for details) ## Gene Set - Format: VectorBase IDs (e.g., AGAP001234) @@ -62,11 +61,11 @@ expression-shepherd/ ├── (existing files: src/main.ts, package.json, etc.) ├── comparison/ │ ├── config/ -│ │ ├── sites.json (site configurations and endpoints) -│ │ └── proxy.json (proxy configuration for bmaccallum* sites) +│ │ └── sites.json (site configurations and endpoints) │ ├── input/ │ │ └── gene-list.txt (VectorBase gene IDs, one per line) │ ├── scripts/ +│ │ ├── shared-utils.ts (shared utilities including authentication) │ │ ├── fetch-summaries.ts (Phase 1: API calls and JSON storage) │ │ ├── compare-summaries.ts (Phase 2: AI-powered comparison) │ │ └── aggregate-analysis.ts (Phase 3: theme identification) @@ -123,10 +122,10 @@ expression-shepherd/ - Response includes progress information when not yet complete - See `useAiExpressionSummary` hook for reference implementation -### Proxy Configuration -- User will provide webpack dev server proxy config -- Needed for bmaccallum* sites (firewall/proxy hurdles) -- Will be adapted for fetch calls in TypeScript +### Authentication +- VEuPathDB dev sites use cookie-based authentication +- Requires `auth_tkt` cookie obtained from login endpoint +- Credentials stored in `.env` file (see Appendix for implementation details) ### AI Comparison Scripts - Must be deterministic (TypeScript scripts calling Anthropic API) @@ -138,11 +137,11 @@ expression-shepherd/ 1. **Initial Setup** - Create `comparison/` directory structure - - Set up config files (will need user input for proxy details) + - Set up config files for site configurations 2. **Phase 1 Implementation** - Create `comparison/scripts/fetch-summaries.ts` with API client - - Implement proxy-aware fetch for all three sites + - Implement cookie-based authentication for all three sites - Implement progress monitoring and polling logic - Save JSON responses to `comparison/data/summaries/{model}/{geneId}.json` - Add retry logic for failed requests @@ -161,10 +160,9 @@ expression-shepherd/ User needs to provide: - Gene list (`comparison/input/gene-list.txt`) -- Proxy configuration details for bmaccallum* sites -- Confirm VectorBase project ID (e.g., "VectorBase") for gene primary keys Already available in repository: +- VEuPathDB authentication credentials (in `.env` as `VEUPATHDB_LOGIN_USER` and `VEUPATHDB_LOGIN_PASS`) - Anthropic API key (in `.env` as `ANTHROPIC_API_KEY`) - TypeScript build setup - Node.js environment @@ -175,4 +173,99 @@ Already available in repository: - Complete set of JSON summaries saved locally (3 models × 20 genes = 60 files) - Pairwise comparisons generated for each gene (3 comparisons × 20 genes = 60 files) - Final aggregated report identifying systematic differences between models -- Reproducible process that can be re-run with new gene sets \ No newline at end of file +- Reproducible process that can be re-run with new gene sets + + +# Appendices + + + +## VEuPathDB Dev Site Authentication + +The VEuPathDB dev sites use cookie-based authentication. You need to obtain an `auth_tkt` cookie and include it with all requests. + +### Prep. + +Username and password have been added to `.env`: + +``` +VEUPATHDB_LOGIN_USER=apidb +VEUPATHDB_LOGIN_PASS=XXXXXXXXXXXXX +``` + +### Step 1: Obtain the auth_tkt Cookie + +Make a POST request to the VEuPathDB login endpoint: + +```typescript +import https from 'https'; +import querystring from 'querystring'; + +async function getAuthCookie(username: string, password: string): Promise { + return new Promise((resolve, reject) => { + const postData = querystring.stringify({ username, password }); + + const req = https.request( + 'https://veupathdb.org/auth/bin/login', + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': Buffer.byteLength(postData), + 'Cookie': 'auth_probe=1', // Required! + }, + }, + (res) => { + // Extract auth_tkt cookie from Set-Cookie headers + const setCookieHeader = res.headers['set-cookie']; + if (setCookieHeader) { + const authCookie = setCookieHeader.find(cookie => + cookie.startsWith('auth_tkt=') + ); + if (authCookie) { + // Extract just the cookie value (between = and ;) + const match = authCookie.match(/auth_tkt=([^;]+)/); + if (match) { + resolve(match[1]); + return; + } + } + } + reject(new Error('Could not get auth_tkt cookie')); + } + ); + + req.on('error', reject); + req.write(postData); + req.end(); + }); +} +``` + +### Step 2: Include the Cookie in All Requests + +Once you have the `auth_tkt` value, include it as a cookie in all requests to the dev site: + +```typescript +const authTkt = await getAuthCookie(username, password); + +// For fetch API: +fetch('https://vectorbase.org/your-endpoint', { + headers: { + 'Cookie': `auth_tkt=${authTkt}` + } +}); + +// For axios: +axios.get('https://vectorbase.org/your-endpoint', { + headers: { + 'Cookie': `auth_tkt=${authTkt}` + } +}); +``` + +### Key Points: +- The login endpoint requires `Cookie: auth_probe=1` in the request headers +- The credentials should be the VEuPathDB BRC Pre-Release username/password +- The `auth_tkt` cookie must be included in **all** subsequent requests to the dev sites +- The cookie is valid for a session - you may need to re-authenticate if it expires (expiry is days not hours, so no worries here) From d4b51beb5970a8337de57dc33d6bca6b2e433bc8 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 18 Oct 2025 12:32:30 +0100 Subject: [PATCH 02/36] getting started --- comparison/config/sites.json | 24 +++ comparison/scripts/fetch-summaries.ts | 273 ++++++++++++++++++++++++++ comparison/scripts/shared-utils.ts | 78 ++++++++ package.json | 5 +- 4 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 comparison/config/sites.json create mode 100644 comparison/scripts/fetch-summaries.ts create mode 100644 comparison/scripts/shared-utils.ts diff --git a/comparison/config/sites.json b/comparison/config/sites.json new file mode 100644 index 0000000..8facc8b --- /dev/null +++ b/comparison/config/sites.json @@ -0,0 +1,24 @@ +{ + "sites": [ + { + "name": "claude", + "hostname": "bmaccallum.vectorbase.org", + "model": "Claude", + "useProxy": true + }, + { + "name": "gpt5", + "hostname": "bmaccallum-b.vectorbase.org", + "model": "GPT-5", + "useProxy": true + }, + { + "name": "gpt4o", + "hostname": "qa.vectorbase.org", + "model": "GPT-4o", + "useProxy": false + } + ], + "endpoint": "/wdk-service/record-types/gene/searches/single_record_question_GeneRecordClasses_GeneRecordClass/reports/aiExpression", + "projectId": "VectorBase" +} diff --git a/comparison/scripts/fetch-summaries.ts b/comparison/scripts/fetch-summaries.ts new file mode 100644 index 0000000..7c5b6af --- /dev/null +++ b/comparison/scripts/fetch-summaries.ts @@ -0,0 +1,273 @@ +import "dotenv/config"; +import axios from "axios"; +import { readFile } from "fs/promises"; +import path from "path"; +import { writeToFile, getAuthCookie, sleep } from "./shared-utils"; + +interface SiteConfig { + name: string; + hostname: string; + model: string; + useProxy: boolean; +} + +interface Config { + sites: SiteConfig[]; + endpoint: string; + projectId: string; +} + +interface FetchResult { + geneId: string; + site: string; + status: "success" | "failed"; + error?: string; +} + +const POLL_INTERVAL_MS = 5000; // 5 seconds +const MAX_RETRIES = 3; +const MAX_POLL_ATTEMPTS = 120; // 10 minutes max (120 * 5 seconds) + +/** + * Load site configuration + */ +async function loadConfig(): Promise { + const configPath = path.join(__dirname, "../config/sites.json"); + const configContent = await readFile(configPath, "utf-8"); + return JSON.parse(configContent); +} + +/** + * Load gene list from input file + * Filters out comments and empty lines + */ +async function loadGeneList(): Promise { + const geneListPath = path.join(__dirname, "../input/gene-list.txt"); + const content = await readFile(geneListPath, "utf-8"); + return content + .split("\n") + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith("#")); +} + +/** + * Make API request to fetch AI expression summary + */ +async function fetchSummary( + geneId: string, + site: SiteConfig, + config: Config, + authCookie: string, + populateIfNotPresent: boolean +): Promise { + const url = `https://${site.hostname}${config.endpoint}`; + + const requestBody = { + reportConfig: { + populateIfNotPresent, + }, + searchConfig: { + parameters: { + primaryKeys: `${geneId},${config.projectId}`, + }, + }, + }; + + const response = await axios.post(url, requestBody, { + headers: { + "Content-Type": "application/json", + Cookie: `auth_tkt=${authCookie}`, + }, + }); + + return response.data; +} + +/** + * Fetch summary for a gene from a specific site with polling + */ +async function fetchSummaryWithPolling( + geneId: string, + site: SiteConfig, + config: Config, + authCookie: string +): Promise { + console.log(`[${site.name}] ${geneId}: Triggering summary generation...`); + + // Initial request to trigger generation + let response = await fetchSummary(geneId, site, config, authCookie, true); + + // Check if already present + if (response[geneId]?.resultStatus === "present") { + console.log(`[${site.name}] ${geneId}: Summary already present`); + return response[geneId].expressionSummary; + } + + // Poll until present or max attempts reached + let attempts = 0; + while (attempts < MAX_POLL_ATTEMPTS) { + console.log( + `[${site.name}] ${geneId}: Polling (attempt ${attempts + 1}/${MAX_POLL_ATTEMPTS})...` + ); + + await sleep(POLL_INTERVAL_MS); + + response = await fetchSummary(geneId, site, config, authCookie, false); + + if (response[geneId]?.resultStatus === "present") { + console.log(`[${site.name}] ${geneId}: Summary ready!`); + return response[geneId].expressionSummary; + } + + if (response[geneId]?.resultStatus === "error") { + throw new Error(`Generation failed: ${JSON.stringify(response[geneId])}`); + } + + attempts++; + } + + throw new Error(`Polling timeout after ${MAX_POLL_ATTEMPTS} attempts`); +} + +/** + * Fetch summary with retry logic + */ +async function fetchWithRetry( + geneId: string, + site: SiteConfig, + config: Config, + authCookie: string +): Promise { + let lastError: any; + + for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + const summary = await fetchSummaryWithPolling(geneId, site, config, authCookie); + + // Save to file + const outputPath = path.join( + __dirname, + `../data/summaries/${site.name}/${geneId}.json` + ); + await writeToFile(outputPath, JSON.stringify(summary, null, 2)); + + return { + geneId, + site: site.name, + status: "success", + }; + } catch (error) { + lastError = error; + console.error( + `[${site.name}] ${geneId}: Attempt ${attempt}/${MAX_RETRIES} failed:`, + error instanceof Error ? error.message : error + ); + + if (attempt < MAX_RETRIES) { + const backoffMs = attempt * 2000; // Exponential backoff + console.log(`[${site.name}] ${geneId}: Retrying in ${backoffMs}ms...`); + await sleep(backoffMs); + } + } + } + + return { + geneId, + site: site.name, + status: "failed", + error: lastError instanceof Error ? lastError.message : String(lastError), + }; +} + +/** + * Main execution + */ +async function main() { + console.log("Loading configuration..."); + const config = await loadConfig(); + + console.log("Loading gene list..."); + const geneIds = await loadGeneList(); + console.log(`Found ${geneIds.length} genes to process`); + + if (geneIds.length === 0) { + console.error("No genes found in gene-list.txt. Please add gene IDs (one per line)."); + process.exit(1); + } + + console.log("Authenticating..."); + const username = process.env.VEUPATHDB_LOGIN_USER; + const password = process.env.VEUPATHDB_LOGIN_PASS; + + if (!username || !password) { + console.error("Missing VEUPATHDB_LOGIN_USER or VEUPATHDB_LOGIN_PASS in .env file"); + process.exit(1); + } + + const authCookie = await getAuthCookie(username, password); + console.log("Authentication successful!"); + + const results: FetchResult[] = []; + + // Process each gene across all sites + for (const geneId of geneIds) { + console.log(`\n${"=".repeat(60)}`); + console.log(`Processing gene: ${geneId}`); + console.log("=".repeat(60)); + + // Process all sites for this gene in parallel + const siteResults = await Promise.all( + config.sites.map((site) => fetchWithRetry(geneId, site, config, authCookie)) + ); + + results.push(...siteResults); + } + + // Summary report + console.log("\n" + "=".repeat(60)); + console.log("SUMMARY"); + console.log("=".repeat(60)); + + const successful = results.filter((r) => r.status === "success"); + const failed = results.filter((r) => r.status === "failed"); + + console.log(`Total attempts: ${results.length}`); + console.log(`Successful: ${successful.length}`); + console.log(`Failed: ${failed.length}`); + + if (failed.length > 0) { + console.log("\nFailed fetches:"); + failed.forEach((r) => { + console.log(` - ${r.geneId} (${r.site}): ${r.error}`); + }); + } + + // Save summary report + const reportPath = path.join(__dirname, "../data/fetch-summary.json"); + await writeToFile( + reportPath, + JSON.stringify( + { + timestamp: new Date().toISOString(), + total: results.length, + successful: successful.length, + failed: failed.length, + results, + }, + null, + 2 + ) + ); + + console.log(`\nSummary report saved to ${reportPath}`); + + if (failed.length > 0) { + process.exit(1); + } +} + +// Run the script +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); +}); diff --git a/comparison/scripts/shared-utils.ts b/comparison/scripts/shared-utils.ts new file mode 100644 index 0000000..a34a0ab --- /dev/null +++ b/comparison/scripts/shared-utils.ts @@ -0,0 +1,78 @@ +import { writeFile } from "fs/promises"; +import https from 'https'; +import querystring from 'querystring'; + +/** + * Writes content to a file + */ +export async function writeToFile(filename: string, content: string): Promise { + try { + await writeFile(filename, content, "utf-8"); + console.log(`File written successfully to ${filename}`); + } catch (error) { + console.error("Error writing to file:", error); + } +} + +/** + * Strips markdown code block formatting from JSON responses + * Handles both ```json and ``` formats + */ +export function stripMarkdownCodeBlocks(response: string): string { + let stripped = response; + if (stripped.startsWith('```json')) { + stripped = stripped.replace(/^```json\s*/, '').replace(/\s*```$/, ''); + } else if (stripped.startsWith('```')) { + stripped = stripped.replace(/^```\s*/, '').replace(/\s*```$/, ''); + } + return stripped; +} + +/** + * Obtains auth_tkt cookie from VEuPathDB login endpoint + * Required for accessing dev sites (bmaccallum.vectorbase.org, etc.) + */ +export async function getAuthCookie(username: string, password: string): Promise { + return new Promise((resolve, reject) => { + const postData = querystring.stringify({ username, password }); + + const req = https.request( + 'https://veupathdb.org/auth/bin/login', + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': Buffer.byteLength(postData), + 'Cookie': 'auth_probe=1', // Required! + }, + }, + (res) => { + // Extract auth_tkt cookie from Set-Cookie headers + const setCookieHeader = res.headers['set-cookie']; + if (setCookieHeader) { + const authCookie = setCookieHeader.find(cookie => + cookie.startsWith('auth_tkt=') + ); + if (authCookie) { + // Extract just the cookie value (between = and ;) + const match = authCookie.match(/auth_tkt=([^;]+)/); + if (match) { + resolve(match[1]); + return; + } + } + } + reject(new Error('Could not get auth_tkt cookie')); + } + ); + + req.on('error', reject); + req.write(postData); + req.end(); + }); +} + +/** + * Sleep utility for rate limiting or polling delays + */ +export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/package.json b/package.json index 7553551..a599362 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,9 @@ "scripts": { "build": "tsc", "start": "node dist/main.js", - "node-check": "node -v" + "node-check": "node -v", + "comparison:fetch": "yarn build && node dist/comparison/scripts/fetch-summaries.js", + "comparison:compare": "yarn build && node dist/comparison/scripts/compare-summaries.js", + "comparison:aggregate": "yarn build && node dist/comparison/scripts/aggregate-analysis.js" } } From 95b694aeab25257ce9141b214ea5d9efa3d9a9cb Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 18 Oct 2025 12:37:47 +0100 Subject: [PATCH 03/36] added some genes --- comparison/input/gene-list.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 comparison/input/gene-list.txt diff --git a/comparison/input/gene-list.txt b/comparison/input/gene-list.txt new file mode 100644 index 0000000..19074f5 --- /dev/null +++ b/comparison/input/gene-list.txt @@ -0,0 +1,4 @@ +AGAP001212 +AGAP000693 +AGAP000999 +AGAP009551 From 0119dbaf4e4bd6bd511934935b0abd40bf343669 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 18 Oct 2025 13:15:23 +0100 Subject: [PATCH 04/36] fetching working nicely, thanks Claude --- CLAUDE-ai-provider-comparison.md | 17 +++++++++++++++++ comparison/config/sites.json | 5 ++++- comparison/input/gene-list.txt | 1 + comparison/scripts/fetch-summaries.ts | 16 ++++++++++------ 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/CLAUDE-ai-provider-comparison.md b/CLAUDE-ai-provider-comparison.md index c21877c..46e2e56 100644 --- a/CLAUDE-ai-provider-comparison.md +++ b/CLAUDE-ai-provider-comparison.md @@ -33,6 +33,14 @@ This work will be done within the existing `expression-shepherd` repository, lev 3. Save JSON responses locally, organized by gene ID and model 4. Track progress and any errors +**Runtime Expectations:** +- Polling timeout: 10 minutes per gene per site (MAX_POLL_ATTEMPTS = 120 × 5 seconds) +- For 20 genes × 3 sites: expect 60+ minutes total runtime due to: + - AI generation time (varies by model and gene complexity) + - Backend rate limiting (especially for Anthropic/Claude) + - Sequential processing per gene, parallel across sites +- **Important for Claude Code**: When using Bash tool to run `yarn comparison:fetch`, set timeout to at least 3600000ms (1 hour) for full gene lists + ### Phase 2: AI-Powered Comparison 1. Create TypeScript scripts that use Anthropic API to compare summaries 2. Perform pairwise comparisons for each gene: @@ -156,6 +164,15 @@ expression-shepherd/ - Collect and structure all comparison data - Generate final thematic summary +## Running the Scripts + +NPM scripts are available in `package.json`: +- `yarn comparison:fetch` - Phase 1: Fetch summaries from all three sites +- `yarn comparison:compare` - Phase 2: Generate pairwise comparisons +- `yarn comparison:aggregate` - Phase 3: Generate aggregate analysis report + +All scripts automatically run `yarn build` before execution. + ## Next Steps User needs to provide: diff --git a/comparison/config/sites.json b/comparison/config/sites.json index 8facc8b..24d19b7 100644 --- a/comparison/config/sites.json +++ b/comparison/config/sites.json @@ -3,22 +3,25 @@ { "name": "claude", "hostname": "bmaccallum.vectorbase.org", + "appPath": "vectorbase.bmaccallum", "model": "Claude", "useProxy": true }, { "name": "gpt5", "hostname": "bmaccallum-b.vectorbase.org", + "appPath": "vectorbase.bmaccallum-b", "model": "GPT-5", "useProxy": true }, { "name": "gpt4o", "hostname": "qa.vectorbase.org", + "appPath": "vectorbase.b69", "model": "GPT-4o", "useProxy": false } ], - "endpoint": "/wdk-service/record-types/gene/searches/single_record_question_GeneRecordClasses_GeneRecordClass/reports/aiExpression", + "endpoint": "/service/record-types/gene/searches/single_record_question_GeneRecordClasses_GeneRecordClass/reports/aiExpression", "projectId": "VectorBase" } diff --git a/comparison/input/gene-list.txt b/comparison/input/gene-list.txt index 19074f5..74e1ddc 100644 --- a/comparison/input/gene-list.txt +++ b/comparison/input/gene-list.txt @@ -2,3 +2,4 @@ AGAP001212 AGAP000693 AGAP000999 AGAP009551 +AGAP006268 diff --git a/comparison/scripts/fetch-summaries.ts b/comparison/scripts/fetch-summaries.ts index 7c5b6af..fb8c8e7 100644 --- a/comparison/scripts/fetch-summaries.ts +++ b/comparison/scripts/fetch-summaries.ts @@ -7,6 +7,7 @@ import { writeToFile, getAuthCookie, sleep } from "./shared-utils"; interface SiteConfig { name: string; hostname: string; + appPath: string; model: string; useProxy: boolean; } @@ -32,7 +33,8 @@ const MAX_POLL_ATTEMPTS = 120; // 10 minutes max (120 * 5 seconds) * Load site configuration */ async function loadConfig(): Promise { - const configPath = path.join(__dirname, "../config/sites.json"); + // Use paths relative to project root, not dist directory + const configPath = path.join(process.cwd(), "comparison/config/sites.json"); const configContent = await readFile(configPath, "utf-8"); return JSON.parse(configContent); } @@ -42,7 +44,8 @@ async function loadConfig(): Promise { * Filters out comments and empty lines */ async function loadGeneList(): Promise { - const geneListPath = path.join(__dirname, "../input/gene-list.txt"); + // Use paths relative to project root, not dist directory + const geneListPath = path.join(process.cwd(), "comparison/input/gene-list.txt"); const content = await readFile(geneListPath, "utf-8"); return content .split("\n") @@ -60,7 +63,7 @@ async function fetchSummary( authCookie: string, populateIfNotPresent: boolean ): Promise { - const url = `https://${site.hostname}${config.endpoint}`; + const url = `https://${site.hostname}/${site.appPath}${config.endpoint}`; const requestBody = { reportConfig: { @@ -146,8 +149,8 @@ async function fetchWithRetry( // Save to file const outputPath = path.join( - __dirname, - `../data/summaries/${site.name}/${geneId}.json` + process.cwd(), + `comparison/data/summaries/${site.name}/${geneId}.json` ); await writeToFile(outputPath, JSON.stringify(summary, null, 2)); @@ -206,6 +209,7 @@ async function main() { const authCookie = await getAuthCookie(username, password); console.log("Authentication successful!"); + console.log(`Auth cookie (for curl debugging): auth_tkt=${authCookie}`); const results: FetchResult[] = []; @@ -243,7 +247,7 @@ async function main() { } // Save summary report - const reportPath = path.join(__dirname, "../data/fetch-summary.json"); + const reportPath = path.join(process.cwd(), "comparison/data/fetch-summary.json"); await writeToFile( reportPath, JSON.stringify( From db28d0f5d1e6edb7f0a2009000ee6b4d6e4a8744 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Sat, 18 Oct 2025 22:23:41 +0100 Subject: [PATCH 05/36] add skipFetch option to sites.json --- comparison/config/sites.json | 4 +- comparison/scripts/fetch-summaries.ts | 24 +- yarn.lock | 2656 +++++++++++-------------- 3 files changed, 1190 insertions(+), 1494 deletions(-) diff --git a/comparison/config/sites.json b/comparison/config/sites.json index 24d19b7..d6c713d 100644 --- a/comparison/config/sites.json +++ b/comparison/config/sites.json @@ -1,10 +1,10 @@ { "sites": [ { - "name": "claude", + "name": "claude4", "hostname": "bmaccallum.vectorbase.org", "appPath": "vectorbase.bmaccallum", - "model": "Claude", + "model": "Claude Sonnet 4", "useProxy": true }, { diff --git a/comparison/scripts/fetch-summaries.ts b/comparison/scripts/fetch-summaries.ts index fb8c8e7..23ac07e 100644 --- a/comparison/scripts/fetch-summaries.ts +++ b/comparison/scripts/fetch-summaries.ts @@ -10,6 +10,7 @@ interface SiteConfig { appPath: string; model: string; useProxy: boolean; + skipFetch?: boolean; } interface Config { @@ -189,7 +190,24 @@ async function main() { console.log("Loading configuration..."); const config = await loadConfig(); - console.log("Loading gene list..."); + // Filter out sites with skipFetch: true + const activeSites = config.sites.filter((site) => !site.skipFetch); + const skippedSites = config.sites.filter((site) => site.skipFetch); + + if (skippedSites.length > 0) { + console.log(`Skipping ${skippedSites.length} site(s) with skipFetch=true:`); + skippedSites.forEach((site) => console.log(` - ${site.name}`)); + } + + if (activeSites.length === 0) { + console.error("No active sites to process (all sites have skipFetch=true)"); + process.exit(1); + } + + console.log(`Processing ${activeSites.length} active site(s):`); + activeSites.forEach((site) => console.log(` - ${site.name} (${site.model})`)); + + console.log("\nLoading gene list..."); const geneIds = await loadGeneList(); console.log(`Found ${geneIds.length} genes to process`); @@ -219,9 +237,9 @@ async function main() { console.log(`Processing gene: ${geneId}`); console.log("=".repeat(60)); - // Process all sites for this gene in parallel + // Process all active sites for this gene in parallel const siteResults = await Promise.all( - config.sites.map((site) => fetchWithRetry(geneId, site, config, authCookie)) + activeSites.map((site) => fetchWithRetry(geneId, site, config, authCookie)) ); results.push(...siteResults); diff --git a/yarn.lock b/yarn.lock index 50d5d01..c57f7b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,1489 +1,1167 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 8 - cacheKey: 10c0 - -"@anthropic-ai/sdk@npm:^0.56.0": - version: 0.56.0 - resolution: "@anthropic-ai/sdk@npm:0.56.0" - bin: - anthropic-ai-sdk: bin/cli - checksum: 10c0/b8506daa740b3700c56cf7e7cd16c5f3c092b96ad0bca893530d3e12ed543bdae174ea5e34b270ba86958f193a8ac31559f17ed4a79ba9219771d3c457e15c06 - languageName: node - linkType: hard - -"@cspotcode/source-map-support@npm:^0.8.0": - version: 0.8.1 - resolution: "@cspotcode/source-map-support@npm:0.8.1" - dependencies: - "@jridgewell/trace-mapping": "npm:0.3.9" - checksum: 10c0/05c5368c13b662ee4c122c7bfbe5dc0b613416672a829f3e78bc49a357a197e0218d6e74e7c66cfcd04e15a179acab080bd3c69658c9fbefd0e1ccd950a07fc6 - languageName: node - linkType: hard - -"@fast-csv/format@npm:4.3.5": - version: 4.3.5 - resolution: "@fast-csv/format@npm:4.3.5" - dependencies: - "@types/node": "npm:^14.0.1" - lodash.escaperegexp: "npm:^4.1.2" - lodash.isboolean: "npm:^3.0.3" - lodash.isequal: "npm:^4.5.0" - lodash.isfunction: "npm:^3.0.9" - lodash.isnil: "npm:^4.0.0" - checksum: 10c0/06c6b3310edaf08033236539b93ebed027ea36f9f8a3cf42069034d4f75dff103a35930c9a9f01e2e344d8836fb2cc55a16affb5c345a8b3682d5a0cb031e0ea - languageName: node - linkType: hard - -"@fast-csv/parse@npm:4.3.6": - version: 4.3.6 - resolution: "@fast-csv/parse@npm:4.3.6" - dependencies: - "@types/node": "npm:^14.0.1" - lodash.escaperegexp: "npm:^4.1.2" - lodash.groupby: "npm:^4.6.0" - lodash.isfunction: "npm:^3.0.9" - lodash.isnil: "npm:^4.0.0" - lodash.isundefined: "npm:^3.0.1" - lodash.uniq: "npm:^4.5.0" - checksum: 10c0/dfd1834bfcea2665bd9db05b21793f79fd3502abdf955d6d63f7bf9724082f0b67a6379687b2945ca8d513b4d383a7bdaeed72a03cabd9191032b81448379917 - languageName: node - linkType: hard - -"@jridgewell/resolve-uri@npm:^3.0.3": - version: 3.1.2 - resolution: "@jridgewell/resolve-uri@npm:3.1.2" - checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:^1.4.10": - version: 1.5.0 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" - checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 - languageName: node - linkType: hard - -"@jridgewell/trace-mapping@npm:0.3.9": - version: 0.3.9 - resolution: "@jridgewell/trace-mapping@npm:0.3.9" - dependencies: - "@jridgewell/resolve-uri": "npm:^3.0.3" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" - checksum: 10c0/fa425b606d7c7ee5bfa6a31a7b050dd5814b4082f318e0e4190f991902181b4330f43f4805db1dd4f2433fd0ed9cc7a7b9c2683f1deeab1df1b0a98b1e24055b - languageName: node - linkType: hard - -"@tsconfig/node10@npm:^1.0.7": - version: 1.0.11 - resolution: "@tsconfig/node10@npm:1.0.11" - checksum: 10c0/28a0710e5d039e0de484bdf85fee883bfd3f6a8980601f4d44066b0a6bcd821d31c4e231d1117731c4e24268bd4cf2a788a6787c12fc7f8d11014c07d582783c - languageName: node - linkType: hard - -"@tsconfig/node12@npm:^1.0.7": - version: 1.0.11 - resolution: "@tsconfig/node12@npm:1.0.11" - checksum: 10c0/dddca2b553e2bee1308a056705103fc8304e42bb2d2cbd797b84403a223b25c78f2c683ec3e24a095e82cd435387c877239bffcb15a590ba817cd3f6b9a99fd9 - languageName: node - linkType: hard - -"@tsconfig/node14@npm:^1.0.0": - version: 1.0.3 - resolution: "@tsconfig/node14@npm:1.0.3" - checksum: 10c0/67c1316d065fdaa32525bc9449ff82c197c4c19092b9663b23213c8cbbf8d88b6ed6a17898e0cbc2711950fbfaf40388938c1c748a2ee89f7234fc9e7fe2bf44 - languageName: node - linkType: hard - -"@tsconfig/node16@npm:^1.0.2": - version: 1.0.4 - resolution: "@tsconfig/node16@npm:1.0.4" - checksum: 10c0/05f8f2734e266fb1839eb1d57290df1664fe2aa3b0fdd685a9035806daa635f7519bf6d5d9b33f6e69dd545b8c46bd6e2b5c79acb2b1f146e885f7f11a42a5bb - languageName: node - linkType: hard - -"@types/git-rev-sync@npm:^2.0.2": - version: 2.0.2 - resolution: "@types/git-rev-sync@npm:2.0.2" - checksum: 10c0/bfbc046554135c7eaa9f3f973974bec8f877b88c00c5a8f6bd00a2df4ef629432593d33853cda6f67b68a36c70217d08c059c4457baba9329e63f1b0c807317f - languageName: node - linkType: hard - -"@types/lodash@npm:^4.17.14": - version: 4.17.14 - resolution: "@types/lodash@npm:4.17.14" - checksum: 10c0/343c6f722e0b39969036a885ad5aad6578479ead83987128c9b994e6b7f2fb9808244d802d4d332396bb09096f720a6c7060de16a492f5460e06a44560360322 - languageName: node - linkType: hard - -"@types/node-fetch@npm:^2.6.4": - version: 2.6.12 - resolution: "@types/node-fetch@npm:2.6.12" - dependencies: - "@types/node": "npm:*" - form-data: "npm:^4.0.0" - checksum: 10c0/7693acad5499b7df2d1727d46cff092a63896dc04645f36b973dd6dd754a59a7faba76fcb777bdaa35d80625c6a9dd7257cca9c401a4bab03b04480cda7fd1af - languageName: node - linkType: hard - -"@types/node@npm:*, @types/node@npm:^22.10.6": - version: 22.10.6 - resolution: "@types/node@npm:22.10.6" - dependencies: - undici-types: "npm:~6.20.0" - checksum: 10c0/8b67affc211e5f9c74f7949cda04ca669721e50b83d71b8772a7bed7a4a3c41d663b3a794413f618e763ca6c5da8c234c25ffebcb0737983fc3e7415818ab9a7 - languageName: node - linkType: hard - -"@types/node@npm:^14.0.1": - version: 14.18.63 - resolution: "@types/node@npm:14.18.63" - checksum: 10c0/626a371419a6a0e11ca460b22bb4894abe5d75c303739588bc96267e380aa8b90ba5a87bc552400584f0ac2a84b5c458dadcbcf0dfd2396ebeb765f7a7f95893 - languageName: node - linkType: hard - -"@types/node@npm:^18.11.18": - version: 18.19.70 - resolution: "@types/node@npm:18.19.70" - dependencies: - undici-types: "npm:~5.26.4" - checksum: 10c0/68866e53b92be60d8840f5c93232d3ae39c71663101decc1d4f1870d9236c3c89e74177b616c2a2cabce116b1356f3e89c57df3e969c9f9b0e0b5c59b5f790f7 - languageName: node - linkType: hard - -"@types/retry@npm:0.12.0": - version: 0.12.0 - resolution: "@types/retry@npm:0.12.0" - checksum: 10c0/7c5c9086369826f569b83a4683661557cab1361bac0897a1cefa1a915ff739acd10ca0d62b01071046fe3f5a3f7f2aec80785fe283b75602dc6726781ea3e328 - languageName: node - linkType: hard - -"@types/xml2js@npm:^0.4.14": - version: 0.4.14 - resolution: "@types/xml2js@npm:0.4.14" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/06776e7f7aec55a698795e60425417caa7d7db3ff680a7b4ccaae1567c5fec28ff49b9975e9a0d74ff4acb8f4a43730501bbe64f9f761d784c6476ba4db12e13 - languageName: node - linkType: hard - -"abort-controller@npm:^3.0.0": - version: 3.0.0 - resolution: "abort-controller@npm:3.0.0" - dependencies: - event-target-shim: "npm:^5.0.0" - checksum: 10c0/90ccc50f010250152509a344eb2e71977fbf8db0ab8f1061197e3275ddf6c61a41a6edfd7b9409c664513131dd96e962065415325ef23efa5db931b382d24ca5 - languageName: node - linkType: hard - -"acorn-walk@npm:^8.1.1": - version: 8.3.4 - resolution: "acorn-walk@npm:8.3.4" - dependencies: - acorn: "npm:^8.11.0" - checksum: 10c0/76537ac5fb2c37a64560feaf3342023dadc086c46da57da363e64c6148dc21b57d49ace26f949e225063acb6fb441eabffd89f7a3066de5ad37ab3e328927c62 - languageName: node - linkType: hard - -"acorn@npm:^8.11.0, acorn@npm:^8.4.1": - version: 8.14.1 - resolution: "acorn@npm:8.14.1" - bin: - acorn: bin/acorn - checksum: 10c0/dbd36c1ed1d2fa3550140000371fcf721578095b18777b85a79df231ca093b08edc6858d75d6e48c73e431c174dcf9214edbd7e6fa5911b93bd8abfa54e47123 - languageName: node - linkType: hard - -"agentkeepalive@npm:^4.2.1": - version: 4.6.0 - resolution: "agentkeepalive@npm:4.6.0" - dependencies: - humanize-ms: "npm:^1.2.1" - checksum: 10c0/235c182432f75046835b05f239708107138a40103deee23b6a08caee5136873709155753b394ec212e49e60e94a378189562cb01347765515cff61b692c69187 - languageName: node - linkType: hard - -"archiver-utils@npm:^2.1.0": - version: 2.1.0 - resolution: "archiver-utils@npm:2.1.0" - dependencies: - glob: "npm:^7.1.4" - graceful-fs: "npm:^4.2.0" - lazystream: "npm:^1.0.0" - lodash.defaults: "npm:^4.2.0" - lodash.difference: "npm:^4.5.0" - lodash.flatten: "npm:^4.4.0" - lodash.isplainobject: "npm:^4.0.6" - lodash.union: "npm:^4.6.0" - normalize-path: "npm:^3.0.0" - readable-stream: "npm:^2.0.0" - checksum: 10c0/6ea5b02e440f3099aff58b18dd384f84ecfe18632e81d26c1011fe7dfdb80ade43d7a06cbf048ef0e9ee0f2c87a80cb24c0f0ac5e3a2c4d67641d6f0d6e36ece - languageName: node - linkType: hard - -"archiver-utils@npm:^3.0.4": - version: 3.0.4 - resolution: "archiver-utils@npm:3.0.4" - dependencies: - glob: "npm:^7.2.3" - graceful-fs: "npm:^4.2.0" - lazystream: "npm:^1.0.0" - lodash.defaults: "npm:^4.2.0" - lodash.difference: "npm:^4.5.0" - lodash.flatten: "npm:^4.4.0" - lodash.isplainobject: "npm:^4.0.6" - lodash.union: "npm:^4.6.0" - normalize-path: "npm:^3.0.0" - readable-stream: "npm:^3.6.0" - checksum: 10c0/9bb7e271e95ff33bdbdcd6f69f8860e0aeed3fcba352a74f51a626d1c32b404f20e3185d5214f171b24a692471d01702f43874d1a4f0d2e5f57bd0834bc54c14 - languageName: node - linkType: hard - -"archiver@npm:^5.0.0": - version: 5.3.2 - resolution: "archiver@npm:5.3.2" - dependencies: - archiver-utils: "npm:^2.1.0" - async: "npm:^3.2.4" - buffer-crc32: "npm:^0.2.1" - readable-stream: "npm:^3.6.0" - readdir-glob: "npm:^1.1.2" - tar-stream: "npm:^2.2.0" - zip-stream: "npm:^4.1.0" - checksum: 10c0/973384d749b3fa96f44ceda1603a65aaa3f24a267230d69a4df9d7b607d38d3ebc6c18c358af76eb06345b6b331ccb9eca07bd079430226b5afce95de22dfade - languageName: node - linkType: hard - -"arg@npm:^4.1.0": - version: 4.1.3 - resolution: "arg@npm:4.1.3" - checksum: 10c0/070ff801a9d236a6caa647507bdcc7034530604844d64408149a26b9e87c2f97650055c0f049abd1efc024b334635c01f29e0b632b371ac3f26130f4cf65997a - languageName: node - linkType: hard - -"async@npm:^3.2.4": - version: 3.2.6 - resolution: "async@npm:3.2.6" - checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 - languageName: node - linkType: hard - -"asynckit@npm:^0.4.0": - version: 0.4.0 - resolution: "asynckit@npm:0.4.0" - checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d - languageName: node - linkType: hard - -"axios@npm:^1.8.4": - version: 1.8.4 - resolution: "axios@npm:1.8.4" - dependencies: - follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.0" - proxy-from-env: "npm:^1.1.0" - checksum: 10c0/450993c2ba975ffccaf0d480b68839a3b2435a5469a71fa2fb0b8a55cdb2c2ae47e609360b9c1e2b2534b73dfd69e2733a1cf9f8215bee0bcd729b72f801b0ce - languageName: node - linkType: hard - -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee - languageName: node - linkType: hard - -"base64-js@npm:^1.3.1": - version: 1.5.1 - resolution: "base64-js@npm:1.5.1" - checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf - languageName: node - linkType: hard - -"big-integer@npm:^1.6.17": - version: 1.6.52 - resolution: "big-integer@npm:1.6.52" - checksum: 10c0/9604224b4c2ab3c43c075d92da15863077a9f59e5d4205f4e7e76acd0cd47e8d469ec5e5dba8d9b32aa233951893b29329ca56ac80c20ce094b4a647a66abae0 - languageName: node - linkType: hard - -"binary@npm:~0.3.0": - version: 0.3.0 - resolution: "binary@npm:0.3.0" - dependencies: - buffers: "npm:~0.1.1" - chainsaw: "npm:~0.1.0" - checksum: 10c0/752c2c2ff9f23506b3428cc8accbfcc92fec07bf8a31a1953e9c7e2193eb5db8a67252034ab93e8adab2a1c43f3eeb3da0bacae0320e9814f3ca127942c55871 - languageName: node - linkType: hard - -"bl@npm:^4.0.3": - version: 4.1.0 - resolution: "bl@npm:4.1.0" - dependencies: - buffer: "npm:^5.5.0" - inherits: "npm:^2.0.4" - readable-stream: "npm:^3.4.0" - checksum: 10c0/02847e1d2cb089c9dc6958add42e3cdeaf07d13f575973963335ac0fdece563a50ac770ac4c8fa06492d2dd276f6cc3b7f08c7cd9c7a7ad0f8d388b2a28def5f - languageName: node - linkType: hard - -"bluebird@npm:~3.4.1": - version: 3.4.7 - resolution: "bluebird@npm:3.4.7" - checksum: 10c0/ac7e3df09a433b985a0ba61a0be4fc23e3874bf62440ffbca2f275a8498b00c11336f1f633631f38419b2c842515473985f9c4aaa9e4c9b36105535026d94144 - languageName: node - linkType: hard - -"brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" - dependencies: - balanced-match: "npm:^1.0.0" - concat-map: "npm:0.0.1" - checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668 - languageName: node - linkType: hard - -"brace-expansion@npm:^2.0.1": - version: 2.0.1 - resolution: "brace-expansion@npm:2.0.1" - dependencies: - balanced-match: "npm:^1.0.0" - checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f - languageName: node - linkType: hard - -"buffer-crc32@npm:^0.2.1, buffer-crc32@npm:^0.2.13": - version: 0.2.13 - resolution: "buffer-crc32@npm:0.2.13" - checksum: 10c0/cb0a8ddf5cf4f766466db63279e47761eb825693eeba6a5a95ee4ec8cb8f81ede70aa7f9d8aeec083e781d47154290eb5d4d26b3f7a465ec57fb9e7d59c47150 - languageName: node - linkType: hard - -"buffer-indexof-polyfill@npm:~1.0.0": - version: 1.0.2 - resolution: "buffer-indexof-polyfill@npm:1.0.2" - checksum: 10c0/b8376d5f8b2c230d02fce36762b149b6c436aa03aca5e02b934ea13ce72a7e731c785fa30fb30e9c713df5173b4f8e89856574e70ce86b2f1d94d7d90166eab0 - languageName: node - linkType: hard - -"buffer@npm:^5.5.0": - version: 5.7.1 - resolution: "buffer@npm:5.7.1" - dependencies: - base64-js: "npm:^1.3.1" - ieee754: "npm:^1.1.13" - checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e - languageName: node - linkType: hard - -"buffers@npm:~0.1.1": - version: 0.1.1 - resolution: "buffers@npm:0.1.1" - checksum: 10c0/c7a3284ddb4f5c65431508be65535e3739215f7996aa03e5d3a3fcf03144d35ffca7d9825572e6c6c6007f5308b8553c7b2941fcf5e56fac20dedea7178f5f71 - languageName: node - linkType: hard - -"chainsaw@npm:~0.1.0": - version: 0.1.0 - resolution: "chainsaw@npm:0.1.0" - dependencies: - traverse: "npm:>=0.3.0 <0.4" - checksum: 10c0/c27b8b10fd372b07d80b3f63615ce5ecb9bb1b0be6934fe5de3bb0328f9ffad5051f206bd7a0b426b85778fee0c063a1f029fb32cc639f3b2ee38d6b39f52c5c - languageName: node - linkType: hard - -"combined-stream@npm:^1.0.8": - version: 1.0.8 - resolution: "combined-stream@npm:1.0.8" - dependencies: - delayed-stream: "npm:~1.0.0" - checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 - languageName: node - linkType: hard - -"compress-commons@npm:^4.1.2": - version: 4.1.2 - resolution: "compress-commons@npm:4.1.2" - dependencies: - buffer-crc32: "npm:^0.2.13" - crc32-stream: "npm:^4.0.2" - normalize-path: "npm:^3.0.0" - readable-stream: "npm:^3.6.0" - checksum: 10c0/e5fa03cb374ed89028e20226c70481e87286240392d5c6856f4e7fef40605c1892748648e20ed56597d390d76513b1b9bb4dbd658a1bbff41c9fa60107c74d3f - languageName: node - linkType: hard - -"concat-map@npm:0.0.1": - version: 0.0.1 - resolution: "concat-map@npm:0.0.1" - checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f - languageName: node - linkType: hard - -"core-util-is@npm:~1.0.0": - version: 1.0.3 - resolution: "core-util-is@npm:1.0.3" - checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 - languageName: node - linkType: hard - -"crc-32@npm:^1.2.0": - version: 1.2.2 - resolution: "crc-32@npm:1.2.2" - bin: - crc32: bin/crc32.njs - checksum: 10c0/11dcf4a2e77ee793835d49f2c028838eae58b44f50d1ff08394a610bfd817523f105d6ae4d9b5bef0aad45510f633eb23c903e9902e4409bed1ce70cb82b9bf0 - languageName: node - linkType: hard - -"crc32-stream@npm:^4.0.2": - version: 4.0.3 - resolution: "crc32-stream@npm:4.0.3" - dependencies: - crc-32: "npm:^1.2.0" - readable-stream: "npm:^3.4.0" - checksum: 10c0/127b0c66a947c54db37054fca86085722140644d3a75ebc61d4477bad19304d2936386b0461e8ee9e1c24b00e804cd7c2e205180e5bcb4632d20eccd60533bc4 - languageName: node - linkType: hard - -"create-require@npm:^1.1.0": - version: 1.1.1 - resolution: "create-require@npm:1.1.1" - checksum: 10c0/157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 - languageName: node - linkType: hard - -"dayjs@npm:^1.8.34": - version: 1.11.13 - resolution: "dayjs@npm:1.11.13" - checksum: 10c0/a3caf6ac8363c7dade9d1ee797848ddcf25c1ace68d9fe8678ecf8ba0675825430de5d793672ec87b24a69bf04a1544b176547b2539982275d5542a7955f35b7 - languageName: node - linkType: hard - -"delayed-stream@npm:~1.0.0": - version: 1.0.0 - resolution: "delayed-stream@npm:1.0.0" - checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 - languageName: node - linkType: hard - -"diff@npm:^4.0.1": - version: 4.0.2 - resolution: "diff@npm:4.0.2" - checksum: 10c0/81b91f9d39c4eaca068eb0c1eb0e4afbdc5bb2941d197f513dd596b820b956fef43485876226d65d497bebc15666aa2aa82c679e84f65d5f2bfbf14ee46e32c1 - languageName: node - linkType: hard - -"dotenv@npm:^16.4.7": - version: 16.4.7 - resolution: "dotenv@npm:16.4.7" - checksum: 10c0/be9f597e36a8daf834452daa1f4cc30e5375a5968f98f46d89b16b983c567398a330580c88395069a77473943c06b877d1ca25b4afafcdd6d4adb549e8293462 - languageName: node - linkType: hard - -"duplexer2@npm:~0.1.4": - version: 0.1.4 - resolution: "duplexer2@npm:0.1.4" - dependencies: - readable-stream: "npm:^2.0.2" - checksum: 10c0/0765a4cc6fe6d9615d43cc6dbccff6f8412811d89a6f6aa44828ca9422a0a469625ce023bf81cee68f52930dbedf9c5716056ff264ac886612702d134b5e39b4 - languageName: node - linkType: hard - -"end-of-stream@npm:^1.4.1": - version: 1.4.4 - resolution: "end-of-stream@npm:1.4.4" - dependencies: - once: "npm:^1.4.0" - checksum: 10c0/870b423afb2d54bb8d243c63e07c170409d41e20b47eeef0727547aea5740bd6717aca45597a9f2745525667a6b804c1e7bede41f856818faee5806dd9ff3975 - languageName: node - linkType: hard - -"escape-string-regexp@npm:1.0.5": - version: 1.0.5 - resolution: "escape-string-regexp@npm:1.0.5" - checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 - languageName: node - linkType: hard - -"event-target-shim@npm:^5.0.0": - version: 5.0.1 - resolution: "event-target-shim@npm:5.0.1" - checksum: 10c0/0255d9f936215fd206156fd4caa9e8d35e62075d720dc7d847e89b417e5e62cf1ce6c9b4e0a1633a9256de0efefaf9f8d26924b1f3c8620cffb9db78e7d3076b - languageName: node - linkType: hard - -"eventemitter3@npm:^4.0.4": - version: 4.0.7 - resolution: "eventemitter3@npm:4.0.7" - checksum: 10c0/5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b - languageName: node - linkType: hard - -"exceljs@npm:^4.4.0": - version: 4.4.0 - resolution: "exceljs@npm:4.4.0" - dependencies: - archiver: "npm:^5.0.0" - dayjs: "npm:^1.8.34" - fast-csv: "npm:^4.3.1" - jszip: "npm:^3.10.1" - readable-stream: "npm:^3.6.0" - saxes: "npm:^5.0.1" - tmp: "npm:^0.2.0" - unzipper: "npm:^0.10.11" - uuid: "npm:^8.3.0" - checksum: 10c0/47aa3e2b1238719946b788bbe00ea7068e746df64697ec7b93662061f10c8081a69190f0c2110a69af8b0eedf26e40120479f4e93b8ce3957a83ab92dfe57f10 - languageName: node - linkType: hard - -"expression-wrangler@workspace:.": - version: 0.0.0-use.local - resolution: "expression-wrangler@workspace:." - dependencies: - "@anthropic-ai/sdk": "npm:^0.56.0" - "@types/git-rev-sync": "npm:^2.0.2" - "@types/lodash": "npm:^4.17.14" - "@types/node": "npm:^22.10.6" - "@types/xml2js": "npm:^0.4.14" - axios: "npm:^1.8.4" - dotenv: "npm:^16.4.7" - exceljs: "npm:^4.4.0" - git-rev-sync: "npm:^3.0.2" - lodash: "npm:^4.17.21" - openai: "npm:^4.78.1" - p-queue: "npm:^6.6.2" - p-retry: "npm:^4.6.2" - ts-node: "npm:^10.9.2" - typescript: "npm:^5.7.3" - xml2js: "npm:^0.6.2" - yaml: "npm:^2.7.0" - zod: "npm:^3.24.1" - languageName: unknown - linkType: soft - -"fast-csv@npm:^4.3.1": - version: 4.3.6 - resolution: "fast-csv@npm:4.3.6" - dependencies: - "@fast-csv/format": "npm:4.3.5" - "@fast-csv/parse": "npm:4.3.6" - checksum: 10c0/45118395a75dbb4fb3a074ee76f03abefe8414accbde6613ec3f326c3fef7098b8dc6297de65b6f15755f7520079aac58249cc939010033285161af8b1e007c9 - languageName: node - linkType: hard - -"follow-redirects@npm:^1.15.6": - version: 1.15.9 - resolution: "follow-redirects@npm:1.15.9" - peerDependenciesMeta: - debug: - optional: true - checksum: 10c0/5829165bd112c3c0e82be6c15b1a58fa9dcfaede3b3c54697a82fe4a62dd5ae5e8222956b448d2f98e331525f05d00404aba7d696de9e761ef6e42fdc780244f - languageName: node - linkType: hard - -"form-data-encoder@npm:1.7.2": - version: 1.7.2 - resolution: "form-data-encoder@npm:1.7.2" - checksum: 10c0/56553768037b6d55d9de524f97fe70555f0e415e781cb56fc457a68263de3d40fadea2304d4beef2d40b1a851269bd7854e42c362107071892cb5238debe9464 - languageName: node - linkType: hard - -"form-data@npm:^4.0.0": - version: 4.0.1 - resolution: "form-data@npm:4.0.1" - dependencies: - asynckit: "npm:^0.4.0" - combined-stream: "npm:^1.0.8" - mime-types: "npm:^2.1.12" - checksum: 10c0/bb102d570be8592c23f4ea72d7df9daa50c7792eb0cf1c5d7e506c1706e7426a4e4ae48a35b109e91c85f1c0ec63774a21ae252b66f4eb981cb8efef7d0463c8 - languageName: node - linkType: hard - -"formdata-node@npm:^4.3.2": - version: 4.4.1 - resolution: "formdata-node@npm:4.4.1" - dependencies: - node-domexception: "npm:1.0.0" - web-streams-polyfill: "npm:4.0.0-beta.3" - checksum: 10c0/74151e7b228ffb33b565cec69182694ad07cc3fdd9126a8240468bb70a8ba66e97e097072b60bcb08729b24c7ce3fd3e0bd7f1f80df6f9f662b9656786e76f6a - languageName: node - linkType: hard - -"fs-constants@npm:^1.0.0": - version: 1.0.0 - resolution: "fs-constants@npm:1.0.0" - checksum: 10c0/a0cde99085f0872f4d244e83e03a46aa387b74f5a5af750896c6b05e9077fac00e9932fdf5aef84f2f16634cd473c63037d7a512576da7d5c2b9163d1909f3a8 - languageName: node - linkType: hard - -"fs.realpath@npm:^1.0.0": - version: 1.0.0 - resolution: "fs.realpath@npm:1.0.0" - checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 - languageName: node - linkType: hard - -"fstream@npm:^1.0.12": - version: 1.0.12 - resolution: "fstream@npm:1.0.12" - dependencies: - graceful-fs: "npm:^4.1.2" - inherits: "npm:~2.0.0" - mkdirp: "npm:>=0.5 0" - rimraf: "npm:2" - checksum: 10c0/f52a0687a0649c6b93973eb7f1d5495e445fa993f797ba1af186e666b6aebe53916a8c497dce7037c74d0d4a33c56b0ab1f98f10ad71cca84ba8661110d25ee2 - languageName: node - linkType: hard - -"function-bind@npm:^1.1.2": - version: 1.1.2 - resolution: "function-bind@npm:1.1.2" - checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 - languageName: node - linkType: hard - -"git-rev-sync@npm:^3.0.2": - version: 3.0.2 - resolution: "git-rev-sync@npm:3.0.2" - dependencies: - escape-string-regexp: "npm:1.0.5" - graceful-fs: "npm:4.1.15" - shelljs: "npm:0.8.5" - checksum: 10c0/6dab6d32cc437751eb9324de15218f22cee9e9143dc77c2252dfc4132e4b8c99a1e4e66ede916e7980c2dc4d68c4d16db86880a1aaa4dccc258f5fd15721bba9 - languageName: node - linkType: hard - -"glob@npm:^7.0.0, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.2.3": - version: 7.2.3 - resolution: "glob@npm:7.2.3" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^3.1.1" - once: "npm:^1.3.0" - path-is-absolute: "npm:^1.0.0" - checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe - languageName: node - linkType: hard - -"graceful-fs@npm:4.1.15": - version: 4.1.15 - resolution: "graceful-fs@npm:4.1.15" - checksum: 10c0/3f6a67b5d77b04ecf1eecd2fd82c51b64d073db1a7f3144fca70bcbf272a49908cd9b46994e07bb35d6bdbbe6a1cab8b53161fdedbce17e8ff699c03a434f62d - languageName: node - linkType: hard - -"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.2": - version: 4.2.11 - resolution: "graceful-fs@npm:4.2.11" - checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 - languageName: node - linkType: hard - -"hasown@npm:^2.0.2": - version: 2.0.2 - resolution: "hasown@npm:2.0.2" - dependencies: - function-bind: "npm:^1.1.2" - checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 - languageName: node - linkType: hard - -"humanize-ms@npm:^1.2.1": - version: 1.2.1 - resolution: "humanize-ms@npm:1.2.1" - dependencies: - ms: "npm:^2.0.0" - checksum: 10c0/f34a2c20161d02303c2807badec2f3b49cbfbbb409abd4f95a07377ae01cfe6b59e3d15ac609cffcd8f2521f0eb37b7e1091acf65da99aa2a4f1ad63c21e7e7a - languageName: node - linkType: hard - -"ieee754@npm:^1.1.13": - version: 1.2.1 - resolution: "ieee754@npm:1.2.1" - checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb - languageName: node - linkType: hard - -"immediate@npm:~3.0.5": - version: 3.0.6 - resolution: "immediate@npm:3.0.6" - checksum: 10c0/f8ba7ede69bee9260241ad078d2d535848745ff5f6995c7c7cb41cfdc9ccc213f66e10fa5afb881f90298b24a3f7344b637b592beb4f54e582770cdce3f1f039 - languageName: node - linkType: hard - -"inflight@npm:^1.0.4": - version: 1.0.6 - resolution: "inflight@npm:1.0.6" - dependencies: - once: "npm:^1.3.0" - wrappy: "npm:1" - checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 - languageName: node - linkType: hard - -"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.0, inherits@npm:~2.0.3": - version: 2.0.4 - resolution: "inherits@npm:2.0.4" - checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 - languageName: node - linkType: hard - -"interpret@npm:^1.0.0": - version: 1.4.0 - resolution: "interpret@npm:1.4.0" - checksum: 10c0/08c5ad30032edeec638485bc3f6db7d0094d9b3e85e0f950866600af3c52e9fd69715416d29564731c479d9f4d43ff3e4d302a178196bdc0e6837ec147640450 - languageName: node - linkType: hard - -"is-core-module@npm:^2.16.0": - version: 2.16.1 - resolution: "is-core-module@npm:2.16.1" - dependencies: - hasown: "npm:^2.0.2" - checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd - languageName: node - linkType: hard - -"isarray@npm:~1.0.0": - version: 1.0.0 - resolution: "isarray@npm:1.0.0" - checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d - languageName: node - linkType: hard - -"jszip@npm:^3.10.1": - version: 3.10.1 - resolution: "jszip@npm:3.10.1" - dependencies: - lie: "npm:~3.3.0" - pako: "npm:~1.0.2" - readable-stream: "npm:~2.3.6" - setimmediate: "npm:^1.0.5" - checksum: 10c0/58e01ec9c4960383fb8b38dd5f67b83ccc1ec215bf74c8a5b32f42b6e5fb79fada5176842a11409c4051b5b94275044851814a31076bf49e1be218d3ef57c863 - languageName: node - linkType: hard - -"lazystream@npm:^1.0.0": - version: 1.0.1 - resolution: "lazystream@npm:1.0.1" - dependencies: - readable-stream: "npm:^2.0.5" - checksum: 10c0/ea4e509a5226ecfcc303ba6782cc269be8867d372b9bcbd625c88955df1987ea1a20da4643bf9270336415a398d33531ebf0d5f0d393b9283dc7c98bfcbd7b69 - languageName: node - linkType: hard - -"lie@npm:~3.3.0": - version: 3.3.0 - resolution: "lie@npm:3.3.0" - dependencies: - immediate: "npm:~3.0.5" - checksum: 10c0/56dd113091978f82f9dc5081769c6f3b947852ecf9feccaf83e14a123bc630c2301439ce6182521e5fbafbde88e88ac38314327a4e0493a1bea7e0699a7af808 - languageName: node - linkType: hard - -"listenercount@npm:~1.0.1": - version: 1.0.1 - resolution: "listenercount@npm:1.0.1" - checksum: 10c0/280c38501984f0a83272187ea472aff18a2aa3db40d8e05be5f797dc813c3d9351ae67a64e09d23d36e6061288b291c989390297db6a99674de2394c6930284c - languageName: node - linkType: hard - -"lodash.defaults@npm:^4.2.0": - version: 4.2.0 - resolution: "lodash.defaults@npm:4.2.0" - checksum: 10c0/d5b77aeb702caa69b17be1358faece33a84497bcca814897383c58b28a2f8dfc381b1d9edbec239f8b425126a3bbe4916223da2a576bb0411c2cefd67df80707 - languageName: node - linkType: hard - -"lodash.difference@npm:^4.5.0": - version: 4.5.0 - resolution: "lodash.difference@npm:4.5.0" - checksum: 10c0/5d52859218a7df427547ff1fadbc397879709fe6c788b037df7d6d92b676122c92bd35ec85d364edb596b65dfc6573132f420c9b4ee22bb6b9600cd454c90637 - languageName: node - linkType: hard - -"lodash.escaperegexp@npm:^4.1.2": - version: 4.1.2 - resolution: "lodash.escaperegexp@npm:4.1.2" - checksum: 10c0/484ad4067fa9119bb0f7c19a36ab143d0173a081314993fe977bd00cf2a3c6a487ce417a10f6bac598d968364f992153315f0dbe25c9e38e3eb7581dd333e087 - languageName: node - linkType: hard - -"lodash.flatten@npm:^4.4.0": - version: 4.4.0 - resolution: "lodash.flatten@npm:4.4.0" - checksum: 10c0/97e8f0d6b61fe4723c02ad0c6e67e51784c4a2c48f56ef283483e556ad01594cf9cec9c773e177bbbdbdb5d19e99b09d2487cb6b6e5dc405c2693e93b125bd3a - languageName: node - linkType: hard - -"lodash.groupby@npm:^4.6.0": - version: 4.6.0 - resolution: "lodash.groupby@npm:4.6.0" - checksum: 10c0/3d136cad438ad6c3a078984ef60e057a3498b1312aa3621b00246ecb99e8f2c4d447e2815460db7a0b661a4fe4e2eeee96c84cb661a824bad04b6cf1f7bc6e9b - languageName: node - linkType: hard - -"lodash.isboolean@npm:^3.0.3": - version: 3.0.3 - resolution: "lodash.isboolean@npm:3.0.3" - checksum: 10c0/0aac604c1ef7e72f9a6b798e5b676606042401dd58e49f051df3cc1e3adb497b3d7695635a5cbec4ae5f66456b951fdabe7d6b387055f13267cde521f10ec7f7 - languageName: node - linkType: hard - -"lodash.isequal@npm:^4.5.0": - version: 4.5.0 - resolution: "lodash.isequal@npm:4.5.0" - checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f - languageName: node - linkType: hard - -"lodash.isfunction@npm:^3.0.9": - version: 3.0.9 - resolution: "lodash.isfunction@npm:3.0.9" - checksum: 10c0/e88620922f5f104819496884779ca85bfc542efb2946df661ab3e2cd38da5c8375434c6adbedfc76dd3c2b04075d2ba8ec215cfdedf08ddd2e3c3467e8a26ccd - languageName: node - linkType: hard - -"lodash.isnil@npm:^4.0.0": - version: 4.0.0 - resolution: "lodash.isnil@npm:4.0.0" - checksum: 10c0/1a410a62eb2e797f077d038c11cbf1ea18ab36f713982849f086f86e050234d69988c76fa18d00278c0947daec67e9ecbc666326b8a06b43e36d3ece813a8120 - languageName: node - linkType: hard - -"lodash.isplainobject@npm:^4.0.6": - version: 4.0.6 - resolution: "lodash.isplainobject@npm:4.0.6" - checksum: 10c0/afd70b5c450d1e09f32a737bed06ff85b873ecd3d3d3400458725283e3f2e0bb6bf48e67dbe7a309eb371a822b16a26cca4a63c8c52db3fc7dc9d5f9dd324cbb - languageName: node - linkType: hard - -"lodash.isundefined@npm:^3.0.1": - version: 3.0.1 - resolution: "lodash.isundefined@npm:3.0.1" - checksum: 10c0/00ca2ae6fc83e10f806769130ee62b5bf419a4aaa52d1a084164b4cf2b2ab1dbf7246e05c72cf0df2ebf4ea38ab565a688c1a7362b54331bb336ea8b492f327f - languageName: node - linkType: hard - -"lodash.union@npm:^4.6.0": - version: 4.6.0 - resolution: "lodash.union@npm:4.6.0" - checksum: 10c0/6da7f72d1facd472f6090b49eefff984c9f9179e13172039c0debca6851d21d37d83c7ad5c43af23bd220f184cd80e6897e8e3206509fae491f9068b02ae6319 - languageName: node - linkType: hard - -"lodash.uniq@npm:^4.5.0": - version: 4.5.0 - resolution: "lodash.uniq@npm:4.5.0" - checksum: 10c0/262d400bb0952f112162a320cc4a75dea4f66078b9e7e3075ffbc9c6aa30b3e9df3cf20e7da7d566105e1ccf7804e4fbd7d804eee0b53de05d83f16ffbf41c5e - languageName: node - linkType: hard - -"lodash@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c - languageName: node - linkType: hard - -"make-error@npm:^1.1.1": - version: 1.3.6 - resolution: "make-error@npm:1.3.6" - checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f - languageName: node - linkType: hard - -"mime-db@npm:1.52.0": - version: 1.52.0 - resolution: "mime-db@npm:1.52.0" - checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa - languageName: node - linkType: hard - -"mime-types@npm:^2.1.12": - version: 2.1.35 - resolution: "mime-types@npm:2.1.35" - dependencies: - mime-db: "npm:1.52.0" - checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 - languageName: node - linkType: hard - -"minimatch@npm:^3.1.1": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 - languageName: node - linkType: hard - -"minimatch@npm:^5.1.0": - version: 5.1.6 - resolution: "minimatch@npm:5.1.6" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 - languageName: node - linkType: hard - -"minimist@npm:^1.2.6": - version: 1.2.8 - resolution: "minimist@npm:1.2.8" - checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 - languageName: node - linkType: hard - -"mkdirp@npm:>=0.5 0": - version: 0.5.6 - resolution: "mkdirp@npm:0.5.6" - dependencies: - minimist: "npm:^1.2.6" - bin: - mkdirp: bin/cmd.js - checksum: 10c0/e2e2be789218807b58abced04e7b49851d9e46e88a2f9539242cc8a92c9b5c3a0b9bab360bd3014e02a140fc4fbc58e31176c408b493f8a2a6f4986bd7527b01 - languageName: node - linkType: hard - -"ms@npm:^2.0.0": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 - languageName: node - linkType: hard - -"node-domexception@npm:1.0.0": - version: 1.0.0 - resolution: "node-domexception@npm:1.0.0" - checksum: 10c0/5e5d63cda29856402df9472335af4bb13875e1927ad3be861dc5ebde38917aecbf9ae337923777af52a48c426b70148815e890a5d72760f1b4d758cc671b1a2b - languageName: node - linkType: hard - -"node-fetch@npm:^2.6.7": - version: 2.7.0 - resolution: "node-fetch@npm:2.7.0" - dependencies: - whatwg-url: "npm:^5.0.0" - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8 - languageName: node - linkType: hard - -"normalize-path@npm:^3.0.0": - version: 3.0.0 - resolution: "normalize-path@npm:3.0.0" - checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 - languageName: node - linkType: hard - -"once@npm:^1.3.0, once@npm:^1.4.0": - version: 1.4.0 - resolution: "once@npm:1.4.0" - dependencies: - wrappy: "npm:1" - checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 - languageName: node - linkType: hard - -"openai@npm:^4.78.1": - version: 4.78.1 - resolution: "openai@npm:4.78.1" - dependencies: - "@types/node": "npm:^18.11.18" - "@types/node-fetch": "npm:^2.6.4" - abort-controller: "npm:^3.0.0" - agentkeepalive: "npm:^4.2.1" - form-data-encoder: "npm:1.7.2" - formdata-node: "npm:^4.3.2" - node-fetch: "npm:^2.6.7" - peerDependencies: - zod: ^3.23.8 - peerDependenciesMeta: - zod: - optional: true - bin: - openai: bin/cli - checksum: 10c0/3f76bd7d1b52be230bb0dcc5773911b06d276886ecfbed88ec7b2d9c24b151ac870299f8e71682c3cf4bad761a8c617bd35839d1b44012bc3d29e0969bc83b92 - languageName: node - linkType: hard - -"p-finally@npm:^1.0.0": - version: 1.0.0 - resolution: "p-finally@npm:1.0.0" - checksum: 10c0/6b8552339a71fe7bd424d01d8451eea92d379a711fc62f6b2fe64cad8a472c7259a236c9a22b4733abca0b5666ad503cb497792a0478c5af31ded793d00937e7 - languageName: node - linkType: hard - -"p-queue@npm:^6.6.2": - version: 6.6.2 - resolution: "p-queue@npm:6.6.2" - dependencies: - eventemitter3: "npm:^4.0.4" - p-timeout: "npm:^3.2.0" - checksum: 10c0/5739ecf5806bbeadf8e463793d5e3004d08bb3f6177bd1a44a005da8fd81bb90f80e4633e1fb6f1dfd35ee663a5c0229abe26aebb36f547ad5a858347c7b0d3e - languageName: node - linkType: hard - -"p-retry@npm:^4.6.2": - version: 4.6.2 - resolution: "p-retry@npm:4.6.2" - dependencies: - "@types/retry": "npm:0.12.0" - retry: "npm:^0.13.1" - checksum: 10c0/d58512f120f1590cfedb4c2e0c42cb3fa66f3cea8a4646632fcb834c56055bb7a6f138aa57b20cc236fb207c9d694e362e0b5c2b14d9b062f67e8925580c73b0 - languageName: node - linkType: hard - -"p-timeout@npm:^3.2.0": - version: 3.2.0 - resolution: "p-timeout@npm:3.2.0" - dependencies: - p-finally: "npm:^1.0.0" - checksum: 10c0/524b393711a6ba8e1d48137c5924749f29c93d70b671e6db761afa784726572ca06149c715632da8f70c090073afb2af1c05730303f915604fd38ee207b70a61 - languageName: node - linkType: hard - -"pako@npm:~1.0.2": - version: 1.0.11 - resolution: "pako@npm:1.0.11" - checksum: 10c0/86dd99d8b34c3930345b8bbeb5e1cd8a05f608eeb40967b293f72fe469d0e9c88b783a8777e4cc7dc7c91ce54c5e93d88ff4b4f060e6ff18408fd21030d9ffbe - languageName: node - linkType: hard - -"path-is-absolute@npm:^1.0.0": - version: 1.0.1 - resolution: "path-is-absolute@npm:1.0.1" - checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 - languageName: node - linkType: hard - -"path-parse@npm:^1.0.7": - version: 1.0.7 - resolution: "path-parse@npm:1.0.7" - checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 - languageName: node - linkType: hard - -"process-nextick-args@npm:~2.0.0": - version: 2.0.1 - resolution: "process-nextick-args@npm:2.0.1" - checksum: 10c0/bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367 - languageName: node - linkType: hard - -"proxy-from-env@npm:^1.1.0": - version: 1.1.0 - resolution: "proxy-from-env@npm:1.1.0" - checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b - languageName: node - linkType: hard - -"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:~2.3.6": - version: 2.3.8 - resolution: "readable-stream@npm:2.3.8" - dependencies: - core-util-is: "npm:~1.0.0" - inherits: "npm:~2.0.3" - isarray: "npm:~1.0.0" - process-nextick-args: "npm:~2.0.0" - safe-buffer: "npm:~5.1.1" - string_decoder: "npm:~1.1.1" - util-deprecate: "npm:~1.0.1" - checksum: 10c0/7efdb01f3853bc35ac62ea25493567bf588773213f5f4a79f9c365e1ad13bab845ac0dae7bc946270dc40c3929483228415e92a3fc600cc7e4548992f41ee3fa - languageName: node - linkType: hard - -"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": - version: 3.6.2 - resolution: "readable-stream@npm:3.6.2" - dependencies: - inherits: "npm:^2.0.3" - string_decoder: "npm:^1.1.1" - util-deprecate: "npm:^1.0.1" - checksum: 10c0/e37be5c79c376fdd088a45fa31ea2e423e5d48854be7a22a58869b4e84d25047b193f6acb54f1012331e1bcd667ffb569c01b99d36b0bd59658fb33f513511b7 - languageName: node - linkType: hard - -"readdir-glob@npm:^1.1.2": - version: 1.1.3 - resolution: "readdir-glob@npm:1.1.3" - dependencies: - minimatch: "npm:^5.1.0" - checksum: 10c0/a37e0716726650845d761f1041387acd93aa91b28dd5381950733f994b6c349ddc1e21e266ec7cc1f9b92e205a7a972232f9b89d5424d07361c2c3753d5dbace - languageName: node - linkType: hard - -"rechoir@npm:^0.6.2": - version: 0.6.2 - resolution: "rechoir@npm:0.6.2" - dependencies: - resolve: "npm:^1.1.6" - checksum: 10c0/22c4bb32f4934a9468468b608417194f7e3ceba9a508512125b16082c64f161915a28467562368eeb15dc16058eb5b7c13a20b9eb29ff9927d1ebb3b5aa83e84 - languageName: node - linkType: hard - -"resolve@npm:^1.1.6": - version: 1.22.10 - resolution: "resolve@npm:1.22.10" - dependencies: - is-core-module: "npm:^2.16.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/8967e1f4e2cc40f79b7e080b4582b9a8c5ee36ffb46041dccb20e6461161adf69f843b43067b4a375de926a2cd669157e29a29578191def399dd5ef89a1b5203 - languageName: node - linkType: hard - -"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin": - version: 1.22.10 - resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.16.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/52a4e505bbfc7925ac8f4cd91fd8c4e096b6a89728b9f46861d3b405ac9a1ccf4dcbf8befb4e89a2e11370dacd0160918163885cbc669369590f2f31f4c58939 - languageName: node - linkType: hard - -"retry@npm:^0.13.1": - version: 0.13.1 - resolution: "retry@npm:0.13.1" - checksum: 10c0/9ae822ee19db2163497e074ea919780b1efa00431d197c7afdb950e42bf109196774b92a49fc9821f0b8b328a98eea6017410bfc5e8a0fc19c85c6d11adb3772 - languageName: node - linkType: hard - -"rimraf@npm:2": - version: 2.7.1 - resolution: "rimraf@npm:2.7.1" - dependencies: - glob: "npm:^7.1.3" - bin: - rimraf: ./bin.js - checksum: 10c0/4eef73d406c6940927479a3a9dee551e14a54faf54b31ef861250ac815172bade86cc6f7d64a4dc5e98b65e4b18a2e1c9ff3b68d296be0c748413f092bb0dd40 - languageName: node - linkType: hard - -"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": - version: 5.1.2 - resolution: "safe-buffer@npm:5.1.2" - checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 - languageName: node - linkType: hard - -"safe-buffer@npm:~5.2.0": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 - languageName: node - linkType: hard - -"sax@npm:>=0.6.0": - version: 1.4.1 - resolution: "sax@npm:1.4.1" - checksum: 10c0/6bf86318a254c5d898ede6bd3ded15daf68ae08a5495a2739564eb265cd13bcc64a07ab466fb204f67ce472bb534eb8612dac587435515169593f4fffa11de7c - languageName: node - linkType: hard - -"saxes@npm:^5.0.1": - version: 5.0.1 - resolution: "saxes@npm:5.0.1" - dependencies: - xmlchars: "npm:^2.2.0" - checksum: 10c0/b7476c41dbe1c3a89907d2546fecfba234de5e66743ef914cde2603f47b19bed09732ab51b528ad0f98b958369d8be72b6f5af5c9cfad69972a73d061f0b3952 - languageName: node - linkType: hard - -"setimmediate@npm:^1.0.5, setimmediate@npm:~1.0.4": - version: 1.0.5 - resolution: "setimmediate@npm:1.0.5" - checksum: 10c0/5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49 - languageName: node - linkType: hard - -"shelljs@npm:0.8.5": - version: 0.8.5 - resolution: "shelljs@npm:0.8.5" - dependencies: - glob: "npm:^7.0.0" - interpret: "npm:^1.0.0" - rechoir: "npm:^0.6.2" - bin: - shjs: bin/shjs - checksum: 10c0/feb25289a12e4bcd04c40ddfab51aff98a3729f5c2602d5b1a1b95f6819ec7804ac8147ebd8d9a85dfab69d501bcf92d7acef03247320f51c1552cec8d8e2382 - languageName: node - linkType: hard - -"string_decoder@npm:^1.1.1": - version: 1.3.0 - resolution: "string_decoder@npm:1.3.0" - dependencies: - safe-buffer: "npm:~5.2.0" - checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d - languageName: node - linkType: hard - -"string_decoder@npm:~1.1.1": - version: 1.1.1 - resolution: "string_decoder@npm:1.1.1" - dependencies: - safe-buffer: "npm:~5.1.0" - checksum: 10c0/b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e - languageName: node - linkType: hard - -"supports-preserve-symlinks-flag@npm:^1.0.0": - version: 1.0.0 - resolution: "supports-preserve-symlinks-flag@npm:1.0.0" - checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 - languageName: node - linkType: hard - -"tar-stream@npm:^2.2.0": - version: 2.2.0 - resolution: "tar-stream@npm:2.2.0" - dependencies: - bl: "npm:^4.0.3" - end-of-stream: "npm:^1.4.1" - fs-constants: "npm:^1.0.0" - inherits: "npm:^2.0.3" - readable-stream: "npm:^3.1.1" - checksum: 10c0/2f4c910b3ee7196502e1ff015a7ba321ec6ea837667220d7bcb8d0852d51cb04b87f7ae471008a6fb8f5b1a1b5078f62f3a82d30c706f20ada1238ac797e7692 - languageName: node - linkType: hard - -"tmp@npm:^0.2.0": - version: 0.2.3 - resolution: "tmp@npm:0.2.3" - checksum: 10c0/3e809d9c2f46817475b452725c2aaa5d11985cf18d32a7a970ff25b568438e2c076c2e8609224feef3b7923fa9749b74428e3e634f6b8e520c534eef2fd24125 - languageName: node - linkType: hard - -"tr46@npm:~0.0.3": - version: 0.0.3 - resolution: "tr46@npm:0.0.3" - checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 - languageName: node - linkType: hard - -"traverse@npm:>=0.3.0 <0.4": - version: 0.3.9 - resolution: "traverse@npm:0.3.9" - checksum: 10c0/05f04ff1002f08f19b033187124764e2713186c7a7c0ad88172368df993edc4fa7580e829e252cef6b38375317b69671932ee3820381398a9e375aad3797f607 - languageName: node - linkType: hard - -"ts-node@npm:^10.9.2": - version: 10.9.2 - resolution: "ts-node@npm:10.9.2" - dependencies: - "@cspotcode/source-map-support": "npm:^0.8.0" - "@tsconfig/node10": "npm:^1.0.7" - "@tsconfig/node12": "npm:^1.0.7" - "@tsconfig/node14": "npm:^1.0.0" - "@tsconfig/node16": "npm:^1.0.2" - acorn: "npm:^8.4.1" - acorn-walk: "npm:^8.1.1" - arg: "npm:^4.1.0" - create-require: "npm:^1.1.0" - diff: "npm:^4.0.1" - make-error: "npm:^1.1.1" - v8-compile-cache-lib: "npm:^3.0.1" - yn: "npm:3.1.1" - peerDependencies: - "@swc/core": ">=1.2.50" - "@swc/wasm": ">=1.2.50" - "@types/node": "*" - typescript: ">=2.7" - peerDependenciesMeta: - "@swc/core": - optional: true - "@swc/wasm": - optional: true - bin: - ts-node: dist/bin.js - ts-node-cwd: dist/bin-cwd.js - ts-node-esm: dist/bin-esm.js - ts-node-script: dist/bin-script.js - ts-node-transpile-only: dist/bin-transpile.js - ts-script: dist/bin-script-deprecated.js - checksum: 10c0/5f29938489f96982a25ba650b64218e83a3357d76f7bede80195c65ab44ad279c8357264639b7abdd5d7e75fc269a83daa0e9c62fd8637a3def67254ecc9ddc2 - languageName: node - linkType: hard - -"typescript@npm:^5.7.3": - version: 5.7.3 - resolution: "typescript@npm:5.7.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/b7580d716cf1824736cc6e628ab4cd8b51877408ba2be0869d2866da35ef8366dd6ae9eb9d0851470a39be17cbd61df1126f9e211d8799d764ea7431d5435afa - languageName: node - linkType: hard - -"typescript@patch:typescript@npm%3A^5.7.3#optional!builtin": - version: 5.7.3 - resolution: "typescript@patch:typescript@npm%3A5.7.3#optional!builtin::version=5.7.3&hash=5786d5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/6fd7e0ed3bf23a81246878c613423730c40e8bdbfec4c6e4d7bf1b847cbb39076e56ad5f50aa9d7ebd89877999abaee216002d3f2818885e41c907caaa192cc4 - languageName: node - linkType: hard - -"undici-types@npm:~5.26.4": - version: 5.26.5 - resolution: "undici-types@npm:5.26.5" - checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 - languageName: node - linkType: hard - -"undici-types@npm:~6.20.0": - version: 6.20.0 - resolution: "undici-types@npm:6.20.0" - checksum: 10c0/68e659a98898d6a836a9a59e6adf14a5d799707f5ea629433e025ac90d239f75e408e2e5ff086afc3cace26f8b26ee52155293564593fbb4a2f666af57fc59bf - languageName: node - linkType: hard - -"unzipper@npm:^0.10.11": - version: 0.10.14 - resolution: "unzipper@npm:0.10.14" - dependencies: - big-integer: "npm:^1.6.17" - binary: "npm:~0.3.0" - bluebird: "npm:~3.4.1" - buffer-indexof-polyfill: "npm:~1.0.0" - duplexer2: "npm:~0.1.4" - fstream: "npm:^1.0.12" - graceful-fs: "npm:^4.2.2" - listenercount: "npm:~1.0.1" - readable-stream: "npm:~2.3.6" - setimmediate: "npm:~1.0.4" - checksum: 10c0/0d9d0bdb566581534fba4ad88cbf037f3c1d9aa97fcd26ca52d30e7e198a3c6cb9e315deadc59821647c98657f233601cb9ebfc92f59228a1fe594197061760e - languageName: node - linkType: hard - -"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": - version: 1.0.2 - resolution: "util-deprecate@npm:1.0.2" - checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 - languageName: node - linkType: hard - -"uuid@npm:^8.3.0": - version: 8.3.2 - resolution: "uuid@npm:8.3.2" - bin: - uuid: dist/bin/uuid - checksum: 10c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 - languageName: node - linkType: hard - -"v8-compile-cache-lib@npm:^3.0.1": - version: 3.0.1 - resolution: "v8-compile-cache-lib@npm:3.0.1" - checksum: 10c0/bdc36fb8095d3b41df197f5fb6f11e3a26adf4059df3213e3baa93810d8f0cc76f9a74aaefc18b73e91fe7e19154ed6f134eda6fded2e0f1c8d2272ed2d2d391 - languageName: node - linkType: hard - -"web-streams-polyfill@npm:4.0.0-beta.3": - version: 4.0.0-beta.3 - resolution: "web-streams-polyfill@npm:4.0.0-beta.3" - checksum: 10c0/a9596779db2766990117ed3a158e0b0e9f69b887a6d6ba0779940259e95f99dc3922e534acc3e5a117b5f5905300f527d6fbf8a9f0957faf1d8e585ce3452e8e - languageName: node - linkType: hard - -"webidl-conversions@npm:^3.0.0": - version: 3.0.1 - resolution: "webidl-conversions@npm:3.0.1" - checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db - languageName: node - linkType: hard - -"whatwg-url@npm:^5.0.0": - version: 5.0.0 - resolution: "whatwg-url@npm:5.0.0" - dependencies: - tr46: "npm:~0.0.3" - webidl-conversions: "npm:^3.0.0" - checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 - languageName: node - linkType: hard - -"wrappy@npm:1": - version: 1.0.2 - resolution: "wrappy@npm:1.0.2" - checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 - languageName: node - linkType: hard - -"xml2js@npm:^0.6.2": - version: 0.6.2 - resolution: "xml2js@npm:0.6.2" - dependencies: - sax: "npm:>=0.6.0" - xmlbuilder: "npm:~11.0.0" - checksum: 10c0/e98a84e9c172c556ee2c5afa0fc7161b46919e8b53ab20de140eedea19903ed82f7cd5b1576fb345c84f0a18da1982ddf65908129b58fc3d7cbc658ae232108f - languageName: node - linkType: hard - -"xmlbuilder@npm:~11.0.0": - version: 11.0.1 - resolution: "xmlbuilder@npm:11.0.1" - checksum: 10c0/74b979f89a0a129926bc786b913459bdbcefa809afaa551c5ab83f89b1915bdaea14c11c759284bb9b931e3b53004dbc2181e21d3ca9553eeb0b2a7b4e40c35b - languageName: node - linkType: hard - -"xmlchars@npm:^2.2.0": - version: 2.2.0 - resolution: "xmlchars@npm:2.2.0" - checksum: 10c0/b64b535861a6f310c5d9bfa10834cf49127c71922c297da9d4d1b45eeaae40bf9b4363275876088fbe2667e5db028d2cd4f8ee72eed9bede840a67d57dab7593 - languageName: node - linkType: hard - -"yaml@npm:^2.7.0": - version: 2.7.0 - resolution: "yaml@npm:2.7.0" - bin: - yaml: bin.mjs - checksum: 10c0/886a7d2abbd70704b79f1d2d05fe9fb0aa63aefb86e1cb9991837dced65193d300f5554747a872b4b10ae9a12bc5d5327e4d04205f70336e863e35e89d8f4ea9 - languageName: node - linkType: hard - -"yn@npm:3.1.1": - version: 3.1.1 - resolution: "yn@npm:3.1.1" - checksum: 10c0/0732468dd7622ed8a274f640f191f3eaf1f39d5349a1b72836df484998d7d9807fbea094e2f5486d6b0cd2414aad5775972df0e68f8604db89a239f0f4bf7443 - languageName: node - linkType: hard - -"zip-stream@npm:^4.1.0": - version: 4.1.1 - resolution: "zip-stream@npm:4.1.1" - dependencies: - archiver-utils: "npm:^3.0.4" - compress-commons: "npm:^4.1.2" - readable-stream: "npm:^3.6.0" - checksum: 10c0/38f91ca116a38561cf184c29e035e9453b12c30eaf574e0993107a4a5331882b58c9a7f7b97f63910664028089fbde3296d0b3682d1ccb2ad96929e68f1b2b89 - languageName: node - linkType: hard - -"zod@npm:^3.24.1": - version: 3.24.1 - resolution: "zod@npm:3.24.1" - checksum: 10c0/0223d21dbaa15d8928fe0da3b54696391d8e3e1e2d0283a1a070b5980a1dbba945ce631c2d1eccc088fdbad0f2dfa40155590bf83732d3ac4fcca2cc9237591b - languageName: node - linkType: hard +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@anthropic-ai/sdk@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.56.0.tgz#8b6366d5d22235c3ec978c05b2c9420fdf426ed9" + integrity sha512-SLCB8M8+VMg1cpCucnA1XWHGWqVSZtIWzmOdDOEu3eTFZMB+A0sGZ1ESO5MHDnqrNTXz3safMrWx9x4rMZSOqA== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@fast-csv/format@4.3.5": + version "4.3.5" + resolved "https://registry.yarnpkg.com/@fast-csv/format/-/format-4.3.5.tgz#90d83d1b47b6aaf67be70d6118f84f3e12ee1ff3" + integrity sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A== + dependencies: + "@types/node" "^14.0.1" + lodash.escaperegexp "^4.1.2" + lodash.isboolean "^3.0.3" + lodash.isequal "^4.5.0" + lodash.isfunction "^3.0.9" + lodash.isnil "^4.0.0" + +"@fast-csv/parse@4.3.6": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@fast-csv/parse/-/parse-4.3.6.tgz#ee47d0640ca0291034c7aa94039a744cfb019264" + integrity sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA== + dependencies: + "@types/node" "^14.0.1" + lodash.escaperegexp "^4.1.2" + lodash.groupby "^4.6.0" + lodash.isfunction "^3.0.9" + lodash.isnil "^4.0.0" + lodash.isundefined "^3.0.1" + lodash.uniq "^4.5.0" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/git-rev-sync@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/git-rev-sync/-/git-rev-sync-2.0.2.tgz#97be2eff77d0a21acbce7e3947683633746b6233" + integrity sha512-ygFM5I5q4VJjU+xrb2MSzgj4BpC6HUzMnmfWp4d8bgAw/XFkJTiKn1uaNpOOT1gw+IxELyfY97JA6sRBv7J9sA== + +"@types/lodash@^4.17.14": + version "4.17.20" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.20.tgz#1ca77361d7363432d29f5e55950d9ec1e1c6ea93" + integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA== + +"@types/node-fetch@^2.6.4": + version "2.6.13" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.13.tgz#e0c9b7b5edbdb1b50ce32c127e85e880872d56ee" + integrity sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw== + dependencies: + "@types/node" "*" + form-data "^4.0.4" + +"@types/node@*": + version "24.8.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.8.1.tgz#74c8ae00b045a0a351f2837ec00f25dfed0053be" + integrity sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q== + dependencies: + undici-types "~7.14.0" + +"@types/node@^14.0.1": + version "14.18.63" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b" + integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ== + +"@types/node@^18.11.18": + version "18.19.130" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.130.tgz#da4c6324793a79defb7a62cba3947ec5add00d59" + integrity sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg== + dependencies: + undici-types "~5.26.4" + +"@types/node@^22.10.6": + version "22.18.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.18.11.tgz#aa8a8ccae8cc828512df642f0d82606b89450d71" + integrity sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ== + dependencies: + undici-types "~6.21.0" + +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/xml2js@^0.4.14": + version "0.4.14" + resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.14.tgz#5d462a2a7330345e2309c6b549a183a376de8f9a" + integrity sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ== + dependencies: + "@types/node" "*" + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +agentkeepalive@^4.2.1: + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== + dependencies: + humanize-ms "^1.2.1" + +archiver-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" + integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== + dependencies: + glob "^7.1.4" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^2.0.0" + +archiver-utils@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-3.0.4.tgz#a0d201f1cf8fce7af3b5a05aea0a337329e96ec7" + integrity sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw== + dependencies: + glob "^7.2.3" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + +archiver@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.2.tgz#99991d5957e53bd0303a392979276ac4ddccf3b0" + integrity sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw== + dependencies: + archiver-utils "^2.1.0" + async "^3.2.4" + buffer-crc32 "^0.2.1" + readable-stream "^3.6.0" + readdir-glob "^1.1.2" + tar-stream "^2.2.0" + zip-stream "^4.1.0" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +async@^3.2.4: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.8.4: + version "1.12.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.12.2.tgz#6c307390136cf7a2278d09cec63b136dfc6e6da7" + integrity sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.4" + proxy-from-env "^1.1.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +big-integer@^1.6.17: + version "1.6.52" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" + integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== + +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg== + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA== + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + +buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ== + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ== + dependencies: + traverse ">=0.3.0 <0.4" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +compress-commons@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.2.tgz#6542e59cb63e1f46a8b21b0e06f9a32e4c8b06df" + integrity sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg== + dependencies: + buffer-crc32 "^0.2.13" + crc32-stream "^4.0.2" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +crc-32@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + +crc32-stream@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.3.tgz#85dd677eb78fa7cad1ba17cc506a597d41fc6f33" + integrity sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw== + dependencies: + crc-32 "^1.2.0" + readable-stream "^3.4.0" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +dayjs@^1.8.34: + version "1.11.18" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.18.tgz#835fa712aac52ab9dec8b1494098774ed7070a11" + integrity sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dotenv@^16.4.7: + version "16.6.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA== + dependencies: + readable-stream "^2.0.2" + +end-of-stream@^1.4.1: + version "1.4.5" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" + integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== + dependencies: + once "^1.4.0" + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +escape-string-regexp@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +eventemitter3@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +exceljs@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/exceljs/-/exceljs-4.4.0.tgz#cfb1cb8dcc82c760a9fc9faa9e52dadab66b0156" + integrity sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg== + dependencies: + archiver "^5.0.0" + dayjs "^1.8.34" + fast-csv "^4.3.1" + jszip "^3.10.1" + readable-stream "^3.6.0" + saxes "^5.0.1" + tmp "^0.2.0" + unzipper "^0.10.11" + uuid "^8.3.0" + +fast-csv@^4.3.1: + version "4.3.6" + resolved "https://registry.yarnpkg.com/fast-csv/-/fast-csv-4.3.6.tgz#70349bdd8fe4d66b1130d8c91820b64a21bc4a63" + integrity sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw== + dependencies: + "@fast-csv/format" "4.3.5" + "@fast-csv/parse" "4.3.6" + +follow-redirects@^1.15.6: + version "1.15.11" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== + +form-data-encoder@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" + integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== + +form-data@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" + integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.12" + +formdata-node@^4.3.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" + integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.3" + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +git-rev-sync@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/git-rev-sync/-/git-rev-sync-3.0.2.tgz#9763c730981187c3419b75dd270088cc5f0e161b" + integrity sha512-Nd5RiYpyncjLv0j6IONy0lGzAqdRXUaBctuGBbrEA2m6Bn4iDrN/9MeQTXuiquw8AEKL9D2BW0nw5m/lQvxqnQ== + dependencies: + escape-string-regexp "1.0.5" + graceful-fs "4.1.15" + shelljs "0.8.5" + +glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.2.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@4.1.15: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== + +graceful-fs@^4.1.2, graceful-fs@^4.2.0, graceful-fs@^4.2.2: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + +lazystream@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" + integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== + dependencies: + readable-stream "^2.0.5" + +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ== + +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== + +lodash.escaperegexp@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" + integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw== + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + +lodash.groupby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" + integrity sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + +lodash.isfunction@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== + +lodash.isnil@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz#49e28cd559013458c814c5479d3c663a21bfaa6c" + integrity sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isundefined@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" + integrity sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA== + +lodash.union@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.1.0: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +"mkdirp@>=0.5 0": + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +ms@^2.0.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +node-domexception@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +openai@^4.78.1: + version "4.104.0" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.104.0.tgz#c489765dc051b95019845dab64b0e5207cae4d30" + integrity sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-queue@^6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + +p-retry@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdir-glob@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" + integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== + dependencies: + minimatch "^5.1.0" + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + +resolve@^1.1.6: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +rimraf@2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +sax@>=0.6.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + +setimmediate@^1.0.5, setimmediate@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + +shelljs@0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tar-stream@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tmp@^0.2.0: + version "0.2.5" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" + integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ== + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +typescript@^5.7.3: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +undici-types@~7.14.0: + version "7.14.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.14.0.tgz#4c037b32ca4d7d62fae042174604341588bc0840" + integrity sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA== + +unzipper@^0.10.11: + version "0.10.14" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.14.tgz#d2b33c977714da0fbc0f82774ad35470a7c962b1" + integrity sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +web-streams-polyfill@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" + integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +xml2js@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" + integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +yaml@^2.7.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79" + integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +zip-stream@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.1.tgz#1337fe974dbaffd2fa9a1ba09662a66932bd7135" + integrity sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ== + dependencies: + archiver-utils "^3.0.4" + compress-commons "^4.1.2" + readable-stream "^3.6.0" + +zod@^3.24.1: + version "3.25.76" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" + integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== From abcaa7e8b0ec1df675f12570d0b455f0766bdf64 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Sat, 18 Oct 2025 22:27:06 +0100 Subject: [PATCH 06/36] remove useProxy unused prop --- comparison/config/sites.json | 9 +++------ comparison/scripts/fetch-summaries.ts | 1 - 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/comparison/config/sites.json b/comparison/config/sites.json index d6c713d..5ee4630 100644 --- a/comparison/config/sites.json +++ b/comparison/config/sites.json @@ -4,22 +4,19 @@ "name": "claude4", "hostname": "bmaccallum.vectorbase.org", "appPath": "vectorbase.bmaccallum", - "model": "Claude Sonnet 4", - "useProxy": true + "model": "Claude Sonnet 4" }, { "name": "gpt5", "hostname": "bmaccallum-b.vectorbase.org", "appPath": "vectorbase.bmaccallum-b", - "model": "GPT-5", - "useProxy": true + "model": "GPT-5" }, { "name": "gpt4o", "hostname": "qa.vectorbase.org", "appPath": "vectorbase.b69", - "model": "GPT-4o", - "useProxy": false + "model": "GPT-4o" } ], "endpoint": "/service/record-types/gene/searches/single_record_question_GeneRecordClasses_GeneRecordClass/reports/aiExpression", diff --git a/comparison/scripts/fetch-summaries.ts b/comparison/scripts/fetch-summaries.ts index 23ac07e..12e9ac2 100644 --- a/comparison/scripts/fetch-summaries.ts +++ b/comparison/scripts/fetch-summaries.ts @@ -9,7 +9,6 @@ interface SiteConfig { hostname: string; appPath: string; model: string; - useProxy: boolean; skipFetch?: boolean; } From caba1a27101a87c3e3549221cfb26a66635c17c9 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Sun, 19 Oct 2025 20:45:54 +0100 Subject: [PATCH 07/36] compare and condense steps done --- CLAUDE-ai-provider-comparison.md | 104 +++-- comparison/input/gene-list.txt | 8 +- comparison/scripts/compare-summaries.ts | 501 +++++++++++++++++++++ comparison/scripts/condense-comparisons.ts | 419 +++++++++++++++++ comparison/scripts/fetch-summaries.ts | 15 +- comparison/scripts/shared-utils.ts | 23 +- package.json | 1 + 7 files changed, 1025 insertions(+), 46 deletions(-) create mode 100644 comparison/scripts/compare-summaries.ts create mode 100644 comparison/scripts/condense-comparisons.ts diff --git a/CLAUDE-ai-provider-comparison.md b/CLAUDE-ai-provider-comparison.md index 46e2e56..2c4cfb8 100644 --- a/CLAUDE-ai-provider-comparison.md +++ b/CLAUDE-ai-provider-comparison.md @@ -43,22 +43,46 @@ This work will be done within the existing `expression-shepherd` repository, lev ### Phase 2: AI-Powered Comparison 1. Create TypeScript scripts that use Anthropic API to compare summaries -2. Perform pairwise comparisons for each gene: - - Claude vs GPT-5 - - Claude vs GPT-4o - - GPT-5 vs GPT-4o +2. Perform bidirectional pairwise comparisons for each gene (to detect position bias): + - Claude vs GPT-5 (both directions) + - Claude vs GPT-4o (both directions) + - GPT-5 vs GPT-4o (both directions) 3. Comparison dimensions: - - Specific biological insights mentioned + - Specific biological observations and insights (with only_in_A, only_in_B, in_both categorization) - Tone and style - Level of technical detail - - Length and structure -4. Output: JSON format for each comparison + - Structure and organization + - Deterministic metrics (word count, topic count, etc.) + - Quantitative expression mentions (fold changes, TPM, percentiles) +4. Output: JSON format for each bidirectional comparison (6 files per gene) + +### Phase 2.5: Condensation +1. Merge bidirectional comparison pairs into condensed summaries +2. For each model pair: + - Calculate biological content averages (observations/insights counts only, not full text) + - Calculate quantitative mention averages + - Use AI to merge qualitative assessments and detect position bias + - Preserve deterministic metrics from both models +3. Output: Condensed JSON files (3 files per gene: claude4-gpt4o, claude4-gpt5, gpt4o-gpt5) +4. Result: ~95% reduction in data volume by replacing detailed observation lists with summary counts ### Phase 3: Aggregate Analysis -1. Collect all pairwise comparison JSONs -2. Feed into second-pass AI summarization -3. Identify themes and patterns across the gene set -4. Generate final report on systematic differences +1. For each model pair (claude4-gpt4o, claude4-gpt5, gpt4o-gpt5): + - Collect all 20 condensed comparison files for that pair + - Calculate aggregate statistics: + - Average unique observations/insights per model across all genes + - Average quantitative mentions per model + - Average deterministic metrics (word count, topic count, etc.) + - Position bias frequency (% of genes with detected bias) + - Feed all qualitative assessments into AI for pattern identification: + - Consistent tone/style differences + - Consistent technical detail level differences + - Consistent organizational approach differences + - Any systematic contradictions or themes + - Generate model pair report with both quantitative stats and qualitative themes +2. Output: One aggregate report per model pair (3 reports total) +3. Model identities remain anonymous through aggregation; only revealed when saving final reports +4. Optional future step: Super-aggregation comparing all 3 pairwise reports ## Project Structure @@ -71,23 +95,35 @@ expression-shepherd/ │ ├── config/ │ │ └── sites.json (site configurations and endpoints) │ ├── input/ -│ │ └── gene-list.txt (VectorBase gene IDs, one per line) +│ │ └── gene-list.txt (VectorBase gene IDs, one per line, supports # comments) │ ├── scripts/ │ │ ├── shared-utils.ts (shared utilities including authentication) │ │ ├── fetch-summaries.ts (Phase 1: API calls and JSON storage) -│ │ ├── compare-summaries.ts (Phase 2: AI-powered comparison) +│ │ ├── compare-summaries.ts (Phase 2: AI-powered bidirectional comparison) +│ │ ├── condense-comparisons.ts (Phase 2.5: merge bidirectional pairs) │ │ └── aggregate-analysis.ts (Phase 3: theme identification) │ └── data/ │ ├── summaries/ -│ │ ├── claude/ (JSON response files) +│ │ ├── claude4/ (JSON response files) │ │ ├── gpt5/ │ │ └── gpt4o/ │ ├── comparisons/ │ │ └── {geneId}/ -│ │ ├── claude-vs-gpt5.json -│ │ ├── claude-vs-gpt4o.json -│ │ └── gpt5-vs-gpt4o.json -│ └── final-report.json (aggregated themes and patterns) +│ │ ├── claude4-vs-gpt5.json +│ │ ├── gpt5-vs-claude4.json +│ │ ├── claude4-vs-gpt4o.json +│ │ ├── gpt4o-vs-claude4.json +│ │ ├── gpt5-vs-gpt4o.json +│ │ └── gpt4o-vs-gpt5.json +│ ├── condensed/ +│ │ └── {geneId}/ +│ │ ├── claude4-gpt4o.json +│ │ ├── claude4-gpt5.json +│ │ └── gpt4o-gpt5.json +│ └── aggregate-reports/ +│ ├── claude4-gpt4o-report.json +│ ├── claude4-gpt5-report.json +│ └── gpt4o-gpt5-report.json ``` ## Technical Notes @@ -156,23 +192,34 @@ expression-shepherd/ 3. **Phase 2 Implementation** - Create `comparison/scripts/compare-summaries.ts` with Anthropic API integration - - Design comparison prompt for consistent results - - Generate pairwise comparison JSONs for all genes + - Design comparison prompt for consistent, blind results (no model names revealed) + - Generate bidirectional pairwise comparison JSONs for all genes (6 per gene) + +4. **Phase 2.5 Implementation** + - Create `comparison/scripts/condense-comparisons.ts` + - Merge bidirectional pairs with AI-powered qualitative assessment synthesis + - Calculate biological content statistics and detect position bias + - Maintain model anonymity throughout condensation process -4. **Phase 3 Implementation** +5. **Phase 3 Implementation** - Create `comparison/scripts/aggregate-analysis.ts` - - Collect and structure all comparison data - - Generate final thematic summary + - Process one model pair at a time (3 separate aggregation runs) + - Calculate aggregate statistics across all genes for each pair + - Use AI to identify qualitative patterns and themes + - Reveal model identities only when writing final reports ## Running the Scripts NPM scripts are available in `package.json`: - `yarn comparison:fetch` - Phase 1: Fetch summaries from all three sites -- `yarn comparison:compare` - Phase 2: Generate pairwise comparisons +- `yarn comparison:compare` - Phase 2: Generate bidirectional pairwise comparisons +- `yarn comparison:condense` - Phase 2.5: Condense bidirectional pairs into merged summaries - `yarn comparison:aggregate` - Phase 3: Generate aggregate analysis report All scripts automatically run `yarn build` before execution. +**Note**: Use `#` comments in `gene-list.txt` to temporarily exclude genes during development/testing. + ## Next Steps User needs to provide: @@ -188,8 +235,13 @@ Already available in repository: - All 20 genes successfully processed through all three sites - Complete set of JSON summaries saved locally (3 models × 20 genes = 60 files) -- Pairwise comparisons generated for each gene (3 comparisons × 20 genes = 60 files) -- Final aggregated report identifying systematic differences between models +- Bidirectional pairwise comparisons generated for each gene (6 comparisons × 20 genes = 120 files) +- Condensed comparison summaries for each gene (3 model pairs × 20 genes = 60 files) +- Three aggregate reports (one per model pair) identifying systematic differences: + - Quantitative metrics (avg observations, insights, quantitative mentions, word counts, etc.) + - Qualitative patterns (tone, technical detail, structure themes) + - Position bias statistics +- Model anonymity maintained through all analysis phases until final report generation - Reproducible process that can be re-run with new gene sets diff --git a/comparison/input/gene-list.txt b/comparison/input/gene-list.txt index 74e1ddc..fca622e 100644 --- a/comparison/input/gene-list.txt +++ b/comparison/input/gene-list.txt @@ -1,5 +1,5 @@ -AGAP001212 +#AGAP001212 AGAP000693 -AGAP000999 -AGAP009551 -AGAP006268 +#AGAP000999 +#AGAP009551 +#AGAP006268 diff --git a/comparison/scripts/compare-summaries.ts b/comparison/scripts/compare-summaries.ts new file mode 100644 index 0000000..15a9816 --- /dev/null +++ b/comparison/scripts/compare-summaries.ts @@ -0,0 +1,501 @@ +import "dotenv/config"; +import Anthropic from "@anthropic-ai/sdk"; +import { readFile } from "fs/promises"; +import path from "path"; +import { writeToFile, stripMarkdownCodeBlocks, loadGeneList } from "./shared-utils"; + +// ============================================================================ +// Type Definitions +// ============================================================================ + +interface ExperimentSummary { + assay_type: string; + experiment_name: string; + notes: string; + one_sentence_summary: string; + confidence: string | number; + dataset_id: string; + experiment_keywords: string[]; + biological_importance: string | number; +} + +interface Topic { + one_sentence_summary: string; + headline: string; + summaries: ExperimentSummary[]; +} + +interface ExpressionSummary { + one_paragraph_summary: string; + headline: string; + topics: Topic[]; +} + +interface SimplifiedTopic { + one_sentence_summary: string; + headline: string; + experiment_names: string[]; +} + +interface SimplifiedSummary { + one_paragraph_summary: string; + headline: string; + topics: SimplifiedTopic[]; +} + +interface DeterministicMetrics { + character_count: number; + word_count: number; + sentence_count: number; + paragraph_count: number; + topic_count: number; + has_bullets: boolean; + average_sentence_length: number; +} + +interface BiologicalContent { + observations: { + only_in_A: string[]; + only_in_B: string[]; + in_both: string[]; + }; + insights: { + only_in_A: string[]; + only_in_B: string[]; + in_both: string[]; + }; +} + +interface QualitativeAssessment { + tone_and_style: { + summary_A: string; + summary_B: string; + comparison: string; + }; + technical_detail_level: { + summary_A: string; + summary_B: string; + comparison: string; + }; + structure_and_organization: { + summary_A: string; + summary_B: string; + comparison: string; + }; +} + +interface QuantitativeMentions { + summary_A: number; + summary_B: number; +} + +interface ComparisonResult { + model_A: string; + model_B: string; + gene_id: string; + biological_content: BiologicalContent; + qualitative_assessment: QualitativeAssessment; + deterministic_metrics: { + summary_A: DeterministicMetrics; + summary_B: DeterministicMetrics; + }; + quantitative_expression_mentions: QuantitativeMentions; + token_usage: { + input_tokens: number; + output_tokens: number; + total_tokens: number; + }; +} + +interface SiteConfig { + name: string; + hostname: string; + appPath: string; + model: string; +} + +interface Config { + sites: SiteConfig[]; + endpoint: string; + projectId: string; +} + +// ============================================================================ +// Deterministic Metrics Calculation +// ============================================================================ + +/** + * Calculate deterministic metrics from a summary + */ +function calculateMetrics(summary: ExpressionSummary): DeterministicMetrics { + const fullText = `${summary.headline} ${summary.one_paragraph_summary} ${summary.topics + .map((t) => `${t.headline} ${t.one_sentence_summary}`) + .join(" ")}`; + + // Character count (excluding whitespace) + const character_count = fullText.replace(/\s/g, "").length; + + // Word count + const words = fullText.trim().split(/\s+/); + const word_count = words.length; + + // Sentence count (approximate - count periods, exclamation marks, question marks) + const sentences = fullText.match(/[.!?]+/g) || []; + const sentence_count = sentences.length; + + // Paragraph count (from one_paragraph_summary + topics) + const paragraph_count = 1 + summary.topics.length; + + // Topic count + const topic_count = summary.topics.length; + + // Has bullets (check for HTML list items) + const has_bullets = /
  • /i.test(fullText); + + // Average sentence length + const average_sentence_length = sentence_count > 0 ? word_count / sentence_count : 0; + + return { + character_count, + word_count, + sentence_count, + paragraph_count, + topic_count, + has_bullets, + average_sentence_length: Math.round(average_sentence_length * 10) / 10, + }; +} + +// ============================================================================ +// Summary Transformation +// ============================================================================ + +/** + * Simplify summary by replacing experiment summaries with just experiment names + * This reduces token usage and context clutter + */ +function simplifySummary(summary: ExpressionSummary): SimplifiedSummary { + return { + one_paragraph_summary: summary.one_paragraph_summary, + headline: summary.headline, + topics: summary.topics.map((topic) => ({ + one_sentence_summary: topic.one_sentence_summary, + headline: topic.headline, + experiment_names: topic.summaries.map((s) => s.experiment_name), + })), + }; +} + +// ============================================================================ +// AI-Powered Comparison +// ============================================================================ + +/** + * Generate comparison using Anthropic API + */ +async function compareWithAI( + geneId: string, + modelAName: string, + modelBName: string, + summaryA: ExpressionSummary, + summaryB: ExpressionSummary, + anthropic: Anthropic +): Promise<{ + biological_content: BiologicalContent; + qualitative_assessment: QualitativeAssessment; + quantitative_expression_mentions: QuantitativeMentions; + token_usage: { input_tokens: number; output_tokens: number; total_tokens: number }; +}> { + // Simplify summaries to reduce token usage + const simplifiedA = simplifySummary(summaryA); + const simplifiedB = simplifySummary(summaryB); + + const prompt = `You are comparing two gene expression summaries for the same gene. + +Summary A: +\`\`\`json +${JSON.stringify(simplifiedA, null, 2)} +\`\`\` + +Summary B: +\`\`\`json +${JSON.stringify(simplifiedB, null, 2)} +\`\`\` + +--- + +Please provide a detailed comparison in the following JSON format: + +{ + "biological_content": { + "observations": { + "only_in_A": ["list of factual observations about expression patterns found ONLY in Summary A"], + "only_in_B": ["list of factual observations about expression patterns found ONLY in Summary B"], + "in_both": ["list of factual observations found in BOTH summaries"] + }, + "insights": { + "only_in_A": ["list of biological insights/interpretations found ONLY in Summary A"], + "only_in_B": ["list of biological insights/interpretations found ONLY in Summary B"], + "in_both": ["list of biological insights/interpretations found in BOTH summaries"] + } + }, + "qualitative_assessment": { + "tone_and_style": { + "summary_A": "description of tone and writing style in Summary A", + "summary_B": "description of tone and writing style in Summary B", + "comparison": "comparison of tones and styles" + }, + "technical_detail_level": { + "summary_A": "assessment of technical detail in Summary A", + "summary_B": "assessment of technical detail in Summary B", + "comparison": "comparison of detail levels" + }, + "structure_and_organization": { + "summary_A": "assessment of structure in Summary A", + "summary_B": "assessment of structure in Summary B", + "comparison": "comparison of organizational approaches" + } + }, + "quantitative_expression_mentions": { + "summary_A": ${Math.floor(Math.random() * 9)}, + "summary_B": ${Math.floor(Math.random() * 9)} + } +} + +Important notes for quantitative_expression_mentions: +- Count ONLY specific numerical mentions of gene expression levels or changes +- Include: fold changes (e.g., "28-fold", "3.5x"), TPM values (e.g., "35,270 TPM"), percentile ranks (e.g., "99th percentile", "97.5%ile") +- EXCLUDE: time points (e.g., "3h", "24 hours"), ages (e.g., "10 days old"), experimental conditions (e.g., "30% RH"), sample sizes, temperatures, or any other non-expression numerical values +- Return ONLY the integer count, not explanatory text + +Important distinctions: +- **Observations** are factual statements about expression patterns (e.g., "high expression after blood feeding", "increased in salivary glands") +- **Insights** are interpretations or biological conclusions (e.g., "likely involved in digestion", "may play a role in immune response") + +Respond ONLY with valid JSON, no other text.`; + + const message = await anthropic.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 4000, + messages: [ + { + role: "user", + content: prompt, + }, + ], + }); + + const responseText = + message.content[0].type === "text" ? message.content[0].text : JSON.stringify(message.content[0]); + + // Strip markdown code blocks if present + const cleanedResponse = stripMarkdownCodeBlocks(responseText); + + try { + const parsed = JSON.parse(cleanedResponse); + + // Extract token usage from message + const input_tokens = message.usage.input_tokens; + const output_tokens = message.usage.output_tokens; + const total_tokens = input_tokens + output_tokens; + + return { + ...parsed, + token_usage: { + input_tokens, + output_tokens, + total_tokens, + }, + }; + } catch (error) { + console.error("Failed to parse AI response:", cleanedResponse); + throw new Error(`Failed to parse AI response: ${error}`); + } +} + +// ============================================================================ +// File Operations +// ============================================================================ + +/** + * Load site configuration + */ +async function loadConfig(): Promise { + const configPath = path.join(process.cwd(), "comparison/config/sites.json"); + const configContent = await readFile(configPath, "utf-8"); + return JSON.parse(configContent); +} + +/** + * Load summary for a specific gene and model + */ +async function loadSummary(modelName: string, geneId: string): Promise { + const summaryPath = path.join(process.cwd(), `comparison/data/summaries/${modelName}/${geneId}.json`); + const content = await readFile(summaryPath, "utf-8"); + return JSON.parse(content); +} + +/** + * Check that all required summary files exist before starting comparisons + * Exits with error if any files are missing + */ +async function checkAvailableSummaries(geneIds: string[], modelNames: string[]): Promise { + const missingFiles: Array<{ geneId: string; modelName: string }> = []; + + for (const geneId of geneIds) { + for (const modelName of modelNames) { + const summaryPath = path.join(process.cwd(), `comparison/data/summaries/${modelName}/${geneId}.json`); + try { + await readFile(summaryPath, "utf-8"); + } catch { + missingFiles.push({ geneId, modelName }); + } + } + } + + if (missingFiles.length > 0) { + console.error("\nERROR: Missing summary files:"); + missingFiles.forEach(({ geneId, modelName }) => { + console.error(` - ${geneId} (${modelName})`); + }); + console.error( + `\nPlease run 'yarn comparison:fetch' to generate missing summaries, or update gene-list.txt to only include genes with complete summaries.` + ); + process.exit(1); + } +} + +// ============================================================================ +// Main Comparison Logic +// ============================================================================ + +/** + * Compare two models for a specific gene + */ +async function comparePair( + geneId: string, + modelAName: string, + modelBName: string, + anthropic: Anthropic +): Promise { + console.log(` Comparing ${modelAName} vs ${modelBName}...`); + + // Load summaries + const summaryA = await loadSummary(modelAName, geneId); + const summaryB = await loadSummary(modelBName, geneId); + + // Calculate deterministic metrics + const metricsA = calculateMetrics(summaryA); + const metricsB = calculateMetrics(summaryB); + + // Get AI comparison + const aiComparison = await compareWithAI(geneId, modelAName, modelBName, summaryA, summaryB, anthropic); + + return { + model_A: modelAName, + model_B: modelBName, + gene_id: geneId, + biological_content: aiComparison.biological_content, + qualitative_assessment: aiComparison.qualitative_assessment, + deterministic_metrics: { + summary_A: metricsA, + summary_B: metricsB, + }, + quantitative_expression_mentions: aiComparison.quantitative_expression_mentions, + token_usage: aiComparison.token_usage, + }; +} + +/** + * Main execution + */ +async function main() { + console.log("Loading configuration..."); + const config = await loadConfig(); + + const modelNames = config.sites.map((s) => s.name); + console.log(`Found ${modelNames.length} models: ${modelNames.join(", ")}`); + + console.log("\nLoading gene list..."); + const geneIds = await loadGeneList(); + console.log(`Found ${geneIds.length} genes to compare`); + + if (geneIds.length === 0) { + console.error("No genes found in gene-list.txt. Please add gene IDs (one per line)."); + process.exit(1); + } + + // Validate that all required summary files exist + console.log("\nValidating summary files..."); + await checkAvailableSummaries(geneIds, modelNames); + console.log("All required summary files are present!"); + + // Initialize Anthropic client + const apiKey = process.env.ANTHROPIC_API_KEY; + if (!apiKey) { + console.error("Missing ANTHROPIC_API_KEY in .env file"); + process.exit(1); + } + const anthropic = new Anthropic({ apiKey }); + + // Generate all pairwise comparisons (bidirectional) + const pairs: Array<[string, string]> = []; + for (let i = 0; i < modelNames.length; i++) { + for (let j = i + 1; j < modelNames.length; j++) { + // Both directions + pairs.push([modelNames[i], modelNames[j]]); + pairs.push([modelNames[j], modelNames[i]]); + } + } + + console.log(`\nWill generate ${pairs.length} comparisons per gene (${pairs.length / 2} pairs × 2 directions)`); + console.log("Comparison pairs:"); + pairs.forEach(([a, b]) => console.log(` - ${a} vs ${b}`)); + + let successCount = 0; + let errorCount = 0; + + // Process each gene + for (const geneId of geneIds) { + console.log(`\n${"=".repeat(60)}`); + console.log(`Processing gene: ${geneId}`); + console.log("=".repeat(60)); + + // Process each comparison pair + for (const [modelA, modelB] of pairs) { + try { + const result = await comparePair(geneId, modelA, modelB, anthropic); + + // Save result + const outputPath = path.join( + process.cwd(), + `comparison/data/comparisons/${geneId}/${modelA}-vs-${modelB}.json` + ); + await writeToFile(outputPath, JSON.stringify(result, null, 2)); + + successCount++; + } catch (error) { + console.error(` ERROR comparing ${modelA} vs ${modelB}:`, error instanceof Error ? error.message : error); + errorCount++; + } + } + } + + // Summary + console.log("\n" + "=".repeat(60)); + console.log("SUMMARY"); + console.log("=".repeat(60)); + console.log(`Total comparisons: ${successCount + errorCount}`); + console.log(`Successful: ${successCount}`); + console.log(`Failed: ${errorCount}`); + + if (errorCount > 0) { + process.exit(1); + } +} + +// Run the script +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); +}); diff --git a/comparison/scripts/condense-comparisons.ts b/comparison/scripts/condense-comparisons.ts new file mode 100644 index 0000000..e2c34ee --- /dev/null +++ b/comparison/scripts/condense-comparisons.ts @@ -0,0 +1,419 @@ +import "dotenv/config"; +import Anthropic from "@anthropic-ai/sdk"; +import { readFile } from "fs/promises"; +import path from "path"; +import { writeToFile, stripMarkdownCodeBlocks, loadGeneList } from "./shared-utils"; + +// ============================================================================ +// Type Definitions +// ============================================================================ + +interface BiologicalContentCounts { + only_in_A: string[]; + only_in_B: string[]; + in_both: string[]; +} + +interface BiologicalContent { + observations: BiologicalContentCounts; + insights: BiologicalContentCounts; +} + +interface QualitativeCategory { + summary_A: string; + summary_B: string; + comparison: string; +} + +interface QualitativeAssessment { + tone_and_style: QualitativeCategory; + technical_detail_level: QualitativeCategory; + structure_and_organization: QualitativeCategory; +} + +interface DeterministicMetrics { + character_count: number; + word_count: number; + sentence_count: number; + paragraph_count: number; + topic_count: number; + has_bullets: boolean; + average_sentence_length: number; +} + +interface QuantitativeMentions { + summary_A: number; + summary_B: number; +} + +interface ComparisonResult { + model_A: string; + model_B: string; + gene_id: string; + biological_content: BiologicalContent; + qualitative_assessment: QualitativeAssessment; + deterministic_metrics: { + summary_A: DeterministicMetrics; + summary_B: DeterministicMetrics; + }; + quantitative_expression_mentions: QuantitativeMentions; + token_usage: { + input_tokens: number; + output_tokens: number; + total_tokens: number; + }; +} + +interface BiologicalContentSummary { + avg_unique_to_model_A: number; + avg_unique_to_model_B: number; + avg_shared: number; + position_variance: number; +} + +interface MergedQualitativeAssessment { + tone_and_style: QualitativeCategory; + technical_detail_level: QualitativeCategory; + structure_and_organization: QualitativeCategory; + position_bias_detected: boolean; + merge_notes: string; +} + +interface CondensedComparison { + gene_id: string; + model_A: string; + model_B: string; + biological_content_summary: { + observations: BiologicalContentSummary; + insights: BiologicalContentSummary; + }; + qualitative_assessment: MergedQualitativeAssessment; + deterministic_metrics: { + model_A: DeterministicMetrics; + model_B: DeterministicMetrics; + }; + quantitative_mentions: { + avg_model_A: number; + avg_model_B: number; + }; +} + +interface SiteConfig { + name: string; + hostname: string; + appPath: string; + model: string; +} + +interface Config { + sites: SiteConfig[]; + endpoint: string; + projectId: string; +} + +// ============================================================================ +// Calculation Functions +// ============================================================================ + +/** + * Calculate summary statistics for biological content from both directions + */ +function summarizeBiologicalContent( + comparison1: ComparisonResult, + comparison2: ComparisonResult, + category: "observations" | "insights" +): BiologicalContentSummary { + // In comparison1: model_A vs model_B (A first) + // In comparison2: model_B vs model_A (B first) + // So comparison1.only_in_A and comparison2.only_in_B both refer to the same model + + const uniqueToA_dir1 = comparison1.biological_content[category].only_in_A.length; + const uniqueToA_dir2 = comparison2.biological_content[category].only_in_B.length; + const avg_unique_to_model_A = (uniqueToA_dir1 + uniqueToA_dir2) / 2; + + const uniqueToB_dir1 = comparison1.biological_content[category].only_in_B.length; + const uniqueToB_dir2 = comparison2.biological_content[category].only_in_A.length; + const avg_unique_to_model_B = (uniqueToB_dir1 + uniqueToB_dir2) / 2; + + const shared_dir1 = comparison1.biological_content[category].in_both.length; + const shared_dir2 = comparison2.biological_content[category].in_both.length; + const avg_shared = (shared_dir1 + shared_dir2) / 2; + + // Calculate variance as a simple measure of consistency between directions + const variance_A = Math.abs(uniqueToA_dir1 - uniqueToA_dir2); + const variance_B = Math.abs(uniqueToB_dir1 - uniqueToB_dir2); + const position_variance = (variance_A + variance_B) / 2; + + return { + avg_unique_to_model_A, + avg_unique_to_model_B, + avg_shared, + position_variance, + }; +} + +// ============================================================================ +// AI-Powered Merging +// ============================================================================ + +/** + * Use AI to merge qualitative assessments from both directions + */ +async function mergeQualitativeAssessments( + geneId: string, + assessment1: QualitativeAssessment, + assessment2: QualitativeAssessment, + anthropic: Anthropic +): Promise { + const prompt = `You are merging two qualitative assessments of gene expression summaries for gene ${geneId}. + +These assessments compared the same two summaries but in opposite presentation orders to detect position bias. + +Assessment 1 (Summary A presented first, Summary B presented second): +\`\`\`json +${JSON.stringify(assessment1, null, 2)} +\`\`\` + +Assessment 2 (Summary B presented first, Summary A presented second): +\`\`\`json +${JSON.stringify(assessment2, null, 2)} +\`\`\` + +Note: In Assessment 2, the summary labels are reversed from Assessment 1 because the presentation order is swapped. When comparing: +- Assessment 1's "summary_A" refers to Summary A +- Assessment 2's "summary_A" refers to Summary B + +Your task: Synthesize these into a single merged assessment. If they largely agree, consolidate them. If they contradict significantly, note the contradiction and flag potential position bias. + +IMPORTANT: Do NOT refer to the summaries by any names other than "Summary A" and "Summary B". Do not use any identifying information about which AI model generated which summary. + +Respond with JSON in this format: +\`\`\`json +{ + "tone_and_style": { + "summary_A": "consolidated description of Summary A's tone", + "summary_B": "consolidated description of Summary B's tone", + "comparison": "merged comparison (use only 'Summary A' and 'Summary B' labels)" + }, + "technical_detail_level": { + "summary_A": "consolidated assessment of Summary A's detail level", + "summary_B": "consolidated assessment of Summary B's detail level", + "comparison": "merged comparison (use only 'Summary A' and 'Summary B' labels)" + }, + "structure_and_organization": { + "summary_A": "consolidated assessment of Summary A's structure", + "summary_B": "consolidated assessment of Summary B's structure", + "comparison": "merged comparison (use only 'Summary A' and 'Summary B' labels)" + }, + "position_bias_detected": false, + "merge_notes": "Brief notes on consistency or any contradictions found" +} +\`\`\` + +Respond ONLY with valid JSON, no other text.`; + + const message = await anthropic.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 2000, + messages: [ + { + role: "user", + content: prompt, + }, + ], + }); + + const responseText = + message.content[0].type === "text" ? message.content[0].text : JSON.stringify(message.content[0]); + + const cleanedResponse = stripMarkdownCodeBlocks(responseText); + + try { + const parsed = JSON.parse(cleanedResponse); + return parsed; + } catch (error) { + console.error("Failed to parse AI response:", cleanedResponse); + throw new Error(`Failed to parse AI response: ${error}`); + } +} + +// ============================================================================ +// File Operations +// ============================================================================ + +/** + * Load site configuration + */ +async function loadConfig(): Promise { + const configPath = path.join(process.cwd(), "comparison/config/sites.json"); + const configContent = await readFile(configPath, "utf-8"); + return JSON.parse(configContent); +} + +/** + * Load comparison result for a specific gene and model pair + */ +async function loadComparison(geneId: string, modelA: string, modelB: string): Promise { + const comparisonPath = path.join( + process.cwd(), + `comparison/data/comparisons/${geneId}/${modelA}-vs-${modelB}.json` + ); + const content = await readFile(comparisonPath, "utf-8"); + return JSON.parse(content); +} + +/** + * Get sorted model pair name (alphabetically) + */ +function getSortedPair(modelA: string, modelB: string): [string, string] { + return modelA < modelB ? [modelA, modelB] : [modelB, modelA]; +} + +// ============================================================================ +// Main Condensation Logic +// ============================================================================ + +/** + * Condense a pair of bidirectional comparisons + */ +async function condensePair( + geneId: string, + modelA: string, + modelB: string, + anthropic: Anthropic +): Promise { + console.log(` Condensing ${modelA} <-> ${modelB}...`); + + // Load both directions + const comparison_AvsB = await loadComparison(geneId, modelA, modelB); + const comparison_BvsA = await loadComparison(geneId, modelB, modelA); + + // Summarize biological content + const observations_summary = summarizeBiologicalContent(comparison_AvsB, comparison_BvsA, "observations"); + const insights_summary = summarizeBiologicalContent(comparison_AvsB, comparison_BvsA, "insights"); + + // Merge qualitative assessments with AI + const merged_qualitative = await mergeQualitativeAssessments( + geneId, + comparison_AvsB.qualitative_assessment, + comparison_BvsA.qualitative_assessment, + anthropic + ); + + // Average quantitative mentions + const avg_model_A = + (comparison_AvsB.quantitative_expression_mentions.summary_A + + comparison_BvsA.quantitative_expression_mentions.summary_B) / + 2; + const avg_model_B = + (comparison_AvsB.quantitative_expression_mentions.summary_B + + comparison_BvsA.quantitative_expression_mentions.summary_A) / + 2; + + return { + gene_id: geneId, + model_A: modelA, + model_B: modelB, + biological_content_summary: { + observations: observations_summary, + insights: insights_summary, + }, + qualitative_assessment: merged_qualitative, + deterministic_metrics: { + model_A: comparison_AvsB.deterministic_metrics.summary_A, + model_B: comparison_AvsB.deterministic_metrics.summary_B, + }, + quantitative_mentions: { + avg_model_A, + avg_model_B, + }, + }; +} + +/** + * Main execution + */ +async function main() { + console.log("Loading configuration..."); + const config = await loadConfig(); + + const modelNames = config.sites.map((s) => s.name); + console.log(`Found ${modelNames.length} models: ${modelNames.join(", ")}`); + + console.log("\nLoading gene list..."); + const geneIds = await loadGeneList(); + console.log(`Found ${geneIds.length} genes to condense`); + + if (geneIds.length === 0) { + console.error("No genes found in gene-list.txt. Please add gene IDs (one per line)."); + process.exit(1); + } + + // Initialize Anthropic client + const apiKey = process.env.ANTHROPIC_API_KEY; + if (!apiKey) { + console.error("Missing ANTHROPIC_API_KEY in .env file"); + process.exit(1); + } + const anthropic = new Anthropic({ apiKey }); + + // Generate all unique model pairs (alphabetically sorted) + const pairs: Array<[string, string]> = []; + for (let i = 0; i < modelNames.length; i++) { + for (let j = i + 1; j < modelNames.length; j++) { + const [sortedA, sortedB] = getSortedPair(modelNames[i], modelNames[j]); + pairs.push([sortedA, sortedB]); + } + } + + console.log(`\nWill condense ${pairs.length} model pairs`); + console.log("Model pairs:"); + pairs.forEach(([a, b]) => console.log(` - ${a} <-> ${b}`)); + + let successCount = 0; + let errorCount = 0; + + // Process each gene + for (const geneId of geneIds) { + console.log(`\n${"=".repeat(60)}`); + console.log(`Processing gene: ${geneId}`); + console.log("=".repeat(60)); + + // Process each model pair + for (const [modelA, modelB] of pairs) { + try { + const result = await condensePair(geneId, modelA, modelB, anthropic); + + // Save result + const outputPath = path.join( + process.cwd(), + `comparison/data/condensed/${geneId}/${modelA}-${modelB}.json` + ); + await writeToFile(outputPath, JSON.stringify(result, null, 2)); + + successCount++; + } catch (error) { + console.error(` ERROR condensing ${modelA} <-> ${modelB}:`, error instanceof Error ? error.message : error); + errorCount++; + } + } + } + + // Summary + console.log("\n" + "=".repeat(60)); + console.log("SUMMARY"); + console.log("=".repeat(60)); + console.log(`Total condensations: ${successCount + errorCount}`); + console.log(`Successful: ${successCount}`); + console.log(`Failed: ${errorCount}`); + + if (errorCount > 0) { + process.exit(1); + } +} + +// Run the script +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); +}); diff --git a/comparison/scripts/fetch-summaries.ts b/comparison/scripts/fetch-summaries.ts index 12e9ac2..b103e9b 100644 --- a/comparison/scripts/fetch-summaries.ts +++ b/comparison/scripts/fetch-summaries.ts @@ -2,7 +2,7 @@ import "dotenv/config"; import axios from "axios"; import { readFile } from "fs/promises"; import path from "path"; -import { writeToFile, getAuthCookie, sleep } from "./shared-utils"; +import { writeToFile, getAuthCookie, sleep, loadGeneList } from "./shared-utils"; interface SiteConfig { name: string; @@ -39,19 +39,6 @@ async function loadConfig(): Promise { return JSON.parse(configContent); } -/** - * Load gene list from input file - * Filters out comments and empty lines - */ -async function loadGeneList(): Promise { - // Use paths relative to project root, not dist directory - const geneListPath = path.join(process.cwd(), "comparison/input/gene-list.txt"); - const content = await readFile(geneListPath, "utf-8"); - return content - .split("\n") - .map((line) => line.trim()) - .filter((line) => line && !line.startsWith("#")); -} /** * Make API request to fetch AI expression summary diff --git a/comparison/scripts/shared-utils.ts b/comparison/scripts/shared-utils.ts index a34a0ab..07b3a0e 100644 --- a/comparison/scripts/shared-utils.ts +++ b/comparison/scripts/shared-utils.ts @@ -1,12 +1,17 @@ -import { writeFile } from "fs/promises"; +import { writeFile, mkdir } from "fs/promises"; import https from 'https'; import querystring from 'querystring'; +import path from 'path'; /** - * Writes content to a file + * Writes content to a file, creating parent directories if needed */ export async function writeToFile(filename: string, content: string): Promise { try { + // Create parent directory if it doesn't exist + const dir = path.dirname(filename); + await mkdir(dir, { recursive: true }); + await writeFile(filename, content, "utf-8"); console.log(`File written successfully to ${filename}`); } catch (error) { @@ -76,3 +81,17 @@ export async function getAuthCookie(username: string, password: string): Promise * Sleep utility for rate limiting or polling delays */ export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +/** + * Load gene list from input file + * Filters out comments (lines starting with #) and empty lines + */ +export async function loadGeneList(): Promise { + const { readFile } = await import("fs/promises"); + const geneListPath = path.join(process.cwd(), "comparison/input/gene-list.txt"); + const content = await readFile(geneListPath, "utf-8"); + return content + .split("\n") + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith("#")); +} diff --git a/package.json b/package.json index a599362..8338165 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "node-check": "node -v", "comparison:fetch": "yarn build && node dist/comparison/scripts/fetch-summaries.js", "comparison:compare": "yarn build && node dist/comparison/scripts/compare-summaries.js", + "comparison:condense": "yarn build && node dist/comparison/scripts/condense-comparisons.js", "comparison:aggregate": "yarn build && node dist/comparison/scripts/aggregate-analysis.js" } } From 8db4dffaad9c29a51d18d517ec731fbfcb461e4f Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Sun, 19 Oct 2025 22:26:04 +0100 Subject: [PATCH 08/36] simplify condensation merging by swapping B-A to A-B in second input --- CLAUDE-ai-provider-comparison.md | 2 +- comparison/scripts/condense-comparisons.ts | 55 +++++++++++++++------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/CLAUDE-ai-provider-comparison.md b/CLAUDE-ai-provider-comparison.md index 2c4cfb8..500c5a5 100644 --- a/CLAUDE-ai-provider-comparison.md +++ b/CLAUDE-ai-provider-comparison.md @@ -73,7 +73,7 @@ This work will be done within the existing `expression-shepherd` repository, lev - Average unique observations/insights per model across all genes - Average quantitative mentions per model - Average deterministic metrics (word count, topic count, etc.) - - Position bias frequency (% of genes with detected bias) + - Position bias frequency (% of genes with AB/BA merge contradictions) - Feed all qualitative assessments into AI for pattern identification: - Consistent tone/style differences - Consistent technical detail level differences diff --git a/comparison/scripts/condense-comparisons.ts b/comparison/scripts/condense-comparisons.ts index e2c34ee..045bd1d 100644 --- a/comparison/scripts/condense-comparisons.ts +++ b/comparison/scripts/condense-comparisons.ts @@ -75,7 +75,7 @@ interface MergedQualitativeAssessment { tone_and_style: QualitativeCategory; technical_detail_level: QualitativeCategory; structure_and_organization: QualitativeCategory; - position_bias_detected: boolean; + contradiction_detected: boolean; merge_notes: string; } @@ -156,6 +156,30 @@ function summarizeBiologicalContent( // AI-Powered Merging // ============================================================================ +/** + * Swap summary_A and summary_B labels in an assessment + * Used to normalize the second assessment so both use the same labels + */ +function swapAssessmentLabels(assessment: QualitativeAssessment): QualitativeAssessment { + return { + tone_and_style: { + summary_A: assessment.tone_and_style.summary_B, + summary_B: assessment.tone_and_style.summary_A, + comparison: assessment.tone_and_style.comparison, + }, + technical_detail_level: { + summary_A: assessment.technical_detail_level.summary_B, + summary_B: assessment.technical_detail_level.summary_A, + comparison: assessment.technical_detail_level.comparison, + }, + structure_and_organization: { + summary_A: assessment.structure_and_organization.summary_B, + summary_B: assessment.structure_and_organization.summary_A, + comparison: assessment.structure_and_organization.comparison, + }, + }; +} + /** * Use AI to merge qualitative assessments from both directions */ @@ -165,27 +189,26 @@ async function mergeQualitativeAssessments( assessment2: QualitativeAssessment, anthropic: Anthropic ): Promise { + // Swap labels in assessment2 so both assessments use the same A/B labels + const assessment2_normalized = swapAssessmentLabels(assessment2); + const prompt = `You are merging two qualitative assessments of gene expression summaries for gene ${geneId}. -These assessments compared the same two summaries but in opposite presentation orders to detect position bias. +Both assessments evaluated the same two summaries (Summary A and Summary B). -Assessment 1 (Summary A presented first, Summary B presented second): +Assessment 1: \`\`\`json ${JSON.stringify(assessment1, null, 2)} \`\`\` -Assessment 2 (Summary B presented first, Summary A presented second): +Assessment 2: \`\`\`json -${JSON.stringify(assessment2, null, 2)} +${JSON.stringify(assessment2_normalized, null, 2)} \`\`\` -Note: In Assessment 2, the summary labels are reversed from Assessment 1 because the presentation order is swapped. When comparing: -- Assessment 1's "summary_A" refers to Summary A -- Assessment 2's "summary_A" refers to Summary B - -Your task: Synthesize these into a single merged assessment. If they largely agree, consolidate them. If they contradict significantly, note the contradiction and flag potential position bias. +Your task: Synthesize these into a single consolidated assessment. If they largely agree, merge them into a coherent summary. If they contradict, note the contradictions. -IMPORTANT: Do NOT refer to the summaries by any names other than "Summary A" and "Summary B". Do not use any identifying information about which AI model generated which summary. +IMPORTANT: Use only "Summary A" and "Summary B" labels. Respond with JSON in this format: \`\`\`json @@ -193,20 +216,20 @@ Respond with JSON in this format: "tone_and_style": { "summary_A": "consolidated description of Summary A's tone", "summary_B": "consolidated description of Summary B's tone", - "comparison": "merged comparison (use only 'Summary A' and 'Summary B' labels)" + "comparison": "merged comparison" }, "technical_detail_level": { "summary_A": "consolidated assessment of Summary A's detail level", "summary_B": "consolidated assessment of Summary B's detail level", - "comparison": "merged comparison (use only 'Summary A' and 'Summary B' labels)" + "comparison": "merged comparison" }, "structure_and_organization": { "summary_A": "consolidated assessment of Summary A's structure", "summary_B": "consolidated assessment of Summary B's structure", - "comparison": "merged comparison (use only 'Summary A' and 'Summary B' labels)" + "comparison": "merged comparison" }, - "position_bias_detected": false, - "merge_notes": "Brief notes on consistency or any contradictions found" + "contradiction_detected": true or false (set to true if assessments contradict significantly), + "merge_notes": "Brief notes on consistency or contradictions" } \`\`\` From aca3cabf58d71e36362b53b61ddc841787af3a65 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Mon, 20 Oct 2025 09:21:50 +0100 Subject: [PATCH 09/36] types consolidation --- comparison/scripts/compare-summaries.ts | 130 ++-------------- comparison/scripts/condense-comparisons.ts | 121 ++------------- comparison/scripts/fetch-summaries.ts | 22 +-- comparison/scripts/types.ts | 169 +++++++++++++++++++++ 4 files changed, 198 insertions(+), 244 deletions(-) create mode 100644 comparison/scripts/types.ts diff --git a/comparison/scripts/compare-summaries.ts b/comparison/scripts/compare-summaries.ts index 15a9816..0a40dc8 100644 --- a/comparison/scripts/compare-summaries.ts +++ b/comparison/scripts/compare-summaries.ts @@ -3,122 +3,20 @@ import Anthropic from "@anthropic-ai/sdk"; import { readFile } from "fs/promises"; import path from "path"; import { writeToFile, stripMarkdownCodeBlocks, loadGeneList } from "./shared-utils"; - -// ============================================================================ -// Type Definitions -// ============================================================================ - -interface ExperimentSummary { - assay_type: string; - experiment_name: string; - notes: string; - one_sentence_summary: string; - confidence: string | number; - dataset_id: string; - experiment_keywords: string[]; - biological_importance: string | number; -} - -interface Topic { - one_sentence_summary: string; - headline: string; - summaries: ExperimentSummary[]; -} - -interface ExpressionSummary { - one_paragraph_summary: string; - headline: string; - topics: Topic[]; -} - -interface SimplifiedTopic { - one_sentence_summary: string; - headline: string; - experiment_names: string[]; -} - -interface SimplifiedSummary { - one_paragraph_summary: string; - headline: string; - topics: SimplifiedTopic[]; -} - -interface DeterministicMetrics { - character_count: number; - word_count: number; - sentence_count: number; - paragraph_count: number; - topic_count: number; - has_bullets: boolean; - average_sentence_length: number; -} - -interface BiologicalContent { - observations: { - only_in_A: string[]; - only_in_B: string[]; - in_both: string[]; - }; - insights: { - only_in_A: string[]; - only_in_B: string[]; - in_both: string[]; - }; -} - -interface QualitativeAssessment { - tone_and_style: { - summary_A: string; - summary_B: string; - comparison: string; - }; - technical_detail_level: { - summary_A: string; - summary_B: string; - comparison: string; - }; - structure_and_organization: { - summary_A: string; - summary_B: string; - comparison: string; - }; -} - -interface QuantitativeMentions { - summary_A: number; - summary_B: number; -} - -interface ComparisonResult { - model_A: string; - model_B: string; - gene_id: string; - biological_content: BiologicalContent; - qualitative_assessment: QualitativeAssessment; - deterministic_metrics: { - summary_A: DeterministicMetrics; - summary_B: DeterministicMetrics; - }; - quantitative_expression_mentions: QuantitativeMentions; - token_usage: { - input_tokens: number; - output_tokens: number; - total_tokens: number; - }; -} - -interface SiteConfig { - name: string; - hostname: string; - appPath: string; - model: string; -} - -interface Config { - sites: SiteConfig[]; - endpoint: string; - projectId: string; -} +import type { + Config, + SiteConfig, + ExperimentSummary, + Topic, + ExpressionSummary, + SimplifiedTopic, + SimplifiedSummary, + DeterministicMetrics, + BiologicalContent, + QualitativeAssessment, + QuantitativeMentions, + ComparisonResult, +} from "./types"; // ============================================================================ // Deterministic Metrics Calculation diff --git a/comparison/scripts/condense-comparisons.ts b/comparison/scripts/condense-comparisons.ts index 045bd1d..1fd4052 100644 --- a/comparison/scripts/condense-comparisons.ts +++ b/comparison/scripts/condense-comparisons.ts @@ -3,113 +3,20 @@ import Anthropic from "@anthropic-ai/sdk"; import { readFile } from "fs/promises"; import path from "path"; import { writeToFile, stripMarkdownCodeBlocks, loadGeneList } from "./shared-utils"; - -// ============================================================================ -// Type Definitions -// ============================================================================ - -interface BiologicalContentCounts { - only_in_A: string[]; - only_in_B: string[]; - in_both: string[]; -} - -interface BiologicalContent { - observations: BiologicalContentCounts; - insights: BiologicalContentCounts; -} - -interface QualitativeCategory { - summary_A: string; - summary_B: string; - comparison: string; -} - -interface QualitativeAssessment { - tone_and_style: QualitativeCategory; - technical_detail_level: QualitativeCategory; - structure_and_organization: QualitativeCategory; -} - -interface DeterministicMetrics { - character_count: number; - word_count: number; - sentence_count: number; - paragraph_count: number; - topic_count: number; - has_bullets: boolean; - average_sentence_length: number; -} - -interface QuantitativeMentions { - summary_A: number; - summary_B: number; -} - -interface ComparisonResult { - model_A: string; - model_B: string; - gene_id: string; - biological_content: BiologicalContent; - qualitative_assessment: QualitativeAssessment; - deterministic_metrics: { - summary_A: DeterministicMetrics; - summary_B: DeterministicMetrics; - }; - quantitative_expression_mentions: QuantitativeMentions; - token_usage: { - input_tokens: number; - output_tokens: number; - total_tokens: number; - }; -} - -interface BiologicalContentSummary { - avg_unique_to_model_A: number; - avg_unique_to_model_B: number; - avg_shared: number; - position_variance: number; -} - -interface MergedQualitativeAssessment { - tone_and_style: QualitativeCategory; - technical_detail_level: QualitativeCategory; - structure_and_organization: QualitativeCategory; - contradiction_detected: boolean; - merge_notes: string; -} - -interface CondensedComparison { - gene_id: string; - model_A: string; - model_B: string; - biological_content_summary: { - observations: BiologicalContentSummary; - insights: BiologicalContentSummary; - }; - qualitative_assessment: MergedQualitativeAssessment; - deterministic_metrics: { - model_A: DeterministicMetrics; - model_B: DeterministicMetrics; - }; - quantitative_mentions: { - avg_model_A: number; - avg_model_B: number; - }; -} - -interface SiteConfig { - name: string; - hostname: string; - appPath: string; - model: string; -} - -interface Config { - sites: SiteConfig[]; - endpoint: string; - projectId: string; -} +import type { + Config, + SiteConfig, + BiologicalContentCounts, + BiologicalContent, + QualitativeCategory, + QualitativeAssessment, + DeterministicMetrics, + QuantitativeMentions, + ComparisonResult, + BiologicalContentSummary, + MergedQualitativeAssessment, + CondensedComparison, +} from "./types"; // ============================================================================ // Calculation Functions diff --git a/comparison/scripts/fetch-summaries.ts b/comparison/scripts/fetch-summaries.ts index b103e9b..93b4522 100644 --- a/comparison/scripts/fetch-summaries.ts +++ b/comparison/scripts/fetch-summaries.ts @@ -3,27 +3,7 @@ import axios from "axios"; import { readFile } from "fs/promises"; import path from "path"; import { writeToFile, getAuthCookie, sleep, loadGeneList } from "./shared-utils"; - -interface SiteConfig { - name: string; - hostname: string; - appPath: string; - model: string; - skipFetch?: boolean; -} - -interface Config { - sites: SiteConfig[]; - endpoint: string; - projectId: string; -} - -interface FetchResult { - geneId: string; - site: string; - status: "success" | "failed"; - error?: string; -} +import type { SiteConfig, Config, FetchResult } from "./types"; const POLL_INTERVAL_MS = 5000; // 5 seconds const MAX_RETRIES = 3; diff --git a/comparison/scripts/types.ts b/comparison/scripts/types.ts new file mode 100644 index 0000000..88bf0b3 --- /dev/null +++ b/comparison/scripts/types.ts @@ -0,0 +1,169 @@ +// ============================================================================ +// Configuration Types +// ============================================================================ + +export interface SiteConfig { + name: string; + hostname: string; + appPath: string; + model: string; + skipFetch?: boolean; +} + +export interface Config { + sites: SiteConfig[]; + endpoint: string; + projectId: string; +} + +// ============================================================================ +// Expression Summary Types +// ============================================================================ + +export interface ExperimentSummary { + assay_type: string; + experiment_name: string; + notes: string; + one_sentence_summary: string; + confidence: string | number; + dataset_id: string; + experiment_keywords: string[]; + biological_importance: string | number; +} + +export interface Topic { + one_sentence_summary: string; + headline: string; + summaries: ExperimentSummary[]; +} + +export interface ExpressionSummary { + one_paragraph_summary: string; + headline: string; + topics: Topic[]; +} + +export interface SimplifiedTopic { + one_sentence_summary: string; + headline: string; + experiment_names: string[]; +} + +export interface SimplifiedSummary { + one_paragraph_summary: string; + headline: string; + topics: SimplifiedTopic[]; +} + +// ============================================================================ +// Metrics Types +// ============================================================================ + +export interface DeterministicMetrics { + character_count: number; + word_count: number; + sentence_count: number; + paragraph_count: number; + topic_count: number; + has_bullets: boolean; + average_sentence_length: number; +} + +// ============================================================================ +// Comparison Types +// ============================================================================ + +export interface BiologicalContentCounts { + only_in_A: string[]; + only_in_B: string[]; + in_both: string[]; +} + +export interface BiologicalContent { + observations: BiologicalContentCounts; + insights: BiologicalContentCounts; +} + +export interface QualitativeCategory { + summary_A: string; + summary_B: string; + comparison: string; +} + +export interface QualitativeAssessment { + tone_and_style: QualitativeCategory; + technical_detail_level: QualitativeCategory; + structure_and_organization: QualitativeCategory; +} + +export interface QuantitativeMentions { + summary_A: number; + summary_B: number; +} + +export interface ComparisonResult { + model_A: string; + model_B: string; + gene_id: string; + biological_content: BiologicalContent; + qualitative_assessment: QualitativeAssessment; + deterministic_metrics: { + summary_A: DeterministicMetrics; + summary_B: DeterministicMetrics; + }; + quantitative_expression_mentions: QuantitativeMentions; + token_usage: { + input_tokens: number; + output_tokens: number; + total_tokens: number; + }; +} + +// ============================================================================ +// Condensation Types +// ============================================================================ + +export interface BiologicalContentSummary { + avg_unique_to_model_A: number; + avg_unique_to_model_B: number; + avg_shared: number; + position_variance: number; +} + +export interface MergedQualitativeAssessment { + tone_and_style: QualitativeCategory; + technical_detail_level: QualitativeCategory; + structure_and_organization: QualitativeCategory; + contradiction_detected: boolean; + merge_notes: string; +} + +export interface CondensedComparison { + gene_id: string; + model_A: string; + model_B: string; + biological_content_summary: { + observations: BiologicalContentSummary; + insights: BiologicalContentSummary; + }; + qualitative_assessment: MergedQualitativeAssessment; + deterministic_metrics: { + model_A: DeterministicMetrics; + model_B: DeterministicMetrics; + }; + quantitative_mentions: { + avg_model_A: number; + avg_model_B: number; + }; +} + +// ============================================================================ +// Fetch Types +// ============================================================================ + +export interface FetchResult { + geneId: string; + site: string; + status: "success" | "failed"; + error?: string; +} From 12e9376fbb6583b20ac1270280cb17351e0dab09 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Mon, 20 Oct 2025 13:24:27 +0100 Subject: [PATCH 10/36] aggregate phase 3 --- comparison/input/gene-list.txt | 7 +- comparison/scripts/aggregate-analysis.ts | 559 ++++++ comparison/scripts/condense-comparisons.ts | 27 +- comparison/scripts/types.ts | 95 + package.json | 1 + yarn.lock | 1983 +++++++++++++++++++- 6 files changed, 2658 insertions(+), 14 deletions(-) create mode 100644 comparison/scripts/aggregate-analysis.ts diff --git a/comparison/input/gene-list.txt b/comparison/input/gene-list.txt index fca622e..19074f5 100644 --- a/comparison/input/gene-list.txt +++ b/comparison/input/gene-list.txt @@ -1,5 +1,4 @@ -#AGAP001212 +AGAP001212 AGAP000693 -#AGAP000999 -#AGAP009551 -#AGAP006268 +AGAP000999 +AGAP009551 diff --git a/comparison/scripts/aggregate-analysis.ts b/comparison/scripts/aggregate-analysis.ts new file mode 100644 index 0000000..2488888 --- /dev/null +++ b/comparison/scripts/aggregate-analysis.ts @@ -0,0 +1,559 @@ +import "dotenv/config"; +import Anthropic from "@anthropic-ai/sdk"; +import { readFile } from "fs/promises"; +import path from "path"; +import ttest2 from "@stdlib/stats-ttest2"; +import { writeToFile, stripMarkdownCodeBlocks, loadGeneList } from "./shared-utils"; +import type { + Config, + CondensedComparison, + StatisticalMetric, + DescriptiveMetric, + BiologicalContentAggregate, + BiologicalContentMetric, + DeterministicMetricsAggregate, + PositionBiasAggregate, + QuantitativeAggregates, + QualitativeAggregates, + QualitativeFieldAggregate, + QualitativeDimensionAggregate, + AggregateReport, + ConsistencyScore, + AgreementDistribution, +} from "./types"; + +// ============================================================================ +// Statistical Helper Functions +// ============================================================================ + +/** + * Calculate mean of an array of numbers + */ +function mean(values: number[]): number { + return values.reduce((sum, val) => sum + val, 0) / values.length; +} + +/** + * Calculate standard deviation of an array of numbers + */ +function stdDev(values: number[]): number { + const avg = mean(values); + const squareDiffs = values.map((value) => Math.pow(value - avg, 2)); + const avgSquareDiff = mean(squareDiffs); + return Math.sqrt(avgSquareDiff); +} + +/** + * Calculate standard error + */ +function stdErr(values: number[]): number { + return stdDev(values) / Math.sqrt(values.length); +} + +/** + * Compute descriptive statistics (mean and standard error only) + */ +function computeDescriptiveMetric(values: number[]): DescriptiveMetric { + return { + mean: mean(values), + std_err: stdErr(values), + }; +} + +/** + * Generate summary string for statistical comparison + */ +function generateStatSummary(meanA: number, meanB: number, pValue: number): string { + const pRounded = pValue.toFixed(3); + + if (pValue < 0.05) { + if (meanA > meanB) { + return `Model A significantly higher (p=${pRounded})`; + } else { + return `Model B significantly higher (p=${pRounded})`; + } + } else { + return `No significant difference (p=${pRounded})`; + } +} + +/** + * Perform t-test and return StatisticalMetric + */ +function computeStatisticalMetric(valuesA: number[], valuesB: number[]): StatisticalMetric { + const meanA = mean(valuesA); + const meanB = mean(valuesB); + const stdErrA = stdErr(valuesA); + const stdErrB = stdErr(valuesB); + + const result = ttest2(valuesA, valuesB); + const pValue = result.pValue; + + return { + summary: generateStatSummary(meanA, meanB, pValue), + mean_A: meanA, + std_err_A: stdErrA, + mean_B: meanB, + std_err_B: stdErrB, + t_test_pval: pValue, + }; +} + +// ============================================================================ +// File Operations +// ============================================================================ + +/** + * Load site configuration + */ +async function loadConfig(): Promise { + const configPath = path.join(process.cwd(), "comparison/config/sites.json"); + const configContent = await readFile(configPath, "utf-8"); + return JSON.parse(configContent); +} + +/** + * Load all condensed comparisons for a model pair + */ +async function loadCondensedComparisons( + geneIds: string[], + modelA: string, + modelB: string +): Promise { + const comparisons: CondensedComparison[] = []; + + for (const geneId of geneIds) { + const filePath = path.join( + process.cwd(), + `comparison/data/condensed/${geneId}/${modelA}-${modelB}.json` + ); + const content = await readFile(filePath, "utf-8"); + comparisons.push(JSON.parse(content)); + } + + return comparisons; +} + +// ============================================================================ +// Quantitative Aggregation Functions +// ============================================================================ + +/** + * Aggregate biological content statistics (descriptive only, no t-tests) + */ +function aggregateBiologicalContent( + comparisons: CondensedComparison[] +): BiologicalContentAggregate { + // Observations + const obsUniqueA = comparisons.map((c) => c.biological_content_summary.observations.avg_unique_to_model_A); + const obsUniqueB = comparisons.map((c) => c.biological_content_summary.observations.avg_unique_to_model_B); + const obsShared = comparisons.map((c) => c.biological_content_summary.observations.avg_shared); + const obsPosVar = comparisons.map((c) => c.biological_content_summary.observations.position_variance); + + // Insights + const insUniqueA = comparisons.map((c) => c.biological_content_summary.insights.avg_unique_to_model_A); + const insUniqueB = comparisons.map((c) => c.biological_content_summary.insights.avg_unique_to_model_B); + const insShared = comparisons.map((c) => c.biological_content_summary.insights.avg_shared); + const insPosVar = comparisons.map((c) => c.biological_content_summary.insights.position_variance); + + return { + observations: { + avg_unique_to_model_A: computeDescriptiveMetric(obsUniqueA), + avg_unique_to_model_B: computeDescriptiveMetric(obsUniqueB), + avg_shared: computeDescriptiveMetric(obsShared), + avg_position_variance: computeDescriptiveMetric(obsPosVar), + }, + insights: { + avg_unique_to_model_A: computeDescriptiveMetric(insUniqueA), + avg_unique_to_model_B: computeDescriptiveMetric(insUniqueB), + avg_shared: computeDescriptiveMetric(insShared), + avg_position_variance: computeDescriptiveMetric(insPosVar), + }, + }; +} + +/** + * Aggregate deterministic metrics + */ +function aggregateDeterministicMetrics( + comparisons: CondensedComparison[] +): DeterministicMetricsAggregate { + // Collect all metric values + const wordCountA = comparisons.map((c) => c.deterministic_metrics.model_A.word_count); + const wordCountB = comparisons.map((c) => c.deterministic_metrics.model_B.word_count); + + const topicCountA = comparisons.map((c) => c.deterministic_metrics.model_A.topic_count); + const topicCountB = comparisons.map((c) => c.deterministic_metrics.model_B.topic_count); + + const sentenceCountA = comparisons.map((c) => c.deterministic_metrics.model_A.sentence_count); + const sentenceCountB = comparisons.map((c) => c.deterministic_metrics.model_B.sentence_count); + + const charCountA = comparisons.map((c) => c.deterministic_metrics.model_A.character_count); + const charCountB = comparisons.map((c) => c.deterministic_metrics.model_B.character_count); + + const paraCountA = comparisons.map((c) => c.deterministic_metrics.model_A.paragraph_count); + const paraCountB = comparisons.map((c) => c.deterministic_metrics.model_B.paragraph_count); + + const avgSentLengthA = comparisons.map((c) => c.deterministic_metrics.model_A.average_sentence_length); + const avgSentLengthB = comparisons.map((c) => c.deterministic_metrics.model_B.average_sentence_length); + + // Calculate bullets percentage + const bulletsA = comparisons.filter((c) => c.deterministic_metrics.model_A.has_bullets).length; + const bulletsB = comparisons.filter((c) => c.deterministic_metrics.model_B.has_bullets).length; + const percentA = (bulletsA / comparisons.length) * 100; + const percentB = (bulletsB / comparisons.length) * 100; + + return { + word_count: computeStatisticalMetric(wordCountA, wordCountB), + topic_count: computeStatisticalMetric(topicCountA, topicCountB), + sentence_count: computeStatisticalMetric(sentenceCountA, sentenceCountB), + character_count: computeStatisticalMetric(charCountA, charCountB), + paragraph_count: computeStatisticalMetric(paraCountA, paraCountB), + average_sentence_length: computeStatisticalMetric(avgSentLengthA, avgSentLengthB), + has_bullets_percent: { + percent_A: percentA, + percent_B: percentB, + note: "Percentage of summaries using bullets (not t-testable)", + }, + }; +} + +/** + * Aggregate quantitative mentions + */ +function aggregateQuantitativeMentions(comparisons: CondensedComparison[]): StatisticalMetric { + const mentionsA = comparisons.map((c) => c.quantitative_mentions.avg_model_A); + const mentionsB = comparisons.map((c) => c.quantitative_mentions.avg_model_B); + + return computeStatisticalMetric(mentionsA, mentionsB); +} + +/** + * Aggregate position bias + */ +function aggregatePositionBias(comparisons: CondensedComparison[]): PositionBiasAggregate { + const genesWithContradictions = comparisons + .filter((c) => c.qualitative_assessment.contradiction_detected) + .map((c) => c.gene_id); + + const contradictionRate = (genesWithContradictions.length / comparisons.length) * 100; + + return { + contradiction_rate_percent: contradictionRate, + genes_with_contradictions: genesWithContradictions, + }; +} + +/** + * Aggregate all quantitative data + */ +function aggregateQuantitativeData(comparisons: CondensedComparison[]): QuantitativeAggregates { + return { + biological_content: aggregateBiologicalContent(comparisons), + deterministic_metrics: aggregateDeterministicMetrics(comparisons), + quantitative_mentions: aggregateQuantitativeMentions(comparisons), + position_bias: aggregatePositionBias(comparisons), + }; +} + +// ============================================================================ +// AI-Powered Qualitative Aggregation +// ============================================================================ + +/** + * Calculate consistency score from agreement distribution + */ +function calculateConsistencyScore(distribution: AgreementDistribution): ConsistencyScore { + const total = + distribution.strong_agreement.length + + distribution.mild_agreement.length + + distribution.neutral_mixed.length + + distribution.mild_disagreement.length + + distribution.strong_disagreement.length; + + const agreementCount = distribution.strong_agreement.length + distribution.mild_agreement.length; + const agreementPercent = (agreementCount / total) * 100; + + if (agreementPercent >= 80) { + return "High consistency"; + } else if (agreementPercent >= 60) { + return "Moderate consistency"; + } else { + return "Low consistency"; + } +} + +/** + * Use AI to aggregate qualitative assessments for a single field + */ +async function aggregateQualitativeField( + dimension: string, + fieldType: "summary_A" | "summary_B" | "comparison", + statements: Map, + anthropic: Anthropic +): Promise { + // Build statements list for prompt + const statementsList = Array.from(statements.entries()) + .map(([geneId, statement]) => `${geneId}: ${statement}`) + .join("\n\n"); + + const prompt = `You are aggregating assessments of the **${dimension}** dimension from ${statements.size} different genes. + +Here are the ${statements.size} statements: + +${statementsList} + +Your task: +1. Generate a consensus summary that synthesizes these statements into a coherent assessment (similar length and style to the inputs) +2. Categorize each gene by how well it agrees with the consensus: + - Strong agreement: Fully aligned with consensus + - Mild agreement: Mostly aligned, minor variations + - Neutral/mixed: Neither clearly agrees nor disagrees + - Mild disagreement: Some contradictions + - Strong disagreement: Major contradictions +3. Describe the main axes of disagreement (if any) + +Respond with JSON in this format: +\`\`\`json +{ + "consensus_summary": "Your synthesized consensus statement here", + "agreement_distribution": { + "strong_agreement": ["AGAP000693", "AGAP001212"], + "mild_agreement": ["AGAP000999"], + "neutral_mixed": [], + "mild_disagreement": [], + "strong_disagreement": [] + }, + "disagreement_analysis": "Description of main disagreement axes, or 'No significant disagreement' if largely consistent" +} +\`\`\` + +Respond ONLY with valid JSON, no other text.`; + + const message = await anthropic.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 2000, + messages: [ + { + role: "user", + content: prompt, + }, + ], + }); + + const responseText = + message.content[0].type === "text" ? message.content[0].text : JSON.stringify(message.content[0]); + + const cleanedResponse = stripMarkdownCodeBlocks(responseText); + + try { + const parsed = JSON.parse(cleanedResponse); + const consistency_score = calculateConsistencyScore(parsed.agreement_distribution); + + return { + consensus_summary: parsed.consensus_summary, + consistency_score, + agreement_distribution: parsed.agreement_distribution, + disagreement_analysis: parsed.disagreement_analysis, + }; + } catch (error) { + console.error("Failed to parse AI response:", cleanedResponse); + throw new Error(`Failed to parse AI response: ${error}`); + } +} + +/** + * Aggregate qualitative assessments for one dimension + */ +async function aggregateQualitativeDimension( + dimension: "tone_and_style" | "technical_detail_level" | "structure_and_organization", + comparisons: CondensedComparison[], + anthropic: Anthropic +): Promise { + console.log(` Aggregating ${dimension}...`); + + // Extract statements for each field + const summaryA_statements = new Map(); + const summaryB_statements = new Map(); + const comparison_statements = new Map(); + + for (const comp of comparisons) { + const assessment = comp.qualitative_assessment[dimension]; + summaryA_statements.set(comp.gene_id, assessment.summary_A); + summaryB_statements.set(comp.gene_id, assessment.summary_B); + comparison_statements.set(comp.gene_id, assessment.comparison); + } + + // Aggregate each field with AI + const [summary_A, summary_B, comparison] = await Promise.all([ + aggregateQualitativeField(dimension, "summary_A", summaryA_statements, anthropic), + aggregateQualitativeField(dimension, "summary_B", summaryB_statements, anthropic), + aggregateQualitativeField(dimension, "comparison", comparison_statements, anthropic), + ]); + + return { + summary_A, + summary_B, + comparison, + }; +} + +/** + * Aggregate all qualitative data + */ +async function aggregateQualitativeData( + comparisons: CondensedComparison[], + anthropic: Anthropic +): Promise { + console.log("\nAggregating qualitative assessments..."); + + const tone_and_style = await aggregateQualitativeDimension("tone_and_style", comparisons, anthropic); + const technical_detail_level = await aggregateQualitativeDimension( + "technical_detail_level", + comparisons, + anthropic + ); + const structure_and_organization = await aggregateQualitativeDimension( + "structure_and_organization", + comparisons, + anthropic + ); + + return { + tone_and_style, + technical_detail_level, + structure_and_organization, + }; +} + +// ============================================================================ +// Main Execution +// ============================================================================ + +/** + * Get sorted model pair name (alphabetically) + */ +function getSortedPair(modelA: string, modelB: string): [string, string] { + return modelA < modelB ? [modelA, modelB] : [modelB, modelA]; +} + +/** + * Process one model pair and generate aggregate report + */ +async function processModelPair( + modelA: string, + modelB: string, + geneIds: string[], + anthropic: Anthropic +): Promise { + console.log(`\nProcessing model pair: ${modelA} <-> ${modelB}`); + console.log("=".repeat(60)); + + // Load all condensed comparisons + console.log("Loading condensed comparisons..."); + const comparisons = await loadCondensedComparisons(geneIds, modelA, modelB); + console.log(`Loaded ${comparisons.length} comparisons`); + + // Aggregate quantitative data + console.log("\nAggregating quantitative data..."); + const quantitative_aggregates = aggregateQuantitativeData(comparisons); + + // Aggregate qualitative data (with AI) + const qualitative_aggregates = await aggregateQualitativeData(comparisons, anthropic); + + // Build report + const report: AggregateReport = { + model_pair: { + model_A: modelA, + model_B: modelB, + }, + gene_count: comparisons.length, + quantitative_aggregates, + qualitative_aggregates, + }; + + // Save report + const outputPath = path.join( + process.cwd(), + `comparison/data/aggregate-reports/${modelA}-${modelB}-report.json` + ); + await writeToFile(outputPath, JSON.stringify(report, null, 2)); + console.log(`\nReport saved to: ${outputPath}`); +} + +/** + * Main execution + */ +async function main() { + console.log("=".repeat(60)); + console.log("AGGREGATE ANALYSIS"); + console.log("=".repeat(60)); + + // Load configuration + console.log("\nLoading configuration..."); + const config = await loadConfig(); + const modelNames = config.sites.map((s) => s.name); + console.log(`Found ${modelNames.length} models: ${modelNames.join(", ")}`); + + // Load gene list + console.log("\nLoading gene list..."); + const geneIds = await loadGeneList(); + console.log(`Found ${geneIds.length} genes to aggregate`); + + if (geneIds.length === 0) { + console.error("No genes found in gene-list.txt. Please add gene IDs (one per line)."); + process.exit(1); + } + + // Initialize Anthropic client + const apiKey = process.env.ANTHROPIC_API_KEY; + if (!apiKey) { + console.error("Missing ANTHROPIC_API_KEY in .env file"); + process.exit(1); + } + const anthropic = new Anthropic({ apiKey }); + + // Generate all unique model pairs (alphabetically sorted) + const pairs: Array<[string, string]> = []; + for (let i = 0; i < modelNames.length; i++) { + for (let j = i + 1; j < modelNames.length; j++) { + const [sortedA, sortedB] = getSortedPair(modelNames[i], modelNames[j]); + pairs.push([sortedA, sortedB]); + } + } + + console.log(`\nWill process ${pairs.length} model pairs:`); + pairs.forEach(([a, b]) => console.log(` - ${a} <-> ${b}`)); + + let successCount = 0; + let errorCount = 0; + + // Process each model pair + for (const [modelA, modelB] of pairs) { + try { + await processModelPair(modelA, modelB, geneIds, anthropic); + successCount++; + } catch (error) { + console.error(`\nERROR processing ${modelA} <-> ${modelB}:`, error instanceof Error ? error.message : error); + errorCount++; + } + } + + // Summary + console.log("\n" + "=".repeat(60)); + console.log("SUMMARY"); + console.log("=".repeat(60)); + console.log(`Total model pairs: ${pairs.length}`); + console.log(`Successful: ${successCount}`); + console.log(`Failed: ${errorCount}`); + + if (errorCount > 0) { + process.exit(1); + } +} + +// Run the script +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); +}); diff --git a/comparison/scripts/condense-comparisons.ts b/comparison/scripts/condense-comparisons.ts index 1fd4052..dd93d55 100644 --- a/comparison/scripts/condense-comparisons.ts +++ b/comparison/scripts/condense-comparisons.ts @@ -63,6 +63,15 @@ function summarizeBiologicalContent( // AI-Powered Merging // ============================================================================ +/** + * Swap "Summary A" and "Summary B" text references within a string + */ +function swapSummaryLabelsInText(text: string): string { + return text.replace(/Summary ([AB])/g, (match, letter) => { + return letter === 'A' ? 'Summary B' : 'Summary A'; + }); +} + /** * Swap summary_A and summary_B labels in an assessment * Used to normalize the second assessment so both use the same labels @@ -70,19 +79,19 @@ function summarizeBiologicalContent( function swapAssessmentLabels(assessment: QualitativeAssessment): QualitativeAssessment { return { tone_and_style: { - summary_A: assessment.tone_and_style.summary_B, - summary_B: assessment.tone_and_style.summary_A, - comparison: assessment.tone_and_style.comparison, + summary_A: swapSummaryLabelsInText(assessment.tone_and_style.summary_B), + summary_B: swapSummaryLabelsInText(assessment.tone_and_style.summary_A), + comparison: swapSummaryLabelsInText(assessment.tone_and_style.comparison), }, technical_detail_level: { - summary_A: assessment.technical_detail_level.summary_B, - summary_B: assessment.technical_detail_level.summary_A, - comparison: assessment.technical_detail_level.comparison, + summary_A: swapSummaryLabelsInText(assessment.technical_detail_level.summary_B), + summary_B: swapSummaryLabelsInText(assessment.technical_detail_level.summary_A), + comparison: swapSummaryLabelsInText(assessment.technical_detail_level.comparison), }, structure_and_organization: { - summary_A: assessment.structure_and_organization.summary_B, - summary_B: assessment.structure_and_organization.summary_A, - comparison: assessment.structure_and_organization.comparison, + summary_A: swapSummaryLabelsInText(assessment.structure_and_organization.summary_B), + summary_B: swapSummaryLabelsInText(assessment.structure_and_organization.summary_A), + comparison: swapSummaryLabelsInText(assessment.structure_and_organization.comparison), }, }; } diff --git a/comparison/scripts/types.ts b/comparison/scripts/types.ts index 88bf0b3..a8b177b 100644 --- a/comparison/scripts/types.ts +++ b/comparison/scripts/types.ts @@ -167,3 +167,98 @@ export interface FetchResult { status: "success" | "failed"; error?: string; } + +// ============================================================================ +// Aggregate Analysis Types +// ============================================================================ + +export interface StatisticalMetric { + summary: string; // e.g., "Model A significantly higher (p=0.003)" or "No significant difference (p=0.342)" + mean_A: number; + std_err_A: number; + mean_B: number; + std_err_B: number; + t_test_pval: number; +} + +export interface DescriptiveMetric { + mean: number; + std_err: number; +} + +export interface BiologicalContentMetric { + avg_unique_to_model_A: DescriptiveMetric; + avg_unique_to_model_B: DescriptiveMetric; + avg_shared: DescriptiveMetric; + avg_position_variance: DescriptiveMetric; +} + +export interface BiologicalContentAggregate { + observations: BiologicalContentMetric; + insights: BiologicalContentMetric; +} + +export interface DeterministicMetricsAggregate { + word_count: StatisticalMetric; + topic_count: StatisticalMetric; + sentence_count: StatisticalMetric; + character_count: StatisticalMetric; + paragraph_count: StatisticalMetric; + average_sentence_length: StatisticalMetric; + has_bullets_percent: { + percent_A: number; + percent_B: number; + note: string; + }; +} + +export interface PositionBiasAggregate { + contradiction_rate_percent: number; + genes_with_contradictions: string[]; +} + +export interface AgreementDistribution { + strong_agreement: string[]; + mild_agreement: string[]; + neutral_mixed: string[]; + mild_disagreement: string[]; + strong_disagreement: string[]; +} + +export type ConsistencyScore = "High consistency" | "Moderate consistency" | "Low consistency"; + +export interface QualitativeFieldAggregate { + consensus_summary: string; + consistency_score: ConsistencyScore; + agreement_distribution: AgreementDistribution; + disagreement_analysis: string; +} + +export interface QualitativeDimensionAggregate { + summary_A: QualitativeFieldAggregate; + summary_B: QualitativeFieldAggregate; + comparison: QualitativeFieldAggregate; +} + +export interface QualitativeAggregates { + tone_and_style: QualitativeDimensionAggregate; + technical_detail_level: QualitativeDimensionAggregate; + structure_and_organization: QualitativeDimensionAggregate; +} + +export interface QuantitativeAggregates { + biological_content: BiologicalContentAggregate; + deterministic_metrics: DeterministicMetricsAggregate; + quantitative_mentions: StatisticalMetric; + position_bias: PositionBiasAggregate; +} + +export interface AggregateReport { + model_pair: { + model_A: string; + model_B: string; + }; + gene_count: number; + quantitative_aggregates: QuantitativeAggregates; + qualitative_aggregates: QualitativeAggregates; +} diff --git a/package.json b/package.json index 8338165..5b14a85 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.56.0", + "@stdlib/stats-ttest2": "^0.2.2", "axios": "^1.8.4", "dotenv": "^16.4.7", "exceljs": "^4.4.0", diff --git a/yarn.lock b/yarn.lock index c57f7b6..714285c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -57,6 +57,1975 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@stdlib/array-base-filled@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/array-base-filled/-/array-base-filled-0.2.2.tgz#07571a36f9997441048147af8c1a32d04ee4398b" + integrity sha512-T7nB7dni5Y4/nsq6Gc1bAhYfzJbcOdqsmVZJUI698xpDbhCdVCIIaEbf0PnDMGN24psN+5mgAVmnNBom+uF0Xg== + +"@stdlib/array-base-zeros@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/array-base-zeros/-/array-base-zeros-0.2.2.tgz#552bbcdf879ec78856d84f89e157eb6a3a71c89d" + integrity sha512-iwxqaEtpi4c2qpqabmhFdaQGkzgo5COwjHPn2T0S0wfJuM1VuVl5UBl15syr+MmZPJQOB1eBbh6F1uTh9597qw== + dependencies: + "@stdlib/array-base-filled" "^0.2.1" + +"@stdlib/array-float32@^0.2.1", "@stdlib/array-float32@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/array-float32/-/array-float32-0.2.2.tgz#88dcbb6cb138da3f3b4bc565423a0afc4dec4e1b" + integrity sha512-pTcy1FNQrrJLL1LMxJjuVpcKJaibbGCFFTe41iCSXpSOC8SuTBuNohrO6K9+xR301Ruxxn4yrzjJJ6Fa3nQJ2g== + dependencies: + "@stdlib/assert-has-float32array-support" "^0.2.2" + +"@stdlib/array-float64@^0.2.1", "@stdlib/array-float64@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/array-float64/-/array-float64-0.2.2.tgz#66b3a6fd0e030da1b3d9ba195b865791486ec3a7" + integrity sha512-ZmV5wcacGrhT0maw9dfLXNv4N3ZwFUV3D7ItFfZFGFnKIJbubrWzwtaYnxzIXigrDc8g3F6FVHRpsQLMxq0/lA== + dependencies: + "@stdlib/assert-has-float64array-support" "^0.2.2" + +"@stdlib/array-uint16@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/array-uint16/-/array-uint16-0.2.2.tgz#d9647ec67f86dcb032b4e72659df818874498959" + integrity sha512-z5c/Izw43HkKfb1pTgEUMAS8GFvhtHkkHZSjX3XJN+17P0VjknxjlSvPiCBGqaDX9jXtlWH3mn1LSyDKtJQoeA== + dependencies: + "@stdlib/assert-has-uint16array-support" "^0.2.2" + +"@stdlib/array-uint32@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/array-uint32/-/array-uint32-0.2.2.tgz#0e772f971706e7060fa1878f81b0fe05b86c8f07" + integrity sha512-3T894I9C2MqZJJmRCYFTuJp4Qw9RAt+GzYnVPyIXoK1h3TepUXe9VIVx50cUFIibdXycgu0IFGASeAb3YMyupw== + dependencies: + "@stdlib/assert-has-uint32array-support" "^0.2.2" + +"@stdlib/array-uint8@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/array-uint8/-/array-uint8-0.2.2.tgz#4add6fc8fd574c6330a6162aac1ebb421f8a0a82" + integrity sha512-Ip9MUC8+10U9x0crMKWkpvfoUBBhWzc6k5SI4lxx38neFVmiJ3f+5MBADEagjpoKSBs71vlY2drnEZe+Gs2Ytg== + dependencies: + "@stdlib/assert-has-uint8array-support" "^0.2.2" + +"@stdlib/assert-has-float32array-support@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-has-float32array-support/-/assert-has-float32array-support-0.2.2.tgz#dacf3439d9a91be30c5637144a2f9afc342ef258" + integrity sha512-pi2akQl8mVki43fF1GNQVLYW0bHIPp2HuRNThX9GjB3OFQTpvrV8/3zPSh4lOxQa5gRiabgf0+Rgeu3AOhEw9A== + dependencies: + "@stdlib/assert-is-float32array" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + +"@stdlib/assert-has-float64array-support@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-has-float64array-support/-/assert-has-float64array-support-0.2.2.tgz#228ed3c8a174c4a467b6daccb24b6c9c307cbab5" + integrity sha512-8L3GuKY1o0dJARCOsW9MXcugXapaMTpSG6dGxyNuUVEvFfY5UOzcj9/JIDal5FjqSgqVOGL5qZl2qtRwub34VA== + dependencies: + "@stdlib/assert-is-float64array" "^0.2.2" + +"@stdlib/assert-has-generator-support@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-has-generator-support/-/assert-has-generator-support-0.2.2.tgz#93d81c9d8e7ccee8a90bbc8d3f0ddaa1ae7e1ee8" + integrity sha512-TcE9BGV8i7B2OmxPlJ/2DUrAwG0W4fFS/DE7HmVk68PXVZsgyNQ/WP/IHBoazHDjhN5c3dU21c20kM/Bw007Rw== + dependencies: + "@stdlib/utils-eval" "^0.2.2" + +"@stdlib/assert-has-own-property@^0.2.1", "@stdlib/assert-has-own-property@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-has-own-property/-/assert-has-own-property-0.2.2.tgz#072661539bb79c353dc5e62ae9252ce428adb5f1" + integrity sha512-m5rV4Z2/iNkwx2vRsNheM6sQZMzc8rQQOo90LieICXovXZy8wA5jNld4kRKjMNcRt/TjrNP7i2Rhh8hruRDlHg== + +"@stdlib/assert-has-symbol-support@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-has-symbol-support/-/assert-has-symbol-support-0.2.2.tgz#ed7abecb6ae513c5f52dbd14d4601f3d707ab19f" + integrity sha512-vCsGGmDZz5dikGgdF26rIL0y0nHvH7qaVf89HLLTybceuZijAqFSJEqcB3Gpl5uaeueLNAWExHi2EkoUVqKHGg== + +"@stdlib/assert-has-tostringtag-support@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-has-tostringtag-support/-/assert-has-tostringtag-support-0.2.2.tgz#4e5053b69571aca325b9ccf26f8e6acbf8190acb" + integrity sha512-bSHGqku11VH0swPEzO4Y2Dr+lTYEtjSWjamwqCTC8udOiOIOHKoxuU4uaMGKJjVfXG1L+XefLHqzuO5azxdRaA== + dependencies: + "@stdlib/assert-has-symbol-support" "^0.2.1" + +"@stdlib/assert-has-uint16array-support@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-has-uint16array-support/-/assert-has-uint16array-support-0.2.2.tgz#b94f9adf53292151129e46a4f2aae2629c679a86" + integrity sha512-aL188V7rOkkEH4wYjfpB+1waDO4ULxo5ppGEK6X0kG4YiXYBL2Zyum53bjEQvo0Nkn6ixe18dNzqqWWytBmDeg== + dependencies: + "@stdlib/assert-is-uint16array" "^0.2.1" + "@stdlib/constants-uint16-max" "^0.2.2" + +"@stdlib/assert-has-uint32array-support@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-has-uint32array-support/-/assert-has-uint32array-support-0.2.2.tgz#d5b70c4c068cff8dec176fcd71868690e47abee9" + integrity sha512-+UHKP3mZOACkJ9CQjeKNfbXHm5HGQB862V5nV5q3UQlHPzhslnXKyG1SwAxTx+0g88C/2vlDLeqG8H4TH2UTFA== + dependencies: + "@stdlib/assert-is-uint32array" "^0.2.1" + "@stdlib/constants-uint32-max" "^0.2.2" + +"@stdlib/assert-has-uint8array-support@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-has-uint8array-support/-/assert-has-uint8array-support-0.2.2.tgz#33af366594a8540a643360763aada11a1d837075" + integrity sha512-VfzrB0BMik9MvPyKcMDJL3waq4nM30RZUrr2EuuQ/RbUpromRWSDbzGTlRq5SfjtJrHDxILPV3rytDCc03dgWA== + dependencies: + "@stdlib/assert-is-uint8array" "^0.2.1" + "@stdlib/constants-uint8-max" "^0.2.2" + +"@stdlib/assert-is-array-like@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-array-like/-/assert-is-array-like-0.2.2.tgz#c9f7755e873246effb75d30942f8ce12c786b8da" + integrity sha512-kOa5oNJucpBI4rn4+EPL+FlGm7nvTOrYmjD0tdl8JsaNNiQ1w2u2i7fBvCz2iKiQmtuI/+Cu/ES5BtjYoPRxAQ== + dependencies: + "@stdlib/constants-array-max-array-length" "^0.2.2" + "@stdlib/math-base-assert-is-integer" "^0.2.5" + +"@stdlib/assert-is-array@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-array/-/assert-is-array-0.2.2.tgz#ba820d24dd914fe8c29bd61033417ab5a2c2c34f" + integrity sha512-aJyTX2U3JqAGCATgaAX9ygvDHc97GCIKkIhiZm/AZaLoFHPtMA1atQ4bKcefEC8Um9eefryxTHfFPfSr9CoNQQ== + dependencies: + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/assert-is-big-endian@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-big-endian/-/assert-is-big-endian-0.2.2.tgz#6894d3c01f83b0441801bd5b4a25edc1c2a959c7" + integrity sha512-mPEl30/bqZh++UyQbxlyOuB7k0wC73y5J9nD2J6Ud6Fcl76R5IAGHRW0WT3W18is/6jG1jzMd8hrISFyD7N0sA== + dependencies: + "@stdlib/array-uint16" "^0.2.1" + "@stdlib/array-uint8" "^0.2.1" + +"@stdlib/assert-is-boolean@^0.2.1", "@stdlib/assert-is-boolean@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-boolean/-/assert-is-boolean-0.2.2.tgz#1d6361f66a25cd81ae12085da6ce1457311758ee" + integrity sha512-3KFLRTYZpX6u95baZ6PubBvjehJs2xBU6+zrenR0jx8KToUYCnJPxqqj7JXRhSD+cOURmcjj9rocVaG9Nz18Pg== + dependencies: + "@stdlib/assert-has-tostringtag-support" "^0.2.2" + "@stdlib/boolean-ctor" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/assert-is-buffer@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-buffer/-/assert-is-buffer-0.2.2.tgz#f32894cc86103c151e144cf3dbac63ef9e3f8f15" + integrity sha512-4/WMFTEcDYlVbRhxY8Wlqag4S70QCnn6WmQ4wmfiLW92kqQHsLvTNvdt/qqh/SDyDV31R/cpd3QPsVN534dNEA== + dependencies: + "@stdlib/assert-is-object-like" "^0.2.1" + +"@stdlib/assert-is-float32array@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-float32array/-/assert-is-float32array-0.2.2.tgz#8b6187136f95e3ef8ba8acad33197736e4844bfb" + integrity sha512-hxEKz/Y4m1NYuOaiQKoqQA1HeAYwNXFqSk3FJ4hC71DuGNit2tuxucVyck3mcWLpLmqo0+Qlojgwo5P9/C/9MQ== + dependencies: + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/assert-is-float64array@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-float64array/-/assert-is-float64array-0.2.2.tgz#c69a894d85a0a9c71f8b68b3aea1ea35bd3ebe85" + integrity sha512-3R1wLi6u/IHXsXMtaLnvN9BSpqAJ8tWhwjOOr6kadDqCWsU7Odc7xKLeAXAInAxwnV8VDpO4ifym4A3wehazPQ== + dependencies: + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/assert-is-function@^0.2.1", "@stdlib/assert-is-function@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-function/-/assert-is-function-0.2.2.tgz#97b54f449e54fd15913054cc69c7385ea9baab81" + integrity sha512-whY69DUYWljCJ79Cvygp7VzWGOtGTsh3SQhzNuGt+ut6EsOW+8nwiRkyBXYKf/MOF+NRn15pxg8cJEoeRgsPcA== + dependencies: + "@stdlib/utils-type-of" "^0.2.1" + +"@stdlib/assert-is-integer@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-integer/-/assert-is-integer-0.2.2.tgz#2b0b76e11926b7530b510c80e2f3e3fdf271a368" + integrity sha512-2d4CioQmnPcNDvNfC3Q6+xAJLwYYcSUamnxP0bSBJ1oAazWaVArdXNUAUxufek2Uaq6TVIM2gNSMyivIKIJd2w== + dependencies: + "@stdlib/assert-is-number" "^0.2.2" + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-assert-is-integer" "^0.2.4" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/assert-is-little-endian@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-little-endian/-/assert-is-little-endian-0.2.2.tgz#3f8f2400cee206ce55a21135fd42277361ba88dc" + integrity sha512-KMzPndj85jDiE1+hYCpw12k2OQOVkfpCo7ojCmCl8366wtKGEaEdGbz1iH98zkxRvnZLSMXcYXI2z3gtdmB0Ag== + dependencies: + "@stdlib/array-uint16" "^0.2.1" + "@stdlib/array-uint8" "^0.2.1" + +"@stdlib/assert-is-nan@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-nan/-/assert-is-nan-0.2.2.tgz#8d1a65a4ea0c5db87dadb0778bb1eef97b007826" + integrity sha512-Wh7KPIVfi6UVBRuPgkjVnoJP6mVtDNg+Y4m3Hko86TSf78KqFXfyZy/m6hnlYBWZRkNJDKo1J/7A/zpPwcEUVg== + dependencies: + "@stdlib/assert-is-number" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.1" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/assert-is-nonnegative-integer@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-nonnegative-integer/-/assert-is-nonnegative-integer-0.2.2.tgz#c47a7afabede723bfc05ed02b28a590163ec03f9" + integrity sha512-4t2FoZQeZ5nMYHYSeTVlgAp/HLEMYqe9qMcJgbvj63KTrGCDsuIpTE0S+UTxAc6Oc3Ftgb0ygjBFJQ0mxwN0Ow== + dependencies: + "@stdlib/assert-is-integer" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/assert-is-number-array@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-number-array/-/assert-is-number-array-0.2.2.tgz#6323ca43f0ca4e1c0a5f96f1566e3f84340b7637" + integrity sha512-8aQ/unulf5ED1Pu7na1Y1YPRigIBIMw/OiO99V69aGyYmlJaIQPAaYYecmRo1tMzKwP/jkhm3mTBsaPtCpJiLw== + dependencies: + "@stdlib/assert-is-number" "^0.2.2" + "@stdlib/assert-tools-array-like-function" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/assert-is-number@^0.2.1", "@stdlib/assert-is-number@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-number/-/assert-is-number-0.2.2.tgz#269ab5bf779a26a2cec7575c9a47e163f5bb74b2" + integrity sha512-sWpJ59GqGbmlcdYSUV/OYkmQW8k47w10+E0K0zPu1x1VKzhjgA5ZB2sJcpgI8Vt3ckRLjdhuc62ZHJkrJujG7A== + dependencies: + "@stdlib/assert-has-tostringtag-support" "^0.2.2" + "@stdlib/number-ctor" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/assert-is-object-like@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-object-like/-/assert-is-object-like-0.2.2.tgz#3bd47386addeb7ccb4ac82b9d924ddaa5fddde57" + integrity sha512-MjQBpHdEebbJwLlxh/BKNH8IEHqY0YlcCMRKOQU0UOlILSJg0vG+GL4fDDqtx9FSXxcTqC+w3keHx8kAKvQhzg== + dependencies: + "@stdlib/assert-tools-array-function" "^0.2.1" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/assert-is-object@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-object/-/assert-is-object-0.2.2.tgz#671297efc43788aa5368ce59ede28a8089387a7f" + integrity sha512-sNnphJuHyMDHHHaonlx6vaCKMe4sHOn0ag5Ck4iW3kJtM2OZB2J4h8qFcwKzlMk7fgFu7vYNGCZtpm1dYbbUfQ== + dependencies: + "@stdlib/assert-is-array" "^0.2.1" + +"@stdlib/assert-is-plain-object@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-plain-object/-/assert-is-plain-object-0.2.2.tgz#90b67c33ec6430ee5ca5a4c053ef5843550a3435" + integrity sha512-o4AFWgBsSNzZAOOfIrxoDFYTqnLuGiaHDFwIeZGUHdpQeav2Fll+sGeaqOcekF7yKawoswnwWdJqTsjapb4Yzw== + dependencies: + "@stdlib/assert-has-own-property" "^0.2.1" + "@stdlib/assert-is-function" "^0.2.1" + "@stdlib/assert-is-object" "^0.2.1" + "@stdlib/utils-get-prototype-of" "^0.2.1" + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/assert-is-positive-integer@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-positive-integer/-/assert-is-positive-integer-0.2.2.tgz#48fe0f99cfc42cffd57ea08821e93938fde79104" + integrity sha512-cHdVJC88DZYpN4q39cWjx9fAqKBY9x0FFf3yY6aFBUaAXP/K5Ptdh22+y1wX210vC/wvxL5V9n4pcWO3wmBGHg== + dependencies: + "@stdlib/assert-is-integer" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/assert-is-regexp@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-regexp/-/assert-is-regexp-0.2.2.tgz#4d0f24c5ab189da3839ceca7e6955d263d7b798d" + integrity sha512-2JtiUtRJxPaVXL7dkWoV3n5jouI65DwYDXsDXg3xo23TXlTNGgU/HhKO4FWC1Yqju7YMZi0hcZSW6E9v8ISqeQ== + dependencies: + "@stdlib/assert-has-tostringtag-support" "^0.2.2" + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/assert-is-string@^0.2.1", "@stdlib/assert-is-string@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-string/-/assert-is-string-0.2.2.tgz#2f3099045f5c9bdb85bf7620c021c17e5be19f2f" + integrity sha512-SOkFg4Hq443hkadM4tzcwTHWvTyKP9ULOZ8MSnnqmU0nBX1zLVFLFGY8jnF6Cary0dL0V7QQBCfuxqKFM6u2PQ== + dependencies: + "@stdlib/assert-has-tostringtag-support" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/assert-is-typed-array-like@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-typed-array-like/-/assert-is-typed-array-like-0.2.2.tgz#d4d8f6da0839ea7218d4c5d670b8900511431745" + integrity sha512-7Toa5t+camga8IfIAVsiTd5U2GgVMncN0Q6KYMF4Jmj4+DhEYzqgQckFaVrfhuL14Au7ik4ZhS4K7X4wEL1S0g== + dependencies: + "@stdlib/assert-is-nonnegative-integer" "^0.2.2" + "@stdlib/constants-array-max-typed-array-length" "^0.2.2" + +"@stdlib/assert-is-uint16array@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-uint16array/-/assert-is-uint16array-0.2.2.tgz#85346d95d8fd08c879a0b33a210d9224f54a2d4b" + integrity sha512-w3+HeTiXGLJGw5nCqr0WbvgArNMEj7ulED1Yd19xXbmmk2W1ZUB+g9hJDOQTiKsTU4AVyH4/As+aA8eDVmWtmg== + dependencies: + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/assert-is-uint32array@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-uint32array/-/assert-is-uint32array-0.2.2.tgz#37f35526101e5847c54cb8c9952976d1888a0bb8" + integrity sha512-3F4nIHg1Qp0mMIsImWUC8DwQ3qBK5vdIJTjS2LufLbFBhHNmv5kK1yJiIXQDTLkENU0STZe05TByo01ZNLOmDQ== + dependencies: + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/assert-is-uint8array@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-is-uint8array/-/assert-is-uint8array-0.2.2.tgz#2d46b13d58b8d1b6aa4e4841fbb6903c6cd07a08" + integrity sha512-51WnDip6H2RrN0CbqWmfqySAjam8IZ0VjlfUDc3PtcgrZGrKKjVgyHAsT/L3ZDydwF+aB94uvYJu5QyrCPNaZw== + dependencies: + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/assert-tools-array-function@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-tools-array-function/-/assert-tools-array-function-0.2.2.tgz#aba9b71b5164e97872cd2d6b16b221e01bd8c5e0" + integrity sha512-FYeT7X9x0C8Nh+MN6IJUDz+7i7yB6mio2/SDlrvyepjyPSU/cfHfwW0GEOnQhxZ+keLZC/YqDD930WjRODwMdA== + dependencies: + "@stdlib/assert-is-array" "^0.2.1" + "@stdlib/error-tools-fmtprodmsg" "^0.2.2" + "@stdlib/string-format" "^0.2.2" + +"@stdlib/assert-tools-array-like-function@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/assert-tools-array-like-function/-/assert-tools-array-like-function-0.2.2.tgz#8ee6eb0aaa26b549ee21e1dbac398a979f44ee20" + integrity sha512-j/lK6Bap3uyfIKxpQjAZ4zKG5YhJR0J50bVNN+dZ+W3KVgPQ4aQCJ4to6poOF2hHB/oCpKkHMydA7XGe8yw0xw== + dependencies: + "@stdlib/assert-is-array-like" "^0.2.1" + "@stdlib/error-tools-fmtprodmsg" "^0.2.2" + "@stdlib/string-format" "^0.2.2" + +"@stdlib/blas-ext-base-gapxsumpw@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/blas-ext-base-gapxsumpw/-/blas-ext-base-gapxsumpw-0.2.2.tgz#99ed4ac1e10e933f66e919380110e433e2a99a33" + integrity sha512-ffJit/xSoXhKO1Bm3vAHH9sF5bowSys9xyYJa9E+YYpvN/iMFyRS9sUL7p4kjcyD263rAQnTNvLV0jYnSVwtLA== + dependencies: + "@stdlib/math-base-special-floor" "^0.2.3" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/blas-ext-base-gsumpw@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/blas-ext-base-gsumpw/-/blas-ext-base-gsumpw-0.2.2.tgz#fdab0b4d104b24cbd160b93791079a019c1e3cb1" + integrity sha512-0CVHaOAXfDZ83NyQF9IURfiRKHFyI0CMPh2IP7hxWqRFpNnZlgCOvNXyxZSVpk77R+Gf3ETqJrwAEM/GqwSLhA== + dependencies: + "@stdlib/math-base-special-floor" "^0.2.3" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/boolean-ctor@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/boolean-ctor/-/boolean-ctor-0.2.2.tgz#d0add4760adeca22631625dd95bb9ca32abb931a" + integrity sha512-qIkHzmfxDvGzQ3XI9R7sZG97QSaWG5TvWVlrvcysOGT1cs6HtQgnf4D//SRzZ52VLm8oICP+6OKtd8Hpm6G7Ww== + +"@stdlib/complex-float32-ctor@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@stdlib/complex-float32-ctor/-/complex-float32-ctor-0.0.2.tgz#57f6d3f0217c1ae1f83ea12b044a80e951a215d3" + integrity sha512-QsTLynhTRmDT0mSkfdHj0FSqQSxh2nKx+vvrH3Y0/Cd/r0WoHFZwyibndDxshfkf9B7nist8QKyvV82I3IZciA== + dependencies: + "@stdlib/assert-is-number" "^0.2.2" + "@stdlib/error-tools-fmtprodmsg" "^0.2.2" + "@stdlib/number-float64-base-to-float32" "^0.2.1" + "@stdlib/string-format" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + "@stdlib/utils-define-property" "^0.2.4" + +"@stdlib/complex-float32-reim@^0.1.1", "@stdlib/complex-float32-reim@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@stdlib/complex-float32-reim/-/complex-float32-reim-0.1.2.tgz#095fcd9e4c2657d58c294eeeaeb5d46591d844bc" + integrity sha512-24H+t1xwQF6vhOoMZdDA3TFB4M+jb5Swm/FwNaepovlzVIG2NlthUZs6mZg1T3oegqesIRQRwhpn4jIPjuGiTw== + dependencies: + "@stdlib/array-float32" "^0.2.2" + "@stdlib/complex-float32-ctor" "^0.0.2" + +"@stdlib/complex-float32@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/complex-float32/-/complex-float32-0.2.1.tgz#a0438d278f4dd6ed76f31cc5d2094afdc113ff1e" + integrity sha512-tp83HfJzcZLK7/6P6gZPcAa/8F/aHS7gBHgB6ft45d/n6oE+/VbnyOvsJKanRv8S96kBRj8xkvlWHz4IiBrT0Q== + dependencies: + "@stdlib/assert-is-number" "^0.2.1" + "@stdlib/number-float64-base-to-float32" "^0.2.1" + "@stdlib/string-format" "^0.2.1" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.1" + "@stdlib/utils-define-property" "^0.2.1" + +"@stdlib/complex-float64-ctor@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@stdlib/complex-float64-ctor/-/complex-float64-ctor-0.0.3.tgz#740fdb24f5d1d5db82fa7800b91037e552a47bb6" + integrity sha512-oixCtBif+Uab2rKtgedwQTbQTEC+wVSu4JQH935eJ8Jo0eL6vXUHHlVrkLgYKlCDLvq5px1QQn42Czg/ixh6Gw== + dependencies: + "@stdlib/assert-is-number" "^0.2.2" + "@stdlib/complex-float32-ctor" "^0.0.2" + "@stdlib/error-tools-fmtprodmsg" "^0.2.2" + "@stdlib/string-format" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + "@stdlib/utils-define-property" "^0.2.4" + +"@stdlib/complex-float64-reim@^0.1.1", "@stdlib/complex-float64-reim@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@stdlib/complex-float64-reim/-/complex-float64-reim-0.1.2.tgz#22276ea7feef9d61c5bc0d4b0d0137386c57960e" + integrity sha512-q6RnfgbUunApAYuGmkft1oOM3x3xVMVJwNRlRgfIXwKDb8pYt+S/CeIwi3Su5SF6ay3AqA1s+ze7m21osXAJyw== + dependencies: + "@stdlib/array-float64" "^0.2.2" + "@stdlib/complex-float64-ctor" "^0.0.3" + +"@stdlib/complex-float64@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/complex-float64/-/complex-float64-0.2.1.tgz#c22fb40c12078a9ed3a4b8dadf82ab642455b149" + integrity sha512-vN9GqlSaonoREf8/RIN9tfNLnkfN4s7AI0DPsGnvc1491oOqq9UqMw8rYTrnxuum9/OaNAAUqDkb5GLu5uTveQ== + dependencies: + "@stdlib/assert-is-number" "^0.2.1" + "@stdlib/complex-float32" "^0.2.1" + "@stdlib/string-format" "^0.2.1" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.1" + "@stdlib/utils-define-property" "^0.2.1" + +"@stdlib/complex-reim@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/complex-reim/-/complex-reim-0.2.1.tgz#19d50743696060f3d993ad9c014f5794ea6f1d94" + integrity sha512-67nakj+HwBRx/ha3j/sLbrMr2hwFVgEZtaczOgn1Jy/cU03lKvNbMkR7QI9s+sA+b+A3yJB3ob8ZQSqh3D1+dA== + dependencies: + "@stdlib/array-float64" "^0.2.1" + "@stdlib/complex-float64" "^0.2.1" + +"@stdlib/complex-reimf@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/complex-reimf/-/complex-reimf-0.2.1.tgz#d14cd881ea0da0f952e381b936c58629f575803f" + integrity sha512-6HyPPmo0CEHoBjOg2w70mMFLcFEunM78ljnW6kf1OxjM/mqMaBM1NRpDrQoFwCIdh1RF1ojl3JR0YLllEf0qyQ== + dependencies: + "@stdlib/array-float32" "^0.2.1" + "@stdlib/complex-float32" "^0.2.1" + +"@stdlib/constants-array-max-array-length@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-array-max-array-length/-/constants-array-max-array-length-0.2.2.tgz#9ab7ca4968fa5c45f14fccf17585d7b3f26ed3ba" + integrity sha512-fZzM6cvIEXhNSXR+erL1PYYIbZYAXGss9aN7C8KXVGGDfq4Eebts7ZWcqLgPfxftMnJ07yPyPffFqRnsDSfqqQ== + +"@stdlib/constants-array-max-typed-array-length@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-array-max-typed-array-length/-/constants-array-max-typed-array-length-0.2.2.tgz#1cf750d8f0732a88159f2bc6a9c881fcb816add0" + integrity sha512-uAoBItVIfuzR4zKK1F57Znrn2frKL0U9gqJkg30BXuno3YlUvbhIfVP3VsUmGJCmi9ztgYLqX10yqb0KvlM2Ig== + +"@stdlib/constants-float32-max@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float32-max/-/constants-float32-max-0.2.2.tgz#5fdb911e4ec0516abb5e18dd2191ffd9988c12ab" + integrity sha512-uxvIm/KmIeZP4vyfoqPd72l5/uidnCN9YJT3p7Z2LD8hYN3PPLu6pd/5b51HMFLwfkZ27byRJ9+YK6XnneJP0Q== + +"@stdlib/constants-float32-smallest-normal@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float32-smallest-normal/-/constants-float32-smallest-normal-0.2.2.tgz#6919f88ec5398935a5e52950e3910e0a454f6292" + integrity sha512-2qkGjGML2/8P9YguHnac2AKXLbfycpYdCxKmuXQdAVzMMNCJWjHoIqZMFG29WBEDBOP057X+48S6WhIqoxRpWA== + +"@stdlib/constants-float64-e@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-e/-/constants-float64-e-0.2.2.tgz#d05f73f9901852a71eb0d79eaff79a574d22b2d6" + integrity sha512-7fxHaABwosbUzpBsw6Z9Dd9MqUYne8x+44EjohVcWDr0p0mHB/DXVYEYTlwEP/U/XbRrKdO3jUG6IO/GsEjzWg== + +"@stdlib/constants-float64-eps@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-eps/-/constants-float64-eps-0.2.2.tgz#4a39f718e1ce570a0e020e8b393cf4dc28b5c453" + integrity sha512-61Pb2ip9aPhHXxiCn+VZ0UVw2rMYUp0xrX93FXyB3UTLacrofRKLMKtbV0SFac4VXx5igv2+0G+h6G/fwCgjyw== + +"@stdlib/constants-float64-eulergamma@^0.2.1", "@stdlib/constants-float64-eulergamma@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-eulergamma/-/constants-float64-eulergamma-0.2.2.tgz#18b42b978e1f8763049661d98948aa548a1e2d66" + integrity sha512-XsuVud0d1hLTQspFzgUSH2e3IawTXLlJi2k4Vg0Nn6juulxfNO9PnAGtHz+p1BynYF/YwN+qhKnISQxrN31rsQ== + +"@stdlib/constants-float64-exponent-bias@^0.2.1", "@stdlib/constants-float64-exponent-bias@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-exponent-bias/-/constants-float64-exponent-bias-0.2.2.tgz#ca64c846f1eac982b45fe8b0c3dc059796781ecb" + integrity sha512-zLWkjzDYHSsBsXB/4mwHysOGl64JS3XBt/McjvjCLc/IZpfsUNFxLCl7oVCplXzYYHcQj/RfEBFy6cxQ6FvdpQ== + +"@stdlib/constants-float64-fourth-pi@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-fourth-pi/-/constants-float64-fourth-pi-0.2.2.tgz#0e1cb8c11018930bc36a9feba9a46acbda8b68ab" + integrity sha512-j0NOg45ouibms4ML8pfS/eDrurdtnhJTNPCGQM4mg3X+1ljsuO0pvkpVCvuz29t5J23KTcfGBXXr90ikoBmjlw== + +"@stdlib/constants-float64-gamma-lanczos-g@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-gamma-lanczos-g/-/constants-float64-gamma-lanczos-g-0.2.2.tgz#3c33664a71869a31cdc2d6e8b9564ec1a74cd8d1" + integrity sha512-hCaZbZ042htCy9mlGrfUEbz4d0xW/DLdr3vHs5KiBWU+G+WHVH33vubSnEoyT0ugWpAk2ZqWXe/V8sLGgOu0xg== + +"@stdlib/constants-float64-half-ln-two@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-half-ln-two/-/constants-float64-half-ln-two-0.2.2.tgz#aebc7069842ddb2bb8a5f07c47465a4024a9fe8b" + integrity sha512-yv1XhzZR2AfJmnAGL0kdWlIUhc/vqdWol+1Gq2brXPVfgqbUmJu5XZuuK+jZA2k+fHyvRHNEwQRv9OPnOjchFg== + +"@stdlib/constants-float64-half-pi@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-half-pi/-/constants-float64-half-pi-0.2.2.tgz#50e022720f1d96fc1bd0f49573cb5283778a11a7" + integrity sha512-lM3SiDsZCKiuF5lPThZFFqioIwh1bUiBUnnDMLB04/QkVRCAaXUo+dsq2hOB6iBhHoYhiKds6T+PsHSBlpqAaA== + +"@stdlib/constants-float64-high-word-abs-mask@^0.2.1", "@stdlib/constants-float64-high-word-abs-mask@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-high-word-abs-mask/-/constants-float64-high-word-abs-mask-0.2.2.tgz#475f292601837efb5d1647239a80cf4f9e68d2e9" + integrity sha512-YtYngcHlw9qvOpmsSlkNHi6cy/7Y7QkyYh5kJbDvuOUXPDKa3rEwBln4mKjbWsXhmmN0bk7TLypH7Ryd/UAjUQ== + +"@stdlib/constants-float64-high-word-exponent-mask@^0.2.1", "@stdlib/constants-float64-high-word-exponent-mask@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-high-word-exponent-mask/-/constants-float64-high-word-exponent-mask-0.2.2.tgz#d215f3f1e36278a8349759c6451e457e7d037723" + integrity sha512-LhYUXvpnLOFnWr8ucHA9N/H75VxcS2T9EoBDTmWBZoKj2Pg0icGVDmcNciRLIWbuPA9osgcKpxoU+ADIfaipVA== + +"@stdlib/constants-float64-high-word-sign-mask@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-high-word-sign-mask/-/constants-float64-high-word-sign-mask-0.2.1.tgz#c5b4bc07c023e92aee6bc4dd5c737b9a6374cd55" + integrity sha512-Fep/Ccgvz5i9d5k96zJsDjgXGno8HJfmH7wihLmziFmA2z9t7NSacH4/BH4rPJ5yXFHLkacNLDxaF1gO1XpcLA== + +"@stdlib/constants-float64-high-word-significand-mask@^0.2.1", "@stdlib/constants-float64-high-word-significand-mask@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-high-word-significand-mask/-/constants-float64-high-word-significand-mask-0.2.2.tgz#66d3a609b9e06d6e7eb219d75ffe36550e95a312" + integrity sha512-eDDyiQ5PR1/qyklrW0Pus0ZopM7BYjkWTjqhSHhj0DibH6UMwSMlIl4ddCh3VX37p5eByuAavnaPgizk5c9mUw== + +"@stdlib/constants-float64-ln-sqrt-two-pi@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-ln-sqrt-two-pi/-/constants-float64-ln-sqrt-two-pi-0.2.2.tgz#510f06f63ff49caa7e2df0f094999e97c2369665" + integrity sha512-C9YS9W/lvv54wUC7DojQSRH9faKw0sMAM09oMRVm8OOYNr01Rs1wXeSPStl9ns4qiV/G13vZzd1I3nGqgqihbw== + +"@stdlib/constants-float64-ln-two@^0.2.1", "@stdlib/constants-float64-ln-two@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-ln-two/-/constants-float64-ln-two-0.2.2.tgz#9fc1ba5f35b11c780a13efb1c238d1488f314c14" + integrity sha512-EQ8EJ6B1wPfuhva0aApKIsF7lTna++txV4AUzL2wTfwDHw6RzWpA44u+k54KnLF8ZXUNIYDNQHHvtzdfKrFzCA== + +"@stdlib/constants-float64-max-base10-exponent@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-max-base10-exponent/-/constants-float64-max-base10-exponent-0.2.2.tgz#1a7344885d767f3b8275b43501f9f96aba4e2e32" + integrity sha512-Jammf5J//rhgT66DtAk1sVYPQqCwNVptImfpIN2ZTo8krx0HcvDWFEmsNZwVq6q5iUhKhSShzWSvty5ysswp1A== + +"@stdlib/constants-float64-max-base2-exponent-subnormal@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-max-base2-exponent-subnormal/-/constants-float64-max-base2-exponent-subnormal-0.2.1.tgz#57a5550f0f148cab0f51872237016344e6b0b10c" + integrity sha512-D1wBNn54Hu2pK6P/yBz0FtPBI3/7HdgK8igYjWDKWUKzC92R/6PHZ9q5NzedcGxoBs8MUk1zNpP0tZyYj9Y4YQ== + +"@stdlib/constants-float64-max-base2-exponent@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-max-base2-exponent/-/constants-float64-max-base2-exponent-0.2.2.tgz#bc5f506ae7022442f7ea8da8105b5392b72efdfd" + integrity sha512-KmDe98pJ2HXz2SbqyFfSDhlSSVD7JssjbZ5K11HEK2avqMcoCbdHH20T+6/TpA01VqaK8dLbeyphOfALcDdMKA== + +"@stdlib/constants-float64-max-ln@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-max-ln/-/constants-float64-max-ln-0.2.2.tgz#7d0c813590533544e7984a9eb1db9375b2adbd04" + integrity sha512-FPAEGjnoQMDPWJbCyyto7HWQ/SY2jjD8IkjyD8aOwENqbswjCbOINXRiK2ar27OOXG7Dv7CCpFpoorTxv0gmfA== + +"@stdlib/constants-float64-max-safe-integer@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-max-safe-integer/-/constants-float64-max-safe-integer-0.2.2.tgz#2ac062325f9d2da4869005db396b75d16f15e53b" + integrity sha512-d+sxmxhkt980SDFhnnRDSpujPQTv4nEt5Ox3L86HgYZU4mQU/wbzYVkMuHIANW9x3ehww5blnGXTKYG9rQCXAw== + +"@stdlib/constants-float64-max-safe-nth-factorial@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-max-safe-nth-factorial/-/constants-float64-max-safe-nth-factorial-0.1.0.tgz#d11a056dba48a6106865c96265f487dc93eec56a" + integrity sha512-sppIfkBbeyKNwfRbmNFi5obI7Q+IJCQzfWKYqvzmEJVOkmEg6hhtEeFc8zZJGCU7+Pndc3M2wdbTT5a3rhamHw== + +"@stdlib/constants-float64-max@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-max/-/constants-float64-max-0.2.2.tgz#a8c2e6fdcc845266c9796a912f58ef14c11499dd" + integrity sha512-S3kcIKTK65hPqirziof3KTYqfFKopgaTnaiDlDKdzaCzBZ5qkrAcRd4vl+W1KHoZruUyWC2/RYZUa/8+h075TQ== + +"@stdlib/constants-float64-min-base10-exponent-subnormal@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-min-base10-exponent-subnormal/-/constants-float64-min-base10-exponent-subnormal-0.2.1.tgz#42d3ed5552d09b45379988d71a051a5eaf5e7fc8" + integrity sha512-AghpVQcTuhPiLE1IAk+e8HNU+wce/132Sg3MsL60CQaRVnG8X/VdivzEJGt2Nw9uH5rctvVINFrNnM0gxtmzJg== + +"@stdlib/constants-float64-min-base10-exponent@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-min-base10-exponent/-/constants-float64-min-base10-exponent-0.2.2.tgz#a83ae0ba147162362d1db148299b1d25999ceafb" + integrity sha512-xrDj8cT4Aojo3TO1zm2LcFjXfkpf/NzNONkkE+YEnq+wHjV7UcXLTJefLPBET2T1uSwEXYYw7ZlbpQ7vbhNDEg== + +"@stdlib/constants-float64-min-base2-exponent-subnormal@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-min-base2-exponent-subnormal/-/constants-float64-min-base2-exponent-subnormal-0.2.1.tgz#b09334e3ba1ef29a166a46bf21d10c5896f46968" + integrity sha512-fTXfvctXWj/48gK+gbRBrHuEHEKY4QOJoXSGp414Sz6vUxHusHJJ686p8ze3XqM7CY6fmL09ZgdGz/uhJl/7lw== + +"@stdlib/constants-float64-min-base2-exponent@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-min-base2-exponent/-/constants-float64-min-base2-exponent-0.2.2.tgz#a6f7d080d54b68a753114dabe724ed881ccdc3c7" + integrity sha512-YZmBiKik6LbWB4EOZ/ZUs/u6OIF742xNK8mhEqL0OEN4NuJe3OdErpOic6KjMmHjQuqCXdFoSqsWZaFHcIN7HA== + +"@stdlib/constants-float64-min-ln@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-min-ln/-/constants-float64-min-ln-0.2.2.tgz#5e50f21037b78cfb8c81f9f39bfd00982fdb29ec" + integrity sha512-N1Sxjo3uTdEIpHeG2TzaX06UuvpcKHvjYKpIMhJSajbxvfVDURHlc9kIpfbP9C9/YYoCy0FvewA/kvbqNaYypA== + +"@stdlib/constants-float64-ninf@^0.2.1", "@stdlib/constants-float64-ninf@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-ninf/-/constants-float64-ninf-0.2.2.tgz#d7f5c5d445701dca25d39c14cac7a17acd7c5ee0" + integrity sha512-Iu+wZs/vgudAKVg9FEcRY3FadkmvsWuq/wJ3jIHjhaP5xcnoF3XJUO4IneEndybHwehfJL65NShnDsJcg1gicw== + dependencies: + "@stdlib/number-ctor" "^0.2.2" + +"@stdlib/constants-float64-pi@^0.2.1", "@stdlib/constants-float64-pi@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-pi/-/constants-float64-pi-0.2.2.tgz#78fce5be6156bf3f676c1e2d3398b8ede4b39beb" + integrity sha512-ix34KmpUQ0LUM++L6avLhM9LFCcGTlsUDyWD/tYVGZBiIzDS3TMKShHRkZvC+v87fuyYNPoxolYtk5AlbacI6g== + +"@stdlib/constants-float64-pinf@^0.2.1", "@stdlib/constants-float64-pinf@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-pinf/-/constants-float64-pinf-0.2.2.tgz#e568ccfc63f8788f48acb55821bc9f0a7403ec5d" + integrity sha512-UcwnWaSkUMD8QyKADwkXPlY7yOosCPZpE2EDXf/+WOzuWi5vpsec+JaasD5ggAN8Rv8OTVmexTFs1uZfrHgqVQ== + +"@stdlib/constants-float64-smallest-normal@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-smallest-normal/-/constants-float64-smallest-normal-0.2.2.tgz#943976c55fbb8f1a53d48cd2cb16e26611a290e2" + integrity sha512-GXNBkdqLT9X+dU59O1kmb7W5da/RhSXSvxx0xG5r7ipJPOtRLfTXGGvvTzWD4xA3Z5TKlrEL6ww5sph9BsPJnA== + +"@stdlib/constants-float64-smallest-subnormal@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-smallest-subnormal/-/constants-float64-smallest-subnormal-0.2.2.tgz#0b33a2f9105d5e48b72b0c29dfc90a3b471f4ed6" + integrity sha512-KuF+scDOsP0okx8RLF+q3l1RheaYChf+u/HbhzFbz82GeCIdIVp86UMwoBgfn8AT8cnR5SrtvLtQw15MGfa/vg== + +"@stdlib/constants-float64-sqrt-eps@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-sqrt-eps/-/constants-float64-sqrt-eps-0.2.2.tgz#24ec5396e6ec54e46844d27f1e3301b2bb602203" + integrity sha512-X7LnGfnwNnhiwlY+zd3FX6zclsx61MaboGTNAAdaV78YjBDTdGdWMHk5MQo1U17ryPlhdGphOAejhDHeaSnTXQ== + +"@stdlib/constants-float64-sqrt-two-pi@^0.2.1", "@stdlib/constants-float64-sqrt-two-pi@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-sqrt-two-pi/-/constants-float64-sqrt-two-pi-0.2.2.tgz#92f7b972e486cd6508054d2c6b85ef05004dffd3" + integrity sha512-I8Ylr64x8AFSQ2hFBT8szuIBAy2wqPx69taJMzfcmuM5SnSbS8SE/H19YnCimZErVFo4bz0Rh8Fp3edN4i6teQ== + +"@stdlib/constants-float64-sqrt-two@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-sqrt-two/-/constants-float64-sqrt-two-0.2.2.tgz#8017fbd7720c853b31d059861a7cd69318b1fc81" + integrity sha512-iqqouCuS9pUhjD91i5siScxLDtQTF1HsSZor6jaZRviMiOjCj/mjzxxTFHWUlU/rxHMBBhj/u7i12fv6a7dCAQ== + +"@stdlib/constants-float64-two-pi@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-float64-two-pi/-/constants-float64-two-pi-0.2.2.tgz#5c4b9dfad499932dec554da9f62da46a9bbf071c" + integrity sha512-cyXuwYOersVsA8tDSJ0ocMbtOc5KGxjlGvYC4vrpLQVkgNpxcGbA57n6JvaGmNk7+InXXbQ7qhTWGbTNgafcLQ== + +"@stdlib/constants-int32-max@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/constants-int32-max/-/constants-int32-max-0.2.1.tgz#5e4b47015561de50fdfb5c7ad9350a192c5a2f48" + integrity sha512-vKtp3q/HdAeGG8BJBZdNzFrYpVQeleODgvOxh9Pn/TX1Ktjc50I9TVl7nTVWsT2QnacruOorILk2zNsdgBHPUQ== + +"@stdlib/constants-int32-max@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/constants-int32-max/-/constants-int32-max-0.3.0.tgz#e575c365738d81b5fa1273877893312d3597af2c" + integrity sha512-jYN84QfG/yP2RYw98OR6UYehFFs0PsGAihV6pYU0ey+WF9IOXgSjRP56KMoZ7ctHwl4wsnj9I+qB2tGuEXr+pQ== + +"@stdlib/constants-uint16-max@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-uint16-max/-/constants-uint16-max-0.2.2.tgz#8bba489909ea11a468a01afe57be912cbce57f56" + integrity sha512-qaFXbxgFnAkt73P5Ch7ODb0TsOTg0LEBM52hw6qt7+gTMZUdS0zBAiy5J2eEkTxA9rD9X3nIyUtLf2C7jafNdw== + +"@stdlib/constants-uint32-max@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-uint32-max/-/constants-uint32-max-0.2.2.tgz#354b3c0f78ad54ff565087f01d9d8c337af63831" + integrity sha512-2G44HQgIKDrh3tJUkmvtz+eM+uwDvOMF+2I3sONcTHacANb+zP7la4LDYiTp+HFkPJyfh/kPapXBiHpissAb1A== + +"@stdlib/constants-uint8-max@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/constants-uint8-max/-/constants-uint8-max-0.2.2.tgz#1187e326b5f03d94a72051cace560ef156ac609d" + integrity sha512-ZTBQq3fqS/Y4ll6cPY5SKaS266EfmKP9PW3YLJaTELmYIzVo9w2RFtfCqN05G3olTQ6Le9MUEE/C6VFgZNElDQ== + +"@stdlib/error-tools-fmtprodmsg@^0.2.1", "@stdlib/error-tools-fmtprodmsg@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/error-tools-fmtprodmsg/-/error-tools-fmtprodmsg-0.2.2.tgz#0b42240fc5131b460f1120b77da8345dd22ee2dd" + integrity sha512-2IliQfTes4WV5odPidZFGD5eYDswZrPXob7oOu95Q69ERqImo8WzSwnG2EDbHPyOyYCewuMfM5Ha6Ggf+u944Q== + +"@stdlib/fs-exists@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/fs-exists/-/fs-exists-0.2.2.tgz#ccb289c0784f765796c27593abe6e398fb1bbdd2" + integrity sha512-uGLqc7izCIam2aTyv0miyktl4l8awgRkCS39eIEvvvnKIaTBF6pxfac7FtFHeEQKE3XhtKsOmdQ/yJjUMChLuA== + dependencies: + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/fs-resolve-parent-path@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/fs-resolve-parent-path/-/fs-resolve-parent-path-0.2.2.tgz#434fa93c067894fea7632aa4b93fba41d7a58cf5" + integrity sha512-ZG78ouZc+pdPLtU+sSpYTvbKTiLUgn6NTtlVFYmcmkYRFn+fGOOakwVuhYMcYG6ti10cLD6WzB/YujxIt8f+nA== + dependencies: + "@stdlib/assert-has-own-property" "^0.2.2" + "@stdlib/assert-is-function" "^0.2.2" + "@stdlib/assert-is-plain-object" "^0.2.2" + "@stdlib/assert-is-string" "^0.2.2" + "@stdlib/error-tools-fmtprodmsg" "^0.2.2" + "@stdlib/fs-exists" "^0.2.2" + "@stdlib/process-cwd" "^0.2.2" + "@stdlib/string-format" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/function-ctor@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/function-ctor/-/function-ctor-0.2.2.tgz#7212e3dc6ff3ec719584b68427d17d7b7bd8b6f1" + integrity sha512-qSn1XQnnhgCSYBfFy4II0dY5eW4wdOprgDTHcOJ3PkPWuZHDC1fXZsok1OYAosHqIiIw44zBFcMS/JRex4ebdQ== + +"@stdlib/math-base-assert-is-even@^0.2.1", "@stdlib/math-base-assert-is-even@^0.2.2": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-assert-is-even/-/math-base-assert-is-even-0.2.3.tgz#1bc3977e3f9b9fc732c2230a6cf515971a6b16c0" + integrity sha512-cziGv8F/aNyfME7Wx2XJjnYBnf9vIeh8yTIzlLELd0OqGHqfsHU5OQxxcl9x5DbjZ1G/w0lphWxHFHYCuwFCHw== + dependencies: + "@stdlib/math-base-assert-is-integer" "^0.2.4" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-assert-is-infinite@^0.2.1", "@stdlib/math-base-assert-is-infinite@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-assert-is-infinite/-/math-base-assert-is-infinite-0.2.2.tgz#9bf19e92e8498cbf6ffb46900f422799e6b08523" + integrity sha512-4zDZuinC3vkXRdQepr0ZTwWX3KgM0VIWqYthOmCSgLLA87L9M9z9MgUZL1QeYeYa0+60epjDcQ8MS3ecT70Jxw== + dependencies: + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.1" + +"@stdlib/math-base-assert-is-integer@^0.2.1", "@stdlib/math-base-assert-is-integer@^0.2.4", "@stdlib/math-base-assert-is-integer@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-assert-is-integer/-/math-base-assert-is-integer-0.2.5.tgz#fa30a62ee27a90bf5cf598f78d7c0de50b582413" + integrity sha512-Zi8N66GbWtSCR3OUsRdBknjNlX+aBN8w6CaVEP5+Jy/a7MgMYzevS52TNS5sm8jqzKBlFhZlPLex+Zl2GlPvSA== + dependencies: + "@stdlib/math-base-special-floor" "^0.2.3" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-assert-is-nan@^0.2.1", "@stdlib/math-base-assert-is-nan@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-assert-is-nan/-/math-base-assert-is-nan-0.2.2.tgz#84289029340e0002a3795e640b7c46be3c3e1696" + integrity sha512-QVS8rpWdkR9YmHqiYLDVLsCiM+dASt/2feuTl4T/GSdou3Y/PS/4j/tuDvCDoHDNfDkULUW+FCVjKYpbyoeqBQ== + dependencies: + "@stdlib/utils-library-manifest" "^0.2.1" + +"@stdlib/math-base-assert-is-negative-zero@^0.2.1", "@stdlib/math-base-assert-is-negative-zero@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-assert-is-negative-zero/-/math-base-assert-is-negative-zero-0.2.2.tgz#63c7a9a8d3a876e2107abab6c5e520135f7c36f6" + integrity sha512-WvKNuBZ6CDarOTzOuFLmO1jwZnFD+butIvfD2Ws6SsuqSCiWOaF4OhIckqPzo1XEdkqqhRNPqBxqc0D+hsEYVA== + dependencies: + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.1" + +"@stdlib/math-base-assert-is-odd@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-assert-is-odd/-/math-base-assert-is-odd-0.2.1.tgz#c75a051c3fca6211908f5cbc161e987022de2b66" + integrity sha512-V4qQuCO6/AA5udqlNatMRZ8R/MgpqD8mPIkFrpSZJdpLcGYSz815uAAf3NBOuWXkE2Izw0/Tg/hTQ+YcOW2g5g== + dependencies: + "@stdlib/math-base-assert-is-even" "^0.2.1" + +"@stdlib/math-base-assert-is-odd@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-assert-is-odd/-/math-base-assert-is-odd-0.3.0.tgz#b20b99e31ec4c6cc28aae4e722360f12eea82c1a" + integrity sha512-V44F3xdR5/bHXqqYvE/AldLnVmijLr/rgf7EjnJXXDQLfPCgemy0iHTFl19N68KG1YO9SMPdyOaNjh4K0O9Qqw== + dependencies: + "@stdlib/math-base-assert-is-even" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-assert-is-positive-zero@^0.2.1", "@stdlib/math-base-assert-is-positive-zero@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-assert-is-positive-zero/-/math-base-assert-is-positive-zero-0.2.2.tgz#51e3f4bcda6e7d53c9cd840b872029169cf6dafc" + integrity sha512-mMX5xsemKpHRAgjpVJCb3eVZ3WIkZh6KnHQH8i8n4vI44pcdpN5rcTdEAMlhLjxT/rT7H2wq85f7/FRsq9r9rw== + dependencies: + "@stdlib/constants-float64-pinf" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.1" + +"@stdlib/math-base-napi-binary@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-napi-binary/-/math-base-napi-binary-0.2.1.tgz#0e69751a6570efe606bcc9826ebcacdfc2864213" + integrity sha512-ewGarSRaz5gaLsE17yJ4me03e56ICgPAA0ru0SYFCeMK2E5Z4Z2Lbu7HAQTTg+8XhpoaZSw0h2GJopTV7PCKmw== + dependencies: + "@stdlib/complex-float32" "^0.2.1" + "@stdlib/complex-float64" "^0.2.1" + "@stdlib/complex-reim" "^0.2.1" + "@stdlib/complex-reimf" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.1" + +"@stdlib/math-base-napi-binary@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-napi-binary/-/math-base-napi-binary-0.3.0.tgz#3a3e2d29b2298fc07e3bf39ef408371dfeedfa40" + integrity sha512-bhwsmGMOMN1srcpNAFRjDMSXe9ue1s/XmaoBBlqcG6S2nqRQlIVnKKH4WZx4hmC1jDqoFXuNPJGE47VXpVV+mA== + dependencies: + "@stdlib/complex-float32-ctor" "^0.0.2" + "@stdlib/complex-float32-reim" "^0.1.2" + "@stdlib/complex-float64-ctor" "^0.0.3" + "@stdlib/complex-float64-reim" "^0.1.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-napi-unary@^0.2.1", "@stdlib/math-base-napi-unary@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-napi-unary/-/math-base-napi-unary-0.2.3.tgz#57862685d6ce037aa927020d272e8d74cc243320" + integrity sha512-BCyJmpq2S8EFo2yMt1z+v1EL7nn8RHcM6jn7fa8n3BTP679K0MSlawIh3A0CFogfrTdjPM4G44VO1ddsdLExcg== + dependencies: + "@stdlib/complex-float32-ctor" "^0.0.2" + "@stdlib/complex-float32-reim" "^0.1.1" + "@stdlib/complex-float64-ctor" "^0.0.3" + "@stdlib/complex-float64-reim" "^0.1.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-abs@^0.2.1", "@stdlib/math-base-special-abs@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-abs/-/math-base-special-abs-0.2.2.tgz#cc52e7e62f43df358c078ec1894faa5a988efee0" + integrity sha512-cw5CXj05c/L0COaD9J+paHXwmoN5IBYh+Spk0331f1pEMvGxSO1KmCREZaooUEEFKPhKDukEHKeitja2yAQh4Q== + dependencies: + "@stdlib/constants-float64-high-word-abs-mask" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/number-float64-base-to-words" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-acos@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-acos/-/math-base-special-acos-0.2.3.tgz#806f71436c8ff4848e9351f138f743176444031c" + integrity sha512-f66Ikq0E3U5XQm6sTu4UHwP3TmcPrVgSK/mZTvg2JenswZ6qPtGO1A8KHZ5+/5bk1TSc9EW4zDGUqWG7mGzT4Q== + dependencies: + "@stdlib/constants-float64-fourth-pi" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/math-base-special-asin" "^0.2.2" + "@stdlib/math-base-special-sqrt" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-asin@^0.2.2", "@stdlib/math-base-special-asin@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-asin/-/math-base-special-asin-0.2.3.tgz#69c6b0ffd29ceb46b6904ce3cb2b6e22821fd631" + integrity sha512-Ju1UFJspOOL630SqBtVmUh3lHv5JMu1szcAgx7kQupJwZiwWljoVQ5MmxlNY4l3nGM5oMokenlqTDNXOau43lw== + dependencies: + "@stdlib/constants-float64-fourth-pi" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/math-base-special-sqrt" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-beta@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-beta/-/math-base-special-beta-0.3.0.tgz#c94a7a973bfdad70cdf8fd381d3f4b4fae8b1926" + integrity sha512-SWUF1AZLqaEJ8g1Lj0/UOfj955AsIS3QPYH/ZMijELVxCwmp7VRgalI0AxMM09IraJt1cH5WrSwSnouH1WC3ZQ== + dependencies: + "@stdlib/constants-float64-e" "^0.2.2" + "@stdlib/constants-float64-eps" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-binary" "^0.3.0" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-exp" "^0.2.4" + "@stdlib/math-base-special-log1p" "^0.2.3" + "@stdlib/math-base-special-pow" "^0.3.0" + "@stdlib/math-base-special-sqrt" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-betainc@^0.2.1", "@stdlib/math-base-special-betainc@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-betainc/-/math-base-special-betainc-0.2.2.tgz#62407f509dbb3da79cdf94291d82751f46943d11" + integrity sha512-95tzDgn5d9RV9al4gxHwKfszd9M6AizlpnhAiwIi0JwqcO+OY3xgbABWal4/H09Tb8DaC9jDqiyGuyPuB0iDew== + dependencies: + "@stdlib/math-base-special-kernel-betainc" "^0.2.1" + +"@stdlib/math-base-special-binomcoef@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-binomcoef/-/math-base-special-binomcoef-0.2.3.tgz#0025b0cf5abb70d69b58177e3a0aed336efe5877" + integrity sha512-RxnQ/QGgKUeqTvBL+7IH8rNKQYCfGs0I3PsFYfb0e9V1O2yIVvthURUpzjukurZM89JRapK1dN6aeZ5UM71Zgw== + dependencies: + "@stdlib/constants-float64-max-safe-integer" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-assert-is-integer" "^0.2.5" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-assert-is-odd" "^0.3.0" + "@stdlib/math-base-special-floor" "^0.2.3" + "@stdlib/math-base-special-gcd" "^0.2.1" + +"@stdlib/math-base-special-ceil@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-ceil/-/math-base-special-ceil-0.2.2.tgz#26213c02d8fe3cd3c8d95566e0a18c0db7528744" + integrity sha512-zGkDaMcPrxQ9Zo+fegf2MyI8UPIrVTK5sc/FgCN9qdwEFJTKGLsBd249T3xH7L2MDxx5JbIMGrr6L4U4uEm2Hw== + dependencies: + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-copysign@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-copysign/-/math-base-special-copysign-0.2.2.tgz#5d56fd4429e6aa63d2c32861920d904128558e67" + integrity sha512-m9nWIQhKsaNrZtS2vIPeToWDbzs/T0d0NWy7gSci38auQVufSbF6FYnCKl0f+uwiWlh5GYXs0uVbyCp7FFXN+A== + dependencies: + "@stdlib/constants-float64-high-word-abs-mask" "^0.2.2" + "@stdlib/constants-float64-high-word-sign-mask" "^0.2.1" + "@stdlib/math-base-napi-binary" "^0.3.0" + "@stdlib/number-float64-base-from-words" "^0.2.2" + "@stdlib/number-float64-base-get-high-word" "^0.2.2" + "@stdlib/number-float64-base-to-words" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-cos@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-cos/-/math-base-special-cos-0.2.1.tgz#82158055e80be3255e633c94c2f2bfb66d36eed4" + integrity sha512-Yre+ASwsv4pQJk5dqY6488ZfmYDA6vtUTdapAVjCx28NluSFhXw1+S8EmsqnzYnqp/4x7Y1H7V2UPZfw+AdnbQ== + dependencies: + "@stdlib/math-base-special-kernel-cos" "^0.2.1" + "@stdlib/math-base-special-kernel-sin" "^0.2.1" + "@stdlib/math-base-special-rempio2" "^0.2.1" + "@stdlib/number-float64-base-get-high-word" "^0.2.1" + +"@stdlib/math-base-special-cos@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-cos/-/math-base-special-cos-0.3.0.tgz#414dd3a5b58108e46f2b25380bf554ee7ab1f9e5" + integrity sha512-SsZpptPgCn15MjpWqEei6XaI60N+lrP0B7pFL89KFOXmagN4MprdU/YUmG413yPgUDLEEQfA0C9KbRWuf9uRvw== + dependencies: + "@stdlib/constants-float64-high-word-abs-mask" "^0.2.2" + "@stdlib/constants-float64-high-word-exponent-mask" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/math-base-special-kernel-cos" "^0.2.3" + "@stdlib/math-base-special-kernel-sin" "^0.2.3" + "@stdlib/math-base-special-rempio2" "^0.2.1" + "@stdlib/number-float64-base-get-high-word" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-erfc@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-erfc/-/math-base-special-erfc-0.2.4.tgz#043f096897d3ddaf2d42cb7bf3bae8e82c0d8799" + integrity sha512-tVI+mMnW+oDfQXwoH86sZ8q4ximpUXX6wZFCYZB6KcO5GXeKuvK74DnU0YyIm+sTV+r9WJiTSBEW9iVQLZOkzg== + dependencies: + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/math-base-special-exp" "^0.2.4" + "@stdlib/number-float64-base-set-low-word" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-erfcinv@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-erfcinv/-/math-base-special-erfcinv-0.2.3.tgz#b7d961b76964e90d84d84c44b7288842a422f5a9" + integrity sha512-B8u7WZiIh0+rX8VWNOwvjPWpmeKBHIQoJtIigUseBgbch/rmgV43k63MCkjh2u+V2SmcFo38yD94qJg5bYyWeA== + dependencies: + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/math-base-special-ln" "^0.2.4" + "@stdlib/math-base-special-sqrt" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-exp@^0.2.1", "@stdlib/math-base-special-exp@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-exp/-/math-base-special-exp-0.2.4.tgz#bf242b719f21f559a0cb454ee113c8b055e9a3ac" + integrity sha512-G6pZqu1wA4WwBj7DcnztA+/ro61wXJUTpKFLOwrIb2f/28pHGpA//Lub+3vAk6/ksAkhJ+qM/dfdM2ue7zLuEw== + dependencies: + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/math-base-special-ldexp" "^0.2.3" + "@stdlib/math-base-special-trunc" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-expm1@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-expm1/-/math-base-special-expm1-0.2.3.tgz#56e60252f3d6924200c0c3881ab0a9b5455f09af" + integrity sha512-uJlYZjPjG9X8owuwp1h1/iz9xf21v3dlyEAuutQ0NoacUDzZKVSCbQ3Of0i2Mujn+4N+kjCvEeph6cqhfYAl+A== + dependencies: + "@stdlib/constants-float64-exponent-bias" "^0.2.2" + "@stdlib/constants-float64-half-ln-two" "^0.2.2" + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/number-float64-base-from-words" "^0.2.2" + "@stdlib/number-float64-base-get-high-word" "^0.2.2" + "@stdlib/number-float64-base-set-high-word" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-factorial@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-factorial/-/math-base-special-factorial-0.2.1.tgz#c79862d441a3eb533dc449d17a357609bcf78748" + integrity sha512-uqsANeW4gHFzhgDrV9X0INEwO74MPzQvDVXbxY9+b0E13Vq2HHCi0GqdtPOWXdhOCUk8RkLRs9GizU3X6Coy8A== + dependencies: + "@stdlib/constants-float64-pinf" "^0.2.1" + "@stdlib/math-base-assert-is-integer" "^0.2.1" + "@stdlib/math-base-assert-is-nan" "^0.2.1" + "@stdlib/math-base-special-gamma" "^0.2.1" + +"@stdlib/math-base-special-factorial@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-factorial/-/math-base-special-factorial-0.3.0.tgz#2017b5fb0b70649fb3b6c917c31f22dac24a01ab" + integrity sha512-tXdXqstF4gmy4HpzALo3Bhkj2UQSlyk+PU3alWXZH5XtKUozHuXhQDnak+2c4w0JqnKxHq4mnaR2qgjfkDNZcA== + dependencies: + "@stdlib/constants-float64-max-safe-nth-factorial" "^0.1.0" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-assert-is-integer" "^0.2.5" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/math-base-special-gamma" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-floor@^0.2.1", "@stdlib/math-base-special-floor@^0.2.2", "@stdlib/math-base-special-floor@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-floor/-/math-base-special-floor-0.2.3.tgz#978f69d99f298e571cadf00d8d4b92111db4644d" + integrity sha512-zTkxVRawtWwJ4NmAT/1e+ZsIoBj1JqUquGOpiNVGNIKtyLOeCONZlZSbN7zuxPkshvmcSjpQ/VLKR8Tw/37E9A== + dependencies: + "@stdlib/math-base-napi-unary" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-fmod@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-fmod/-/math-base-special-fmod-0.1.0.tgz#48ac979b10196bd2a3f3b0dd8f89332160b37e76" + integrity sha512-osHwmEOT5MPWOXRx8y3wKCp362eGHIcJRt8LARJJICr/qTZlu1HMnZnbwuhfy1NIQzpJ8aLOhEdl2PrProTt3A== + dependencies: + "@stdlib/constants-float64-exponent-bias" "^0.2.2" + "@stdlib/constants-float64-high-word-abs-mask" "^0.2.2" + "@stdlib/constants-float64-high-word-exponent-mask" "^0.2.2" + "@stdlib/constants-float64-high-word-sign-mask" "^0.2.1" + "@stdlib/constants-float64-high-word-significand-mask" "^0.2.2" + "@stdlib/constants-float64-min-base2-exponent" "^0.2.2" + "@stdlib/math-base-napi-binary" "^0.3.0" + "@stdlib/number-float64-base-from-words" "^0.2.2" + "@stdlib/number-float64-base-to-words" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-gamma-delta-ratio@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-gamma-delta-ratio/-/math-base-special-gamma-delta-ratio-0.2.2.tgz#19e873541be1d7e63b9915b75cb58e29d88dfd49" + integrity sha512-lan+cfafH7aoyUxa88vLO+pYwLA+0uiyVFmCumxDemQUboCrTiNCYhBjONFGI/ljE3RukHoE3ZV4AccIcx526A== + dependencies: + "@stdlib/constants-float64-e" "^0.2.2" + "@stdlib/constants-float64-eps" "^0.2.2" + "@stdlib/constants-float64-gamma-lanczos-g" "^0.2.2" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-exp" "^0.2.4" + "@stdlib/math-base-special-factorial" "^0.2.1" + "@stdlib/math-base-special-floor" "^0.2.3" + "@stdlib/math-base-special-gamma" "^0.2.1" + "@stdlib/math-base-special-gamma-lanczos-sum" "^0.3.0" + "@stdlib/math-base-special-log1p" "^0.2.3" + "@stdlib/math-base-special-pow" "^0.3.0" + +"@stdlib/math-base-special-gamma-lanczos-sum-expg-scaled@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-gamma-lanczos-sum-expg-scaled/-/math-base-special-gamma-lanczos-sum-expg-scaled-0.3.0.tgz#7ccff49e23f54d96c1ac32df323c8a8f538b4cfb" + integrity sha512-hScjKZvueOK5piX84ZLIV3ZiYvtvYtcixN8psxkPIxJlN7Bd5nAmSkEOBL+T+LeW2RjmdEMXFFJMF7FsK1js/Q== + dependencies: + "@stdlib/math-base-napi-unary" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-gamma-lanczos-sum@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-gamma-lanczos-sum/-/math-base-special-gamma-lanczos-sum-0.3.0.tgz#804ec9259a336ae2ccac2156d6a42b66aa9f6c5b" + integrity sha512-q13p6r7G0TmbD54cU8QgG8wGgdGGznV9dNKiNszw+hOqCQ+1DqziG8I6vN64R3EQLP7QN4yVprZcmuXSK+fgsg== + dependencies: + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-gamma1pm1@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-gamma1pm1/-/math-base-special-gamma1pm1-0.2.2.tgz#c1773fa01bfcc1e68fcd25841bc8202bb491f027" + integrity sha512-lNT1lk0ifK2a/ta3GfR5V8KvfgkgheE44n5AQ/07BBfcVBMiAdqNuyjSMeWqsH/zVGzjU6G8+kLBzmaJXivPXQ== + dependencies: + "@stdlib/constants-float64-eps" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-special-expm1" "^0.2.3" + "@stdlib/math-base-special-gamma" "^0.2.1" + "@stdlib/math-base-special-ln" "^0.2.4" + "@stdlib/math-base-special-log1p" "^0.2.3" + +"@stdlib/math-base-special-gamma@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-gamma/-/math-base-special-gamma-0.2.1.tgz#33b9a84c5514a4fa7a290664d65a315922b211f1" + integrity sha512-Sfq1HnVoL4kN9EDHH3YparEAF0r7QD5jNFppUTOXmrqkofgImSl5tLttttnr2I7O9zsNhYkBAiTx9q0y25bAiA== + dependencies: + "@stdlib/constants-float64-eulergamma" "^0.2.1" + "@stdlib/constants-float64-ninf" "^0.2.1" + "@stdlib/constants-float64-pi" "^0.2.1" + "@stdlib/constants-float64-pinf" "^0.2.1" + "@stdlib/constants-float64-sqrt-two-pi" "^0.2.1" + "@stdlib/math-base-assert-is-integer" "^0.2.1" + "@stdlib/math-base-assert-is-nan" "^0.2.1" + "@stdlib/math-base-assert-is-negative-zero" "^0.2.1" + "@stdlib/math-base-special-abs" "^0.2.1" + "@stdlib/math-base-special-exp" "^0.2.1" + "@stdlib/math-base-special-floor" "^0.2.1" + "@stdlib/math-base-special-pow" "^0.2.1" + "@stdlib/math-base-special-sin" "^0.2.1" + +"@stdlib/math-base-special-gamma@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-gamma/-/math-base-special-gamma-0.3.0.tgz#234cc8e1e0cc25bfc2b5f15ac301838d1370dc74" + integrity sha512-YfW+e5xuSDoUxgpquXPrFtAbdwOzE7Kqt7M0dcAkDNot8/yUn+QmrDGzURyBVzUyhRm9SaC9bACHxTShdJkcuA== + dependencies: + "@stdlib/constants-float64-eulergamma" "^0.2.2" + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/constants-float64-pi" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/constants-float64-sqrt-two-pi" "^0.2.2" + "@stdlib/math-base-assert-is-integer" "^0.2.5" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-assert-is-negative-zero" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-exp" "^0.2.4" + "@stdlib/math-base-special-floor" "^0.2.3" + "@stdlib/math-base-special-pow" "^0.3.0" + "@stdlib/math-base-special-sin" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-gammainc@^0.2.1", "@stdlib/math-base-special-gammainc@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-gammainc/-/math-base-special-gammainc-0.2.2.tgz#8f60e3b0cdce1fa5dac3d18836f14032ea92b8ae" + integrity sha512-ffKZFiv/41SXs2Xms7IW3lPnICR898yfWAidq5uKjOLgRb3wrzNjq0sZ6EAVXvdBwyGULvSjyud28PpVhDLv3A== + dependencies: + "@stdlib/constants-float64-e" "^0.2.2" + "@stdlib/constants-float64-gamma-lanczos-g" "^0.2.2" + "@stdlib/constants-float64-max" "^0.2.2" + "@stdlib/constants-float64-max-ln" "^0.2.2" + "@stdlib/constants-float64-min-ln" "^0.2.2" + "@stdlib/constants-float64-pi" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/constants-float64-sqrt-eps" "^0.2.2" + "@stdlib/constants-float64-sqrt-two-pi" "^0.2.2" + "@stdlib/constants-float64-two-pi" "^0.2.2" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-erfc" "^0.2.4" + "@stdlib/math-base-special-exp" "^0.2.4" + "@stdlib/math-base-special-floor" "^0.2.3" + "@stdlib/math-base-special-gamma" "^0.3.0" + "@stdlib/math-base-special-gamma-lanczos-sum-expg-scaled" "^0.3.0" + "@stdlib/math-base-special-gamma1pm1" "^0.2.2" + "@stdlib/math-base-special-gammaln" "^0.2.2" + "@stdlib/math-base-special-ln" "^0.2.4" + "@stdlib/math-base-special-log1p" "^0.2.3" + "@stdlib/math-base-special-log1pmx" "^0.2.3" + "@stdlib/math-base-special-max" "^0.3.0" + "@stdlib/math-base-special-min" "^0.2.3" + "@stdlib/math-base-special-pow" "^0.3.0" + "@stdlib/math-base-special-powm1" "^0.3.0" + "@stdlib/math-base-special-sqrt" "^0.2.2" + "@stdlib/math-base-tools-continued-fraction" "^0.2.2" + "@stdlib/math-base-tools-evalpoly" "^0.2.2" + "@stdlib/math-base-tools-sum-series" "^0.2.2" + +"@stdlib/math-base-special-gammaincinv@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-gammaincinv/-/math-base-special-gammaincinv-0.2.2.tgz#f060929091f2197f5b64604446211b700e86cfae" + integrity sha512-bIZ94ob1rY87seDWsvBTBRxp8Ja2Y46DLtQYuaylHUQuK+I2xKy8XKL2ZHPsOfuwhXRqm+q+91PDjPEAdH1dQw== + dependencies: + "@stdlib/constants-float32-max" "^0.2.2" + "@stdlib/constants-float32-smallest-normal" "^0.2.2" + "@stdlib/constants-float64-ln-sqrt-two-pi" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/constants-float64-sqrt-two-pi" "^0.2.2" + "@stdlib/constants-float64-two-pi" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-erfcinv" "^0.2.3" + "@stdlib/math-base-special-exp" "^0.2.4" + "@stdlib/math-base-special-gamma" "^0.3.0" + "@stdlib/math-base-special-gammainc" "^0.2.2" + "@stdlib/math-base-special-gammaln" "^0.2.2" + "@stdlib/math-base-special-ln" "^0.2.4" + "@stdlib/math-base-special-min" "^0.2.3" + "@stdlib/math-base-special-pow" "^0.3.0" + "@stdlib/math-base-special-sqrt" "^0.2.2" + "@stdlib/math-base-tools-evalpoly" "^0.2.2" + debug "^2.6.9" + +"@stdlib/math-base-special-gammaln@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-gammaln/-/math-base-special-gammaln-0.2.2.tgz#98bf083f46753d914bf735e256d1851a924fcafd" + integrity sha512-opG6HUlspi/GLvQAr4pcwyAevm7BYuymlopgNZ1VulWUvksDpytalaX3zva0idlD2HvniKrDmzHngT1N9p0J1A== + dependencies: + "@stdlib/constants-float64-pi" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-assert-is-infinite" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-ln" "^0.2.4" + "@stdlib/math-base-special-sinpi" "^0.2.1" + "@stdlib/math-base-special-trunc" "^0.2.2" + +"@stdlib/math-base-special-gcd@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-gcd/-/math-base-special-gcd-0.2.1.tgz#583eb234aa6ccab9449a55cb9e96310145fd1af3" + integrity sha512-w10k9W176lDkbiDIwnmVr1nkTyypTQLwA3/CN9qEUmXh/u8NlxkSnDYBpArcWnxE0oFaIggw8sLJ58TuMvxMaw== + dependencies: + "@stdlib/constants-float64-ninf" "^0.2.1" + "@stdlib/constants-float64-pinf" "^0.2.1" + "@stdlib/constants-int32-max" "^0.2.1" + "@stdlib/math-base-assert-is-integer" "^0.2.1" + "@stdlib/math-base-assert-is-nan" "^0.2.1" + +"@stdlib/math-base-special-kernel-betainc@^0.2.1", "@stdlib/math-base-special-kernel-betainc@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-kernel-betainc/-/math-base-special-kernel-betainc-0.2.2.tgz#b04ea3cb371e00ed1153e2327efe145b46ac0908" + integrity sha512-DQwQUWQkmZtjRgdvZ1yZOEdAYLQoEUEndbr47Z69Oe6AgwKwxxpZUh09h9imKheFCFHLVnwVUz20azIM5KifQw== + dependencies: + "@stdlib/constants-float64-e" "^0.2.2" + "@stdlib/constants-float64-eps" "^0.2.2" + "@stdlib/constants-float64-gamma-lanczos-g" "^0.2.2" + "@stdlib/constants-float64-half-pi" "^0.2.2" + "@stdlib/constants-float64-max" "^0.2.2" + "@stdlib/constants-float64-max-ln" "^0.2.2" + "@stdlib/constants-float64-min-ln" "^0.2.2" + "@stdlib/constants-float64-pi" "^0.2.2" + "@stdlib/constants-float64-smallest-normal" "^0.2.2" + "@stdlib/constants-int32-max" "^0.3.0" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-asin" "^0.2.3" + "@stdlib/math-base-special-beta" "^0.3.0" + "@stdlib/math-base-special-binomcoef" "^0.2.3" + "@stdlib/math-base-special-exp" "^0.2.4" + "@stdlib/math-base-special-expm1" "^0.2.3" + "@stdlib/math-base-special-factorial" "^0.3.0" + "@stdlib/math-base-special-floor" "^0.2.3" + "@stdlib/math-base-special-gamma" "^0.3.0" + "@stdlib/math-base-special-gamma-delta-ratio" "^0.2.2" + "@stdlib/math-base-special-gamma-lanczos-sum-expg-scaled" "^0.3.0" + "@stdlib/math-base-special-gammainc" "^0.2.1" + "@stdlib/math-base-special-gammaln" "^0.2.2" + "@stdlib/math-base-special-ln" "^0.2.4" + "@stdlib/math-base-special-log1p" "^0.2.3" + "@stdlib/math-base-special-max" "^0.3.0" + "@stdlib/math-base-special-maxabs" "^0.3.0" + "@stdlib/math-base-special-min" "^0.2.3" + "@stdlib/math-base-special-minabs" "^0.2.3" + "@stdlib/math-base-special-pow" "^0.3.0" + "@stdlib/math-base-special-sqrt" "^0.2.2" + "@stdlib/math-base-tools-continued-fraction" "^0.2.2" + "@stdlib/math-base-tools-sum-series" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/math-base-special-kernel-betaincinv@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-kernel-betaincinv/-/math-base-special-kernel-betaincinv-0.2.2.tgz#9888c44d82bbe6a4fbf3b757be24a712accc5016" + integrity sha512-FVGf/clx14n3cPfRLKmd8+vAj90F200VxFOlf8kEULqGKbcQfoTHUfsViJ+r8l0cz1xf9K6uyfAl5KfqUIln4w== + dependencies: + "@stdlib/constants-float64-eps" "^0.2.2" + "@stdlib/constants-float64-half-pi" "^0.2.2" + "@stdlib/constants-float64-max" "^0.2.2" + "@stdlib/constants-float64-pi" "^0.2.2" + "@stdlib/constants-float64-smallest-normal" "^0.2.2" + "@stdlib/constants-float64-smallest-subnormal" "^0.2.2" + "@stdlib/constants-float64-sqrt-two" "^0.2.2" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-acos" "^0.2.3" + "@stdlib/math-base-special-asin" "^0.2.3" + "@stdlib/math-base-special-beta" "^0.3.0" + "@stdlib/math-base-special-betainc" "^0.2.2" + "@stdlib/math-base-special-cos" "^0.3.0" + "@stdlib/math-base-special-erfcinv" "^0.2.3" + "@stdlib/math-base-special-exp" "^0.2.4" + "@stdlib/math-base-special-expm1" "^0.2.3" + "@stdlib/math-base-special-floor" "^0.2.3" + "@stdlib/math-base-special-gamma-delta-ratio" "^0.2.2" + "@stdlib/math-base-special-gammaincinv" "^0.2.2" + "@stdlib/math-base-special-kernel-betainc" "^0.2.2" + "@stdlib/math-base-special-ldexp" "^0.2.3" + "@stdlib/math-base-special-ln" "^0.2.4" + "@stdlib/math-base-special-log1p" "^0.2.3" + "@stdlib/math-base-special-max" "^0.3.0" + "@stdlib/math-base-special-min" "^0.2.3" + "@stdlib/math-base-special-pow" "^0.3.0" + "@stdlib/math-base-special-round" "^0.3.0" + "@stdlib/math-base-special-signum" "^0.2.2" + "@stdlib/math-base-special-sin" "^0.3.0" + "@stdlib/math-base-special-sqrt" "^0.2.2" + "@stdlib/math-base-tools-evalpoly" "^0.2.2" + +"@stdlib/math-base-special-kernel-cos@^0.2.1", "@stdlib/math-base-special-kernel-cos@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-kernel-cos/-/math-base-special-kernel-cos-0.2.3.tgz#57db4c0299fad0d649f4984cc6c074647f4e24a3" + integrity sha512-K5FbN25SmEc5Z89GejUkrZpqCv05ZX6D7g9SUFcKWFJ1fwiZNgxrF8q4aJtGDQhuV3q66C1gaKJyQeLq/OI8lQ== + dependencies: + "@stdlib/math-base-napi-binary" "^0.3.0" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-kernel-sin@^0.2.1", "@stdlib/math-base-special-kernel-sin@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-kernel-sin/-/math-base-special-kernel-sin-0.2.3.tgz#80b10e6d64e614f939100ec155a2b45e38b8765c" + integrity sha512-PFnlGdapUaCaMXqZr+tG5Ioq+l4TCyGE5e8XEYlsyhNDILf0XE2ghHzlROA/wW365Arl4sPLWUoo4oH98DUPqw== + dependencies: + "@stdlib/math-base-napi-binary" "^0.3.0" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-ldexp@^0.2.1", "@stdlib/math-base-special-ldexp@^0.2.2", "@stdlib/math-base-special-ldexp@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-ldexp/-/math-base-special-ldexp-0.2.3.tgz#083d45ab30b82e3a7e6c19e79a9b7b1f220cb95f" + integrity sha512-yD4YisQGVTJmTJUshuzpaoq34sxJtrU+Aw4Ih39mzgXiQi6sh3E3nijB8WXDNKr2v05acUWJ1PRMkkJSfu16Kg== + dependencies: + "@stdlib/constants-float64-exponent-bias" "^0.2.2" + "@stdlib/constants-float64-max-base2-exponent" "^0.2.2" + "@stdlib/constants-float64-max-base2-exponent-subnormal" "^0.2.1" + "@stdlib/constants-float64-min-base2-exponent-subnormal" "^0.2.1" + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-assert-is-infinite" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-copysign" "^0.2.1" + "@stdlib/number-float32-base-to-word" "^0.2.2" + "@stdlib/number-float64-base-exponent" "^0.2.2" + "@stdlib/number-float64-base-from-words" "^0.2.2" + "@stdlib/number-float64-base-normalize" "^0.2.3" + "@stdlib/number-float64-base-to-words" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-ln@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-ln/-/math-base-special-ln-0.2.4.tgz#3fff35956181b4c16d2f805992d61077fed6e6cf" + integrity sha512-lSB47USaixrEmxwadT0/yByvTtxNhaRwN0FIXt5oj38bsgMXGW4V8xrANOy1N+hrn3KGfHJNDyFPYbXWVdMTIw== + dependencies: + "@stdlib/constants-float64-exponent-bias" "^0.2.2" + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.1" + "@stdlib/number-float64-base-get-high-word" "^0.2.2" + "@stdlib/number-float64-base-set-high-word" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-log1p@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-log1p/-/math-base-special-log1p-0.2.3.tgz#f71fce8f2e2aa6fcdaee05a21b4e2254da6ff258" + integrity sha512-1Pu3attNR+DcskIvhvyls+2KRZ0UCHQ/jP2tvgFI9bWDCgb4oEimXPzjFteGNg9Mj6WlAW2b9wU9tHt3bp8R3g== + dependencies: + "@stdlib/constants-float64-exponent-bias" "^0.2.2" + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.1" + "@stdlib/number-float64-base-get-high-word" "^0.2.2" + "@stdlib/number-float64-base-set-high-word" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-log1pmx@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-log1pmx/-/math-base-special-log1pmx-0.2.3.tgz#308fa40eed20ca507f2c524da27102d25f99fa7d" + integrity sha512-HfjDXcbFztm/GQRrn7a9FMYS0rm/4VPXWa50sYQzBHSYaEwYv5Y1awaZz+cA/ncuqAq1Mw0dfcwEMNRmZtnxEQ== + dependencies: + "@stdlib/constants-float64-eps" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-ln" "^0.2.4" + "@stdlib/math-base-special-log1p" "^0.2.3" + "@stdlib/math-base-tools-sum-series" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-max@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-max/-/math-base-special-max-0.2.1.tgz#505d10449b199530fbe7d287f5b362611aeae61c" + integrity sha512-jsA3x5azfclbULDFwvHjNlB2nciUDHwrw7qHP/QlSdJi47E1iBDNYdzhlOa3JKzblbrITpzgZEsGBcpCinEInQ== + dependencies: + "@stdlib/constants-float64-pinf" "^0.2.1" + "@stdlib/math-base-assert-is-nan" "^0.2.1" + "@stdlib/math-base-assert-is-positive-zero" "^0.2.1" + +"@stdlib/math-base-special-max@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-max/-/math-base-special-max-0.3.0.tgz#cdb72e28d17c1439fdb7ab94d59d837b185d1e48" + integrity sha512-yXsmdFCLHRB24l34Kn1kHZXHKoGqBxPY/5Mi+n5qLg+FwrX85ZG6KGVbO3DfcpG1NxDTcEKb1hxbUargI0P5fw== + dependencies: + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-assert-is-positive-zero" "^0.2.2" + "@stdlib/math-base-napi-binary" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-maxabs@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-maxabs/-/math-base-special-maxabs-0.3.0.tgz#9c7092a00342ca8f7ef87844ba9d33ca8fd15aea" + integrity sha512-SDj+rGD9itZ/YG2hKzhLX4Tf13SNJdOyNsMy1ezjec6Az3xJXKzv2wJAJIteo0KF6jQnEDkI/F6OIF65MY+o0g== + dependencies: + "@stdlib/math-base-napi-binary" "^0.2.1" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-max" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-min@^0.2.2", "@stdlib/math-base-special-min@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-min/-/math-base-special-min-0.2.3.tgz#0a3668687a5ff6426eb222adc8227f49a5fe86e5" + integrity sha512-tNrKnkcHCRVWzteZJpZ/xql9B6N6EzecnUVizDYqG9y66bOVtI+TADcQ5I/bijEwAIi2BjrIVeq/TBEgQEQBkw== + dependencies: + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-assert-is-negative-zero" "^0.2.2" + "@stdlib/math-base-napi-binary" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-minabs@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-minabs/-/math-base-special-minabs-0.2.3.tgz#c363e58a82b8b84f8d5152d25fada7726c7d1dc8" + integrity sha512-IV7PSL09S2GHmsxxtFgebPEwLm/wHnC1e1ulP8Uiuo2zinOiv4NXy2tpf9T+nq95d0ICFMnr9IGxFs6Nd74hRw== + dependencies: + "@stdlib/math-base-napi-binary" "^0.2.1" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-min" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-pow@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-pow/-/math-base-special-pow-0.2.1.tgz#f9576d1cf458cf45cd3e98431f18d55e4f902184" + integrity sha512-7SvgVzDkuilZKrHh4tPiXx9fypF/V7PSvAcUVjvcRj5kVEwv/15RpzlmCJlT9B20VPSx4gJ1S0UIA6xgmYFuAg== + dependencies: + "@stdlib/constants-float64-exponent-bias" "^0.2.1" + "@stdlib/constants-float64-high-word-abs-mask" "^0.2.1" + "@stdlib/constants-float64-high-word-significand-mask" "^0.2.1" + "@stdlib/constants-float64-ln-two" "^0.2.1" + "@stdlib/constants-float64-ninf" "^0.2.1" + "@stdlib/constants-float64-pinf" "^0.2.1" + "@stdlib/math-base-assert-is-infinite" "^0.2.1" + "@stdlib/math-base-assert-is-integer" "^0.2.1" + "@stdlib/math-base-assert-is-nan" "^0.2.1" + "@stdlib/math-base-assert-is-odd" "^0.2.1" + "@stdlib/math-base-special-abs" "^0.2.1" + "@stdlib/math-base-special-copysign" "^0.2.1" + "@stdlib/math-base-special-ldexp" "^0.2.1" + "@stdlib/math-base-special-sqrt" "^0.2.1" + "@stdlib/number-float64-base-get-high-word" "^0.2.1" + "@stdlib/number-float64-base-set-high-word" "^0.2.1" + "@stdlib/number-float64-base-set-low-word" "^0.2.1" + "@stdlib/number-float64-base-to-words" "^0.2.1" + "@stdlib/number-uint32-base-to-int32" "^0.2.1" + +"@stdlib/math-base-special-pow@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-pow/-/math-base-special-pow-0.3.0.tgz#5ce8f7d0e36d1a0ae710447a5951985e3b29129a" + integrity sha512-sMDYRUYGFyMXDHcCYy7hE07lV7jgI6rDspLMROKyESWcH4n8j54XE4/0w0i8OpdzR40H895MaPWU/tVnU1tP6w== + dependencies: + "@stdlib/constants-float64-exponent-bias" "^0.2.2" + "@stdlib/constants-float64-high-word-abs-mask" "^0.2.2" + "@stdlib/constants-float64-high-word-significand-mask" "^0.2.2" + "@stdlib/constants-float64-ln-two" "^0.2.2" + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/math-base-assert-is-infinite" "^0.2.2" + "@stdlib/math-base-assert-is-integer" "^0.2.5" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-assert-is-odd" "^0.3.0" + "@stdlib/math-base-napi-binary" "^0.3.0" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-copysign" "^0.2.1" + "@stdlib/math-base-special-ldexp" "^0.2.2" + "@stdlib/math-base-special-sqrt" "^0.2.2" + "@stdlib/number-float64-base-get-high-word" "^0.2.2" + "@stdlib/number-float64-base-set-high-word" "^0.2.2" + "@stdlib/number-float64-base-set-low-word" "^0.2.2" + "@stdlib/number-float64-base-to-words" "^0.2.2" + "@stdlib/number-uint32-base-to-int32" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-powm1@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-powm1/-/math-base-special-powm1-0.3.1.tgz#bb2179bed625abd7569a3faacd022ada2e08d18d" + integrity sha512-Pz7e2JlZH9EktJCDuyFPoT9IxMUSiZiJquyh2xB92NQQi9CAIdyaPUryNo36LxG65bne5GZF47MeiWCE8oWgiA== + dependencies: + "@stdlib/math-base-assert-is-infinite" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-binary" "^0.3.0" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-expm1" "^0.2.3" + "@stdlib/math-base-special-fmod" "^0.1.0" + "@stdlib/math-base-special-ln" "^0.2.4" + "@stdlib/math-base-special-pow" "^0.3.0" + "@stdlib/math-base-special-trunc" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-rempio2@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-rempio2/-/math-base-special-rempio2-0.2.1.tgz#ecf8f5c82952d47eda26b6e6d944ff3a3829adf3" + integrity sha512-ErV5EAe3SQCSijg4Pi4Z0sRPOGrODF3jkyCeiLM+iYj2TMOwDaOWQ0xCTME0p9G45TDrbZCLM5arxN83TfzgXQ== + dependencies: + "@stdlib/array-base-zeros" "^0.2.1" + "@stdlib/constants-float64-high-word-abs-mask" "^0.2.1" + "@stdlib/constants-float64-high-word-exponent-mask" "^0.2.1" + "@stdlib/constants-float64-high-word-significand-mask" "^0.2.1" + "@stdlib/math-base-special-floor" "^0.2.1" + "@stdlib/math-base-special-ldexp" "^0.2.1" + "@stdlib/math-base-special-round" "^0.2.1" + "@stdlib/number-float64-base-from-words" "^0.2.1" + "@stdlib/number-float64-base-get-high-word" "^0.2.1" + "@stdlib/number-float64-base-get-low-word" "^0.2.1" + +"@stdlib/math-base-special-round@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-round/-/math-base-special-round-0.2.1.tgz#f57a295e18eaf93179b686ad652ffc398c3f17f8" + integrity sha512-ibeKiN9z//6wS4H4uaa+vGnh/t1vJtZYXz+NqRVtwoP+nnE/mtL+fIrBlAnkIWVIH+smQPNNo8qsohjyGLBvUQ== + +"@stdlib/math-base-special-round@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-round/-/math-base-special-round-0.3.0.tgz#1658ff46324e003cd7977be61d1abad8429ec581" + integrity sha512-LwHmjWKL88u1HttH3Tmu3qrETb1KZ0J7uvhVkHvU7KgfCvCU8SX/wep47xv74rKDZMXyVEtmp7ZwpZPFm/zBcg== + dependencies: + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-assert-is-negative-zero" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.1" + "@stdlib/math-base-special-floor" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-roundn@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-roundn/-/math-base-special-roundn-0.2.2.tgz#c6d39f282a4f1c87997525cd116ac4cb81b2acaf" + integrity sha512-V9MOFB92s29sV0khneJWte8H4wiatypkthq6BP7HABcUxNLzW/oeBsLR+DpRhG04jaC1jowjcpV2PeyKdzvK0A== + dependencies: + "@stdlib/constants-float64-max-base10-exponent" "^0.2.2" + "@stdlib/constants-float64-max-safe-integer" "^0.2.2" + "@stdlib/constants-float64-min-base10-exponent" "^0.2.2" + "@stdlib/constants-float64-min-base10-exponent-subnormal" "^0.2.1" + "@stdlib/math-base-assert-is-infinite" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-binary" "^0.3.0" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-pow" "^0.3.0" + "@stdlib/math-base-special-round" "^0.3.0" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-signum@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-signum/-/math-base-special-signum-0.2.2.tgz#61d74eb4ef9bb3b7c2a6cc217ab722ba8804cdb2" + integrity sha512-cszwgkfeMTnUiORRWdWv6Q/tpoXkXkMYNMoAFO5qzHTuahnDP37Lkn8fTmCEtgHEasg3Cm69xLbqP0UDuNPHyA== + dependencies: + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-sin@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-sin/-/math-base-special-sin-0.2.1.tgz#eeeca3ef116b7f768198d8142cc3b6b108155bc2" + integrity sha512-IQ6+bzfiZ6/VUn5DIe6iwCsYERE1pwtAOsAWkgNZ1Ih3FzXUxdEOyHtv1zraPrVUb8mR+V5q7OfAGy8TCTnkUg== + dependencies: + "@stdlib/constants-float64-high-word-abs-mask" "^0.2.1" + "@stdlib/constants-float64-high-word-exponent-mask" "^0.2.1" + "@stdlib/math-base-special-kernel-cos" "^0.2.1" + "@stdlib/math-base-special-kernel-sin" "^0.2.1" + "@stdlib/math-base-special-rempio2" "^0.2.1" + "@stdlib/number-float64-base-get-high-word" "^0.2.1" + +"@stdlib/math-base-special-sin@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-sin/-/math-base-special-sin-0.3.0.tgz#a5a08a9f97a11bc80386dd0c11d45774aaf51705" + integrity sha512-n5qefWrHs0tZ8hM73EvixH5nFnTU/IW2BnY9/Gamtsu90oTusqOi0WB1jVOMypC8i1mJEHdOWrsip7aDRWk1kQ== + dependencies: + "@stdlib/constants-float64-high-word-abs-mask" "^0.2.2" + "@stdlib/constants-float64-high-word-exponent-mask" "^0.2.2" + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/math-base-special-kernel-cos" "^0.2.3" + "@stdlib/math-base-special-kernel-sin" "^0.2.3" + "@stdlib/math-base-special-rempio2" "^0.2.1" + "@stdlib/number-float64-base-get-high-word" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-sinpi@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-sinpi/-/math-base-special-sinpi-0.2.1.tgz#ba603ee34a05a5c0e086d7410ef2828096c5da17" + integrity sha512-Q3yCp1CoD7gemIILO28bU7iBn8OFiCgXm9vP/9q0tRBxmjtiUnjqbFd+3jRXdAmiCc/B/bPjwGBtVnCnrEMY9g== + dependencies: + "@stdlib/constants-float64-pi" "^0.2.1" + "@stdlib/math-base-assert-is-infinite" "^0.2.1" + "@stdlib/math-base-assert-is-nan" "^0.2.1" + "@stdlib/math-base-special-abs" "^0.2.1" + "@stdlib/math-base-special-copysign" "^0.2.1" + "@stdlib/math-base-special-cos" "^0.2.1" + "@stdlib/math-base-special-sin" "^0.2.1" + +"@stdlib/math-base-special-sqrt@^0.2.1", "@stdlib/math-base-special-sqrt@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-sqrt/-/math-base-special-sqrt-0.2.2.tgz#3bfc224cb84a09de4aca7e76f238c1c514aa8962" + integrity sha512-YWxe9vVE5blDbRPDAdZfU03vfGTBHy/8pLDa/qLz7SiJj5n5sVWKObdbMR2oPHF4c6DaZh4IYkrcHFleiY8YkQ== + dependencies: + "@stdlib/math-base-napi-unary" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-special-trunc@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-special-trunc/-/math-base-special-trunc-0.2.2.tgz#ba03662e824966c5bda0bee60ae169dd7673ca1b" + integrity sha512-cvizbo6oFEbdiv7BrtEMODGW+cJcBgyAIleJnIpCf75C722Y/IZgWikWhACSjv4stxGywFubx85B7uvm3vLgwA== + dependencies: + "@stdlib/math-base-napi-unary" "^0.2.3" + "@stdlib/math-base-special-ceil" "^0.2.1" + "@stdlib/math-base-special-floor" "^0.2.3" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/math-base-tools-continued-fraction@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-tools-continued-fraction/-/math-base-tools-continued-fraction-0.2.2.tgz#1ff108a177d97cf7a8c47b572222cf8d839f236e" + integrity sha512-5dm72lTXwSVOsBsOLF57RZqqHehRd9X3HKdQ3WhOoHx7fNc0lxJJEDjtK8gMdV3NvfoER1MBiGbs2h23oaK5qw== + dependencies: + "@stdlib/assert-has-generator-support" "^0.2.2" + "@stdlib/constants-float32-smallest-normal" "^0.2.2" + "@stdlib/constants-float64-eps" "^0.2.2" + "@stdlib/math-base-special-abs" "^0.2.2" + +"@stdlib/math-base-tools-evalpoly@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-tools-evalpoly/-/math-base-tools-evalpoly-0.2.2.tgz#a913a3d5c81ee5ebc51bab25c3c0f31640b48bff" + integrity sha512-vLvfkMkccXZGFiyI3GPf8Ayi6vPEZeHgENnoBDGC+eMIDIoVWmOpVWsjpUz8xtc5xGNsa1hKalSI40IrouHsYA== + dependencies: + "@stdlib/function-ctor" "^0.2.1" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/math-base-tools-sum-series@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/math-base-tools-sum-series/-/math-base-tools-sum-series-0.2.2.tgz#4975d6b0da7acf679c1bbad83707199191eb5977" + integrity sha512-P3X+jMONClp93ucJi1Up/x26uwL0kH20CMV9bLzcQyRY8Mceh7jPZuEwzGQR0jq/tJ/4J7AnHg4kdrx4Pd+BNA== + dependencies: + "@stdlib/assert-has-generator-support" "^0.2.2" + "@stdlib/constants-float64-eps" "^0.2.2" + "@stdlib/math-base-special-abs" "^0.2.2" + +"@stdlib/number-ctor@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/number-ctor/-/number-ctor-0.2.2.tgz#64f76c5b5e2adcde7f089e9fd6625881e35a6fb0" + integrity sha512-98pL4f1uiXVIw9uRV6t4xecMFUYRRTUoctsqDDV8MSRtKEYDzqkWCNz/auupJFJ135L1ejzkejh73fASsgcwKQ== + +"@stdlib/number-float32-base-to-word@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/number-float32-base-to-word/-/number-float32-base-to-word-0.2.2.tgz#dd506cadfb7e1fd9df7938638fa3f8ed59562d61" + integrity sha512-/I866ocLExPpAjgZnHAjeaBw3ZHg5tVPcRdJoTPEiBG2hwD/OonHdCsfB9lu6FxO6sbp7I9BR1JolCoEyrhmYg== + dependencies: + "@stdlib/array-float32" "^0.2.2" + "@stdlib/array-uint32" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/number-float64-base-exponent@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/number-float64-base-exponent/-/number-float64-base-exponent-0.2.2.tgz#d941fbb3b2d6ed93f1ce343e22557da8df16ab7f" + integrity sha512-mYivBQKCuu54ulorf5A5rIhFaGPjGvmtkxhvK14q7gzRA80si83dk8buUsLpeeYsakg7yLn10RCVjBEP9/gm7Q== + dependencies: + "@stdlib/constants-float64-exponent-bias" "^0.2.2" + "@stdlib/constants-float64-high-word-exponent-mask" "^0.2.2" + "@stdlib/number-float64-base-get-high-word" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/number-float64-base-from-words@^0.2.1", "@stdlib/number-float64-base-from-words@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/number-float64-base-from-words/-/number-float64-base-from-words-0.2.2.tgz#fec74abe7f00d0353f3289a5dfcf65d8adaa8f96" + integrity sha512-SzMDXSnIDZ8l3PDmtN9TPKTf0mUmh83kKCtj4FisKTcTbcmUmT/ovmrpMTiqdposymjHBieNvGiCz/K03NmlAA== + dependencies: + "@stdlib/array-float64" "^0.2.1" + "@stdlib/array-uint32" "^0.2.2" + "@stdlib/assert-is-little-endian" "^0.2.1" + "@stdlib/number-float64-base-to-words" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/number-float64-base-get-high-word@^0.2.1", "@stdlib/number-float64-base-get-high-word@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/number-float64-base-get-high-word/-/number-float64-base-get-high-word-0.2.2.tgz#9e76add4b9145b7d9dad07cba8b5a4eb87a880fd" + integrity sha512-LMNQAHdLZepKOFMRXAXLuq30GInmEdTtR0rO7Ka4F3m7KpYvw84JMyvZByMQHBu+daR6JNr2a/o9aFjmVIe51g== + dependencies: + "@stdlib/array-float64" "^0.2.1" + "@stdlib/array-uint32" "^0.2.2" + "@stdlib/assert-is-little-endian" "^0.2.1" + "@stdlib/number-float64-base-to-words" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/number-float64-base-get-low-word@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/number-float64-base-get-low-word/-/number-float64-base-get-low-word-0.2.2.tgz#e60d872465a6abac958769731923a6020f1f7631" + integrity sha512-VZjflvoQ9//rZwwuhl7uSLUnnscdIIYmBrHofnBHRjHwdLGUzSd9PM0iagtvI82OHw5QnydBYI4hohBeAAg+aQ== + dependencies: + "@stdlib/array-float64" "^0.2.1" + "@stdlib/array-uint32" "^0.2.2" + "@stdlib/assert-is-little-endian" "^0.2.1" + "@stdlib/number-float64-base-to-words" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/number-float64-base-normalize@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@stdlib/number-float64-base-normalize/-/number-float64-base-normalize-0.2.3.tgz#2277d647b726d2504092ecd25cc14d2580a4044c" + integrity sha512-HT+3fhYZOEg2JgHBWS/ysc9ZveQZV10weKbtxhLHOsvceQVp1GbThsLik62mU2/3f96S9MgiVfPfSDI3jnBoYw== + dependencies: + "@stdlib/constants-float64-smallest-normal" "^0.2.2" + "@stdlib/math-base-assert-is-infinite" "^0.2.2" + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-special-abs" "^0.2.1" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/number-float64-base-set-high-word@^0.2.1", "@stdlib/number-float64-base-set-high-word@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/number-float64-base-set-high-word/-/number-float64-base-set-high-word-0.2.2.tgz#9e92b12c52e7bbd8bd459089c3ef05dff9b1527e" + integrity sha512-bLvH15GJgX5URMaOOJAQgO8/dCJPYUQoXPZH7ecSC3XnnVMfWEf43knkjEGYCnWp4nro5hPRElbtdV4mKEjpUg== + dependencies: + "@stdlib/array-float64" "^0.2.1" + "@stdlib/array-uint32" "^0.2.2" + "@stdlib/assert-is-little-endian" "^0.2.1" + "@stdlib/number-float64-base-to-words" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/number-float64-base-set-low-word@^0.2.1", "@stdlib/number-float64-base-set-low-word@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/number-float64-base-set-low-word/-/number-float64-base-set-low-word-0.2.2.tgz#52f8260c14125487b7a235476e439b92dc75b62c" + integrity sha512-E1pGjTwacJ+Tkt5rKQNdwitKnM1iDgMlulYosNdn6CtvU0Pkq359bNhscMscxehdY3MifwuJpuGzDWD2EGUXzQ== + dependencies: + "@stdlib/array-float64" "^0.2.1" + "@stdlib/array-uint32" "^0.2.2" + "@stdlib/assert-is-little-endian" "^0.2.1" + "@stdlib/number-float64-base-to-words" "^0.2.1" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/number-float64-base-to-float32@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/number-float64-base-to-float32/-/number-float64-base-to-float32-0.2.2.tgz#5cb3bd9bf59fddd5747d50b5d54913178c562c3a" + integrity sha512-T5snDkVNZY6pomrSW/qLWQfZ9JHgqCFLi8jaaarfNj2o+5NMUuvvRifLUIacTm8/uC96xB0j3+wKTh1zbIV5ig== + dependencies: + "@stdlib/array-float32" "^0.2.1" + +"@stdlib/number-float64-base-to-words@^0.2.1", "@stdlib/number-float64-base-to-words@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/number-float64-base-to-words/-/number-float64-base-to-words-0.2.2.tgz#f0b9dce3c883461c75046659294d2fdcb95924da" + integrity sha512-nkFHHXoMhb3vcfl7ZvzgiNdqBdBfbKxHtgvDXRxrNQoVmyYbnjljjYD489d2/TAhe+Zvn7qph6QLgTod3zaeww== + dependencies: + "@stdlib/array-float64" "^0.2.1" + "@stdlib/array-uint32" "^0.2.2" + "@stdlib/assert-is-little-endian" "^0.2.1" + "@stdlib/os-byte-order" "^0.2.1" + "@stdlib/os-float-word-order" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + "@stdlib/utils-library-manifest" "^0.2.2" + +"@stdlib/number-uint32-base-to-int32@^0.2.1", "@stdlib/number-uint32-base-to-int32@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/number-uint32-base-to-int32/-/number-uint32-base-to-int32-0.2.2.tgz#d633a27e6eda53e6a8381aa4aa98e54199481466" + integrity sha512-NPADfdHE/3VEifKDttXM24dRj5YQqxwh2wTRD8fQrpHeaWiMIUo8yDqWrrFNIdLVAcqjL2SwWpo4VJ7oKTYaIA== + +"@stdlib/object-ctor@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@stdlib/object-ctor/-/object-ctor-0.2.1.tgz#a3e261cd65eecffcb03e2cc7472aa5058efba48f" + integrity sha512-HEIBBpfdQS9Nh5mmIqMk9fzedx6E0tayJrVa2FD7No86rVuq/Ikxq1QP7qNXm+i6z9iNUUS/lZq7BmJESWO/Zg== + +"@stdlib/os-byte-order@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/os-byte-order/-/os-byte-order-0.2.2.tgz#3bdb2e02854d2df9cfbf47ac06c5c4d8cc8f72bc" + integrity sha512-2y6rHAvZo43YmZu9u/E/7cnqZa0hNTLoIiMpV1IxQ/7iv03xZ45Z3xyvWMk0b7bAWwWL7iUknOAAmEchK/kHBA== + dependencies: + "@stdlib/assert-is-big-endian" "^0.2.1" + "@stdlib/assert-is-little-endian" "^0.2.1" + +"@stdlib/os-float-word-order@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/os-float-word-order/-/os-float-word-order-0.2.2.tgz#c87d26cc5ce118d764d95faba43ff750239a0c4b" + integrity sha512-5xpcEuxv/CudKctHS5czKdM7Bj/gC+sm/5R5bRPYyqxANM67t365j3v2v8rmmOxkEp9t0fa8Dggx8VmOkpJXaA== + dependencies: + "@stdlib/os-byte-order" "^0.2.1" + +"@stdlib/process-cwd@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/process-cwd/-/process-cwd-0.2.2.tgz#228df717417c335da7eeda37b6cc2b90fc3205f1" + integrity sha512-8Q/nA/ud5d5PEzzG6ZtKzcOw+RMLm5CWR8Wd+zVO5vcPj+JD7IV7M2lBhbzfUzr63Torrf/vEhT3cob8vUHV/A== + +"@stdlib/regexp-extended-length-path@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/regexp-extended-length-path/-/regexp-extended-length-path-0.2.2.tgz#5ea1664bc07de520236f8ab8201b160c9d9bffcd" + integrity sha512-z3jqauEsaxpsQU3rj1A1QnOgu17pyW5kt+Az8QkoTk7wqNE8HhPikI6k4o7XBHV689rSFWZCl4c4W+7JAiNObQ== + dependencies: + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/regexp-function-name@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/regexp-function-name/-/regexp-function-name-0.2.2.tgz#e85e4e94eb382c9c8416b18ffe712c934c2b2b1f" + integrity sha512-0z/KRsgHJJ3UQkmBeLH+Nin0hXIeA+Fw1T+mnG2V5CHnTA6FKlpxJxWrvwLEsRX7mR/DNtDp06zGyzMFE/4kig== + dependencies: + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/stats-base-dists-t-cdf@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/stats-base-dists-t-cdf/-/stats-base-dists-t-cdf-0.2.2.tgz#aa0d8ddca9bbe200d89de6060215cae047da0eb2" + integrity sha512-Oi64G193xMRxdgcfOXS7sID+WQ9HqlqcgzBPkNKJsbQvkAKSaeKKMt9yccYEPLVO7cEPLSLCXO9s4W1lrf4TGA== + dependencies: + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-special-betainc" "^0.2.1" + "@stdlib/math-base-special-pow" "^0.3.0" + "@stdlib/utils-constant-function" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/stats-base-dists-t-quantile@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/stats-base-dists-t-quantile/-/stats-base-dists-t-quantile-0.2.2.tgz#d3a009c9b131fa41a127d3ca67c51fb3347fbe89" + integrity sha512-Ca88AGDGLU3gJeGy0aUXh6gMXXFp5JM+zEciTLT3bhyerycJYKawxNVdHeur0J7LLnujGfeH/rOfSPFTHzgkHg== + dependencies: + "@stdlib/math-base-assert-is-nan" "^0.2.2" + "@stdlib/math-base-special-kernel-betaincinv" "^0.2.1" + "@stdlib/math-base-special-signum" "^0.2.2" + "@stdlib/math-base-special-sqrt" "^0.2.2" + "@stdlib/utils-constant-function" "^0.2.2" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/stats-base-mean@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/stats-base-mean/-/stats-base-mean-0.2.2.tgz#2a13fc79c2540ff26fabefefceebf89b6fec7ad3" + integrity sha512-I6TTyFOstbLBC4qOz5eMxoNUNbx5AheZBhyFHXlRREkwmRdxAbh2Eh1/cyWl7qxpDyBj9eOYu09ya5lSSO6z8A== + dependencies: + "@stdlib/stats-base-meanpn" "^0.2.1" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/stats-base-meanpn@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/stats-base-meanpn/-/stats-base-meanpn-0.2.2.tgz#15c30427b959189160909fecabeabcd9ab9e9bf4" + integrity sha512-PgXFet4HKbiYvQdpP3AT+BIQBj2Nw8LZxFyn1Pjm2+rmZIWCxQGJs03zmKLEwvtD+IBn4MEY+xmDOhsyeBqCSA== + dependencies: + "@stdlib/blas-ext-base-gapxsumpw" "^0.2.1" + "@stdlib/blas-ext-base-gsumpw" "^0.2.1" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/stats-base-variance@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/stats-base-variance/-/stats-base-variance-0.2.2.tgz#0f3f42dd476484a70cce01f1152de2e3202c6495" + integrity sha512-+CeGWpAry+ONvQEA0Z6GMH2DKqPgAzEwpqJLgHGwNLmBE1XdrRFPv53nyAS0P+LE1fbhWTDR1Jvn5iadgAvRoQ== + dependencies: + "@stdlib/stats-base-variancepn" "^0.2.1" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/stats-base-variancepn@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/stats-base-variancepn/-/stats-base-variancepn-0.2.2.tgz#ddc8b94ba852bbb05273942b3852522a8d664d48" + integrity sha512-91NZKAvhdwsbukRUnXCUVyk5f6lOggn4toNsAaocGSL+DC/e7WacTDJPA3F33YeRTh7oBQAAIgvtlD22IAoPAw== + dependencies: + "@stdlib/blas-ext-base-gsumpw" "^0.2.1" + "@stdlib/utils-define-nonenumerable-read-only-property" "^0.2.2" + +"@stdlib/stats-ttest2@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/stats-ttest2/-/stats-ttest2-0.2.2.tgz#b81c713c8f3b2132c70665eca6fbf8c5cc75b36b" + integrity sha512-MsQtiXZnZzwJfzClu+Ga8qsPBZXnvohVM+tmbuZ2rZijalXvZiOjCtQ8/CYtcj2jmUGnCPg3a0Xq+9FUBXowQw== + dependencies: + "@stdlib/assert-has-own-property" "^0.2.2" + "@stdlib/assert-is-boolean" "^0.2.2" + "@stdlib/assert-is-nan" "^0.2.2" + "@stdlib/assert-is-number" "^0.2.2" + "@stdlib/assert-is-number-array" "^0.2.2" + "@stdlib/assert-is-plain-object" "^0.2.2" + "@stdlib/assert-is-positive-integer" "^0.2.2" + "@stdlib/assert-is-string" "^0.2.2" + "@stdlib/assert-is-typed-array-like" "^0.2.2" + "@stdlib/constants-float64-ninf" "^0.2.2" + "@stdlib/constants-float64-pinf" "^0.2.2" + "@stdlib/error-tools-fmtprodmsg" "^0.2.2" + "@stdlib/math-base-special-abs" "^0.2.2" + "@stdlib/math-base-special-pow" "^0.3.0" + "@stdlib/math-base-special-roundn" "^0.2.2" + "@stdlib/math-base-special-sqrt" "^0.2.2" + "@stdlib/stats-base-dists-t-cdf" "^0.2.2" + "@stdlib/stats-base-dists-t-quantile" "^0.2.1" + "@stdlib/stats-base-mean" "^0.2.2" + "@stdlib/stats-base-variance" "^0.2.2" + "@stdlib/string-format" "^0.2.2" + "@stdlib/utils-define-read-only-property" "^0.2.2" + +"@stdlib/string-base-format-interpolate@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/string-base-format-interpolate/-/string-base-format-interpolate-0.2.2.tgz#67c22f0ca93ccffd0eb7e1c7276e487b26e786c6" + integrity sha512-i9nU9rAB2+o/RR66TS9iQ8x+YzeUDL1SGiAo6GY3hP6Umz5Dx9Qp/v8T69gWVsb4a1YSclz5+YeCWaFgwvPjKA== + +"@stdlib/string-base-format-tokenize@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/string-base-format-tokenize/-/string-base-format-tokenize-0.2.2.tgz#3ef9e49f6619ce39d9ba8399c9f4f63b3199289a" + integrity sha512-kXq2015i+LJjqth5dN+hYnvJXBSzRm8w0ABWB5tYAsIuQTpQK+mSo2muM8JBEFEnqUHAwpUsu2qNTK/9o8lsJg== + +"@stdlib/string-base-lowercase@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@stdlib/string-base-lowercase/-/string-base-lowercase-0.4.0.tgz#079b55c30be8f2ea5b63c7a24707852e63a8d1b8" + integrity sha512-IH35Z5e4T+S3b3SfYY39mUhrD2qvJVp4VS7Rn3+jgj4+C3syocuAPsJ8C4OQXWGfblX/N9ymizbpFBCiVvMW8w== + +"@stdlib/string-base-replace@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/string-base-replace/-/string-base-replace-0.2.2.tgz#d5f8967600d530b2b2938ba1a8c1b333b6be8c1f" + integrity sha512-Y4jZwRV4Uertw7AlA/lwaYl1HjTefSriN5+ztRcQQyDYmoVN3gzoVKLJ123HPiggZ89vROfC+sk/6AKvly+0CA== + +"@stdlib/string-format@^0.2.1", "@stdlib/string-format@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/string-format/-/string-format-0.2.2.tgz#5f2ac8cfb06e1b11be9ac8fc546075d0c77ec938" + integrity sha512-GUa50uxgMAtoItsxTbMmwkyhIwrCxCrsjzk3nAbLnt/1Kt1EWOWMwsALqZdD6K4V/xSJ4ns6PZur3W6w+vKk9g== + dependencies: + "@stdlib/string-base-format-interpolate" "^0.2.1" + "@stdlib/string-base-format-tokenize" "^0.2.2" + +"@stdlib/string-replace@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/string-replace/-/string-replace-0.2.2.tgz#c4a526abdec7ec031beeb87f98c4c9356fdce969" + integrity sha512-czNS5IU7sBuHjac45Y3VWUTsUoi82yc8JsMZrOMcjgSrEuDrVmA6sNJg7HC1DuSpdPjm/v9uUk102s1gIfk3Nw== + dependencies: + "@stdlib/assert-is-function" "^0.2.2" + "@stdlib/assert-is-regexp" "^0.2.2" + "@stdlib/assert-is-string" "^0.2.2" + "@stdlib/error-tools-fmtprodmsg" "^0.2.2" + "@stdlib/string-base-replace" "^0.2.2" + "@stdlib/string-format" "^0.2.2" + "@stdlib/utils-escape-regexp-string" "^0.2.2" + +"@stdlib/symbol-ctor@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/symbol-ctor/-/symbol-ctor-0.2.2.tgz#07a1477df50d9c54f4b79f810a0f0667e52c24d6" + integrity sha512-XsmiTfHnTb9jSPf2SoK3O0wrNOXMxqzukvDvtzVur1XBKfim9+seaAS4akmV1H3+AroAXQWVtde885e1B6jz1w== + +"@stdlib/utils-constant-function@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/utils-constant-function/-/utils-constant-function-0.2.2.tgz#48bd5d3c1e256ee0d2a8939f574c624ed4686578" + integrity sha512-ezRenGy5zU4R0JTfha/bpF8U+Hx0b52AZV++ca/pcaQVvPBRkgCsJacXX0eDbexoBB4+ZZ1vcyIi4RKJ0RRlbQ== + +"@stdlib/utils-constructor-name@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/utils-constructor-name/-/utils-constructor-name-0.2.2.tgz#3462fb107196d00698604aac32089353273c82a2" + integrity sha512-TBtO3MKDAf05ij5ajmyBCbpKKt0Lfahn5tu18gqds4PkFltgcw5tVZfSHY5DZ2HySJQ2GMMYjPW2Kbg6yPCSVg== + dependencies: + "@stdlib/assert-is-buffer" "^0.2.1" + "@stdlib/regexp-function-name" "^0.2.2" + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/utils-convert-path@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/utils-convert-path/-/utils-convert-path-0.2.2.tgz#7ffcd09a4f2384e0421a4154e31fe520ee0a62b7" + integrity sha512-8nNuAgt23Np9NssjShUrPK42c6gRTweGuoQw+yTpTfBR9VQv8WFyt048n8gRGUlAHizrdMNpEY9VAb7IBzpVYw== + dependencies: + "@stdlib/assert-is-string" "^0.2.2" + "@stdlib/error-tools-fmtprodmsg" "^0.2.2" + "@stdlib/regexp-extended-length-path" "^0.2.2" + "@stdlib/string-base-lowercase" "^0.4.0" + "@stdlib/string-format" "^0.2.2" + "@stdlib/string-replace" "^0.2.1" + +"@stdlib/utils-define-nonenumerable-read-only-property@^0.2.1", "@stdlib/utils-define-nonenumerable-read-only-property@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/utils-define-nonenumerable-read-only-property/-/utils-define-nonenumerable-read-only-property-0.2.2.tgz#80be97888609d1e471d20812cc5ba83a01f92e88" + integrity sha512-V3mpAesJemLYDKG376CsmoczWPE/4LKsp8xBvUxCt5CLNAx3J/1W39iZQyA5q6nY1RStGinGn1/dYZwa8ig0Uw== + dependencies: + "@stdlib/utils-define-property" "^0.2.3" + +"@stdlib/utils-define-property@^0.2.1", "@stdlib/utils-define-property@^0.2.3", "@stdlib/utils-define-property@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@stdlib/utils-define-property/-/utils-define-property-0.2.4.tgz#a8b6e120c829ee99ed81cf0111bb4c76ef85da9e" + integrity sha512-XlMdz7xwuw/sqXc9LbsV8XunCzZXjbZPC+OAdf4t4PBw4ZRwGzlTI6WED+f4PYR5Tp9F1cHgLPyMYCIBfA2zRg== + dependencies: + "@stdlib/error-tools-fmtprodmsg" "^0.2.1" + "@stdlib/string-format" "^0.2.1" + +"@stdlib/utils-define-read-only-property@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/utils-define-read-only-property/-/utils-define-read-only-property-0.2.2.tgz#e2a1b3e1f18caefef449eb6efa850b499822b49d" + integrity sha512-JcoavbkwxXFWtIHNypiL/5oCFU76WXYUFTeUMKPTNx0oyO2UjW9sKNuJfpoY9ksHPZNZGHRTsNbXVHabkNjVBQ== + dependencies: + "@stdlib/utils-define-property" "^0.2.3" + +"@stdlib/utils-escape-regexp-string@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/utils-escape-regexp-string/-/utils-escape-regexp-string-0.2.2.tgz#dd407c9324c1da4fa7b25e5c862502e8dc6d61ab" + integrity sha512-areCibzgpmvm6pGKBg+mXkSDJW4NxtS5jcAT7RtunGMdAYhA/I5whISMPaeJkIT2XhjjFkjKBaIs5pF6aPr4fQ== + dependencies: + "@stdlib/assert-is-string" "^0.2.1" + "@stdlib/error-tools-fmtprodmsg" "^0.2.2" + "@stdlib/string-format" "^0.2.2" + +"@stdlib/utils-eval@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/utils-eval/-/utils-eval-0.2.2.tgz#1591f1a5017d4fefacec6964a93724dfd1de6545" + integrity sha512-MaFpWZh3fGcTjUeozju5faXqH8w4MRVfpO/M5pon3osTM0by8zrKiI5D9oWqNVygb9JBd+etE+4tj2L1nr5j2A== + +"@stdlib/utils-get-prototype-of@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/utils-get-prototype-of/-/utils-get-prototype-of-0.2.2.tgz#a65def101deece8d81f3bbf892ababe4d61114bb" + integrity sha512-eDb1BAvt7GW/jduBkfuQrUsA9p09mV8RW20g0DWPaxci6ORYg/UB0tdbAA23aZz2QUoxdYY5s/UJxlq/GHwoKQ== + dependencies: + "@stdlib/assert-is-function" "^0.2.1" + "@stdlib/object-ctor" "^0.2.1" + "@stdlib/utils-native-class" "^0.2.1" + +"@stdlib/utils-global@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/utils-global/-/utils-global-0.2.2.tgz#61f875ef4ed74a091ed841127262961edef2d973" + integrity sha512-A4E8VFHn+1bpfJ4PA8H2b62CMQpjv2A+H3QDEBrouLFWne0wrx0TNq8vH6VYHxx9ZRxhgWQjfHiSAxtUJobrbQ== + dependencies: + "@stdlib/assert-is-boolean" "^0.2.1" + "@stdlib/error-tools-fmtprodmsg" "^0.2.2" + "@stdlib/string-format" "^0.2.2" + +"@stdlib/utils-library-manifest@^0.2.1", "@stdlib/utils-library-manifest@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/utils-library-manifest/-/utils-library-manifest-0.2.2.tgz#1908504dbdbb665a8b72ff40c4f426afefbd7fd2" + integrity sha512-YqzVLuBsB4wTqzdUtRArAjBJoT3x61iop2jFChXexhl6ejV3vDpHcukEEkqIOcJKut+1cG5TLJdexgHNt1C0NA== + dependencies: + "@stdlib/fs-resolve-parent-path" "^0.2.1" + "@stdlib/utils-convert-path" "^0.2.1" + debug "^2.6.9" + resolve "^1.1.7" + +"@stdlib/utils-native-class@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/utils-native-class/-/utils-native-class-0.2.2.tgz#dbb00a84e8c583cdd1bc40b163f1786dc44c4f09" + integrity sha512-cSn/FozbEpfR/FlJAoceQtZ8yUJFhZ8RFkbEsxW/7+H4o09yln3lBS0HSfUJISYNtpTNN/2/Fup88vmvwspvwA== + dependencies: + "@stdlib/assert-has-own-property" "^0.2.1" + "@stdlib/assert-has-tostringtag-support" "^0.2.2" + "@stdlib/symbol-ctor" "^0.2.2" + +"@stdlib/utils-type-of@^0.2.1": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@stdlib/utils-type-of/-/utils-type-of-0.2.2.tgz#517de513c043d7c0a48743593be41f67f0d51a9f" + integrity sha512-RLGFxPNgY9AtVVrFGdKO6Y3pOd/Ov2WA4O2/czZN/AbgYzbPdoF0KkfvHRIney6k+TtvoyYB8YqZXJ4G88f9BQ== + dependencies: + "@stdlib/utils-constructor-name" "^0.2.1" + "@stdlib/utils-global" "^0.2.2" + "@tsconfig/node10@^1.0.7": version "1.0.11" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" @@ -368,6 +2337,13 @@ dayjs@^1.8.34: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.18.tgz#835fa712aac52ab9dec8b1494098774ed7070a11" integrity sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA== +debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -798,6 +2774,11 @@ minimist@^1.2.6: dependencies: minimist "^1.2.6" +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + ms@^2.0.0: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -929,7 +2910,7 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" -resolve@^1.1.6: +resolve@^1.1.6, resolve@^1.1.7: version "1.22.10" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== From 3fed74ad7f48d7cb32d28c8e8965cca86caf3636 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Mon, 20 Oct 2025 13:57:48 +0100 Subject: [PATCH 11/36] standard loadSitesConfig and skip=true behaviour --- comparison/scripts/aggregate-analysis.ts | 28 ++++++++++++---------- comparison/scripts/compare-summaries.ts | 27 +++++++++++---------- comparison/scripts/condense-comparisons.ts | 27 +++++++++++---------- comparison/scripts/fetch-summaries.ts | 24 ++++--------------- comparison/scripts/shared-utils.ts | 23 ++++++++++++++++-- comparison/scripts/types.ts | 2 +- 6 files changed, 69 insertions(+), 62 deletions(-) diff --git a/comparison/scripts/aggregate-analysis.ts b/comparison/scripts/aggregate-analysis.ts index 2488888..97c3b24 100644 --- a/comparison/scripts/aggregate-analysis.ts +++ b/comparison/scripts/aggregate-analysis.ts @@ -3,7 +3,7 @@ import Anthropic from "@anthropic-ai/sdk"; import { readFile } from "fs/promises"; import path from "path"; import ttest2 from "@stdlib/stats-ttest2"; -import { writeToFile, stripMarkdownCodeBlocks, loadGeneList } from "./shared-utils"; +import { writeToFile, stripMarkdownCodeBlocks, loadGeneList, loadSitesConfig } from "./shared-utils"; import type { Config, CondensedComparison, @@ -103,15 +103,6 @@ function computeStatisticalMetric(valuesA: number[], valuesB: number[]): Statist // File Operations // ============================================================================ -/** - * Load site configuration - */ -async function loadConfig(): Promise { - const configPath = path.join(process.cwd(), "comparison/config/sites.json"); - const configContent = await readFile(configPath, "utf-8"); - return JSON.parse(configContent); -} - /** * Load all condensed comparisons for a model pair */ @@ -491,9 +482,20 @@ async function main() { // Load configuration console.log("\nLoading configuration..."); - const config = await loadConfig(); - const modelNames = config.sites.map((s) => s.name); - console.log(`Found ${modelNames.length} models: ${modelNames.join(", ")}`); + const { activeSites, skippedSites } = await loadSitesConfig(); + + if (skippedSites.length > 0) { + console.log(`Skipping ${skippedSites.length} site(s) with skip=true:`); + skippedSites.forEach((site) => console.log(` - ${site.name}`)); + } + + if (activeSites.length === 0) { + console.error("No active sites to process (all sites have skip=true)"); + process.exit(1); + } + + const modelNames = activeSites.map((s) => s.name); + console.log(`Found ${modelNames.length} active model(s): ${modelNames.join(", ")}`); // Load gene list console.log("\nLoading gene list..."); diff --git a/comparison/scripts/compare-summaries.ts b/comparison/scripts/compare-summaries.ts index 0a40dc8..0a7e89b 100644 --- a/comparison/scripts/compare-summaries.ts +++ b/comparison/scripts/compare-summaries.ts @@ -2,7 +2,7 @@ import "dotenv/config"; import Anthropic from "@anthropic-ai/sdk"; import { readFile } from "fs/promises"; import path from "path"; -import { writeToFile, stripMarkdownCodeBlocks, loadGeneList } from "./shared-utils"; +import { writeToFile, stripMarkdownCodeBlocks, loadGeneList, loadSitesConfig } from "./shared-utils"; import type { Config, SiteConfig, @@ -215,15 +215,6 @@ Respond ONLY with valid JSON, no other text.`; // File Operations // ============================================================================ -/** - * Load site configuration - */ -async function loadConfig(): Promise { - const configPath = path.join(process.cwd(), "comparison/config/sites.json"); - const configContent = await readFile(configPath, "utf-8"); - return JSON.parse(configContent); -} - /** * Load summary for a specific gene and model */ @@ -309,10 +300,20 @@ async function comparePair( */ async function main() { console.log("Loading configuration..."); - const config = await loadConfig(); + const { activeSites, skippedSites } = await loadSitesConfig(); + + if (skippedSites.length > 0) { + console.log(`Skipping ${skippedSites.length} site(s) with skip=true:`); + skippedSites.forEach((site) => console.log(` - ${site.name}`)); + } + + if (activeSites.length === 0) { + console.error("No active sites to process (all sites have skip=true)"); + process.exit(1); + } - const modelNames = config.sites.map((s) => s.name); - console.log(`Found ${modelNames.length} models: ${modelNames.join(", ")}`); + const modelNames = activeSites.map((s) => s.name); + console.log(`Found ${modelNames.length} active model(s): ${modelNames.join(", ")}`); console.log("\nLoading gene list..."); const geneIds = await loadGeneList(); diff --git a/comparison/scripts/condense-comparisons.ts b/comparison/scripts/condense-comparisons.ts index dd93d55..b5b28ab 100644 --- a/comparison/scripts/condense-comparisons.ts +++ b/comparison/scripts/condense-comparisons.ts @@ -2,7 +2,7 @@ import "dotenv/config"; import Anthropic from "@anthropic-ai/sdk"; import { readFile } from "fs/promises"; import path from "path"; -import { writeToFile, stripMarkdownCodeBlocks, loadGeneList } from "./shared-utils"; +import { writeToFile, stripMarkdownCodeBlocks, loadGeneList, loadSitesConfig } from "./shared-utils"; import type { Config, SiteConfig, @@ -180,15 +180,6 @@ Respond ONLY with valid JSON, no other text.`; // File Operations // ============================================================================ -/** - * Load site configuration - */ -async function loadConfig(): Promise { - const configPath = path.join(process.cwd(), "comparison/config/sites.json"); - const configContent = await readFile(configPath, "utf-8"); - return JSON.parse(configContent); -} - /** * Load comparison result for a specific gene and model pair */ @@ -274,10 +265,20 @@ async function condensePair( */ async function main() { console.log("Loading configuration..."); - const config = await loadConfig(); + const { activeSites, skippedSites } = await loadSitesConfig(); + + if (skippedSites.length > 0) { + console.log(`Skipping ${skippedSites.length} site(s) with skip=true:`); + skippedSites.forEach((site) => console.log(` - ${site.name}`)); + } + + if (activeSites.length === 0) { + console.error("No active sites to process (all sites have skip=true)"); + process.exit(1); + } - const modelNames = config.sites.map((s) => s.name); - console.log(`Found ${modelNames.length} models: ${modelNames.join(", ")}`); + const modelNames = activeSites.map((s) => s.name); + console.log(`Found ${modelNames.length} active model(s): ${modelNames.join(", ")}`); console.log("\nLoading gene list..."); const geneIds = await loadGeneList(); diff --git a/comparison/scripts/fetch-summaries.ts b/comparison/scripts/fetch-summaries.ts index 93b4522..0db3124 100644 --- a/comparison/scripts/fetch-summaries.ts +++ b/comparison/scripts/fetch-summaries.ts @@ -1,25 +1,13 @@ import "dotenv/config"; import axios from "axios"; -import { readFile } from "fs/promises"; import path from "path"; -import { writeToFile, getAuthCookie, sleep, loadGeneList } from "./shared-utils"; +import { writeToFile, getAuthCookie, sleep, loadGeneList, loadSitesConfig } from "./shared-utils"; import type { SiteConfig, Config, FetchResult } from "./types"; const POLL_INTERVAL_MS = 5000; // 5 seconds const MAX_RETRIES = 3; const MAX_POLL_ATTEMPTS = 120; // 10 minutes max (120 * 5 seconds) -/** - * Load site configuration - */ -async function loadConfig(): Promise { - // Use paths relative to project root, not dist directory - const configPath = path.join(process.cwd(), "comparison/config/sites.json"); - const configContent = await readFile(configPath, "utf-8"); - return JSON.parse(configContent); -} - - /** * Make API request to fetch AI expression summary */ @@ -154,19 +142,15 @@ async function fetchWithRetry( */ async function main() { console.log("Loading configuration..."); - const config = await loadConfig(); - - // Filter out sites with skipFetch: true - const activeSites = config.sites.filter((site) => !site.skipFetch); - const skippedSites = config.sites.filter((site) => site.skipFetch); + const { config, activeSites, skippedSites } = await loadSitesConfig(); if (skippedSites.length > 0) { - console.log(`Skipping ${skippedSites.length} site(s) with skipFetch=true:`); + console.log(`Skipping ${skippedSites.length} site(s) with skip=true:`); skippedSites.forEach((site) => console.log(` - ${site.name}`)); } if (activeSites.length === 0) { - console.error("No active sites to process (all sites have skipFetch=true)"); + console.error("No active sites to process (all sites have skip=true)"); process.exit(1); } diff --git a/comparison/scripts/shared-utils.ts b/comparison/scripts/shared-utils.ts index 07b3a0e..8c862e8 100644 --- a/comparison/scripts/shared-utils.ts +++ b/comparison/scripts/shared-utils.ts @@ -1,7 +1,8 @@ -import { writeFile, mkdir } from "fs/promises"; +import { writeFile, mkdir, readFile } from "fs/promises"; import https from 'https'; import querystring from 'querystring'; import path from 'path'; +import { Config, SiteConfig } from './types'; /** * Writes content to a file, creating parent directories if needed @@ -87,7 +88,6 @@ export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve * Filters out comments (lines starting with #) and empty lines */ export async function loadGeneList(): Promise { - const { readFile } = await import("fs/promises"); const geneListPath = path.join(process.cwd(), "comparison/input/gene-list.txt"); const content = await readFile(geneListPath, "utf-8"); return content @@ -95,3 +95,22 @@ export async function loadGeneList(): Promise { .map((line) => line.trim()) .filter((line) => line && !line.startsWith("#")); } + +/** + * Load site configuration from sites.json + * Returns full config, active sites (skip !== true), and skipped sites + */ +export async function loadSitesConfig(): Promise<{ + config: Config; + activeSites: SiteConfig[]; + skippedSites: SiteConfig[]; +}> { + const configPath = path.join(process.cwd(), "comparison/config/sites.json"); + const configContent = await readFile(configPath, "utf-8"); + const config: Config = JSON.parse(configContent); + + const activeSites = config.sites.filter((site) => !site.skip); + const skippedSites = config.sites.filter((site) => site.skip); + + return { config, activeSites, skippedSites }; +} diff --git a/comparison/scripts/types.ts b/comparison/scripts/types.ts index a8b177b..822bb67 100644 --- a/comparison/scripts/types.ts +++ b/comparison/scripts/types.ts @@ -7,7 +7,7 @@ export interface SiteConfig { hostname: string; appPath: string; model: string; - skipFetch?: boolean; + skip?: boolean; } export interface Config { From b96ed3c2d1c66a8abb0dfd82312a9dcfa93b7384 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Mon, 20 Oct 2025 18:29:13 +0100 Subject: [PATCH 12/36] full gene list and gene name feature --- comparison/input/gene-list.txt | 24 +++++- comparison/scripts/aggregate-analysis.ts | 86 ++++++++++++++++++++-- comparison/scripts/compare-summaries.ts | 9 ++- comparison/scripts/condense-comparisons.ts | 9 ++- comparison/scripts/fetch-summaries.ts | 9 ++- comparison/scripts/shared-utils.ts | 25 ++++++- 6 files changed, 139 insertions(+), 23 deletions(-) diff --git a/comparison/input/gene-list.txt b/comparison/input/gene-list.txt index 19074f5..57dd330 100644 --- a/comparison/input/gene-list.txt +++ b/comparison/input/gene-list.txt @@ -1,4 +1,20 @@ -AGAP001212 -AGAP000693 -AGAP000999 -AGAP009551 +AGAP001212 PGRP-LB +AGAP000693 CEC1 +AGAP000999 TOLL5A +AGAP009551 Sult +AGAP013543 anocin +AGAP001826 lipophorin +AGAP009646 Caudal +AGAP011294 DEF1 +AGAP004203 vitellogenin +AGAP011434 zinc carboxypeptidase A1 +AGAP001713 stearoyl CoA desaturase +AGAP006747 REL2 +AGAP011630 putative transcription factor +AGAP002851 Niemann-Pick C2 protein +AGAP000610 saglin +AGAP006414 chitinase +AGAP006796 peritrophin +AGAP012703 takeout +AGAP003751 cuticular protein gene +AGAP004036 heme peroxidase 7 diff --git a/comparison/scripts/aggregate-analysis.ts b/comparison/scripts/aggregate-analysis.ts index 97c3b24..b61f0fd 100644 --- a/comparison/scripts/aggregate-analysis.ts +++ b/comparison/scripts/aggregate-analysis.ts @@ -3,7 +3,7 @@ import Anthropic from "@anthropic-ai/sdk"; import { readFile } from "fs/promises"; import path from "path"; import ttest2 from "@stdlib/stats-ttest2"; -import { writeToFile, stripMarkdownCodeBlocks, loadGeneList, loadSitesConfig } from "./shared-utils"; +import { writeToFile, stripMarkdownCodeBlocks, loadGeneList, loadSitesConfig, type GeneEntry } from "./shared-utils"; import type { Config, CondensedComparison, @@ -99,6 +99,68 @@ function computeStatisticalMetric(valuesA: number[], valuesB: number[]): Statist }; } +// ============================================================================ +// Gene Name Formatting +// ============================================================================ + +/** + * Format gene ID with optional name + * Returns "AGAP001212 (Gene name)" if name exists, otherwise just "AGAP001212" + */ +function formatGeneWithName(geneId: string, geneNameMap: Map): string { + const name = geneNameMap.get(geneId); + return name ? `${geneId} (${name})` : geneId; +} + +/** + * Post-process aggregate report to interpolate gene names + * Replaces gene IDs with formatted strings in arrays and prose + */ +function interpolateGeneNames(report: AggregateReport, geneNameMap: Map): AggregateReport { + // Deep clone the report to avoid mutations + const processedReport = JSON.parse(JSON.stringify(report)) as AggregateReport; + + // Create regex pattern matching all gene IDs: \b(?:ID1|ID2|ID3)\b + const geneIds = Array.from(geneNameMap.keys()); + const allGeneIdsRegex = new RegExp(`\\b(?:${geneIds.join('|')})\\b`, 'g'); + + // Process position bias gene list + if (processedReport.quantitative_aggregates.position_bias.genes_with_contradictions.length > 0) { + processedReport.quantitative_aggregates.position_bias.genes_with_contradictions = + processedReport.quantitative_aggregates.position_bias.genes_with_contradictions.map((geneId) => + formatGeneWithName(geneId, geneNameMap) + ); + } + + // Process qualitative aggregates - all three dimensions + const dimensions = ["tone_and_style", "technical_detail_level", "structure_and_organization"] as const; + + for (const dimension of dimensions) { + const dimAggregate = processedReport.qualitative_aggregates[dimension]; + + // Process each field (summary_A, summary_B, comparison) + for (const field of ["summary_A", "summary_B", "comparison"] as const) { + const fieldAggregate = dimAggregate[field]; + + // Replace gene IDs in agreement distribution arrays + const dist = fieldAggregate.agreement_distribution; + dist.strong_agreement = dist.strong_agreement.map((id) => formatGeneWithName(id, geneNameMap)); + dist.mild_agreement = dist.mild_agreement.map((id) => formatGeneWithName(id, geneNameMap)); + dist.neutral_mixed = dist.neutral_mixed.map((id) => formatGeneWithName(id, geneNameMap)); + dist.mild_disagreement = dist.mild_disagreement.map((id) => formatGeneWithName(id, geneNameMap)); + dist.strong_disagreement = dist.strong_disagreement.map((id) => formatGeneWithName(id, geneNameMap)); + + // Replace gene IDs in disagreement_analysis prose using single regex pass + fieldAggregate.disagreement_analysis = fieldAggregate.disagreement_analysis.replace( + allGeneIdsRegex, + (match) => formatGeneWithName(match, geneNameMap) + ); + } + } + + return processedReport; +} + // ============================================================================ // File Operations // ============================================================================ @@ -434,12 +496,18 @@ function getSortedPair(modelA: string, modelB: string): [string, string] { async function processModelPair( modelA: string, modelB: string, - geneIds: string[], + genes: GeneEntry[], anthropic: Anthropic ): Promise { console.log(`\nProcessing model pair: ${modelA} <-> ${modelB}`); console.log("=".repeat(60)); + // Extract gene IDs for loading data files + const geneIds = genes.map((g) => g.id); + + // Create gene name mapping for interpolation + const geneNameMap = new Map(genes.map((g) => [g.id, g.name])); + // Load all condensed comparisons console.log("Loading condensed comparisons..."); const comparisons = await loadCondensedComparisons(geneIds, modelA, modelB); @@ -453,7 +521,7 @@ async function processModelPair( const qualitative_aggregates = await aggregateQualitativeData(comparisons, anthropic); // Build report - const report: AggregateReport = { + let report: AggregateReport = { model_pair: { model_A: modelA, model_B: modelB, @@ -463,6 +531,10 @@ async function processModelPair( qualitative_aggregates, }; + // Interpolate gene names into the report + console.log("\nInterpolating gene names into report..."); + report = interpolateGeneNames(report, geneNameMap); + // Save report const outputPath = path.join( process.cwd(), @@ -499,10 +571,10 @@ async function main() { // Load gene list console.log("\nLoading gene list..."); - const geneIds = await loadGeneList(); - console.log(`Found ${geneIds.length} genes to aggregate`); + const genes = await loadGeneList(); + console.log(`Found ${genes.length} genes to aggregate`); - if (geneIds.length === 0) { + if (genes.length === 0) { console.error("No genes found in gene-list.txt. Please add gene IDs (one per line)."); process.exit(1); } @@ -533,7 +605,7 @@ async function main() { // Process each model pair for (const [modelA, modelB] of pairs) { try { - await processModelPair(modelA, modelB, geneIds, anthropic); + await processModelPair(modelA, modelB, genes, anthropic); successCount++; } catch (error) { console.error(`\nERROR processing ${modelA} <-> ${modelB}:`, error instanceof Error ? error.message : error); diff --git a/comparison/scripts/compare-summaries.ts b/comparison/scripts/compare-summaries.ts index 0a7e89b..f84a094 100644 --- a/comparison/scripts/compare-summaries.ts +++ b/comparison/scripts/compare-summaries.ts @@ -316,14 +316,17 @@ async function main() { console.log(`Found ${modelNames.length} active model(s): ${modelNames.join(", ")}`); console.log("\nLoading gene list..."); - const geneIds = await loadGeneList(); - console.log(`Found ${geneIds.length} genes to compare`); + const genes = await loadGeneList(); + console.log(`Found ${genes.length} genes to compare`); - if (geneIds.length === 0) { + if (genes.length === 0) { console.error("No genes found in gene-list.txt. Please add gene IDs (one per line)."); process.exit(1); } + // Extract gene IDs for processing + const geneIds = genes.map(g => g.id); + // Validate that all required summary files exist console.log("\nValidating summary files..."); await checkAvailableSummaries(geneIds, modelNames); diff --git a/comparison/scripts/condense-comparisons.ts b/comparison/scripts/condense-comparisons.ts index b5b28ab..1f57d30 100644 --- a/comparison/scripts/condense-comparisons.ts +++ b/comparison/scripts/condense-comparisons.ts @@ -281,14 +281,17 @@ async function main() { console.log(`Found ${modelNames.length} active model(s): ${modelNames.join(", ")}`); console.log("\nLoading gene list..."); - const geneIds = await loadGeneList(); - console.log(`Found ${geneIds.length} genes to condense`); + const genes = await loadGeneList(); + console.log(`Found ${genes.length} genes to condense`); - if (geneIds.length === 0) { + if (genes.length === 0) { console.error("No genes found in gene-list.txt. Please add gene IDs (one per line)."); process.exit(1); } + // Extract gene IDs for processing + const geneIds = genes.map(g => g.id); + // Initialize Anthropic client const apiKey = process.env.ANTHROPIC_API_KEY; if (!apiKey) { diff --git a/comparison/scripts/fetch-summaries.ts b/comparison/scripts/fetch-summaries.ts index 0db3124..b2f93db 100644 --- a/comparison/scripts/fetch-summaries.ts +++ b/comparison/scripts/fetch-summaries.ts @@ -158,10 +158,10 @@ async function main() { activeSites.forEach((site) => console.log(` - ${site.name} (${site.model})`)); console.log("\nLoading gene list..."); - const geneIds = await loadGeneList(); - console.log(`Found ${geneIds.length} genes to process`); + const genes = await loadGeneList(); + console.log(`Found ${genes.length} genes to process`); - if (geneIds.length === 0) { + if (genes.length === 0) { console.error("No genes found in gene-list.txt. Please add gene IDs (one per line)."); process.exit(1); } @@ -182,7 +182,8 @@ async function main() { const results: FetchResult[] = []; // Process each gene across all sites - for (const geneId of geneIds) { + for (const gene of genes) { + const geneId = gene.id; console.log(`\n${"=".repeat(60)}`); console.log(`Processing gene: ${geneId}`); console.log("=".repeat(60)); diff --git a/comparison/scripts/shared-utils.ts b/comparison/scripts/shared-utils.ts index 8c862e8..249da7b 100644 --- a/comparison/scripts/shared-utils.ts +++ b/comparison/scripts/shared-utils.ts @@ -83,17 +83,38 @@ export async function getAuthCookie(username: string, password: string): Promise */ export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); +/** + * Gene entry with optional name/description + */ +export interface GeneEntry { + id: string; + name?: string; +} + /** * Load gene list from input file * Filters out comments (lines starting with #) and empty lines + * Supports optional gene names after the gene ID (separated by space) + * Format: "AGAP001212 ABC1 transporter protein" or just "AGAP001212" */ -export async function loadGeneList(): Promise { +export async function loadGeneList(): Promise { const geneListPath = path.join(process.cwd(), "comparison/input/gene-list.txt"); const content = await readFile(geneListPath, "utf-8"); return content .split("\n") .map((line) => line.trim()) - .filter((line) => line && !line.startsWith("#")); + .filter((line) => line && !line.startsWith("#")) + .map((line) => { + const spaceIndex = line.indexOf(" "); + if (spaceIndex === -1) { + // No space found, entire line is the gene ID + return { id: line }; + } + // Split on first space: ID and optional name + const id = line.substring(0, spaceIndex); + const name = line.substring(spaceIndex + 1).trim(); + return { id, name: name || undefined }; + }); } /** From f57700cc828bbb82bc6bd034186635399b87a935 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Tue, 21 Oct 2025 11:00:25 +0100 Subject: [PATCH 13/36] phase 1 configurable analysis_model --- comparison/config/sites.json | 5 +++ comparison/scripts/aggregate-analysis.ts | 46 +++++++++++++--------- comparison/scripts/compare-summaries.ts | 20 ++++++---- comparison/scripts/condense-comparisons.ts | 28 +++++++------ comparison/scripts/types.ts | 7 ++++ 5 files changed, 69 insertions(+), 37 deletions(-) diff --git a/comparison/config/sites.json b/comparison/config/sites.json index 5ee4630..ba16c61 100644 --- a/comparison/config/sites.json +++ b/comparison/config/sites.json @@ -1,4 +1,9 @@ { + "analysis_model": { + "model_string": "claude-sonnet-4-20250514", + "name": "claude4", + "platform": "anthropic" + }, "sites": [ { "name": "claude4", diff --git a/comparison/scripts/aggregate-analysis.ts b/comparison/scripts/aggregate-analysis.ts index b61f0fd..bdff6e8 100644 --- a/comparison/scripts/aggregate-analysis.ts +++ b/comparison/scripts/aggregate-analysis.ts @@ -6,6 +6,7 @@ import ttest2 from "@stdlib/stats-ttest2"; import { writeToFile, stripMarkdownCodeBlocks, loadGeneList, loadSitesConfig, type GeneEntry } from "./shared-utils"; import type { Config, + AnalysisModelConfig, CondensedComparison, StatisticalMetric, DescriptiveMetric, @@ -171,14 +172,15 @@ function interpolateGeneNames(report: AggregateReport, geneNameMap: Map { const comparisons: CondensedComparison[] = []; for (const geneId of geneIds) { const filePath = path.join( process.cwd(), - `comparison/data/condensed/${geneId}/${modelA}-${modelB}.json` + `comparison/data/condensed/${analysisModelName}/${geneId}/${modelA}-${modelB}.json` ); const content = await readFile(filePath, "utf-8"); comparisons.push(JSON.parse(content)); @@ -343,7 +345,8 @@ async function aggregateQualitativeField( dimension: string, fieldType: "summary_A" | "summary_B" | "comparison", statements: Map, - anthropic: Anthropic + anthropic: Anthropic, + analysisModelString: string ): Promise { // Build statements list for prompt const statementsList = Array.from(statements.entries()) @@ -384,7 +387,7 @@ Respond with JSON in this format: Respond ONLY with valid JSON, no other text.`; const message = await anthropic.messages.create({ - model: "claude-sonnet-4-20250514", + model: analysisModelString, max_tokens: 2000, messages: [ { @@ -421,7 +424,8 @@ Respond ONLY with valid JSON, no other text.`; async function aggregateQualitativeDimension( dimension: "tone_and_style" | "technical_detail_level" | "structure_and_organization", comparisons: CondensedComparison[], - anthropic: Anthropic + anthropic: Anthropic, + analysisModelString: string ): Promise { console.log(` Aggregating ${dimension}...`); @@ -439,9 +443,9 @@ async function aggregateQualitativeDimension( // Aggregate each field with AI const [summary_A, summary_B, comparison] = await Promise.all([ - aggregateQualitativeField(dimension, "summary_A", summaryA_statements, anthropic), - aggregateQualitativeField(dimension, "summary_B", summaryB_statements, anthropic), - aggregateQualitativeField(dimension, "comparison", comparison_statements, anthropic), + aggregateQualitativeField(dimension, "summary_A", summaryA_statements, anthropic, analysisModelString), + aggregateQualitativeField(dimension, "summary_B", summaryB_statements, anthropic, analysisModelString), + aggregateQualitativeField(dimension, "comparison", comparison_statements, anthropic, analysisModelString), ]); return { @@ -456,20 +460,23 @@ async function aggregateQualitativeDimension( */ async function aggregateQualitativeData( comparisons: CondensedComparison[], - anthropic: Anthropic + anthropic: Anthropic, + analysisModelString: string ): Promise { console.log("\nAggregating qualitative assessments..."); - const tone_and_style = await aggregateQualitativeDimension("tone_and_style", comparisons, anthropic); + const tone_and_style = await aggregateQualitativeDimension("tone_and_style", comparisons, anthropic, analysisModelString); const technical_detail_level = await aggregateQualitativeDimension( "technical_detail_level", comparisons, - anthropic + anthropic, + analysisModelString ); const structure_and_organization = await aggregateQualitativeDimension( "structure_and_organization", comparisons, - anthropic + anthropic, + analysisModelString ); return { @@ -497,7 +504,9 @@ async function processModelPair( modelA: string, modelB: string, genes: GeneEntry[], - anthropic: Anthropic + anthropic: Anthropic, + analysisModelName: string, + analysisModelString: string ): Promise { console.log(`\nProcessing model pair: ${modelA} <-> ${modelB}`); console.log("=".repeat(60)); @@ -510,7 +519,7 @@ async function processModelPair( // Load all condensed comparisons console.log("Loading condensed comparisons..."); - const comparisons = await loadCondensedComparisons(geneIds, modelA, modelB); + const comparisons = await loadCondensedComparisons(geneIds, modelA, modelB, analysisModelName); console.log(`Loaded ${comparisons.length} comparisons`); // Aggregate quantitative data @@ -518,7 +527,7 @@ async function processModelPair( const quantitative_aggregates = aggregateQuantitativeData(comparisons); // Aggregate qualitative data (with AI) - const qualitative_aggregates = await aggregateQualitativeData(comparisons, anthropic); + const qualitative_aggregates = await aggregateQualitativeData(comparisons, anthropic, analysisModelString); // Build report let report: AggregateReport = { @@ -538,7 +547,7 @@ async function processModelPair( // Save report const outputPath = path.join( process.cwd(), - `comparison/data/aggregate-reports/${modelA}-${modelB}-report.json` + `comparison/data/aggregate-reports/${analysisModelName}/${modelA}-${modelB}-report.json` ); await writeToFile(outputPath, JSON.stringify(report, null, 2)); console.log(`\nReport saved to: ${outputPath}`); @@ -554,7 +563,7 @@ async function main() { // Load configuration console.log("\nLoading configuration..."); - const { activeSites, skippedSites } = await loadSitesConfig(); + const { config, activeSites, skippedSites } = await loadSitesConfig(); if (skippedSites.length > 0) { console.log(`Skipping ${skippedSites.length} site(s) with skip=true:`); @@ -568,6 +577,7 @@ async function main() { const modelNames = activeSites.map((s) => s.name); console.log(`Found ${modelNames.length} active model(s): ${modelNames.join(", ")}`); + console.log(`Analysis model: ${config.analysis_model.name} (${config.analysis_model.model_string})`); // Load gene list console.log("\nLoading gene list..."); @@ -605,7 +615,7 @@ async function main() { // Process each model pair for (const [modelA, modelB] of pairs) { try { - await processModelPair(modelA, modelB, genes, anthropic); + await processModelPair(modelA, modelB, genes, anthropic, config.analysis_model.name, config.analysis_model.model_string); successCount++; } catch (error) { console.error(`\nERROR processing ${modelA} <-> ${modelB}:`, error instanceof Error ? error.message : error); diff --git a/comparison/scripts/compare-summaries.ts b/comparison/scripts/compare-summaries.ts index f84a094..6c67e56 100644 --- a/comparison/scripts/compare-summaries.ts +++ b/comparison/scripts/compare-summaries.ts @@ -6,6 +6,7 @@ import { writeToFile, stripMarkdownCodeBlocks, loadGeneList, loadSitesConfig } f import type { Config, SiteConfig, + AnalysisModelConfig, ExperimentSummary, Topic, ExpressionSummary, @@ -97,11 +98,12 @@ async function compareWithAI( modelBName: string, summaryA: ExpressionSummary, summaryB: ExpressionSummary, - anthropic: Anthropic + anthropic: Anthropic, + analysisModelString: string ): Promise<{ biological_content: BiologicalContent; qualitative_assessment: QualitativeAssessment; - quantitative_expression_mentions: QuantitativeMentions; + quantitative_expression_mentions: QuantitativeMentions; token_usage: { input_tokens: number; output_tokens: number; total_tokens: number }; }> { // Simplify summaries to reduce token usage @@ -173,7 +175,7 @@ Important distinctions: Respond ONLY with valid JSON, no other text.`; const message = await anthropic.messages.create({ - model: "claude-sonnet-4-20250514", + model: analysisModelString, max_tokens: 4000, messages: [ { @@ -265,7 +267,8 @@ async function comparePair( geneId: string, modelAName: string, modelBName: string, - anthropic: Anthropic + anthropic: Anthropic, + analysisModelString: string ): Promise { console.log(` Comparing ${modelAName} vs ${modelBName}...`); @@ -278,7 +281,7 @@ async function comparePair( const metricsB = calculateMetrics(summaryB); // Get AI comparison - const aiComparison = await compareWithAI(geneId, modelAName, modelBName, summaryA, summaryB, anthropic); + const aiComparison = await compareWithAI(geneId, modelAName, modelBName, summaryA, summaryB, anthropic, analysisModelString); return { model_A: modelAName, @@ -300,7 +303,7 @@ async function comparePair( */ async function main() { console.log("Loading configuration..."); - const { activeSites, skippedSites } = await loadSitesConfig(); + const { config, activeSites, skippedSites } = await loadSitesConfig(); if (skippedSites.length > 0) { console.log(`Skipping ${skippedSites.length} site(s) with skip=true:`); @@ -314,6 +317,7 @@ async function main() { const modelNames = activeSites.map((s) => s.name); console.log(`Found ${modelNames.length} active model(s): ${modelNames.join(", ")}`); + console.log(`Analysis model: ${config.analysis_model.name} (${config.analysis_model.model_string})`); console.log("\nLoading gene list..."); const genes = await loadGeneList(); @@ -366,12 +370,12 @@ async function main() { // Process each comparison pair for (const [modelA, modelB] of pairs) { try { - const result = await comparePair(geneId, modelA, modelB, anthropic); + const result = await comparePair(geneId, modelA, modelB, anthropic, config.analysis_model.model_string); // Save result const outputPath = path.join( process.cwd(), - `comparison/data/comparisons/${geneId}/${modelA}-vs-${modelB}.json` + `comparison/data/comparisons/${config.analysis_model.name}/${geneId}/${modelA}-vs-${modelB}.json` ); await writeToFile(outputPath, JSON.stringify(result, null, 2)); diff --git a/comparison/scripts/condense-comparisons.ts b/comparison/scripts/condense-comparisons.ts index 1f57d30..828ad17 100644 --- a/comparison/scripts/condense-comparisons.ts +++ b/comparison/scripts/condense-comparisons.ts @@ -6,6 +6,7 @@ import { writeToFile, stripMarkdownCodeBlocks, loadGeneList, loadSitesConfig } f import type { Config, SiteConfig, + AnalysisModelConfig, BiologicalContentCounts, BiologicalContent, QualitativeCategory, @@ -103,7 +104,8 @@ async function mergeQualitativeAssessments( geneId: string, assessment1: QualitativeAssessment, assessment2: QualitativeAssessment, - anthropic: Anthropic + anthropic: Anthropic, + analysisModelString: string ): Promise { // Swap labels in assessment2 so both assessments use the same A/B labels const assessment2_normalized = swapAssessmentLabels(assessment2); @@ -152,7 +154,7 @@ Respond with JSON in this format: Respond ONLY with valid JSON, no other text.`; const message = await anthropic.messages.create({ - model: "claude-sonnet-4-20250514", + model: analysisModelString, max_tokens: 2000, messages: [ { @@ -183,10 +185,10 @@ Respond ONLY with valid JSON, no other text.`; /** * Load comparison result for a specific gene and model pair */ -async function loadComparison(geneId: string, modelA: string, modelB: string): Promise { +async function loadComparison(geneId: string, modelA: string, modelB: string, analysisModelName: string): Promise { const comparisonPath = path.join( process.cwd(), - `comparison/data/comparisons/${geneId}/${modelA}-vs-${modelB}.json` + `comparison/data/comparisons/${analysisModelName}/${geneId}/${modelA}-vs-${modelB}.json` ); const content = await readFile(comparisonPath, "utf-8"); return JSON.parse(content); @@ -210,13 +212,15 @@ async function condensePair( geneId: string, modelA: string, modelB: string, - anthropic: Anthropic + anthropic: Anthropic, + analysisModelName: string, + analysisModelString: string ): Promise { console.log(` Condensing ${modelA} <-> ${modelB}...`); // Load both directions - const comparison_AvsB = await loadComparison(geneId, modelA, modelB); - const comparison_BvsA = await loadComparison(geneId, modelB, modelA); + const comparison_AvsB = await loadComparison(geneId, modelA, modelB, analysisModelName); + const comparison_BvsA = await loadComparison(geneId, modelB, modelA, analysisModelName); // Summarize biological content const observations_summary = summarizeBiologicalContent(comparison_AvsB, comparison_BvsA, "observations"); @@ -227,7 +231,8 @@ async function condensePair( geneId, comparison_AvsB.qualitative_assessment, comparison_BvsA.qualitative_assessment, - anthropic + anthropic, + analysisModelString ); // Average quantitative mentions @@ -265,7 +270,7 @@ async function condensePair( */ async function main() { console.log("Loading configuration..."); - const { activeSites, skippedSites } = await loadSitesConfig(); + const { config, activeSites, skippedSites } = await loadSitesConfig(); if (skippedSites.length > 0) { console.log(`Skipping ${skippedSites.length} site(s) with skip=true:`); @@ -279,6 +284,7 @@ async function main() { const modelNames = activeSites.map((s) => s.name); console.log(`Found ${modelNames.length} active model(s): ${modelNames.join(", ")}`); + console.log(`Analysis model: ${config.analysis_model.name} (${config.analysis_model.model_string})`); console.log("\nLoading gene list..."); const genes = await loadGeneList(); @@ -325,12 +331,12 @@ async function main() { // Process each model pair for (const [modelA, modelB] of pairs) { try { - const result = await condensePair(geneId, modelA, modelB, anthropic); + const result = await condensePair(geneId, modelA, modelB, anthropic, config.analysis_model.name, config.analysis_model.model_string); // Save result const outputPath = path.join( process.cwd(), - `comparison/data/condensed/${geneId}/${modelA}-${modelB}.json` + `comparison/data/condensed/${config.analysis_model.name}/${geneId}/${modelA}-${modelB}.json` ); await writeToFile(outputPath, JSON.stringify(result, null, 2)); diff --git a/comparison/scripts/types.ts b/comparison/scripts/types.ts index 822bb67..84aa436 100644 --- a/comparison/scripts/types.ts +++ b/comparison/scripts/types.ts @@ -10,7 +10,14 @@ export interface SiteConfig { skip?: boolean; } +export interface AnalysisModelConfig { + model_string: string; + name: string; + platform: 'anthropic' | 'openai'; +} + export interface Config { + analysis_model: AnalysisModelConfig; sites: SiteConfig[]; endpoint: string; projectId: string; From 55f1c6396758a7f085bfed44a241267d1324ec70 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Tue, 21 Oct 2025 11:06:14 +0100 Subject: [PATCH 14/36] update Claude notes --- CLAUDE-ai-provider-comparison.md | 341 +++++++++++++++++++++++++++++-- 1 file changed, 327 insertions(+), 14 deletions(-) diff --git a/CLAUDE-ai-provider-comparison.md b/CLAUDE-ai-provider-comparison.md index 500c5a5..8bb7c3d 100644 --- a/CLAUDE-ai-provider-comparison.md +++ b/CLAUDE-ai-provider-comparison.md @@ -108,22 +108,25 @@ expression-shepherd/ │ │ ├── gpt5/ │ │ └── gpt4o/ │ ├── comparisons/ -│ │ └── {geneId}/ -│ │ ├── claude4-vs-gpt5.json -│ │ ├── gpt5-vs-claude4.json -│ │ ├── claude4-vs-gpt4o.json -│ │ ├── gpt4o-vs-claude4.json -│ │ ├── gpt5-vs-gpt4o.json -│ │ └── gpt4o-vs-gpt5.json +│ │ └── {analysisModel}/ (e.g., claude4) +│ │ └── {geneId}/ +│ │ ├── claude4-vs-gpt5.json +│ │ ├── gpt5-vs-claude4.json +│ │ ├── claude4-vs-gpt4o.json +│ │ ├── gpt4o-vs-claude4.json +│ │ ├── gpt5-vs-gpt4o.json +│ │ └── gpt4o-vs-gpt5.json │ ├── condensed/ -│ │ └── {geneId}/ -│ │ ├── claude4-gpt4o.json -│ │ ├── claude4-gpt5.json -│ │ └── gpt4o-gpt5.json +│ │ └── {analysisModel}/ (e.g., claude4) +│ │ └── {geneId}/ +│ │ ├── claude4-gpt4o.json +│ │ ├── claude4-gpt5.json +│ │ └── gpt4o-gpt5.json │ └── aggregate-reports/ -│ ├── claude4-gpt4o-report.json -│ ├── claude4-gpt5-report.json -│ └── gpt4o-gpt5-report.json +│ └── {analysisModel}/ (e.g., claude4) +│ ├── claude4-gpt4o-report.json +│ ├── claude4-gpt5-report.json +│ └── gpt4o-gpt5-report.json ``` ## Technical Notes @@ -244,6 +247,316 @@ Already available in repository: - Model anonymity maintained through all analysis phases until final report generation - Reproducible process that can be re-run with new gene sets +--- + +# Development History + +## Phase 1 (COMPLETED): Configurable Analysis Model + +**Status**: ✅ Completed 2025-10-21 + +**Summary**: Added `analysis_model` configuration to enable switching between different AI models for comparison analysis (Phases 2-3). This allows comparing how different AI models (e.g., Claude 4 vs GPT-5) perform the analysis itself, independent of the source summaries being compared. + +**Changes implemented**: +1. Added `analysis_model` config to `comparison/config/sites.json`: + ```json + { + "analysis_model": { + "model_string": "claude-sonnet-4-20250514", + "name": "claude4", + "platform": "anthropic" + }, + "sites": [...], + ... + } + ``` + +2. Updated TypeScript types (`comparison/scripts/types.ts`): + - Added `AnalysisModelConfig` interface + - Updated `Config` interface to include `analysis_model` + +3. Refactored all comparison scripts to use config: + - `compare-summaries.ts`: Uses `config.analysis_model.model_string` for API calls + - `condense-comparisons.ts`: Uses `config.analysis_model.model_string` for AI merging + - `aggregate-analysis.ts`: Uses `config.analysis_model.model_string` for aggregation + +4. Updated output paths to include analysis model subdirectories: + - `comparisons/{analysisModel}/{geneId}/...` + - `condensed/{analysisModel}/{geneId}/...` + - `aggregate-reports/{analysisModel}/...` + +5. Migrated existing files into `claude4/` subdirectories (20 genes, all comparisons/condensed/aggregate-reports) + +**Build verification**: ✅ TypeScript compiles without errors + +--- + +## Phase 2 (TODO): Add OpenAI Platform Support + +**Goal**: Enable using OpenAI models (GPT-4o, GPT-5) as the analysis model for comparison scripts, not just Anthropic Claude. + +**Context**: Currently all three comparison scripts (`compare-summaries.ts`, `condense-comparisons.ts`, `aggregate-analysis.ts`) use Anthropic's API directly. To support OpenAI as an analysis model, we need platform abstraction. + +### Implementation Strategy + +**Approach**: Unified ad-hoc JSON prompting for both platforms (no `response_format` for OpenAI) + +Rationale: +- Simplicity: One code path instead of platform-specific branching +- GPT-5/4o are excellent at following ad-hoc JSON instructions +- Methodologically cleaner: Identical prompting strategy across platforms +- Already battle-tested: Claude ad-hoc approach working well in comparison scripts + +**Note**: Unlike `src/main.ts` (which uses OpenAI's `zodResponseFormat`), we intentionally use simpler ad-hoc JSON instructions for both platforms. + +### Tasks + +#### 1. Check/Upgrade OpenAI Package + +```bash +# Check current version +yarn list --pattern openai + +# Upgrade if needed for GPT-5 support +yarn add openai@latest +``` + +#### 2. Create Platform Abstraction (`comparison/scripts/shared-utils.ts`) + +Add these functions to `shared-utils.ts`: + +**a) AI Client Factory** +```typescript +import Anthropic from "@anthropic-ai/sdk"; +import OpenAI from "openai"; +import type { AnalysisModelConfig } from "./types"; + +export function createAIClient(analysisModel: AnalysisModelConfig): Anthropic | OpenAI { + if (analysisModel.platform === "anthropic") { + const apiKey = process.env.ANTHROPIC_API_KEY; + if (!apiKey) throw new Error("Missing ANTHROPIC_API_KEY in .env"); + return new Anthropic({ apiKey }); + } else if (analysisModel.platform === "openai") { + const apiKey = process.env.OPENAI_API_KEY; + if (!apiKey) throw new Error("Missing OPENAI_API_KEY in .env"); + return new OpenAI({ apiKey }); + } else { + throw new Error(`Unsupported platform: ${analysisModel.platform}`); + } +} +``` + +**b) Unified AI Call Function** +```typescript +export interface AICallResult { + parsed: any; // The parsed JSON response + token_usage: { + input_tokens: number; + output_tokens: number; + total_tokens: number; + }; +} + +export async function callAI( + client: Anthropic | OpenAI, + platform: 'anthropic' | 'openai', + modelString: string, + prompt: string, + maxTokens: number +): Promise { + let rawResponse: string; + let usage: any; + + if (platform === "anthropic") { + const anthropic = client as Anthropic; + const message = await anthropic.messages.create({ + model: modelString, + max_tokens: maxTokens, + messages: [{ role: "user", content: prompt }], + }); + + rawResponse = message.content[0].type === "text" + ? message.content[0].text + : JSON.stringify(message.content[0]); + usage = message.usage; + + // Strip markdown for Anthropic + rawResponse = stripMarkdownCodeBlocks(rawResponse); + + return { + parsed: JSON.parse(rawResponse), + token_usage: { + input_tokens: usage.input_tokens, + output_tokens: usage.output_tokens, + total_tokens: usage.input_tokens + usage.output_tokens, + }, + }; + } else { + const openai = client as OpenAI; + + // Ad-hoc JSON instructions (no response_format) + const completion = await openai.chat.completions.create({ + model: modelString, + messages: [{ role: "user", content: prompt }], + max_tokens: maxTokens, + }); + + rawResponse = completion.choices[0].message.content || ""; + usage = completion.usage; + + // Apply markdown stripping to OpenAI too (just in case) + rawResponse = stripMarkdownCodeBlocks(rawResponse); + + return { + parsed: JSON.parse(rawResponse), + token_usage: { + input_tokens: usage?.prompt_tokens || 0, + output_tokens: usage?.completion_tokens || 0, + total_tokens: usage?.total_tokens || 0, + }, + }; + } +} +``` + +#### 3. Update Comparison Scripts + +**For each script** (`compare-summaries.ts`, `condense-comparisons.ts`, `aggregate-analysis.ts`): + +a) **Change imports**: +```typescript +// Before: +import Anthropic from "@anthropic-ai/sdk"; + +// After: +import Anthropic from "@anthropic-ai/sdk"; +import OpenAI from "openai"; +import { createAIClient, callAI, type AICallResult } from "./shared-utils"; +``` + +b) **Update `main()` function**: +```typescript +// Before: +const apiKey = process.env.ANTHROPIC_API_KEY; +if (!apiKey) { + console.error("Missing ANTHROPIC_API_KEY in .env file"); + process.exit(1); +} +const anthropic = new Anthropic({ apiKey }); + +// After: +const aiClient = createAIClient(config.analysis_model); +``` + +c) **Replace direct API calls** with `callAI()`: +```typescript +// Before: +const message = await anthropic.messages.create({ + model: analysisModelString, + max_tokens: 4000, + messages: [{ role: "user", content: prompt }], +}); +const responseText = message.content[0].type === "text" ? message.content[0].text : ...; +const cleanedResponse = stripMarkdownCodeBlocks(responseText); +const parsed = JSON.parse(cleanedResponse); +// ... extract token_usage from message.usage + +// After: +const { parsed, token_usage } = await callAI( + aiClient, + config.analysis_model.platform, + config.analysis_model.model_string, + prompt, + 4000 +); +``` + +d) **Update function signatures** to accept generic client: +```typescript +// Before: +async function compareWithAI(..., anthropic: Anthropic, ...): Promise<...> { + +// After: +async function compareWithAI(..., aiClient: Anthropic | OpenAI, platform: 'anthropic' | 'openai', ...): Promise<...> { +``` + +#### 4. Testing Strategy + +1. **Build verification**: `yarn build` (check for TypeScript errors) +2. **Test with Anthropic** (baseline - should still work): + ```json + // sites.json + "analysis_model": { + "model_string": "claude-sonnet-4-20250514", + "name": "claude4", + "platform": "anthropic" + } + ``` + - Run: `yarn comparison:compare` on a single gene (comment out others in `gene-list.txt`) + - Verify output in `comparisons/claude4/` + +3. **Test with OpenAI**: + ```json + // sites.json + "analysis_model": { + "model_string": "gpt-4o-2024-11-20", // or latest GPT-5 model + "name": "gpt4o", + "platform": "openai" + } + ``` + - Run: `yarn comparison:compare` on same test gene + - Verify output in `comparisons/gpt4o/` + - Compare JSON structure matches (same schema) + - Check for JSON parsing errors in logs + +4. **Regression test**: Run all three scripts end-to-end with both platforms + +### GPT-5 Model String + +When GPT-5 support is available, update model string: +```json +"analysis_model": { + "model_string": "gpt-5-YYYY-MM-DD", // TBD when GPT-5 launches + "name": "gpt5", + "platform": "openai" +} +``` + +Check OpenAI docs for exact model identifier and context window limits. + +### Potential Issues & Solutions + +**Issue**: OpenAI doesn't follow JSON format despite ad-hoc instructions +- **Solution**: Add retry logic with error message feedback, or fall back to `response_format` (add as Phase 2.1 if needed) + +**Issue**: Different token limits between models +- **Solution**: Make `maxTokens` configurable per model in `analysis_model` config: + ```json + "analysis_model": { + "model_string": "...", + "name": "...", + "platform": "...", + "max_tokens": { + "compare": 4000, + "condense": 2000, + "aggregate": 2000 + } + } + ``` + +**Issue**: Rate limiting differences between platforms +- **Solution**: Add platform-specific rate limiting in `callAI()` function + +### Success Criteria + +- ✅ All three comparison scripts work with both `platform: "anthropic"` and `platform: "openai"` +- ✅ No TypeScript compilation errors +- ✅ JSON parsing works reliably for both platforms +- ✅ Token usage tracking accurate for both platforms +- ✅ Can switch analysis models by editing `sites.json` alone (no code changes) +- ✅ Existing claude4 analysis results unchanged (regression test) + +--- # Appendices From d6907f5ad0d5cb85509c583689aae5a5d4c19df6 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Tue, 21 Oct 2025 11:35:24 +0100 Subject: [PATCH 15/36] first pass openai support --- comparison/scripts/aggregate-analysis.ts | 76 +++++++------- comparison/scripts/compare-summaries.ts | 69 +++++-------- comparison/scripts/condense-comparisons.ts | 57 +++++----- comparison/scripts/shared-utils.ts | 98 +++++++++++++++++- package.json | 2 +- yarn.lock | 115 +-------------------- 6 files changed, 194 insertions(+), 223 deletions(-) diff --git a/comparison/scripts/aggregate-analysis.ts b/comparison/scripts/aggregate-analysis.ts index bdff6e8..4bcb9bd 100644 --- a/comparison/scripts/aggregate-analysis.ts +++ b/comparison/scripts/aggregate-analysis.ts @@ -1,9 +1,10 @@ import "dotenv/config"; import Anthropic from "@anthropic-ai/sdk"; +import OpenAI from "openai"; import { readFile } from "fs/promises"; import path from "path"; import ttest2 from "@stdlib/stats-ttest2"; -import { writeToFile, stripMarkdownCodeBlocks, loadGeneList, loadSitesConfig, type GeneEntry } from "./shared-utils"; +import { writeToFile, createAIClient, callAI, loadGeneList, loadSitesConfig, type GeneEntry, type AICallResult } from "./shared-utils"; import type { Config, AnalysisModelConfig, @@ -345,7 +346,8 @@ async function aggregateQualitativeField( dimension: string, fieldType: "summary_A" | "summary_B" | "comparison", statements: Map, - anthropic: Anthropic, + aiClient: Anthropic | OpenAI, + platform: 'anthropic' | 'openai', analysisModelString: string ): Promise { // Build statements list for prompt @@ -386,24 +388,14 @@ Respond with JSON in this format: Respond ONLY with valid JSON, no other text.`; - const message = await anthropic.messages.create({ - model: analysisModelString, - max_tokens: 2000, - messages: [ - { - role: "user", - content: prompt, - }, - ], - }); - - const responseText = - message.content[0].type === "text" ? message.content[0].text : JSON.stringify(message.content[0]); - - const cleanedResponse = stripMarkdownCodeBlocks(responseText); - try { - const parsed = JSON.parse(cleanedResponse); + const { parsed } = await callAI( + aiClient, + platform, + analysisModelString, + prompt, + 2000 + ); const consistency_score = calculateConsistencyScore(parsed.agreement_distribution); return { @@ -413,7 +405,7 @@ Respond ONLY with valid JSON, no other text.`; disagreement_analysis: parsed.disagreement_analysis, }; } catch (error) { - console.error("Failed to parse AI response:", cleanedResponse); + console.error("Failed to parse AI response"); throw new Error(`Failed to parse AI response: ${error}`); } } @@ -424,7 +416,8 @@ Respond ONLY with valid JSON, no other text.`; async function aggregateQualitativeDimension( dimension: "tone_and_style" | "technical_detail_level" | "structure_and_organization", comparisons: CondensedComparison[], - anthropic: Anthropic, + aiClient: Anthropic | OpenAI, + platform: 'anthropic' | 'openai', analysisModelString: string ): Promise { console.log(` Aggregating ${dimension}...`); @@ -443,9 +436,9 @@ async function aggregateQualitativeDimension( // Aggregate each field with AI const [summary_A, summary_B, comparison] = await Promise.all([ - aggregateQualitativeField(dimension, "summary_A", summaryA_statements, anthropic, analysisModelString), - aggregateQualitativeField(dimension, "summary_B", summaryB_statements, anthropic, analysisModelString), - aggregateQualitativeField(dimension, "comparison", comparison_statements, anthropic, analysisModelString), + aggregateQualitativeField(dimension, "summary_A", summaryA_statements, aiClient, platform, analysisModelString), + aggregateQualitativeField(dimension, "summary_B", summaryB_statements, aiClient, platform, analysisModelString), + aggregateQualitativeField(dimension, "comparison", comparison_statements, aiClient, platform, analysisModelString), ]); return { @@ -460,22 +453,25 @@ async function aggregateQualitativeDimension( */ async function aggregateQualitativeData( comparisons: CondensedComparison[], - anthropic: Anthropic, + aiClient: Anthropic | OpenAI, + platform: 'anthropic' | 'openai', analysisModelString: string ): Promise { console.log("\nAggregating qualitative assessments..."); - const tone_and_style = await aggregateQualitativeDimension("tone_and_style", comparisons, anthropic, analysisModelString); + const tone_and_style = await aggregateQualitativeDimension("tone_and_style", comparisons, aiClient, platform, analysisModelString); const technical_detail_level = await aggregateQualitativeDimension( "technical_detail_level", comparisons, - anthropic, + aiClient, + platform, analysisModelString ); const structure_and_organization = await aggregateQualitativeDimension( "structure_and_organization", comparisons, - anthropic, + aiClient, + platform, analysisModelString ); @@ -504,7 +500,8 @@ async function processModelPair( modelA: string, modelB: string, genes: GeneEntry[], - anthropic: Anthropic, + aiClient: Anthropic | OpenAI, + platform: 'anthropic' | 'openai', analysisModelName: string, analysisModelString: string ): Promise { @@ -527,7 +524,7 @@ async function processModelPair( const quantitative_aggregates = aggregateQuantitativeData(comparisons); // Aggregate qualitative data (with AI) - const qualitative_aggregates = await aggregateQualitativeData(comparisons, anthropic, analysisModelString); + const qualitative_aggregates = await aggregateQualitativeData(comparisons, aiClient, platform, analysisModelString); // Build report let report: AggregateReport = { @@ -589,13 +586,8 @@ async function main() { process.exit(1); } - // Initialize Anthropic client - const apiKey = process.env.ANTHROPIC_API_KEY; - if (!apiKey) { - console.error("Missing ANTHROPIC_API_KEY in .env file"); - process.exit(1); - } - const anthropic = new Anthropic({ apiKey }); + // Initialize AI client (Anthropic or OpenAI based on config) + const aiClient = createAIClient(config.analysis_model); // Generate all unique model pairs (alphabetically sorted) const pairs: Array<[string, string]> = []; @@ -615,7 +607,15 @@ async function main() { // Process each model pair for (const [modelA, modelB] of pairs) { try { - await processModelPair(modelA, modelB, genes, anthropic, config.analysis_model.name, config.analysis_model.model_string); + await processModelPair( + modelA, + modelB, + genes, + aiClient, + config.analysis_model.platform, + config.analysis_model.name, + config.analysis_model.model_string + ); successCount++; } catch (error) { console.error(`\nERROR processing ${modelA} <-> ${modelB}:`, error instanceof Error ? error.message : error); diff --git a/comparison/scripts/compare-summaries.ts b/comparison/scripts/compare-summaries.ts index 6c67e56..0b6844d 100644 --- a/comparison/scripts/compare-summaries.ts +++ b/comparison/scripts/compare-summaries.ts @@ -1,8 +1,9 @@ import "dotenv/config"; import Anthropic from "@anthropic-ai/sdk"; +import OpenAI from "openai"; import { readFile } from "fs/promises"; import path from "path"; -import { writeToFile, stripMarkdownCodeBlocks, loadGeneList, loadSitesConfig } from "./shared-utils"; +import { writeToFile, createAIClient, callAI, loadGeneList, loadSitesConfig, type AICallResult } from "./shared-utils"; import type { Config, SiteConfig, @@ -90,7 +91,7 @@ function simplifySummary(summary: ExpressionSummary): SimplifiedSummary { // ============================================================================ /** - * Generate comparison using Anthropic API + * Generate comparison using AI (Anthropic or OpenAI) */ async function compareWithAI( geneId: string, @@ -98,7 +99,8 @@ async function compareWithAI( modelBName: string, summaryA: ExpressionSummary, summaryB: ExpressionSummary, - anthropic: Anthropic, + aiClient: Anthropic | OpenAI, + platform: 'anthropic' | 'openai', analysisModelString: string ): Promise<{ biological_content: BiologicalContent; @@ -174,41 +176,21 @@ Important distinctions: Respond ONLY with valid JSON, no other text.`; - const message = await anthropic.messages.create({ - model: analysisModelString, - max_tokens: 4000, - messages: [ - { - role: "user", - content: prompt, - }, - ], - }); - - const responseText = - message.content[0].type === "text" ? message.content[0].text : JSON.stringify(message.content[0]); - - // Strip markdown code blocks if present - const cleanedResponse = stripMarkdownCodeBlocks(responseText); - try { - const parsed = JSON.parse(cleanedResponse); - - // Extract token usage from message - const input_tokens = message.usage.input_tokens; - const output_tokens = message.usage.output_tokens; - const total_tokens = input_tokens + output_tokens; + const { parsed, token_usage } = await callAI( + aiClient, + platform, + analysisModelString, + prompt, + 4000 + ); return { ...parsed, - token_usage: { - input_tokens, - output_tokens, - total_tokens, - }, + token_usage, }; } catch (error) { - console.error("Failed to parse AI response:", cleanedResponse); + console.error("Failed to parse AI response"); throw new Error(`Failed to parse AI response: ${error}`); } } @@ -267,7 +249,8 @@ async function comparePair( geneId: string, modelAName: string, modelBName: string, - anthropic: Anthropic, + aiClient: Anthropic | OpenAI, + platform: 'anthropic' | 'openai', analysisModelString: string ): Promise { console.log(` Comparing ${modelAName} vs ${modelBName}...`); @@ -281,7 +264,7 @@ async function comparePair( const metricsB = calculateMetrics(summaryB); // Get AI comparison - const aiComparison = await compareWithAI(geneId, modelAName, modelBName, summaryA, summaryB, anthropic, analysisModelString); + const aiComparison = await compareWithAI(geneId, modelAName, modelBName, summaryA, summaryB, aiClient, platform, analysisModelString); return { model_A: modelAName, @@ -336,13 +319,8 @@ async function main() { await checkAvailableSummaries(geneIds, modelNames); console.log("All required summary files are present!"); - // Initialize Anthropic client - const apiKey = process.env.ANTHROPIC_API_KEY; - if (!apiKey) { - console.error("Missing ANTHROPIC_API_KEY in .env file"); - process.exit(1); - } - const anthropic = new Anthropic({ apiKey }); + // Initialize AI client (Anthropic or OpenAI based on config) + const aiClient = createAIClient(config.analysis_model); // Generate all pairwise comparisons (bidirectional) const pairs: Array<[string, string]> = []; @@ -370,7 +348,14 @@ async function main() { // Process each comparison pair for (const [modelA, modelB] of pairs) { try { - const result = await comparePair(geneId, modelA, modelB, anthropic, config.analysis_model.model_string); + const result = await comparePair( + geneId, + modelA, + modelB, + aiClient, + config.analysis_model.platform, + config.analysis_model.model_string + ); // Save result const outputPath = path.join( diff --git a/comparison/scripts/condense-comparisons.ts b/comparison/scripts/condense-comparisons.ts index 828ad17..8219b73 100644 --- a/comparison/scripts/condense-comparisons.ts +++ b/comparison/scripts/condense-comparisons.ts @@ -1,8 +1,9 @@ import "dotenv/config"; import Anthropic from "@anthropic-ai/sdk"; +import OpenAI from "openai"; import { readFile } from "fs/promises"; import path from "path"; -import { writeToFile, stripMarkdownCodeBlocks, loadGeneList, loadSitesConfig } from "./shared-utils"; +import { writeToFile, createAIClient, callAI, loadGeneList, loadSitesConfig, type AICallResult } from "./shared-utils"; import type { Config, SiteConfig, @@ -104,7 +105,8 @@ async function mergeQualitativeAssessments( geneId: string, assessment1: QualitativeAssessment, assessment2: QualitativeAssessment, - anthropic: Anthropic, + aiClient: Anthropic | OpenAI, + platform: 'anthropic' | 'openai', analysisModelString: string ): Promise { // Swap labels in assessment2 so both assessments use the same A/B labels @@ -153,27 +155,17 @@ Respond with JSON in this format: Respond ONLY with valid JSON, no other text.`; - const message = await anthropic.messages.create({ - model: analysisModelString, - max_tokens: 2000, - messages: [ - { - role: "user", - content: prompt, - }, - ], - }); - - const responseText = - message.content[0].type === "text" ? message.content[0].text : JSON.stringify(message.content[0]); - - const cleanedResponse = stripMarkdownCodeBlocks(responseText); - try { - const parsed = JSON.parse(cleanedResponse); + const { parsed } = await callAI( + aiClient, + platform, + analysisModelString, + prompt, + 2000 + ); return parsed; } catch (error) { - console.error("Failed to parse AI response:", cleanedResponse); + console.error("Failed to parse AI response"); throw new Error(`Failed to parse AI response: ${error}`); } } @@ -212,7 +204,8 @@ async function condensePair( geneId: string, modelA: string, modelB: string, - anthropic: Anthropic, + aiClient: Anthropic | OpenAI, + platform: 'anthropic' | 'openai', analysisModelName: string, analysisModelString: string ): Promise { @@ -231,7 +224,8 @@ async function condensePair( geneId, comparison_AvsB.qualitative_assessment, comparison_BvsA.qualitative_assessment, - anthropic, + aiClient, + platform, analysisModelString ); @@ -298,13 +292,8 @@ async function main() { // Extract gene IDs for processing const geneIds = genes.map(g => g.id); - // Initialize Anthropic client - const apiKey = process.env.ANTHROPIC_API_KEY; - if (!apiKey) { - console.error("Missing ANTHROPIC_API_KEY in .env file"); - process.exit(1); - } - const anthropic = new Anthropic({ apiKey }); + // Initialize AI client (Anthropic or OpenAI based on config) + const aiClient = createAIClient(config.analysis_model); // Generate all unique model pairs (alphabetically sorted) const pairs: Array<[string, string]> = []; @@ -331,7 +320,15 @@ async function main() { // Process each model pair for (const [modelA, modelB] of pairs) { try { - const result = await condensePair(geneId, modelA, modelB, anthropic, config.analysis_model.name, config.analysis_model.model_string); + const result = await condensePair( + geneId, + modelA, + modelB, + aiClient, + config.analysis_model.platform, + config.analysis_model.name, + config.analysis_model.model_string + ); // Save result const outputPath = path.join( diff --git a/comparison/scripts/shared-utils.ts b/comparison/scripts/shared-utils.ts index 249da7b..ea8bca7 100644 --- a/comparison/scripts/shared-utils.ts +++ b/comparison/scripts/shared-utils.ts @@ -2,7 +2,9 @@ import { writeFile, mkdir, readFile } from "fs/promises"; import https from 'https'; import querystring from 'querystring'; import path from 'path'; -import { Config, SiteConfig } from './types'; +import Anthropic from "@anthropic-ai/sdk"; +import OpenAI from "openai"; +import { Config, SiteConfig, AnalysisModelConfig } from './types'; /** * Writes content to a file, creating parent directories if needed @@ -135,3 +137,97 @@ export async function loadSitesConfig(): Promise<{ return { config, activeSites, skippedSites }; } + +/** + * AI Client factory - creates either Anthropic or OpenAI client based on platform + */ +export function createAIClient(analysisModel: AnalysisModelConfig): Anthropic | OpenAI { + if (analysisModel.platform === "anthropic") { + const apiKey = process.env.ANTHROPIC_API_KEY; + if (!apiKey) throw new Error("Missing ANTHROPIC_API_KEY in .env"); + return new Anthropic({ apiKey }); + } else if (analysisModel.platform === "openai") { + const apiKey = process.env.OPENAI_API_KEY; + if (!apiKey) throw new Error("Missing OPENAI_API_KEY in .env"); + return new OpenAI({ apiKey }); + } else { + throw new Error(`Unsupported platform: ${analysisModel.platform}`); + } +} + +/** + * AI Call Result - unified return type for both platforms + */ +export interface AICallResult { + parsed: any; // The parsed JSON response + token_usage: { + input_tokens: number; + output_tokens: number; + total_tokens: number; + }; +} + +/** + * Unified AI call function - supports both Anthropic and OpenAI platforms + * Uses ad-hoc JSON prompting for both (no response_format for OpenAI) + */ +export async function callAI( + client: Anthropic | OpenAI, + platform: 'anthropic' | 'openai', + modelString: string, + prompt: string, + maxTokens: number +): Promise { + let rawResponse: string; + let usage: any; + + if (platform === "anthropic") { + const anthropic = client as Anthropic; + const message = await anthropic.messages.create({ + model: modelString, + max_tokens: maxTokens, + messages: [{ role: "user", content: prompt }], + }); + + rawResponse = message.content[0].type === "text" + ? message.content[0].text + : JSON.stringify(message.content[0]); + usage = message.usage; + + // Strip markdown for Anthropic + rawResponse = stripMarkdownCodeBlocks(rawResponse); + + return { + parsed: JSON.parse(rawResponse), + token_usage: { + input_tokens: usage.input_tokens, + output_tokens: usage.output_tokens, + total_tokens: usage.input_tokens + usage.output_tokens, + }, + }; + } else { + const openai = client as OpenAI; + + // Ad-hoc JSON instructions (no response_format) + const completion = await openai.chat.completions.create({ + model: modelString, + messages: [{ role: "user", content: prompt }], + max_tokens: maxTokens, + }); + + rawResponse = completion.choices[0].message.content || ""; + usage = completion.usage; + + // Apply markdown stripping to OpenAI too (just in case) + rawResponse = stripMarkdownCodeBlocks(rawResponse); + + return { + parsed: JSON.parse(rawResponse), + token_usage: { + input_tokens: usage?.prompt_tokens || 0, + output_tokens: usage?.completion_tokens || 0, + total_tokens: usage?.total_tokens || 0, + }, + }; + } +} diff --git a/package.json b/package.json index 5b14a85..0df3c3b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "exceljs": "^4.4.0", "git-rev-sync": "^3.0.2", "lodash": "^4.17.21", - "openai": "^4.78.1", + "openai": "^5.12.1", "p-queue": "^6.6.2", "p-retry": "^4.6.2", "xml2js": "^0.6.2", diff --git a/yarn.lock b/yarn.lock index 714285c..2a0d92f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2056,14 +2056,6 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.20.tgz#1ca77361d7363432d29f5e55950d9ec1e1c6ea93" integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA== -"@types/node-fetch@^2.6.4": - version "2.6.13" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.13.tgz#e0c9b7b5edbdb1b50ce32c127e85e880872d56ee" - integrity sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw== - dependencies: - "@types/node" "*" - form-data "^4.0.4" - "@types/node@*": version "24.8.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-24.8.1.tgz#74c8ae00b045a0a351f2837ec00f25dfed0053be" @@ -2076,13 +2068,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b" integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ== -"@types/node@^18.11.18": - version "18.19.130" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.130.tgz#da4c6324793a79defb7a62cba3947ec5add00d59" - integrity sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg== - dependencies: - undici-types "~5.26.4" - "@types/node@^22.10.6": version "22.18.11" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.18.11.tgz#aa8a8ccae8cc828512df642f0d82606b89450d71" @@ -2102,13 +2087,6 @@ dependencies: "@types/node" "*" -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - acorn-walk@^8.1.1: version "8.3.4" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" @@ -2121,13 +2099,6 @@ acorn@^8.11.0, acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== -agentkeepalive@^4.2.1: - version "4.6.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" - integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== - dependencies: - humanize-ms "^1.2.1" - archiver-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" @@ -2414,11 +2385,6 @@ escape-string-regexp@1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" @@ -2452,11 +2418,6 @@ follow-redirects@^1.15.6: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== -form-data-encoder@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" - integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== - form-data@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" @@ -2468,14 +2429,6 @@ form-data@^4.0.4: hasown "^2.0.2" mime-types "^2.1.12" -formdata-node@^4.3.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" - integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== - dependencies: - node-domexception "1.0.0" - web-streams-polyfill "4.0.0-beta.3" - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -2580,13 +2533,6 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - ieee754@^1.1.13: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -2779,23 +2725,6 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@^2.0.0: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -node-domexception@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - -node-fetch@^2.6.7: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -2808,18 +2737,10 @@ once@^1.3.0, once@^1.4.0: dependencies: wrappy "1" -openai@^4.78.1: - version "4.104.0" - resolved "https://registry.yarnpkg.com/openai/-/openai-4.104.0.tgz#c489765dc051b95019845dab64b0e5207cae4d30" - integrity sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA== - dependencies: - "@types/node" "^18.11.18" - "@types/node-fetch" "^2.6.4" - abort-controller "^3.0.0" - agentkeepalive "^4.2.1" - form-data-encoder "1.7.2" - formdata-node "^4.3.2" - node-fetch "^2.6.7" +openai@^5.12.1: + version "5.23.2" + resolved "https://registry.yarnpkg.com/openai/-/openai-5.23.2.tgz#f13e2dc2ef6b88aab6a9b97cdc68d41a1d083c68" + integrity sha512-MQBzmTulj+MM5O8SKEk/gL8a7s5mktS9zUtAkU257WjvobGc9nKcBuVwjyEEcb9SI8a8Y2G/mzn3vm9n1Jlleg== p-finally@^1.0.0: version "1.0.0" @@ -3002,11 +2923,6 @@ tmp@^0.2.0: resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - "traverse@>=0.3.0 <0.4": version "0.3.9" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" @@ -3036,11 +2952,6 @@ typescript@^5.7.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - undici-types@~6.21.0: version "6.21.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" @@ -3082,24 +2993,6 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -web-streams-polyfill@4.0.0-beta.3: - version "4.0.0-beta.3" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" - integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From fdf36e7a30ec9f3975e8ed92bda4dd5f53c33bad Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Tue, 21 Oct 2025 11:51:53 +0100 Subject: [PATCH 16/36] aiClient OO refactor --- comparison/scripts/aggregate-analysis.ts | 69 ++++---------- comparison/scripts/compare-summaries.ts | 33 ++----- comparison/scripts/condense-comparisons.ts | 41 ++------ comparison/scripts/shared-utils.ts | 104 ++++++++++++++++++--- 4 files changed, 124 insertions(+), 123 deletions(-) diff --git a/comparison/scripts/aggregate-analysis.ts b/comparison/scripts/aggregate-analysis.ts index 4bcb9bd..c50eca6 100644 --- a/comparison/scripts/aggregate-analysis.ts +++ b/comparison/scripts/aggregate-analysis.ts @@ -1,10 +1,8 @@ import "dotenv/config"; -import Anthropic from "@anthropic-ai/sdk"; -import OpenAI from "openai"; import { readFile } from "fs/promises"; import path from "path"; import ttest2 from "@stdlib/stats-ttest2"; -import { writeToFile, createAIClient, callAI, loadGeneList, loadSitesConfig, type GeneEntry, type AICallResult } from "./shared-utils"; +import { writeToFile, createAIClient, AIClient, loadGeneList, loadSitesConfig, type GeneEntry } from "./shared-utils"; import type { Config, AnalysisModelConfig, @@ -346,9 +344,7 @@ async function aggregateQualitativeField( dimension: string, fieldType: "summary_A" | "summary_B" | "comparison", statements: Map, - aiClient: Anthropic | OpenAI, - platform: 'anthropic' | 'openai', - analysisModelString: string + aiClient: AIClient ): Promise { // Build statements list for prompt const statementsList = Array.from(statements.entries()) @@ -389,13 +385,7 @@ Respond with JSON in this format: Respond ONLY with valid JSON, no other text.`; try { - const { parsed } = await callAI( - aiClient, - platform, - analysisModelString, - prompt, - 2000 - ); + const { parsed } = await aiClient.call(prompt, 2000); const consistency_score = calculateConsistencyScore(parsed.agreement_distribution); return { @@ -416,9 +406,7 @@ Respond ONLY with valid JSON, no other text.`; async function aggregateQualitativeDimension( dimension: "tone_and_style" | "technical_detail_level" | "structure_and_organization", comparisons: CondensedComparison[], - aiClient: Anthropic | OpenAI, - platform: 'anthropic' | 'openai', - analysisModelString: string + aiClient: AIClient ): Promise { console.log(` Aggregating ${dimension}...`); @@ -436,9 +424,9 @@ async function aggregateQualitativeDimension( // Aggregate each field with AI const [summary_A, summary_B, comparison] = await Promise.all([ - aggregateQualitativeField(dimension, "summary_A", summaryA_statements, aiClient, platform, analysisModelString), - aggregateQualitativeField(dimension, "summary_B", summaryB_statements, aiClient, platform, analysisModelString), - aggregateQualitativeField(dimension, "comparison", comparison_statements, aiClient, platform, analysisModelString), + aggregateQualitativeField(dimension, "summary_A", summaryA_statements, aiClient), + aggregateQualitativeField(dimension, "summary_B", summaryB_statements, aiClient), + aggregateQualitativeField(dimension, "comparison", comparison_statements, aiClient), ]); return { @@ -453,27 +441,13 @@ async function aggregateQualitativeDimension( */ async function aggregateQualitativeData( comparisons: CondensedComparison[], - aiClient: Anthropic | OpenAI, - platform: 'anthropic' | 'openai', - analysisModelString: string + aiClient: AIClient ): Promise { console.log("\nAggregating qualitative assessments..."); - const tone_and_style = await aggregateQualitativeDimension("tone_and_style", comparisons, aiClient, platform, analysisModelString); - const technical_detail_level = await aggregateQualitativeDimension( - "technical_detail_level", - comparisons, - aiClient, - platform, - analysisModelString - ); - const structure_and_organization = await aggregateQualitativeDimension( - "structure_and_organization", - comparisons, - aiClient, - platform, - analysisModelString - ); + const tone_and_style = await aggregateQualitativeDimension("tone_and_style", comparisons, aiClient); + const technical_detail_level = await aggregateQualitativeDimension("technical_detail_level", comparisons, aiClient); + const structure_and_organization = await aggregateQualitativeDimension("structure_and_organization", comparisons, aiClient); return { tone_and_style, @@ -500,10 +474,7 @@ async function processModelPair( modelA: string, modelB: string, genes: GeneEntry[], - aiClient: Anthropic | OpenAI, - platform: 'anthropic' | 'openai', - analysisModelName: string, - analysisModelString: string + aiClient: AIClient ): Promise { console.log(`\nProcessing model pair: ${modelA} <-> ${modelB}`); console.log("=".repeat(60)); @@ -516,7 +487,7 @@ async function processModelPair( // Load all condensed comparisons console.log("Loading condensed comparisons..."); - const comparisons = await loadCondensedComparisons(geneIds, modelA, modelB, analysisModelName); + const comparisons = await loadCondensedComparisons(geneIds, modelA, modelB, aiClient.name); console.log(`Loaded ${comparisons.length} comparisons`); // Aggregate quantitative data @@ -524,7 +495,7 @@ async function processModelPair( const quantitative_aggregates = aggregateQuantitativeData(comparisons); // Aggregate qualitative data (with AI) - const qualitative_aggregates = await aggregateQualitativeData(comparisons, aiClient, platform, analysisModelString); + const qualitative_aggregates = await aggregateQualitativeData(comparisons, aiClient); // Build report let report: AggregateReport = { @@ -544,7 +515,7 @@ async function processModelPair( // Save report const outputPath = path.join( process.cwd(), - `comparison/data/aggregate-reports/${analysisModelName}/${modelA}-${modelB}-report.json` + `comparison/data/aggregate-reports/${aiClient.name}/${modelA}-${modelB}-report.json` ); await writeToFile(outputPath, JSON.stringify(report, null, 2)); console.log(`\nReport saved to: ${outputPath}`); @@ -607,15 +578,7 @@ async function main() { // Process each model pair for (const [modelA, modelB] of pairs) { try { - await processModelPair( - modelA, - modelB, - genes, - aiClient, - config.analysis_model.platform, - config.analysis_model.name, - config.analysis_model.model_string - ); + await processModelPair(modelA, modelB, genes, aiClient); successCount++; } catch (error) { console.error(`\nERROR processing ${modelA} <-> ${modelB}:`, error instanceof Error ? error.message : error); diff --git a/comparison/scripts/compare-summaries.ts b/comparison/scripts/compare-summaries.ts index 0b6844d..f36bba2 100644 --- a/comparison/scripts/compare-summaries.ts +++ b/comparison/scripts/compare-summaries.ts @@ -1,9 +1,7 @@ import "dotenv/config"; -import Anthropic from "@anthropic-ai/sdk"; -import OpenAI from "openai"; import { readFile } from "fs/promises"; import path from "path"; -import { writeToFile, createAIClient, callAI, loadGeneList, loadSitesConfig, type AICallResult } from "./shared-utils"; +import { writeToFile, createAIClient, AIClient, loadGeneList, loadSitesConfig } from "./shared-utils"; import type { Config, SiteConfig, @@ -99,9 +97,7 @@ async function compareWithAI( modelBName: string, summaryA: ExpressionSummary, summaryB: ExpressionSummary, - aiClient: Anthropic | OpenAI, - platform: 'anthropic' | 'openai', - analysisModelString: string + aiClient: AIClient ): Promise<{ biological_content: BiologicalContent; qualitative_assessment: QualitativeAssessment; @@ -177,13 +173,7 @@ Important distinctions: Respond ONLY with valid JSON, no other text.`; try { - const { parsed, token_usage } = await callAI( - aiClient, - platform, - analysisModelString, - prompt, - 4000 - ); + const { parsed, token_usage } = await aiClient.call(prompt, 4000); return { ...parsed, @@ -249,9 +239,7 @@ async function comparePair( geneId: string, modelAName: string, modelBName: string, - aiClient: Anthropic | OpenAI, - platform: 'anthropic' | 'openai', - analysisModelString: string + aiClient: AIClient ): Promise { console.log(` Comparing ${modelAName} vs ${modelBName}...`); @@ -264,7 +252,7 @@ async function comparePair( const metricsB = calculateMetrics(summaryB); // Get AI comparison - const aiComparison = await compareWithAI(geneId, modelAName, modelBName, summaryA, summaryB, aiClient, platform, analysisModelString); + const aiComparison = await compareWithAI(geneId, modelAName, modelBName, summaryA, summaryB, aiClient); return { model_A: modelAName, @@ -348,19 +336,12 @@ async function main() { // Process each comparison pair for (const [modelA, modelB] of pairs) { try { - const result = await comparePair( - geneId, - modelA, - modelB, - aiClient, - config.analysis_model.platform, - config.analysis_model.model_string - ); + const result = await comparePair(geneId, modelA, modelB, aiClient); // Save result const outputPath = path.join( process.cwd(), - `comparison/data/comparisons/${config.analysis_model.name}/${geneId}/${modelA}-vs-${modelB}.json` + `comparison/data/comparisons/${aiClient.name}/${geneId}/${modelA}-vs-${modelB}.json` ); await writeToFile(outputPath, JSON.stringify(result, null, 2)); diff --git a/comparison/scripts/condense-comparisons.ts b/comparison/scripts/condense-comparisons.ts index 8219b73..379b4ca 100644 --- a/comparison/scripts/condense-comparisons.ts +++ b/comparison/scripts/condense-comparisons.ts @@ -1,9 +1,7 @@ import "dotenv/config"; -import Anthropic from "@anthropic-ai/sdk"; -import OpenAI from "openai"; import { readFile } from "fs/promises"; import path from "path"; -import { writeToFile, createAIClient, callAI, loadGeneList, loadSitesConfig, type AICallResult } from "./shared-utils"; +import { writeToFile, createAIClient, AIClient, loadGeneList, loadSitesConfig } from "./shared-utils"; import type { Config, SiteConfig, @@ -105,9 +103,7 @@ async function mergeQualitativeAssessments( geneId: string, assessment1: QualitativeAssessment, assessment2: QualitativeAssessment, - aiClient: Anthropic | OpenAI, - platform: 'anthropic' | 'openai', - analysisModelString: string + aiClient: AIClient ): Promise { // Swap labels in assessment2 so both assessments use the same A/B labels const assessment2_normalized = swapAssessmentLabels(assessment2); @@ -156,13 +152,7 @@ Respond with JSON in this format: Respond ONLY with valid JSON, no other text.`; try { - const { parsed } = await callAI( - aiClient, - platform, - analysisModelString, - prompt, - 2000 - ); + const { parsed } = await aiClient.call(prompt, 2000); return parsed; } catch (error) { console.error("Failed to parse AI response"); @@ -204,16 +194,13 @@ async function condensePair( geneId: string, modelA: string, modelB: string, - aiClient: Anthropic | OpenAI, - platform: 'anthropic' | 'openai', - analysisModelName: string, - analysisModelString: string + aiClient: AIClient ): Promise { console.log(` Condensing ${modelA} <-> ${modelB}...`); // Load both directions - const comparison_AvsB = await loadComparison(geneId, modelA, modelB, analysisModelName); - const comparison_BvsA = await loadComparison(geneId, modelB, modelA, analysisModelName); + const comparison_AvsB = await loadComparison(geneId, modelA, modelB, aiClient.name); + const comparison_BvsA = await loadComparison(geneId, modelB, modelA, aiClient.name); // Summarize biological content const observations_summary = summarizeBiologicalContent(comparison_AvsB, comparison_BvsA, "observations"); @@ -224,9 +211,7 @@ async function condensePair( geneId, comparison_AvsB.qualitative_assessment, comparison_BvsA.qualitative_assessment, - aiClient, - platform, - analysisModelString + aiClient ); // Average quantitative mentions @@ -320,20 +305,12 @@ async function main() { // Process each model pair for (const [modelA, modelB] of pairs) { try { - const result = await condensePair( - geneId, - modelA, - modelB, - aiClient, - config.analysis_model.platform, - config.analysis_model.name, - config.analysis_model.model_string - ); + const result = await condensePair(geneId, modelA, modelB, aiClient); // Save result const outputPath = path.join( process.cwd(), - `comparison/data/condensed/${config.analysis_model.name}/${geneId}/${modelA}-${modelB}.json` + `comparison/data/condensed/${aiClient.name}/${geneId}/${modelA}-${modelB}.json` ); await writeToFile(outputPath, JSON.stringify(result, null, 2)); diff --git a/comparison/scripts/shared-utils.ts b/comparison/scripts/shared-utils.ts index ea8bca7..287aa15 100644 --- a/comparison/scripts/shared-utils.ts +++ b/comparison/scripts/shared-utils.ts @@ -139,20 +139,98 @@ export async function loadSitesConfig(): Promise<{ } /** - * AI Client factory - creates either Anthropic or OpenAI client based on platform + * AI Client class - encapsulates AI client with its configuration + * Knows its own platform, model string, and name */ -export function createAIClient(analysisModel: AnalysisModelConfig): Anthropic | OpenAI { - if (analysisModel.platform === "anthropic") { - const apiKey = process.env.ANTHROPIC_API_KEY; - if (!apiKey) throw new Error("Missing ANTHROPIC_API_KEY in .env"); - return new Anthropic({ apiKey }); - } else if (analysisModel.platform === "openai") { - const apiKey = process.env.OPENAI_API_KEY; - if (!apiKey) throw new Error("Missing OPENAI_API_KEY in .env"); - return new OpenAI({ apiKey }); - } else { - throw new Error(`Unsupported platform: ${analysisModel.platform}`); +export class AIClient { + public readonly name: string; + public readonly platform: 'anthropic' | 'openai'; + public readonly modelString: string; + private readonly client: Anthropic | OpenAI; + + constructor(analysisModel: AnalysisModelConfig) { + this.name = analysisModel.name; + this.platform = analysisModel.platform; + this.modelString = analysisModel.model_string; + + if (this.platform === "anthropic") { + const apiKey = process.env.ANTHROPIC_API_KEY; + if (!apiKey) throw new Error("Missing ANTHROPIC_API_KEY in .env"); + this.client = new Anthropic({ apiKey }); + } else if (this.platform === "openai") { + const apiKey = process.env.OPENAI_API_KEY; + if (!apiKey) throw new Error("Missing OPENAI_API_KEY in .env"); + this.client = new OpenAI({ apiKey }); + } else { + throw new Error(`Unsupported platform: ${this.platform}`); + } } + + /** + * Call the AI model with a prompt + * Handles platform-specific logic internally + */ + async call(prompt: string, maxTokens: number): Promise { + let rawResponse: string; + let usage: any; + + if (this.platform === "anthropic") { + const anthropic = this.client as Anthropic; + const message = await anthropic.messages.create({ + model: this.modelString, + max_tokens: maxTokens, + messages: [{ role: "user", content: prompt }], + }); + + rawResponse = message.content[0].type === "text" + ? message.content[0].text + : JSON.stringify(message.content[0]); + usage = message.usage; + + // Strip markdown for Anthropic + rawResponse = stripMarkdownCodeBlocks(rawResponse); + + return { + parsed: JSON.parse(rawResponse), + token_usage: { + input_tokens: usage.input_tokens, + output_tokens: usage.output_tokens, + total_tokens: usage.input_tokens + usage.output_tokens, + }, + }; + } else { + const openai = this.client as OpenAI; + + // Ad-hoc JSON instructions (no response_format) + const completion = await openai.chat.completions.create({ + model: this.modelString, + messages: [{ role: "user", content: prompt }], + max_tokens: maxTokens, + }); + + rawResponse = completion.choices[0].message.content || ""; + usage = completion.usage; + + // Apply markdown stripping to OpenAI too (just in case) + rawResponse = stripMarkdownCodeBlocks(rawResponse); + + return { + parsed: JSON.parse(rawResponse), + token_usage: { + input_tokens: usage?.prompt_tokens || 0, + output_tokens: usage?.completion_tokens || 0, + total_tokens: usage?.total_tokens || 0, + }, + }; + } + } +} + +/** + * AI Client factory - creates AIClient instance with encapsulated configuration + */ +export function createAIClient(analysisModel: AnalysisModelConfig): AIClient { + return new AIClient(analysisModel); } /** @@ -168,6 +246,8 @@ export interface AICallResult { } /** + * @deprecated Use AIClient.call() instead. This function is kept for backwards compatibility. + * * Unified AI call function - supports both Anthropic and OpenAI platforms * Uses ad-hoc JSON prompting for both (no response_format for OpenAI) */ From d6b51825921c990b5a031a68bb76fd45e9c5f0e8 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Tue, 21 Oct 2025 12:01:41 +0100 Subject: [PATCH 17/36] docs update --- CLAUDE-ai-provider-comparison.md | 330 ++----------------------------- 1 file changed, 20 insertions(+), 310 deletions(-) diff --git a/CLAUDE-ai-provider-comparison.md b/CLAUDE-ai-provider-comparison.md index 8bb7c3d..5d436e0 100644 --- a/CLAUDE-ai-provider-comparison.md +++ b/CLAUDE-ai-provider-comparison.md @@ -24,8 +24,27 @@ This work will be done within the existing `expression-shepherd` repository, lev ## Workflow +### Phase 0: Setup & Configuration + +**Gene List (`comparison/input/gene-list.txt`):** +- Format: One gene ID per line (e.g., `AGAP001234`) +- Optional: Add gene name/description after ID (space-separated): `AGAP001234 ABC1 transporter protein` +- Comments: Lines starting with `#` are ignored (useful for temporarily excluding genes) + +**Sites Configuration (`comparison/config/sites.json`):** +- **`analysis_model`**: Configures which AI model performs the comparison analysis (Phases 2-3) + - `model_string`: Full model identifier (e.g., `"claude-sonnet-4-20250514"` or `"gpt-4o-2024-11-20"`) + - `name`: Short name for output directory organization (e.g., `"claude4"`, `"gpt4o"`, `"gpt5"`) + - `platform`: Either `"anthropic"` or `"openai"` + - Note: This is independent of the models being compared (which are defined in `sites`) +- **`sites`**: Array of model endpoints to compare (VEuPathDB dev sites) + - Each site has: `name`, `hostname`, `appPath`, `model` (display name) + - Set `skip: true` to temporarily exclude a site from processing +- **`endpoint`**: VEuPathDB API endpoint path +- **`projectId`**: VEuPathDB project (e.g., `"VectorBase"`) + ### Phase 1: Trigger Summary Generation & Fetch Results -1. Read gene list from input file (format TBD by user) +1. Read gene list from input file 2. For each gene, make API calls to all three sites: - Initial request with `populateIfNotPresent: true` to trigger generation - Poll with `populateIfNotPresent: false` until `resultStatus: "present"` @@ -249,315 +268,6 @@ Already available in repository: --- -# Development History - -## Phase 1 (COMPLETED): Configurable Analysis Model - -**Status**: ✅ Completed 2025-10-21 - -**Summary**: Added `analysis_model` configuration to enable switching between different AI models for comparison analysis (Phases 2-3). This allows comparing how different AI models (e.g., Claude 4 vs GPT-5) perform the analysis itself, independent of the source summaries being compared. - -**Changes implemented**: -1. Added `analysis_model` config to `comparison/config/sites.json`: - ```json - { - "analysis_model": { - "model_string": "claude-sonnet-4-20250514", - "name": "claude4", - "platform": "anthropic" - }, - "sites": [...], - ... - } - ``` - -2. Updated TypeScript types (`comparison/scripts/types.ts`): - - Added `AnalysisModelConfig` interface - - Updated `Config` interface to include `analysis_model` - -3. Refactored all comparison scripts to use config: - - `compare-summaries.ts`: Uses `config.analysis_model.model_string` for API calls - - `condense-comparisons.ts`: Uses `config.analysis_model.model_string` for AI merging - - `aggregate-analysis.ts`: Uses `config.analysis_model.model_string` for aggregation - -4. Updated output paths to include analysis model subdirectories: - - `comparisons/{analysisModel}/{geneId}/...` - - `condensed/{analysisModel}/{geneId}/...` - - `aggregate-reports/{analysisModel}/...` - -5. Migrated existing files into `claude4/` subdirectories (20 genes, all comparisons/condensed/aggregate-reports) - -**Build verification**: ✅ TypeScript compiles without errors - ---- - -## Phase 2 (TODO): Add OpenAI Platform Support - -**Goal**: Enable using OpenAI models (GPT-4o, GPT-5) as the analysis model for comparison scripts, not just Anthropic Claude. - -**Context**: Currently all three comparison scripts (`compare-summaries.ts`, `condense-comparisons.ts`, `aggregate-analysis.ts`) use Anthropic's API directly. To support OpenAI as an analysis model, we need platform abstraction. - -### Implementation Strategy - -**Approach**: Unified ad-hoc JSON prompting for both platforms (no `response_format` for OpenAI) - -Rationale: -- Simplicity: One code path instead of platform-specific branching -- GPT-5/4o are excellent at following ad-hoc JSON instructions -- Methodologically cleaner: Identical prompting strategy across platforms -- Already battle-tested: Claude ad-hoc approach working well in comparison scripts - -**Note**: Unlike `src/main.ts` (which uses OpenAI's `zodResponseFormat`), we intentionally use simpler ad-hoc JSON instructions for both platforms. - -### Tasks - -#### 1. Check/Upgrade OpenAI Package - -```bash -# Check current version -yarn list --pattern openai - -# Upgrade if needed for GPT-5 support -yarn add openai@latest -``` - -#### 2. Create Platform Abstraction (`comparison/scripts/shared-utils.ts`) - -Add these functions to `shared-utils.ts`: - -**a) AI Client Factory** -```typescript -import Anthropic from "@anthropic-ai/sdk"; -import OpenAI from "openai"; -import type { AnalysisModelConfig } from "./types"; - -export function createAIClient(analysisModel: AnalysisModelConfig): Anthropic | OpenAI { - if (analysisModel.platform === "anthropic") { - const apiKey = process.env.ANTHROPIC_API_KEY; - if (!apiKey) throw new Error("Missing ANTHROPIC_API_KEY in .env"); - return new Anthropic({ apiKey }); - } else if (analysisModel.platform === "openai") { - const apiKey = process.env.OPENAI_API_KEY; - if (!apiKey) throw new Error("Missing OPENAI_API_KEY in .env"); - return new OpenAI({ apiKey }); - } else { - throw new Error(`Unsupported platform: ${analysisModel.platform}`); - } -} -``` - -**b) Unified AI Call Function** -```typescript -export interface AICallResult { - parsed: any; // The parsed JSON response - token_usage: { - input_tokens: number; - output_tokens: number; - total_tokens: number; - }; -} - -export async function callAI( - client: Anthropic | OpenAI, - platform: 'anthropic' | 'openai', - modelString: string, - prompt: string, - maxTokens: number -): Promise { - let rawResponse: string; - let usage: any; - - if (platform === "anthropic") { - const anthropic = client as Anthropic; - const message = await anthropic.messages.create({ - model: modelString, - max_tokens: maxTokens, - messages: [{ role: "user", content: prompt }], - }); - - rawResponse = message.content[0].type === "text" - ? message.content[0].text - : JSON.stringify(message.content[0]); - usage = message.usage; - - // Strip markdown for Anthropic - rawResponse = stripMarkdownCodeBlocks(rawResponse); - - return { - parsed: JSON.parse(rawResponse), - token_usage: { - input_tokens: usage.input_tokens, - output_tokens: usage.output_tokens, - total_tokens: usage.input_tokens + usage.output_tokens, - }, - }; - } else { - const openai = client as OpenAI; - - // Ad-hoc JSON instructions (no response_format) - const completion = await openai.chat.completions.create({ - model: modelString, - messages: [{ role: "user", content: prompt }], - max_tokens: maxTokens, - }); - - rawResponse = completion.choices[0].message.content || ""; - usage = completion.usage; - - // Apply markdown stripping to OpenAI too (just in case) - rawResponse = stripMarkdownCodeBlocks(rawResponse); - - return { - parsed: JSON.parse(rawResponse), - token_usage: { - input_tokens: usage?.prompt_tokens || 0, - output_tokens: usage?.completion_tokens || 0, - total_tokens: usage?.total_tokens || 0, - }, - }; - } -} -``` - -#### 3. Update Comparison Scripts - -**For each script** (`compare-summaries.ts`, `condense-comparisons.ts`, `aggregate-analysis.ts`): - -a) **Change imports**: -```typescript -// Before: -import Anthropic from "@anthropic-ai/sdk"; - -// After: -import Anthropic from "@anthropic-ai/sdk"; -import OpenAI from "openai"; -import { createAIClient, callAI, type AICallResult } from "./shared-utils"; -``` - -b) **Update `main()` function**: -```typescript -// Before: -const apiKey = process.env.ANTHROPIC_API_KEY; -if (!apiKey) { - console.error("Missing ANTHROPIC_API_KEY in .env file"); - process.exit(1); -} -const anthropic = new Anthropic({ apiKey }); - -// After: -const aiClient = createAIClient(config.analysis_model); -``` - -c) **Replace direct API calls** with `callAI()`: -```typescript -// Before: -const message = await anthropic.messages.create({ - model: analysisModelString, - max_tokens: 4000, - messages: [{ role: "user", content: prompt }], -}); -const responseText = message.content[0].type === "text" ? message.content[0].text : ...; -const cleanedResponse = stripMarkdownCodeBlocks(responseText); -const parsed = JSON.parse(cleanedResponse); -// ... extract token_usage from message.usage - -// After: -const { parsed, token_usage } = await callAI( - aiClient, - config.analysis_model.platform, - config.analysis_model.model_string, - prompt, - 4000 -); -``` - -d) **Update function signatures** to accept generic client: -```typescript -// Before: -async function compareWithAI(..., anthropic: Anthropic, ...): Promise<...> { - -// After: -async function compareWithAI(..., aiClient: Anthropic | OpenAI, platform: 'anthropic' | 'openai', ...): Promise<...> { -``` - -#### 4. Testing Strategy - -1. **Build verification**: `yarn build` (check for TypeScript errors) -2. **Test with Anthropic** (baseline - should still work): - ```json - // sites.json - "analysis_model": { - "model_string": "claude-sonnet-4-20250514", - "name": "claude4", - "platform": "anthropic" - } - ``` - - Run: `yarn comparison:compare` on a single gene (comment out others in `gene-list.txt`) - - Verify output in `comparisons/claude4/` - -3. **Test with OpenAI**: - ```json - // sites.json - "analysis_model": { - "model_string": "gpt-4o-2024-11-20", // or latest GPT-5 model - "name": "gpt4o", - "platform": "openai" - } - ``` - - Run: `yarn comparison:compare` on same test gene - - Verify output in `comparisons/gpt4o/` - - Compare JSON structure matches (same schema) - - Check for JSON parsing errors in logs - -4. **Regression test**: Run all three scripts end-to-end with both platforms - -### GPT-5 Model String - -When GPT-5 support is available, update model string: -```json -"analysis_model": { - "model_string": "gpt-5-YYYY-MM-DD", // TBD when GPT-5 launches - "name": "gpt5", - "platform": "openai" -} -``` - -Check OpenAI docs for exact model identifier and context window limits. - -### Potential Issues & Solutions - -**Issue**: OpenAI doesn't follow JSON format despite ad-hoc instructions -- **Solution**: Add retry logic with error message feedback, or fall back to `response_format` (add as Phase 2.1 if needed) - -**Issue**: Different token limits between models -- **Solution**: Make `maxTokens` configurable per model in `analysis_model` config: - ```json - "analysis_model": { - "model_string": "...", - "name": "...", - "platform": "...", - "max_tokens": { - "compare": 4000, - "condense": 2000, - "aggregate": 2000 - } - } - ``` - -**Issue**: Rate limiting differences between platforms -- **Solution**: Add platform-specific rate limiting in `callAI()` function - -### Success Criteria - -- ✅ All three comparison scripts work with both `platform: "anthropic"` and `platform: "openai"` -- ✅ No TypeScript compilation errors -- ✅ JSON parsing works reliably for both platforms -- ✅ Token usage tracking accurate for both platforms -- ✅ Can switch analysis models by editing `sites.json` alone (no code changes) -- ✅ Existing claude4 analysis results unchanged (regression test) - ---- - # Appendices From c1b8b77775375188bcf85607d2d0036ac96275ec Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Tue, 21 Oct 2025 12:15:35 +0100 Subject: [PATCH 18/36] remove unused polling code and documentation --- CLAUDE-ai-provider-comparison.md | 26 ++++++++--------- comparison/scripts/fetch-summaries.ts | 41 +++++---------------------- 2 files changed, 20 insertions(+), 47 deletions(-) diff --git a/CLAUDE-ai-provider-comparison.md b/CLAUDE-ai-provider-comparison.md index 5d436e0..fa3a633 100644 --- a/CLAUDE-ai-provider-comparison.md +++ b/CLAUDE-ai-provider-comparison.md @@ -43,22 +43,22 @@ This work will be done within the existing `expression-shepherd` repository, lev - **`endpoint`**: VEuPathDB API endpoint path - **`projectId`**: VEuPathDB project (e.g., `"VectorBase"`) -### Phase 1: Trigger Summary Generation & Fetch Results +### Phase 1: Fetch Summary Results 1. Read gene list from input file -2. For each gene, make API calls to all three sites: - - Initial request with `populateIfNotPresent: true` to trigger generation - - Poll with `populateIfNotPresent: false` until `resultStatus: "present"` - - See `useAiExpressionSummary` in [AiExpressionSummary.tsx](https://raw.githubusercontent.com/VEuPathDB/web-monorepo/refs/heads/main/packages/sites/genomics-site/webapp/wdkCustomization/js/client/components/records/AiExpressionSummary.tsx) for orchestration details +2. For each gene, make API calls to all three sites with `populateIfNotPresent: true` + - Summaries are typically cached server-side from previous generations + - If fetch fails (summary not ready), script will report error at end + - Simply re-run `yarn comparison:fetch` to retry failed fetches 3. Save JSON responses locally, organized by gene ID and model 4. Track progress and any errors **Runtime Expectations:** -- Polling timeout: 10 minutes per gene per site (MAX_POLL_ATTEMPTS = 120 × 5 seconds) -- For 20 genes × 3 sites: expect 60+ minutes total runtime due to: +- For 20 genes × 3 sites: typically completes quickly when summaries are cached +- First-time generation may take longer due to: - AI generation time (varies by model and gene complexity) - Backend rate limiting (especially for Anthropic/Claude) - Sequential processing per gene, parallel across sites -- **Important for Claude Code**: When using Bash tool to run `yarn comparison:fetch`, set timeout to at least 3600000ms (1 hour) for full gene lists +- Failed fetches can be retried by re-running the script ### Phase 2: AI-Powered Comparison 1. Create TypeScript scripts that use Anthropic API to compare summaries @@ -183,10 +183,10 @@ expression-shepherd/ ``` **Orchestration**: -- Initial request with `populateIfNotPresent: true` triggers AI generation -- Subsequent polls with `populateIfNotPresent: false` check status -- Response includes progress information when not yet complete -- See `useAiExpressionSummary` hook for reference implementation +- Request with `populateIfNotPresent: true` fetches or triggers AI generation +- Summaries are typically cached server-side from previous generations +- If not ready, script will fail and can be re-run to retry +- See `useAiExpressionSummary` hook in the web UI for reference on progressive polling (not used in this script) ### Authentication - VEuPathDB dev sites use cookie-based authentication @@ -208,7 +208,7 @@ expression-shepherd/ 2. **Phase 1 Implementation** - Create `comparison/scripts/fetch-summaries.ts` with API client - Implement cookie-based authentication for all three sites - - Implement progress monitoring and polling logic + - Implement progress monitoring and error tracking - Save JSON responses to `comparison/data/summaries/{model}/{geneId}.json` - Add retry logic for failed requests diff --git a/comparison/scripts/fetch-summaries.ts b/comparison/scripts/fetch-summaries.ts index b2f93db..6e752e2 100644 --- a/comparison/scripts/fetch-summaries.ts +++ b/comparison/scripts/fetch-summaries.ts @@ -4,9 +4,7 @@ import path from "path"; import { writeToFile, getAuthCookie, sleep, loadGeneList, loadSitesConfig } from "./shared-utils"; import type { SiteConfig, Config, FetchResult } from "./types"; -const POLL_INTERVAL_MS = 5000; // 5 seconds const MAX_RETRIES = 3; -const MAX_POLL_ATTEMPTS = 120; // 10 minutes max (120 * 5 seconds) /** * Make API request to fetch AI expression summary @@ -42,49 +40,24 @@ async function fetchSummary( } /** - * Fetch summary for a gene from a specific site with polling + * Fetch summary for a gene from a specific site */ -async function fetchSummaryWithPolling( +async function fetchSummaryForGene( geneId: string, site: SiteConfig, config: Config, authCookie: string ): Promise { - console.log(`[${site.name}] ${geneId}: Triggering summary generation...`); + console.log(`[${site.name}] ${geneId}: Fetching summary...`); - // Initial request to trigger generation - let response = await fetchSummary(geneId, site, config, authCookie, true); + const response = await fetchSummary(geneId, site, config, authCookie, true); - // Check if already present if (response[geneId]?.resultStatus === "present") { - console.log(`[${site.name}] ${geneId}: Summary already present`); + console.log(`[${site.name}] ${geneId}: Summary retrieved successfully`); return response[geneId].expressionSummary; } - // Poll until present or max attempts reached - let attempts = 0; - while (attempts < MAX_POLL_ATTEMPTS) { - console.log( - `[${site.name}] ${geneId}: Polling (attempt ${attempts + 1}/${MAX_POLL_ATTEMPTS})...` - ); - - await sleep(POLL_INTERVAL_MS); - - response = await fetchSummary(geneId, site, config, authCookie, false); - - if (response[geneId]?.resultStatus === "present") { - console.log(`[${site.name}] ${geneId}: Summary ready!`); - return response[geneId].expressionSummary; - } - - if (response[geneId]?.resultStatus === "error") { - throw new Error(`Generation failed: ${JSON.stringify(response[geneId])}`); - } - - attempts++; - } - - throw new Error(`Polling timeout after ${MAX_POLL_ATTEMPTS} attempts`); + throw new Error(`Generation failed: ${JSON.stringify(response[geneId])}`); } /** @@ -100,7 +73,7 @@ async function fetchWithRetry( for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { try { - const summary = await fetchSummaryWithPolling(geneId, site, config, authCookie); + const summary = await fetchSummaryForGene(geneId, site, config, authCookie); // Save to file const outputPath = path.join( From 3f2ff51c6e781f930528e872995271f5309cf9ed Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Tue, 21 Oct 2025 12:21:50 +0100 Subject: [PATCH 19/36] max_completion_tokens for openai --- comparison/scripts/shared-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comparison/scripts/shared-utils.ts b/comparison/scripts/shared-utils.ts index 287aa15..edb55ff 100644 --- a/comparison/scripts/shared-utils.ts +++ b/comparison/scripts/shared-utils.ts @@ -205,7 +205,7 @@ export class AIClient { const completion = await openai.chat.completions.create({ model: this.modelString, messages: [{ role: "user", content: prompt }], - max_tokens: maxTokens, + max_completion_tokens: maxTokens, }); rawResponse = completion.choices[0].message.content || ""; From d949b6d138c740cf7b836aee0f189c263e43780d Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Tue, 21 Oct 2025 14:23:12 +0100 Subject: [PATCH 20/36] handle GPT-5 reasoning issue --- comparison/config/sites.json | 16 ++++++++++++---- comparison/scripts/compare-summaries.ts | 21 ++++++++------------- comparison/scripts/shared-utils.ts | 6 ++++++ 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/comparison/config/sites.json b/comparison/config/sites.json index ba16c61..b8fce75 100644 --- a/comparison/config/sites.json +++ b/comparison/config/sites.json @@ -1,27 +1,35 @@ { - "analysis_model": { + "_not_this_analysis_model": { "model_string": "claude-sonnet-4-20250514", "name": "claude4", "platform": "anthropic" }, + "analysis_model": { + "model_string": "gpt-5-2025-08-07", + "name": "gpt5", + "platform": "openai" + }, "sites": [ { "name": "claude4", "hostname": "bmaccallum.vectorbase.org", "appPath": "vectorbase.bmaccallum", - "model": "Claude Sonnet 4" + "model": "Claude Sonnet 4", + "skip": false }, { "name": "gpt5", "hostname": "bmaccallum-b.vectorbase.org", "appPath": "vectorbase.bmaccallum-b", - "model": "GPT-5" + "model": "GPT-5", + "skip": false }, { "name": "gpt4o", "hostname": "qa.vectorbase.org", "appPath": "vectorbase.b69", - "model": "GPT-4o" + "model": "GPT-4o", + "skip": false } ], "endpoint": "/service/record-types/gene/searches/single_record_question_GeneRecordClasses_GeneRecordClass/reports/aiExpression", diff --git a/comparison/scripts/compare-summaries.ts b/comparison/scripts/compare-summaries.ts index f36bba2..f083b2a 100644 --- a/comparison/scripts/compare-summaries.ts +++ b/comparison/scripts/compare-summaries.ts @@ -141,17 +141,17 @@ Please provide a detailed comparison in the following JSON format: "tone_and_style": { "summary_A": "description of tone and writing style in Summary A", "summary_B": "description of tone and writing style in Summary B", - "comparison": "comparison of tones and styles" + "comparison": "comparison of tones and styles (always refer to 'Summary A' and 'Summary B', never just 'A' or 'B')" }, "technical_detail_level": { "summary_A": "assessment of technical detail in Summary A", "summary_B": "assessment of technical detail in Summary B", - "comparison": "comparison of detail levels" + "comparison": "comparison of detail levels (always refer to 'Summary A' and 'Summary B', never just 'A' or 'B')" }, "structure_and_organization": { "summary_A": "assessment of structure in Summary A", "summary_B": "assessment of structure in Summary B", - "comparison": "comparison of organizational approaches" + "comparison": "comparison of organizational approaches (always refer to 'Summary A' and 'Summary B', never just 'A' or 'B')" } }, "quantitative_expression_mentions": { @@ -172,17 +172,12 @@ Important distinctions: Respond ONLY with valid JSON, no other text.`; - try { - const { parsed, token_usage } = await aiClient.call(prompt, 4000); + const { parsed, token_usage } = await aiClient.call(prompt, 4000); - return { - ...parsed, - token_usage, - }; - } catch (error) { - console.error("Failed to parse AI response"); - throw new Error(`Failed to parse AI response: ${error}`); - } + return { + ...parsed, + token_usage, + }; } // ============================================================================ diff --git a/comparison/scripts/shared-utils.ts b/comparison/scripts/shared-utils.ts index edb55ff..ae91e24 100644 --- a/comparison/scripts/shared-utils.ts +++ b/comparison/scripts/shared-utils.ts @@ -206,8 +206,14 @@ export class AIClient { model: this.modelString, messages: [{ role: "user", content: prompt }], max_completion_tokens: maxTokens, + reasoning_effort: 'minimal', // Reduce reasoning tokens for faster, more efficient responses }); + // Check for refusal or errors + if (completion.choices[0].message.refusal) { + throw new Error(`OpenAI refused the request: ${completion.choices[0].message.refusal}`); + } + rawResponse = completion.choices[0].message.content || ""; usage = completion.usage; From caded98c754d1d3e04b4117a30e7186ceb458eeb Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Tue, 21 Oct 2025 14:38:23 +0100 Subject: [PATCH 21/36] skip already performed comparisons --- comparison/scripts/compare-summaries.ts | 34 ++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/comparison/scripts/compare-summaries.ts b/comparison/scripts/compare-summaries.ts index f083b2a..d3ed37f 100644 --- a/comparison/scripts/compare-summaries.ts +++ b/comparison/scripts/compare-summaries.ts @@ -1,5 +1,5 @@ import "dotenv/config"; -import { readFile } from "fs/promises"; +import { readFile, access } from "fs/promises"; import path from "path"; import { writeToFile, createAIClient, AIClient, loadGeneList, loadSitesConfig } from "./shared-utils"; import type { @@ -184,6 +184,18 @@ Respond ONLY with valid JSON, no other text.`; // File Operations // ============================================================================ +/** + * Check if a file exists + */ +async function fileExists(filePath: string): Promise { + try { + await access(filePath); + return true; + } catch { + return false; + } +} + /** * Load summary for a specific gene and model */ @@ -321,6 +333,7 @@ async function main() { let successCount = 0; let errorCount = 0; + let skippedCount = 0; // Process each gene for (const geneId of geneIds) { @@ -330,14 +343,22 @@ async function main() { // Process each comparison pair for (const [modelA, modelB] of pairs) { + // Check if output file already exists + const outputPath = path.join( + process.cwd(), + `comparison/data/comparisons/${aiClient.name}/${geneId}/${modelA}-vs-${modelB}.json` + ); + + if (await fileExists(outputPath)) { + console.log(` Skipping ${modelA} vs ${modelB} (already exists)...`); + skippedCount++; + continue; + } + try { const result = await comparePair(geneId, modelA, modelB, aiClient); // Save result - const outputPath = path.join( - process.cwd(), - `comparison/data/comparisons/${aiClient.name}/${geneId}/${modelA}-vs-${modelB}.json` - ); await writeToFile(outputPath, JSON.stringify(result, null, 2)); successCount++; @@ -352,8 +373,9 @@ async function main() { console.log("\n" + "=".repeat(60)); console.log("SUMMARY"); console.log("=".repeat(60)); - console.log(`Total comparisons: ${successCount + errorCount}`); + console.log(`Total comparisons: ${successCount + errorCount + skippedCount}`); console.log(`Successful: ${successCount}`); + console.log(`Skipped (already exist): ${skippedCount}`); console.log(`Failed: ${errorCount}`); if (errorCount > 0) { From f33b2143457beb3fb934e99e1b76b224368eec26 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Tue, 21 Oct 2025 14:47:52 +0100 Subject: [PATCH 22/36] add deterministic topic size stats --- comparison/scripts/aggregate-analysis.ts | 8 ++++++++ comparison/scripts/compare-summaries.ts | 13 +++++++++++++ comparison/scripts/types.ts | 4 ++++ 3 files changed, 25 insertions(+) diff --git a/comparison/scripts/aggregate-analysis.ts b/comparison/scripts/aggregate-analysis.ts index c50eca6..cf64369 100644 --- a/comparison/scripts/aggregate-analysis.ts +++ b/comparison/scripts/aggregate-analysis.ts @@ -251,6 +251,12 @@ function aggregateDeterministicMetrics( const avgSentLengthA = comparisons.map((c) => c.deterministic_metrics.model_A.average_sentence_length); const avgSentLengthB = comparisons.map((c) => c.deterministic_metrics.model_B.average_sentence_length); + const meanTopicSizeA = comparisons.map((c) => c.deterministic_metrics.model_A.mean_ai_topic_size); + const meanTopicSizeB = comparisons.map((c) => c.deterministic_metrics.model_B.mean_ai_topic_size); + + const otherTopicSizeA = comparisons.map((c) => c.deterministic_metrics.model_A.other_topic_size); + const otherTopicSizeB = comparisons.map((c) => c.deterministic_metrics.model_B.other_topic_size); + // Calculate bullets percentage const bulletsA = comparisons.filter((c) => c.deterministic_metrics.model_A.has_bullets).length; const bulletsB = comparisons.filter((c) => c.deterministic_metrics.model_B.has_bullets).length; @@ -264,6 +270,8 @@ function aggregateDeterministicMetrics( character_count: computeStatisticalMetric(charCountA, charCountB), paragraph_count: computeStatisticalMetric(paraCountA, paraCountB), average_sentence_length: computeStatisticalMetric(avgSentLengthA, avgSentLengthB), + mean_ai_topic_size: computeStatisticalMetric(meanTopicSizeA, meanTopicSizeB), + other_topic_size: computeStatisticalMetric(otherTopicSizeA, otherTopicSizeB), has_bullets_percent: { percent_A: percentA, percent_B: percentB, diff --git a/comparison/scripts/compare-summaries.ts b/comparison/scripts/compare-summaries.ts index d3ed37f..930afdb 100644 --- a/comparison/scripts/compare-summaries.ts +++ b/comparison/scripts/compare-summaries.ts @@ -53,6 +53,17 @@ function calculateMetrics(summary: ExpressionSummary): DeterministicMetrics { // Average sentence length const average_sentence_length = sentence_count > 0 ? word_count / sentence_count : 0; + // Mean AI topic size (excluding "Other" topic) + const nonOtherTopics = summary.topics.filter((t) => t.headline !== "Other"); + const mean_ai_topic_size = + nonOtherTopics.length > 0 + ? nonOtherTopics.reduce((sum, t) => sum + t.summaries.length, 0) / nonOtherTopics.length + : 0; + + // Other topic size + const otherTopic = summary.topics.find((t) => t.headline === "Other"); + const other_topic_size = otherTopic ? otherTopic.summaries.length : 0; + return { character_count, word_count, @@ -61,6 +72,8 @@ function calculateMetrics(summary: ExpressionSummary): DeterministicMetrics { topic_count, has_bullets, average_sentence_length: Math.round(average_sentence_length * 10) / 10, + mean_ai_topic_size: Math.round(mean_ai_topic_size * 10) / 10, + other_topic_size, }; } diff --git a/comparison/scripts/types.ts b/comparison/scripts/types.ts index 84aa436..779a532 100644 --- a/comparison/scripts/types.ts +++ b/comparison/scripts/types.ts @@ -74,6 +74,8 @@ export interface DeterministicMetrics { topic_count: number; has_bullets: boolean; average_sentence_length: number; + mean_ai_topic_size: number; // Mean of summaries.length for topics where headline !== "Other" + other_topic_size: number; // summaries.length for the single topic where headline === "Other" } // ============================================================================ @@ -212,6 +214,8 @@ export interface DeterministicMetricsAggregate { character_count: StatisticalMetric; paragraph_count: StatisticalMetric; average_sentence_length: StatisticalMetric; + mean_ai_topic_size: StatisticalMetric; + other_topic_size: StatisticalMetric; has_bullets_percent: { percent_A: number; percent_B: number; From 47307d688776803c8f8e9106561c78c8bdcb6711 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Wed, 22 Oct 2025 01:57:52 +0100 Subject: [PATCH 23/36] html reports --- CLAUDE-ai-provider-comparison.md | 14 +- comparison/config/sites.json | 23 +- comparison/scripts/generate-html-reports.ts | 528 ++++++++++++++++++++ comparison/scripts/types.ts | 1 + package.json | 3 +- 5 files changed, 562 insertions(+), 7 deletions(-) create mode 100644 comparison/scripts/generate-html-reports.ts diff --git a/CLAUDE-ai-provider-comparison.md b/CLAUDE-ai-provider-comparison.md index fa3a633..80c2aea 100644 --- a/CLAUDE-ai-provider-comparison.md +++ b/CLAUDE-ai-provider-comparison.md @@ -103,6 +103,10 @@ This work will be done within the existing `expression-shepherd` repository, lev 3. Model identities remain anonymous through aggregation; only revealed when saving final reports 4. Optional future step: Super-aggregation comparing all 3 pairwise reports +### Phase 4: HTML Report Generation +1. Generate user-friendly HTML reports from aggregate JSON files with Tailwind CSS styling +2. Output: One HTML report per model pair (3 reports total), organized into sections for quantitative metrics, qualitative comparisons, and explanatory notes + ## Project Structure New files/directories within existing `expression-shepherd` repository: @@ -120,7 +124,8 @@ expression-shepherd/ │ │ ├── fetch-summaries.ts (Phase 1: API calls and JSON storage) │ │ ├── compare-summaries.ts (Phase 2: AI-powered bidirectional comparison) │ │ ├── condense-comparisons.ts (Phase 2.5: merge bidirectional pairs) -│ │ └── aggregate-analysis.ts (Phase 3: theme identification) +│ │ ├── aggregate-analysis.ts (Phase 3: theme identification) +│ │ └── generate-html-reports.ts (Phase 4: HTML report generation) │ └── data/ │ ├── summaries/ │ │ ├── claude4/ (JSON response files) @@ -144,8 +149,11 @@ expression-shepherd/ │ └── aggregate-reports/ │ └── {analysisModel}/ (e.g., claude4) │ ├── claude4-gpt4o-report.json +│ ├── claude4-gpt4o-report.html │ ├── claude4-gpt5-report.json -│ └── gpt4o-gpt5-report.json +│ ├── claude4-gpt5-report.html +│ ├── gpt4o-gpt5-report.json +│ └── gpt4o-gpt5-report.html ``` ## Technical Notes @@ -237,6 +245,7 @@ NPM scripts are available in `package.json`: - `yarn comparison:compare` - Phase 2: Generate bidirectional pairwise comparisons - `yarn comparison:condense` - Phase 2.5: Condense bidirectional pairs into merged summaries - `yarn comparison:aggregate` - Phase 3: Generate aggregate analysis report +- `yarn comparison:html` - Phase 4: Generate HTML reports from aggregate JSON files All scripts automatically run `yarn build` before execution. @@ -263,6 +272,7 @@ Already available in repository: - Quantitative metrics (avg observations, insights, quantitative mentions, word counts, etc.) - Qualitative patterns (tone, technical detail, structure themes) - Position bias statistics +- Three user-friendly HTML reports (one per model pair) with organized sections and explanatory notes - Model anonymity maintained through all analysis phases until final report generation - Reproducible process that can be re-run with new gene sets diff --git a/comparison/config/sites.json b/comparison/config/sites.json index b8fce75..af7bd82 100644 --- a/comparison/config/sites.json +++ b/comparison/config/sites.json @@ -1,15 +1,30 @@ { - "_not_this_analysis_model": { + "analysis_model": { + "model_string": "claude-sonnet-4-5-20250929", + "name": "claude4_5", + "model": "Claude Sonnet 4.5", + "platform": "anthropic" + }, + "_nor_this_analysis_model": { "model_string": "claude-sonnet-4-20250514", "name": "claude4", + "model": "Claude Sonnet 4", "platform": "anthropic" }, - "analysis_model": { + "_not_this_analysis_model": { "model_string": "gpt-5-2025-08-07", "name": "gpt5", + "model": "GPT-5 (minimal reasoning)", "platform": "openai" }, "sites": [ + { + "name": "claude4_5", + "hostname": "bmaccallum.vectorbase.org", + "appPath": "vectorbase.bmaccallum", + "model": "Claude Sonnet 4.5", + "skip": false + }, { "name": "claude4", "hostname": "bmaccallum.vectorbase.org", @@ -22,14 +37,14 @@ "hostname": "bmaccallum-b.vectorbase.org", "appPath": "vectorbase.bmaccallum-b", "model": "GPT-5", - "skip": false + "skip": true }, { "name": "gpt4o", "hostname": "qa.vectorbase.org", "appPath": "vectorbase.b69", "model": "GPT-4o", - "skip": false + "skip": true } ], "endpoint": "/service/record-types/gene/searches/single_record_question_GeneRecordClasses_GeneRecordClass/reports/aiExpression", diff --git a/comparison/scripts/generate-html-reports.ts b/comparison/scripts/generate-html-reports.ts new file mode 100644 index 0000000..678bdca --- /dev/null +++ b/comparison/scripts/generate-html-reports.ts @@ -0,0 +1,528 @@ +import "dotenv/config"; +import { readFile, readdir } from "fs/promises"; +import path from "path"; +import { writeToFile, loadGeneList, loadSitesConfig, type GeneEntry } from "./shared-utils"; +import type { AggregateReport, StatisticalMetric, DescriptiveMetric } from "./types"; + +// ============================================================================ +// Helper Functions +// ============================================================================ + +/** + * Format a number with specified decimal places + */ +function formatNumber(num: number, decimals: number = 2): string { + return num.toFixed(decimals); +} + +/** + * Format a statistical metric with mean ± std_err + */ +function formatStatMetric(metric: StatisticalMetric | DescriptiveMetric): string { + if ('mean_A' in metric) { + // StatisticalMetric + return ` +
    +
    + Model A: ${formatNumber(metric.mean_A)} ± ${formatNumber(metric.std_err_A)} +
    +
    + Model B: ${formatNumber(metric.mean_B)} ± ${formatNumber(metric.std_err_B)} +
    +
    + p = ${formatNumber(metric.t_test_pval, 4)} +
    +
    +
    ${metric.summary}
    + `; + } else { + // DescriptiveMetric + return `${formatNumber(metric.mean)} ± ${formatNumber(metric.std_err)}`; + } +} + +/** + * Get the full model display name from short name + */ +function getModelDisplayName(shortName: string, sites: Array<{ name: string; model: string }>): string { + const site = sites.find(s => s.name === shortName); + return site ? site.model : shortName; +} + +/** + * Replace "Model A" / "Model B" with actual model names in text + */ +function replaceModelNames(text: string, modelAName: string, modelBName: string): string { + return text + .replace(/\bModel A\b/g, modelAName) + .replace(/\bModel B\b/g, modelBName) + .replace(/\bSummary A\b/g, modelAName) + .replace(/\bSummary B\b/g, modelBName); +} + +// ============================================================================ +// HTML Generation Functions +// ============================================================================ + +/** + * Generate header section HTML + */ +function generateHeader( + report: AggregateReport, + modelAName: string, + modelBName: string, + analysisModelName: string, + genes: GeneEntry[] +): string { + const genesList = genes + .map(g => g.name ? `${g.id} (${g.name})` : g.id) + .join(", "); + + return ` +
    +

    + VEuPathDB AI Gene Expression Summaries: Model Comparison Report +

    +
    +
    +

    Models Compared

    +

    Model A: ${modelAName}

    +

    Model B: ${modelBName}

    +
    +
    +

    Analysis Details

    +

    Analysis Model: ${analysisModelName}

    +

    Gene Count: ${report.gene_count}

    +
    +
    +
    +

    Genes Analyzed

    +

    ${genesList}

    +
    +
    +

    + Note: All numeric results are presented as mean ± standard error + (standard error of the mean, or SEM), which indicates the precision of the mean estimate across the ${report.gene_count} genes analyzed. +

    +
    +
    + `; +} + +/** + * Generate biological content section HTML + */ +function generateBiologicalContentSection( + report: AggregateReport, + modelAName: string, + modelBName: string +): string { + const bio = report.quantitative_aggregates.biological_content; + + return ` +
    +

    Biological Content

    + +
    +

    + Note: Statistical significance tests are not performed for biological content metrics. + These observations and insights have not been expert-validated, so we present descriptive statistics only without + making claims about which model performs better or worse. +

    +
    + +

    Observations

    +
    +
    +

    Unique to ${modelAName}

    +

    ${formatStatMetric(bio.observations.avg_unique_to_model_A)}

    +
    +
    +

    Unique to ${modelBName}

    +

    ${formatStatMetric(bio.observations.avg_unique_to_model_B)}

    +
    +
    +

    Shared

    +

    ${formatStatMetric(bio.observations.avg_shared)}

    +
    +
    +

    Position Variance

    +

    ${formatStatMetric(bio.observations.avg_position_variance)}

    +
    +
    + +

    Insights

    +
    +
    +

    Unique to ${modelAName}

    +

    ${formatStatMetric(bio.insights.avg_unique_to_model_A)}

    +
    +
    +

    Unique to ${modelBName}

    +

    ${formatStatMetric(bio.insights.avg_unique_to_model_B)}

    +
    +
    +

    Shared

    +

    ${formatStatMetric(bio.insights.avg_shared)}

    +
    +
    +

    Position Variance

    +

    ${formatStatMetric(bio.insights.avg_position_variance)}

    +
    +
    +
    + `; +} + +/** + * Generate deterministic metrics section HTML + */ +function generateDeterministicMetricsSection( + report: AggregateReport, + modelAName: string, + modelBName: string +): string { + const metrics = report.quantitative_aggregates.deterministic_metrics; + + return ` +
    +

    Deterministic Metrics

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Metric${modelAName}${modelBName}p-valueResult
    Word Count${formatNumber(metrics.word_count.mean_A)} ± ${formatNumber(metrics.word_count.std_err_A)}${formatNumber(metrics.word_count.mean_B)} ± ${formatNumber(metrics.word_count.std_err_B)}${formatNumber(metrics.word_count.t_test_pval, 4)}${replaceModelNames(metrics.word_count.summary, modelAName, modelBName)}
    Topic Count${formatNumber(metrics.topic_count.mean_A)} ± ${formatNumber(metrics.topic_count.std_err_A)}${formatNumber(metrics.topic_count.mean_B)} ± ${formatNumber(metrics.topic_count.std_err_B)}${formatNumber(metrics.topic_count.t_test_pval, 4)}${replaceModelNames(metrics.topic_count.summary, modelAName, modelBName)}
    Sentence Count${formatNumber(metrics.sentence_count.mean_A)} ± ${formatNumber(metrics.sentence_count.std_err_A)}${formatNumber(metrics.sentence_count.mean_B)} ± ${formatNumber(metrics.sentence_count.std_err_B)}${formatNumber(metrics.sentence_count.t_test_pval, 4)}${replaceModelNames(metrics.sentence_count.summary, modelAName, modelBName)}
    Character Count${formatNumber(metrics.character_count.mean_A)} ± ${formatNumber(metrics.character_count.std_err_A)}${formatNumber(metrics.character_count.mean_B)} ± ${formatNumber(metrics.character_count.std_err_B)}${formatNumber(metrics.character_count.t_test_pval, 4)}${replaceModelNames(metrics.character_count.summary, modelAName, modelBName)}
    Paragraph Count${formatNumber(metrics.paragraph_count.mean_A)} ± ${formatNumber(metrics.paragraph_count.std_err_A)}${formatNumber(metrics.paragraph_count.mean_B)} ± ${formatNumber(metrics.paragraph_count.std_err_B)}${formatNumber(metrics.paragraph_count.t_test_pval, 4)}${replaceModelNames(metrics.paragraph_count.summary, modelAName, modelBName)}
    Avg Sentence Length${formatNumber(metrics.average_sentence_length.mean_A)} ± ${formatNumber(metrics.average_sentence_length.std_err_A)}${formatNumber(metrics.average_sentence_length.mean_B)} ± ${formatNumber(metrics.average_sentence_length.std_err_B)}${formatNumber(metrics.average_sentence_length.t_test_pval, 4)}${replaceModelNames(metrics.average_sentence_length.summary, modelAName, modelBName)}
    Mean AI Topic Size${formatNumber(metrics.mean_ai_topic_size.mean_A)} ± ${formatNumber(metrics.mean_ai_topic_size.std_err_A)}${formatNumber(metrics.mean_ai_topic_size.mean_B)} ± ${formatNumber(metrics.mean_ai_topic_size.std_err_B)}${formatNumber(metrics.mean_ai_topic_size.t_test_pval, 4)}${replaceModelNames(metrics.mean_ai_topic_size.summary, modelAName, modelBName)}
    Other Topic Size${formatNumber(metrics.other_topic_size.mean_A)} ± ${formatNumber(metrics.other_topic_size.std_err_A)}${formatNumber(metrics.other_topic_size.mean_B)} ± ${formatNumber(metrics.other_topic_size.std_err_B)}${formatNumber(metrics.other_topic_size.t_test_pval, 4)}${replaceModelNames(metrics.other_topic_size.summary, modelAName, modelBName)}
    Has Bullets %${formatNumber(metrics.has_bullets_percent.percent_A)}%${formatNumber(metrics.has_bullets_percent.percent_B)}%N/A${metrics.has_bullets_percent.note}
    +
    +
    + `; +} + +/** + * Generate quantitative mentions and position bias section HTML + */ +function generateQuantitativeMentionsSection( + report: AggregateReport, + modelAName: string, + modelBName: string +): string { + const quant = report.quantitative_aggregates.quantitative_mentions; + const bias = report.quantitative_aggregates.position_bias; + + const genesWithContradictions = bias.genes_with_contradictions.length > 0 + ? bias.genes_with_contradictions.join(", ") + : "None"; + + return ` +
    +

    Quantitative Expression Mentions

    + +
    +

    + What are Quantitative Expression Mentions? These are specific numeric values + cited in the AI-generated summaries, such as fold changes, TPM (transcripts per million) values, and percentile ranks. + A higher count indicates more evidence-based, data-rich summaries. +

    +
    + +
    +
    +

    ${modelAName}

    +

    ${formatNumber(quant.mean_A)} ± ${formatNumber(quant.std_err_A)}

    +
    +
    +

    ${modelBName}

    +

    ${formatNumber(quant.mean_B)} ± ${formatNumber(quant.std_err_B)}

    +
    +
    +

    Statistical Test

    +

    ${replaceModelNames(quant.summary, modelAName, modelBName)}

    +
    +
    + +

    Position Bias Analysis

    + +
    +

    + What is Position Bias? When asking AI (or humans) to make comparative judgments, + the order of presentation can influence the result. To detect this bias, we compare each pair of summaries twice—once + as "A vs B" and again as "B vs A"—and check whether the qualitative assessments contradict each other. A low + contradiction rate indicates robust, order-independent judgments. +

    +
    + +
    +
    +

    Contradiction Rate

    +

    ${formatNumber(bias.contradiction_rate_percent)}%

    +
    +
    +

    Genes with Contradictions

    +

    ${genesWithContradictions}

    +
    +
    +
    + `; +} + +/** + * Generate qualitative aggregates section HTML (comparison only) + */ +function generateQualitativeSection( + report: AggregateReport, + modelAName: string, + modelBName: string +): string { + const qual = report.qualitative_aggregates; + + return ` +
    +

    Qualitative Comparison

    + +
    +
    +

    Tone and Style

    +
    +

    ${replaceModelNames(qual.tone_and_style.comparison.consensus_summary, modelAName, modelBName)}

    +
    +

    + Consistency of this pattern across ${report.gene_count} independently assessed genes: ${qual.tone_and_style.comparison.consistency_score} +

    +
    + +
    +

    Technical Detail Level

    +
    +

    ${replaceModelNames(qual.technical_detail_level.comparison.consensus_summary, modelAName, modelBName)}

    +
    +

    + Consistency of this pattern across ${report.gene_count} independently assessed genes: ${qual.technical_detail_level.comparison.consistency_score} +

    +
    + +
    +

    Structure and Organization

    +
    +

    ${replaceModelNames(qual.structure_and_organization.comparison.consensus_summary, modelAName, modelBName)}

    +
    +

    + Consistency of this pattern across ${report.gene_count} independently assessed genes: ${qual.structure_and_organization.comparison.consistency_score} +

    +
    +
    +
    + `; +} + +/** + * Generate complete HTML report + */ +function generateHTMLReport( + report: AggregateReport, + modelAName: string, + modelBName: string, + analysisModelName: string, + genes: GeneEntry[] +): string { + return ` + + + + + ${modelAName} vs ${modelBName} - Comparison Report + + + +
    + ${generateHeader(report, modelAName, modelBName, analysisModelName, genes)} + ${generateBiologicalContentSection(report, modelAName, modelBName)} + ${generateDeterministicMetricsSection(report, modelAName, modelBName)} + ${generateQuantitativeMentionsSection(report, modelAName, modelBName)} + ${generateQualitativeSection(report, modelAName, modelBName)} + +
    + Generated on ${new Date().toLocaleString()} +
    +
    + +`; +} + +// ============================================================================ +// Main Execution +// ============================================================================ + +async function main() { + console.log("=".repeat(60)); + console.log("HTML REPORT GENERATION"); + console.log("=".repeat(60)); + + // Load configuration + console.log("\nLoading configuration..."); + const { config, activeSites } = await loadSitesConfig(); + console.log(`Analysis model: ${config.analysis_model.name}`); + + // Load gene list + console.log("\nLoading gene list..."); + const genes = await loadGeneList(); + console.log(`Found ${genes.length} genes`); + + // Find all aggregate report JSON files for this analysis model + const aggregateDir = path.join(process.cwd(), `comparison/data/aggregate-reports/${config.analysis_model.name}`); + console.log(`\nScanning directory: ${aggregateDir}`); + + let reportFiles: string[]; + try { + const allFiles = await readdir(aggregateDir); + reportFiles = allFiles.filter(f => f.endsWith("-report.json")); + console.log(`Found ${reportFiles.length} aggregate report(s)`); + } catch (error) { + console.error(`Error reading aggregate reports directory: ${error instanceof Error ? error.message : error}`); + process.exit(1); + } + + if (reportFiles.length === 0) { + console.error("No aggregate reports found. Run Phase 3 (yarn comparison:aggregate) first."); + process.exit(1); + } + + // Process each report + let successCount = 0; + let errorCount = 0; + let skippedCount = 0; + + for (const reportFile of reportFiles) { + try { + console.log(`\nProcessing: ${reportFile}`); + + // Load the JSON report + const reportPath = path.join(aggregateDir, reportFile); + const reportContent = await readFile(reportPath, "utf-8"); + const report: AggregateReport = JSON.parse(reportContent); + + // Check if both models exist in activeSites + const modelAExists = activeSites.some(s => s.name === report.model_pair.model_A); + const modelBExists = activeSites.some(s => s.name === report.model_pair.model_B); + + if (!modelAExists || !modelBExists) { + const missing = []; + if (!modelAExists) missing.push(report.model_pair.model_A); + if (!modelBExists) missing.push(report.model_pair.model_B); + console.log(` SKIPPED: Model(s) not in active sites: ${missing.join(", ")}`); + skippedCount++; + continue; + } + + // Get full model display names + const modelAName = getModelDisplayName(report.model_pair.model_A, activeSites); + const modelBName = getModelDisplayName(report.model_pair.model_B, activeSites); + const analysisModelName = config.analysis_model.model || config.analysis_model.name; + + console.log(` Models: ${modelAName} vs ${modelBName}`); + + // Generate HTML + const html = generateHTMLReport(report, modelAName, modelBName, analysisModelName, genes); + + // Write HTML file + const htmlFileName = reportFile.replace("-report.json", "-report.html"); + const htmlPath = path.join(aggregateDir, htmlFileName); + await writeToFile(htmlPath, html); + + successCount++; + } catch (error) { + console.error(` ERROR: ${error instanceof Error ? error.message : error}`); + errorCount++; + } + } + + // Summary + console.log("\n" + "=".repeat(60)); + console.log("SUMMARY"); + console.log("=".repeat(60)); + console.log(`Total reports: ${reportFiles.length}`); + console.log(`Successful: ${successCount}`); + console.log(`Skipped: ${skippedCount}`); + console.log(`Failed: ${errorCount}`); + + if (errorCount > 0) { + process.exit(1); + } +} + +// Run the script +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); +}); diff --git a/comparison/scripts/types.ts b/comparison/scripts/types.ts index 779a532..2059dee 100644 --- a/comparison/scripts/types.ts +++ b/comparison/scripts/types.ts @@ -13,6 +13,7 @@ export interface SiteConfig { export interface AnalysisModelConfig { model_string: string; name: string; + model?: string; // Display name for the analysis model (e.g., "GPT-5 (minimal reasoning)") platform: 'anthropic' | 'openai'; } diff --git a/package.json b/package.json index 0df3c3b..3f1e571 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "comparison:fetch": "yarn build && node dist/comparison/scripts/fetch-summaries.js", "comparison:compare": "yarn build && node dist/comparison/scripts/compare-summaries.js", "comparison:condense": "yarn build && node dist/comparison/scripts/condense-comparisons.js", - "comparison:aggregate": "yarn build && node dist/comparison/scripts/aggregate-analysis.js" + "comparison:aggregate": "yarn build && node dist/comparison/scripts/aggregate-analysis.js", + "comparison:html": "yarn build && node dist/comparison/scripts/generate-html-reports.js" } } From b2d3a92f1c3bb7da7811c77324a012d6bdffc361 Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Wed, 22 Oct 2025 02:18:02 +0100 Subject: [PATCH 24/36] modal rep --- comparison/scripts/aggregate-analysis.ts | 12 +++++++++--- comparison/scripts/generate-html-reports.ts | 15 ++++++++++++--- comparison/scripts/types.ts | 1 + 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/comparison/scripts/aggregate-analysis.ts b/comparison/scripts/aggregate-analysis.ts index cf64369..27667c2 100644 --- a/comparison/scripts/aggregate-analysis.ts +++ b/comparison/scripts/aggregate-analysis.ts @@ -150,6 +150,9 @@ function interpolateGeneNames(report: AggregateReport, geneNameMap: Map formatGeneWithName(id, geneNameMap)); dist.strong_disagreement = dist.strong_disagreement.map((id) => formatGeneWithName(id, geneNameMap)); + // Replace modal_representative gene ID + fieldAggregate.modal_representative = formatGeneWithName(fieldAggregate.modal_representative, geneNameMap); + // Replace gene IDs in disagreement_analysis prose using single regex pass fieldAggregate.disagreement_analysis = fieldAggregate.disagreement_analysis.replace( allGeneIdsRegex, @@ -373,19 +376,21 @@ Your task: - Neutral/mixed: Neither clearly agrees nor disagrees - Mild disagreement: Some contradictions - Strong disagreement: Major contradictions -3. Describe the main axes of disagreement (if any) +3. Identify the modal representative: the single gene from the largest cluster that best exemplifies the consensus +4. Describe the main axes of disagreement (if any) Respond with JSON in this format: \`\`\`json { "consensus_summary": "Your synthesized consensus statement here", "agreement_distribution": { - "strong_agreement": ["AGAP000693", "AGAP001212"], - "mild_agreement": ["AGAP000999"], + "strong_agreement": ["AGAP012345", "AGAP012346"], + "mild_agreement": ["AGAP012347"], "neutral_mixed": [], "mild_disagreement": [], "strong_disagreement": [] }, + "modal_representative": "AGAP012345", "disagreement_analysis": "Description of main disagreement axes, or 'No significant disagreement' if largely consistent" } \`\`\` @@ -401,6 +406,7 @@ Respond ONLY with valid JSON, no other text.`; consistency_score, agreement_distribution: parsed.agreement_distribution, disagreement_analysis: parsed.disagreement_analysis, + modal_representative: parsed.modal_representative, }; } catch (error) { console.error("Failed to parse AI response"); diff --git a/comparison/scripts/generate-html-reports.ts b/comparison/scripts/generate-html-reports.ts index 678bdca..05aea41 100644 --- a/comparison/scripts/generate-html-reports.ts +++ b/comparison/scripts/generate-html-reports.ts @@ -357,7 +357,10 @@ function generateQualitativeSection(

    ${replaceModelNames(qual.tone_and_style.comparison.consensus_summary, modelAName, modelBName)}

    -

    +

    + Modal representative: ${qual.tone_and_style.comparison.modal_representative} +

    +

    Consistency of this pattern across ${report.gene_count} independently assessed genes: ${qual.tone_and_style.comparison.consistency_score}

    @@ -367,7 +370,10 @@ function generateQualitativeSection(

    ${replaceModelNames(qual.technical_detail_level.comparison.consensus_summary, modelAName, modelBName)}

    -

    +

    + Modal representative: ${qual.technical_detail_level.comparison.modal_representative} +

    +

    Consistency of this pattern across ${report.gene_count} independently assessed genes: ${qual.technical_detail_level.comparison.consistency_score}

    @@ -377,7 +383,10 @@ function generateQualitativeSection(

    ${replaceModelNames(qual.structure_and_organization.comparison.consensus_summary, modelAName, modelBName)}

    -

    +

    + Modal representative: ${qual.structure_and_organization.comparison.modal_representative} +

    +

    Consistency of this pattern across ${report.gene_count} independently assessed genes: ${qual.structure_and_organization.comparison.consistency_score}

    diff --git a/comparison/scripts/types.ts b/comparison/scripts/types.ts index 2059dee..8c96a4c 100644 --- a/comparison/scripts/types.ts +++ b/comparison/scripts/types.ts @@ -244,6 +244,7 @@ export interface QualitativeFieldAggregate { consistency_score: ConsistencyScore; agreement_distribution: AgreementDistribution; disagreement_analysis: string; + modal_representative: string; // Gene ID that best represents the consensus (from largest cluster) } export interface QualitativeDimensionAggregate { From d7b63b4e9dd07e39857ec7eeafc1767d215e319f Mon Sep 17 00:00:00 2001 From: Bob MacCallum Date: Wed, 22 Oct 2025 02:59:31 +0100 Subject: [PATCH 25/36] add simple gene summary pages --- comparison/scripts/generate-html-reports.ts | 49 ++- comparison/scripts/make-html-summaries.ts | 340 ++++++++++++++++++++ package.json | 2 +- 3 files changed, 381 insertions(+), 10 deletions(-) create mode 100644 comparison/scripts/make-html-summaries.ts diff --git a/comparison/scripts/generate-html-reports.ts b/comparison/scripts/generate-html-reports.ts index 05aea41..4bee2a5 100644 --- a/comparison/scripts/generate-html-reports.ts +++ b/comparison/scripts/generate-html-reports.ts @@ -60,6 +60,29 @@ function replaceModelNames(text: string, modelAName: string, modelBName: string) .replace(/\bSummary B\b/g, modelBName); } +/** + * Extract gene ID from formatted string like "AGAP001212 (Gene name)" or "AGAP001212" + * Returns the first non-whitespace word + */ +function extractGeneId(formattedGene: string): string { + return formattedGene.trim().split(/\s+/)[0]; +} + +/** + * Create HTML link for a gene to its summary pages + */ +function createGeneLink(formattedGene: string, modelAShort: string, modelBShort: string): string { + const geneId = extractGeneId(formattedGene); + return `${formattedGene} (alt)`; +} + +/** + * Convert list of formatted gene strings into linked HTML + */ +function createGeneLinks(formattedGenes: string[], modelAShort: string, modelBShort: string): string { + return formattedGenes.map(g => createGeneLink(g, modelAShort, modelBShort)).join(", "); +} + // ============================================================================ // HTML Generation Functions // ============================================================================ @@ -276,13 +299,15 @@ function generateDeterministicMetricsSection( function generateQuantitativeMentionsSection( report: AggregateReport, modelAName: string, - modelBName: string + modelBName: string, + modelAShort: string, + modelBShort: string ): string { const quant = report.quantitative_aggregates.quantitative_mentions; const bias = report.quantitative_aggregates.position_bias; const genesWithContradictions = bias.genes_with_contradictions.length > 0 - ? bias.genes_with_contradictions.join(", ") + ? createGeneLinks(bias.genes_with_contradictions, modelAShort, modelBShort) : "None"; return ` @@ -343,7 +368,9 @@ function generateQuantitativeMentionsSection( function generateQualitativeSection( report: AggregateReport, modelAName: string, - modelBName: string + modelBName: string, + modelAShort: string, + modelBShort: string ): string { const qual = report.qualitative_aggregates; @@ -358,7 +385,7 @@ function generateQualitativeSection(

    ${replaceModelNames(qual.tone_and_style.comparison.consensus_summary, modelAName, modelBName)}

    - Modal representative: ${qual.tone_and_style.comparison.modal_representative} + Modal representative: ${createGeneLink(qual.tone_and_style.comparison.modal_representative, modelAShort, modelBShort)}

    Consistency of this pattern across ${report.gene_count} independently assessed genes: ${qual.tone_and_style.comparison.consistency_score} @@ -371,7 +398,7 @@ function generateQualitativeSection(

    ${replaceModelNames(qual.technical_detail_level.comparison.consensus_summary, modelAName, modelBName)}

    - Modal representative: ${qual.technical_detail_level.comparison.modal_representative} + Modal representative: ${createGeneLink(qual.technical_detail_level.comparison.modal_representative, modelAShort, modelBShort)}

    Consistency of this pattern across ${report.gene_count} independently assessed genes: ${qual.technical_detail_level.comparison.consistency_score} @@ -384,7 +411,7 @@ function generateQualitativeSection(

    ${replaceModelNames(qual.structure_and_organization.comparison.consensus_summary, modelAName, modelBName)}

    - Modal representative: ${qual.structure_and_organization.comparison.modal_representative} + Modal representative: ${createGeneLink(qual.structure_and_organization.comparison.modal_representative, modelAShort, modelBShort)}

    Consistency of this pattern across ${report.gene_count} independently assessed genes: ${qual.structure_and_organization.comparison.consistency_score} @@ -402,6 +429,8 @@ function generateHTMLReport( report: AggregateReport, modelAName: string, modelBName: string, + modelAShort: string, + modelBShort: string, analysisModelName: string, genes: GeneEntry[] ): string { @@ -418,8 +447,8 @@ function generateHTMLReport( ${generateHeader(report, modelAName, modelBName, analysisModelName, genes)} ${generateBiologicalContentSection(report, modelAName, modelBName)} ${generateDeterministicMetricsSection(report, modelAName, modelBName)} - ${generateQuantitativeMentionsSection(report, modelAName, modelBName)} - ${generateQualitativeSection(report, modelAName, modelBName)} + ${generateQuantitativeMentionsSection(report, modelAName, modelBName, modelAShort, modelBShort)} + ${generateQualitativeSection(report, modelAName, modelBName, modelAShort, modelBShort)}