Compile function Met_Test_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        :   Met_Test_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

    History     :   (Date, Initials, Notes)
        05/13/25    djs     Original developer

***********************************************************************************************************************/
#pragma precomp SRP_PreCompiler

$Insert SERVICE_SETUP
$Insert APP_INSERTS
$Insert MET_TEST_EQUATES
$Insert MET_TEST_DATA_EQUATES
$Insert MET_TEST_INSERTS
$Insert DICT_EQUATES
$Insert TOOL_EQUATES
$Insert LOT_OPERATION_EQUATES

Declare function   RTI_CreateGuid, Error_Services, Tool_Services, Database_Services, SRP_Array, SRP_JSON, Datetime
Declare function    Met_Test_Services, Date_Services
Declare subroutine Database_Services, Error_Services, Btree.Extract, SRP_JSON, Extract_Si_Keys, Lot_Event_Services

GoToService

Return Response or ""

//----------------------------------------------------------------------------------------------------------------------
// Service Parameter Options
//----------------------------------------------------------------------------------------------------------------------
Options LAYERS             = '1', '2', '3'
Options ZONES              = '1', '2'
Options INSPECTION_RECIPES = 'PRE', 'LOAD', 'FWI', 'UNLOAD', 'LWI', 'POST', 'QA'

//----------------------------------------------------------------------------------------------------------------------
// Services
//----------------------------------------------------------------------------------------------------------------------


Service ManualMetTestEntry(ToolId, LotId, Recipe, Pattern, RunDataLayer, RunDataZone, Positions, RawDataPoints)
    
    ToolClass = XLATE('TOOL', ToolId, 'CLASS', 'X')
    RDSKeyId = ''
    MetTestId = Met_Test_Services('CreateMetTest', LotId, RDSKeyId, ToolClass, Recipe, Pattern, Layer, RunDataZone)
    Met_Test_Services('SetPropsAndLimits', MetTestId)
    Met_Test_Services('SetMetTestTool', MetTestId, ToolId)
    Met_Test_Services('SetMetTestDtm', MetTestId, DateTime())
    
    for each DataPoint in RawDataPoints using @VM setting vPos
        position = Positions<0, vPos>
        Met_Test_Services('AddMetTestData', MetTestId, Position, DataPoint)
    Next DataPoint
    
end service

//----------------------------------------------------------------------------------------------------------------------
// CreateMetTest
//
// Input:
//      LotId            [Required] - Key of LOT_ID record to relate MET_TEST record to.
//      LegacyLotId      [Optional] - Key of legacy lot id (e.g., RDS, WO_MAT, WM_OUT) to relate MET_TEST record to
//      ToolClass        [Optional] - TOOL_CLASS of metrology test. Used for PRS_PROP spec lookup.
//                                    Required parameter for RDS_TEST metrology.
//      ToolRecipe       [Optional] - Recipe of metrology test prescribed in PSN and selected on metrology tool.
//                                    This can be a recipe from an RDS_TEST, CLEAN_INSP, or WO_MAT_QA metrology test.
//                                    Required parameter for metrology tests (i.e., not visual inspections)!
//      ToolPattern      [Optional] - Pattern of metrology test prescribed in PSN and selected on metrology tool.
//                                    This will drive the sample size (i.e., number of data points) to require in order
//                                    for the test to be considered "complete".
//      Layer            [Optional] - Layer of metrology test. Required for RDS_TEST metrology.
//                                    Note: This should be 1, 2, or 3 to match PRS_PROP keys. Not L1, L2, or 2.
//      Zone             [Optional] - Zone of metrology test. Should be null (""), 1, or 2
//      InspectionRecipe [Optional] - PRS_STAGE to retrieve visual inspection specs from. 
//                                    Required parameter for CLEAN_INSP visual inspections.
//
// Output:
//      If successful, returns the key of the MET_TEST record created.
//
// Note:
//      The SAMPLE_SIZE field derived from the ToolPattern may refer to the number of data points on a 
//      single wafer (e.g., RDS_TEST metrology) or the number of wafers in a cassette (e.g., surfscan).
//
// Creates a parent MET_TEST record related to a LOT_ID record. 
//----------------------------------------------------------------------------------------------------------------------
Service CreateMetTest(LotId, LegacyLotId, ToolClass, ToolRecipe, ToolPattern, Layer=LAYERS, Zone=ZONES, InspectionRecipe=INSPECTION_RECIPES)

	MetTestRec = ''
	MetTestId = ''
	ErrorMsg   = ''
	
	If (LotId NE '')  then
		If RowExists('LOT', LotId) then
			If Layer NE '' then
				Swap 2 with 3 in Layer
				Swap 'L2' with 2 in Layer
				Swap 'L1' with 1 in Layer
			end
			// Create MET_TEST key and write record
			MetTestId                              = RTI_CreateGuid()
			MetTestRec<MET_TEST.LOT_ID$>            = LotId
			MetTestRec<MET_TEST.LEGACY_LOT_ID$>     = LegacyLotId
			MetTestRec<MET_TEST.TOOL_CLASS$>        = ToolClass
			MetTestRec<MET_TEST.TOOL_RECIPE$>       = ToolRecipe
			MetTestRec<MET_TEST.TOOL_PATTERN$>      = ToolPattern
			MetTestRec<MET_TEST.LAYER$>             = Layer
			MetTestRec<MET_TEST.ZONE$>              = Zone
			MetTestRec<MET_TEST.INSPECTION_RECIPE$> = InspectionRecipe
			Database_Services('WriteDataRow', 'MET_TEST', MetTestId, MetTestRec)
			If Error_Services('HasError') then
				ErrorMsg = 'Error in ':Service:' service. Error writing MET_TEST record. Error message: ':Error_Services('GetMessage')
			end                                
		end else
			ErrorMsg = 'Error in ':Service:' service. LotId "':LotId:'" does not exist in the LOT table.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null LotId passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = MetTestId
	end else
		Error_Services('Add', ErrorMsg)
	end
	
End Service


