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

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


//----------------------------------------------------------------------------------------------------------------------
// 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'
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08DDUPSFS6420\Source\MET08DDUPSFS6420\'
				Case Machine _EQC 'HgCV'
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESIHGCV\Source\MET08RESIHGCV\'
				Case Machine _EQC 'CDE'
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESIMAPCDE\Source\MET08RESIMAPCDE\'
				Case Machine _EQC 'Biorad'
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08THFTIRQS408M\Source\MET08THFTIRQS408M\'
				Case Machine _EQC 'Stratus'
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08THFTIRSTRATUS\Source\MET08THFTIRSTRATUS\'
				Case Machine _EQC 'SP1'
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08DDUPSP1TBI\Source\MET08DDUPSP1TBI\'
				Case Machine _EQC 'SPV'
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESISRP2100\Source\MET08RESISRP2100\'
				Case Machine _EQC 'SRP'
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08ANLYSDIFAAST230\Source\MET08ANLYSDIFAAST230\'
				Case Otherwise$
					Error_Services('Add', 'Error in ':Service:' service. Unsupported Machine "':Machine:'" passed into service')
			End Case
			
			If Error_Services('NoError') then
				
				InitDir DataPath:'*.txt'
				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<FileLoopIndex>
					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<FilePos>
						MinutesSince   = SRP_Datetime('MinuteSpan', LastAttemptDTM, CurrDTM)
						If MinutesSince GE MINUTES_UNTIL_RETRY$ then
							ImportAttempts                = ImportAttemptCounts<FilePos>
							ImportAttempts               += 1
							ImportAttemptCounts<FilePos>  = ImportAttempts
							ImportAttemptDTMs<FilePos>    = CurrDTM
						end else
							ProcessNow = False$
						end
					end else
						ImportFileList<FilePos>      = FileName
						ImportAttempts               = 1
						ImportAttemptCounts<FilePos> = ImportAttempts
						ImportAttemptDTMs<FilePos>   = CurrDTM
					end
					
					If ProcessNow then
						
						FileInfo    = Dir(DataPath:FileName)
						FileSize    = FileInfo<1>
						Metrology_Services('LogResults', '', Machine, 'UID000', 'Dir : ' : FileName : ', Size : ' : FileSize)
						
						ImportStartTime = Time()
						
						OSREAD RunData FROM DataPath:FileName THEN
							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
							
							Set_Status(0)
							OSWrite RunData to RepoPath:FileName
							status_code = ''
							If Get_Status(status_code) then
								ErrorMessage = 'Error writing run data file to repository: status_code = ' : status_code
								Metrology_Services('LogResults', '', Machine, 'UID001', Service : ' : ' : ErrorMessage)
								Set_Status(0)
							end
							
							SWAP '|' WITH @VM IN RunData
							SWAP CRLF$ WITH @FM IN RunData
							
							LOOP
								LastChar = RunData[-1,1]
							UNTIL LastChar NE @FM
								RunData[-1,1] = ''
							REPEAT
							
							*************************
							* Import metrology data *
							*************************
							
							Metrology_Services('ImportMetrologyRunData', RunData, FileName, Machine)
							
							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
							
						END else
							Metrology_Services('LogResults', '', Machine, 'UID001', 'Read : ' : FileName : ', Size : ' : FileSize)
						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


