Function Metrology_Services(@Service, @Params)
/***********************************************************************************************************************

    This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
    permission from SRP Computer Solutions, Inc.

    Name        :   Metrology_Services

    Description :   Handler program for all module related services.

    Notes       :   The generic parameters should contain all the necessary information to process the services. Often
                    this will be information like the data Record and Key ID.

    Parameters  :
        Service         [in] -- Name of the service being requested
        Param1-10   [in/out] -- Additional request parameter holders
        Response       [out] -- Response to be sent back to the Controller (MCP) or requesting procedure

    Metadata    :
        UID000  -   'Success'
        UID001  -   Failure. See the description in the log for further details.

    History     :   (Date, Initials, Notes)
        09/04/16    dmb     Original programmer. - [EPIOI-8]
        01/03/18    dmb     Fix logic for Tencor and SP1 imports to correctly identify the value index for imported
                            data.
        02/23/18    dmb     Add support for the special 14 point thickness reading.
        03/16/18    dmb     Change logic to perform recipe matching for SP1.
        04/26/18    djs     Added logic within ImportTencorData to ensure Tencor recipe matches SurfScan list.
        06/06/18    djs     Added logic within LoadRunDataToDatabase to store the 14 point QA THICK_ONLY data points.
                            Previously only the average of these points was being stored. Also added logic within
                            ImportHgCVData to store the HgCV QA data points.
        06/08/18    djs     Added logic within LoadRunDataToDatabase to store the Min and Max of the data points.
        06/12/18    djs     Added logic within ImportedHgCVData to store the HgCV phase angle data points 
        06/19/18    djs     Added logic within ImportHgCVData to store additional QA Metrology Data.
                            Previously only the average of the imported data points was being stored.
                            Now, in addition to the average, the min, max, edge mean delta, and range percent
                            of the QA data points are being calculated and stored in the WO_MAT_QA table.
                            Finally, the data points themselves are also now being stored in the WO_MAT_QA table.
        07/23/18    djs     Added logic with the ImportBioRadData service to differentiate 6 inch THICK_ONLY QA
                            metrology tests from traditional RDS tests. Also added code to store this data within the
                            WO_MAT_QA database table.
        09/18/18    djs     Added code within the ImportTencorData service to post wafer image pdfs to a staging table
                            where they will be stored locally on the App server.
        09/11/19    fdr     Add new service "ImportStratusData"
        12/05/24	djs		Updated GetIQSViolations service to store reactor violations in the respective reactor record.

***********************************************************************************************************************/

#pragma precomp SRP_PreCompiler

$insert LOGICAL
$insert SERVICE_SETUP
$insert RDS_EQUATES
$insert PROD_SPEC_EQUATES
$insert RDS_LAYER_EQUATES
$insert PRS_LAYER_EQUATES
$insert RDS_TEST_EQUATES
$insert TOOL_CLASS_EQUATES
$insert TEST_POINT_MAP_EQUATES
$insert CLEAN_INSP_EQUATES
$insert REACT_RUN_EQUATES
$insert REACTOR_EQUATES
$insert WO_MAT_QA_EQUATES
$insert PRS_STAGE_EQUATES
$insert SRPMail_Inserts
$insert WO_MAT_EQUATES
$Insert NOTIFICATION_EQUATES
$Insert RLIST_EQUATES
$Insert WM_OUT_EQUATES
$Insert IQS_VIOL_DATA_EQUATES

Common /MetrologyServices/ MachineType@, RDSNo@

Equ RETRY_ATTEMPTS$      TO 3
Equ MINUTES_UNTIL_RETRY$ TO 3

Equ ORP$THICK_READS		    TO 1
Equ ORP$SHEET_RHO_READS	    TO 2
Equ ORP$HGCV1_READS		    TO 3

Equ EPI_READS$READ_NO		TO 1
Equ EPI_READS$THICKNESS		TO 2
Equ EPI_READS$SHEET_RHO		TO 3
Equ EPI_READS$HGCV1			TO 4
Equ EPI_READS$HGCV2			TO 5

Equ Tab$                    to \09\
Equ CRLF$                   to \0D0A\
Equ LF$                     to \0A\
Equ Comma$                  to ','

Declare subroutine  SRP_Stopwatch, Error_Services, obj_Tables, Metrology_Services, obj_RDS_Test, SRP_JSON
Declare subroutine  RTI_Set_Debugger, Database_Services, Btree.Extract, Set_Status, QA_Services, obj_Notes
Declare subroutine  Logging_Services, SRP_Send_Mail, SRP_Run_Command, PM_Services, Httpclient_Services
Declare subroutine  Tool_Services, Mona_Services, Reactor_Services
Declare function    SRP_Sort_Array, Metrology_Services, obj_RDS_Test, obj_Test_Point_Map, Database_Services, UCase
Declare function    Work_Order_Services, SRP_JSON, Logging_Services, Environment_Services, SRP_Trim, Min, Max
Declare function    QA_Services, SRP_Join_Arrays, Get_Status, Obj_Clean_Insp, Datetime, SRP_Datetime
Declare function    Httpclient_Services, PM_Services, Signature_Services, SRP_Array, Math_Services
Declare function	Tool_Class_Services, obj_wo_mat
Declare function	SRP_String

LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\Metrology'
LogDate     = Oconv(Date(), 'D4/')
LogTime     = Oconv(Time(), 'MTS')
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Metrology Log.csv'
Headers     = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objLog      = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogFileName  = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Tencor Metrology Log.csv'
Headers      = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objTencorLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' HgCV Metrology Log.csv'
Headers     = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objHgCVLog  = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' CDE Metrology Log.csv'
Headers     = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objCDELog   = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogFileName  = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Biorad Metrology Log.csv'
Headers      = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objBioradLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogFileName   = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Stratus Metrology Log.csv'
Headers       = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objStratusLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' SP1 Metrology Log.csv'
Headers     = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objSP1Log   = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' SPV Metrology Log.csv'
Headers     = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objSPVLog   = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' SRP Metrology Log.csv'
Headers     = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Type' : @FM : 'UID' : @FM : 'Notes'
objSRPLog   = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Metrology Performance Log.csv'
Headers     = 'Logging DTM' : @FM : 'Machine Type' : @FM : 'RDS Key ID': @FM : 'Notes'
objPerfLog  = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\SOD'
LogFileName   = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' SOD Performance Log.csv'
Headers       = 'Logging DTM' : @FM : 'Machine Type' : @FM : 'RDS Key ID': @FM : 'Notes'
objSODPerfLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LogPath        = Environment_Services('GetApplicationRootPath') : '\LogFiles\POST'
LogFileName    = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' POST Performance Log.csv'
Headers        = 'Logging DTM' : @FM : 'Machine Type' : @FM : 'RDS Key ID': @FM : 'Notes'
objPOSTPerfLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)

LoggingDTM  = LogDate : ' ' : LogTime   ; // Logging DTM

GoToService else
	Error_Services('Set', Service : ' is not a valid service request within the ' : ServiceModule : ' services module.')
end

Return Response else ''


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Services
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


