Пример обогащения данными импортируемого объекта из внешней системы
Данный пример наглядно демонстрирует решение следующих задач:
-
преобразование структуры импортируемого объекта;
-
обогащение импортируемого объекта данными из внешних сервисов;
-
указание группы, в которой должны быть создана основная позиция;
-
выполнять сопоставление позиции;
-
отклонять сообщение импорта по заданным условиям с указанием причины отклонения.
Сценарий импорта: На вход поступает сообщение импорта из внешней системы, в котором передано полное наименование объекта справочника “Номенклатура”. Требуется по полученному наименованию объекта, с помощью сервиса машинного обучения (ML) предсказать группу, в которой должна быть создана позиция и значения атрибутов, в том числе атрибутов вспомогательных справочников и классификаторов. После получения этих данных из сервиса машинного обучения (ML), требуется найти требуемые позиции во вспомогательных справочниках, после чего выполнить обогащение импортируемого объекта полученными данными из сервиса машинного обучения. Если импортируемый объект уже связан с позицией Semantic MDM через переходной ключ, то система должна проверить что позиция принадлежит той же группе, что была предсказана сервисом машинного обучения, если позиция не принадлежит предсказанной группе, то сообщение импорта должно быть отклонено. Сообщение импорта должно быть также отклонено, если не найдена нормализованная позиция во вспомогательном справочнике.
Для решения данной задачи, потребуется следующее:
import groovy.json.JsonSlurper
String transformRoot(ProcessedRootObject root) {
String EI_uid=null;
String DoubleVal=null;
//Получим полное наименование из импортируемого объекта
ProcessedValue FindedVal = root.getValues().find {element -> element. getName()=="FULL_NAME"};
//Если не нашли атрибут с полным наименованием, то отклоняем сообщение
if (FindedVal == null)
return "Сообщение импорта не содержит наименование объекта(FULL_NAME)";
String FullName = FindedVal.getStringValue();
//Удалим атрибут с полным наименованием, т.к. он будет преобразован в набор атрибутов
root.getValues().remove(FindedVal);
// Получить из сервиса машинного обучения предсказанные значения атрибутов, на основе полного наименование объекта (FULL_NAME)
HttpURLConnection connection = new URL("http://ml.sdi-solution.ru/extract1?full_name=" +
ru.sdi.m2.resources.util.StringUtil.urlEncode(FullName)).openConnection() as HttpURLConnection;
//Выполним парсинг JSON, с результатом предсказания
def rootNode = new JsonSlurper().parseText(connection.inputStream.text);
//Получим GUID группы, в которой требуется создать позицию
//GUID группы предсказывается атрибутом attr_name=="groupId". Идентификатор данного атрибута attr_id='31415926'
String NodeUid = rootNode.extracted_attrs.find {it.attr_name=="groupId"}.attr_value;
//Проверим существование группы
ClassifiableElementInfoDto NodeInfo = support.getNodeInfo(NodeUid,false);
if (NodeInfo==null)
return "Предсказанная машинным обучением группы не существует";
root.setMdmNodeId(NodeUid);
//При наличии переходного ключа, проверить соответствует ли группа требуемой
String mdmId = support.getMDMId(root.getSourceId(),root.getExternalSystemId());
if (mdmId!=null)
{//получить uid группы и сравнить ее с предсказанной
String CurNodeUid = support.getItemNodeUid(mdmId);
if (NodeUid!=CurNodeUid)
return "Выявлено несоответствие текущей группы('"+CurNodeUid+"') позиции('"+mdmId+"') и предсказанной группы('"+NodeUid+"')";
}
//Получим список продекларированных атрибутов в предсказанной группе
List<PropertyDeclarationInfoDto> nodeDeclarations = support.getPropertyDeclarationsByNodeUid(NodeUid,false);
//Пройдемся в цикле по извлеченным атрибутам
for (int i = 1; i < rootNode.extracted_attrs.size(); i++) {
String PropPath = rootNode.extracted_attrs[i].attr_id;
String PropValue = rootNode.extracted_attrs[i].attr_value;
//Будем рассматривать только атрибуты с непустым значением
//Атрибут "groupId" с идентификатором '31415926' игнорируем, т.к. обработали его ранее
if ((PropPath!='31415926')&&((PropValue!=null)&&(PropValue!="")))
{
//Если атрибут связь
int delimPos=PropPath.indexOf('->');
if (delimPos>0)
{ //Получим гуид агрегации(атрибут связи) из пути
String AggrPropUid = PropPath.substring(0,delimPos);
PropertyDeclarationInfoDto attrDeclaration = nodeDeclarations.find {element -> element.uuid == AggrPropUid};
//Если агрегация с таким гуид не существует, то выдаем ошибку
if (attrDeclaration == null)
throw new Exception('не удалось найти декларацию атрибута связи с гуид "'+AggrPropUid+'" в группе "'+NodeUid+'"');
//Проверим, обрабатывалась ли ранее эта агрегация. Если обрабатывалась, то она уже будет в списке атрибутов импортируемого объекта
FindedVal = root.getValues().find {element -> element.getPropertyId()==attrDeclaration.id};
if (FindedVal==null)
{ //Найдем все атрибуты, для обрабатываемой агрегации
def allAggrs = rootNode.extracted_attrs.findAll {it.attr_id.contains(AggrPropUid)}
//Получим область поиска агрегации
List<String> aggrScope = attrDeclaration.scopeNodes;
if (attrDeclaration.scopeNodes.size()==0)
return 'Область для атрибута "'+allAggrs[0].attr_name.subString(0,allAggrs[0].attr_name.indexOf('->'))+'" не задана в группе "'+root.getMdmNodeId()+'"';
String aggrItemUid = getAggregationItemUid(rootNode, AggrPropUid, aggrScope);
//Добавим в импортируемый объект атрибут агригации со ссылкой на найденную агрегируемую позицию
if (aggrItemUid!=null)
root.getValues().add(new ProcessedValue(propertyId: attrDeclaration.id, processObject: new ProcessObject(mdmId: aggrItemUid)))
else
return 'Не удалось найти агрегируемую позицию "'+PropValue+'" для атрибута "'+allAggrs[0].attr_name.substring(0,allAggrs[0].attr_name.indexOf('->'))+'"';
}
}
//Если атрибут простой
else
{
//Проверим наличие атрибута, если атрибут не декларирован, то выдаем ошибку
PropertyDeclarationInfoDto attrDeclaration = nodeDeclarations.find {element -> element.uuid == PropPath};
if (attrDeclaration==null)
throw new Exception('не удалось найти атрибут с гуид "'+PropPath+'"');
//Добавим новый атрибут в импортируемый объект, если для него определено значение
if ((PropValue!=null)&&(attrDeclaration!=null))
switch (attrDeclaration.dataType) {
case PropertyDataType.String:
//Добавим строковое значение атрибута в импортируемый объект
root.getValues().add(new ProcessedValue(propertyId: attrDeclaration.id, stringValue: PropValue));
break;
case PropertyDataType.Int:
//Добавим числовое значение атрибута в импортируемый объект
root.getValues().add(new ProcessedValue(propertyId: attrDeclaration.id, intValue: PropValue.toInteger()));
break;
case PropertyDataType.Double:
//Число с плав. запятой можно записать как строку без указания ЕИ, тогда будет считаться что передается значение в базовой ЕИ
//Число с плав. запятой можно записать с указанием обозначения ЕИ через пробел
root.getValues().add(new ProcessedValue(propertyId: attrDeclaration.id, stringValue: PropValue));
break;
}
}
}
}
return null;
}
//Найти агрегированную позицию по вложенным атрибутам и вернуть ее гуид
//rootNode - JSON с ответом подсистемы машинного обучения
//aggrPath - путь к агрегации из идентификаторов, для которой нужно найти агрегируемую позицию
//aggrAttrId - числовой идентификатор атрибута
String getAggregationItemUid(def rootNode, String aggrPath, List<String> aggrScope) {
//Проверим что область агрегации где будем искать не пуста
if (aggrScope.size()==0)
throw new Exception('не определена область для агрегации "'+aggrPath+'"');
//Получим список вложенных в агрегацию атрибутов
def aggrAttrs = rootNode.extracted_attrs.findAll {it.attr_id.contains(aggrPath)};
//Здесь будем хранить список обработанных вложенных агрегаций
List<String> aggrProcessed = [];
//Соберем поисковый запрос
SearchParamsBuilder SearchBuilder=support.findInNodes(aggrScope);
for (int i = 0; i < aggrAttrs.size(); i++) {
//Получим полный путь к вложенному атрибуту
String attrPath = aggrAttrs[i].attr_id;
//Удалим из путя во вложенный атрибут, путь до агрегации
String NestedAttr = attrPath.minus(aggrPath+'->');
if ((aggrAttrs[i].attr_value != '')&&(aggrAttrs[i].attr_value != null)) {
//Если во вложенном атрибуте есть еще вложенные атрибуты, то обработаем их
if (NestedAttr.indexOf('->')>0)
{
String nestedAggrPropUid = Value.subString(0,NestedAttr.indexOf('->'));
if (!aggrProcessed.contains(nestedAggrPropUid))
{
//Получим числовой идентификатор свойства
Long nestedAggrPropId = support.getPropertyId(nestedAggrPropUid);
//Получим область поиска агрегации
List<String> nestedAggrScope = support.getPropertyScopeInNode(nestedAggrPropId,root.getMdmNodeId());
//Получим агрегируемую позицию, найденную по вложенным атрибутам
String AggregationItemUid=getAggregationItemUid(rootNode, aggrPath+'->'+nestedAggrPropUid, nestedAggrScope);
//Добавляем критерий поиска
if (AggregationItemUid!=null)
SearchBuilder = SearchBuilder.byValue(AggregationItemUid, nestedAggrPropId);
else
throw new Exception('не удалось найти позицию для агрегации "'+aggrAttrs[i].nestedAggrPropUid+'"');
}
}
//Если вложенный атрибут является простым атрибутом и значение не пустое, то добавим его в критерии поиска
else
{
PropertyInfoDto searchedProp = support.getPropertyInfoByUid(NestedAttr);
String searchedVal = aggrAttrs[i].attr_value.toString();
//Добавляем критерий поиска
SearchBuilder = SearchBuilder.byValue(searchedVal, searchedProp.id.toString());
}
}
//Выполним поиск по сформированным критериям
List<String> itemUids = SearchBuilder.byStatus(ItemStatus.Standardized).invoke();
//Если нашлась одна единственная позиция, то возвращаем ее
if (itemUids.size()==1)
return itemUids[0];
}
//Вернем пустоту, если ничего не нашли
return null;
}