diff --git a/src/__tests__/acdPrediction/toc.json b/src/__tests__/acdPrediction/toc.json index 1f53b04..826e45a 100644 --- a/src/__tests__/acdPrediction/toc.json +++ b/src/__tests__/acdPrediction/toc.json @@ -3,25 +3,25 @@ "id": "Oz9gTP3NhSQZlWBu4aT4cizhQzg=", "mf": "C4H8O2", "idCodeHash": "8dce89763ab899350b4a01be188c1555", - "idCode": "gGP`@df]j`@", "file": "./Bsp1/index.json", "title": "Bsp1", + "idCode": "gGP`@df]j`@", "selected": true }, { "id": "Il6UfZ1v3NduB9nsPgFi5RA4Xbg=", "mf": "C4H8O2", "idCodeHash": "134d499936f8fedb1170e3f741f72b9e", - "idCode": "gGP`@dfUj`@", "file": "./Bsp2/index.json", - "title": "Bsp2" + "title": "Bsp2", + "idCode": "gGP`@dfUj`@" }, { "id": "z3uJ3OLwf2z/bAlG9yYAuADV/gA=", "mf": "C4H8O", "idCodeHash": "21dcc0027d441801e362cec51f90e9b9", - "idCode": "gJQ@@eKS@@", "file": "./ethylvinylether/index.json", - "title": "ethylvinylether" + "title": "ethylvinylether", + "idCode": "gJQ@@eKS@@" } ] \ No newline at end of file diff --git a/src/__tests__/createExercisesTOC.test.js b/src/__tests__/createExercisesTOC.test.js index b7e4c3b..d9e54cd 100644 --- a/src/__tests__/createExercisesTOC.test.js +++ b/src/__tests__/createExercisesTOC.test.js @@ -8,7 +8,10 @@ import { createExercisesTOC } from '../commands/createExercisesTOC.js'; test('createExercisesTOC', async () => { const dataDir = join(__dirname, 'exercises'); - await createExercisesTOC(__dirname, { dataDir }); + await createExercisesTOC(__dirname, { + dataDir, + appendNoStereoIDCodeHash: true, + }); const files = (await readdir(dataDir)).sort(); const sizes = files @@ -16,14 +19,15 @@ test('createExercisesTOC', async () => { .map((file) => { return statSync(file).size; }); - expect(sizes).toStrictEqual([257, 138, 258, 746, 412]); + expect(sizes).toStrictEqual([138, 257, 138, 258, 1053, 412]); }); test('createExercisesTOC with predicted', async () => { const dataDir = join(__dirname, 'acdPrediction'); await createExercisesTOC(__dirname, { dataDir, - keepIdCode: true, + appendIDCode: true, + cleanJCAMP: true, }); diff --git a/src/__tests__/exercises/Chiral/1h.jdx b/src/__tests__/exercises/Chiral/1h.jdx new file mode 100644 index 0000000..e69de29 diff --git a/src/__tests__/exercises/Chiral/index.json b/src/__tests__/exercises/Chiral/index.json new file mode 100644 index 0000000..9e97533 --- /dev/null +++ b/src/__tests__/exercises/Chiral/index.json @@ -0,0 +1,12 @@ +{ + "spectra": [ + { + "source": { + "jcampURL": "./1h.jdx" + }, + "display": { + "name": "" + } + } + ] +} \ No newline at end of file diff --git a/src/__tests__/exercises/Chiral/structure.mol b/src/__tests__/exercises/Chiral/structure.mol new file mode 100644 index 0000000..3b1850b --- /dev/null +++ b/src/__tests__/exercises/Chiral/structure.mol @@ -0,0 +1,18 @@ + +Actelion Java MolfileCreator 1.0 + + 7 6 0 0 1 0 0 0 0 0999 V2000 + 6.1251 -12.1252 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 7.4241 -12.8752 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 8.7231 -12.1252 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 10.0222 -12.8752 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 11.3211 -12.1252 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 7.4241 -14.3752 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 8.7231 -10.6252 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 1 0 0 0 0 + 3 4 1 0 0 0 0 + 4 5 1 0 0 0 0 + 2 6 1 1 0 0 0 + 3 7 1 1 0 0 0 +M END diff --git a/src/__tests__/exercises/toc.json b/src/__tests__/exercises/toc.json index 785db2a..3c5e9e4 100644 --- a/src/__tests__/exercises/toc.json +++ b/src/__tests__/exercises/toc.json @@ -1,11 +1,20 @@ [ + { + "id": "XjKWTz7MPgN2x3evAUQWsjpoXNE=", + "mf": "C5H12O2", + "idCodeHash": "3d95df75d24a4142629d4a1258458cfb", + "file": "./Chiral/index.json", + "title": "Chiral", + "noStereoIDCodeHash": "21244f700a4490ed7d963ae780723fd6", + "racemateIDCodeHash": "ad594ab988e3bfd5e52f0ab5ffe4e59c", + "selected": true + }, { "id": "8x+bblgo31zA/Y8aAYQHBwjFaZg=", "mf": "C9H18", "idCodeHash": "7ec5fb4efcce0c89de85193a330e6679", "file": "./Exercises3/index.json", - "title": "Exercises3", - "selected": true + "title": "Exercises3" }, { "groupName": "Series1", diff --git a/src/commands/createExercisesTOC.js b/src/commands/createExercisesTOC.js index cd4a6ec..6aef5fb 100644 --- a/src/commands/createExercisesTOC.js +++ b/src/commands/createExercisesTOC.js @@ -6,7 +6,9 @@ import { processExerciseFolder } from './toc/processExerciseFolder.js'; * @param {string} commandDir * @param {object} [options={}] * @param {string} [options.dataDir] - * @param {boolean} [options.keepIdCode] - kepp idCode in the answer to be able to provide tips + * @param {boolean} [options.appendIDCode] - append idCode in the answer to be able to provide tips + * @param {boolean} [options.appendNoStereoIDCodeHash] - append noStrereo idCode hash to check the answer without stereochemistry + * @param {boolean} [options.appendRacemateIDCodeHash] - append racemate idCode hash to check the answer as a racemate * @param {boolean} [options.cleanJCAMP] - keep only the spectrum in the JCAMP-DX */ export async function createExercisesTOC(commandDir, options = {}) { diff --git a/src/commands/toc/createToc.js b/src/commands/toc/createToc.js index bb9ca50..55c6ff4 100644 --- a/src/commands/toc/createToc.js +++ b/src/commands/toc/createToc.js @@ -14,7 +14,7 @@ const debug = debugFct('nmrium.toc'); * @param {string} commandDir * @param {object} [options={}] * @param {string} [options.dataDir=commandDir] - * @param {boolean} [options.keepIdCode=false] - Add the idCode to the toc + * @param {boolean} [options.appendIDCode=false] - Add the idCode to the toc */ export async function createToc(commandDir, folderProcessor, options = {}) { const { dataDir = commandDir } = options; diff --git a/src/commands/toc/processExerciseFolder.js b/src/commands/toc/processExerciseFolder.js index 379ccd2..90a0142 100644 --- a/src/commands/toc/processExerciseFolder.js +++ b/src/commands/toc/processExerciseFolder.js @@ -6,6 +6,7 @@ import { hashElement } from 'folder-hash'; import { createTree } from 'jcampconverter'; import md5 from 'md5'; import OCL from 'openchemlib'; +import { makeRacemic } from 'openchemlib-utils'; const { Molecule } = OCL; @@ -21,7 +22,9 @@ let exercise = 0; * @param {object} toc * @param {object} [options={}] * @param {string} [options.spectraFilter] - comma separated list of experiments to process, if not defined all experiments are processed - * @param {string} [options.keepIdCode=false] - Keep idCode in the toc + * @param {string} [options.appendIDCode=false] - Append idCode in the toc + * @param {string} [options.appendNoStereoIDCodeHash=false] - Append noStereo idCode hash to check + * @param {string} [options.appendRacemateIDCodeHash=true] - Append racemate idCode hash to check * @param {boolean} [options.cleanJCAMP] - keep only the spectrum in the JCAMP-DX */ @@ -31,13 +34,20 @@ export async function processExerciseFolder( toc, options = {}, ) { - let { spectraFilter, keepIdCode = false, cleanJCAMP = false } = options; + let { + spectraFilter, + appendIDCode = false, + cleanJCAMP = false, + appendNoStereoIDCodeHash = false, + appendRacemateIDCodeHash = true, + } = options; if (spectraFilter) { spectraFilter = new RegExp( `^(${spectraFilter.split(',').join('|')}).`, 'i', ); } + const currentFolder = join(basename, folder); const entries = readdirSync(currentFolder); const molfileName = readdirSync(currentFolder).filter((file) => @@ -51,6 +61,7 @@ export async function processExerciseFolder( const mf = molecule.getMolecularFormula().formula; const idCode = molecule.getIDCode(); const idCodeHash = md5(idCode); + const includedFiles = []; // we only hash the included files to find out if it is the same exercise for (let spectrumName of entries.filter( (file) => @@ -95,11 +106,29 @@ export async function processExerciseFolder( id, mf, idCodeHash, - idCode: keepIdCode ? idCode : undefined, file: `${URL_FOLDER}/${folder}/index.json`, title, selected: exercise === 1 || undefined, }; + if (appendIDCode) { + tocEntry.idCode = idCode; + } + + if (appendNoStereoIDCodeHash || appendRacemateIDCodeHash) { + const { noStereoIDCodeHash, racemateIDCodeHash } = getStereoHash(molecule); + if ( + appendNoStereoIDCodeHash && + tocEntry.idCodeHash !== noStereoIDCodeHash + ) { + tocEntry.noStereoIDCodeHash = noStereoIDCodeHash; + } + if ( + appendRacemateIDCodeHash && + tocEntry.idCodeHash !== racemateIDCodeHash + ) { + tocEntry.racemateIDCodeHash = racemateIDCodeHash; + } + } toc.push(tocEntry); } @@ -143,3 +172,22 @@ function flattenTree(entries, flatten) { flatten.push(entry); } } + +/** + * get the hash without stereo information or racemate + * + * @param {import('openchemlib').Molecule} molecule + * @returns + */ +function getStereoHash(molecule) { + const moleculeNoStereo = molecule.getCompactCopy(); + moleculeNoStereo.stripStereoInformation(); + const noStereoIDCode = moleculeNoStereo.getIDCode(); + const noStereoIDCodeHash = md5(noStereoIDCode); + + const moleculeRacemate = molecule.getCompactCopy(); + makeRacemic(moleculeRacemate); + const racemateIDCode = moleculeRacemate.getIDCode(); + const racemateIDCodeHash = md5(racemateIDCode); + return { noStereoIDCodeHash, racemateIDCodeHash }; +} diff --git a/src/index.js b/src/index.js index d887637..08c0f73 100755 --- a/src/index.js +++ b/src/index.js @@ -18,11 +18,31 @@ yargs(hideBin(normalizeArgv(process.argv))) .scriptName('nmrium') .command('createExercisesTOC [options]', 'Build toc.json for exercises', { builder: (yargs) => { - return yargs.option('keepIdCode', { - alias: 'i', - type: 'boolean', - description: 'Add idCode in the exercise TOC', - }); + return yargs + .option('appendIDCode', { + alias: 'i', + type: 'boolean', + default: false, + description: 'Add idCode in the exercise TOC', + }) + .option('appendNoStereoIDCodeHash', { + alias: 's', + type: 'boolean', + default: false, + description: + 'Add an idCode hash without stereo information in order to consider as correct the molecule with wrong stereo information', + }) + .option('appendRacemateIDCodeHash', { + type: 'boolean', + default: true, + description: + 'Allows to check the answer considering any enantiomer as correct', + }) + .option('cleanJCAMP', { + type: 'boolean', + default: false, + description: 'Only keep the first spectrum in the JCAMP-DX file', + }); }, handler: (argv) => { createExercisesTOC(homeDir, { ...argv });