//----------------------------------------------------------------------------------------------------------------------
// LaunchMetrologyViewer
//
// Launches the metrology viewer URL with the client's default browser.
//----------------------------------------------------------------------------------------------------------------------
Service LaunchMetrologyViewer

	MetrologyURL = Environment_Services('GetMetrologyViewerURL')
	Command      = 'start ':MetrologyURL
	SRP_Run_Command(Command)

end service


Service GetJsonFromProcessDataStandardFormat(Text)
	json = '';
	HeaderId = -1;
	SubgroupId = -1;
	FoundEndOfFile = False$;
	SWAP '|' WITH @VM IN Text;
	SWAP CRLF$ WITH @FM IN Text;
	LineCount = DCOUNT(Text, @FM);
	FOR LineLoopIndex = 1 TO LineCount
		Line = Text<LineLoopIndex>;
		IF FoundEndOfFile EQ True$ THEN
			json = json:Line;
		END
		IF Line EQ 'EOF' THEN
			FoundEndOfFile = True$;
			Line = Text<LineLoopIndex - 1>;
			Convert Tab$ to @FM IN Line
			SWAP '=' WITH @VM IN Line;
			SWAP ';' WITH @FM IN Line;
			IF Line<2, 1> EQ 'B_HeaderId' THEN
				HeaderId = Line<2, 2>
			END
			IF Line<3, 1> EQ 'B_SubgroupId' THEN
				SubgroupId = Line<3, 2>
			END
		END
	NEXT LineLoopIndex
    Response = json;
end service


Service GetStratus(Handle)
	Result = ''; // Service ImportStratusData(
	Offset = 1
	FieldPosition = 13
	FieldPositionIncrement = 2
	Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count');
	Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence');
	Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId');
	Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId');
	FOR RecordIndex = 1 TO Result<1, 1>
		IF RecordIndex EQ 1 THEN
			Result<2> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DateTime'); // Timestamp
			Result<3> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MesEntity'); // Tool
			Result<4> = 'FQA Thickness'; // DataType
			Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Employee'); // Operator
			Result<6> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Recipe'); // Recipe
			Result<7> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // Reactor
			Result<8> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSNo
			Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].PSN'); // PSN
			Result<10> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Batch'); // BatchID
			Result<11> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Cassette'); // Cassette
			Result<12> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].GradeMean'); // ThickAvg
		END
		ForOffset = (RecordIndex - 1) * FieldPositionIncrement;
		Result<FieldPosition + ForOffset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Slot'); // Position
		Result<FieldPosition + ForOffset + Offset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Mean'); // DataPoint
	NEXT RecordIndex
    Response = Result;
end service


Service GetBioRad(Handle)
	Result = ''; // Service ImportBioRadData(
	Offset = 1
	FieldPosition = 13
	FieldPositionIncrement = 2
	Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count');
	Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence');
	Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId');
	Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId');
	FOR RecordIndex = 1 TO Result<1, 1>
		IF RecordIndex EQ 1 THEN
			Result<2> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DateTime'); // TimeStamp
			Result<4> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Recipe'); // ScanRecipe
			Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // ReactorID
			Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // ToolID
			Result<6> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID
			Result<7> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].PSN'); // PSN
			Result<8> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Layer'); // RunDataLayer
			Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Zone'); // RunDataZone
			Result<11> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Wafer'); // DataSlotId
		END
		ForOffset = (RecordIndex - 1) * FieldPositionIncrement;
		Result<FieldPosition + ForOffset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Position'); // Position
		Result<FieldPosition + ForOffset + Offset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Thickness'); // DataPoint
	NEXT RecordIndex
    Response = Result;
end service


Service GetCDE(Handle)
	Result = ''; // Service ImportCDEData(
	Offset = 3
	FieldPosition = 23
	FieldPositionIncrement = 5
	Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count');
	Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence');
	Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId');
	Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId');
	FOR RecordIndex = 1 TO Result<1, 1>
		IF RecordIndex EQ 1 THEN
			Result<3> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Date'); // Timestamp
			Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RecipeName'); // ScanRecip
			Result<6> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // ReactorID
			Result<7> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID
			Result<8> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].PSN'); // PSN
			Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Layer'); // RunDataLayer
			Result<10> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Zone'); // RunDataZo
		END
		ForOffset = (RecordIndex - 1) * FieldPositionIncrement;
		Result<FieldPosition + ForOffset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].InferredPoint'); // Position
		Result<FieldPosition + ForOffset + Offset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Data'); // DataPoint
		Result<FieldPosition + ForOffset + FieldPositionIncrement - 1> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MeritGOF'); // N/A
	NEXT RecordIndex
    Response = Result;
end service


Service GetHgCV(Handle)
	Result = ''; // Service ImportHgCVData(
	Offset = 5
	PhaseOffset = 7
	FieldPosition = 53
	FieldPositionIncrement = 9
	Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count');
	Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence');
	Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId');
	Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId');
	FOR RecordIndex = 1 TO Result<1, 1>
		IF RecordIndex EQ 1 THEN
			Result<2> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MesEntity'); // ToolID
			Result<3> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Reactor'); // ReactorID
			Result<4> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID
			Result<5> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].PSN'); // PSN
			Result<8> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Wafer'); // LayerZonePair
			Result<11> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Date'); // Timestamp
		END
		ForOffset = (RecordIndex - 1) * FieldPositionIncrement;
		Result<FieldPosition + ForOffset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Site'); // Position
		Result<FieldPosition + ForOffset + Offset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RhoAvg'); // HgCVDataPoint
		Result<FieldPosition + ForOffset + PhaseOffset> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Phase'); // PhaseDataPoint
		Result<FieldPosition + ForOffset + FieldPositionIncrement - 1> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Grade'); // N/A
	NEXT RecordIndex
    Response = Result;
end service


Service GetTencor(Handle)
	Result = ''; // Service ImportTencorData(
	Result<1, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count');
	Result<1, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence');
	Result<1, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId');
	Result<1, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId');
	FOR RecordIndex = 1 TO Result<1, 1>
		IF RecordIndex EQ 1 THEN
			Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Date'); // Timestamp
			Result<10> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].HazeAverageAvg'); // HazeAvg
			Result<28> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID
			Result<30> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].WaferRecipe'); // ScanRecipe
			Result<39> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].SumOfDefectsAvg'); // SoDAvg
			Result<40> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].SumOfDefectsMax'); // SoDMax
			Result<41> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].SumOfDefectsMin'); // SoDMin
			Result<43> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MesEntity'); // ScanTool
		END
	NEXT RecordIndex
    Response = Result;
end service


Service GetSP1(Handle)
	Result = ''; // Service ImportSP1Data(
	Result<5, 1> = SRP_JSON(Handle, 'GETVALUE', 'Count');
	Result<5, 2> = SRP_JSON(Handle, 'GETVALUE', 'Sequence');
	Result<5, 3> = SRP_JSON(Handle, 'GETVALUE', 'HeaderId');
	Result<5, 4> = SRP_JSON(Handle, 'GETVALUE', 'SubgroupId');
	FOR RecordIndex = 1 TO Result<5, 1>
		IF RecordIndex EQ 1 THEN
			Result<9> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Date'); // Timestamp
			Result<10> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnHazeAvgMean'); // HazeAvg
			Result<28> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].RDS'); // RDSKeyID
			Result<30> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].Session'); // ScanRecipe
			IsMisfit = IndexC(Result<30>, 'MISFIT', 1)
			IF IsMisfit THEN
				Result<3> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMean'); // SoDAvg
				Result<2> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMax'); // SoDMax
				Result<1> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMin'); // SoDMin
			END ELSE
				Result<39> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMean'); // SoDAvg
				Result<40> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMax'); // SoDMax
				Result<41> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAllMin'); // SoDMin
			END
			Result<43> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].MesEntity'); // ScanTool
			Result<44> = SRP_JSON(Handle, 'GETVALUE', 'Records[':RecordIndex:'].DcnAreaMean'); // DCNMM2
		END
	NEXT RecordIndex
    Response = Result;