//----------------------------------------------------------------------------------------------------------------------
// ImportMetrologyRunData
//
// RunData. - [Required]
//
// Imports the metrology run data into the RDS_TEST database table. Returns True$ if successful and False$ if
// unsuccessful.
//----------------------------------------------------------------------------------------------------------------------
Service ImportMetrologyRunData(RunData, FileName, Machine)
	
	If RunData NE '' then
		
		Convert Tab$ to @FM in RunData
		// Scan the run data for machine specific information. Then call the relevant update service for the specific
		// machine.
		Begin Case
			Case IndexC(RunData, 'Stratus', 1)
				Metrology_Services('ImportStratusData', RunData)
				MachineType@ = 'Stratus'
				
			Case IndexC(RunData, 'Bio-Rad', 1)
				Metrology_Services('ImportBioRadData', RunData, FileName)
				MachineType@ = 'Bio-Rad'
				
			Case IndexC(RunData, 'ResMap', 1) OR IndexC(RunData, 'CDE', 1)
				Metrology_Services('ImportCDEData', RunData, FileName)
				MachineType@ = 'CDE'
				
			Case IndexC(RunData, 'PROBE', 1) OR IndexC(RunData, 'HgProbe', 1) OR IndexC(RunData, 'HGCV', 1)
				Metrology_Services('ImportHgCVData', RunData, FileName)
				MachineType@ = 'HgProbe'
				
			Case IndexC(RunData, 'SP1', 1)
				Metrology_Services('ImportSP1Data', RunData)
				MachineType@ = 'SP1'
				
			Case IndexC(RunData, 'TENCOR', 1) OR IndexC(RunData, 'ROTR', 1)                
				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 else
		Error_Services('Add', 'RunData argument was missing')
		Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage'))
	end
	
end service


Service ImportStratusData(RunData)
	
	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.
	PSN               = RunData<9>
	BatchID           = RunData<10>
	Cassette          = RunData<11>
	ThickAvg          = RunData<12>
	Positions         = ''
	DataPoints        = ''
	
	Loop
		Position  = Trim(RunData<FieldPos>)
		DataPoint = Trim(RunData<FieldPos + Offset>)
	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<WO_MAT_MU_WAFER_THK_RESULT$, Position>  = 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<WO_MAT_MU_WAFER_THK_RESULT$>
				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<WM_OUT_MU_WAFER_THK_RESULT$, Wafer>  = 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<WM_OUT_MU_WAFER_THK_RESULT$>
				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<WO_MAT_QA_RECIPE$>
			ProfSteps   = ''
			Profiles    = WOMatQARec<WO_MAT_QA_PROFILE$>
			ProfileCnt  = DCount(WOMatQARec<WO_MAT_QA_PROFILE$>, @VM)
			Stages      = WOMatQARec<WO_MAT_QA_STAGE$>
			Slots       = WOMatQARec<WO_MAT_QA_SLOT$>
			SpecQty     = WOMatQARec<WO_MAT_QA_WFR_QTY$>
			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>
					If ( (Slot EQ 'A') or (SpecQty EQ 'A') or (SpecQty GT 5) ) then
						MetMin                                  = WOMatQARec<WO_MAT_QA_MIN$, pPos>
						MetMax                                  = WOMatQARec<WO_MAT_QA_MAX$, pPos>
						StdDevMax                               = ((MetMin + MetMax) / 2) * (0.02)
						StdDevMax                               = IConv(StdDevMax,'MD3')
						WOMatQARec<WO_MAT_QA_STD_RESULT$, pPos> = StdDev
						WOMatQARec<WO_MAT_QA_STD_MAX$, pPos>    = StdDevMax
					end
					Begin Case
						Case Slot EQ '1' 
							WaferIndex = 1
						Case Slot EQ 'L'
							WaferIndex = 25
						Case Slot EQ 'A'
							WaferIndex = 1
						Case Otherwise$
							WaferIndex = Slot	
					End Case
					Locate WaferIndex in Positions using @VM setting dPos then
						WOMatQARec<WO_MAT_QA_RESULT$, pPos> = 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 ImportBioRadData(RunData, FileName)
	Machine           = 'BioRad'
	IsProdTest        = False$
	ParseArray        = ''
	FieldPos          = 13
	FieldPosIncrement = 2
	Offset            = 1
	Decimals          = 2
	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
		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
			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 else
		// RDS Biorad metrology file
		IsViewerFile     = Index(FileName, 'Viewer', 1)
		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<RDS_WO$>
				
				// 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<RDS_CASS_NO$>
				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<REACTOR_REACT_TYPE$>
					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<FieldPos>)
							DataPoint = Trim(RunData<FieldPos + Offset>)
						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<RDS_LAYER_RDS_TEST_KEYS$>
							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<RDS_TEST_ZONE$>
										// 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 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<DataPos + 4>    = 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<WO_MAT_QA_RECIPE$>
										
										WOStepNo    = RDSRec<RDS_WO_STEP_KEY$>[-1, 'B*']
										ProfSteps   = ''
										ProfileCnt  = DCount(WOMatQARec<WO_MAT_QA_PROFILE$>, @VM)
										For vPos = 1 to ProfileCnt
											ProfSteps<1, vPos>  = WOMatQARec<WO_MAT_QA_PROFILE$, vPos> : '*' : WOMatQARec<WO_MAT_QA_STAGE$, vPos>
										Next vPos
										LastProfSig     = WOMatQARec<WO_MAT_QA_PROFILE$, ProfileCnt>
										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<WO_MAT_QA_RECIPE$, Pos>
													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<WO_MAT_QA_TOOL_CLASS$, Pos>
													WoMatQaRecipePattern = WOMatQARec<WO_MAT_QA_RECIPE_PATTERN$, Pos>
													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<WO_MAT_QA_RESULT$, Pos> EQ '' OR IsViewerFile) then
														If WoMatQaRecipeMatchesScanRecipe and WoMatQaRecipePatternNumPointsMatchesScanNumDataPoints then
															WOMatQARec<WO_MAT_QA_RESULT$, Pos>     = Average
															WOMatQARec<WO_MAT_QA_MIN_RESULT$, Pos> = Oconv(Iconv(Min, 'MD3'), 'MD3')
															WOMatQARec<WO_MAT_QA_MAX_RESULT$, Pos> = Oconv(Iconv(Max, 'MD3'), 'MD3')
															SpecMin                                = WOMatQARec<WO_MAT_QA_MIN$, Pos>
															SpecMax                                = WOMatQARec<WO_MAT_QA_MAX$, Pos>
															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<WO_MAT_QA_OUT_OF_SPEC$, Pos> = True$
																	end    
																end else
																	If ( FormatedData LT SpecMin ) OR ( FormatedData GT SpecMax ) then
																		WOMatQARec<WO_MAT_QA_OUT_OF_SPEC$, Pos> = True$
																	end    
																end
																WOMatQARec<WO_MAT_QA_DATA_POINTS$, Pos, SubValuePos> = FormatedData
															Next DataPoint
															WOMatQARec<WO_MAT_QA_SIG$, Pos>     = ''
															WOMatQARec<WO_MAT_QA_SIG_DTM$, Pos> = ''
															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<RDS_LAYER_RDS_TEST_KEYS$>
											
											// 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
	