//----------------------------------------------------------------------------------------------------------------------
// AddMetTestData
//
// Description:
//      Creates a child MET_TEST_DATA record related to a parent MET_TEST record.
//
// Input:
//      MetTestId      [Required] - Key of MET_TEST parent record
//      Position       [Optional] - Integer indicating an order or values (e.g., wafer slot or point value of point map)
//      PropertyVal1   [Optional] - Metrology value associated with PROPERTY_1 of parent MET_TEST record
//      ...
//      ...
//      ...
//      PropertyVal15  [Optional] - Metrology value associated with PROPERTY_15 of parent MET_TEST record
//
// Output:
//      Returns true if successful, false otherwise.
//
// Notes:
//      At least one property value is required
//      Extend PropertyVal input fields as MET_TEST PropertyVal fields are added (e.g., PropertyVal16, PropertyVal17, ...)
//----------------------------------------------------------------------------------------------------------------------
Service AddMetTestData(MetTestId, Position, PropertyVal1, PropertyVal2, PropertyVal3, PropertyVal4, PropertyVal5, PropertyVal6, PropertyVal7, PropertyVal8, PropertyVal9, PropertyVal10, PropertyVal11, PropertyVal12, PropertyVal13, PropertyVal14, PropertyVal15)
	
	ErrorMsg = ''
	If ( (MetTestId NE '') and (PropertyVal1 NE '') or (PropertyVal2 NE '') or (PropertyVal3 NE '') or (PropertyVal4 NE '') |
	or (PropertyVal5 NE '') or (PropertyVal6 NE '') or (PropertyVal7 NE '') or (PropertyVal8 NE '') or (PropertyVal9 NE '') |
	or (PropertyVal10 NE '') or (PropertyVal11 NE '') or (PropertyVal12 NE '') or (PropertyVal13 NE '') or (PropertyVal14 NE '') |
	or (PropertyVal15 NE '') ) then
		If RowExists('MET_TEST', MetTestId) then
			MetTestDataKey                                   = RTI_CreateGuid()
			MetTestDataRec                                   = ''
			MetTestDataRec<MET_TEST_DATA.MET_TEST_ID$>       = MetTestId
			MetTestDataRec<MET_TEST_DATA.POSITION$>          = Position
			MetTestDataRec<MET_TEST_DATA.PROPERTY_1_VALUE$>  = PropertyVal1
			MetTestDataRec<MET_TEST_DATA.PROPERTY_2_VALUE$>  = PropertyVal2
			MetTestDataRec<MET_TEST_DATA.PROPERTY_3_VALUE$>  = PropertyVal3
			MetTestDataRec<MET_TEST_DATA.PROPERTY_4_VALUE$>  = PropertyVal4
			MetTestDataRec<MET_TEST_DATA.PROPERTY_5_VALUE$>  = PropertyVal5
			MetTestDataRec<MET_TEST_DATA.PROPERTY_6_VALUE$>  = PropertyVal6
			MetTestDataRec<MET_TEST_DATA.PROPERTY_7_VALUE$>  = PropertyVal7
			MetTestDataRec<MET_TEST_DATA.PROPERTY_8_VALUE$>  = PropertyVal8
			MetTestDataRec<MET_TEST_DATA.PROPERTY_9_VALUE$>  = PropertyVal9
			MetTestDataRec<MET_TEST_DATA.PROPERTY_10_VALUE$> = PropertyVal10
			MetTestDataRec<MET_TEST_DATA.PROPERTY_11_VALUE$> = PropertyVal11
			MetTestDataRec<MET_TEST_DATA.PROPERTY_12_VALUE$> = PropertyVal12
			MetTestDataRec<MET_TEST_DATA.PROPERTY_13_VALUE$> = PropertyVal13
			MetTestDataRec<MET_TEST_DATA.PROPERTY_14_VALUE$> = PropertyVal14
			MetTestDataRec<MET_TEST_DATA.PROPERTY_14_VALUE$> = PropertyVal15
			Database_Services('WriteDataRow', 'MET_TEST_DATA', MetTestDataKey, MetTestDataRec)
			If Error_Services('HasError') then
				ErrorMsg = 'Error in ':Service:' service. Failed to write MET_TEST_DATA record. Error message: ':Error_Services('GetMessage')
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. Parent MET_TEST record "':MetTestId:'" does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId, PropertyVal1, PropertyVal2, PropertyVal3, or PropertyVal4 passed into service'
	end
	
	If ErrorMsg EQ '' then
		Response = True$
	end else
		Error_Services('Add', ErrorMsg)
		Response = False$
	end
	
end service