end service


//----------------------------------------------------------------------------------------------------------------------
// ImportMetrologyFiles
//
// Looks for available Metrology files that are ready to be imported into the MES system.
//----------------------------------------------------------------------------------------------------------------------
Service ImportMetrologyFiles(Machine)
	
	If Machine NE '' then
		hSysLists       = Database_Services('GetTableHandle', 'SYSLISTS')
		Lock hSysLists, ServiceKeyID:'*':Machine then

			********************************
			* Verify Metrology data folder *
			********************************

			Begin Case
				Case Machine _EQC 'Tencor'
					SearchPattern = '*.pdsf';
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08DDUPSFS6420\Source\MET08DDUPSFS6420\'
				Case Machine _EQC 'HgCV'
					SearchPattern = '*.pdsf';
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESIHGCV\Source\MET08RESIHGCV\'
				Case Machine _EQC 'CDE'
					SearchPattern = '*.pdsf';
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESIMAPCDE\Source\MET08RESIMAPCDE\'
				Case Machine _EQC 'Biorad'
					SearchPattern = '*.txt';
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08THFTIRQS408M\Source\MET08THFTIRQS408M\'
				Case Machine _EQC 'Stratus'
					SearchPattern = '*.txt';
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08THFTIRSTRATUS\Source\MET08THFTIRSTRATUS\'
				Case Machine _EQC 'SP1'
					SearchPattern = '*.txt';
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08DDUPSP1TBI\Source\MET08DDUPSP1TBI\'
				Case Machine _EQC 'SPV'
					SearchPattern = '*.txt';
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08RESISRP2100\Source\MET08RESISRP2100\'
				Case Machine _EQC 'SRP'
					SearchPattern = '*.txt';
					DataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\MET08ANLYSDIFAAST230\Source\MET08ANLYSDIFAAST230\'
				Case Otherwise$
					SearchPattern = '*.txt';
					Error_Services('Add', 'Error in ':Service:' service. Unsupported Machine "':Machine:'" passed into service')
			End Case

			If Error_Services('NoError') then

				InitDir DataPath:SearchPattern
				FileList = DirList()
				FileNames = ''
				LOOP
					FileName = FileList[1,@FM]
					FileList[1,Col2()] = ""

					LOCATE FileName IN FileNames BY 'AR' USING @FM SETTING Pos ELSE
						FileNames = INSERT(FileNames,Pos,0,0,FileName)
					END
				UNTIL FileList = ""
				REPEAT
				IF FileNames[-1,1] = @FM THEN
					FileNames[-1,1] = ''
				END

				FileCnt = COUNT(FileNames,@FM) + (FileNames NE '')
				FOR FileLoopIndex = 1 TO FileCnt

					***********************
					* Read metrology file *
					***********************
					ProcessNow          = True$
					FileName            = FileNames<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()

						Metrology_Services('LogResults', '', Machine, 'UID000', 'Read : ' : FileName : ', Size : ' : FileSize)

						// Copy Run Data files to repository for troubleshooting purposes
						Begin Case
							Case Machine _EQC 'Tencor'
								RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08DDUPSFS6420\Source\MET08DDUPSFS6420\'
							Case Machine _EQC 'HgCV'
								RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08RESIHGCV\Source\MET08RESIHGCV\'
							Case Machine _EQC 'CDE'
								RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08RESIMAPCDE\Source\MET08RESIMAPCDE\'
							Case Machine _EQC 'Biorad'
								RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08THFTIRQS408M\Source\MET08THFTIRQS408M\'
							Case Machine _EQC 'Stratus'
								RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08THFTIRSTRATUS\Source\MET08THFTIRSTRATUS\'
							Case Machine _EQC 'SP1'
								RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08DDUPSP1TBI\Source\MET08DDUPSP1TBI\'
							Case Machine _EQC 'SPV'
								RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08RESISRP2100\Source\MET08RESISRP2100\'
							Case Machine _EQC 'SRP'
								RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\MET08ANLYSDIFAAST230\Source\MET08ANLYSDIFAAST230\'                                    
						End Case

						IF SearchPattern = '*.pdsf' THEN
							OSREAD Text FROM DataPath:FileName THEN
								json = Metrology_Services('GetJsonFromProcessDataStandardFormat', Text);
							END ELSE
								json = '';
							END
							IF LEN(json) GT 0 THEN
								RunData = Metrology_Services('GetRunData', Machine, json);
							END ELSE
								Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to get PDSF json');
							END
						END ELSE

							OSREAD RunData FROM DataPath:FileName THEN
								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
								Convert Tab$ to @FM in RunData
							END else
								Metrology_Services('LogResults', '', Machine, 'UID001', 'Read : ' : FileName : ', Size : ' : FileSize)
							end
						END

						*************************
						* Import metrology data *
						*************************

						IF RunData NE '' then
							Metrology_Services('ImportMetrologyRunData', Machine, DataPath, FileName, RunData)
						END ELSE
							Error_Services('Add', 'RunData argument was missing')
							Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage'))
						END

						If Error_Services('NoError') then
							Continue    = True$
						end else
							ErrorMessage = Error_Services('GetMessage')
							FQAError     = IndexC(ErrorMessage, 'FQA has already been signed', 1)
							If ( Index(ErrorMessage, 'UID002', 1) and (ImportAttempts LE RETRY_ATTEMPTS$) and Not(FQAError) ) then
								Continue    = False$
							end else
								Continue    = True$
							end
						end

						******************************
						* Delete metrology data file *
						******************************
						If (Continue) then
							Status()            = 0
							OSDELETE DataPath:FileName	;* Deletes local copy of data file
							ImportFileList      = Delete(ImportFileList, FilePos, 0, 0)
							ImportAttemptCounts = Delete(ImportAttemptCounts, FilePos, 0, 0)
							ImportAttemptDTMs   = Delete(ImportAttemptDTMs, FilePos, 0, 0)
							ErrCode             = Status()
							If ErrCode EQ 0 then
								Metrology_Services('LogResults', '', Machine, 'UID000', 'Delete : ' : FileName : ', Size : ' : FileSize)
							end else
								Metrology_Services('LogResults', '', Machine, 'UID001', 'Delete : ' : FileName : ', Size : ' : FileSize : ', Error : ' : ErrCode)
							end
						END else

							ErrorMessage    = Error_Services('GetMessage')
							MetrologyLog    = Database_Services('ReadDataRow', 'SYSLISTS', UCase(Machine):'_METROLOGY_LOG')
							MetrologyLog   := ErrorMessage : @FM
							Database_Services('WriteDataRow', 'SYSLISTS', UCase(Machine):'_METROLOGY_LOG', MetrologyLog, True$)
						end

						Database_Services('WriteDataRow', 'APP_INFO', UCase(Machine):'_FILE_LIST', ImportFileList)
						Database_Services('WriteDataRow', 'APP_INFO', UCase(Machine):'_ATTEMPT_COUNTS', ImportAttemptCounts)
						Database_Services('WriteDataRow', 'APP_INFO', UCase(Machine):'_ATTEMPT_DTMS', ImportAttemptDTMs)

					end

				NEXT FileLoopIndex

			end else
				Metrology_Services('LogResults', '', '', 'UID001', Error_Services('GetMessage'))
			end
			Unlock hSysLists, ServiceKeyID:'*':Machine else Null
		end
	end else
		Metrology_Services('LogResults', '', '', 'UID001', 'Null Machine passed into service')
	end

