Function Metrology_Services(@Service, @Params) /*********************************************************************************************************************** This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written permission from SRP Computer Solutions, Inc. Name : Metrology_Services Description : Handler program for all module related services. Notes : The generic parameters should contain all the necessary information to process the services. Often this will be information like the data Record and Key ID. Parameters : Service [in] -- Name of the service being requested Param1-10 [in/out] -- Additional request parameter holders Response [out] -- Response to be sent back to the Controller (MCP) or requesting procedure Metadata : UID000 - 'Success' UID001 - Failure. See the description in the log for further details. History : (Date, Initials, Notes) 09/04/16 dmb Original programmer. - [EPIOI-8] 01/03/18 dmb Fix logic for Tencor and SP1 imports to correctly identify the value index for imported data. 02/23/18 dmb Add support for the special 14 point thickness reading. 03/16/18 dmb Change logic to perform recipe matching for SP1. 04/26/18 djs Added logic within ImportTencorData to ensure Tencor recipe matches SurfScan list. 06/06/18 djs Added logic within LoadRunDataToDatabase to store the 14 point QA THICK_ONLY data points. Previously only the average of these points was being stored. Also added logic within ImportHgCVData to store the HgCV QA data points. 06/08/18 djs Added logic within LoadRunDataToDatabase to store the Min and Max of the data points. 06/12/18 djs Added logic within ImportedHgCVData to store the HgCV phase angle data points 06/19/18 djs Added logic within ImportHgCVData to store additional QA Metrology Data. Previously only the average of the imported data points was being stored. Now, in addition to the average, the min, max, edge mean delta, and range percent of the QA data points are being calculated and stored in the WO_MAT_QA table. Finally, the data points themselves are also now being stored in the WO_MAT_QA table. 07/23/18 djs Added logic with the ImportBioRadData service to differentiate 6 inch THICK_ONLY QA metrology tests from traditional RDS tests. Also added code to store this data within the WO_MAT_QA database table. 09/18/18 djs Added code within the ImportTencorData service to post wafer image pdfs to a staging table where they will be stored locally on the App server. 09/11/19 fdr Add new service "ImportStratusData" 12/05/24 djs Updated GetIQSViolations service to store reactor violations in the respective reactor record. ***********************************************************************************************************************/ #pragma precomp SRP_PreCompiler $insert LOGICAL $insert SERVICE_SETUP $insert RDS_EQUATES $insert PROD_SPEC_EQUATES $insert RDS_LAYER_EQUATES $insert PRS_LAYER_EQUATES $insert RDS_TEST_EQUATES $insert TOOL_CLASS_EQUATES $insert TEST_POINT_MAP_EQUATES $insert CLEAN_INSP_EQUATES $insert REACT_RUN_EQUATES $insert REACTOR_EQUATES $insert WO_MAT_QA_EQUATES $insert PRS_STAGE_EQUATES $insert SRPMail_Inserts $insert WO_MAT_EQUATES $Insert NOTIFICATION_EQUATES $Insert RLIST_EQUATES $Insert WM_OUT_EQUATES $Insert IQS_VIOL_DATA_EQUATES Common /MetrologyServices/ MachineType@, RDSNo@ Equ RETRY_ATTEMPTS$ TO 3 Equ MINUTES_UNTIL_RETRY$ TO 3 Equ ORP$THICK_READS TO 1 Equ ORP$SHEET_RHO_READS TO 2 Equ ORP$HGCV1_READS TO 3 Equ EPI_READS$READ_NO TO 1 Equ EPI_READS$THICKNESS TO 2 Equ EPI_READS$SHEET_RHO TO 3 Equ EPI_READS$HGCV1 TO 4 Equ EPI_READS$HGCV2 TO 5 Equ Tab$ to \09\ Equ CRLF$ to \0D0A\ Equ LF$ to \0A\ Equ Comma$ to ',' Declare subroutine SRP_Stopwatch, Error_Services, obj_Tables, Metrology_Services, obj_RDS_Test, SRP_JSON Declare subroutine RTI_Set_Debugger, Database_Services, Btree.Extract, Set_Status, QA_Services, obj_Notes Declare subroutine Logging_Services, SRP_Send_Mail, SRP_Run_Command, PM_Services, Httpclient_Services Declare subroutine Tool_Services, Mona_Services, Reactor_Services Declare function SRP_Sort_Array, Metrology_Services, obj_RDS_Test, obj_Test_Point_Map, Database_Services, UCase Declare function Work_Order_Services, SRP_JSON, Logging_Services, Environment_Services, SRP_Trim, Min, Max Declare function QA_Services, SRP_Join_Arrays, Get_Status, Obj_Clean_Insp, Datetime, SRP_Datetime Declare function Httpclient_Services, PM_Services, Signature_Services, SRP_Array, Math_Services Declare function Tool_Class_Services, obj_wo_mat Declare function SRP_String LogPath = Environment_Services('GetApplicationRootPath') : '\LogFiles\Metrology' LogDate = Oconv(Date(), 'D4/') LogTime = Oconv(Time(), 'MTS') LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Metrology Log.csv' Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes' objLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Tencor Metrology Log.csv' Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes' objTencorLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' HgCV Metrology Log.csv' Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes' objHgCVLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' CDE Metrology Log.csv' Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes' objCDELog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Biorad Metrology Log.csv' Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes' objBioradLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Stratus Metrology Log.csv' Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes' objStratusLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' SP1 Metrology Log.csv' Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes' objSP1Log = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' SPV Metrology Log.csv' Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes' objSPVLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' SRP Metrology Log.csv' Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes' objSRPLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Metrology Performance Log.csv' Headers = 'Logging DTM' : @FM : 'Machine Type' : @FM : 'RDS Key ID': @FM : 'Notes' objPerfLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LogPath = Environment_Services('GetApplicationRootPath') : '\LogFiles\SOD' LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' SOD Performance Log.csv' Headers = 'Logging DTM' : @FM : 'Machine Type' : @FM : 'RDS Key ID': @FM : 'Notes' objSODPerfLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LogPath = Environment_Services('GetApplicationRootPath') : '\LogFiles\POST' LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' POST Performance Log.csv' Headers = 'Logging DTM' : @FM : 'Machine Type' : @FM : 'RDS Key ID': @FM : 'Notes' objPOSTPerfLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LoggingDTM = LogDate : ' ' : LogTime ; // Logging DTM GoToService else Error_Services('Set', Service : ' is not a valid service request within the ' : ServiceModule : ' services module.') end Return Response else '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Services //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //---------------------------------------------------------------------------------------------------------------------- // LaunchMetrologyViewer // // Launches the metrology viewer URL with the client's default browser. //---------------------------------------------------------------------------------------------------------------------- Service LaunchMetrologyViewer MetrologyURL = Environment_Services('GetMetrologyViewerURL') Command = 'start ':MetrologyURL SRP_Run_Command(Command) end service Service GetJsonFromProcessDataStandardFormat(Text) json = ''; HeaderId = -1; SubgroupId = -1; FoundEndOfFile = False$; SWAP '|' WITH @VM IN Text; SWAP CRLF$ WITH @FM IN Text; LineCount = DCOUNT(Text, @FM); FOR LineLoopIndex = 1 TO LineCount Line = Text; IF FoundEndOfFile EQ True$ THEN json = json:Line; END IF Line EQ 'EOF' THEN FoundEndOfFile = True$; Line = Text; Convert Tab$ to @FM IN Line SWAP '=' WITH @VM IN Line; SWAP ';' WITH @FM IN Line; IF Line<2, 1> EQ 'B_HeaderId' THEN HeaderId = Line<2, 2> END IF Line<3, 1> EQ 'B_SubgroupId' THEN SubgroupId = Line<3, 2> END END NEXT LineLoopIndex Response = json; end service Service GetStratus(Handle) Result = ''; // Service ImportStratusData( Offset = 1 FieldPosition = 13 FieldPositionIncrement = 2 Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count'); Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence'); Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId'); Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId'); FOR RecordIndex = 1 TO Result<1, 1> IF RecordIndex EQ 1 THEN Result<2> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DateTime'); // Timestamp Result<3> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MesEntity'); // Tool Result<4> = 'FQA Thickness'; // DataType Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Employee'); // Operator Result<6> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Recipe'); // Recipe Result<7> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // Reactor Result<8> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSNo Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].PSN'); // PSN Result<10> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Batch'); // BatchID Result<11> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Cassette'); // Cassette Result<12> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].GradeMean'); // ThickAvg END ForOffset = (RecordIndex - 1) * FieldPositionIncrement; Result = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Slot'); // Position Result = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Mean'); // DataPoint NEXT RecordIndex Response = Result; end service Service GetBioRad(Handle) Result = ''; // Service ImportBioRadData( Offset = 1 FieldPosition = 13 FieldPositionIncrement = 2 Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count'); Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence'); Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId'); Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId'); FOR RecordIndex = 1 TO Result<1, 1> IF RecordIndex EQ 1 THEN Result<2> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DateTime'); // TimeStamp Result<4> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Recipe'); // ScanRecipe Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // ReactorID Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // ToolID Result<6> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID Result<7> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].PSN'); // PSN Result<8> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Layer'); // RunDataLayer Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Zone'); // RunDataZone Result<11> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Wafer'); // DataSlotId END ForOffset = (RecordIndex - 1) * FieldPositionIncrement; Result = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Position'); // Position Result = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Thickness'); // DataPoint NEXT RecordIndex Response = Result; end service Service GetCDE(Handle) Result = ''; // Service ImportCDEData( Offset = 3 FieldPosition = 23 FieldPositionIncrement = 5 Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count'); Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence'); Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId'); Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId'); FOR RecordIndex = 1 TO Result<1, 1> IF RecordIndex EQ 1 THEN Result<3> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Date'); // Timestamp Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RecipeName'); // ScanRecip Result<6> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // ReactorID Result<7> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID Result<8> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].PSN'); // PSN Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Layer'); // RunDataLayer Result<10> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Zone'); // RunDataZo END ForOffset = (RecordIndex - 1) * FieldPositionIncrement; Result = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].InferredPoint'); // Position Result = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Data'); // DataPoint Result = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MeritGOF'); // N/A NEXT RecordIndex Response = Result; end service Service GetHgCV(Handle) Result = ''; // Service ImportHgCVData( Offset = 5 PhaseOffset = 7 FieldPosition = 53 FieldPositionIncrement = 9 Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count'); Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence'); Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId'); Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId'); FOR RecordIndex = 1 TO Result<1, 1> IF RecordIndex EQ 1 THEN Result<2> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MesEntity'); // ToolID Result<3> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // ReactorID Result<4> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].PSN'); // PSN Result<8> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Wafer'); // LayerZonePair Result<11> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Date'); // Timestamp END ForOffset = (RecordIndex - 1) * FieldPositionIncrement; Result = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Site'); // Position Result = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RhoAvg'); // HgCVDataPoint Result = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Phase'); // PhaseDataPoint Result = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Grade'); // N/A NEXT RecordIndex Response = Result; end service Service GetTencor(Handle) Result = ''; // Service ImportTencorData( Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count'); Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence'); Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId'); Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId'); FOR RecordIndex = 1 TO Result<1, 1> IF RecordIndex EQ 1 THEN Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Date'); // Timestamp Result<10> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].HazeAverageAvg'); // HazeAvg Result<28> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID Result<30> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].WaferRecipe'); // ScanRecipe Result<39> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].SumOfDefectsAvg'); // SoDAvg Result<40> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].SumOfDefectsMax'); // SoDMax Result<41> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].SumOfDefectsMin'); // SoDMin Result<43> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MesEntity'); // ScanTool END NEXT RecordIndex Response = Result; end service Service GetSP1(Handle) Result = ''; // Service ImportSP1Data( Result<5, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count'); Result<5, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence'); Result<5, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId'); Result<5, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId'); FOR RecordIndex = 1 TO Result<5, 1> IF RecordIndex EQ 1 THEN Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Date'); // Timestamp Result<10> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnHazeAvgMean'); // HazeAvg Result<28> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID Result<30> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Session'); // ScanRecipe IsMisfit = IndexC(Result<30>, 'MISFIT', 1) IF IsMisfit THEN Result<3> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMean'); // SoDAvg Result<2> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMax'); // SoDMax Result<1> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMin'); // SoDMin END ELSE Result<39> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMean'); // SoDAvg Result<40> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMax'); // SoDMax Result<41> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMin'); // SoDMin END Result<43> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MesEntity'); // ScanTool Result<44> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAreaMean'); // DCNMM2 END NEXT RecordIndex Response = Result; end service //---------------------------------------------------------------------------------------------------------------------- // ImportMetrologyFiles // // Looks for available Metrology files that are ready to be imported into the MES system. //---------------------------------------------------------------------------------------------------------------------- Service ImportMetrologyFiles(Machine) If Machine NE '' then hSysLists = Database_Services('GetTableHandle', 'SYSLISTS') Lock hSysLists, ServiceKeyID:'*':Machine then ******************************** * Verify Metrology data folder * ******************************** Begin Case Case Machine _EQC 'Tencor' SearchPattern = '*.pdsf'; DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08DDUPSFS6420\Source\MET08DDUPSFS6420\' Case Machine _EQC 'HgCV' SearchPattern = '*.pdsf'; DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESIHGCV\Source\MET08RESIHGCV\' Case Machine _EQC 'CDE' SearchPattern = '*.pdsf'; DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESIMAPCDE\Source\MET08RESIMAPCDE\' Case Machine _EQC 'Biorad' SearchPattern = '*.txt'; DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08THFTIRQS408M\Source\MET08THFTIRQS408M\' Case Machine _EQC 'Stratus' SearchPattern = '*.txt'; DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08THFTIRSTRATUS\Source\MET08THFTIRSTRATUS\' Case Machine _EQC 'SP1' SearchPattern = '*.txt'; DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08DDUPSP1TBI\Source\MET08DDUPSP1TBI\' Case Machine _EQC 'SPV' SearchPattern = '*.txt'; DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESISRP2100\Source\MET08RESISRP2100\' Case Machine _EQC 'SRP' SearchPattern = '*.txt'; DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08ANLYSDIFAAST230\Source\MET08ANLYSDIFAAST230\' Case Otherwise$ SearchPattern = '*.txt'; Error_Services('Add', 'Error in ':Service:' service. Unsupported Machine "':Machine:'" passed into service') End Case If Error_Services('NoError') then InitDir DataPath:SearchPattern FileList = DirList() FileNames = '' LOOP FileName = FileList[1,@FM] FileList[1,Col2()] = "" LOCATE FileName IN FileNames BY 'AR' USING @FM SETTING Pos ELSE FileNames = INSERT(FileNames,Pos,0,0,FileName) END UNTIL FileList = "" REPEAT IF FileNames[-1,1] = @FM THEN FileNames[-1,1] = '' END FileCnt = COUNT(FileNames,@FM) + (FileNames NE '') FOR FileLoopIndex = 1 TO FileCnt *********************** * Read metrology file * *********************** ProcessNow = True$ FileName = FileNames ImportFileList = Database_Services('ReadDataRow', 'APP_INFO', UCase(Machine):'_FILE_LIST') ImportAttemptCounts = Database_Services('ReadDataRow', 'APP_INFO', UCase(Machine):'_ATTEMPT_COUNTS') ImportAttemptDTMs = Database_Services('ReadDataRow', 'APP_INFO', UCase(Machine):'_ATTEMPT_DTMS') CurrDTM = Datetime() Locate FileName in ImportFileList using @FM setting FilePos then LastAttemptDTM = ImportAttemptDTMs MinutesSince = SRP_Datetime('MinuteSpan', LastAttemptDTM, CurrDTM) If MinutesSince GE MINUTES_UNTIL_RETRY$ then ImportAttempts = ImportAttemptCounts ImportAttempts += 1 ImportAttemptCounts = ImportAttempts ImportAttemptDTMs = CurrDTM end else ProcessNow = False$ end end else ImportFileList = FileName ImportAttempts = 1 ImportAttemptCounts = ImportAttempts ImportAttemptDTMs = CurrDTM end If ProcessNow then FileInfo = Dir(DataPath:FileName) FileSize = FileInfo<1> Metrology_Services('LogResults', '', Machine, 'UID000', 'Dir : ' : FileName : ', Size : ' : FileSize) ImportStartTime = Time() Metrology_Services('LogResults', '', Machine, 'UID000', 'Read : ' : FileName : ', Size : ' : FileSize) // Copy Run Data files to repository for troubleshooting purposes Begin Case Case Machine _EQC 'Tencor' RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08DDUPSFS6420\Source\MET08DDUPSFS6420\' Case Machine _EQC 'HgCV' RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08RESIHGCV\Source\MET08RESIHGCV\' Case Machine _EQC 'CDE' RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08RESIMAPCDE\Source\MET08RESIMAPCDE\' Case Machine _EQC 'Biorad' RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08THFTIRQS408M\Source\MET08THFTIRQS408M\' Case Machine _EQC 'Stratus' RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08THFTIRSTRATUS\Source\MET08THFTIRSTRATUS\' Case Machine _EQC 'SP1' RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08DDUPSP1TBI\Source\MET08DDUPSP1TBI\' Case Machine _EQC 'SPV' RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08RESISRP2100\Source\MET08RESISRP2100\' Case Machine _EQC 'SRP' RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08ANLYSDIFAAST230\Source\MET08ANLYSDIFAAST230\' End Case IF SearchPattern = '*.pdsf' THEN OSREAD Text FROM DataPath:FileName THEN json = Metrology_Services('GetJsonFromProcessDataStandardFormat', Text); END ELSE json = ''; END IF LEN(json) GT 0 THEN RunData = Metrology_Services('GetRunData', Machine, json); END ELSE Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to get PDSF json'); END END ELSE OSREAD RunData FROM DataPath:FileName THEN SWAP '|' WITH @VM IN RunData SWAP CRLF$ WITH @FM IN RunData LOOP LastChar = RunData[-1,1] UNTIL LastChar NE @FM RunData[-1,1] = '' REPEAT Convert Tab$ to @FM in RunData END else Metrology_Services('LogResults', '', Machine, 'UID001', 'Read : ' : FileName : ', Size : ' : FileSize) end END ************************* * Import metrology data * ************************* IF RunData NE '' then Metrology_Services('ImportMetrologyRunData', Machine, DataPath, FileName, RunData) END ELSE Error_Services('Add', 'RunData argument was missing') Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage')) END If Error_Services('NoError') then Continue = True$ end else ErrorMessage = Error_Services('GetMessage') FQAError = IndexC(ErrorMessage, 'FQA has already been signed', 1) If ( Index(ErrorMessage, 'UID002', 1) and (ImportAttempts LE RETRY_ATTEMPTS$) and Not(FQAError) ) then Continue = False$ end else Continue = True$ end end ****************************** * Delete metrology data file * ****************************** If (Continue) then Status() = 0 OSDELETE DataPath:FileName ;* Deletes local copy of data file ImportFileList = Delete(ImportFileList, FilePos, 0, 0) ImportAttemptCounts = Delete(ImportAttemptCounts, FilePos, 0, 0) ImportAttemptDTMs = Delete(ImportAttemptDTMs, FilePos, 0, 0) ErrCode = Status() If ErrCode EQ 0 then Metrology_Services('LogResults', '', Machine, 'UID000', 'Delete : ' : FileName : ', Size : ' : FileSize) end else Metrology_Services('LogResults', '', Machine, 'UID001', 'Delete : ' : FileName : ', Size : ' : FileSize : ', Error : ' : ErrCode) end END else ErrorMessage = Error_Services('GetMessage') MetrologyLog = Database_Services('ReadDataRow', 'SYSLISTS', UCase(Machine):'_METROLOGY_LOG') MetrologyLog := ErrorMessage : @FM Database_Services('WriteDataRow', 'SYSLISTS', UCase(Machine):'_METROLOGY_LOG', MetrologyLog, True$) end Database_Services('WriteDataRow', 'APP_INFO', UCase(Machine):'_FILE_LIST', ImportFileList) Database_Services('WriteDataRow', 'APP_INFO', UCase(Machine):'_ATTEMPT_COUNTS', ImportAttemptCounts) Database_Services('WriteDataRow', 'APP_INFO', UCase(Machine):'_ATTEMPT_DTMS', ImportAttemptDTMs) end NEXT FileLoopIndex end else Metrology_Services('LogResults', '', '', 'UID001', Error_Services('GetMessage')) end Unlock hSysLists, ServiceKeyID:'*':Machine else Null end end else Metrology_Services('LogResults', '', '', 'UID001', 'Null Machine passed into service') end end service Service GetRunData(Machine, json) Result = ''; Handle = ''; If Assigned(Handle) else Handle = 0 If Handle LE 0 then ParseError = SRP_JSON(Handle, 'PARSE', json); IF ParseError EQ '' THEN Begin Case Case Machine _EQC 'Stratus' Result = Metrology_Services('GetStratus', Handle); Case Machine _EQC 'Biorad' Result = Metrology_Services('GetBiorad', Handle); Case Machine _EQC 'CDE' Result = Metrology_Services('GetCDE', Handle); Case Machine _EQC 'HgCV' Result = Metrology_Services('GetHgCV', Handle); Case Machine _EQC 'SP1' Result = Metrology_Services('GetSP1', Handle); Case Machine _EQC 'Tencor' Result = Metrology_Services('GetTencor', Handle); End Case SRP_JSON(Handle, 'RELEASE'); END ELSE Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to parse PDSF json'); END END Response = Result; end service //---------------------------------------------------------------------------------------------------------------------- // ImportMetrologyRunData // // RunData. - [Required] // // Imports the metrology run data into the RDS_TEST database table. Returns True$ if successful and False$ if // unsuccessful. //---------------------------------------------------------------------------------------------------------------------- Service ImportMetrologyRunData(Machine, DataPath, FileName, RunData) // Scan the run data for machine specific information. Then call the relevant update service for the specific // machine. ResourceID = Field(FileName, ' ', 1, 1) IsViewerFile = Index(FileName, 'Viewer', 1) Begin Case Case Machine _EQC 'Stratus' PSN = RunData<9> Metrology_Services('ImportStratusData', RunData, ResourceID, PSN) MachineType@ = 'Stratus' Case Machine _EQC 'Biorad' PSN = RunData<7> QualFile = ( (PSN EQ 'T-Low') or (PSN EQ 'T-Mid') or (PSN EQ 'T-High') or (PSN EQ 'T_LOW') or (PSN EQ 'T_MID') or (PSN EQ 'T_HIGH') ) IF QualFile THEN Metrology_Services('ImportBioRadQualData', RunData, ResourceID, PSN) END ELSE Metrology_Services('ImportBioRadData', RunData, ResourceID, IsViewerFile, PSN, FileName) END MachineType@ = 'Bio-Rad' Case Machine _EQC 'CDE' PSN = RunData<8> QualFile = ( (PSN EQ 'RLOW_STD') or (PSN EQ 'RMID_STD') or (PSN EQ 'RHI_STD') or (PSN EQ 'THINSPC') ) IF QualFile THEN Metrology_Services('ImportCDEQualData', RunData, ResourceID, PSN) END ELSE Metrology_Services('ImportCDEData', RunData, ResourceID, IsViewerFile, PSN) END MachineType@ = 'CDE' Case Machine _EQC 'HgCV' PSN = RunData<5> QualFile = ( (PSN EQ 'Low') or (PSN EQ 'Mid') or (PSN EQ 'High') or (PSN EQ 'Thin') ) IF QualFile THEN Metrology_Services('ImportHgCVQualData', RunData, ResourceID, PSN) END ELSE Metrology_Services('ImportHgCVData', RunData, ResourceID, IsViewerFile, PSN) END MachineType@ = 'HgProbe' Case Machine _EQC 'SP1' Metrology_Services('ImportSP1Data', RunData) MachineType@ = 'SP1' Case Machine _EQC 'Tencor' Metrology_Services('ImportTencorData', RunData) MachineType@ = 'TENCOR' Case Machine _EQC 'SRP' Error_Services('Add', 'SRP data import is currently disabled.') Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage')) MachineType@ = 'SRP' Case Machine _EQC 'SPV' Error_Services('Add', 'SPV data import is currently disabled.') Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage')) MachineType@ = 'SPV' Case Otherwise$ Error_Services('Add', 'Unrecognized machine used to create metrology run data') Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage')) MachineType@ = 'Unrecognized' End Case end service Service ImportStratusData(RunData, ResourceID, PSN) Machine = 'Stratus' ParseArray = '' FieldPos = 13 FieldPosIncrement = 2 Offset = 1 Decimals = 2 Timestamp = RunData<2> Tool = RunData<3> DataType = RunData<4> Operator = RunData<5> Recipe = RunData<6> Reactor = RunData<7> RDSNo = RunData<8> ; // If Non-EpiPro this will be an RDS Key, otherwise it will be a work order key. BatchID = RunData<10> Cassette = RunData<11> ThickAvg = RunData<12> Positions = '' DataPoints = '' Loop Position = Trim(RunData) DataPoint = Trim(RunData) Until Position EQ '' Positions := Position : @VM DataPoints := DataPoint : @VM FieldPos += FieldPosIncrement Repeat Positions[-1, 1] = '' ; // Strip final @VM DataPoints[-1, 1] = '' ; // Strip final @VM // Clean the cassette user input. Swap '.' with '*' in Cassette Swap 'o' with '' in Cassette Swap 'O' with '' in Cassette // Determine if the key ID is an RDS or WM_OUT key IsEpiPro = false$ Begin Case // Check for WM_OUT first because a work order key may also be an RDS key. Case RowExists('WM_OUT', Cassette) WorkOrderNo = Field(Cassette, '*', 1) CassNo = Field(Cassette, '*', 3) RDSNo@ = Cassette ; // This is used for logging purposes. IsEpiPro = true$ Case RowExists('RDS', RDSNo) WorkOrderNo = Xlate('RDS', RDSNo, 'WO', 'X') CassNo = Xlate('RDS', RDSNo, 'CASS_NO', 'X') RDSNo@ = RDSNo ; // This is used for logging purposes. Case Otherwise$ Error_Services('Add', 'Unrecognized cassette ID ':Cassette:'.') End Case If Error_Services('NoError') then // Update WO_MAT record StdDev = '' WOMatKey = WorkOrderNo : '*' : CassNo WOMatRec = Database_Services('ReadDataRow', 'WO_MAT', WOMatKey) If Error_Services('NoError') then NumVals = 0 For each Position in Positions using @VM setting vPos If Position NE '' then WOMatRec = DataPoints<0, vPos> NumVals += 1 end Next Position If NumVals EQ 25 then StdDevType = 'POPULATION' end else StdDevType = 'SAMPLE' end If NumVals GT 0 then Vals = WOMatRec Vals = SRP_Array('Clean', Vals, 'Trim', @VM) StdDev = Math_Services('GetStdDev', Vals, StdDevType) StdDev = OConv(IConv(StdDev, 'MD3'), 'MD3') end Database_Services('WriteDataRow', 'WO_MAT', WOMatKey, WOMatRec, True$, False$, True$) If Error_Services('HasError') then Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end // Update the WM_OUT record for EpiPro If IsEpiPro then NumVals = 0 WMORec = Database_Services('ReadDataRow', 'WM_OUT', RDSNo@) For each Wafer in Positions using @VM setting dPos If Wafer NE '' then WMORec = DataPoints<0, dPos> NumVals += 1 end Next Wafer If NumVals EQ 25 then StdDevType = 'POPULATION' end else StdDevType = 'SAMPLE' end If NumVals GT 0 then Vals = WMORec Vals = SRP_Array('Clean', Vals, 'Trim', @VM) StdDev = Math_Services('GetStdDev', Vals, StdDevType) StdDev = OConv(IConv(StdDev, 'MD3'), 'MD3') end Database_Services('WriteDataRow', 'WM_OUT', RDSNo@, WMORec, True$, False$, True$) end // Update WO_MAT_QA record StdDevMax = '' WOMatQAID = WorkOrderNo : '*' : CassNo WOMatQARec = Database_Services('ReadDataRow', 'WO_MAT_QA', WOMatQAID) If Error_Services('NoError') then SpecRecipes = WOMatQARec ProfSteps = '' Profiles = WOMatQARec ProfileCnt = DCount(WOMatQARec, @VM) Stages = WOMatQARec Slots = WOMatQARec SpecQty = WOMatQARec pPos = '' WOMatSlotProfile = obj_WO_Mat('SlotWaferIDs',WOMatKey:@RM:WOMatRec) For each Profile in Profiles using @VM setting pPos Stage = Stages<0, pPos> If ( (Profile EQ '1ADE') and ( (Stage EQ 'QA') or (Stage EQ 'MO_QA') ) ) then Slot = Slots<0, pPos> If ( (Slot EQ 'A') or (SpecQty EQ 'A') or (SpecQty GT 5) ) then MetMin = WOMatQARec MetMax = WOMatQARec StdDevMax = ((MetMin + MetMax) / 2) * (0.02) StdDevMax = IConv(StdDevMax,'MD3') WOMatQARec = StdDev WOMatQARec = StdDevMax end Begin Case Case Slot EQ '1' For WaferIndex = 1 to 25 SlotIsEmpty = WOMatSlotProfile<1, WaferIndex> EQ '' Until SlotIsEmpty EQ False$ Next WaferIndex Case Slot EQ 'L' For WaferIndex = 25 to 1 Step -1 SlotIsEmpty = WOMatSlotProfile<1, WaferIndex> EQ '' Until SlotIsEmpty EQ False$ Next WaferIndex Case Slot EQ 'A' WaferIndex = 1 Case Otherwise$ WaferIndex = Slot End Case Locate WaferIndex in Positions using @VM setting dPos then WOMatQARec = DataPoints<0, dPos> end end Next Profile Database_Services('WriteDataRow', 'WO_MAT_QA', WOMatQAID, WOMatQARec, True$, False$, True$) If Error_Services('NoError') then Metrology_Services('LogResults', RDSNo@, Machine, 'UID000', Service : ' : Success.') end else Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end service Service ImportBioRadQualData(RunData, ResourceID, PSN) Machine = 'BioRad' URL = "https://oi-metrology-viewer-prod.mes.infineon.com/api/InfinityQSV3/":ResourceID:"/header" TimeoutDuration = HTTPClient_Services('GetTimeoutDuration') If TimeoutDuration NE 30 then Httpclient_Services('SetTimeoutDuration', 30) Response = Httpclient_Services('SendHTTPRequest', 'GET', URL, '', '', '', '', '', '', '') If Response NE '' then objJSON = '' If SRP_JSON(objJSON, 'Parse', Response) EQ '' then SumOOS = SRP_JSON(objJSON, 'GetValue', 'Results[1].iq_sum') ToolID = RunData<5> TimeStamp = RunData<2> If SumOOS NE '' then Swap 'T_LOW' with 'T-Low' in PSN Swap 'T_MID' with 'T-Mid' in PSN Swap 'T_HIGH' with 'T-High' in PSN Pass = (SumOOS EQ 0) QualResponse = PM_Services('ProcessQual', PSN, ToolID, TimeStamp, Pass) StatusCode = QualResponse<1> Message = QualResponse<2> Metrology_Services('LogResults', PSN, Machine, StatusCode, Service : ' : ' : Message) end else LogMessage = 'IQS response missing Results[1].iq_sum. Error message: ':SRP_JSON(objJSON, 'GetValue', 'message') Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : LogMessage) end SRP_JSON(objJSON, 'Release') end else Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to parse IQS JSON response') end end else Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Null response from IQS API.') end end service Service ImportBioRadData(RunData, ResourceID, IsViewerFile, PSN, FileName) Machine = 'BioRad' IsProdTest = False$ ParseArray = '' FieldPos = 13 FieldPosIncrement = 2 Offset = 1 Decimals = 2 // RDS Biorad metrology file Timestamp = RunData<2> RDSKeyID = RunData<6> RunDataLayer = RunData<8> RunDataZone = RunData<9> ReactorID = RunData<5> ScanRecipe = RunData<4> ToolClassID = 'FTIR' PatternNameIndex = RDS_TEST_SPEC_THICK_MPATTERN$ ToolClassIndex = RDS_TEST_SPEC_THICK_MTOOL$ DataIndex = RDS_TEST_READ_THICK$ DTMIndex = RDS_TEST_TEST_RUN_THICKNESS_DTM$ // Clean the cassette user input. Cassette = RDSKeyID Swap '.' with '*' in Cassette Swap 'o' with '' in Cassette Swap 'O' with '' in Cassette if RowExists('WM_OUT', Cassette) then //Redirect to EPP FQA Import Metrology_Services('ImportBioRadEPPFQAData', RunData, FileName) end else RDSNo@ = RDSKeyID RDSRec = Database_Services('ReadDataRow', 'RDS', RDSKeyID) If Error_Services('NoError') then WorkOrderNo = RDSRec // HgCV Project Development Code ------------------------------------------------------------------------------- DevelopmentFlag = Xlate('DEVELOPMENT', 'HGCV', 'STATUS', 'X') If (DevelopmentFlag EQ True$) then IsProdTest = ( Indexc(ScanRecipe, 'PROD', 1) GT 0 ) If IsProdTest EQ True$ then Metrology_Services('LogResults',RDSKeyID,Machine,'HgCV','Product test recognized. Recipe name: ':ScanRecipe) end // ------------------------------------------------------------------------------------------------------------- CassNo = RDSRec WorkOrder = Work_Order_Services('GetWorkOrder', WorkOrderNo, False$) GoSub ParseWorkOrder IsEpiPro = (WOReactorType _EQC 'EpiPro') OR (WOReactorType _EQC 'EPP') RunDataZone = Metrology_Services('FormatZoneKeyID', RunDataZone) ; // 1, 2 RunDataLayer = Metrology_Services('FormatLayerKeyID', RunDataLayer) ; // L1, L2, 2 ZoneForValidation = RunDataZone LayerForValidation = RunDataLayer If LayerForValidation EQ '' then LayerForValidation = 'L1' end CalculatedZone = Metrology_Services('GetCalculatedZone', RDSKeyID, IsEpiPro, RunDataZone) If RunDataZone NE CalculatedZone then Error_Services('Add', 'Entered Zone [' : RunDataZone : '] and calculated Zone [' : CalculatedZone : '] do not match.') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end Transfer CalculatedZone to RunDataZone CalculatedLayer = Metrology_Services('GetCalculatedLayer', RDSKeyID, Machine, RunDataZone, IsEpiPro, RunDataLayer) If (RunDataLayer NE CalculatedLayer) then Error_Services('Add', 'Entered Layer [' : RunDataLayer : '] and calculated Layer [' : CalculatedLayer : '] do not match for the Zone [' : RunDataZone : '].') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end Transfer CalculatedLayer to RunDataLayer ReactorRec = Database_Services('ReadDataRow', 'REACTOR', ReactorID) If Error_Services('NoError') then ReactorType = ReactorRec If ReactorType _EQC 'EpiPro' OR ReactorType _EQC 'EPP' OR ReactorType _EQC 'P' then TestPointMapping = True$ If RunDataLayer EQ '' OR RunDataZone EQ '' then Message = 'Layer and Zone are required for EpiPro.' Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Message) Error_Services('Add', Message) end end else TestPointMapping = False$ If RunDataLayer NE '' then RunDataZone = '' end else Message = 'Layer is required for non-EpiPro.' Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Message) Error_Services('Add', Message) end end If Error_Services('NoError') then Positions = '' DataPoints = '' Loop Position = Trim(RunData) DataPoint = Trim(RunData) Until Position EQ '' Positions := Position : @VM DataPoints := DataPoint : @VM FieldPos += FieldPosIncrement Repeat Positions[-1, 1] = '' ; // Strip final @VM DataPoints[-1, 1] = '' ; // Strip final @VM RawDataPoints = DataPoints DataPoints = Iconv(DataPoints, 'MD' : Decimals) Swap ' AM' with 'AM' in Timestamp Swap ' PM' with 'PM' in Timestamp Timestamp = IConv(Timestamp,'DT') RDSLayerKeyID = RDSKeyID : '*' : RunDataLayer NumDataPoints = DCount(DataPoints, @VM) RDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', RDSLayerKeyID) If Error_Services('NoError') then RDSTestKeyIDs = RDSLayerRec Found = False$ If RDSTestKeyIDs NE '' then For Each RDSTestKeyID in RDSTestKeyIDs using @VM setting mkPos RDSTestRec = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID) If Error_Services('NoError') then RDSTestZoneID = RDSTestRec // First test - Look for a Zone ID match. If (RDSTestZoneID EQ RunDataZone) then // A match has been found. No need to continue searching. Found = True$ end end Until Found Next RDSTestKeyID end else Error_Services('Add', 'Unable to obtain a valid Metrology Test Key ID.') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end // If not found this is because there was no zone. However, if this is not EpiPro, this is still a valid // test so set the found flag to true. If Not(Found) AND Not(IsEpiPro) then Found = True$ If (Found EQ True$) then // Check if QA Metrology run or conventional RDS Metrology run. // Recipe name match requirement has been turned off per Tom Tillery. djs 10/03/18 Begin Case Case IsProdTest // WO_MAT_QA THICK_ONLY test -> import and send to SPC LogPath = Environment_Services('GetSpcFilesharePath') LogPath2 = Environment_Services('GetApplicationRootPath') : '\Metrology' SendToSPC = False$ Begin Case Case NumDataPoints EQ 5 // Create 5 point SPC file objSPC = Logging_Services('NewLog', LogPath, OConv(Datetime(), 'DT_^1_HS_'):'_Thickness_5Points.txt', CRLF$, Comma$, '', '', False$, True$) NotesLog = Logging_Services('NewLog', LogPath2, 'Thickness_5Points.txt', CRLF$, Comma$, '', '', False$, False$) SendToSPC = True$ Case NumDataPoints EQ 9 // Create 9 point SPC file objSPC = Logging_Services('NewLog', LogPath, OConv(Datetime(), 'DT_^1_HS_'):'_Thickness_9Points.txt', CRLF$, Comma$, '', '', False$, True$) NotesLog = Logging_Services('NewLog', LogPath2, 'Thickness_9Points.txt', CRLF$, Comma$, '', '', False$, False$) SendToSPC = True$ Case NumDataPoints EQ 10 objSPC = Logging_Services('NewLog', LogPath, OConv(Datetime(), 'DT_^1_HS_'):'_Thickness_10Points.txt', CRLF$, Comma$, '', '', False$, True$) NotesLog = Logging_Services('NewLog', LogPath2, 'Thickness_10Points.txt', CRLF$, Comma$, '', '', False$, False$) SendToSPC = True$ Case NumDataPoints EQ 14 objSPC = Logging_Services('NewLog', LogPath, OConv(Datetime(), 'DT_^1_HS_'):'_Thickness_14Points.txt', CRLF$, Comma$, '', '', False$, True$) NotesLog = Logging_Services('NewLog', LogPath2, 'Thickness_14Points.txt', CRLF$, Comma$, '', '', False$, False$) SendToSPC = True$ Case Otherwise$ // Log this as it is unexpected NotesLog = Logging_Services('NewLog', LogPath2, 'Thickness_Points_Error.txt', CRLF$, Comma$, '', '', False$, False$) End Case LogData = '' LogData<1> = Oconv(Timestamp, 'DT/^HS') ; // Metrology date/time stamp LogData<2> = ReactorID ; // Reactor LogData<3> = RDSKeyID ; // RDS No LogData<4> = RunData<7> ; // PSN // Log data positions 5 through 10, 14, or 18 will be populated with data point measurements. Convert @VM to @FM in RawDataPoints Min = RawDataPoints<1> Max = RawDataPoints<1> For each DataPoint in RawDataPoints using @FM Setting DataPos Min = Min(Min, DataPoint) Max = Max(Max, DataPoint) LogData = DataPoint Next DataPoint LogData := @RM : ' ' If SendToSPC then Logging_Services('AppendLog', objSPC, LogData, @RM, @FM, True$) Logging_Services('AppendLog', NotesLog, LogData, @RM, @FM, True$) Average = Sum(RawDataPoints) / NumDataPoints Average = Iconv(Average, 'MD3') Average = Oconv(Average, 'MD3') WOMatKey = Xlate('RDS', RDSKeyID, 'WO_MAT_KEY', 'X') WOMatQAID = WorkOrderNo : '*' : CassNo WOMatQARec = Database_Services('ReadDataRow', 'WO_MAT_QA', WOMatQAID) SpecRecipes = WOMatQARec WOStepNo = RDSRec[-1, 'B*'] ProfSteps = '' ProfileCnt = DCount(WOMatQARec, @VM) For vPos = 1 to ProfileCnt ProfSteps<1, vPos> = WOMatQARec : '*' : WOMatQARec Next vPos LastProfSig = WOMatQARec SigProfKeys = 'THICK_ONLY' SigCnt = DCount(SigProfKeys, @VM) Stages = 'UNLOAD' SigProfFound = False$ For Each SigProfKey in SigProfKeys using @VM setting vPos Stage = Stages<1, vPos> If Num(SigProfKey[1, 1]) else SigProfKey = WOStepNo : SigProfKey end ProfStep = SigProfKey : '*' : Stage Locate ProfStep in ProfSteps using @VM setting Pos then SigProfFound = True$ UnloadSigned = Signature_Services('GetStageSummary', WoMatKey, 'UNLOAD')<2> IF (Stage EQ 'UNLOAD' AND UnloadSigned EQ True$) OR Stage NE 'UNLOAD' then // verify recipe is correct WoMatQaRecipe = WOMatQARec WoMatQaRecipeMatchesScanRecipe = ScanRecipe _EQC WoMatQaRecipe If WoMatQaRecipeMatchesScanRecipe EQ False$ then ErrMsg = 'Scan recipe [ ' : ScanRecipe : ' ] does not match WoMatQa recipe [ ' : WoMatQaRecipe : ' ] for RDS [ ' : RDSKeyID : ' ] layer [ ' : CalculatedLayer : ' ].' Error_Services('Add', ErrMsg) Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end // verify number of points is correct WoMatQaToolClass = WOMatQARec WoMatQaRecipePattern = WOMatQARec NumDataPointsInSpec = Tool_Class_Services('GetNumberOfPointsForPattern', WoMatQaToolClass, WoMatQaRecipePattern) WoMatQaRecipePatternNumPointsMatchesScanNumDataPoints = NumDataPointsInSpec EQ NumDataPoints If WoMatQaRecipePatternNumPointsMatchesScanNumDataPoints EQ False$ then ErrMsg = 'Scan data point count [ ' : NumDataPoints : ' ] does not match WoMatQa recipe pattern data point count [ ' : NumDataPointsInSpec : ' ] for RDS [ ' : RDSKeyID : ' ] layer [ ' : CalculatedLayer : ' ].' Error_Services('Add', ErrMsg) Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end If (WOMatQARec EQ '' OR IsViewerFile) then If WoMatQaRecipeMatchesScanRecipe and WoMatQaRecipePatternNumPointsMatchesScanNumDataPoints then WOMatQARec = Average WOMatQARec = Oconv(Iconv(Min, 'MD3'), 'MD3') WOMatQARec = Oconv(Iconv(Max, 'MD3'), 'MD3') SpecMin = WOMatQARec SpecMax = WOMatQARec For each DataPoint in RawDataPoints using @FM setting SubValuePos FormatedData = Oconv(Iconv(DataPoint, 'MD3'), 'MD3') If NumDataPoints LT 14 then If ( ( FormatedData LT SpecMin ) OR ( FormatedData GT SpecMax ) and (SubValuePos LT 10) ) then WOMatQARec = True$ end end else If ( FormatedData LT SpecMin ) OR ( FormatedData GT SpecMax ) then WOMatQARec = True$ end end WOMatQARec = FormatedData Next DataPoint WOMatQARec = '' WOMatQARec = '' Database_Services('WriteDataRow', 'WO_MAT_QA', WOMatQAID, WOMatQARec, True$, False$, True$) end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - Data for this metrology test already exists and is not from the metrology viewer.') end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - Unload signature must first be signed for this data to be uploaded.') end end Until SigProfFound Next SigProfKey If SigProfFound EQ True$ then Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Sig Prof. Found') end else Error_Services('Add', 'Unable to locate the signature profile [' : ProfStep : '].') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end Case Otherwise$ // If not an above case, then the run is a conventional THICK_ONLY RDS metrology test. RDSLayerKeyID = RDSKeyID : '*' : LayerForValidation RDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', RDSLayerKeyID) If Error_Services('NoError') then RDSTestKeyIDs = RDSLayerRec // Verify the scan recipe matches the spec recipe RecipeMatches = Metrology_Services('ScanRecipeMatchesRdsTestSpecThickMrecipe', RDSKeyID, LayerForValidation, ScanRecipe, RDSTestKeyIDs, ZoneForValidation) If RecipeMatches EQ False$ then ErrMsg = 'Scan recipe [ ' : ScanRecipe : ' ] does not match RDS Test recipe for RDS [ ' : RDSKeyID : ' ] layer [ ' : CalculatedLayer : ' ] zone [ ' : CalculatedZone : ' ].' Error_Services('Add', ErrMsg) Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end // Verfiy the scan number of points matches the spec number of points NumPointsMatches = Metrology_Services('ScanNumDataPointsMatchesRdsTestSpecThickMPattern', RDSKeyID, LayerForValidation, NumDataPoints, RDSTestKeyIDs, 'FTIR', ZoneForValidation) If NumPointsMatches EQ False$ then ErrMsg = 'Scan data point count [ ' : NumDataPoints : ' ] does not match RDS Test recipe pattern data point count for RDS [ ' : RDSKeyID : ' ] layer [ ' : CalculatedLayer : ' ] zone [ ' : CalculatedZone : ' ].' Error_Services('Add', ErrMsg) Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end If RecipeMatches and NumPointsMatches then GoSub LoadRunDataToDatabase end end else ErrMsg = 'RDS Test scan failed for RDS [' : RDSKeyID : '] layer [' : LayerForValidation : '], because unable to get RDS_LAYER record [' : RDSLayerKeyID : ']' Error_Services('Add', ErrMsg) Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end End Case end else Error_Services('Add', 'Data type / Spec not found for Layer [' : RunDataLayer : '].') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end end service Service ImportCDEQualData(RunData, ResourceID, PSN) Machine = 'CDE' ResourceID = Field(FileName, ' ', 1, 1) URL = "https://oi-metrology-viewer-prod.mes.infineon.com/api/InfinityQSV3/":ResourceID:"/header" TimeoutDuration = HTTPClient_Services('GetTimeoutDuration') If TimeoutDuration NE 30 then Httpclient_Services('SetTimeoutDuration', 30) Response = Httpclient_Services('SendHTTPRequest', 'GET', URL, '', '', '', '', '', '', '') If Response NE '' then If SRP_JSON(objJSON, 'Parse', Response) EQ '' then SumOOS = SRP_JSON(objJSON, 'GetValue', 'Results[1].iq_sum') ToolID = RunData<6> TimeStamp = RunData<3> If SumOOS NE '' then Pass = (SumOOS EQ 0) QualResponse = PM_Services('ProcessQual', PSN, ToolID, TimeStamp, Pass) StatusCode = QualResponse<1> Message = QualResponse<2> Metrology_Services('LogResults', PSN, Machine, StatusCode, Service : ' : ' : Message) end else LogMessage = 'IQS response missing Results[1].iq_sum. Error message: ':SRP_JSON(objJSON, 'GetValue', 'message') Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : LogMessage) end SRP_JSON(objJSON, 'Release') end else Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to parse IQS JSON response') end end else Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Null response from IQS API.') end end service Service ImportCDEData(RunData, ResourceID, IsViewerFile, PSN) Machine = 'CDE' ParseArray = '' FieldPos = 23 FieldPosIncrement = 5 Offset = 3 Decimals = 3 Timestamp = RunData<3> RDSKeyID = RunData<7> RunDataLayer = RunData<9> RunDataZone = RunData<10> ReactorID = RunData<6> ScanRecipe = RunData<5> // Regular metrology file ToolClassID = '4PP' PatternNameIndex = RDS_TEST_SPEC_RES_MPATTERN$ ToolClassIndex = RDS_TEST_SPEC_RES_MTOOL$ DataIndex = RDS_TEST_READ_SHEET_RHO$ DTMIndex = RDS_TEST_TEST_RUN_SHEET_RHO_DTM$ IsProdTest = False$ RDSNo@ = RDSKeyID RDSRec = Database_Services('ReadDataRow', 'RDS', RDSKeyID) If Error_Services('NoError') then // HgCV Project Development Code ------------------------------------------------------------------------------- DevelopmentFlag = Xlate('DEVELOPMENT', 'HGCV', 'STATUS', 'X') If (DevelopmentFlag EQ True$) then IsProdTest = ( Indexc(ScanRecipe, 'PROD', 1) GT 0 ) If IsProdTest EQ True$ then Metrology_Services('LogResults',RDSKeyID,Machine,'HgCV','Product test recognized. Recipe name: ':ScanRecipe) end // ------------------------------------------------------------------------------------------------------------- WorkOrderNo = RDSRec CassNo = RDSRec WorkOrder = Work_Order_Services('GetWorkOrder', WorkOrderNo, False$) GoSub ParseWorkOrder IsEpiPro = (WOReactorType _EQC 'EpiPro') OR (WOReactorType _EQC 'EPP') RunDataZone = Metrology_Services('FormatZoneKeyID', RunDataZone) ; // 1, 2 RunDataLayer = Metrology_Services('FormatLayerKeyID', RunDataLayer) ; // L1, L2, 2 CalculatedZone = Metrology_Services('GetCalculatedZone', RDSKeyID, IsEpiPro, RunDataZone) If RunDataZone NE CalculatedZone then Error_Services('Add', 'Entered Zone [' : RunDataZone : '] and calculated Zone [' : CalculatedZone : '] do not match.') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end Transfer CalculatedZone to RunDataZone CalculatedLayer = Metrology_Services('GetCalculatedLayer', RDSKeyID, Machine, RunDataZone) If RunDataLayer NE CalculatedLayer then Error_Services('Add', 'Entered Layer [' : RunDataLayer : '] and calculated Layer [' : CalculatedLayer : '] do not match for the Zone [' : RunDataZone : '].') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end Transfer CalculatedLayer to RunDataLayer ReactorRec = Database_Services('ReadDataRow', 'REACTOR', ReactorID) If Error_Services('NoError') then ReactorType = ReactorRec If ReactorType _EQC 'EpiPro' OR ReactorType _EQC 'EPP' OR ReactorType _EQC 'P' then TestPointMapping = True$ If RunDataLayer EQ '' OR RunDataZone EQ '' then Message = 'Layer and Zone are required for EpiPro.' Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Message) Error_Services('Add', Message) end end else TestPointMapping = False$ If RunDataLayer NE '' then RunDataZone = '' end else Message = 'Layer is required for non-EpiPro.' Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Message) Error_Services('Add', Message) end end If Error_Services('NoError') then Positions = '' DataPoints = '' Loop Position = Trim(RunData) DataPoint = Trim(RunData) Until Position EQ '' Positions := Position : @VM DataPoints := DataPoint : @VM FieldPos += FieldPosIncrement Repeat RawDataPoints = DataPoints Positions[-1, 1] = '' ; // Strip final @VM DataPoints[-1, 1] = '' ; // Strip final @VM DataPoints = Iconv(DataPoints, 'MD' : Decimals) Swap ' AM' with 'AM' in Timestamp Swap ' PM' with 'PM' in Timestamp Timestamp = IConv(Timestamp,'DT') RDSLayerKeyID = RDSKeyID : '*' : RunDataLayer NumDataPoints = DCount(DataPoints, @VM) RDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', RDSLayerKeyID) If Error_Services('NoError') then RDSTestKeyIDs = RDSLayerRec Found = False$ If RDSTestKeyIDs NE '' then For Each RDSTestKeyID in RDSTestKeyIDs using @VM setting mkPos RDSTestRec = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID) If Error_Services('NoError') then RDSTestZoneID = RDSTestRec // First test - Look for a Zone ID match. If (RDSTestZoneID EQ RunDataZone) then // A match has been found. No need to continue searching. Found = True$ end end Until Found Next RDSTestKeyID end else Error_Services('Add', 'Unable to obtain a valid Metrology Test Key ID.') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end // If not found this is because there was no zone. However, if this is not EpiPro, this is still a valid // test so set the found flag to true. If Not(Found) AND Not(IsEpiPro) then Found = True$ If (Found EQ True$) then If (IsProdTest EQ True$) then // QA Metrology test (i.e. not RDS Metrology test) Convert @VM to @FM in RawDataPoints Min = RawDataPoints<1> Max = RawDataPoints<1> For each DataPoint in RawDataPoints using @FM Setting DataPos Min = Min(Min, DataPoint) Max = Max(Max, DataPoint) Next DataPoint Average = Sum(RawDataPoints) / NumDataPoints Average = Iconv(Average, 'MD3') Average = Oconv(Average, 'MD3') WOMatQAID = WorkOrderNo : '*' : CassNo WOMatQARec = Database_Services('ReadDataRow', 'WO_MAT_QA', WOMatQAID) SpecRecipes = WOMatQARec WOStepNo = RDSRec[-1, 'B*'] ProfStep = '' ProfSteps = '' ProfileCnt = DCount(WOMatQARec, @VM) For vPos = 1 to ProfileCnt ProfSteps<1, vPos> = WOMatQARec : '*' : WOMatQARec Next vPos LastProfSig = WOMatQARec SigProfKeys = '1LW_RHO' SigCnt = DCount(SigProfKeys, @VM) Stages = 'UNLOAD' SigProfFound = False$ UnloadSigned = Signature_Services('GetStageSummary', WOMatQAID, 'UNLOAD')<2> For Each SigProfKey in SigProfKeys using @VM setting vPos Stage = Stages<1, vPos> if (Stage EQ 'UNLOAD' AND UnloadSigned EQ True$) OR Stage NE 'UNLOAD' then If Num(SigProfKey[1, 1]) else SigProfKey = WOStepNo : SigProfKey end ProfStep = SigProfKey : '*' : Stage Locate ProfStep in ProfSteps using @VM setting Pos then SigProfFound = True$ IF IsViewerFile OR WOMatQARec = '' then WOMatQARec = Average WOMatQARec = Oconv(Iconv(Min, 'MD3'), 'MD3') WOMatQARec = Oconv(Iconv(Max, 'MD3'), 'MD3') SpecMin = WOMatQARec SpecMax = WOMatQARec For each DataPoint in RawDataPoints using @FM setting SubValuePos FormatedData = Oconv(Iconv(DataPoint, 'MD3'), 'MD3') If ( ( FormatedData LT SpecMin ) OR ( FormatedData GT SpecMax ) and (SubValuePos LT 10) ) then WOMatQARec = True$ end WOMatQARec = FormatedData Next DataPoint Database_Services('WriteDataRow', 'WO_MAT_QA', WOMatQAID, WOMatQARec, True$, False$, True$) end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - Data for this metrology test already exists and is not from the metrology viewer.') end end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - Unload signature must first be signed for this data to be uploaded.') end Until SigProfFound Next SigProfKey If SigProfFound EQ True$ then Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Success.') end else Error_Services('Add', 'Unable to locate the signature profile [' : ProfStep : '].') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else GoSub LoadRunDataToDatabase end end else Error_Services('Add', 'Data type / Spec not found for Layer [' : RunDataLayer : '].') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end service Service ImportHgCVQualData(RunData, ResourceID, PSN) Machine = 'HgCV' /* Resistivity */ URL = "https://oi-metrology-viewer-prod.mes.infineon.com/api/InfinityQSV3/":ResourceID:"/header" TimeoutDuration = HTTPClient_Services('GetTimeoutDuration') If TimeoutDuration NE 30 then Httpclient_Services('SetTimeoutDuration', 30) Response = Httpclient_Services('SendHTTPRequest', 'GET', URL, '', '', '', '', '', '', '') If Response NE '' then If SRP_JSON(objJSON, 'Parse', Response) EQ '' then SumOOS = SRP_JSON(objJSON, 'GetValue', 'Results[1].iq_sum') ToolID = RunData<2> TimeStamp = RunData<11> If SumOOS NE '' then Pass = (SumOOS EQ 0) QualResponse = PM_Services('ProcessQual', PSN, ToolID, TimeStamp, Pass) StatusCode = QualResponse<1> Message = QualResponse<2> Metrology_Services('LogResults', PSN, Machine, StatusCode, Service : ' : ' : Message) end else LogMessage = 'IQS response missing Results[1].iq_sum. Error message: ':SRP_JSON(objJSON, 'GetValue', 'message') Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : LogMessage) end SRP_JSON(objJSON, 'Release') end else Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to parse IQS JSON response') end end else Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Null response from IQS API.') end end service Service ImportHgCVData(RunData, ResourceID, IsViewerFile, PSN) Machine = 'HgCV' /* Resistivity */ FieldPos = 53 FieldPosIncrement = 9 Offset = 5 PhaseOffset = 7 Decimals = 3 Timestamp = RunData<11> RDSKeyID = RunData<4> LayerZonePair = RunData<8> ReactorID = RunData<3> // Regular metrology file ToolClassID = 'HGCV' PatternNameIndex = RDS_TEST_SPEC_RES_MPATTERN$ ToolClassIndex = RDS_TEST_SPEC_RES_MTOOL$ DataIndex = RDS_TEST_READ_HGCV1_RES$ DTMIndex = RDS_TEST_TEST_RUN_HGCV_DTM$ Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : ' : 'Beginning ImportHgCVData') RDSNo@ = RDSKeyID RDSRec = Database_Services('ReadDataRow', 'RDS', RDSKeyID) If Error_Services('NoError') then WorkOrderNo = RDSRec CassNo = RDSRec WorkOrder = Work_Order_Services('GetWorkOrder', WorkOrderNo, False$) GoSub ParseWorkOrder IsEpiPro = (WOReactorType _EQC 'EpiPro') OR (WOReactorType _EQC 'EPP') Convert @Lower_Case to @Upper_Case in LayerZonePair Convert 'Z' to '-' in LayerZonePair Swap '--' with '-' in LayerZonePair RunDataLayer = Field(LayerZonePair, '-', 1) RunDataZone = Field(LayerZonePair, '-', 2) RunDataZone = Metrology_Services('FormatZoneKeyID', RunDataZone) ; // 1, 2 RunDataLayer = Metrology_Services('FormatLayerKeyID', RunDataLayer) ; // L1, L2, 2 CalculatedZone = Metrology_Services('GetCalculatedZone', RDSKeyID, IsEpiPro, RunDataZone) If RunDataZone NE CalculatedZone then Error_Services('Add', 'Entered Zone [' : RunDataZone : '] and calculated Zone [' : CalculatedZone : '] do not match.') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end Transfer CalculatedZone to RunDataZone CalculatedLayer = Metrology_Services('GetCalculatedLayer', RDSKeyID, Machine, RunDataZone) If RunDataLayer NE CalculatedLayer then Error_Services('Add', 'Entered Layer [' : RunDataLayer : '] and calculated Layer [' : CalculatedLayer : '] do not match for the Zone [' : RunDataZone : '].') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end Transfer CalculatedLayer to RunDataLayer ReactorRec = Database_Services('ReadDataRow', 'REACTOR', ReactorID) If Error_Services('NoError') then ReactorType = ReactorRec If ReactorType _EQC 'EpiPro' OR ReactorType _EQC 'EPP' OR ReactorType _EQC 'P' then TestPointMapping = True$ If RunDataLayer EQ '' OR RunDataZone EQ '' then Message = 'Layer and Zone are required for EpiPro.' Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Message) Error_Services('Add', Message) end end else TestPointMapping = False$ If RunDataLayer NE '' then RunDataZone = '' end else Message = 'Layer is required for non-EpiPro.' Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Message) Error_Services('Add', Message) end end If Error_Services('NoError') then Position = '' Positions = '' DataPoints = '' HgCVDataPoints = '' Loop Position = Trim(RunData) HgCVDataPoint = Trim(RunData) Until Position EQ '' Positions := Position : @VM HgCVDataPoints := HgCVDataPoint : @VM FieldPos += FieldPosIncrement Repeat Positions[-1, 1] = '' ; // Strip final @VM HgCVDataPoints[-1, 1] = '' ; // Strip final @VM HgCVDataPoints = Iconv(HgCVDataPoints, 'MD' : Decimals) Swap ' AM' with 'AM' in Timestamp Swap ' PM' with 'PM' in Timestamp Timestamp = IConv(Timestamp,'DT') RDSLayerKeyID = RDSKeyID : '*' : RunDataLayer NumDataPoints = DCount(HgCVDataPoints, @VM) RDSLayerRec = Database_Services('ReadDataRow', 'RDS_LAYER', RDSLayerKeyID) If Error_Services('NoError') then RDSTestKeyIDs = RDSLayerRec Found = False$ If RDSTestKeyIDs NE '' then For Each RDSTestKeyID in RDSTestKeyIDs using @VM setting mkPos RDSTestRec = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID) If Error_Services('NoError') then RDSTestZoneID = RDSTestRec // Determine if this is Hg Concentration Resistivity. IsHgCvCRes = RDSTestRec _EQC 'HGCV' // First test - Look for a Zone ID match. If (RDSTestZoneID EQ RunDataZone) then // A match has been found. No need to continue searching. Found = True$ end end Until Found Next RDSTestKeyID If (Found EQ True$) then // Only continue checking if there are data points. Otherwise, there is no data by which // the code can calculate values to store. If NumDataPoints GT 0 then // Second test - If HgCv Res, dig deeper to see if this is a possible CRES entry.. If IsHgCvCRes then WOMatQAID = WorkOrderNo : '*' : CassNo WOMatQARec = Database_Services('ReadDataRow', 'WO_MAT_QA', WOMatQAID) If Error_Services('NoError') then Locate 'CRES' in WOMatQARec using @VM setting vPos then Stage = WOMatQARec RequireUnload = False$ IF (WOMatQARec EQ '' OR IsViewerFile) then PhaseDataPoints = '' Positions = '' Position = '' FieldPos = 53 Loop Position = Trim(RunData) PhaseDataPoint = Trim(RunData) Until Position EQ '' Positions := Position : @VM PhaseDataPoints := PhaseDataPoint : @VM FieldPos += FieldPosIncrement Repeat Positions[-1, 1] = '' ; // Strip final @VM PhaseDataPoints[-1, 1] = '' ; // Strip final @VM PhaseDataPoints = Iconv(PhaseDataPoints, 'MD' : Decimals) DataPoints = HgCVDataPoints * GoSub CalculateQAData Response = QA_Services('CalculateHgCVData', Datapoints) HgCVMin = Response<0, 2> HgCVMax = Response<0, 3> HgCVAvg = Response<0, 1> HgCVEdgeMean = Response<0, 4> HgCVRangePct = Response<0, 5> EdgeMean4mm = Response<0, 6> Avg9Point = Response<0, 7> EdgeMean10mm = Response<0, 8> ResStdDev = Response<0, 9> DataPoints = PhaseDataPoints Response = QA_Services('CalculateHgCVData', Datapoints) * GoSub CalculateQAData PhaseMin = Response<0, 2> PhaseMax = Response<0, 3> PhaseAvg = Response<0, 1> PhaseEdgeMean = Response<0, 4> PhaseRangePct = Response<0, 5> // Format data. Round to significant digits. * HgCVAvg = Oconv(Iconv(Oconv(HgCVAvg, 'MD3L'), 'MD3L'), 'MD3L') * PhaseAvg = Oconv(Iconv(Oconv(PhaseAvg, 'MD3L'), 'MD3L'), 'MD3L') * HgCVMin = Oconv(Iconv(Oconv(HgCVMin, 'MD3L'), 'MD3L'), 'MD3L') * HgCVMax = Oconv(Iconv(Oconv(HgCVMax, 'MD3L'), 'MD3L'), 'MD3L') * PhaseMin = Oconv(Iconv(Oconv(PhaseMin, 'MD3L'), 'MD3L'), 'MD3L') * PhaseMax = Oconv(Iconv(Oconv(PhaseMax, 'MD3L'), 'MD3L'), 'MD3L') * HgCVEdgeMean = OConv(IConv(HgCVEdgeMean, 'MD3L'), 'MD3L') * PhaseEdgeMean = Oconv(IConv(PhaseEdgeMean, 'MD3L'), 'MD3L') * HgCVRangePct = Oconv(Iconv(HgCVRangePct, 'MD3L'), 'MD3L') * PhaseRangePct = Oconv(Iconv(PhaseRangePct, 'MD3L'), 'MD3L') WOMatQARec = HgCVAvg : @SVM : PhaseAvg WOMatQARec = HgCVMin : @SVM : PhaseMin WOMatQARec = HgCVMax : @SVM : PhaseMax WOMatQARec = HgCVRangePct : @SVM : PhaseRangePct WOMatQARec = HgCVEdgeMean : @SVM : PhaseEdgeMean SpecMin = WOMatQARec SpecMax = WOMatQARec SpecPhaseMin = WOMatQARec If SpecPhaseMin EQ '' then // Phase Min Spec was not added to WO_MAT_QA record when it should have been. // This bug should be fixed now, but in the meantime current records in production // may not have a phase min specification value. We need to add it here. - djs - 10/15/18 PSN = Xlate('RDS', RDSKeyID, 'PROD_SPEC_ID', 'X') If PSN NE '' then PRSStageKey = PSN:'*UNLOAD' Database_Services('ActivateRecord', 'PRS_STAGE', PRSStageKey) MetProps = {MET_PROP} Locate 'CRES' in MetProps using @VM setting PropPos then SpecPhaseMinCol = {MET_PHASE_MIN} SpecPhaseMin = SpecPhaseMinCol<1, PropPos> WOMatQARec = SpecPhaseMin end end end CriticalPoints = '1,2,5,6,9' // Store data points for HgCV Data Table Project -------------------------------------------------------------- For Index = 1 to NumDataPoints CriticalPoint = Index(CriticalPoints, Index, 1) HgCVData = Oconv(Iconv(Oconv(HgCVDataPoints<0,Index>, 'MD3L'), 'MD3L'), 'MD3L') PhaseData = Oconv(Iconv(Oconv(PhaseDataPoints<0,Index>, 'MD3L'), 'MD3L'), 'MD3L') If ( (HgCVData LT SpecMin) OR (HgCVData GT SpecMax) OR (PhaseData LT SpecPhaseMin) ) and CriticalPoint then // Data point out of spec WOMatQARec = True$ end DataRow = HgCVData : @TM : PhaseData WOMatQARec = DataRow Next Index // ------------------------------------------------------------------------------------------------------------ // Send data to SPC ------------------------------------------------------------------------------------------- LogPath = Environment_Services('GetSpcFilesharePath') SpcFileDtm = OConv(Datetime(), 'DT2^HS ') Convert ' ' to '_' in SpcFileDtm SpcLogFilename = 'HgCV_Unload_Res_9Points_':SpcFileDtm:'.txt' objSPC = Logging_Services('NewLog', LogPath, SpcLogFilename, CRLF$, Comma$, '', '', False$, True$) If Error_Services('NoError') then Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Successfully created blank SPC file.') end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Failed to create blank SPC file. Error message: ':Error_Services('GetMessage')) end LogData = '' LogData<1> = Oconv(Timestamp, 'DT/^HS') ; // Metrology date/time stamp LogData<2> = ReactorID ; // Reactor LogData<3> = RDSKeyID ; // RDS No LogData<4> = Xlate('RDS', RDSKeyID, 'PROD_SPEC_ID', 'X'); // PSN WaferSize = Xlate('RDS', RDSKeyID, 'WAFER_SIZE', 'X') WaferSize = Field(WaferSize, ' ', 3) LogData<5> = WaferSize:' Inch' ; // Wafer Size FormattedData = Oconv(HgCVDataPoints, 'MD3L') Swap @VM with ',' in FormattedData LogData<6> = FormattedData LogData<7> = OConv(PhaseAvg, 'MD43L') LogData<8> = OConv(HgCVAvg, 'MD43L') ; // HgCV Res Avg LogData<9> = OConv(ResStdDev, 'MD83L') ; // HgCV Res Std Dev LogData<10> = HgCVRangePct ; // HgCV Res Range % LogData<11> = OConv(EdgeMean4mm, 'MD43L') ; // HgCV 4mm Edge Mean LogData<12> = OConv(Avg9Point, 'MD83L') ; // HgCV 9 Point Mean LogData<13> = OConv(EdgeMean10mm, 'MD43L') ; // HgCV 10mm Edge Mean LogData<14> = HgCVEdgeMean ; // HgCV Edge Mean Delta % LogData := @RM : ' ' Logging_Services('AppendLog', objSPC, LogData, @RM, @FM, True$) If Error_Services('NoError') then Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Successfully updated SPC file with metrology data.') end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Failed to update SPC file with metrology data. Error message: ':Error_Services('GetMessage')) end // ------------------------------------------------------------------------------------------------------------ WOMatQARec = '' WOMatQARec = '' Database_Services('WriteDataRow', 'WO_MAT_QA', WOMatQAID, WOMatQARec, True$, False$, True$) If Error_Services('NoError') then RDSTestRec = Timestamp // Save results Database_Services('WriteDataRow', 'RDS_TEST', RDSTestKeyID, RDSTestRec, True$, False$, True$) If Error_Services('NoError') then Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Success - Hg CRes.') end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID002', Service : ' : UID002 - ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end // We know it is a CRES test, so we can now safely import the Phase Angle data end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - Data for this metrology test already exists and is not from the metrology viewer.') end end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else // The correct RDSTestKeyID was found. If this is a HgCv measurement and HgConcentration RDS test, // the data has already been written to the database in the above logic. In all other cases, the // data needs to be written to the database using the following logic that works off of the test // point map. DataPoints = HgCVDataPoints GoSub LoadRunDataToDatabase end end else Error_Services('Add', 'No data points found.') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Error_Services('Add', 'Data type / Spec not found for Layer [' : RunDataLayer : '].') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Error_Services('Add', 'Unable to obtain a valid Metrology Test Key ID.') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end service Service ImportTencorData(RunData) ErrorMessage = '' Machine = 'Tencor' Timestamp = RunData<9> HazeAvg = RunData<10> RDSKeyID = RunData<28> ScanRecipe = RunData<30> SoDAvg = RunData<39> SoDMax = RunData<40> SoDMin = RunData<41> ScanTool = RunData<43> Swap '%' with ' ' in ScanTool RDSNo@ = RDSKeyID Convert @Lower_Case to @Upper_Case in ScanTool Swap ' AM' with 'AM' in Timestamp Swap ' PM' with 'PM' in Timestamp Timestamp = IConv(Timestamp,'DT') If RowExists('RDS', RDSKeyID) then // Try to read the CleanInsp datarow. Use this to identify the RDSKeyID. ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSKeyID) If Error_Services('NoError') then // Get the PSN to determine if this recipe is part of the LWI stage or the POST stage. PSN = Xlate('RDS', RDSKeyID, 'PROD_SPEC_ID', 'X') PRSStageKeys = Xlate('PROD_SPEC', PSN, 'PRS_STAGE_KEY', 'X') StageFound = False$ Stage = '' For each PRSStageKey in PRSStageKeys using @VM setting kPos SSRecipes = Xlate('PRS_STAGE', PRSStageKey, 'SURFSCAN_RECIPE', 'X') Locate ScanRecipe in SSRecipes using @VM setting rPos then StageFound = True$ Stage = Field(PRSStageKey, '*', 2, 1) end Next PRSStageKey CIKeyStages = ReactRunRec CIKeyIDs = ReactRunRec Locate Stage in CIKeyStages using @VM setting vPos then // Stage is prescribed or user created the record from the UI. CIKeyID = CIKeyIDs<0, vPos> If (CIKeyID NE '') then HaveLock = Database_Services('GetKeyIDLock', 'CLEAN_INSP', CIKeyID) If HaveLock EQ True$ then If ScanRecipe NE '' then CleanInspRec = Database_Services('ReadDataRow', 'CLEAN_INSP', CIKeyID) NumRecipes = DCount(CleanInspRec, @VM) ScanIndex = NumRecipes + 1 NumTools = DCount(CleanInspRec, @VM) SpecRecipeList = CleanInspRec NumWfrs = 0 Locate ScanRecipe In SpecRecipeList Using @VM Setting RecipeIndex then // Recipe found in spec list SpecSampleQty = CleanInspRec SODWaferArray = '' SODStartTime = Time() SODWaferArray = QA_Services('GetSODPerWafer', RDSKeyID, ScanRecipe, Timestamp) SODStopTime = Time() TimeTaken = SODStopTime - SODStartTime LogData = '' LogData<1> = Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTS') LogData<2> = MachineType@ LogData<3> = RDSNo@ LogData<4> = 'Time taken to import SOD data: ':TimeTaken:' seconds.' Logging_Services('AppendLog', objSODPerfLog, LogData, @RM, @FM) If SODWaferArray NE '' then WaferNos = SODWaferArray<1> SODVals = SODWaferArray<2> SortVals = SODWaferArray<3> For each Wfr in SODVals using @VM setting vPos NumWfrs += (Wfr NE '') Next Wfr For each WaferNo in WaferNos using @VM If WaferNo NE '' then CleanInspRec = SODVals<1,WaferNo> CleanInspRec = SortVals<1,WaferNo> QA_Services('PostWaferImageRequest', RDSKeyID, WaferNo, ScanRecipe) end Next Wafer end // Since a matching recipe was found, clear the recipe mismatch field in case it was // previously set by a user attempting to load rundata with the wrong recipe name. CleanInspRec = '' If NumTools GT NumRecipes then NumRecipes = NumTools end NumRecipes += 1 CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_RECIPE$, NumRecipes, 0, ScanRecipe) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_TOOL$, NumRecipes, 0, ScanTool) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_MIN$, NumRecipes, 0, SoDMin) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_MAX$, NumRecipes, 0, SoDMax) SoDAvg = Iconv(SoDAvg, 'MD3') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_AVG$, NumRecipes, 0, SoDAvg) HazeAvg = Iconv(HazeAvg, 'MD3') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_HAZE_AVG_AVG$, NumRecipes, 0, HazeAvg) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SIG$, NumRecipes, 0, '') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SIG_DTM$, NumRecipes, 0, '') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_INSP_TEST_RUN_DTM$, NumRecipes, 0, Timestamp) // Save results (this will trigger an ROTR_REQUEST via CLEAN_INSP_ACTIONS) Database_Services('WriteDataRow', 'CLEAN_INSP', CIKeyID, CleanInspRec, True$, False$, True$) end else ErrorMessage = 'Tencor recipe ' : ScanRecipe : ' not found in SurfScan recipe list.' Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage) // Set recipe mismatch flag for MFS purposes CleanInspRec = ScanRecipe Database_Services('WriteDataRow', 'CLEAN_INSP', CIKeyID, CleanInspRec, True$, False$, True$) end end else ErrorMessage = 'Scan Recipe is missing.' Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage) end Database_Services('ReleaseKeyIDLock', 'CLEAN_INSP', CIKeyID) end else // 01/31/23 - djs - Modified error message to use UID002 instead of UID001 to ensure the // metrology file is not deleted so that the service can try to import it // again once the lock is released. ErrorMessage = 'Error in service ':Service:'. Failed to lock CLEAN_INSP record: ':CIKeyID:'.' Metrology_Services('LogResults', RDSKeyID, Machine, 'UID002', Service : ' : ' : ErrorMessage) end end else ErrorMessage = 'CI Key ID is missing.' Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage) end end else // Added 8/13/2020 JRO // We can enter in some conditional logic here for Production UAT // If statement to gate from production below PostStartTime = Time() if Indexc(ScanRecipe, 'POST', 1) then //Locate 'POST' in ScanRecipe Using "" setting pPos then ReactRunRec = Xlate('REACT_RUN',RDSKeyID, '', 'X') WONo = ReactRunRec;//WONo WOStep = ReactRunRec;//WOStep CassNo = ReactRunRec;//CassNo Stage = 'POST';//Stages PSNo = XLATE('RDS',RDSKeyID,RDS_PROD_SPEC_ID$,'X');//PSNo DefectSpec = Xlate('PRS_STAGE', PSNo : '*LWI', PRS_STAGE_SURF_DEFECTS$, 'X');//Since there is no set spec for a non-existent stage we need to take one from the unloading step. HazeSpec = Xlate('PRS_STAGE', PSNo : '*LWI', PRS_STAGE_SURF_HAZE$, 'X');//Since there is no set spec for a non-existent stage we need to take one from the unloading step. //Check if CI was created already(usually during cleans) JRO-9-28 CIStages = ReactRunRec Locate 'POST' in CIStages using @VM setting sPos then //Existing POST CI record found, fetch it. CINo = ReactRunRec CleanInspRec = Xlate('CLEAN_INSP', CINo,'','X') IF CleanInspRec NE '' THEN //Append a value mark to the end of the field so that we can add another set of data CleanInspRec = CleanInspRec : @VM END exists = True$ end else //No Exisiting POST CI record found, Create new CI record exists = False$ oCIParms = '' oCIParms = WONo:@RM oCIParms := WOStep:@RM oCIParms := CassNo:@RM oCIParms := Stage:@RM oCIParms := RDSKeyID:@RM oCIParms := PSNo:@RM CleanInspRec = '' CleanInspRec = WONo CleanInspRec = WOStep CleanInspRec = CassNo CleanInspRec = Stage CleanInspRec = RDSKeyID end CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_RECIPE$, -1, 0, ScanRecipe) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_TOOL$, -1, 0, ScanTool) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_MIN$, -1, 0, SoDMin) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_MAX$, -1, 0, SoDMax) SoDAvg = Iconv(SoDAvg, 'MD3') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_AVG$, -1, 0, SoDAvg) HazeAvg = Iconv(HazeAvg, 'MD3') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_HAZE_AVG_AVG$, -1, 0, HazeAvg) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SIG$, -1, 0, '') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SIG_DTM$, -1, 0, '') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_INSP_TEST_RUN_DTM$, -1, 0, Timestamp) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SPEC_SURFSCAN_RECIPE$, -1, 0, ScanRecipe) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SPEC_SURF_DEFECTS$, -1, 0, DefectSpec) //Here I am attempting to create the scan details. SODWaferArray = '' SODWaferArray = QA_Services('GetSODPerWafer', RDSKeyID, ScanRecipe, Timestamp) If SODWaferArray NE '' then WaferNos = SODWaferArray<1> SODVals = SODWaferArray<2> SortVals = SODWaferArray<3> NumRecipes = DCount(CleanInspRec, @VM) NumWfrs = '' For each Wfr in SODVals using @VM setting vPos NumWfrs += (Wfr NE '') Next Wfr For each WaferNo in WaferNos using @VM If WaferNo NE '' then CleanInspRec = SODVals<1,WaferNo> CleanInspRec = SortVals<1,WaferNo> QA_Services('PostWaferImageRequest', RDSKeyID, WaferNo, ScanRecipe) end Next Wafer end if exists Eq False$ then oCIParms := CleanInspRec ; CINo = obj_Clean_Insp('Create',oCIParms) If Error_Services('NoError') then ReactRunRec = INSERT(ReactRunRec,REACT_RUN_CI_NO$,-1,0,CINo) ReactRunRec = INSERT(ReactRunRec,REACT_RUN_CI_STAGE$,-1,0,Stage) obj_Tables('WriteRec','REACT_RUN':@RM:RDSKeyID:@RM:@RM:ReactRunRec) obj_Tables('WriteRec','CLEAN_INSP':@RM:CINo:@RM:@RM:CleanInspRec) PostStopTime = Time() TimeTaken = PostStopTime - PostStartTime LogData = '' LogData<1> = Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTS') LogData<2> = MachineType@ LogData<3> = RDSNo@ LogData<4> = 'Time taken to import POST data: ':TimeTaken:' seconds.' Logging_Services('AppendLog', objPOSTPerfLog, LogData, @RM, @FM) end else // Record error. Ensure metrology run data file is not deleted by including UID002 in the // error message that is placed onto error stack in Error_Services. ErrorMessage = 'Failed to create CLEAN_INSP record. Error UID002.' end end else //write to existing CI rec. obj_Tables('WriteRec','CLEAN_INSP':@RM:CINo:@RM:@RM:CleanInspRec) end end else // Unexpected recipe -> notify users if not a QUAL scan QualScan = (Indexc(ScanRecipe, 'QUAL', 1) GT 0) If QualScan EQ False$ then Recipients = XLATE('NOTIFICATION','TENCOR_NOTIFICATIONS',NOTIFICATION_USER_ID$,'X') SentFrom = 'OI Admin' Subject = 'Tencor SurfScan Import Failure for RDS ':RDSKeyID:'.' Message = 'Scanned recipe ':ScanRecipe:' not found in PSN ':PSN:' for RDS ':RDSKeyID:'.' AttachWindow = '' AttachKey = '' SendToGroup = '' Parms = Recipients:@RM:SentFrom:@RM:Subject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup obj_Notes('Create',Parms) end end end end else ErrorMessage = Error_Services('GetMessage') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage) end end else ErrorMessage = 'Invalid RDS Key ID.' Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage) end If ErrorMessage NE '' then Error_Services('Add', ErrorMessage) end service Service ImportSP1Data(RunData) Machine = 'SP1' Timestamp = RunData<9> HazeAvg = RunData<10> RDSKeyID = RunData<28> ScanRecipe = RunData<30> CompareScanRecipe = ScanRecipe IsMisfit = IndexC(ScanRecipe, 'MISFIT', 1) If IsMisfit then // Sum of Defects coming from LPD. SoDAvg = RunData<3> SoDMax = RunData<2> SoDMin = RunData<1> end else // Sum of Defects coming from All. SoDAvg = RunData<39> SoDMax = RunData<40> SoDMin = RunData<41> end ScanTool = RunData<43> DCNMM2 = RunData<44> * Swap '%' with ' ' in ScanTool // The 4th character can be 0 or %, but needs to be ' ' in OpenInsight ScanTool[4,1] = ' ' Convert @Lower_Case to @Upper_Case in ScanTool Convert @Lower_Case to @Upper_Case in CompareScanRecipe * Convert ' ' to '' in CompareScanRecipe Swap ' AM' with 'AM' in Timestamp Swap ' PM' with 'PM' in Timestamp Timestamp = IConv(Timestamp,'DT') RDSNo@ = RDSKeyID RDSRec = Database_Services('ReadDataRow', 'RDS', RDSKeyID) If Error_Services('NoError') then WorkOrderNo = RDSRec CassNo = RDSRec WorkOrder = Work_Order_Services('GetWorkOrder', WorkOrderNo, True$) GoSub ParseWorkOrder If RowExists('RDS', RDSKeyID) then // Try to read the CleanInsp datarow. Use this to identify the RDSKeyID. ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSKeyID) // Check to confirm that the LWI stage has a recipe. If so, then update the UNLOAD stage. If PSN NE '' then PRSStageRow = Database_Services('ReadDataRow', 'PRS_STAGE', PSN : '*LWI') PRSSurfscanRecipe = PRSStageRow Convert @Lower_Case to @Upper_Case in PRSSurfscanRecipe TrimPRSRecipes = SRP_Trim(PRSSurfscanRecipe, 'FMB') TrimScanRecipe = SRP_Trim(CompareScanRecipe, 'FMB') Locate TrimScanRecipe in TrimPRSRecipes using @VM setting vPos then * If SRP_Trim(PRSSurfscanRecipe, 'FMB') EQ SRP_Trim(CompareScanRecipe, 'FMB') then Stage = 'LWI' ; // Viewed in the Unloading form. end else // LWI does not have a recipe. Check the POST stage. If so, then update the POST stage. Transfer PRSSurfscanRecipe to LWIPRSSurfscanRecipe PRSStageRow = Database_Services('ReadDataRow', 'PRS_STAGE', PSN : '*POST') PRSSurfscanRecipe = PRSStageRow Convert @Lower_Case to @Upper_Case in PRSSurfscanRecipe TrimPRSRecipes = SRP_Trim(PRSSurfscanRecipe, 'FMB') TrimScanRecipe = SRP_Trim(CompareScanRecipe, 'FMB') Locate TrimScanRecipe in TrimPRSRecipes using @VM setting vPos then * If SRP_Trim(PRSSurfscanRecipe, 'FMB') EQ SRP_Trim(CompareScanRecipe, 'FMB') then Stage = 'POST' ; // Viewed in the Post EPI form. end else // No recipe was found. Set an error and skip. Stage = '' Error_Services('Set', 'No matching recipe was found. PSN=' : PSN : ', Scan Recipe=' : ScanRecipe : ', LWI Recipe=' : LWIPRSSurfscanRecipe : ', POST Recipe=' : PRSSurfscanRecipe) end end end else // No PRN was found. Set an error and skip. Stage = '' Error_Services('Set', 'No PRN was found.') end If Error_Services('NoError') then CIKeyStages = ReactRunRec CIKeyIDs = ReactRunRec Locate Stage in CIKeyStages using @VM setting vPos then CIKeyID = CIKeyIDs<0, vPos> If (CIKeyID NE '') then If ScanRecipe NE '' then CleanInspRec = Database_Services('ReadDataRow', 'CLEAN_INSP', CIKeyID) NumRecipes = DCount(CleanInspRec, @VM) NumTools = DCount(CleanInspRec, @VM) If NumTools GT NumRecipes then NumRecipes = NumTools end NumRecipes += 1 CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_RECIPE$, NumRecipes, 0, ScanRecipe) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_TOOL$, NumRecipes, 0, ScanTool) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_MIN$, NumRecipes, 0, SoDMin) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_MAX$, NumRecipes, 0, SoDMax) SoDAvg = Iconv(SoDAvg, 'MD3') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SUM_OF_DEF_AVG$, NumRecipes, 0, SoDAvg) HazeAvg = Iconv(HazeAvg, 'MD3') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_HAZE_AVG_AVG$, NumRecipes, 0, HazeAvg) CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SIG$, NumRecipes, 0, '') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_SCAN_SIG_DTM$, NumRecipes, 0, '') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_INSP_TEST_RUN_DTM$, NumRecipes, 0, Timestamp) DCNMM2 = Iconv(DCNMM2, 'MD3') CleanInspRec = Insert(CleanInspRec, CLEAN_INSP_DCN_MM2$, NumRecipes, 0, DCNMM2) Database_Services('WriteDataRow', 'CLEAN_INSP', CIKeyID, CleanInspRec, True$, False$, True$) If Error_Services('NoError') then Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Success.') end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Error_Services('Add', 'Scan Recipe is missing.') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Error_Services('Add', 'CI Key ID is missing.') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Error_Services('Add', 'Invalid RDS Key ID.') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end service Service FormatLayerKeyID(RunDataLayer) If RunDataLayer NE '' then // Development code OriginalRunDataLayer = RunDataLayer CONVERT @LOWER_CASE TO @UPPER_CASE IN RunDataLayer // Allow for 'P', which represents a Product (ie. QA) Test - Solution abandoned 08/14/18 per Francois Convert 'ABCDEFGHIJKLMNOPQRSTUVWXYZ- ' to '' in RunDataLayer Begin Case Case (RunDataLayer = '1') RunDataLayer = 'L1' Case (RunDataLayer = '2') RunDataLayer = 'L2' Case (RunDataLayer = '3') RunDataLayer = '2' * Case (RunDataLayer = 'P') * Null ; // Leave it be. Case Otherwise$ Error_Services('Add', 'Invalid RunDataLayer [' : OriginalRunDataLayer : '].') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) End Case end Response = RunDataLayer end service Service FormatZoneKeyID(RunDataZone) If RunDataZone NE '' then CONVERT @LOWER_CASE TO @UPPER_CASE IN RunDataZone Convert 'ABCDEFGHIJKLMNOPQRSTUVWXYZ- ' to '' in RunDataZone End Response = RunDataZone end service Service GetCalculatedZone(RDSKeyID, IsEpiPro, RunDataZone) CalculatedZone = '' If (RDSKeyID NE '') AND (IsEpiPro NE '') then If IsEpiPro then // This is Epi Pro, so there must be a zone. If a zone is specified then verify that there are RDS_TEST Keys // IDs associated with this zone. If so, then accept the zone. If not, check to see if the other zone has // RDS_TEST Key IDs. If so, then use that zone. If neither zone has RDS_TEST Key IDs then set an error. If // no zone is specified then verify if one and only one zone has RDS_TEST KeyIDS. If so, use the zone found. // If not, set an error. // Check for the RDS_TEST Key IDS for both zones first. Zone2RDSTestKeyIDs = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS_Z2', 'X') Zone1RDSTestKeyIDs = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS_Z1', 'X') // Run the tests. Begin Case Case (RunDataZone EQ 1) AND (Zone1RDSTestKeyIDs NE '') CalculatedZone = 1 Case (RunDataZone EQ 2) AND (Zone2RDSTestKeyIDs NE '') CalculatedZone = 2 Case (Zone1RDSTestKeyIDs NE '') AND (Zone2RDSTestKeyIDs EQ '') CalculatedZone = 1 Case (Zone1RDSTestKeyIDs EQ '') AND (Zone2RDSTestKeyIDs NE '') CalculatedZone = 2 Case (Zone1RDSTestKeyIDs EQ '') AND (Zone2RDSTestKeyIDs EQ '') Error_Services('Add', 'RDS Key ID [' : RDSKeyID : '] has no RDS_TEST Keys for any zone.') Case (Zone1RDSTestKeyIDs NE '') AND (Zone2RDSTestKeyIDs NE '') Error_Services('Add', 'No zone was entered for RDS Key ID [' : RDSKeyID : '] and both zones have RDS_TEST Keys.') End Case end else // Non-Epi Pro, so there is never a Zone. CalculatedZone = '' end end else Error_Services('Add', 'The RDSKeyID or IsEpiPro argument is missing.') end Response = CalculatedZone end service Service GetCalculatedLayer(RDSKeyID, Machine, RunDataZone, WoIsEpiPro, ScannedLayer) CalculatedLayer = '' If (RDSKeyID NE '') AND (Machine NE '') then Begin Case Case RunDataZone EQ 2 RDSTestKeyIDs = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS_Z2', 'X') Case RunDataZone EQ 1 RDSTestKeyIDs = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS_Z1', 'X') Case RunDataZone EQ '' RDSTestKeyIDs = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS', 'X') Case Otherwise$ Error_Services('Add', 'Unrecognized RunDataZone [' : RunDataZone : '] in the [' : Service : '] service.') End Case If Error_Services('NoError') then If RDSTestKeyIDs NE '' then Begin Case Case Machine _EQC 'BioRad' // Just get the last RDS Test Key ID whether there is one or multiple Key IDs. RDSTestKeyID = RDSTestKeyIDs[-1, 'B' : @VM] RDSTestRow = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID) If Error_Services('NoError') then CalculatedLayer = RDSTestRow end If Dcount(RDSTestKeyIDs, @VM) GT 1 and WoIsEpiPro = False$ and (ScannedLayer _EQC 'L1' or ScannedLayer _EQC 'L2' or ScannedLayer _EQC '2') then CalculatedLayer = ScannedLayer end Case Machine _EQC 'CDE' ToolClassID = Metrology_Services('GetToolClassID', Machine) ToolMatch = False$ ; // Assume no match for now. For Each RDSTestKeyID in RDSTestKeyIDs using @VM RDSTestRow = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID) If Error_Services('NoError') then If RDSTestRow _EQC ToolClassID then ToolMatch = True$ end Until ToolMatch Next RDSTestKeyID If ToolMatch then CalculatedLayer = RDSTestRow end else Error_Services('Add', 'Unable to find the RDS Key ID [' : RDSKeyID : '] layer that has this Tool Class ID [' : ToolClassID : '].') end Case Machine _EQC 'HgCV' ToolClassID = Metrology_Services('GetToolClassID', Machine) ToolMatch = False$ ; // Assume no match for now. For Each RDSTestKeyID in RDSTestKeyIDs using @VM RDSTestRow = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID) If Error_Services('NoError') then If (RDSTestRow _EQC ToolClassID) OR (RDSTestRow _EQC ToolClassID) then ToolMatch = True$ end Until ToolMatch Next RDSTestKeyID If ToolMatch then CalculatedLayer = RDSTestRow end else Error_Services('Add', 'Unable to find the RDS Key ID [' : RDSKeyID : '] layer that has this Tool Class ID [' : ToolClassID : '].') end End Case end else Error_Services('Add', 'No RDS Test Key IDS were found for RDS Key ID [' : RDSKeyID : '] and Zone Key ID [' : RunDataZone : '] in the [' : Service : '] service.') end end end else Error_Services('Add', 'The RDSKeyID or Machine argument is missing in the [' : Service : '] service.') end Response = CalculatedLayer end service Service GetToolClassID(Machine) ToolClassID = '' If Machine NE '' then Begin Case Case Machine _EQC 'BioRad' ; ToolClassID = 'FTIR' Case Machine _EQC 'CDE' ; ToolClassID = '4PP' Case Machine _EQC 'HgCV' ; ToolClassID = 'HGCV' Case Otherwise$ ; Error_Services('Add', 'Unrecognized Machine in the [' : Service : '] service.') End Case end else Error_Services('Add', 'The Machine argument is missing in the [' : Service : '] service.') end Response = ToolClassID end service Service LogResults(RDSKeyID, Machine, UID, Results) Begin Case Case Machine _EQC 'Tencor' LogHandle = objTencorLog Case Machine _EQC 'HgCV' LogHandle = objHgCVLog Case Machine _EQC 'CDE' LogHandle = objCDELog Case Machine _EQC 'Biorad' LogHandle = objBioradLog Case Machine _EQC 'Stratus' LogHandle = objStratusLog Case Machine _EQC 'SP1' LogHandle = objSP1Log Case Machine _EQC 'SPV' LogHandle = objSPVLog Case Machine _EQC 'SRP' LogHandle = objSRPLog Case Otherwise$ LogHandle = objLog End Case LogData = '' LogData<1> = LoggingDTM LogData<2> = RDSKeyID ; // RDS Key ID LogData<3> = Machine ; // Type (i.e., Machine) LogData<4> = UID ; // UID000=Success, UID001=Failure LogData<5> = Results Logging_Services('AppendLog', LogHandle, LogData, @RM, @FM) If UID NE 'UID000' then Error_Services('Add', UID:' ':Results) end end service //---------------------------------------------------------------------------------------------------------------------- // RemoveOldMetrology // // Runs a database query logic to find pre-existing metrology data already linked to future RDS Key IDs and then removes // them from the database to avoid appearing in RDS rows when they get created. This service will only remove metrology // data related to 300 future RDS Key IDs at a time. This allow the system to always stay ahead of the operators but // leaves as much history as possible in the system for as long as possible. //---------------------------------------------------------------------------------------------------------------------- Service RemoveOldMetrology() NextRDSNo = Database_Services('ReadDataRow', 'DICT.RDS', '%SK%') If Error_Services('NoError') then Flag = '' LastRDSNo = NextRDSNo + 600 // Remove old RDS_LAYER records Open 'RDS_LAYER' to RLHandle then For CurrRDS = NextRDSNo to LastRDSNo Delete RLHandle, CurrRDS:'*L1' else Null Delete RLHandle, CurrRDS:'*L2' else Null Delete RLHandle, CurrRDS:'*2' else Null Next CurrRDS end hDictRDSTest = Database_Services('GetTableHandle', 'DICT.RDS_TEST') SearchString = 'RDS_NO' : @VM : (NextRDSNo - 1) : '~' : (LastRDSNo + 1) : @FM Btree.Extract(SearchString, 'RDS_TEST', hDictRDSTest, RDSTestKeyIDs, '', Flag) If Flag EQ 0 then If RDSTestKeyIDs NE '' then LogPath = Environment_Services('GetApplicationRootPath') : '\LogFiles\RemoveMetrology' LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : '.log' Headers = 'Logging DTM' : @FM : 'Results' ColumnWidths = 20 : @FM : 50 objLog2 = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ' ', Headers, ColumnWidths, False$, False$) For Each RDSTestKeyID in RDSTestKeyIDs using @VM If Len(RDSTestKeyID) LT 7 then Database_Services('DeleteDataRow', 'RDS_TEST', RDSTestKeyID, True$) If Error_Services('NoError') then Results = RDSTestKeyID : ' deleted' end else Results = RDSTestKeyID : ' not deleted. Error : ' : Error_Services('GetMessage') end LogData = '' LogData<1> = LoggingDTM LogData<2> = Results Logging_Services('AppendLog', objLog2, LogData, @RM, @FM) end Next RDSTestKeyID end end end end service /* ImportBioRadEPPFQAData Modified and annotated by JRO 4/18/2025 Written to import data specifically for EpiPro QA/FQA Thickness. Note: Due to the nature of the forms used to validate EpiPro FQA, we currently only support single value data import. If we wish to extend this to multi value data import than we need to write the values with Sub Value Marks at the Value Mark position of the test profile. Then we also need to modify the form usage to support parsing those multi-values. I am leaving the iterating methods in here with the possibility that we can extend this to support multi valued data should the business need it. This service requires the following parameters: 1. RunData, The internal delimited run data from the parent calling routine ImportBioRadData. The service does the following actions: 1. Parses through the run data to extract the WMO Lot id, reactor id, recipe id, and slot id. 2. Writes the data point to the slot position in the WM_OUT record - > MU_WAFER_THK_RESULT field, for all wafers whether they are MU wafers or not. This allows us to detect that Any and all MU wafers have thickness data. 3. Reads the WO_MAT_QA record. 4. Iterates through WO_MAT_QA record -> PROFILE fields @VM values, IF the profile is 1ADE, or QA, or MO_QA then 5. Determines the spec slot by reading the WO_MAT_QA record -> SLOT field 6. If the detected RunDataSlot matches the spec slot, writes the datapoint to the WO_MAT_QA record - > RESULT field in the same @VM that the iterator is currently set to. */ Service ImportBioRadEPPFQAData(RunData, FileName) Timestamp = RunData<2> WMOKeyID = RunData<6> Swap '.' with '*' in WMOKeyID Swap 'o' with '' in WMOKeyID Swap 'O' with '' in WMOKeyID RunDataLayer = RunData<8> ReactorID = RunData<5> ScanRecipe = RunData<4> DataSlotId = RunData<11> DataSlotId = SRP_Trim(DataSlotId, 'F', 0) FieldPos = 13 FieldPosIncrement = 2 Offset = 1 Decimals = 2 Positions = '' DataPoints = '' WMORec = Database_Services('ReadDataRow', 'WM_OUT', WMOKeyID) WOMatQAKey = Field(WMOKeyID, '*', 1) : '*' : Field(WMOKeyID, '*', 3) Loop Position = Trim(RunData) DataPoint = Trim(RunData) Until Position EQ '' Position = SRP_Trim(Position, 'F', '0') Positions := Position : @VM DataPoints := DataPoint : @VM FieldPos += FieldPosIncrement Repeat Positions[-1, 1] = '' ; // Strip final @VM DataPoints[-1, 1] = '' ; // Strip final @VM If Error_Services('NoError') then If Error_Services('HasError') then Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end //Update the WM_OUT record for EpiPro WMORec = DataPoints Database_Services('WriteDataRow', 'WM_OUT', WMOKeyID, WMORec, True$, False$, True$) end else Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end // Update WO_MAT_QA record WOMatQAID = Field(WMOKeyID, '*', 1) : '*' : Field(WMOKeyID, '*', 3) WOMatQARec = Database_Services('ReadDataRow', 'WO_MAT_QA', WOMatQAID) If Error_Services('NoError') then SpecRecipes = WOMatQARec ProfSteps = '' Profiles = WOMatQARec ProfileCnt = DCount(WOMatQARec, @VM) Stages = WOMatQARec Slots = WOMatQARec pPos = '' For each Profile in Profiles using @VM setting pPos Stage = Stages<0, pPos> If ( (Profile EQ '1ADE') and ( (Stage EQ 'QA') or (Stage EQ 'MO_QA') ) ) then Slot = Slots<0, pPos> //There is a specific scenario where when the spec wafers are 1 and L, and there is a makeup wafer in each of the first and last slots, they would then want to //measure the next open slot, either incremented down from the last wafer position or incremented up from the first wafer position. //We may need to add that in when the definition becomes clearer. Begin Case Case Slot EQ '1' For WaferIndex = 1 to 25 SlotIsEmpty = Database_Services('ReadDataColumn', 'WM_OUT', WMOKeyID, WM_OUT_RDS$, True$, 0, False$)<1, WaferIndex> EQ '' Until SlotIsEmpty EQ False$ Next WaferIndex Case Slot EQ 'L' For WaferIndex = 25 to 1 Step -1 SlotIsEmpty = Database_Services('ReadDataColumn', 'WM_OUT', WMOKeyID, WM_OUT_RDS$, True$, 0, False$)<1, WaferIndex> EQ '' Until SlotIsEmpty EQ False$ Next WaferIndex Case Slot EQ 'A' WaferIndex = 1 Case Otherwise$ WaferIndex = Slot End Case If DataSlotId EQ WaferIndex then WOMatQARec = DataPoints end end Next Profile Database_Services('WriteDataRow', 'WO_MAT_QA', WOMatQAID, WOMatQARec, True$, False$, True$) If Error_Services('NoError') then Metrology_Services('LogResults', RDSNo@, Machine, 'UID000', Service : ' : Success.') end else Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end service //---------------------------------------------------------------------------------------------------------------------- // CleanRepository // // Input: NumDays - The number of days which determines whether or not a file will be deleted. // // Removes rundata files from the Run Data Repository folder that are more than the desired number of days old. //---------------------------------------------------------------------------------------------------------------------- Service CleanRepository(NumDays) RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\' InitDir RepoPath:'*.txt' FileList = DirList() Today = Date() For each Filename in FileList FileInfo = Dir(RepoPath:Filename) LastWriteDate = FileInfo<2> FileAge = Today - LastWriteDate If FileAge GT NumDays then OSDelete RepoPath:Filename end Next Filename end service //---------------------------------------------------------------------------------------------------------------------- // MonitorQueue // // Input: NumFiles - The number of files at which manual data entry for ROTR will be allowed by all users. // // Monitors the Metrology run data import queue. If the number of files exceed the NumFiles variable, then // the restriction of manual data entry of ROTR data by LEADS, SUPERVISORS, ENGINEERING, and FINAL_QA will be lifted. // Once the queue drops back down below the NumFiles variable, then the restriction will be put back into place. //---------------------------------------------------------------------------------------------------------------------- Service MonitorQueue(NumFiles) RunDataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Data\' InitDir RunDataPath:'*.txt' FileList = DirList() CurrFileCount = DCount(FileList, @FM) If CurrFileCount LT NumFiles then // Enable manual entry restriction - check current status to avoid unnecessary writes. CurrLockStatus = Database_Services('ReadDataRow', 'APP_INFO', 'ROTR_DATA_ENTRY_LOCK') If CurrLockStatus EQ False$ then Database_Services('WriteDataRow', 'APP_INFO', 'ROTR_DATA_ENTRY_LOCK', True$) end end else // Lift manual entry restriction - check current status to avoid unnecessary writes. CurrLockStatus = Database_Services('ReadDataRow', 'APP_INFO', 'ROTR_DATA_ENTRY_LOCK') If CurrLockStatus EQ True$ then Database_Services('WriteDataRow', 'APP_INFO', 'ROTR_DATA_ENTRY_LOCK', False$) end end end service Service GetIQSViolations() hSysLists = Database_Services('GetTableHandle', 'SYSLISTS') Lock hSysLists, ServiceKeyID then Response = 0 ParsedIQSData = '' Pass = 1 URL = "https://messa04ec.infineon.com/product-thick-and-res-health-reduced-web.html" TimeoutDuration = HTTPClient_Services('GetTimeoutDuration') If TimeoutDuration NE 30 then Httpclient_Services('SetTimeoutDuration', 30) IQSResponse = Httpclient_Services('SendHTTPRequest', 'GET', URL, '', '', '', '', '', '', '') swap CRLF$ with '' in IQSResponse PublishDateStart = Indexc(IQSResponse, 'Published: ', 1) + 11 PublishDateCnt = Indexc(IQSResponse, '