//----------------------------------------------------------------------------------------------------------------------
// SetPropsAndLimits
//
// Input:
//      MetTestId    [Required] - Key of MET_TEST record to update.
//
// Output:
//      Returns true if successful, false otherwise.
//
// Searches PROD_SPEC, PRS_PROP, and PRS_STAGe for matching property names and limits to apply to columns
// PROPERTY_1, PROPERTY_1_MIN, PROPERTY_1_MAX, ... PROPERTY_N, PROPERTY_N_MIN, PROPERTY_N_MAX.
//----------------------------------------------------------------------------------------------------------------------
Service SetPropsAndLimits(MetTestId)
	
	ErrorMsg = ''
	If (MetTestId NE '') then
		If RowExists('MET_TEST', MetTestId) then
			Open 'MET_TEST' to hTable then
				Met_Test_Services('ClearPropsAndLimits', MetTestId)
				If Error_Services('NoError') then
					MetTestRec = Database_Services('ReadDataRow', 'MET_TEST', MetTestId)
					If Error_Services('NoError') then
						LotId = MetTestRec<MET_TEST.LOT_ID$>
						If (LotId NE '')  then
							If RowExists('LOT', LotId) then
								// Find specs from provided recipe, pattern, tool class, layer, etc.
								PSNo = Xlate('LOT', LotId, 'PROD_SPEC_ID', 'X')
								If PSNo NE '' then
									If RowExists('PROD_SPEC', PSNo) then
										SpecFound        = False$
										Layer            = MetTestRec<MET_TEST.LAYER$>
										ToolRecipe       = MetTestRec<MET_TEST.TOOL_RECIPE$>
										ToolPattern      = MetTestRec<MET_TEST.TOOL_PATTERN$>
										Tool             = MetTestRec<MET_TEST.TOOL$>
										ToolClass        = MetTestRec<MET_TEST.TOOL_CLASS$>
										InspectionRecipe = MetTestRec<MET_TEST.INSPECTION_RECIPE$>
										If InspectionRecipe NE '' then
											// Get Inspection values from PRS_STAGE record
											PRSStageKey = PSNo:'*':InspectionRecipe
											If RowExists('PRS_STAGE', PRSStageKey) then
												MetTestRec<MET_TEST.PROPERTY_1_SPEC_MAX$>  = Xlate('PRS_PROP', PRSPropKey, 'LPD', 'X')
												If MetTestRec<MET_TEST.PROPERTY_1_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_1$> = 'LPD'
												MetTestRec<MET_TEST.PROPERTY_2_SPEC_MAX$>  = Xlate('PRS_PROP', PRSPropKey, 'SCRATCHES', 'X')
												If MetTestRec<MET_TEST.PROPERTY_2_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_2$> = 'Scratches'
												MetTestRec<MET_TEST.PROPERTY_3_SPEC_MAX$>  = Xlate('PRS_PROP', PRSPropKey, 'SCRATCH_LEN', 'X')
												If MetTestRec<MET_TEST.PROPERTY_3_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_3$> = 'Scratch Len'
												MetTestRec<MET_TEST.PROPERTY_4_SPEC_MAX$>  = Xlate('PRS_PROP', PRSPropKey, 'PITS', 'X')
												If MetTestRec<MET_TEST.PROPERTY_4_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_4$> = 'Pits'
												MetTestRec<MET_TEST.PROPERTY_5_SPEC_MAX$>  = Xlate('PRS_PROP', PRSPropKey, 'MOUNDS', 'X')
												If MetTestRec<MET_TEST.PROPERTY_5_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_5$> = 'Mounds'
												MetTestRec<MET_TEST.PROPERTY_6_SPEC_MAX$>  = Xlate('PRS_PROP', PRSPropKey, 'STACK_FAULTS', 'X')
												If MetTestRec<MET_TEST.PROPERTY_6_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_6$> = 'Stack Faults'
												MetTestRec<MET_TEST.PROPERTY_7_SPEC_MAX$>  = Xlate('PRS_PROP', PRSPropKey, 'SPIKES', 'X')
												If  MetTestRec<MET_TEST.PROPERTY_7_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_7$> = 'Spikes'
												MetTestRec<MET_TEST.PROPERTY_8_SPEC_MAX$>  = Xlate('PRS_PROP', PRSPropKey, 'SPOTS', 'X')
												If MetTestRec<MET_TEST.PROPERTY_8_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_8$> = 'Spots'
												MetTestRec<MET_TEST.PROPERTY_9_SPEC_MAX$>  = Xlate('PRS_PROP', PRSPropKey, 'FOV', 'X')
												If MetTestRec<MET_TEST.PROPERTY_9_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_9$> = 'FOV'
												MetTestRec<MET_TEST.PROPERTY_10_SPEC_MAX$> = Xlate('PRS_PROP', PRSPropKey, 'BL_DEFECTS', 'X')
												If MetTestRec<MET_TEST.PROPERTY_10_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_10$> = 'BL Defects'
												MetTestRec<MET_TEST.PROPERTY_11_SPEC_MAX$> = Xlate('PRS_PROP', PRSPropKey, 'BSIDE_SCRATCHES', 'X')
												If MetTestRec<MET_TEST.PROPERTY_11_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_11$> = 'Backside Scratches'
												MetTestRec<MET_TEST.PROPERTY_12_SPEC_MAX$> = Xlate('PRS_PROP', PRSPropKey, 'BSIDE_SCRATCH_LEN', 'X')
												If MetTestRec<MET_TEST.PROPERTY_12_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_12$> = 'Backside Scratch Len'
												MetTestRec<MET_TEST.PROPERTY_13_SPEC_MAX$> = Xlate('PRS_PROP', PRSPropKey, 'BSIDE_NODULES', 'X')
												If MetTestRec<MET_TEST.PROPERTY_13_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_13$> = 'Backside Nodules'
												MetTestRec<MET_TEST.PROPERTY_14_SPEC_MAX$> = Xlate('PRS_PROP', PRSPropKey, 'BSIDE_SPIKES', 'X')
												If MetTestRec<MET_TEST.PROPERTY_14_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_14$> = 'Backside Spikes'
												If ( Xlate('PRS_PROP', PRSPropKey, 'EDGE', 'X') EQ 1 ) then MetTestRec<MET_TEST.PROPERTY_15_SPEC_MAX$> = 0
												If MetTestRec<MET_TEST.PROPERTY_15_SPEC_MAX$> NE '' then MetTestRec<MET_TEST.PROPERTY_14$> = 'Edge Defects'
												MetTestRec<MET_TEST.SAMPLE_SIZE$>          = 1
											end else
												ErrorMsg = 'Error in ':Service:' service. Error finding inspection specs. PRS_STAGE record "':PRSStageKey:'" does not exist.'
											end                        
										end else
											Open 'DICT.PRS_PROP' to dPRSProp then
												Open 'DICT.PRS_STAGE' to dPRSStage then
													// Search PRS_PROP for matching layer, recipe, tool class, and tool pattern
													Query       = 'PS_NO':@VM:PSNo:@FM
													If Layer        NE '' then Query := 'LAYER_NO':@VM:Layer:@FM
													If ToolRecipe   NE '' then Query := 'MET_RECIPE':@VM:ToolRecipe:@FM
													If ToolClass    NE '' then Query := 'TOOL':@VM:ToolClass:@FM
													If ToolPattern  NE '' then Query := 'MET_RECIPE_PATTERN':@VM:ToolPattern:@FM
													PRSPropKey  = ''
													PRSPropKeys = ''
													Flag        = ''
													Btree.Extract(Query, 'PRS_PROP', dPRSProp, PRSPropKeys, 'E', Flag)
													If Flag EQ 0 then
														If (DCount(PRSPropKeys, @VM) EQ 1) then
															SpecFound                                 = True$
															PRSPropKey                                = PRSPropKeys
															// Get RAW_MIN and RAW_MAX from PRS_PROP record (MD5)
															MetTestRec<MET_TEST.PROPERTY_1$>          = Field(PRSPropKey, '*', 3, 1)
															MetTestRec<MET_TEST.PROPERTY_1_SPEC_MIN$> = OConv(Xlate('PRS_PROP', PRSPropKey, 'RAW_MIN', 'X'), 'MD5')
															MetTestRec<MET_TEST.PROPERTY_1_SPEC_MAX$> = OConv(Xlate('PRS_PROP', PRSPropKey, 'RAW_MAX', 'X'), 'MD5')
															SpecToolPattern                           = Xlate('PRS_PROP', PRSPropKey, 'MET_RECIPE_PATTERN', 'X')
															If SpecToolPattern NE '' then
																SpecToolClass = Xlate('PRS_PROP', PRSPropKey, 'TOOL', 'X')
																If SpecToolClass NE '' then
																	SampleSize = Tool_Services('GetNumPoints', SpecToolClass, SpecToolPattern)
																	If Error_Services('NoError') then 
																		If SampleSize EQ '' then SampleSize = 1
																		MetTestRec<MET_TEST.SAMPLE_SIZE$> = SampleSize                                                    
																	end else
																		ErrorMsg = Error_Services('GetMessage')
																	end
																end else
																	ErrorMsg = 'Error in ':Service:' service. TOOL (i.e., TOOL_CLASS) in PRS_PROP "':PRSPropKey:'" not set.'
																end
															end else
																ErrorMsg = 'Error in ':Service:' service. MET_RECIPE_PATTERN in PRS_PROP "':PRSPropKey:'" not set.'
															end                                            
														end
													end
													
													If Not(SpecFound) then
														// Search PRS Stage for matching SURFSCAN_RECIPE
														Query         = 'PS_NO':@VM:PSNo:@FM
														If ToolRecipe NE '' then Query := 'SURFSCAN_RECIPE':@VM:ToolRecipe:@FM
														PRSStageKey   = ''
														PRSStageKeys  = ''
														Flag          = ''
														Btree.Extract(Query, 'PRS_STAGE', dPRSStage, PRSStageKeys, 'E', Flag)
														If Flag EQ 0 then
															If (DCount(PRSStageKeys, @VM) EQ 1) then
																PRSStageKey                 = PRSStageKeys
																PRSStageSpecSurfscanRecipes = Xlate('PRS_STAGE', PRSStageKey, 'SURFSCAN_RECIPE', 'X')
																If PRSStageSpecSurfscanRecipes NE '' then
																	For each PRSStageSpecSurfscanRecipe in PRSStageSpecSurfscanRecipes using @VM Setting SpecSurfIndex
																		SpecFound = (PRSStageSpecSurfscanRecipe EQ ToolRecipe)
																		If SpecFound then
																			// Get sum of defects max (SURF_DEFECTS - MD0) and haze max (SURF_HAZE - MD2) from PRS_STAGE record (there is no min spec)
																			MetTestRec<MET_TEST.PROPERTY_1$>          = 'SURFACE_SCAN_SUM_OF_DEFECTS_MIN'
																			MetTestRec<MET_TEST.PROPERTY_1_SPEC_MAX$> = Xlate('PRS_STAGE', PRSStageKey, 'SURF_DEFECTS', 'X')<0, SpecSurfIndex>
																			MetTestRec<MET_TEST.PROPERTY_2$>          = 'SURFACE_SCAN_SUM_OF_DEFECTS_MAX'
																			MetTestRec<MET_TEST.PROPERTY_2_SPEC_MAX$> = Xlate('PRS_STAGE', PRSStageKey, 'SURF_DEFECTS', 'X')<0, SpecSurfIndex>
																			MetTestRec<MET_TEST.PROPERTY_3$>          = 'SURFACE_SCAN_SUM_OF_DEFECTS_AVG'
																			MetTestRec<MET_TEST.PROPERTY_3_SPEC_MAX$> = Xlate('PRS_STAGE', PRSStageKey, 'SURF_DEFECTS', 'X')<0, SpecSurfIndex>
																			MetTestRec<MET_TEST.PROPERTY_4$>          = 'SURFACE_SCAN_SUM_OF_DEFECTS'
																			MetTestRec<MET_TEST.PROPERTY_4_SPEC_MAX$> = Xlate('PRS_STAGE', PRSStageKey, 'SURF_DEFECTS', 'X')<0, SpecSurfIndex>                                                        
																			MetTestRec<MET_TEST.PROPERTY_5$>          = 'SURFACE_SCAN_HAZE_AVG'
																			MetTestRec<MET_TEST.PROPERTY_5_SPEC_MAX$> = IConv(Xlate('PRS_STAGE', PRSStageKey, 'SURF_HAZE', 'X')<0, SpecSurfIndex>, 'MD2')
																			MetTestRec<MET_TEST.SAMPLE_SIZE$>         = Xlate('PRS_STAGE', PRSStageKey, 'SS_SAMP_QTY', 'X')<0, SpecSurfIndex>
																		end
																	Until SpecFound
																	Next PRSStageSpecSurfscanRecipe
																end                                            
															end
														end else
															ErrorMsg = 'Error in ':Service:' service. Error calling Btree.Extract on PRS_STAGE table.'
														end
													end
													
													If Not(SpecFound) then
														// Search PRS Stage for matching MET_RECIPE and optionally, matching MET_PATTERN
														Query         = 'PS_NO':@VM:PSNo:@FM
														If ToolRecipe  NE '' then Query := 'MET_RECIPE':@VM:ToolRecipe:@FM
														If ToolPattern NE '' then Query := 'MET_RECIPE_PATTERN':@VM:ToolPattern:@FM
														PRSStageKey   = ''
														PRSStageKeys  = ''                                
														Flag          = ''
														Btree.Extract(Query, 'PRS_STAGE', dPRSStage, PRSStageKeys, 'E', Flag)
														If Flag EQ 0 then
															If (DCount(PRSStageKeys, @VM) EQ 1) then
																PRSStageKey             = PRSStageKeys
																PRSStageSpecMetRecipes  = Xlate('PRS_STAGE', PRSStageKey, 'MET_RECIPE', 'X')
																PRSStageSpecMetPatterns = Xlate('PRS_STAGE', PRSStageKey, 'MET_RECIPE_PATTERN', 'X')
																If PRSStageSpecMetRecipes NE '' then
																	For each PRSStageSpecMetRecipe in PRSStageSpecMetRecipes using @VM setting SpecMetIndex
																		If ToolPattern NE '' then
																			PRSStageSpecMetPattern = PRSStageSpecMetPatterns<0, SpecMetIndex>
																			SpecFound              = ( (PRSStageSpecMetRecipe EQ ToolRecipe) and (PRSStageSpecMetPattern = ToolPattern) )
																		end else
																			SpecFound = (PRSStageSpecMetRecipe EQ ToolRecipe)
																		end
																		If SpecFound then
																			// Get min and max from MET_MIN and MET_MAX in PRS_STAGE record (MD0)
																			MetTestRec<MET_TEST.PROPERTY_1$>          = Xlate('PRS_STAGE', PRSStageKey, 'MET_PROP', 'X')<0, SpecMetIndex>
																			MetTestRec<MET_TEST.PROPERTY_1_SPEC_MIN$> = Xlate('PRS_STAGE', PRSStageKey, 'MET_MIN', 'X')<0, SpecMetIndex> 
																			MetTestRec<MET_TEST.PROPERTY_1_SPEC_MAX$> = Xlate('PRS_STAGE', PRSStageKey, 'MET_MAX', 'X')<0, SpecMetIndex>
																			If MetTestRec<MET_TEST.PROPERTY_1$> EQ 'CRES' then
																				MetTestRec<MET_TEST.PROPERTY_2$>          = 'PHASE'
																				MetTestRec<MET_TEST.PROPERTY_2_SPEC_MIN$> = Xlate('PRS_STAGE', PRSStageKey, 'MET_PHASE_MIN', 'X')<0, SpecMetIndex> 
																			end
																			SpecToolPattern = Xlate('PRS_STAGE', PRSStageKey, 'MET_RECIPE_PATTERN', 'X')<0, SpecMetIndex>
																			If SpecToolPattern NE '' then 
																				SpecToolClass = Xlate('PRS_STAGE', PRSStageKey, 'MET_TOOL_CLASS', 'X')<0, SpecMetIndex>
																				If SpecToolClass NE '' then
																					SampleSize = Tool_Services('GetNumPoints', SpecToolClass, SpecToolPattern)
																					If Error_Services('NoError') then 
																						If SampleSize EQ '' then SampleSize = 1
																						MetTestRec<MET_TEST.SAMPLE_SIZE$> = SampleSize                                                        
																					end else
																						ErrorMsg = Error_Services('GetMessage')
																					end
																				end else
																					ErrorMsg = 'Error in ':Service:' service. MET_TOOL_CLASS in PRS_STAGE "':PRSStageKey:'" not set.'
																				end
																			end else
																				ErrorMsg = 'Error in ':Service:' service. MET_RECIPE_PATTERN in PRS_STAGE "':PRSStageKey:'" not set.'
																			end
																		end
																	Until SpecFound
																	Next PRSStageSpecMetRecipe
																end                                        
															end
														end else
															ErrorMsg = 'Error in ':Service:' service. Error calling Btree.Extract on PRS_STAGE table.'
														end
													end
													
													If Not(SpecFound) then
														// Last resort - look up matching spec limits by PSN and tool class
														Query         = 'PS_NO':@VM:PSNo:@FM
														If ToolClass NE '' then Query := 'MET_TOOL_CLASS':@VM:ToolClass:@FM
														PRSStageKey   = ''
														PRSStageKeys  = ''                                
														Flag          = ''
														Btree.Extract(Query, 'PRS_STAGE', dPRSStage, PRSStageKeys, 'E', Flag)
														If Flag EQ 0 then
															If (DCount(PRSStageKeys, @VM) EQ 1) then
																PRSStageKey             = PRSStageKeys
																PRSStageSpecToolClasses = Xlate('PRS_STAGE', PRSStageKey, 'MET_TOOL_CLASS', 'X')
																If PRSStageSpecToolClasses NE '' then
																	For each PRSStageSpecToolClass in PRSStageSpecToolClasses using @VM setting SpecMetIndex
																		SpecFound = (PRSStageSpecToolClass EQ ToolClass)
																		If SpecFound then
																			// Get min and max from MET_MIN and MET_MAX in PRS_STAGE record (MD0)
																			MetTestRec<MET_TEST.PROPERTY_1$>          = Xlate('PRS_STAGE', PRSStageKey, 'MET_PROP', 'X')<0, SpecMetIndex>
																			MetTestRec<MET_TEST.PROPERTY_1_SPEC_MIN$> = Xlate('PRS_STAGE', PRSStageKey, 'MET_MIN', 'X')<0, SpecMetIndex> 
																			MetTestRec<MET_TEST.PROPERTY_1_SPEC_MAX$> = Xlate('PRS_STAGE', PRSStageKey, 'MET_MAX', 'X')<0, SpecMetIndex>
																			If MetTestRec<MET_TEST.PROPERTY_1$> EQ 'CRES' then
																				MetTestRec<MET_TEST.PROPERTY_2$>          = 'PHASE'
																				MetTestRec<MET_TEST.PROPERTY_2_SPEC_MIN$> = Xlate('PRS_STAGE', PRSStageKey, 'MET_PHASE_MIN', 'X')<0, SpecMetIndex> 
																			end
																			SpecToolPattern = Xlate('PRS_STAGE', PRSStageKey, 'MET_RECIPE_PATTERN', 'X')<0, SpecMetIndex>
																			If SpecToolPattern NE '' then 
																				SpecToolClass = Xlate('PRS_STAGE', PRSStageKey, 'MET_TOOL_CLASS', 'X')<0, SpecMetIndex>
																				If SpecToolClass NE '' then
																					SampleSize = Tool_Services('GetNumPoints', SpecToolClass, SpecToolPattern)
																					If Error_Services('NoError') then 
																						If SampleSize EQ '' then SampleSize = 1
																						MetTestRec<MET_TEST.SAMPLE_SIZE$> = SampleSize                                                        
																					end else
																						ErrorMsg = Error_Services('GetMessage')
																					end
																				end else
																					ErrorMsg = 'Error in ':Service:' service. MET_TOOL_CLASS in PRS_STAGE "':PRSStageKey:'" not set.'
																				end
																			end else
																				ErrorMsg = 'Error in ':Service:' service. MET_RECIPE_PATTERN in PRS_STAGE "':PRSStageKey:'" not set.'
																			end
																		end
																	Until SpecFound
																	Next PRSStageSpecMetRecipe
																end                                        
															end
														end else
															ErrorMsg = 'Error in ':Service:' service. Error calling Btree.Extract on PRS_STAGE table.'
														end
														
														If Not(SpecFound) then 
															// Search PRS_PROP for matching PSN, layer, and tool class
															Query        = 'PS_NO':@VM:PSNo:@FM
															If Layer     NE '' then Query := 'LAYER_NO':@VM:Layer:@FM
															If ToolClass NE '' then Query := 'TOOL':@VM:ToolClass:@FM
															PRSPropKey   = ''
															PRSPropKeys  = ''
															Flag         = ''
															Btree.Extract(Query, 'PRS_PROP', dPRSProp, PRSPropKeys, 'E', Flag)
															If Flag EQ 0 then
																If (DCount(PRSPropKeys, @VM) EQ 1) then
																	SpecFound                                 = True$
																	PRSPropKey                                = PRSPropKeys
																	// Get RAW_MIN and RAW_MAX from PRS_PROP record (MD5)
																	MetTestRec<MET_TEST.PROPERTY_1$>          = Field(PRSPropKey, '*', 3, 1)
																	MetTestRec<MET_TEST.PROPERTY_1_SPEC_MIN$> = OConv(Xlate('PRS_PROP', PRSPropKey, 'RAW_MIN', 'X'), 'MD5')
																	MetTestRec<MET_TEST.PROPERTY_1_SPEC_MAX$> = OConv(Xlate('PRS_PROP', PRSPropKey, 'RAW_MAX', 'X'), 'MD5')
																	SpecToolPattern                           = Xlate('PRS_PROP', PRSPropKey, 'MET_RECIPE_PATTERN', 'X')
																	If SpecToolPattern NE '' then
																		SpecToolClass = Xlate('PRS_PROP', PRSPropKey, 'TOOL', 'X')
																		If SpecToolClass NE '' then
																			SampleSize = Tool_Services('GetNumPoints', SpecToolClass, SpecToolPattern)
																			If Error_Services('NoError') then 
																				If SampleSize EQ '' then SampleSize = 1
																				MetTestRec<MET_TEST.SAMPLE_SIZE$> = SampleSize                                                    
																			end else
																				ErrorMsg = Error_Services('GetMessage')
																			end
																		end else
																			ErrorMsg = 'Error in ':Service:' service. TOOL (i.e., TOOL_CLASS) in PRS_PROP "':PRSPropKey:'" not set.'
																		end
																	end else
																		ErrorMsg = 'Error in ':Service:' service. MET_RECIPE_PATTERN in PRS_PROP "':PRSPropKey:'" not set.'
																	end                                            
																end
															end else
																ErrorMsg = 'Error in ':Service:' service. Error calling Btree.Extract on PRS_PROP table.'
															end
														end
													end
												end else
													ErrorMsg = 'Error in ':Service:' service. Error opening DICT.PRS_STAGE table.'
												end
											end else
												ErrorMsg = 'Error in ':Service:' service. Error opening DICT.PRS_PROP table.'
											end
											If ErrorMsg EQ '' then
												If SpecFound then
													// Update MET_TEST record
													Database_Services('WriteDataRow', 'MET_TEST', MetTestId, MetTestRec)
													If Error_Services('HasError') then
														ErrorMsg = 'Error in ':Service:' service. Error writing MET_TEST record. Error message: ':Error_Services('GetMessage')
													end                                
												end else
													ErrorMsg = 'Error in ':Service:' service. Error finding metrology specs.'
												end
											end
										end
									end else
										ErrorMsg = 'Error in ':Service:' service. Error retrieving metrology specs. PROD_SPEC "':PSNo:'" does not exist.'
									end
								end else
									ErrorMsg = 'Error in ':Service:' service. Error retrieving metrology specs. Null PROD_SPEC_ID in LOT record "':LotId:'".'
								end
							end else
								ErrorMsg = 'Error in ':Service:' service. LotId "':LotId:'" does not exist in the LOT table.'
							end
						end else
							ErrorMsg = 'Error in ':Service:' service. Null LotId passed into service.'
						end
					end else
						ErrorMsg = 'Error in ':Service:' service. Error message: ':Error_Services('GetMessage')
					end
				end else
					ErrorMsg = 'Error in ':Service:' service. Error message: ':Error_Services('GetMessage')
				end
			end else
				ErrorMsg = 'Error in ':Service:' service. Failed to open MET_TEST table.'
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. MET_TEST ':MetTestId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = True$
	end else
		Error_Services('Add', ErrorMsg)
		Response = False$
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// ClearPropsAndLimits
//
// Input:
//      MetTestId    [Required] - Key of MET_TEST record to update.
//
// Output:
//      Returns true if successful, false otherwise.
//
// Clears columns PROPERTY_1, PROPERTY_1_MIN, PROPERTY_1_MAX, ... PROPERTY_N, PROPERTY_N_MIN, PROPERTY_N_MAX.
//----------------------------------------------------------------------------------------------------------------------
Service ClearPropsAndLimits(MetTestId)
	
	ErrorMsg = ''
	If (MetTestId NE '') then
		If RowExists('MET_TEST', MetTestId) then
			MetTestRec = Database_Services('ReadDataRow', 'MET_TEST', MetTestId)
			If Error_Services('NoError') then
				For PropertyIndex = 1 to NUM_PROPERTIES$
					PropColNo    = Xlate('DICT.MET_TEST', 'PROPERTY_':PropertyIndex, DICT_COLUMN_NO$, 'X')
					PropMinColNo = Xlate('DICT.MET_TEST', 'PROPERTY_':PropertyIndex:'_SPEC_MIN', DICT_COLUMN_NO$, 'X')
					PropMaxColNo = Xlate('DICT.MET_TEST', 'PROPERTY_':PropertyIndex:'_SPEC_MAX', DICT_COLUMN_NO$, 'X')
					MetTestRec<PropColNo>    = ''
					MetTestRec<PropMinColNo> = ''
					MetTestRec<PropMaxColNo> = ''
				Next PropertyIndex
				Database_Services('WriteDataRow', 'MET_TEST', MetTestId, MetTestRec, True$, False$, False$)
				If Error_Services('HasError') then
					ErrorMsg = Error_Services('GetMessage')
				end
			end else
				ErrorMsg = Error_Services('GetMessage')
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. MET_TEST ':MetTestId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId or ToolId passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = True$
	end else
		Error_Services('Add', ErrorMsg)
		Response = False$
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// SetMetTestRecipe
//
// Input:
//      MetTestId    [Required] - Key of MET_TEST record to update.
//      MetRecipe    [Required] - Recipe name of metrology test.
//
// Output:
//      Returns true if successful, false otherwise.
//
// Sets the TOOL_RECIPE column of a MET_TEST record.
//----------------------------------------------------------------------------------------------------------------------
Service SetMetTestRecipe(MetTestId, MetRecipe)
	
	ErrorMsg = ''
	If ( (MetTestId NE '') and (MetRecipe NE '') ) then
		If RowExists('MET_TEST', MetTestId) then
			Open 'MET_TEST' to hTable then
				WriteV MetRecipe on hTable, MetTestId, MET_TEST.TOOL_RECIPE$ else
					ErrorMsg = 'Error in ':Service:' service. Failed to write ':MetRecipe:' on TOOL_RECIPE column for MET_TEST ':MetTestId
				end
			end else
				ErrorMsg = 'Error in ':Service:' service. Failed to open MET_TEST table.'
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. MET_TEST ':MetTestId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId or ToolId passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = True$
	end else
		Error_Services('Add', ErrorMsg)
		Response = False$
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// SetMetTestPattern
//
// Input:
//      MetTestId    [Required] - Key of MET_TEST record to update.
//      MetPattern   [Required] - Pattern name of metrology test.
//
// Output:
//      Returns true if successful, false otherwise.
//
// Sets the TOOL_PATTERN column of a MET_TEST record.
//----------------------------------------------------------------------------------------------------------------------
Service SetMetTestPattern(MetTestId, MetPattern)
	
	ErrorMsg = ''
	If ( (MetTestId NE '') and (MetPattern NE '') ) then
		If RowExists('MET_TEST', MetTestId) then
			Open 'MET_TEST' to hTable then
				WriteV MetPattern on hTable, MetTestId, MET_TEST.TOOL_PATTERN$ else
					ErrorMsg = 'Error in ':Service:' service. Failed to write ':MetPattern:' on TOOL_PATTERN column for MET_TEST ':MetTestId
				end
			end else
				ErrorMsg = 'Error in ':Service:' service. Failed to open MET_TEST table.'
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. MET_TEST ':MetTestId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId or ToolId passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = True$
	end else
		Error_Services('Add', ErrorMsg)
		Response = False$
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// SetMetTestTool
//
// Input:
//      MetTestId    [Required] - Key of MET_TEST record to update.
//      ToolId       [Required] - Key of TOOL record, which represents the tool the metrology test was performed on.
//
// Output:
//      Returns true if successful, false otherwise.
//
// Sets the TOOL column of a MET_TEST record.
//----------------------------------------------------------------------------------------------------------------------
Service SetMetTestTool(MetTestId, ToolId)
	
	ErrorMsg = ''
	If ( (MetTestId NE '') and (ToolId NE '') ) then
		If RowExists('MET_TEST', MetTestId) then
			Open 'MET_TEST' to hTable then
				WriteV ToolId on hTable, MetTestId, MET_TEST.TOOL$ else
					ErrorMsg = 'Error in ':Service:' service. Failed to write ':ToolId:' on TOOL column for MET_TEST ':MetTestId
				end
			end else
				ErrorMsg = 'Error in ':Service:' service. Failed to open MET_TEST table.'
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. MET_TEST ':MetTestId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId or ToolId passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = True$
	end else
		Error_Services('Add', ErrorMsg)
		Response = False$
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// SetMetTestDtm
//
// Input:
//      MetTestId    [Required] - Key of MET_TEST record to update.
//      TestDtm      [Required] - Datetime of the metrology test. Can be internal or external datetime value.
//
// Output:
//      Returns true if successful, false otherwise.
//
// Sets the TEST_DTM column of a MET_TEST record.
//----------------------------------------------------------------------------------------------------------------------
Service SetMetTestDtm(MetTestId, TestDtm)
	
	ErrorMsg = ''
	If ( (MetTestId NE '') and (TestDtm NE '') ) then
		If RowExists('MET_TEST', MetTestId) then
			Open 'MET_TEST' to hTable then
				If Not(Num(TestDtm)) then
					Swap ' AM' with 'AM' in TestDtm
					Swap ' PM' with 'PM' in TestDtm
					TestDtm = IConv(TestDtm,'DT')
				end
				WriteV TestDtm on hTable, MetTestId, MET_TEST.TEST_DTM$ else
					ErrorMsg  = 'Error in ':Service:' service. Failed to write ':OConv(TestDtm, 'DT2/^H')
					ErrorMsg := ' on TEST_DTM column for MET_TEST ':MetTestId
				end
			end else
				ErrorMsg = 'Error in ':Service:' service. Failed to open MET_TEST table.'
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. MET_TEST ':MetTestId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId or ToolId passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = True$
	end else
		Error_Services('Add', ErrorMsg)
		Response = False$
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// SetMetTestSlot
//
// Input:
//      MetTestId    [Required] - Key of MET_TEST record to update.
//      Slot         [Required] - Slot number of metrology test. N/A to cassette level tests like Tencor surface scans.
//
// Output:
//      Returns true if successful, false otherwise.
//
// Sets the SLOT column of a MET_TEST record.
//----------------------------------------------------------------------------------------------------------------------
Service SetMetTestSlot(MetTestId, Slot)
	
	ErrorMsg = ''
	If ( (MetTestId NE '') and (Slot NE '') ) then
		If RowExists('MET_TEST', MetTestId) then
			Open 'MET_TEST' to hTable then
				WriteV Slot on hTable, MetTestId, MET_TEST.SLOT$ else
					ErrorMsg = 'Error in ':Service:' service. Failed to write ':Slot:' on SLOT column for MET_TEST ':MetTestId
				end
			end else
				ErrorMsg = 'Error in ':Service:' service. Failed to open MET_TEST table.'
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. MET_TEST ':MetTestId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId or ToolId passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = True$
	end else
		Error_Services('Add', ErrorMsg)
		Response = False$
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// AddProperty (aka parameter)
//
// Input:
//      MetTestId    [Required] - Key of MET_TEST record to update.
//      PropName     [Required] - Slot number of metrology test. N/A to cassette level tests like Tencor surface scans.
//      PropMin      [Optional] - Spec minimum value to validate the property values against. 
//      PropMax      [Optional] - Spec maxiumum value to validate the property values against. 
//
// Output:
//      Returns true if successful, false otherwise.
//
// Adds a property to a MET_TEST record.
// Note: If a PropMin and PropMax are not set for the property, it will not be validated within MET_TEST_DATA actions.
//----------------------------------------------------------------------------------------------------------------------
Service AddProperty(MetTestId, PropName, PropMin, PropMax)
	
	ErrorMsg = ''
	If ( (MetTestId NE '') and (PropName NE '') ) then 
		If RowExists('MET_TEST', MetTestId) then
			MetTestRec = Database_Services('ReadDataRow', 'MET_TEST', MetTestId)
			If Error_Services('NoError') then
				PosFound           = False$
				PropAlreadyPresent = False$
				For PropertyIndex = 1 to NUM_PROPERTIES$
					ThisPropName = Xlate('MET_TEST', MetTestId, "PROPERTY_":PropertyIndex, 'X')
					If ( (ThisPropName EQ '') and Not(PropAlreadyPresent) ) then
						PosFound      = True$
						PropNameIndex = Xlate('DICT.MET_TEST', 'PROPERTY_':PropertyIndex, DICT_COLUMN_NO$, 'X')
						PropMinIndex  = Xlate('DICT.MET_TEST', 'PROPERTY_':PropertyIndex:'_SPEC_MIN', DICT_COLUMN_NO$, 'X')
						PropMaxIndex  = Xlate('DICT.MET_TEST', 'PROPERTY_':PropertyIndex:'_SPEC_MAX', DICT_COLUMN_NO$, 'X')
					end
					If (ThisPropName EQ PropName) then PropAlreadyPresent = True$
				Until PropAlreadyPresent or PosFound
				Next PropertyIndex
				Begin Case
					Case PropAlreadyPresent
						ErrorMsg = 'Error in ':Service:' service. Property ':PropName:' already exists in MET_TEST ':MetTestId
					Case PosFound
						MetTestRec<PropNameIndex> = PropName
						MetTestRec<PropMinIndex>  = PropMin
						MetTestRec<PropMaxIndex>  = PropMax
						Database_Services('WriteDataRow', 'MET_TEST', MetTestId, MetTestRec, True$, False$, False$)
					Case Otherwise$
						ErrorMsg = 'Error in ':Service:' service. Failed to find empty property column in MET_TEST ':MetTestId
				End Case
			end else
				ErrorMsg = 'Error in ':Service:' service. Error reading MET_TEST ':MetTestId:'. Error message: ':Error_Services('GetMessage')
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. MET_TEST ':MetTestId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId, PropName, or PropMin/PropMax passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = True$
	end else
		Error_Services('Add', ErrorMsg)
		Response = False$
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// RemoveProperty
//
// Input:
//      MetTestId    [Required] - Key of MET_TEST record to update.
//      PropName     [Required] - Slot number of metrology test. N/A to cassette level tests like Tencor surface scans.
//
// Output:
//      Returns true if successful, false otherwise.
//
// Removes a property from a MET_TEST record.
//----------------------------------------------------------------------------------------------------------------------
Service RemoveProperty(MetTestId, PropName)
	
	ErrorMsg = ''
	If ( (MetTestId NE '') and (PropName NE '') ) then 
		If RowExists('MET_TEST', MetTestId) then
			MetTestRec = Database_Services('ReadDataRow', 'MET_TEST', MetTestId)
			If Error_Services('NoError') then
				PosFound           = False$
				For PropertyIndex = 1 to NUM_PROPERTIES$
					ThisPropName = Xlate('MET_TEST', MetTestId, "PROPERTY_":PropertyIndex, 'X')
					If (ThisPropName EQ PropName) then
						PosFound      = True$
						PropNameIndex = Xlate('DICT.MET_TEST', 'PROPERTY_':PropertyIndex, DICT_COLUMN_NO$, 'X')
						PropMinIndex  = Xlate('DICT.MET_TEST', 'PROPERTY_':PropertyIndex:'_SPEC_MIN', DICT_COLUMN_NO$, 'X')
						PropMaxIndex  = Xlate('DICT.MET_TEST', 'PROPERTY_':PropertyIndex:'_SPEC_MAX', DICT_COLUMN_NO$, 'X')
					end
				Until PosFound
				Next PropertyIndex
				If PosFound then
					MetTestRec<PropNameIndex> = ''
					MetTestRec<PropMinIndex>  = ''
					MetTestRec<PropMaxIndex>  = ''
					Database_Services('WriteDataRow', 'MET_TEST', MetTestId, MetTestRec, True$, False$, False$)                    
				end else
					ErrorMsg = 'Error in ':Service:' service. Failed to find property column containing "':PropName:'" in MET_TEST record ':MetTestId
				end
			end else
				ErrorMsg = 'Error in ':Service:' service. Error reading MET_TEST ':MetTestId:'. Error message: ':Error_Services('GetMessage')
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. MET_TEST ':MetTestId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId, PropName, or PropMin/PropMax passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = True$
	end else
		Error_Services('Add', ErrorMsg)
		Response = False$
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// ConvertRecordToJson
//
// Input:
//      MetTestId    [Required] - Key of MET_TEST record to update.
//
// Output:
//      metTest JSON object containing parent and child record details
//
// Converts a MET_TEST and its child MET_TEST_DATA records into a JSON object.
//----------------------------------------------------------------------------------------------------------------------
Service ConvertRecordToJson(MetTestId)
	
	MetTestJSON = ''
	ErrorMsg    = ''
	If MetTestId NE '' then
		If RowExists('MET_TEST', MetTestId) then
			Database_Services('ActivateRecord', 'MET_TEST', MetTestId)
			If Error_Services('NoError') then
				objJSON = ''
				If SRP_JSON(objJSON, 'New', 'Object') then
					MetTestDataIds = {MET_TEST_DATA_IDS}
					SRP_JSON(objJSON, 'SetValue', 'keyId', @ID, 'String')
					SRP_JSON(objJSON, 'SetValue', 'lotId', {LOT_ID}, 'String')
					SRP_JSON(objJSON, 'SetValue', 'legacyLotId', {LEGACY_LOT_ID}, 'String')
					SRP_JSON(objJSON, 'SetValue', 'lotOperationId', {LOT_OPERATION_ID}, 'String')
					
					TestDtm = Date_Services('ConvertDateTimeToISO8601', {TEST_DTM})
					SRP_JSON(objJSON, 'SetValue', 'testDtm', TestDtm)
					SRP_JSON(objJSON, 'SetValue', 'toolRecipe', {TOOL_RECIPE}, 'String')
					SRP_JSON(objJSON, 'SetValue', 'toolPattern', {TOOL_PATTERN}, 'String')
					SRP_JSON(objJSON, 'SetValue', 'inspectionRecipe', {INSPECTION_RECIPE}, 'String')
					SRP_JSON(objJSON, 'SetValue', 'sampleSize', {SAMPLE_SIZE}, 'Number')
					SRP_JSON(objJSON, 'SetValue', 'tool', {TOOL}, 'String')
					ToolClass = XLATE('TOOL', {TOOL}, TOOL_CLASS$, 'X')
					SRP_JSON(objJSON, 'SetValue', 'toolClass', ToolClass, 'String')
					SRP_JSON(objJSON, 'SetValue', 'layer', {LAYER})
					SRP_JSON(objJSON, 'SetValue', 'zone', {ZONE})
					SRP_JSON(objJSON, 'SetValue', 'slot', {SLOT}, 'Number')
					SRP_JSON(objJSON, 'SetValue', 'outOfSpec', {OUT_OF_SPEC}, 'Boolean')
					SRP_JSON(objJSON, 'SetValue', 'complete', {COMPLETE}, 'Boolean')
					objMetPropArray = ''
					If SRP_JSON(objMetPropArray, 'New', 'Array') then
						For PropIndex = 1 to NUM_PROPERTIES$
							PropName = Xlate('MET_TEST', MetTestId, 'PROPERTY_':PropIndex, 'X')
							PropVals = Xlate('MET_TEST_DATA', MetTestDataIds, 'PROPERTY_':PropIndex:'_VALUE', 'X')							
							If ( (PropName NE '') or (PropVals NE '') ) then
								objMetProp = ''
								If SRP_JSON(objMetProp, 'New', 'Object') then                                    
									PropSpecMin        = Xlate('MET_TEST', MetTestId, 'PROPERTY_':PropIndex:'_SPEC_MIN', 'X')
									PropSpecMax        = Xlate('MET_TEST', MetTestId, 'PROPERTY_':PropIndex:'_SPEC_MAX', 'X')
									PropPositions      = Xlate('MET_TEST_DATA', MetTestDataIds, 'POSITION', 'X')
									PropOutOfSpecs     = Xlate('MET_TEST_DATA', MetTestDataIds, 'PROPERTY_':PropIndex:'_OUT_OF_SPEC', 'X')
									SRP_JSON(objMetProp, 'SetValue', 'property', PropName)
									SRP_JSON(objMetProp, 'SetValue', 'propertySpecMin', PropSpecMin, 'Number')
									SRP_JSON(objMetProp, 'SetValue', 'propertySpecMax', PropSpecMax, 'Number')
									objDatapointsArray = ''
									If SRP_JSON(objDatapointsArray, 'New', 'Array') then
										For each Datapoint in PropVals using @VM setting vPos
											If Datapoint NE '' then 
												objDatapoint = ''
												If SRP_JSON(objDatapoint, 'New', 'Object') then
													Position  = PropPositions<0, vPos>
													OutOfSpec = PropOutofSpecs<0, vPos>
													SRP_JSON(objDatapoint, 'SetValue', 'position', Position, 'Number')
													SRP_JSON(objDatapoint, 'SetValue', 'value', Datapoint, 'Number')
													SRP_JSON(objDatapoint, 'SetValue', 'outOfSpec', OutOfSpec, 'Boolean')
													SRP_JSON(objDatapointsArray, 'Add', objDatapoint)
													SRP_JSON(objDatapoint, 'Release')
												end else
													ErrorMsg = 'Error in ':Service:' service. Error initializing objDatapoint object.'
												end
											end
										Next Datapoint
										SRP_JSON(objMetProp, 'Set', 'datapoints', objDataPointsArray)
										SRP_JSON(objDatapointsArray, 'Release')
									end else
										ErrorMsg = 'Error in ':Service:' service. Error initializing objDatapointsArray object.'
									end
								end else
									ErrorMsg = 'Error in ':Service:' service. Error initializing objMetProp object.'
								end
								SRP_JSON(objMetPropArray, 'Add', objMetProp)
								SRP_JSON(objMetProp, 'Release')
							end
						Next PropIndex
						SRP_JSON(objJSON, 'Set', 'properties', objMetPropArray)
						SRP_JSON(objMetPropArray, 'Release')
					end else
						ErrorMsg = 'Error in ':Service:' service. Error initializing objMetPropArray object.'
					end
					MetTestJSON = SRP_JSON(objJSON, 'Stringify', 'Styled')
					SRP_JSON(objJSON, 'Release')
				end else
					ErrorMsg = 'Error in ':Service:' service. Error initializing objJSON object.'
				end
			end else
				ErrorMsg = 'Error in ':Service:' service. Error reading MET_TEST ':MetTestId:'.'
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. MET_TEST ':MetTestId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId passed into service'
	end
	
	If ErrorMsg EQ '' then
		Response = MetTestJSON
	end else
		Error_Services('Add', ErrorMsg)
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetMetTestsByLotId
//
// Input:
//      LotId    [Required] - Key of LOT record to search MET_TEST table for related records.
//
// Output:
//      MET_TEST key ids.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetMetTestsByLotId(LotId)
	
	ErrorMsg = ''
	MetTestIds = ''
	If LotId NE '' then
		If RowExists('LOT', LotId) then
			Extract_Si_Keys('MET_TEST', 'LOT_ID', LotId, MetTestIds)
		end else
			ErrorMsg = 'Error in ':Service:' service. LOT ':LotId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null LotId passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = MetTestIds
	end else
		Error_Services('Add', ErrorMsg)
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// GetMetTestsByLotId
//
// Input:
//      LotId    [Required] - Key of LOT record to search MET_TEST table for related records.
//
// Output:
//      MET_TEST key ids.
//
//----------------------------------------------------------------------------------------------------------------------
Service GetMetTests(LotId, LegacyLotId, ToolId, ExcludeAssociated, MatchClass, FromDtm, ToDtm)
	
	ErrorMsg = ''
	MetTestIds = ''
	RespMetTestIds = ''
	If LotId NE '' then
		If RowExists('LOT', LotId) then
			Open 'DICT.MET_TEST' to DictVar then
			    SearchString = ''
			    If MatchClass NE '' then
			        If DCount(MatchClass, @VM) GT 1 then
			            SearchString := 'TOOL_CLASS':@VM:MatchClass<1,1>
			            for i = 2 to DCount(MatchClass, @VM)
			                SearchString := @VM : ';':MatchClass<1,i>
			            Next i
			            SearchString := @FM
			        end else
			            SearchString := 'TOOL_CLASS':@VM:MatchClass:@FM
			        end
			    end
			    If LotId NE '' then
			        SearchString := 'LOT_ID':@VM:LotId:@FM
			    end
			    If LegacyLotId NE '' then
			        SearchString := 'LEGACY_LOT_ID':@VM:LegacyLotId:@FM
			    end
			    If ToolId NE '' then
			        SearchString := 'TOOL':@VM:ToolId:@FM
			    end
			    if FromDtm NE '' then
			        FromDtm = FromDtm + 0.000001
			        If ToDtm NE '' then
			            ToDtm = ToDtm + 0.000001
			            
			        end else
			            ToDtm = DateTime()
			        end
			        SearchString := 'TEST_DTM':@VM:FromDtm:'~':ToDtm:@FM
			    end
			    Btree.Extract(SearchString, 'MET_TEST', DictVar, MetTestIds, '', '')
			    If Not(Get_status(errCode)) then
			        If MetTestIds NE '' then
                        If ExcludeAssociated then
                            for each MetTestId in MetTestIds using @VM setting mPos
                                MetTestLotOperationId = Database_Services('ReadDataColumn', 'MET_TEST', MetTestId, MET_TEST.LOT_OPERATION_ID$, True$, 0, False$)
                                If MetTestLotOperationId EQ '' then
                                    RespMetTestIds<1,-1> = MetTestId
                                end
                            Next MetTestId
                        end else
                            RespMetTestIds = MetTestIds
                        end
                    end
			    end else
			        ErrorMsg = ErrCode
			    end 
			end else
			    ErrorMsg = 'Error in ' : Service : ' service. Error opening MET_TEST dictionary.'
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. LOT ':LotId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null LotId passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = RespMetTestIds
	end else
		Error_Services('Add', ErrorMsg)
	end
	