end service


Service GetRunData(Machine, json)

	Result = '';
	Handle = '';
	If Assigned(Handle) else Handle = 0
	If Handle LE 0 then
		ParseError = SRP_JSON(Handle, 'PARSE', json);
		IF ParseError EQ '' THEN
			Begin Case
				Case Machine _EQC 'Stratus'
					Result = Metrology_Services('GetStratus', Handle);

				Case Machine _EQC 'Biorad'
					Result = Metrology_Services('GetBiorad', Handle);

				Case Machine _EQC 'CDE'
					Result = Metrology_Services('GetCDE', Handle);

				Case Machine _EQC 'HgCV'
					Result = Metrology_Services('GetHgCV', Handle);

				Case Machine _EQC 'SP1'
					Result = Metrology_Services('GetSP1', Handle);

				Case Machine _EQC 'Tencor'
					Result = Metrology_Services('GetTencor', Handle);

			End Case
			SRP_JSON(Handle, 'RELEASE');
		END ELSE
			Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to parse PDSF json');
		END
	END
    Response = Result;
end service


//----------------------------------------------------------------------------------------------------------------------
// ImportMetrologyRunData
//
// RunData. - [Required]
//
// Imports the metrology run data into the RDS_TEST database table. Returns True$ if successful and False$ if
// unsuccessful.
//----------------------------------------------------------------------------------------------------------------------
Service ImportMetrologyRunData(Machine, DataPath, FileName, RunData)

		// Scan the run data for machine specific information. Then call the relevant update service for the specific
		// machine.
		ResourceID      = Field(FileName, ' ', 1, 1)
		IsViewerFile    = Index(FileName, 'Viewer', 1)
		Begin Case
			Case Machine _EQC 'Stratus'
				PSN = RunData<9>
				Metrology_Services('ImportStratusData', RunData, ResourceID, PSN)
				MachineType@ = 'Stratus'

			Case Machine _EQC 'Biorad'
				PSN = RunData<7>
				QualFile = ( (PSN EQ 'T-Low') or (PSN EQ 'T-Mid') or (PSN EQ 'T-High') or (PSN EQ 'T_LOW') or (PSN EQ 'T_MID') or (PSN EQ 'T_HIGH') )
				IF QualFile THEN
					Metrology_Services('ImportBioRadQualData', RunData, ResourceID, PSN)
				END ELSE
					Metrology_Services('ImportBioRadData', RunData, ResourceID, IsViewerFile, PSN, FileName)
				END
				MachineType@ = 'Bio-Rad'

			Case Machine _EQC 'CDE'
				PSN = RunData<8>
				QualFile = ( (PSN EQ 'RLOW_STD') or (PSN EQ 'RMID_STD') or (PSN EQ 'RHI_STD') or (PSN EQ 'THINSPC') )
				IF QualFile THEN
					Metrology_Services('ImportCDEQualData', RunData, ResourceID, PSN)
				END ELSE
					Metrology_Services('ImportCDEData', RunData, ResourceID, IsViewerFile, PSN)
				END
				MachineType@ = 'CDE'

			Case Machine _EQC 'HgCV'
				PSN = RunData<5>
				QualFile = ( (PSN EQ 'Low') or (PSN EQ 'Mid') or (PSN EQ 'High') or (PSN EQ 'Thin') )
				IF QualFile THEN
					Metrology_Services('ImportHgCVQualData', RunData, ResourceID, PSN)
				END ELSE
					Metrology_Services('ImportHgCVData', RunData, ResourceID, IsViewerFile, PSN)
				END
				MachineType@ = 'HgProbe'

			Case Machine _EQC 'SP1'
				Metrology_Services('ImportSP1Data', RunData)
				MachineType@ = 'SP1'

			Case Machine _EQC 'Tencor'
				Metrology_Services('ImportTencorData', RunData)
				MachineType@ = 'TENCOR'

			Case Machine _EQC 'SRP'
				Error_Services('Add', 'SRP data import is currently disabled.')
				Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage'))
				MachineType@ = 'SRP'

			Case Machine _EQC 'SPV'
				Error_Services('Add', 'SPV data import is currently disabled.')
				Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage'))
				MachineType@ = 'SPV'

			Case Otherwise$
				Error_Services('Add', 'Unrecognized machine used to create metrology run data')
				Metrology_Services('LogResults', '', '', 'UID001', Service : ' : ' : Error_Services('GetMessage'))
				MachineType@ = 'Unrecognized'

	End Case

end service


Service ImportStratusData(RunData, ResourceID, PSN)

	Machine           = 'Stratus'
	ParseArray        = ''
	FieldPos          = 13
	FieldPosIncrement = 2
	Offset            = 1
	Decimals          = 2
	Timestamp         = RunData<2>
	Tool              = RunData<3>
	DataType          = RunData<4>
	Operator          = RunData<5>
	Recipe            = RunData<6>
	Reactor           = RunData<7>
	RDSNo             = RunData<8>  ; // If Non-EpiPro this will be an RDS Key, otherwise it will be a work order key.
	BatchID           = RunData<10>
	Cassette          = RunData<11>
	ThickAvg          = RunData<12>
	Positions         = ''
	DataPoints        = ''

	Loop
		Position  = Trim(RunData<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        = ''
			WOMatSlotProfile = obj_WO_Mat('SlotWaferIDs',WOMatKey:@RM:WOMatRec)
			For each Profile in Profiles using @VM setting pPos
				Stage = Stages<0, pPos>
				If ( (Profile EQ '1ADE') and ( (Stage EQ 'QA') or (Stage EQ 'MO_QA') ) ) then
					Slot = Slots<0, pPos>
					If ( (Slot EQ 'A') or (SpecQty EQ 'A') or (SpecQty GT 5) ) then
						MetMin                                  = WOMatQARec<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' 
							For WaferIndex = 1 to 25
							    SlotIsEmpty = WOMatSlotProfile<1, WaferIndex> EQ ''
							Until SlotIsEmpty EQ False$    
							Next WaferIndex
						Case Slot EQ 'L'
						    For WaferIndex = 25 to 1 Step -1
							    SlotIsEmpty = WOMatSlotProfile<1, WaferIndex> EQ ''
							Until SlotIsEmpty EQ False$    
							Next WaferIndex
						Case Slot EQ 'A'
							WaferIndex = 1
						Case Otherwise$
							WaferIndex = Slot
					End Case
					Locate WaferIndex in Positions using @VM setting dPos then
						WOMatQARec<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 ImportBioRadQualData(RunData, ResourceID, PSN)

	Machine           = 'BioRad'
	URL             = "https://oi-metrology-viewer-prod.mes.infineon.com/api/InfinityQSV3/":ResourceID:"/header"
	TimeoutDuration = HTTPClient_Services('GetTimeoutDuration')
	If TimeoutDuration NE 30 then Httpclient_Services('SetTimeoutDuration', 30)
	Response        = Httpclient_Services('SendHTTPRequest', 'GET', URL, '', '', '', '', '', '', '')
	If Response NE '' then
		objJSON = ''
		If SRP_JSON(objJSON, 'Parse', Response) EQ '' then
			SumOOS = SRP_JSON(objJSON, 'GetValue', 'Results[1].iq_sum')
			ToolID     = RunData<5>
			TimeStamp  = RunData<2>
			If SumOOS NE '' then
				Swap 'T_LOW'  with 'T-Low'  in PSN
				Swap 'T_MID'  with 'T-Mid'  in PSN
				Swap 'T_HIGH' with 'T-High' in PSN
				Pass         = (SumOOS EQ 0)
				QualResponse = PM_Services('ProcessQual', PSN, ToolID, TimeStamp, Pass)
				StatusCode   = QualResponse<1>
				Message      = QualResponse<2>
				Metrology_Services('LogResults', PSN, Machine, StatusCode, Service : ' : ' : Message)
			end else
				LogMessage = 'IQS response missing Results[1].iq_sum. Error message: ':SRP_JSON(objJSON, 'GetValue', 'message')
				Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : LogMessage)
			end
			SRP_JSON(objJSON, 'Release')
		end else
			Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to parse IQS JSON response')
		end
	end else
		Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Null response from IQS API.')
	end

