@@ -7,7 +7,7 @@ import { decideWorkbenchBoot } from './workbench-boot';
77import { escapeHtml , markPipelineReadyDom , renderOperationConfirmationDom , renderPreparationControlsDom , renderPreparationPolicyDom , renderRunHistoryDom , renderRunJobsDom , setSafeText } from './workbench-dom' ;
88import { acknowledgedPreparationReceipt , campaignScientificRefFrom , contentRef , fullJobId , operationBindingFromReceipt , preparationReceiptBinding , runReceiptFromData , runReceiptMatchesData , runReceiptState , type AcknowledgedPreparationReceipt , type AnyPreparationReceipt as PreparationReceipt , type ArtifactRef , type CampaignScientificRef , type ContentRef , type PreparationReceipt as DurablePreparationReceipt , type PlannerOutputReceipt , type PlannerReceipt , type RunReceipt , type RunReceiptState } from './workbench-receipts' ;
99import { globalPhysicalReceiptKey , RECEIPT_KEYS , WorkbenchReceiptStore , type ReceiptStorage } from './workbench-receipt-store' ;
10- import { OperationCoordinator , aggregateArmMatches , canonicalJson , chemistryEvidenceFrom , chemistryEvidenceView , exactOperationBindingMatches , executionEligibilityFrom , sameExactRef , type ExactAggregateArm , type ExactOperationBinding , type ExactRunBinding , type OperationScope } from './workbench-state' ;
10+ import { OperationCoordinator , aggregateArmMatches , canonicalJson , chemistryEvidenceFrom , chemistryEvidenceView , exactOperationBindingMatches , executionEligibilityFrom , isPotentialEzBond , sameExactRef , type ExactAggregateArm , type ExactOperationBinding , type ExactRunBinding , type OperationScope } from './workbench-state' ;
1111import { aggregatePanelViewFrom , preparationPolicyGate , preparationPolicyViewFrom , runHistoryViewFrom , runJobsViewFrom } from './workbench-view-model' ;
1212import { workbenchShellMarkup } from './workbench-shell' ;
1313import { DemoCompounds , FallbackEdges , FallbackNetwork } from './workbench-fixture' ;
@@ -159,8 +159,10 @@ async function atomInfo(smiles: string): Promise<AtomInfo> {
159159 try {
160160 const stereo = JSON . parse ( mol . get_stereo_tags ( ) ) as { CIP_atoms ?:Array < [ number , string ] > ; CIP_bonds ?:Array < [ number , number , string ] > } ; cipAtoms = Array . isArray ( stereo . CIP_atoms ) ?stereo . CIP_atoms :[ ] ; cipBonds = Array . isArray ( stereo . CIP_bonds ) ?stereo . CIP_bonds :[ ] ;
161161 const json = JSON . parse ( mol . get_json ( ) ) as { defaults ?:{ atom ?:{ impHs ?:number } } ; molecules ?:Array < { atoms ?:Array < { impHs ?:number } > ; extensions ?:Array < { name ?:string ; cipRanks ?:number [ ] } > } > } , molecule = json . molecules ?. [ 0 ] , atoms = molecule ?. atoms || [ ] , ranks = molecule ?. extensions ?. find ( extension => extension . name === 'rdkitRepresentation' ) ?. cipRanks || [ ] , defaultHydrogens = Number ( json . defaults ?. atom ?. impHs || 0 ) ;
162+ const aromaticAtoms = new Set < number > ( ) ; let aromaticQuery :ReturnType < typeof RDKit . get_qmol > | null = null ;
163+ try { aromaticQuery = RDKit . get_qmol ( '[a]' ) ; const matches = aromaticQuery ?JSON . parse ( mol . get_substruct_matches ( aromaticQuery ) ) as Array < { atoms ?:number [ ] } > :[ ] ; if ( Array . isArray ( matches ) ) matches . forEach ( match => match . atoms ?. forEach ( index => aromaticAtoms . add ( index ) ) ) ; } finally { aromaticQuery ?. delete ( ) ; }
162164 const substituentKeys = ( atomIndex :number , partnerIndex :number ) :string [ ] => { const keys = bonds . filter ( bond => bond . left === atomIndex || bond . right === atomIndex ) . map ( bond => bond . left === atomIndex ?bond . right :bond . left ) . filter ( index => index !== partnerIndex ) . map ( index => `A:${ String ( ranks [ index ] ?? `${ symbols [ index ] } :${ index } ` ) } ` ) , implicitHydrogens = Number ( atoms [ atomIndex ] ?. impHs ?? defaultHydrogens ) ; for ( let index = 0 ; index < implicitHydrogens ; index ++ ) keys . push ( 'H' ) ; return keys ; } ;
163- potentialEzBonds = bonds . filter ( bond => bond . order === 2 && ! cipBonds . some ( ( [ left , right ] ) => ( left === bond . left && right === bond . right ) || ( left === bond . right && right === bond . left ) ) ) . filter ( bond => { const left = substituentKeys ( bond . left , bond . right ) , right = substituentKeys ( bond . right , bond . left ) ; return left . length === 2 && right . length === 2 && left [ 0 ] !== left [ 1 ] && right [ 0 ] !== right [ 1 ] ; } ) . map ( bond => [ bond . left , bond . right ] ) ;
165+ potentialEzBonds = bonds . filter ( bond => isPotentialEzBond ( bond , aromaticAtoms ) && ! cipBonds . some ( ( [ left , right ] ) => ( left === bond . left && right === bond . right ) || ( left === bond . right && right === bond . left ) ) ) . filter ( bond => { const left = substituentKeys ( bond . left , bond . right ) , right = substituentKeys ( bond . right , bond . left ) ; return left . length === 2 && right . length === 2 && left [ 0 ] !== left [ 1 ] && right [ 0 ] !== right [ 1 ] ; } ) . map ( bond => [ bond . left , bond . right ] ) ;
164166 } catch { stereoAvailable = false ; }
165167 const result = { symbols, molblock, canonicalSmiles, charges, atomStereo, bonds, cycleRank, stereoAvailable, cipAtoms, cipBonds, potentialEzBonds } ; atomInfoCache . set ( smiles , result ) ; return result ;
166168 }
0 commit comments