end service


//----------------------------------------------------------------------------------------------------------------------
// AttachMetTestToLotOperation
//
// Input:
//      MetTestId       [Required] - Key of MET_TEST record to relate.
//      LotOperationId  [Required] - Key of LOT_OPERATION record to relate.
//
// Output:
//      True$ (1) if successful, False$ (0) otherwise.
//
//----------------------------------------------------------------------------------------------------------------------
Service AttachMetTestToLotOperation(MetTestId, LotOperationId, UserId)
	
	ErrorMsg = ''
	If ( (MetTestId NE '') and (LotOperationId NE '') ) then
		If RowExists('MET_TEST', MetTestId) then
			If RowExists('LOT_OPERATION', LotOperationId) then
				Open 'MET_TEST' to hTable then
					WriteV LotOperationId on hTable, MetTestId, MET_TEST.LOT_OPERATION_ID$ then
					    LotOperationRec = Database_Services('ReadDataRow', 'LOT_OPERATION', LotOperationId, True$, 0, False$)
					    LotOperationRec<LOT_OPERATION_MET_TEST_ID$> = Insert(LotOperationRec<LOT_OPERATION_MET_TEST_ID$>, 1, -1, 1, MetTestId)
					    Database_Services('WriteDataRow', 'LOT_OPERATION', LotOperationId, LotOperationRec)
					    LotId = LotOperationRec<LOT_OPERATION_LOT_ID$>
					    EquipmentId = Database_Services('ReadDataColumn', 'MET_TEST', MetTestId, MET_TEST.TOOL$, True$, 0, False$)
					    Lot_Event_Services('CreateLotEvent', LotId, DateTime(), 'MET_TEST', 'Metrology attached to lot.', EquipmentId, UserId)
					end else
						ErrorMsg  = 'Error in ':Service:' service. Failed to write ':LotOperationId
						ErrorMsg := ' on LOT_OPERATION_ID column for MET_TEST ':MetTestId
					end
				end else
					ErrorMsg = 'Error in ':Service:' service. Failed to open MET_TEST table.'
				end                
			end else
				ErrorMsg = 'Error in ':Service:' service. LOT_OPERATION ':LotOperationId:' does not exist.'
			end
		end else
			ErrorMsg = 'Error in ':Service:' service. MET_TEST ':MetTestId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId or LotOperationId passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = True$
	end else
		Error_Services('Add', ErrorMsg)
		Response = False$
	end
	