end service


Service ImportBioRadData(RunData, ResourceID, IsViewerFile, PSN, FileName)

	Machine           = 'BioRad'
	IsProdTest        = False$
	ParseArray        = ''
	FieldPos          = 13
	FieldPosIncrement = 2
	Offset            = 1
	Decimals          = 2
	// RDS Biorad metrology file
	Timestamp        = RunData<2>
	RDSKeyID         = RunData<6>
	RunDataLayer     = RunData<8>
	RunDataZone      = RunData<9>
	ReactorID        = RunData<5>
	ScanRecipe       = RunData<4>
	ToolClassID      = 'FTIR'
	PatternNameIndex = RDS_TEST_SPEC_THICK_MPATTERN$
	ToolClassIndex   = RDS_TEST_SPEC_THICK_MTOOL$
	DataIndex        = RDS_TEST_READ_THICK$
	DTMIndex         = RDS_TEST_TEST_RUN_THICKNESS_DTM$
	// Clean the cassette user input.
	Cassette         = RDSKeyID
	Swap '.' with '*' in Cassette
	Swap 'o' with ''  in Cassette
	Swap 'O' with ''  in Cassette
	if RowExists('WM_OUT', Cassette) then
		//Redirect to EPP FQA Import
		Metrology_Services('ImportBioRadEPPFQAData', RunData, FileName)
	end else
		RDSNo@ = RDSKeyID
		RDSRec = Database_Services('ReadDataRow', 'RDS', RDSKeyID)
		If Error_Services('NoError') then
			WorkOrderNo     = RDSRec<RDS_WO$>
			IsProdTest = ( Indexc(ScanRecipe, 'PROD', 1) GT 0 )
			If IsProdTest EQ True$ then Metrology_Services('LogResults',RDSKeyID,Machine,'HgCV','Product test recognized. Recipe name: ':ScanRecipe)
			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 10
											objSPC    = Logging_Services('NewLog', LogPath, OConv(Datetime(), 'DT_^1_HS_'):'_Thickness_10Points.txt', CRLF$, Comma$, '', '', False$, True$)
											NotesLog  = Logging_Services('NewLog', LogPath2, 'Thickness_10Points.txt', CRLF$, Comma$, '', '', False$, False$)
											SendToSPC = True$
										Case NumDataPoints EQ 14
											objSPC    = Logging_Services('NewLog', LogPath, OConv(Datetime(), 'DT_^1_HS_'):'_Thickness_14Points.txt', CRLF$, Comma$, '', '', False$, True$)
											NotesLog  = Logging_Services('NewLog', LogPath2, 'Thickness_14Points.txt', CRLF$, Comma$, '', '', False$, False$)
											SendToSPC = True$
										Case Otherwise$
											// Log this as it is unexpected
											NotesLog = Logging_Services('NewLog', LogPath2, 'Thickness_Points_Error.txt', CRLF$, Comma$, '', '', False$, False$)         
									End Case

									LogData    = ''
									LogData<1> = Oconv(Timestamp, 'DT/^HS')    ; // Metrology date/time stamp
									LogData<2> = ReactorID                     ; // Reactor
									LogData<3> = RDSKeyID                      ; // RDS No
									LogData<4> = RunData<7>                    ; // PSN
									// Log data positions 5 through 10, 14, or 18 will be populated with data point measurements.
									Convert @VM to @FM in RawDataPoints
									Min        = RawDataPoints<1>
									Max        = RawDataPoints<1>
									For each DataPoint in RawDataPoints using @FM Setting DataPos
										Min                     = Min(Min, DataPoint)
										Max                     = Max(Max, DataPoint)
										LogData<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 service


Service ImportCDEQualData(RunData, ResourceID, PSN)

	Machine         = 'CDE'
	URL             = "https://oi-metrology-viewer-prod.mes.infineon.com/api/InfinityQSV3/":ResourceID:"/header"
	TimeoutDuration = HTTPClient_Services('GetTimeoutDuration')
	If TimeoutDuration NE 30 then Httpclient_Services('SetTimeoutDuration', 30)
	Response        = Httpclient_Services('SendHTTPRequest', 'GET', URL, '', '', '', '', '', '', '')
	If Response NE '' then
		If SRP_JSON(objJSON, 'Parse', Response) EQ '' then
			SumOOS = SRP_JSON(objJSON, 'GetValue', 'Results[1].iq_sum')
			ToolID     = RunData<6>
			TimeStamp  = RunData<3>
			If SumOOS NE '' then
				Pass         = (SumOOS EQ 0)
				QualResponse = PM_Services('ProcessQual', PSN, ToolID, TimeStamp, Pass)
				StatusCode   = QualResponse<1>
				Message      = QualResponse<2>
				Metrology_Services('LogResults', PSN, Machine, StatusCode, Service : ' : ' : Message)
			end else
				LogMessage = 'IQS response missing Results[1].iq_sum. Error message: ':SRP_JSON(objJSON, 'GetValue', 'message')
				Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : LogMessage)
			end
			SRP_JSON(objJSON, 'Release')
		end else
			Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to parse IQS JSON response')
		end
	end else
		Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Null response from IQS API.')
	end

end service