', 1) - PublishDateStart PublishDate = IQSResponse[PublishDateStart, PublishDateCnt] swap ' AM' with 'AM' in PublishDate swap ' PM' with 'PM' in PublishDate ConvDateTime = IConv(PublishDate, 'DT') DataTableBegin = Indexc(IQSResponse, '', 1) + 7 DataTable = IQSResponse[DataTableBegin, DataTableEnd] swap '' with @FM in DataTable swap '' with '' in DataTable swap '' with '' in DataTable DataTable = Delete(DataTable, 1,0,0) DataTable = Delete(DataTable, 1,0,0) FilteredData = '' for i = 1 to DCount(DataTable, @FM) if Indexc(DataTable, '', 1) then FilteredData<-1> = DataTable end Next i swap '' with @VM in FilteredData for i = 1 to DCount(FilteredData, @FM) //Delete blank empty value FilteredData = Delete(FilteredData, i,1,0) //Delete useuless Entire Database column FilteredData = Delete(FilteredData, i,1,0) ParsedIQSData = FilteredData ParsedIQSData = FilteredData ParsedIQSData = FilteredData ParsedIQSData = FilteredData ParsedIQSData = FilteredData ParsedIQSData = FilteredData ParsedIQSData = FilteredData swap '>' with '>' in FilteredData swap '<' with '<' in FilteredData ParsedIQSData = FilteredData ParsedIQSData = FilteredData ParsedIQSData = FilteredData Next i LastWriteTime = IConv(PublishDate, 'DT') ParsedIQSData = LastWriteTime Database_Services('WriteDataRow', 'CONFIG', 'IQS_VIOL_DATA', ParsedIQSData, True$, False$, True$) //Send status of last write time If SRP_Datetime('MinuteSpan', LastWriteTime, SRP_Datetime('Now')) GT 10 then Mona_Services('SendBufferedStatus', 'IQS_VIOLATION_DATA', 'LastDataTimestamp', 'CRITICAL') end else Mona_Services('SendBufferedStatus', 'IQS_VIOLATION_DATA', 'LastDataTimestamp', 'OK') end Reactor_Services('UpdateReactorIQSViolations') Unlock hSysLists, ServiceKeyID else Null end end service Service ScanRecipeMatchesRdsTestSpecThickMrecipe(RdsNo, RecipeLayer, RecipeInScan, RdsTestKeysFromRdsLayer, Zone='') If Unassigned(RecipeLayer) or RecipeLayer EQ '' then RecipeLayer = 'L1' end MatchFound = False$ ValidArgs = Assigned(RdsNo) and RdsNo NE '' and Assigned(RecipeInScan) and RecipeInScan NE '' ValidArgs = ValidArgs and Assigned(RdsTestKeysFromRdsLayer) and Dcount(RdsTestKeysFromRdsLayer, @VM) GT 0 If ValidArgs then For Each RDSTestKeyID in RdsTestKeysFromRdsLayer using @VM setting mkPos RdsTestRec = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID) If Error_Services('NoError') then RdsTestRdsNo = RdsTestRec If RdsNo EQ RdsTestRdsNo then RdsTestRecLayer = RdsTestRec If RecipeLayer EQ RdsTestRecLayer then Continue = True$ If Zone NE '' then RdsTestRecZone = RdsTestRec If RdsTestRecZone NE Zone then Continue = False$ end end If Continue then SpecThickMrecipe = RdsTestRec If RecipeInScan _EQC SpecThickMrecipe then MatchFound = True$ end end end end end Until MatchFound Next RDSTestKeyID end Response = MatchFound end service Service ScanNumDataPointsMatchesRdsTestSpecThickMPattern(RdsNo, RecipeLayer, ScanNumDataPoints, RdsTestKeysFromRdsLayer, ToolClass, Zone='') If Unassigned(RecipeLayer) or RecipeLayer EQ '' then RecipeLayer = 'L1' end MatchFound = False$ ValidArgs = Assigned(RdsNo) and RdsNo NE '' and Assigned(ScanNumDataPoints) and Num(ScanNumDataPoints) ValidArgs = ValidArgs and Assigned(RdsTestKeysFromRdsLayer) and Dcount(RdsTestKeysFromRdsLayer, @VM) GT 0 ValidArgs = ValidArgs and Assigned(ToolClass) and ToolClass NE '' If ValidArgs then For Each RDSTestKeyID in RdsTestKeysFromRdsLayer using @VM setting mkPos RdsTestRec = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID) If Error_Services('NoError') then RdsTestRdsNo = RdsTestRec If RdsNo EQ RdsTestRdsNo then RdsTestRecLayer = RdsTestRec If RecipeLayer EQ RdsTestRecLayer then Continue = True$ If Zone NE '' then RdsTestRecZone = RdsTestRec If RdsTestRecZone NE Zone then Continue = False$ end end If Continue then SpecPatternName = RdsTestRec SpecNumDataPoints = Tool_Class_Services('GetNumberOfPointsForPattern', ToolClass, SpecPatternName) If ScanNumDataPoints EQ SpecNumDataPoints then MatchFound = True$ end end end end end Until MatchFound Next RDSTestKeyID end Response = MatchFound end service //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Internal GoSubs //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ClearCursors: For Cursor = 1 to 8 ClearSelect Cursor Next Cursor return CalculateQAData: // Find the Min, Max, Average, Edge Mean Delta, and Range % values Sum = 0 Min = DataPoints<0,1> Max = DataPoints<0,1> Delta1 = 0 Delta2 = 0 RangeMin = DataPoints<0,1> RangeMax = DataPoints<0,1> RangeAvg = 0 RangePoints = '1,2,5,6,9' For each DataPoint in DataPoints using @VM setting Index Sum = Sum + DataPoint // Min & Max Min = Min(Min, DataPoint) Max = Max(Max, DataPoint) // Edge Mean Delta If Index GE 6 AND Index LE 9 then Delta1 = Delta1 + DataPoint end If Index GE 2 AND Index LE 5 then Delta2 = Delta2 + DataPoint end // Range % Locate Index in RangePoints using ',' setting unusedIndex then RangeMin = Min(RangeMin, DataPoint) RangeMax = Max(RangeMax, DataPoint) RangeAvg = RangeAvg + DataPoint end Next DataPoint // Edge Mean Delta EdgeMeanDelta = '' If ( (Delta1 NE 0) and (Delta2 NE 0) ) then Delta1Avg = Delta1/4 Delta2Avg = Delta2/4 EdgeMeanDelta = ( (Delta1Avg - Delta2Avg) / Delta1Avg) * 100 end // Range % Range = RangeMax - RangeMin NumRangePoints = DCount(RangePoints, ',') RangeAvg = RangeAvg / NumRangePoints RangePct = (Range / RangeAvg) * 100 // Average NumDataPoints = DCount(DataPoints, @VM) Average = Sum / NumDataPoints return CalculateResults: If IsEpiPro then // Using logic adapted from COMM_DIALOG_EPI_PRO_MET > Done event handler. MetRec = RDSTestRec TestPointMap = BaseMapID MetNo = RDSTestKeyID RDSNo = MetRec LSId = MetRec Zone = MetRec TestPointMap = MetRec IF Zone = '' THEN MetKeys = XLATE('REACT_RUN',RDSNo,'MET_KEYS','X') IF Zone = 1 THEN MetKeys = XLATE('REACT_RUN',RDSNo,'MET_KEYS_Z1','X') IF Zone = 2 THEN MetKeys = XLATE('REACT_RUN',RDSNo,'MET_KEYS_Z2','X') RdsLSKeys = XLATE('RDS',RDSNo,RDS_RDS_LAYER_KEYS$,'X') ReactorNo = XLATE('REACT_RUN',RDSNo,REACT_RUN_REACTOR$,'X') ReactType = XLATE('REACTOR',ReactorNo,REACTOR_REACT_TYPE$,'X') Continue = True$ ; // Assue we will continue for now. IF INDEX(RdsLSKeys,@VM,1) THEN * Multiple layers DepTimeTargets = '' LSCnt = COUNT(RdsLSKeys,@VM) + (RdsLSKeys NE '') DepTimeTargets = XLATE('RDS_LAYER',RdsLSKeys,RDS_LAYER_EPI_TIME$,'X') DepTimeTargets = OCONV(DepTimeTargets,'MD1') LS1DepTime = '' LS2DepTime = '' FOR I = 1 TO LSCnt RdsLSKey = RdsLSKeys<1,I> IF INDEX(RdsLSKey,'L1',1) THEN LS1DepTime = DepTimeTargets<1,I> IF INDEX(RdsLSKey,'L2',1) THEN LS2DepTime = DepTimeTargets<1,I> NEXT I IF LS1DepTime NE '' AND LS2DepTime NE '' THEN TotDepTime = LS1DepTime + LS2DepTime LS1Ratio = LS1DepTime/TotDepTime MetReadings = '' FOR I = 1 TO COUNT(MetKeys,@VM) + (MetKeys NE '') MetKey = MetKeys<1,I> LMetReadings = obj_RDS_Test('GetReadSet',MetKey) IF I = 1 THEN MetReadings<1> = LMetReadings<1> ;* Line Numbers MetReadings<3> = LMetReadings<3> ;* SheetRho MetReadings<4> = LMetReadings<4> ;* Hgcv END IF I = 2 THEN MetReadings<5> = LMetReadings<4> ;* Hgcv END IF I = 3 THEN MetReadings<2> = LMetReadings<2> ;* Thickness readings END NEXT I end else Continue = False$ end END ELSE LS1Ratio = 1 MetReadings = obj_RDS_Test('GetReadSet',MetNo) END if Continue then CONVERT @FM TO @RM IN MetReadings oTPM_Parms = TestPointMap:@RM:MetReadings Results = obj_Test_Point_Map('PointToResult',oTPM_Parms) ThicknessArray = FIELD(Results,@FM,2,4) ThickReads = ThicknessArray<1> SheetRhoReads = ThicknessArray<2> HgCV1ResReads = ThicknessArray<3> HgCV2ResReads = ThicknessArray<4> L1Ratio = LS1Ratio ReadingNos = '' FOR I = 1 TO 9 ReadingNos<1,I> = I NEXT I oTPM_Parms = TestPointMap:@RM:ReadingNos:@RM:ThickReads:@RM:SheetRhoReads:@RM:HgCV1ResReads:@RM:HgCV2ResReads EpiReads = obj_Test_Point_Map('ResultToPoint',oTPM_Parms) BEGIN CASE CASE Zone = '1' ; MetKeys = XLATE('REACT_RUN',RDSNo,'MET_KEYS_Z1','X') CASE Zone = '2' ; MetKeys = XLATE('REACT_RUN',RDSNo,'MET_KEYS_Z2','X') CASE 1 ; MetKeys = XLATE('REACT_RUN',RDSNo,'MET_KEYS','X') END CASE IF INDEX(MetKeys,@VM,1) THEN // MetNo might be from a layer other than the last one. Get the last layer and assign it to the MetNo // variable. MetNo = MetKeys[-1, 'B' : @VM] LOCATE MetNo IN MetKeys USING @VM SETTING Pos THEN MetKeys = DELETE(MetKeys,1,Pos,0) END oRTParms_L1 = '' oRTParms_L2 = '' FOR J = 1 TO COUNT(EpiReads<1>,@VM) + (EpiReads<1> NE '') oRTParms_L1 = OCONV(ICONV(EpiReads * L1Ratio, 'MD2'),'MD2') oRTParms_L2 = EpiReads - oRTParms_L1 IF EpiReads NE '' THEN oRTParms_L1 = EpiReads oRTParms_L2 = '' EpiReads = '' END ELSE oRTParms_L1 = '' oRTParms_L2 = '' END IF EpiReads NE '' THEN oRTParms_L1 = EpiReads END IF EpiReads NE '' THEN oRTParms_L2 = EpiReads END NEXT J CONVERT @FM TO @RM IN oRTParms_L1 CONVERT @FM TO @RM IN oRTParms_L2 obj_RDS_Test('SetReadSet',MetKeys<1,1>:@RM:oRTParms_L1) obj_RDS_Test('SetReadSet',MetKeys<1,2>:@RM:oRTParms_L2) END oRTParms = '' oRTParms = EpiReads oRTParms = EpiReads oRTParms = EpiReads CONVERT @FM TO @RM IN oRTParms obj_RDS_Test('SetReadSet',MetNo:@RM:oRTParms) end end else // Non-Epi Pro calculations. Using logic from adapted from COMM_RDS_TEST > ReadingsPC method. Readings = RDSTestRec Conversion = 'MD' : Decimals Readings = Oconv(Readings, Conversion) rv = Set_Status(0) Stats = obj_RDS_Test('CalcStats', Readings : @RM : Conversion) ErrorCode = '' If Get_Status(ErrorCode) EQ 0 then Convert @RM to @FM in Stats Stats<1> = Iconv(Stats<1>, Conversion) Stats<2> = Iconv(Stats<2>, 'MD4') Stats<3> = Iconv(Stats<3>, 'MD2') Stats<4> = Iconv(Stats<4>, Conversion) Stats<5> = Iconv(Stats<5>, Conversion) Stats<6> = Iconv(Stats<6>, Conversion) Stats<7> = Iconv(Stats<7>, 'MD4') Begin Case Case Machine _EQC 'BioRad' RDSTestRec = Stats<1> RDSTestRec = Stats<2> RDSTestRec = Stats<6> RDSTestRec = Stats<7> RDSTestRec = Stats<3> RDSTestRec = Stats<4> RDSTestRec = Stats<5> Case Machine _EQC 'CDE' RDSTestRec = Stats<1> RDSTestRec = Stats<2> RDSTestRec = Stats<6> RDSTestRec = Stats<7> RDSTestRec = Stats<3> RDSTestRec = Stats<4> RDSTestRec = Stats<5> Case Machine _EQC 'HgCV' RDSTestRec = Stats<1> RDSTestRec = Stats<2> RDSTestRec = Stats<6> RDSTestRec = Stats<7> RDSTestRec = Stats<3> RDSTestRec = Stats<4> RDSTestRec = Stats<5> End Case //Log returned Data for the specific tool: Metrology_Services('LogResults', RDSKeyID, Machine, '', Service : ' : Stats for ': Machine :' : ' : Stats) Readings = obj_RDS_Test('Resistivity', RDSTestKeyID : @RM : RDSTestRec : @RM : 1) Metrology_Services('LogResults', RDSKeyID, Machine, '', Service : ' : Readings: ' : Readings) Conversion = 'MD4' Readings = Oconv(Readings, Conversion) rv = Set_Status(0) Stats = obj_RDS_Test('CalcStats', Readings : @RM : Conversion) //JRO Logging StatsToLog = Stats swap @RM with @FM in StatsToLog Metrology_Services('LogResults', RDSKeyID, Machine, '', Service : ' : Stats: ' : StatsToLog) If Get_Status(ErrorCode) EQ 0 then Convert @RM to @FM in Stats Stats<1> = Iconv(Stats<1>, Conversion) Stats<2> = Iconv(Stats<2>, 'MD4') Stats<3> = Iconv(Stats<3>, 'MD2') Stats<4> = Iconv(Stats<4>, Conversion) Stats<5> = Iconv(Stats<5>, Conversion) Stats<6> = Iconv(Stats<6>, Conversion) Stats<7> = Iconv(Stats<7>, 'MD4') RDSTestRec = Stats<1> RDSTestRec = Stats<2> RDSTestRec = Stats<6> RDSTestRec = Stats<7> RDSTestRec = Stats<3> RDSTestRec = Stats<4> RDSTestRec = Stats<5> //JRO Logging Metrology_Services('LogResults', RDSKeyID, Machine, '', Service : ' : Stats To Write: ' : Stats) end Database_Services('WriteDataRow', 'RDS_TEST', RDSTestKeyID, RDSTestRec, True$, False$, True$) If Error_Services('NoError') then Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Success - Calculated Results.') end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID002', Service : ' : UID002 - ' : Error_Services('GetMessage')) end end end return ParseWorkOrder: If Assigned(objWorkOrder) else objWorkOrder = 0 If objWorkOrder LE 0 then SRP_JSON(objWorkOrder, 'PARSE', WorkOrder) end If objWorkOrder GT 0 then WorkOrderNo = SRP_JSON(objWorkOrder, 'GETVALUE', 'WorkOrderNumber') EpiPartNo = SRP_JSON(objWorkOrder, 'GETVALUE', 'EpiPartNumber') WOReactorType = SRP_JSON(objWorkOrder, 'GETVALUE', 'ReactorType') PSN = SRP_JSON(objWorkOrder, 'GETVALUE', 'PSN') Recipe = SRP_JSON(objWorkOrder, 'GETVALUE', 'Recipe') HotLot = Iconv(SRP_JSON(objWorkOrder, 'GETVALUE', 'HotLot'), 'BYes,No') Closed = Iconv(SRP_JSON(objWorkOrder, 'GETVALUE', 'Closed'), 'BYes,No') TotalWafers = SRP_JSON(objWorkOrder, 'GETVALUE', 'TotalWafers') WafersRemaining = SRP_JSON(objWorkOrder, 'GETVALUE', 'WafersRemaining') PercentComplete = SRP_JSON(objWorkOrder, 'GETVALUE', 'PercentComplete') CustNameShort = SRP_JSON(objWorkOrder, 'GETVALUE', 'Company.NameShort') SRP_JSON(objWorkOrder, 'RELEASE') end else EpiPartNo = '' WOReactorType = '' PSN = '' Recipe = '' HotLot = '' Closed = '' TotalWafers = '' WafersRemaining = '' PercentComplete = '' CustNameShort = '' end return LoadRunDataToDatabase: // Thickness OR Resistivity OR Hg Resistivity Section Read the tool class base map BaseMapID = RDSTestRec Convert @Lower_Case to @Upper_Case in BaseMapID TestPointMapRec = Database_Services('ReadDataRow', 'TEST_POINT_MAP', BaseMapID) If Error_Services('NoError') then BaseMapResults = TestPointMapRec BaseMapPoints = TestPointMapRec end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end // Read the metrology tool map PatternName = Trim(RDSTestRec) Convert @Lower_Case to @Upper_Case in PatternName ToolMapID = RDSTestRec Convert @Lower_Case to @Upper_Case in ToolMapID TestPointMapRec = Database_Services('ReadDataRow', 'TEST_POINT_MAP', ToolMapID) If Error_Services('NoError') then ToolMapResults = TestPointMapRec ToolMapPoints = TestPointMapRec end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end ToolClassRow = Database_Services('ReadDataRow', 'TOOL_CLASS', ToolClassID) If Error_Services('NoError') then PatternNames = ToolClassRow Convert @Lower_Case to @Upper_Case in PatternNames Locate PatternName in PatternNames using @VM setting fPos then PatternSize = ToolClassRow If (NumDataPoints = PatternSize) then ExistingData = false$ // Calculate the array offset for the thickness measurements. If IsEpiPro then // Perform test point mapping. VirtualSize = DCount(BaseMapResults, @VM) end else VirtualSize = PatternSize end Offset = 17 - VirtualSize If Offset LT 0 then Offset = 0 Offset = Int((Offset / 2) + 0.5) // Clear the read values before populating with the new ones. RDSTestRecPrev = RDSTestRec RDSTestRec = '' For ReadingCnt = 1 to NumDataPoints If IsEpiPro then // Perform test point mapping. Locate Positions<0, ReadingCnt> in ToolMapResults using @VM setting tmPos then ToolMapPoint = ToolMapPoints<0, tmPos> Locate ToolMapPoint in BaseMapPoints using @VM setting bmPos then Position = bmPos end end end else Position = Positions<0, ReadingCnt> end DataPoint = DataPoints<0, ReadingCnt> IF RDSTestRecPrev NE '' then ExistingData = true$ end RDSTestRec = DataPoint Next ReadingCnt RDSTestRec = Timestamp // Save results if Not(ExistingData) OR IsViewerFile then Database_Services('WriteDataRow', 'RDS_TEST', RDSTestKeyID, RDSTestRec, True$, False$, True$) end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - Data for this metrology test already exists and is not from the metrology viewer.') end If Error_Services('NoError') then Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : Success.') GoSub CalculateResults end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : UID001 - ' : Error_Services('GetMessage')) end end else Error_Services('Add', 'Number of data points does not match the pattern size [' : PatternSize : '].') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Error_Services('Add', 'Unable to locate the pattern name [' : PatternName : '].') Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end end else Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) end return