Batch Production Records
The Production Tracking data store can be queried and modified using the Lua API. The queries use a syntax similar to the MongoDB query language such as the $lte and $gte comparison operators.
ISA-88 schema which describes the structure of the Batch Production Records is documented here. It’s good to have it as a reference when using the functions documented below. |
Error Handling for isa88.db library functions
The following isa88.db
functions will return error codes and messages along with the result:
All these functions will return three values following the convention shown below:
local result, err_code, err_msg = isa88.db.<func>()
If result is nil, then err_code is a numeric value and err_msg an error string. The error code allows to differentiate hard (permanent) and soft (transient) errors. The function can be retried in the event of transient errors if so desired.
The following example using the isa88.db.find function demonstrates how this works. If the result is nil and transient errors are encountered the function is retried (upto 3 times). Otherwise, an error message describing the nature of the error.
local isa88 = require "isa88"
local find = function(q)
local retries = 3
local result, err_code, err_msg
while retries > 0 do
retries = retries - 1
result, err_code, err_msg = isa88.db.find(q)
if result then
return result
elseif err_code ~= isa88.db.error_code.transient_failure then
-- It doesn't make sense to retry
return result, err_msg
end
end
return result, err_msg
end
local result, err_msg = find([[ ... custom BPR query ...]])
Please look at the isa88.db.update description for more information on error handling with this function.
Querying Batch Production Records (isa88 format)
The Batch Production Record isa88.db.find()
function is provided by the isa88.lua
library.
The find()
function takes a table or a JSON string which represents the MongoDB query by which the user wants to filter the Batch Production Records
. Additionally, the user can pass a second table of options to configure the behavior of the function.
The query
should specify the type of the document and the intended query for that type. find()
returns an array of tables containing filtered documents of the type specified.
For example, here is how the user can find documents of type Event
whose TimeStamp
is within a range of time.
local isa88 = require "isa88"
return isa88.db.find([[
{
"Event": {
"TimeStamp": {
"$lte": {
"$date": "2019-06-24T12:30:00.0Z"
},
"$gte": {
"$date": "2019-06-23T12:00:00.0Z"
}
}
}
}
]])
The returned array of tables has the following structure.
[
{
"ID":<bpr_id>,
"Events":[{...}, {...}]
},
{
"ID":<bpr_id>,
"Events":[{...}, {...}]
},
.
.
]
If the BatchTracker is used with the partial batches option enabled, then several BPRs with the same BatchID can be created. Each of this BPRs will be linked to a different equipment. In order to get only one BPR in this case, the equipment ID can be included into the query.
local isa88 = require"isa88"
local batchId = "Demo_Batch_1619042181991"
local eq_id = 281483214520320
local query = [[ [{"BatchProductionRecord": { "BatchID": "%s", "EquipmentID": %i }}] ]]
doc = isa88.db.find(query:format(batchId, eq_id))
return doc
The same query can also be used for isa88.db.findRetunrBatch()
.
Additionally the query can also be based on the euipment name, rather than the equipment ID.
local isa88 = require"isa88"
local batchId = "Demo_Batch_1619042181991"
local eqName = "/TheCompany/Plant1/Bulk/Building_A10/Floor_C/Line1"
local query = [[ [{"BatchProductionRecord": { "BatchID": "%s", "EquipmentID": {"$elemMatch": {"@name": "%s" } } } }] ]]
doc = isa88.db.find(query:format(batchId, eqName))
return doc
The user can use standard MongoDB keywords like "$and", "$or", "$elemMatch" or "$in" to construct complexe query conditions. This is peticulary usefull to filter for timestamp, material type, equipmentID.
local isa88 = require "isa88"
return isa88.db.find( [[
[
{ "EquipmentProceduralElement":
{ "$and":
[
{ "Parameter":
{ "$elemMatch":
{ "Description.#": "StartTime", "Value":{"$lt": 1605695000000} }
}
},
{ "Parameter":
{ "$elemMatch":
{ "Description.#": "EndTime", "Value":{"$gt": 1605694000000} }
}
},
{ "EquipmentProceduralElementType.@name": "Unit Procedure" }
]
}
}
]
]] )
The user can also specify conditions on multiple document types at once. In that case, the full 'Batch Production Record' documents which match the criterion are returned regardless of the type of documents specified. This is especially usefull to combine conditions of different document types which are spread across different MondoDB collections. Internally, this will be implemented with subquerries ($lookup) and does only allow to combine the conditions in an "and" manner. Additionally the performance might depend on the order of the subquerries.
local isa88 = require "isa88"
return isa88.db.find([[
[
{
"BatchProductionRecord": {
"ID": {"$regex": "^a","$options": ""}
}
},
{
"Event": {
"Value": 3
}
},
{
"ProductionResponse": {
"TimeStamp": {
"$lte": {"$date": "2019-01-24T12:30:00.0Z"},
"$gte": {"$date": "2018-01-23T12:00:00.0Z"}
}
}
}
]
]])
Querying Batch Production Records (simple format)
The user can use the 'isa88.db.findReturnBatch()' function to query BPRs, but in this case the result will be transformed in a simpler presentatition which is not structured according to the isa88 standard, but reflects the internal structure of the BPR during creation.
The query prameters, as well as the querry options follow the same format, as described for 'isa88.db.find'.
Querying IDs of Batch Production Records
The user can use the 'isa88.db.getIDs()' function to query IDs and root_ids of BPRs. This is especially usefull if a large number of documents shall be requested as resonse to a query. Then the ID query returns a unique identification of each relevant document. Consecutive queries can then requests a small number of documents each, without missing documents or duplicates.
The query to retrieve docments based on root_ids can look like this:
local options
options = {limit=1000, timeOut=30000}
local doc_ids, rootIds, err = isa88.db.getIDs(query, options)
local idQuery = JSON.encode( {{BatchProductionRecord={["_root"]= {["$in"]=rootIds}}}} )
options = {limit=1000, timeOut=30000}
local res,err1,err2 = isa88.db.findReturnBatch(idQuery, options)
The query prameters, as well as the querry options follow the same format, as described for 'isa88.db.find'.
Counting Batch Production Records
The user can also use the isa88.db.count()
function to count the number of documents that satisfy some specified conditions. The arguments it accepts are similar to the find()
function. Instead of returning an array of documents, it returns an integer count.
Options
The user can specify a number of options to override the default behavior of the function.
Option |
Default Value |
Description |
data_store |
nil |
Specifies a data store for the find/count operation. If not specified, this defaults to the System Production Tracking Data Store for Master Cores. For Local Cores, this option is required. The value is an inmation objspec, identifying the System object or a custom store object. |
timeOut |
10000 |
Specifies the time in milliseconds for a single database operation after which the query is aborted |
batchSize |
10000 |
The maximum number of items returned by MongoDB in a single batch. |
returnSubDocuments |
true |
If set to true, children documents of a document type according to the ISA-88 specification are fetched from MongoDB and a document that is constructed according to the ISA-88 specification is returned. If not, only the top level fields of the specified document type are returned. This option is not applicable to |
limit |
nil |
The maximum number of matching documents to return. |
skip |
nil |
The number of matching documents to skip before returning results. |
Updating Batch Production Records
The isa88.db.update()
function updates or replaces an existing Batch Production Record or inserts a new Batch Production Record
The Batch Production Record update()
function is provided by the isa88.lua
library.
The update()
function has two parameters:
-
doc (mandatory)
A Lua table or a valid JSON object. The table must represent a valid S88 document (a BatchProductionRecord, Comment, Sample, etc.) -
options (optional)
A Lua table or valid JSON object with different fields. The fields used to query for existing documents (bpr, parent, and filter) only support the specification of document attributes. Trying to include attributes for sub-documents will result in an error - use theisa88.db.find()
function instead and then use for example the EntryID attribute in a parent or filter specification.
The options fields are described in the following table:
Option | Default Value | Description |
---|---|---|
data_store |
nil |
Specifies a data store for the update operation. If not specified, this defaults to the System Production Tracking Data Store for Master Cores. For Local Cores, this option is required. The value is an inmation objspec, identifying the System object or a custom store object. |
operation |
isa88.db.update_op.insert |
Specifies one of the update operations insert (0), replace (1) or update (2) (these are coded in the |
upsert |
false |
For update or replace operations, inserts the document if |
multi |
false |
For update operations, updates all documents that match the filter if multi = true. Otherwise,
updates at most one document. For replace operations, removes all documents that match the filter
if |
doc_type |
"BatchProductionRecord" |
Specifies the S88 document type of the doc argument. |
parent |
nil |
A S88 document used to match parent documents in a S88 Batch Production Record for "insert" operations (specified in options.operation). If parent is |
parent_type |
BatchProductionRecord |
The document type (see doc_type) for the parent document. |
filter |
nil |
A S88 document used to match documents for "update" and "replace" operations. filter must not be |
filter_type |
Same as the doc document type |
The doc_type (see above) for the filter document |
bpr |
nil |
Specifies a filter for the attributes of a Batch Production Record. This option can be used to further constrain the parent or filter option to the set of matched Batch Production Record documents. If a parent document can’t be found unambiguously using both filter and bpr options, the operation fails. |
validate_refs |
false |
If true, DataReference attributes in Change and Comment documents are validated. The validation is performed locally if a new stand-alone BPR with embedded Change or Comment sub-documents is inserted. Otherwise, DataReference attributes are validated by checking the existence of the referenced document attribute in the database. |
wait_for_completion |
true |
If true, the update function will synchronously execute the operation (including database commands) and the return values indicate success or failure directly. If false, the function will only check the validity of the input parameters and return as quickly as possible after queuing database operations. |
The replace operation currently only supports the replacement of full Batch Production Record documents. If
multi is specified, all matching BPRs are deleted and then a single replacement document is inserted. The bpr option is useful for update and insert operations, if the filter or parent option would match sub-documents from different Batch Production Records. Then the bpr option can be used to restrict the matched sub-documents to the set of BachProductionRecord documents matching the attributes specified in the bpr option. |
The update()
function always returns three values: a numeric code and string message (as defined in the
isa88.db.error
table) and a unique string id identifying the operation. If wait_for_completion = false
,
this id can be used to identify matching events published by a BPR Publisher object.
Insert Operations
Insert operations (options.operation = isa88.db.update_op.insert
) are used to insert new stand-alone
ISA88 documents (typically full BatchProductionRecord documents) or to insert sub-documents into
existing records.
This example inserts a new stand-alone Batch Production Record:
local isa88 = require "isa88"
local bpr = isa88.BatchProductionRecord{
BatchID = "insert-bpr-01",
EquipmentID = "/A/B",
ControlRecipes = {
ID = "CR-01"
}
}
isa88.db.update(bpr)
This example inserts a new Comment sub-document into an existing Batch Production Record:
local isa88 = require "isa88"
local comment = isa88.Comment{
Comment = "pipe repaired"
}
local insert_options = {
bpr = { BatchID = "insert-bpr-01" }
}
isa88.db.update(comment, insert_options)
This example inserts a new RecipeElement sub-document into an existing Batch Production Record, specifying its direct parent control recipe:
local isa88 = require "isa88"
local recipe_element = isa88.RecipeElement{
ID = "RE-01",
RecipeElementType = syslib.model.codes.S88RecipeElementType.PHASE
}
local insert_options = {
bpr = { BatchID = "insert-bpr-01" },
parent = { ID = "CR-01" },
parent_type = "ControlRecipe"
}
isa88.db.update(recipe_element, insert_options)
This example tries to insert a new ControlRecipe sub-document into an existing Batch Production Record but fails because a parent document can’t be found unambiguously:
local isa88 = require "isa88"
local control_recipe = isa88.ControlRecipe{
ID = "CR-01"
}
local insert_options = {
parent = { LotID = "Lot-01" },
parent_type = "BatchProductionRecord"
}
local code, msg, opid = isa88.db.update(control_recipe, insert_options)
If there is more than one BatchProductionRecord with the LotID specified, the call fails with the message ambiguous parent. It can be fixed either by modifying the parent option or by specifying the bpr option to make it possible to find the parent document unambiguously.
local isa88 = require "isa88"
local control_recipe = isa88.ControlRecipe{
ID = "CR-01"
}
local insert_options = {
parent = { LotID = "Lot-01" },
parent_type = "BatchProductionRecord",
bpr = {EntryID = "82124a5c-c402-11e9-8b93-a0f3c16f6109"},
}
local code, msg, opid = isa88.db.update(control_recipe, insert_options)
Update Operations
Update operations (options.operation = isa88.db.update_op.update
) are used to update attributes
of existing ISA-88 documents. If upsert = true
, then Batch Production Record documents are inserted
if the filter option does not match any document.
This example updates an existing Batch Production Record, adding an entry to the LotID array attribute.
local isa88 = require "isa88"
local update_options = {
operation = isa88.db.update_op.update,
filter = { BatchID = "insert-bpr-01" }
}
isa88.db.update({ LotID = "lot-0001"}, update_options)
This example updates the description of all Control Recipe documents of batch production records which contain the specified BatchID.
local isa88 = require "isa88"
local update_options = {
operation = isa88.db.update_op.update,
bpr = { BatchID = "insert-bpr-01" },
filter = {}, -- matches all Control Recipe documents
multi = true
}
isa88.db.update(
isa88.ControlRecipe{ Description = "Imported control recipe" },
update_options
)
Replace Operations
Replace operations (options.operation = isa88.db.update_op.replace
) are used to fully replace
existing ISA-88 documents. If upsert = true
, then the replacement document is inserted
if the filter option does not match any document.
This example replaces all Batch Production Records which match the specified filter with the specified record. If no records match the filter, the record is inserted.
local isa88 = require "isa88"
local replace_options = {
operation = isa88.db.update_op.replace,
filter = { BatchID = "BPR-001" }, -- matches all BPRs with a BatchID array element of BPR-001
upsert = true, -- insert if no match found
multi = true -- replace all matching BPRs
}
local bpr = isa88.BatchProductionRecord{
BatchID = "BPR-002",
Description = "replaces all BPRs with BatchID BPR-001"
}
isa88.db.update(bpr, replace_options)
This example tries to replace a single Batch Production Record which matches the specified filter with the specified record. If more than one document matches the filter, the operation fails with the message ambiguous replacement target since multi is false.
local isa88 = require "isa88"
local replace_options = {
operation = isa88.db.update_op.replace,
filter = { BatchID = "BPR-001" }, -- the operation fails if more than one BPR with BatchID 'BPR-001' exists
upsert = true -- insert if no match found
}
local bpr = isa88.BatchProductionRecord{
BatchID = "BPR-002",
Description = "replaces all BPRs with BatchID BPR-001"
}
isa88.db.update(bpr, replace_options)
Annotating Batch Production Records
The isa88.db.annotate()
function adds a textual comment to an existing Batch Production Record.
The Batch Production Record annotate()
function is provided by the isa88.lua
library.
The annotate()
function has three parameters:
-
doc (mandatory)
A Lua table or valid JSON object, representing the Batch production Record you wish to annotate. -
annotation (mandatory)
A Lua table or valid JSON object containing the annotation. This is added into the BPR as a "Comment" document type. -
options (optional)
A Lua table or valid JSON object with the validate_refs and wait_for_completion fields as described for theupdate
function above.
The annotate() function is a convenience wrapper around update() and does not provide additional functionality
at the moment. The update operation is always "insert" and only the validate_refs and wait_for_completion
options are applicable.
|
This example annotates an existing Batch Production Record by adding a ISA-88 Comment sub-document to it.
local isa88 = require "isa88"
isa88.db.annotate(
{ BatchID = "BATCH-0001" }, -- Annotate a record with this BatchID
{ Comment = "Custom annotation" }
)
Comment and Change Data References
The ISA-88 objects Comment and Change may have a DataReference attribute set. This attribute references data in the same Batch Production Record by specifying one or more of the following fields:
- DataReference.ReferenceObjectType
-
Specifies the referenced object type, using a coding from
syslib.model.codes.S88ProductionRecordEntryType
or a string representation of a valid code from this coding group. This is a mandatory field. - DataReference.ReferenceEntryID
-
Specifies the EntryID attribute the referenced document, if available.
- DataReference.ReferenceID
-
Specifies the ID attribute of the referenced document, if available.
- DataReference.Attribute
-
Optionally specifies a path pointing to a particular attribute / data value in the referenced document. The path separator is a
.
and arrays indexes are 0-based. For example, referencing a particular batch id in the BatchID array attribute looks likeBatchID.3
. Referencing the EquipmentID attribute inside a TagSpecification attribute of a DataSet document looks likeTagSpecification.0.EquipmentID
A DataReference points to a particular ISA-88 (sub-)document using the ReferenceObjectType attribute and one or both of the ReferenceEntryID and ReferenceID attributes. It may then further qualify the reference by specifying an attribute path via Attribute, pointing into the referenced (sub-)document.
If you have a ISA-88 object available in your script, make use of the isa88.getDataReference() function described below. This function always returns a valid DataReference attribute.
Creating Data References
A DataReference attribute can be created manually by specifying the attributes described above.
Create a data reference into a DataSet document, referencing the EquipmentID attribute.
local isa88 = require "isa88"
local ref = isa88.DataReference{
ReferenceObjectType = syslib.model.codes.S88ProductionRecordEntryType.ET_DATASET,
ReferenceEntryID = "abcd-efghijklmn-opqr",
Attribute = "TagSpecification.0.EquipmentID"
}
Create a data reference into a MaterialActual document, referencing a MaterialLotID entry. The MaterialActual document does not have an EntryID attribute.
local isa88 = require "isa88"
local ref = isa88.DataReference{
ReferenceObjectType = syslib.model.codes.S88ProductionRecordEntryType.ET_OTHER,
ReferenceID = "MA-0001",
Attribute = "MaterialLotID.2"
}
ref.ReferenceObjectType["@name"] = "MaterialActual"
If the document which is to be referenced is available in the Lua script, the function isa88.getDataReference()
can be used. It returns a ISA-88 DataReference object based on two parameters:
-
doc (mandatory)
A ISA-88 object representing a (sub-)document supporting either the EntryID or ID attribute. -
attribute (optional)
An attribute path specification into the doc object. Path elements are separated by a dot and array indices are 0-based. E.g. "TagSpecification.0.StartTime" for a DataSet object.
The following examples create references to the same elements as the examples for the manual reference
creation above, but use the getDataReference()
function instead.
Create a data reference into a DataSet document, referencing the EquipmentID attribute.
local isa88 = require "isa88"
local bpr = isa88.BatchProductionRecord{
DataSets = {
TagSpecification = {
EquipmentID = "/a/b"
}
},
}
local ref = isa88.getDataReference(bpr.DataSets[1], "TagSpecification.0.EquipmentID")
bpr.Comments = {
Comment = "Wrong equipment id",
DataReference = ref
}
Create a data reference into a MaterialActual document, referencing a MaterialLotID entry. The MaterialActual document does not have an EntryID attribute.
local isa88 = require "isa88"
local bpr = isa88.BatchProductionRecord{
ProductionResponses = {
SegmentResponse = {
ID = "SR-01",
MaterialActual = {
ID = "MA-01",
MaterialLotID = {
"LOT-01", "LOT-02", "LOT-03"
}
}
}
}
}
local ref = isa88.getDataReference(
bpr.ProductionResponses[1],
"SegmentResponse.0.MaterialActual.0.MaterialLotID.2"
)
bpr.Comments = {
Comment = "Wrong lot id",
DataReference = ref
}
Resolving Data References
The isa88.resolveDataReference()
function resolves a DataReference object for a given ISA-88 object
or table and returns the referenced value. It accepts two parameters:
-
doc (mandatory)
A table (usually a proper ISA-88 object) used to resolve the reference ref. -
ref (mandatory)
A ISA-88 DataReference object or a regular table encoding a valid data reference.
If the reference is invalid a Lua error is raised. If it is resolvable, the function returns the
referenced value and nil
otherwise.