Service ImportCDEData(RunData, ResourceID, IsViewerFile, PSN)

	Machine           = 'CDE'
	ParseArray        = ''
	FieldPos          = 23
	FieldPosIncrement = 5
	Offset            = 3
	Decimals          = 3
	Timestamp         = RunData<3>
	RDSKeyID          = RunData<7>
	RunDataLayer      = RunData<9>
	RunDataZone       = RunData<10>
	ReactorID         = RunData<6>
	ScanRecipe        = RunData<5>
	// Regular metrology file
	ToolClassID       = '4PP'
	PatternNameIndex  = RDS_TEST_SPEC_RES_MPATTERN$
	ToolClassIndex    = RDS_TEST_SPEC_RES_MTOOL$
	DataIndex         = RDS_TEST_READ_SHEET_RHO$
	DTMIndex          = RDS_TEST_TEST_RUN_SHEET_RHO_DTM$
	IsProdTest        = False$
	RDSNo@            = RDSKeyID
	RDSRec            = Database_Services('ReadDataRow', 'RDS', RDSKeyID)
	If Error_Services('NoError') then

		IsProdTest = ( Indexc(ScanRecipe, 'PROD', 1) GT 0 )
		If IsProdTest EQ True$ then Metrology_Services('LogResults',RDSKeyID,Machine,'HgCV','Product test recognized. Recipe name: ':ScanRecipe)

		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 service


Service ImportHgCVQualData(RunData, ResourceID, PSN)

	Machine         = 'HgCV'    /* Resistivity */
	URL             = "https://oi-metrology-viewer-prod.mes.infineon.com/api/InfinityQSV3/":ResourceID:"/header"
	TimeoutDuration = HTTPClient_Services('GetTimeoutDuration')
	If TimeoutDuration NE 30 then Httpclient_Services('SetTimeoutDuration', 30)
	Response        = Httpclient_Services('SendHTTPRequest', 'GET', URL, '', '', '', '', '', '', '')
	If Response NE '' then
		If SRP_JSON(objJSON, 'Parse', Response) EQ '' then
			SumOOS = SRP_JSON(objJSON, 'GetValue', 'Results[1].iq_sum')
			ToolID     = RunData<2>
			TimeStamp  = RunData<11>
			If SumOOS NE '' then
				Pass         = (SumOOS EQ 0)
				QualResponse = PM_Services('ProcessQual', PSN, ToolID, TimeStamp, Pass)
				StatusCode   = QualResponse<1>
				Message      = QualResponse<2>
				Metrology_Services('LogResults', PSN, Machine, StatusCode, Service : ' : ' : Message)
			end else
				LogMessage = 'IQS response missing Results[1].iq_sum. Error message: ':SRP_JSON(objJSON, 'GetValue', 'message')
				Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : LogMessage)
			end
			SRP_JSON(objJSON, 'Release')
		end else
			Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Failed to parse IQS JSON response')
		end
	end else
		Metrology_Services('LogResults', PSN, Machine, 'UID002', Service : ' : ' : 'Null response from IQS API.')
	end

end service


Service ImportHgCVData(RunData, ResourceID, IsViewerFile, PSN)

	Machine           = 'HgCV'    /* Resistivity */
	FieldPos          = 53
	FieldPosIncrement = 9
	Offset            = 5
	PhaseOffset       = 7
	Decimals          = 3
	Timestamp         = RunData<11>
	RDSKeyID          = RunData<4>
	LayerZonePair     = RunData<8>
	ReactorID         = RunData<3>
	// Regular metrology file
	ToolClassID       = 'HGCV'
	PatternNameIndex  = RDS_TEST_SPEC_RES_MPATTERN$
	ToolClassIndex    = RDS_TEST_SPEC_RES_MTOOL$
	DataIndex         = RDS_TEST_READ_HGCV1_RES$
	DTMIndex          = RDS_TEST_TEST_RUN_HGCV_DTM$
	Metrology_Services('LogResults', RDSKeyID, Machine, 'UID000', Service : ' : ' : 'Beginning ImportHgCVData')
	RDSNo@            = RDSKeyID
	RDSRec            = Database_Services('ReadDataRow', 'RDS', RDSKeyID)
	If Error_Services('NoError') then
		WorkOrderNo     = RDSRec<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.
									DataPoints = HgCVDataPoints
									GoSub LoadRunDataToDatabase
								end
							end else
								Error_Services('Add', 'No data points found.')
								Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
							end
						end else
							Error_Services('Add', 'Data type / Spec not found for Layer [' : RunDataLayer : '].')
							Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
						end
					end else
						Error_Services('Add', 'Unable to obtain a valid Metrology Test Key ID.')
						Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
					end
				end else
					Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
				end
			end
		end else
			Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
		end
	end else
		Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
	end

end service


Service ImportTencorData(RunData)

	ErrorMessage = ''
	Machine      = 'Tencor'
	Timestamp    = RunData<9>
	HazeAvg      = RunData<10>
	RDSKeyID     = RunData<28>
	ScanRecipe   = RunData<30>
	SoDAvg       = RunData<39>
	SoDMax       = RunData<40>
	SoDMin       = RunData<41>
	ScanTool     = RunData<43>
	Swap '%' with ' ' in ScanTool
	RDSNo@       = RDSKeyID
	Convert @Lower_Case to @Upper_Case in ScanTool

	Swap ' AM' with 'AM' in Timestamp
	Swap ' PM' with 'PM' in Timestamp
	Timestamp   = IConv(Timestamp,'DT')

	If RowExists('RDS', RDSKeyID) then
		// Try to read the CleanInsp datarow. Use this to identify the RDSKeyID.
		ReactRunRec = Database_Services('ReadDataRow', 'REACT_RUN', RDSKeyID)
		If Error_Services('NoError') then
			// Get the PSN to determine if this recipe is part of the LWI stage or the POST stage.
			PSN          = Xlate('RDS', RDSKeyID, 'PROD_SPEC_ID', 'X')
			PRSStageKeys = Xlate('PROD_SPEC', PSN, 'PRS_STAGE_KEY', 'X')
			StageFound   = False$
			Stage        = ''
			For each PRSStageKey in PRSStageKeys using @VM setting kPos
				SSRecipes = Xlate('PRS_STAGE', PRSStageKey, 'SURFSCAN_RECIPE', 'X')
				Locate ScanRecipe in SSRecipes using @VM setting rPos then
					StageFound = True$
					Stage      = Field(PRSStageKey, '*', 2, 1)
				end
			Next PRSStageKey
			CIKeyStages     = ReactRunRec<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
        ErrorMessage = 'Invalid RDS Key ID.'
        Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : ErrorMessage)
    end
	If ErrorMessage NE '' then Error_Services('Add', ErrorMessage)

end service


