Общие сведения

Python-скрипт (далее – скрипт) обеспечивает детализацию Drill-through, выполняя её по условиям, описанным в файле конфигурации config.json (см. Детализация Drill-through).

Скрипт необходимо зарегистрировать для приложения, чтобы приложение могло с ним работать.

При регистрации скрипта необходимо указывать его название, которое в приложении отображается в выпадающем списке варианта «Связанный сценарий» контекстного меню факта (см. Детализация Drill-through).

В приложении может быть зарегистрировано сколько угодно много скриптов. Для каждого зарегистрированного скрипта обязательно должен быть предоставлен файл конфигурации config.json (далее – файл конфигурации).

Если для какого-либо зарегистрированного скрипта файл конфигурации отсутствует или имеет недопустимую структуру и детализация включена параметром конфигурации приложения plm.user_interface.scenario_hyperlink_enabled, то в ЛЮБОМ воспроизведенном сценарии, для ЛЮБОЙ его мультисферы контекстное меню факта НЕ ОТКРЫВАЕТСЯ, а в лог приложения записывается ошибка вида, соответственно:

Failed to get list of registered python scripts: linked scenario script 'Имя сценария' config file not exists
Failed to get list of registered python scripts: Rapidjson assertion error

Конфигурация зарегистрированного скрипта записывается в директорию /var/plmrepo/linkedscenarioscripts/идентификатор_конфигурации_зарегистрированного_скрипта.

Регистрировать и удалять можно только по одному скрипту за раз.

Предусловия

Перед регистрацией и удалением скрипта необходимо остановить работу приложения:

systemctl stop polymatica.service

Код скрипта

Разверните блок и скопируйте код
import argparse
import json
import os
import sys
from typing import List
from polymatica import business_scenarios
from polymatica.exceptions import PolymaticaException, RightsError, ScenarioError
 
sys.stdout = open(1, "w", encoding='utf-8', closefd=False)
 
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
 
print("script args: ", sys.argv[1:])
 
 
class KeyValue(argparse.Action):
    def __call__(self, parser, namespace,
                 values, option_string=None):
        setattr(namespace, self.dest, dict())
 
        for value in values:
            key, value = value.split('=')
 
            getattr(namespace, self.dest)[key] = value
 
 
parser = argparse.ArgumentParser()
 
parser.add_argument('--dimension-elements',
                    nargs='*',
                    action=KeyValue)
 
parser.add_argument('--connection_session', nargs=3)
parser.add_argument('--url')
parser.add_argument('--layer_id')
parser.add_argument('--scenario_id')
parser.add_argument('--cube_id')
parser.add_argument('--config_path', default=os.path.join(ROOT_PATH, 'config.json'))
 
args = parser.parse_args()
 
print("dim args: ", args.dimension_elements)
 
 
class DimFilter:
    def __init__(self, dim_id: str, filter_val: str):
        self.dim_id = dim_id
        self.filter_val = filter_val
 
 
class MapInfo:
    def __init__(self, scenario_id: str, cube_id: str, filters: List[DimFilter]):
        self.scenario_id = scenario_id  # 1. запустить
        self.cube_id = cube_id  # 2. найти на слое мультисферы этого куба
        self.filters = filters  # 3. применить к мультисферам
 
 
def err(msg):
    print(msg, file=sys.stderr)
    sys.exit(1)
 
 
def extract_map_info(cfg) -> MapInfo:
    for scenario_pair in cfg['scenarios']:
        from_sc_id = scenario_pair['from_scenario_id']
        to_sc_id = scenario_pair['to_scenario_id']
 
        if args.scenario_id == from_sc_id:
            dims_to_filter = []
            to_cube_id = 0
 
            for cube_pair in scenario_pair['cubes']:
                if cube_pair['from_cube_id'] == args.cube_id:
                    to_cube_id = cube_pair['to_cube_id']
 
                    for dim in cube_pair['dimensions']:
                        from_dim_id = dim['from_dimension_id']
                        to_dim_id = dim['to_dimension_id']
 
                        if from_dim_id in args.dimension_elements.keys():
                            dim_filter = DimFilter(dim_id=to_dim_id,
                                                   filter_val=args.dimension_elements[from_dim_id].encode('utf8',
                                                                                                          'surrogateescape').decode(
                                                       'utf8'))
 
                            dims_to_filter.append(dim_filter)
 
                    break
 
            return MapInfo(filters=dims_to_filter, cube_id=to_cube_id, scenario_id=to_sc_id)
 
    raise Exception("scenario pair not found")
 
 