end service

Service RemoveMetTestFromLotOperation(MetTestId, LotOperationId, UserId)
	
	ErrorMsg = ''
	If ( (MetTestId NE '') and (LotOperationId NE '') ) then
		If RowExists('MET_TEST', MetTestId) then
			If RowExists('LOT_OPERATION', LotOperationId) then
			    MetTestRec = Database_Services('ReadDataRow', 'MET_TEST', MetTestId, True$, 0, False$)
			    MetTestRec<MET_TEST.LOT_OPERATION_ID$> = ''
			    Database_Services('WriteDataRow', 'MET_TEST', MetTestId, MetTestRec)
			    If Error_Services('NoError') then
			        LotOperationRec = Database_Services('ReadDataRow', 'LOT_OPERATION', LotOperationId, True$, 0, False$)
                    Locate MetTestId in LotOperationRec<LOT_OPERATION_MET_TEST_ID$> using @VM setting MetTestPos then
                        LotOperationRec<LOT_OPERATION_MET_TEST_ID$> = Delete(LotOperationRec<LOT_OPERATION_MET_TEST_ID$>, 1, MetTestPos, 1)
                    end
                    Database_Services('WriteDataRow', 'LOT_OPERATION', LotOperationId, LotOperationRec)
                    LotId = LotOperationRec<LOT_OPERATION_LOT_ID$>
                    EquipmentId = Database_Services('ReadDataColumn', 'MET_TEST', MetTestId, MET_TEST.TOOL$, True$, 0, False$)
                    Lot_Event_Services('CreateLotEvent', LotId, DateTime(), 'MET_TEST', 'Metrology attached to lot.', EquipmentId, UserId)
			    end else
			        ErrorMsg = 'Error in ':Service:' service. Error removing MET_TEST ':MetTestId:' from operation.' 
			    end
			end             
		end else
			ErrorMsg = 'Error in ':Service:' service. MET_TEST ':MetTestId:' does not exist.'
		end
	end else
		ErrorMsg = 'Error in ':Service:' service. Null MetTestId or LotOperationId passed into service.'
	end
	
	If ErrorMsg EQ '' then
		Response = True$
	end else
		Error_Services('Add', ErrorMsg)
		Response = False$
	end
	
end service