Service ImportSP1Data(RunData)

	Machine     = 'SP1'

	Timestamp   = RunData<9>
	HazeAvg     = RunData<10>
	RDSKeyID    = RunData<28>
	ScanRecipe  = RunData<30>
	CompareScanRecipe   = ScanRecipe
	IsMisfit    = IndexC(ScanRecipe, 'MISFIT', 1)
	If IsMisfit then
		// Sum of Defects coming from LPD.
		SoDAvg      = RunData<3>
		SoDMax      = RunData<2>
		SoDMin      = RunData<1>
	end else
		// Sum of Defects coming from All.
		SoDAvg      = RunData<39>
		SoDMax      = RunData<40>
		SoDMin      = RunData<41>
	end
	ScanTool      = RunData<43>
	DCNMM2        = RunData<44>
	* 	Swap '%' with ' ' in ScanTool
	// The 4th character can be 0 or %, but needs to be ' ' in OpenInsight
	ScanTool[4,1] = ' ' 
	Convert @Lower_Case to @Upper_Case in ScanTool
	Convert @Lower_Case to @Upper_Case in CompareScanRecipe
	*    Convert ' ' to '' in CompareScanRecipe

	Swap ' AM' with 'AM' in Timestamp
	Swap ' PM' with 'PM' in Timestamp
	Timestamp = IConv(Timestamp,'DT')
	RDSNo@    = RDSKeyID
	RDSRec    = Database_Services('ReadDataRow', 'RDS', RDSKeyID)
	If Error_Services('NoError') then
		WorkOrderNo     = RDSRec<RDS_WO$>
		CassNo          = RDSRec<RDS_CASS_NO$>
		WorkOrder       = Work_Order_Services('GetWorkOrder', WorkOrderNo, True$)
		GoSub ParseWorkOrder

		If RowExists('RDS', RDSKeyID) then
			// Try to read the CleanInsp datarow. Use this to identify the RDSKeyID.
			ReactRunRec         = Database_Services('ReadDataRow', 'REACT_RUN', RDSKeyID)
			// Check to confirm that the LWI stage has a recipe. If so, then update the UNLOAD stage.
			If PSN NE '' then
				PRSStageRow         = Database_Services('ReadDataRow', 'PRS_STAGE', PSN : '*LWI')
				PRSSurfscanRecipe   = PRSStageRow<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
            Error_Services('Add', 'Invalid RDS Key ID.')
            Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
		end
	end else
		Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
	end

end service