end service


Service ImportCDEData(RunData, FileName)
	
	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>
	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
		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 else
		// Regular metrology file
		IsViewerFile     = Index(FileName, 'Viewer', 1)
		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<RDS_WO$>
			CassNo          = RDSRec<RDS_CASS_NO$>
			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<REACTOR_REACT_TYPE$>
				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<FieldPos>)
						DataPoint = Trim(RunData<FieldPos + Offset>)
					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<RDS_LAYER_RDS_TEST_KEYS$>
						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<RDS_TEST_ZONE$>
									// 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<WO_MAT_QA_RECIPE$>
								WOStepNo    = RDSRec<RDS_WO_STEP_KEY$>[-1, 'B*']
								ProfStep    = ''
								ProfSteps   = ''
								ProfileCnt  = DCount(WOMatQARec<WO_MAT_QA_PROFILE$>, @VM)
								For vPos = 1 to ProfileCnt
									ProfSteps<1, vPos>  = WOMatQARec<WO_MAT_QA_PROFILE$, vPos> : '*' : WOMatQARec<WO_MAT_QA_STAGE$, vPos>
								Next vPos
								LastProfSig     = WOMatQARec<WO_MAT_QA_PROFILE$, ProfileCnt>
								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<WO_MAT_QA_RESULT$, Pos> = '' then
												WOMatQARec<WO_MAT_QA_RESULT$, Pos>     = Average
												WOMatQARec<WO_MAT_QA_MIN_RESULT$, Pos> = Oconv(Iconv(Min, 'MD3'), 'MD3')
												WOMatQARec<WO_MAT_QA_MAX_RESULT$, Pos> = Oconv(Iconv(Max, 'MD3'), 'MD3')
												SpecMin                                = WOMatQARec<WO_MAT_QA_MIN$, Pos>
												SpecMax                                = WOMatQARec<WO_MAT_QA_MAX$, Pos>
												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<WO_MAT_QA_OUT_OF_SPEC$, Pos> = True$
													end
													WOMatQARec<WO_MAT_QA_DATA_POINTS$, Pos, SubValuePos> = 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
	
