Пример обогащения позиции данными, полученными из ML-сервиса
Данный пример наглядно демонстрирует решение следующих задач:
-
подключение к ML-сервису и выполнение предсказания по наименованию позиции;
-
чтение предсказанных данных и заполнение атрибутов позиции данными;
-
поиск позиций во вспомогательных справочниках и запись ссылки на найденную позицию в атрибут связи.
Разработанный groovy-скрипт должен быть размещен в постобработчике на изменение значения атрибута “Полное наименование” справочника “Единый справочник номенклатуры”.
Сценарий работы пользователя: пользователь создает новую позицию и в карточке новой позиции в атрибут “Полное наименование” вводит требуемое полное наименование и нажимает кнопку запуска пост-обработчика. Система выполняет предсказание через ML-сервис, выполняет автоматическое заполнение атрибутов новой позиции. Пользователь корректирует неверно предсказанные значения атрибутов, после чего проводит дозаполнение требуемых атрибутов и публикует пакет изменений.
Скрипт постобработчика
// Предсказание значений атрибутов по полному наименованию
import groovy.json.JsonSlurper
String postProcessValue(String value){
// value - изменённое значение
// @[...] - атрибутика обрабатываемой позиции
// @[#currentuser][...] - атрибутика пользователя, выполняеющего функцию
// вернуть строку!=null, если требуется сигнализировать ошибку
String EI_uid=null;
String DoubleVal=null;
//Получим полное наименование, которое ввел пользователь
String FullName = value;
// Получить из сервиса машинного обучения предсказанные значения атрибутов, на основе полного наименование объекта(FULL_NAME)
HttpURLConnection connection = new URL("https://ml.sdi-solution.ru/extract1?full_name=" +
ru.sdi.m2.resources.util.StringUtil.urlEncod e(FullName)).openConnection() as HttpURLConnection;
//Выполним парсинг JSON, с результатом предсказания
def rootNode = new JsonSlurper().parseText(connection.inputStream.text);
//Получим гуид группы, в которой требуется создать позицию
//гуид группы предсказывается атрибутом attr_name=="groupId". Идентификатор даннаго атрибута attr_id='31415926'
String NodeUid = rootNode.extracted_attrs.find {it.attr_name=="groupId"}.attr_value;
//Убедимся что предсказанная группа и текущая группа совпадают
String curNodeUid = support.getItemNodeUid(scriptData.getItemUid(null));
if (NodeUid != curNodeUid)
return "Предсказанная машинным обучением группа не совпадает с текущей. Выберите группу с GUID: "+NodeUid;
//Получим список продекларированных атрибутов в предсказанной группе
List<PropertyDeclarationInfoDto> nodeDeclarations = support.getPropertyDeclarationsByNodeUid(NodeUid,false);
//Получим построитель запроса на изменение значений текущей позиции
UpdateItemBuilder itemValueUpdates = postOperation.updateItem(scriptData.getItemUid(null));
List<PropertyDeclarationInfoDto> processedDeclarations = [];
//Пройдемся в цикле по извлеченным атрибутам
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')
{
//Если атрибут связь
int delimPos=PropPath.indexOf('->');
if (delimPos>0)
{
//Получим гуид агрегации(атрибут связи) из пути
String AggrPropUid = PropPath.substring(0,delimPos);
PropertyDeclarationInfoDto attrDeclaration = nodeDeclarations.find {element -> element.uuid == AggrPropUid};
//Если агрегация с таким гуид не существует, то выдаем ошибку
if (attrDeclaration == null)
continue; //return 'не удалось найти декларацию атрибута связи с гуид "'+AggrPropUid+'"';
//Для агрегации может быть предсказано несколько вложенных атрибутов,
//поэтому одну и ту же корневую
if (!processedDeclarations.contains(attrDeclaration))
{
processedDeclarations.add(attrDeclaration);
//Найдем все атрибуты, для обрабатываемой агрегации
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('->'))+'" не задана';
String aggrItemUid = getAggregationItemUid(rootNode, AggrPropUid, aggrScope);
//Зададим новое значение для атрибута связи
if (aggrItemUid!=null)
itemValueUpdates.set(aggrItemUid, attrDeclaration.id)
else
itemValueUpdates.clear(attrDeclaration.id);
//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)||(attrDeclaration.uuid=='98fb1b1d-89e6-475c-b2fb-64e6b5424eaa'))
continue; //return 'Не удалось найти атрибут с гуид "'+PropPath+'"';
//Зададим новое значение для простого атрибута
if ((PropValue!=null)&&(PropValue!=""))
itemValueUpdates.set(PropValue, attrDeclaration.id);
else
itemValueUpdates.clear(attrDeclaration.id);
}
}
}
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 = NestedAttr.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;
}