Service FormatLayerKeyID(RunDataLayer)

	If RunDataLayer NE '' then

		// Development code
		OriginalRunDataLayer = RunDataLayer
		CONVERT @LOWER_CASE TO @UPPER_CASE IN RunDataLayer
		// Allow for 'P', which represents a Product (ie. QA) Test - Solution abandoned 08/14/18 per Francois
		Convert 'ABCDEFGHIJKLMNOPQRSTUVWXYZ- ' to '' in RunDataLayer
		Begin Case
			Case (RunDataLayer = '1')
				RunDataLayer = 'L1'
			Case (RunDataLayer = '2')
				RunDataLayer = 'L2'
			Case (RunDataLayer = '3')
				RunDataLayer = '2'
				*            Case (RunDataLayer = 'P')
				*                Null ; // Leave it be.
			Case Otherwise$
				Error_Services('Add', 'Invalid RunDataLayer [' : OriginalRunDataLayer : '].')
				Metrology_Services('LogResults', RDSKeyID, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
		End Case

	end

	Response    = RunDataLayer

end service


Service FormatZoneKeyID(RunDataZone)

	If RunDataZone NE '' then

		CONVERT @LOWER_CASE TO @UPPER_CASE IN RunDataZone
		Convert 'ABCDEFGHIJKLMNOPQRSTUVWXYZ- ' to '' in RunDataZone

	End

	Response    = RunDataZone

end service


Service GetCalculatedZone(RDSKeyID, IsEpiPro, RunDataZone)

	CalculatedZone  =   ''

	If (RDSKeyID NE '') AND (IsEpiPro NE '') then
		If IsEpiPro then
			// This is Epi Pro, so there must be a zone. If a zone is specified then verify that there are RDS_TEST Keys
			// IDs associated with this zone. If so, then accept the zone. If not, check to see if the other zone has
			// RDS_TEST Key IDs. If so, then use that zone. If neither zone has RDS_TEST Key IDs then set an error. If
			// no zone is specified then verify if one and only one zone has RDS_TEST KeyIDS. If so, use the zone found.
			// If not, set an error.

			// Check for the RDS_TEST Key IDS for both zones first.
			Zone2RDSTestKeyIDs  = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS_Z2', 'X')
			Zone1RDSTestKeyIDs  = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS_Z1', 'X')

			// Run the tests.
			Begin Case
				Case (RunDataZone EQ 1) AND (Zone1RDSTestKeyIDs NE '')
					CalculatedZone  = 1
				Case (RunDataZone EQ 2) AND (Zone2RDSTestKeyIDs NE '')
					CalculatedZone  = 2
				Case (Zone1RDSTestKeyIDs NE '') AND (Zone2RDSTestKeyIDs EQ '')
					CalculatedZone  = 1
				Case (Zone1RDSTestKeyIDs EQ '') AND (Zone2RDSTestKeyIDs NE '')
					CalculatedZone  = 2
				Case (Zone1RDSTestKeyIDs EQ '') AND (Zone2RDSTestKeyIDs EQ '')
					Error_Services('Add', 'RDS Key ID [' : RDSKeyID : '] has no RDS_TEST Keys for any zone.')
				Case (Zone1RDSTestKeyIDs NE '') AND (Zone2RDSTestKeyIDs NE '')
					Error_Services('Add', 'No zone was entered for RDS Key ID [' : RDSKeyID : '] and both zones have RDS_TEST Keys.')
			End Case
		end else
			// Non-Epi Pro, so there is never a Zone.
			CalculatedZone  = ''
		end
	end else
		Error_Services('Add', 'The RDSKeyID or IsEpiPro argument is missing.')
	end

	Response    = CalculatedZone

end service


Service GetCalculatedLayer(RDSKeyID, Machine, RunDataZone, WoIsEpiPro, ScannedLayer)

	CalculatedLayer = ''

	If (RDSKeyID NE '') AND (Machine NE '') then
		Begin Case
			Case RunDataZone  EQ 2
				RDSTestKeyIDs   = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS_Z2', 'X')
			Case RunDataZone  EQ 1
				RDSTestKeyIDs   = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS_Z1', 'X')
			Case RunDataZone  EQ ''
				RDSTestKeyIDs   = XLATE('REACT_RUN', RDSKeyID, 'MET_KEYS', 'X')
			Case Otherwise$
				Error_Services('Add', 'Unrecognized RunDataZone [' : RunDataZone : '] in the [' : Service : '] service.')
		End Case
		If Error_Services('NoError') then
			If RDSTestKeyIDs NE '' then
				Begin Case
					Case Machine _EQC 'BioRad'
						// Just get the last RDS Test Key ID whether there is one or multiple Key IDs.
						RDSTestKeyID    = RDSTestKeyIDs[-1, 'B' : @VM]

						RDSTestRow      = Database_Services('ReadDataRow', 'RDS_TEST', RDSTestKeyID)
						If Error_Services('NoError') then
							CalculatedLayer = RDSTestRow<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

/*
ImportBioRadEPPFQAData
Modified and annotated by JRO 4/18/2025
Written to import data specifically for EpiPro QA/FQA Thickness.
Note: Due to the nature of the forms used to validate EpiPro FQA, we currently only support single value data import.
If we wish to extend this to multi value data import than we need to write the values with Sub Value Marks at the Value Mark position of the test profile.
Then we also need to modify the form usage to support parsing those multi-values.
I am leaving the iterating methods in here with the possibility that we can extend this to support multi valued data should the business need it.
This service requires the following parameters:
1. RunData, The internal delimited run data from the parent calling routine ImportBioRadData.
The service does the following actions:
1. Parses through the run data to extract the WMO Lot id, reactor id, recipe id, and slot id.
2. Writes the data point to the slot position in the WM_OUT record - > MU_WAFER_THK_RESULT field, for all wafers whether they are MU wafers or not.
    This allows us to detect that Any and all MU wafers have thickness data.
3. Reads the WO_MAT_QA record.
4. Iterates through WO_MAT_QA record -> PROFILE fields @VM values, IF the profile is 1ADE, or QA, or MO_QA then
5. Determines the spec slot by reading the WO_MAT_QA record -> SLOT field
6. If the detected RunDataSlot matches the spec slot, writes the datapoint to the WO_MAT_QA record - > RESULT field in the same @VM that the iterator is currently set to.
*/
Service ImportBioRadEPPFQAData(RunData, FileName)

	Timestamp         = RunData<2>
	WMOKeyID          = RunData<6>
	Swap '.' with '*' in WMOKeyID
	Swap 'o' with ''  in WMOKeyID
	Swap 'O' with ''  in WMOKeyID
	RunDataLayer      = RunData<8>
	ReactorID         = RunData<5>
	ScanRecipe        = RunData<4>
	DataSlotId = RunData<11>
	DataSlotId = SRP_Trim(DataSlotId, 'F', 0)
	FieldPos          = 13
	FieldPosIncrement = 2
	Offset            = 1
	Decimals          = 2
	Positions         = ''
	DataPoints        = ''

	WMORec            = Database_Services('ReadDataRow', 'WM_OUT', WMOKeyID)
	WOMatQAKey        = Field(WMOKeyID, '*', 1) : '*' : Field(WMOKeyID, '*', 3)
	Loop
		Position  = Trim(RunData<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 DataSlotId NE '' then
			If Num(DataSlotId) then 
				// Update the WM_OUT record for EpiPro
				WMORec<WM_OUT_MU_WAFER_THK_RESULT$, Int(DataSlotId)> = DataPoints
				Database_Services('WriteDataRow', 'WM_OUT', WMOKeyID, WMORec, True$, False$, True$)
			end else
				Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : DataSlotId ':Quote(DataSlotId):' is not a number!')
			end
		end else
			Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : DataSlotId is null!')
		end
	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>
				//There is a specific scenario where when the spec wafers are 1 and L, and there is a makeup wafer in each of the first and last slots, they would then want to
				//measure the next open slot, either incremented down from the last wafer position or incremented up from the first wafer position.
				//We may need to add that in when the definition becomes clearer.
				Begin Case
					Case Slot EQ '1'
					    For WaferIndex = 1 to 25
					        SlotIsEmpty = Database_Services('ReadDataColumn', 'WM_OUT', WMOKeyID, WM_OUT_RDS$, True$, 0, False$)<1, WaferIndex> EQ ''
					    Until SlotIsEmpty EQ False$
					    Next WaferIndex
					Case Slot EQ 'L'
					    For WaferIndex = 25 to 1 Step -1
					        SlotIsEmpty = Database_Services('ReadDataColumn', 'WM_OUT', WMOKeyID, WM_OUT_RDS$, True$, 0, False$)<1, WaferIndex> EQ ''
					    Until SlotIsEmpty EQ False$    
					    Next WaferIndex
					Case Slot EQ 'A'
						WaferIndex = 1
					Case Otherwise$
						WaferIndex = Slot
				End Case
                If DataSlotId EQ WaferIndex then
				    WOMatQARec<WO_MAT_QA_RESULT$, pPos> = DataPoints
                end
			end
		Next Profile
		Database_Services('WriteDataRow', 'WO_MAT_QA', WOMatQAID, WOMatQARec, True$, False$, True$)
		If Error_Services('NoError') then
			Metrology_Services('LogResults', RDSNo@, Machine, 'UID000', Service : ' : Success.')
		end else
			Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage')) 
		end
	end else
		Metrology_Services('LogResults', RDSNo@, Machine, 'UID001', Service : ' : ' : Error_Services('GetMessage'))
	end

end service


//----------------------------------------------------------------------------------------------------------------------
// CleanRepository
//
// Input: NumDays - The number of days which determines whether or not a file will be deleted.
//
// Removes rundata files from the Run Data Repository folder that are more than the desired number of days old.
//----------------------------------------------------------------------------------------------------------------------
Service CleanRepository(NumDays)

	RepoPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Run Data Repository\'
	InitDir RepoPath:'*.txt'
	FileList = DirList()
	Today    = Date()
	For each Filename in FileList
		FileInfo      = Dir(RepoPath:Filename)
		LastWriteDate = FileInfo<2>
		FileAge       = Today - LastWriteDate
		If FileAge GT NumDays then
			OSDelete RepoPath:Filename
		end
	Next Filename

end service


//----------------------------------------------------------------------------------------------------------------------
// MonitorQueue
//
// Input: NumFiles - The number of files at which manual data entry for ROTR will be allowed by all users.
//
// Monitors the Metrology run data import queue. If the number of files exceed the NumFiles variable, then
// the restriction of manual data entry of ROTR data by LEADS, SUPERVISORS, ENGINEERING, and FINAL_QA will be lifted.
// Once the queue drops back down below the NumFiles variable, then the restriction will be put back into place.
//----------------------------------------------------------------------------------------------------------------------
Service MonitorQueue(NumFiles)

	RunDataPath = Environment_Services('GetApplicationRootPath') : '\Metrology\Data\'
	InitDir RunDataPath:'*.txt'
	FileList = DirList()
	CurrFileCount = DCount(FileList, @FM)
	If CurrFileCount LT NumFiles then
		// Enable manual entry restriction - check current status to avoid unnecessary writes.
		CurrLockStatus = Database_Services('ReadDataRow', 'APP_INFO', 'ROTR_DATA_ENTRY_LOCK')
		If CurrLockStatus EQ False$ then
			Database_Services('WriteDataRow', 'APP_INFO', 'ROTR_DATA_ENTRY_LOCK', True$)
		end
	end else
		// Lift manual entry restriction - check current status to avoid unnecessary writes.
		CurrLockStatus = Database_Services('ReadDataRow', 'APP_INFO', 'ROTR_DATA_ENTRY_LOCK')
		If CurrLockStatus EQ True$ then
			Database_Services('WriteDataRow', 'APP_INFO', 'ROTR_DATA_ENTRY_LOCK', False$)
		end
	end

end service


Service GetIQSViolations()

	hSysLists   = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID then
		Response         = 0
		ParsedIQSData    = ''
		Pass             = 1
		URL              = "https://messa04ec.infineon.com/product-thick-and-res-health-reduced-web.html"
		TimeoutDuration  = HTTPClient_Services('GetTimeoutDuration')
		If TimeoutDuration NE 30 then Httpclient_Services('SetTimeoutDuration', 30)
		IQSResponse      = Httpclient_Services('SendHTTPRequest', 'GET', URL, '', '', '', '', '', '', '')
		swap CRLF$ with '' in IQSResponse
		PublishDateStart = Indexc(IQSResponse, 'Published: ', 1) + 11
		PublishDateCnt   = Indexc(IQSResponse, '</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('PostStatus', 'IQS_VIOLATION_DATA', 'LastDataTimestamp', 'CRITICAL')
		end else
			Mona_Services('PostStatus', '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