end service


Service ImportHgCVData(RunData, FileName)
	
	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>
	PSN               = RunData<5>
	QualFile          = ( (PSN EQ 'Low') or (PSN EQ 'Mid') or (PSN EQ 'High') or (PSN EQ 'Thin') )
	If QualFile then
		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<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 else
		// Regular metrology file
		IsViewerFile     = Index(FileName, 'Viewer', 1)
		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<RDS_WO$>
			CassNo          = RDSRec<RDS_CASS_NO$>
			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<REACTOR_REACT_TYPE$>
				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<FieldPos>)
						HgCVDataPoint   = Trim(RunData<FieldPos + Offset>)
					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<RDS_LAYER_RDS_TEST_KEYS$>
						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<RDS_TEST_ZONE$>
									// Determine if this is Hg Concentration Resistivity.
									IsHgCvCRes      = RDSTestRec<RDS_TEST_SPEC_CRES_MTOOL$> _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<WO_MAT_QA_PROP$> using @VM setting vPos then
												Stage         = WOMatQARec<WO_MAT_QA_STAGE$, vPos>
												RequireUnload = False$
												IF (WOMatQARec<WO_MAT_QA_RESULT$, vPos, 1> EQ '' OR IsViewerFile) then
													PhaseDataPoints = ''
													Positions       = ''
													Position        = ''
													FieldPos        = 53
													Loop
														Position        = Trim(RunData<FieldPos>)
														PhaseDataPoint  = Trim(RunData<FieldPos + PhaseOffset>)
													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<WO_MAT_QA_RESULT$, vPos>           = HgCVAvg : @SVM : PhaseAvg
													WOMatQARec<WO_MAT_QA_MIN_RESULT$, vPos>       = HgCVMin : @SVM : PhaseMin
													WOMatQARec<WO_MAT_QA_MAX_RESULT$, vPos>       = HgCVMax : @SVM : PhaseMax
													WOMatQARec<WO_MAT_QA_RANGE_PCT_RESULT$, vPos> = HgCVRangePct : @SVM : PhaseRangePct
													WOMatQARec<WO_MAT_QA_EDGE_MEAN_RESULT$, vPos> = HgCVEdgeMean : @SVM : PhaseEdgeMean
													SpecMin                                       = WOMatQARec<WO_MAT_QA_MIN$, vPos>
													SpecMax                                       = WOMatQARec<WO_MAT_QA_MAX$, vPos>
													SpecPhaseMin                                  = WOMatQARec<WO_MAT_QA_PHASE_MIN$, vPos>
													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<WO_MAT_QA_PHASE_MIN$, vPos> = 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<WO_MAT_QA_OUT_OF_SPEC$, vPos> = True$
														end                                              
														DataRow = HgCVData : @TM : PhaseData
														WOMatQARec<WO_MAT_QA_DATA_POINTS$, vPos, Index> = 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<WO_MAT_QA_SIG$, vPos>     = ''
													WOMatQARec<WO_MAT_QA_SIG_DTM$, vPos> = ''
													Database_Services('WriteDataRow', 'WO_MAT_QA', WOMatQAID, WOMatQARec, True$, False$, True$)
													If Error_Services('NoError') then
														RDSTestRec<DTMIndex> = 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.
										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
	
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 RDSKeyID is not a number or not 6 digits, then ignore. This means it is an engineering run.
	If Num(RDSKeyID) AND (Len(RDSKeyID) EQ 6) 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<REACT_RUN_CI_STAGE$>
			CIKeyIDs        = ReactRunRec<REACT_RUN_CI_NO$>
			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<CLEAN_INSP_SCAN_RECIPE$>, @VM)
							ScanIndex       = NumRecipes + 1
							NumTools        = DCount(CleanInspRec<CLEAN_INSP_SCAN_TOOL$>, @VM)
							SpecRecipeList  = CleanInspRec<CLEAN_INSP_SPEC_SURFSCAN_RECIPE$>                       
							NumWfrs         = 0
							Locate ScanRecipe In SpecRecipeList Using @VM Setting RecipeIndex then
								// Recipe found in spec list                              
								SpecSampleQty = CleanInspRec<CLEAN_INSP_SPEC_SS_SAMP_QTY$, RecipeIndex>
								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<CLEAN_INSP_SCAN_SOD_PER_WAFER$, ScanIndex, WaferNo>  = SODVals<1,WaferNo>
											CleanInspRec<CLEAN_INSP_SCAN_SORT_PER_WAFER$, ScanIndex, WaferNo> = 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<CLEAN_INSP_SCAN_RECIPE_MISMATCH$> = ''
								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<CLEAN_INSP_SCAN_RECIPE_MISMATCH$> = 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<REACT_RUN_WO_NO$>;//WONo
					WOStep = ReactRunRec<REACT_RUN_WO_STEP$>;//WOStep
					CassNo = ReactRunRec<REACT_RUN_CASS_NO$>;//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<REACT_RUN_CI_STAGE$>
					Locate 'POST' in CIStages using @VM setting sPos then
						//Existing POST CI record found, fetch it.
						CINo = ReactRunRec<REACT_RUN_CI_NO$,sPos>
						CleanInspRec = Xlate('CLEAN_INSP', CINo,'','X')
						IF CleanInspRec<CLEAN_INSP_SCAN_SOD_PER_WAFER$> NE '' THEN
							//Append a value mark to the end of the field so that we can add another set of data
							CleanInspRec<CLEAN_INSP_SCAN_SOD_PER_WAFER$> = CleanInspRec<CLEAN_INSP_SCAN_SOD_PER_WAFER$> : @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<CLEAN_INSP_WO_NO$>			= WONo
						CleanInspRec<CLEAN_INSP_WO_STEP$>		= WOStep
						CleanInspRec<CLEAN_INSP_CASS_NO$>		= CassNo
						CleanInspRec<CLEAN_INSP_STAGE$>			= Stage
						CleanInspRec<CLEAN_INSP_RDS_NO$>		= 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<CLEAN_INSP_SCAN_RECIPE$>, @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<CLEAN_INSP_SCAN_SOD_PER_WAFER$, NumRecipes, WaferNo> = SODVals<1,WaferNo>
								CleanInspRec<CLEAN_INSP_SCAN_SORT_PER_WAFER$, NumRecipes, WaferNo> = 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
		If Num(RDSKeyID) AND (Len(RDSKeyID) GT 0) then
			ErrorMessage = 'Invalid RDS Key ID.'
			Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage)
		end
	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<RDS_WO$>
		CassNo          = RDSRec<RDS_CASS_NO$>
		WorkOrder       = Work_Order_Services('GetWorkOrder', WorkOrderNo, True$)
		GoSub ParseWorkOrder
		
		// If RDSKeyID is not a number or not 6 digits, then ignore. This means it is an engineering run.
		If Num(RDSKeyID) AND (Len(RDSKeyID) EQ 6) 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<PRS_STAGE_SURFSCAN_RECIPE$>
				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<PRS_STAGE_SURFSCAN_RECIPE$>
					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<REACT_RUN_CI_STAGE$>
				CIKeyIDs        = ReactRunRec<REACT_RUN_CI_NO$>
				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<CLEAN_INSP_SCAN_RECIPE$>, @VM)
							NumTools        = DCount(CleanInspRec<CLEAN_INSP_SCAN_TOOL$>, @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
			If Num(RDSKeyID) AND (Len(RDSKeyID) GT 0) then
				Error_Services('Add', 'Invalid RDS Key ID.')
				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 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<RDS_TEST_LS_ID$>
						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<RDS_TEST_SPEC_RES_MTOOL$> _EQC ToolClassID then ToolMatch = True$
							end
						Until ToolMatch
						Next RDSTestKeyID
						If ToolMatch then
							CalculatedLayer = RDSTestRow<RDS_TEST_LS_ID$>
						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<RDS_TEST_SPEC_RES_MTOOL$> _EQC ToolClassID) OR (RDSTestRow<RDS_TEST_SPEC_CRES_MTOOL$> _EQC ToolClassID) then ToolMatch = True$
							end
						Until ToolMatch
						Next RDSTestKeyID
						If ToolMatch then
							CalculatedLayer = RDSTestRow<RDS_TEST_LS_ID$>
						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


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>
	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<FieldPos>)
		DataPoint = Trim(RunData<FieldPos + Offset>)
	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
		for each wafer in Positions using @VM setting dPos
			WMORec<WM_OUT_MU_WAFER_THK_RESULT$, wafer> = DataPoints<0, dPos>
		Next wafer
		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<WO_MAT_QA_RECIPE$>
		ProfSteps   = ''
		Profiles    = WOMatQARec<WO_MAT_QA_PROFILE$>
		ProfileCnt  = DCount(WOMatQARec<WO_MAT_QA_PROFILE$>, @VM)
		Stages      = WOMatQARec<WO_MAT_QA_STAGE$>
		Slots       = WOMatQARec<WO_MAT_QA_SLOT$>
		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>
				Begin Case
					Case Slot EQ '1' 
						WaferIndex = 1
					Case Slot EQ 'L'
						WaferIndex = 25
					Case Slot EQ 'A'
						WaferIndex = 1
					Case Otherwise$
						WaferIndex = Slot	
				End Case
				Locate WaferIndex in Positions using @VM setting dPos then
					WOMatQARec<WO_MAT_QA_RESULT$, pPos> = 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 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://oi-metrology-viewer-prod.mes.infineon.com:4432/product-thick-and-res-health-reduced-web.html"
		URL = "https://messa020ec.infineon.com:4438/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, '</p>', 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, '<table', 1)
		DataTableEnd = Indexc(IQSResponse, '</table>', 1) + 7
		DataTable = IQSResponse[DataTableBegin, DataTableEnd]
		swap '<table' with @RM in DataTable
		swap '<tr>' with @FM in DataTable
		swap '<th' with @VM in DataTable
		swap '</td>' with '' in DataTable
		swap '</tr>' 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<i>, '<td nowrap align=left bgcolor="#0000FF">', 1) then
				FilteredData<-1> = DataTable<i>
			end
		Next i
		swap '<td nowrap align=left bgcolor="#0000FF">' 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<IQS_VIOL_DATA.REACTOR$, i>    = FilteredData<i, IQS_VIOL_DATA.REACTOR$>
			ParsedIQSData<IQS_VIOL_DATA.TEST$, i>       = FilteredData<i, IQS_VIOL_DATA.TEST$>
			ParsedIQSData<IQS_VIOL_DATA.USL$, i>        = FilteredData<i, IQS_VIOL_DATA.USL$>
			ParsedIQSData<IQS_VIOL_DATA.TAR$, i>        = FilteredData<i, IQS_VIOL_DATA.TAR$>
			ParsedIQSData<IQS_VIOL_DATA.LSL$, i>        = FilteredData<i, IQS_VIOL_DATA.LSL$>
			ParsedIQSData<IQS_VIOL_DATA.LAST_ENTRY$, i> = FilteredData<i, IQS_VIOL_DATA.LAST_ENTRY$>
			ParsedIQSData<IQS_VIOL_DATA.LAST_ALARM$, i> = FilteredData<i, IQS_VIOL_DATA.LAST_ALARM$>
			swap '&gt;' with '>' in FilteredData
			swap '&lt;' with '<' in FilteredData
			ParsedIQSData<IQS_VIOL_DATA.ALARM$, i>      = FilteredData<i, IQS_VIOL_DATA.ALARM$>
			ParsedIQSData<IQS_VIOL_DATA.SUBGROUPS$, i>  = FilteredData<i, IQS_VIOL_DATA.SUBGROUPS$>
			ParsedIQSData<IQS_VIOL_DATA.MEAN$, i>       = FilteredData<i, IQS_VIOL_DATA.MEAN$>
		Next i
		LastWriteTime                                 = IConv(PublishDate, 'DT')
		ParsedIQSData<IQS_VIOL_DATA.LAST_WRITE_TIME$> = 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<RDS_TEST_RDS_NO$>
				If RdsNo EQ RdsTestRdsNo then
					RdsTestRecLayer = RdsTestRec<RDS_TEST_LS_ID$>
					If RecipeLayer EQ RdsTestRecLayer then
						Continue = True$
						If Zone NE '' then
							RdsTestRecZone = RdsTestRec<RDS_TEST_ZONE$>
							If RdsTestRecZone NE Zone then
								Continue = False$
							end
						end
						If Continue then
							SpecThickMrecipe = RdsTestRec<RDS_TEST_SPEC_THICK_MRECIPE$>
							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<RDS_TEST_RDS_NO$>
				If RdsNo EQ RdsTestRdsNo then
					RdsTestRecLayer = RdsTestRec<RDS_TEST_LS_ID$>
					If RecipeLayer EQ RdsTestRecLayer then
						Continue = True$
						If Zone NE '' then
							RdsTestRecZone = RdsTestRec<RDS_TEST_ZONE$>
							If RdsTestRecZone NE Zone then
								Continue = False$
							end
						end
						If Continue then
							SpecPatternName = RdsTestRec<RDS_TEST_SPEC_THICK_MPATTERN$>
							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<RDS_TEST_RDS_NO$>
		LSId			= MetRec<RDS_TEST_LS_ID$>
		Zone			= MetRec<RDS_TEST_ZONE$>
		TestPointMap	= MetRec<RDS_TEST_TEST_POINT_MAP$>
		
		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<ORP$THICK_READS,J> = OCONV(ICONV(EpiReads<EPI_READS$THICKNESS,J> * L1Ratio, 'MD2'),'MD2')
					oRTParms_L2<ORP$THICK_READS,J> = EpiReads<EPI_READS$THICKNESS,J> - oRTParms_L1<ORP$THICK_READS,J>
					
					IF EpiReads<EPI_READS$SHEET_RHO,J> NE '' THEN
						
						oRTParms_L1<ORP$SHEET_RHO_READS,J>	= EpiReads<EPI_READS$SHEET_RHO,J>
						oRTParms_L2<ORP$SHEET_RHO_READS,J>	= ''
						EpiReads<EPI_READS$SHEET_RHO,J>		= ''
						
					END ELSE
						oRTParms_L1<ORP$SHEET_RHO_READS,J> = ''
						oRTParms_L2<ORP$SHEET_RHO_READS,J> = ''
					END
					
					IF EpiReads<EPI_READS$HGCV1,J> NE '' THEN
						oRTParms_L1<ORP$HGCV1_READS,J>	= EpiReads<EPI_READS$HGCV1,J>
					END
					
					IF EpiReads<EPI_READS$HGCV2,J> NE '' THEN
						oRTParms_L2<ORP$HGCV1_READS,J>	= EpiReads<EPI_READS$HGCV2,J>
					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<ORP$THICK_READS>		= EpiReads<EPI_READS$THICKNESS>
			oRTParms<ORP$SHEET_RHO_READS>	= EpiReads<EPI_READS$SHEET_RHO>
			oRTParms<ORP$HGCV1_READS>		= EpiReads<EPI_READS$HGCV1>
			
			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<DataIndex>
		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<RDS_TEST_THICK_AVG$>             = Stats<1>
					RDSTestRec<RDS_TEST_THICK_STDV$>            = Stats<2>
					RDSTestRec<RDS_TEST_THICK_RANGE$>           = Stats<6>
					RDSTestRec<RDS_TEST_THICK_RANGE_PCNT$>      = Stats<7>
					RDSTestRec<RDS_TEST_THICK_UNIF$>            = Stats<3>
					RDSTestRec<RDS_TEST_THICK_MIN$>             = Stats<4>
					RDSTestRec<RDS_TEST_THICK_MAX$>             = Stats<5>
				Case Machine _EQC 'CDE'
					RDSTestRec<RDS_TEST_SHEETRHO_AVG$>          = Stats<1>
					RDSTestRec<RDS_TEST_SHEETRHO_STDV$>         = Stats<2>
					RDSTestRec<RDS_TEST_SHEETRHO_RANGE$>        = Stats<6>
					RDSTestRec<RDS_TEST_SHEETRHO_RANGE_PCNT$>   = Stats<7>
					RDSTestRec<RDS_TEST_SHEETRHO_UNIF$>         = Stats<3>
					RDSTestRec<RDS_TEST_SHEETRHO_MIN$>          = Stats<4>
					RDSTestRec<RDS_TEST_SHEETRHO_MAX$>          = Stats<5>
				Case Machine _EQC 'HgCV'
					RDSTestRec<RDS_TEST_HGCV1_RES_AVG$>         = Stats<1>
					RDSTestRec<RDS_TEST_HGCV1_RES_STDV$>        = Stats<2>
					RDSTestRec<RDS_TEST_HGCV1_RES_RANGE$>       = Stats<6>
					RDSTestRec<RDS_TEST_HGCV1_RES_RANGE_PCNT$>  = Stats<7>
					RDSTestRec<RDS_TEST_HGCV1_RES_UNIF$>        = Stats<3>
					RDSTestRec<RDS_TEST_HGCV1_RES_MIN$>         = Stats<4>
					RDSTestRec<RDS_TEST_HGCV1_RES_MAX$>         = 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<RDS_TEST_RES_AVG$>           = Stats<1>
				RDSTestRec<RDS_TEST_RES_STDV$>          = Stats<2>
				RDSTestRec<RDS_TEST_RES_RANGE$>         = Stats<6>
				RDSTestRec<RDS_TEST_RES_RANGE_PCNT$>    = Stats<7>
				RDSTestRec<RDS_TEST_RES_UNIF$>          = Stats<3>
				RDSTestRec<RDS_TEST_RES_MIN$>           = Stats<4>
				RDSTestRec<RDS_TEST_RES_MAX$>           = 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<RDS_TEST_TEST_POINT_MAP$>
	Convert @Lower_Case to @Upper_Case in BaseMapID
	TestPointMapRec = Database_Services('ReadDataRow', 'TEST_POINT_MAP', BaseMapID)
	If Error_Services('NoError') then
		BaseMapResults = TestPointMapRec<TEST_POINT_MAP_RESULT_NO$>
		BaseMapPoints  = TestPointMapRec<TEST_POINT_MAP_POINT_ID$>
	end else
		Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
	end
	
	// Read the metrology tool map
	PatternName = Trim(RDSTestRec<PatternNameIndex>)
	Convert @Lower_Case to @Upper_Case in PatternName
	ToolMapID   = RDSTestRec<ToolClassIndex>
	Convert @Lower_Case to @Upper_Case in ToolMapID
	TestPointMapRec = Database_Services('ReadDataRow', 'TEST_POINT_MAP', ToolMapID)
	If Error_Services('NoError') then
		ToolMapResults = TestPointMapRec<TEST_POINT_MAP_RESULT_NO$>
		ToolMapPoints  = TestPointMapRec<TEST_POINT_MAP_POINT_ID$>
	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<TOOL_CLASS_PATTERN$>
		Convert @Lower_Case to @Upper_Case in PatternNames
		Locate PatternName in PatternNames using @VM setting fPos then
			PatternSize     = ToolClassRow<TOOL_CLASS_PATTERN_SIZE$,fpos>
			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<DataIndex> = ''
				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<DataIndex, Position + Offset> NE '' then
						ExistingData = true$
					end
					RDSTestRec<DataIndex, Position + Offset> = DataPoint
				Next ReadingCnt
				RDSTestRec<DTMIndex> = 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