try:
    with open(args.config_path) as f:
        json_config = json.load(f)
except OSError:
    err("cannot open {}".format(args.config_path))
 
try:
    map_info = extract_map_info(json_config)
except KeyError as e:
    err("bad config: {}".format(e))
 
except Exception as e:
    err(e)
 
if len(map_info.filters) != len(args.dimension_elements):
    err("some pairs for dimensions \"{}\" not specified in config".format(args.dimension_elements.keys()))
 
try:
    session_params = {"session_id": args.connection_session[0],
                      "manager_uuid": args.connection_session[1],
                      "full_polymatica_version": args.connection_session[2]}
 
    bs = business_scenarios.BusinessLogic("", session_auth=session_params, url=json_config['connection']['url'])
except Exception as e:
    err(e)
 
print("starting scenario \"{}\" on layer \"{}\"".format(map_info.scenario_id, args.layer_id))
 
try:
    bs.run_scenario_on_layer(scenario_id=map_info.scenario_id, layer_id=args.layer_id)
except RightsError as e:
    err("Не удалось запустить связанный сценарий: " + e.user_msg)
except ScenarioError as e:
    err("Не удалось запустить связанный сценарий: " + e.user_msg)
except Exception as e:
    err("Не удалось запустить связанный сценарий")
 
print("scenario \"{}\" successfully finished on layer \"{}\"".format(map_info.scenario_id, args.layer_id))
 
modules_to_filter = []
for module_info in bs.get_module_list():
    if module_info[3] == map_info.cube_id:
        modules_to_filter.append(module_info[0])
 
if len(modules_to_filter) == 0:
    err("cannot find multispheres created from cube with id \"{}\" on scenario \"{}\"".format(map_info.cube_id,
                                                                                              map_info.scenario_id))
 
for module_id in modules_to_filter:
    bs.set_multisphere_module_id(module_id)
 
    for filter_to_put in map_info.filters:
        try:
            bs.put_dim_filter_by_value(dim_id=filter_to_put.dim_id, value=filter_to_put.filter_val)
            print(
                "putted filter with value - \"{}\" on dim with id - \"{}\"".format(filter_to_put.filter_val,
                                                                                   filter_to_put.dim_id))
 
        except PolymaticaException as e:  # если фильтр исключен другим фильтром то игнорим его
            print(
                "skipped filter with value - \"{}\" on dim with id - \"{}\"".format(filter_to_put.filter_val,
                                                                                    filter_to_put.dim_id))

Регистрация скрипта

1. Создайте в системе хоста приложения директорию /var/plmrepo/pyscripts/директория_python-скрипта, создайте в ней файл с расширением .py, и вставьте в него код скрипта. Например:

/var/plmrepo/pyscripts/drill_through_on_population_ms/script.py

2. Откройте на редактирование файл plm-util.config:

nano /etc/polymatica/plm-util.conf

Добавьте в него строки:

create_pyscript
pyscripts.create.name = Детализация по МС "Население"
pyscripts.create.type = linked_scenario
pyscripts.create.script = /var/plmrepo/pyscripts/drill_through_on_population_ms/script.py

где name – название, которое отображается во вложенном меню варианта «Связанный сценарий»;
      type – тип регистрируемого скрипта, в данном случае всегда linked_scenario;
      script – путь до регистрируемого скрипта из шага 1.

3. Сохраните изменение в plm-util.config и выполните команду:

plm-util -v

При успешном выполнении команды возвращается ответ вида:

[08:41:11.712][    info][util] Creating new python script resource
...
[08:41:11.713][    info][util] Using following parameters:
[08:41:11.713][   debug][util] -- pyscripts.create.name = Детализация по МС "Население"
[08:41:11.713][   debug][util] -- pyscripts.create.type = linked_scenario
[08:41:11.713][   debug][util] -- pyscripts.create.script = /var/plmrepo/pyscripts/drill_through_on_population_ms/script.py
[08:41:11.713][   trace][util:RM] Request to create linked scenario python script 00000000 by aaaaaaaa-aaaaaaaa-aaaaaaaa-aaaaaaaa
[08:41:11.713][   trace][util:RS] Request to save c4684bf2 into "/var/plmrepo/linkedscenarioscripts/c4684bf2"
[08:41:11.714][   trace][util:RI] Adding entry for c4684bf2 type 276 saved at "/var/plmrepo/linkedscenarioscripts/c4684bf2"
[08:41:11.714][   trace][util:RI] Saving entry to disk c4684bf2
[08:41:11.714][    info][util] Dump of created python script 'c4684bf2':
[08:41:11.714][    info][util] >>
[08:41:11.714][    info][util] -- name: Детализация по МС "Население"
[08:41:11.714][    info][util] -- id: c4684bf2
[08:41:11.714][    info][util] -- creator: plm-util
[08:41:11.714][    info][util] -- created at: Thu, 28 Mar 2024 08:41:11 +0000
[08:41:11.714][    info][util] -- updated at: Thu, 28 Mar 2024 08:41:11 +0000
[08:41:11.714][    info][util] -- type: linked_scenario
[08:41:11.714][    info][util] -- python script path: "/var/plmrepo/pyscripts/drill_through_on_population_ms/script.py" (exists: true)
[08:41:11.714][    info][util] <<

На этом шаге регистрация скрипта завершена. Можно запустить приложение:

systemctl start polymatica.service

Обратите внимание!

  • После регистрации скрипта нужно убрать из файла plm-util.config добавленные на шаге 2 строки, иначе алгоритм будет выполняться при каждом вызове plm-util.
  • Чтобы детализация начала работать, её необходимо включить параметром конфигурации приложения plm.user_interface.scenario_hyperlink_enabled.
  • В директории зарегистрированного скрипта обязательно должен находиться файл конфигурации. Зарегистрированный скрипт без файла конфигурации блокирует работу контекстного меню факта для всех воспроизведенных сценариев (см. Общие сведения).

Удаление скрипта

1. Откройте на редактирование файл plm-util.config:

nano /etc/polymatica/plm-util.conf

Добавьте в него строки:

delete_pyscript
pyscripts.delete.by_id = c4684bf2

где by_id – идентификатор конфигурации зарегистрированного скрипта. В примере с регистрацией идентификатор конфигурации "c4684bf2". Идентификатор можно найти в директории /var/plmrepo/linkedscenarioscripts/.

2. Выполните команду:

plm-util -v

При успешном выполнении команды возвращается ответ вида:

[08:36:42.171][    info][util] Removing python script resource
...
[08:36:42.172][    info][util] Using following parameters:
[08:36:42.172][   debug][util] -- pyscripts.delete.by_name =
[08:36:42.172][   debug][util] -- pyscripts.delete.by_id = c4684bf2
[08:36:42.172][    info][util] Deleting python script resource by id 'c4684bf2'
[08:36:42.172][   trace][util:RS] Request to erase c4684bf2 at "/var/plmrepo/linkedscenarioscripts/c4684bf2" (will be renamed instead of erasing)

На этом шаге удаление скрипта завершено. Можно запустить приложение:

systemctl start polymatica.service

Внимание!

После удаления скрипта нужно убрать из файла plm-util.config добавленные на шаге 1 строки, иначе алгоритм будет выполняться при каждом вызове plm-util.

Известные проблемы

Детализация не работает с ошибкой «Не удалось запустить связанный сценарий»

Вариант 1

Если в логе уровня plm.log.main.loglvl = 0 есть ошибка вида:

ModuleNotFoundError: No module named 'polymatica'

выполнить в консоли команду:

pip3 install polyapi --upgrade

после чего перезапустить приложение:

systemctl start polymatica.service

Вариант 2

Если в логе  уровня plm.log.main.loglvl = 0 есть ошибка вида:

some pairs for dimensions "dict_keys(...)" not specified in config

в файле конфигурации не указана одна или несколько размерностей dict_keys – должны быть указаны все размерности, элементы которых участвуют в детализации (см. Детализация Drill-through).

  • Нет меток