Технические лайфхаки
Сбор данных

ADVANTA и Analytic Workspace: как организовать обмен данными между платформами?

ADVANTA — российская информационная система управления портфелями, программами, проектами, мероприятиями и задачами. Готовая проектная ERP-система, которая покрывает все процессы проектного управления, автоматизирует работу всех участников проектной деятельности, позволяет планировать и контролировать ресурсы проектов.

Интеграция ADVANTA с Analytic Workspace открывает новые горизонты для анализа данных и принятия решений. Синергия 2 систем позволяет строить дашборды в AW BI на основе данных из ADVANTA.

Важно! Для реализации обмена у вас должен быть доступ, по крайней мере, к демо-версиям данных продуктов.

Подробно расскажем, как организовать обмен данными между ADVANTA и Analytic Workspace.

1) Сформировать в ADVANTA LINQ-запросы

Интеграция с Analytic Workspace организована с использование LINQ-запросов и Web-API ADVANTA. Таким образом, для того, чтобы загрузить данные в BI-систему, необходимо предварительно сформировать LINQ-запросы внутри ADVANTA для получения тех данных, которые в дальнейшем будут визуализированы.

Пример LINQ-запроса данных по проектам
/* 1. перечень всех проектов (2 типа объектов - ИТ-проект и Организационный проект)
с UID и всеми системными реквизитами (сроки начала/завершения - план, факт, ответственные (ФИО), статус (текстом), последний базовый план)
- объект-родитель (название)
и пользовательскими реквизитами:
- жизненный цикл проекта
- описание
- масштаб проекта (текстовое значение)
- эффект
- последняя запись из справочника "Отчет о статусе" - все поля
- активная запись из справочника "Бюджет проекта" - только поле "Бюджет (план)"
*/
 
var projects = dataContext.Projects
      .Where(p => p is IT_proekt_d46b6d || p is Organizacionnij_proekt_a3e2fb)
      .OrderBy(p => p.CreationDate)
      .Select(p => new { p.Id, 
                          p.Name, ParentName = p.Parent.Name,
                          LifeCycleName = p.Fields.Zhiznennij_cikl_proekta_b07f57.Name,
                          p.SystemStartDate, p.SystemEndDate, p.ActualStartDate, p.ActualEndDate,
                          p.BaselinePlanStartDate, p.BaselinePlanEndDate,
                          BossFIO = String.Concat( p.Owner.LastName, " ", p.Owner.FirstName), RPFIO = String.Concat( p.Responsible.LastName, " ", p.Responsible.FirstName),
                          Description = p.Fields.Opisanie_caaaaa,
                          ProjectScaleName = p.Fields.Masshtab_proekta_04412f.Name,
                          ProjectGainName = p.Fields.Effekt_b6c145.Name,
                          StatusReport_Date = p.GetChildren<Otchet_o_statuse_440616>().OrderByDescending(o => o.Date).FirstOrDefault().Date,
                          StatusReport_Chto_sdelano_problemi_riski = p.GetChildren<Otchet_o_statuse_440616>().OrderByDescending(o => o.Date).FirstOrDefault().Chto_sdelano_problemi_riski_4a11b8,
                          StatusReport_Prichina_problemi = p.GetChildren<Otchet_o_statuse_440616>().OrderByDescending(o => o.Date).FirstOrDefault().Prichina_problemi_a3e564.Name,
                          StatusReport_StatusName = p.GetChildren<Otchet_o_statuse_440616>().OrderByDescending(o => o.Date).FirstOrDefault().Status_e05ba3.Name, 
                          BudjetSum = p.GetChildren<Byudzhet_plan_e5bee7>().Where(b => b.Aktivnostj_versii_281a38.Id == Classifier_Aktivnostj_versii_625508.Aktivnaya_4f5ba775_Id).Sum(b => b.Summa_06ff93)
                        });
return projects;
Пример LINQ-запроса данных по контрольным точкам
/*
2. перечень всех контрольных точек (1 тип объекта - КТ0)
с UID и всеми системными реквизитами (как и у проекта),
- UID - проекта-родителя (непрямой родитель)
и пользовательские поля:
- описание (текст)
- результат (текст)
- последняя запись из справочника "Отчет о мероприятии" - все поля
*/
var projects = dataContext.KT0_1ff431_List
      .Select(p => new { 
      				p.Id, 
      				p.Name,
                    ProjectId = (Guid?)p.GetParentHierarchy<Project>(false).Where(p => p is IT_proekt_d46b6d || p is Organizacionnij_proekt_a3e2fb).FirstOrDefault().Id,
                    // ProjectName = p.GetParentHierarchy<Project>(false).Where(p => p is IT_proekt_d46b6d || p is Organizacionnij_proekt_a3e2fb).FirstOrDefault().Name,
                    p.SystemStartDate, p.SystemEndDate, p.ActualStartDate, p.ActualEndDate,
                    p.BaselinePlanStartDate, p.BaselinePlanEndDate,
                    BossFIO = String.Concat( p.Owner.LastName, " ", p.Owner.FirstName), RPFIO = String.Concat( p.Responsible.LastName, " ", p.Responsible.FirstName),
                    Description = p.Opisanie_caaaaa,
                    Result = p.Rezuljtat_8e716f,
                    StatusReport_Date = p.GetChildren<Otchet_o_statuse_meropriyatiya_2bc942>().OrderByDescending(o => o.Date).FirstOrDefault().Date,
                    StatusReport_Chto_sdelano_problemi_riski = p.GetChildren<Otchet_o_statuse_meropriyatiya_2bc942>().OrderByDescending(o => o.Date).FirstOrDefault().Chto_sdelano_problemi_riski_4a11b8,
                    StatusReport_Prichina_problemi = p.GetChildren<Otchet_o_statuse_meropriyatiya_2bc942>().OrderByDescending(o => o.Date).FirstOrDefault().Prichina_problemi_a3e564.Name,
                    StatusReport_StatusName = p.GetChildren<Otchet_o_statuse_meropriyatiya_2bc942>().OrderByDescending(o => o.Date).FirstOrDefault().Status_e05ba3.Name
      });
return projects;
Данный код запросов приведен в качестве примера. На вашей инсталляции необходимо будет указать наименования сущностей, объектов, справочников, реквизитов, соответствующих сформированному контексту LINQ-запросов.
2) Создать шаблоны таблиц данных в формате .xlsx

Данные шаблоны необходимы для формирования структуры хранения данных в AW BI в соответствии со структурой получаемых их ADVANTA данных.

Для создания шаблона необходимо выполнить созданный LINQ-запрос в ADVANTA, скопировать шапку полученной таблицы в Excel и сохранить файл в формате .xlsx.
Пример шаблона для источника данных по проектам
Пример шаблона для источника данных по контрольным точкам
Загрузите данные шаблоны в Analytic Workspace, в раздел «Источники данных» — здесь необходимо создать источник, выбрать формат «Файл» и загрузить сформированный шаблон.
3) Создать модель в Analytic Workspace и сформировать скрипт в ETL-редакторе

После создания источника необходимо в соответствующем разделе Analytic Workspace создать модель, которая и будет служить витриной данных для созданного источника. Необходимо создать столько же моделей, сколько предполагается использовать LINQ-запросов.
Сразу после создания необходимо перейти в режим редактирования модели и зайти в ETL-редактор.
В открывшемся редакторе необходимо ввести скрипт для обработки получаемых из ADVANTA данных. Скрипт написан на Python.

Пример скрипта для проектов
# -----------------------------------------------------------------------------------
# Cкрипт для обработки модели * * *
# -----------------------------------------------------------------------------------
 
import requests
import datetime
from pyspark.sql import Row
 
# python - m pip install requests, matplotlib, pandas
 
from pandas import json_normalize 
 
def after_all(df, spark, app, *args, **kwargs):
    print(df.schema)
 
    LOGIN = '* * *'
    PASSWORD = '* * *'
    DOMAIN = 'https://* * *.ru'
 
    AUTH = {
    'Login': LOGIN,
    'Password': PASSWORD,
    }
 
    session = requests.Session()
    # cookies = session.cookies.get_dict() # {}
 
    # авторизация
    response = session.post(url=DOMAIN+'/api/auth/login', json=AUTH, verify=False)
 
    cookies = session.cookies.get_dict()
 
    # print(cookies)
 
    # получение LINQ-запроса
    LINQ = {
        'DataSourceKey': 'BI_projects',
        # нужен другой запрос для перечня проектов в модель list1
        # 'DataSourceKey': 'BI_projects', 
        # и еще один для перечня КТ из этих проектов в модель list1_ywrq
        # 'DataSourceKey': 'BI_milestones', 
        'PageSize': 100,     
    }
 
    r = session.post(url=DOMAIN+'/api/queries/get', cookies=cookies, json=LINQ, verify=False)
 
    if not r.ok:
        raise Exception(f'Ошибка в {r.url}. HTTP {r.status_code}: {r.text}')
 
    data = r.json()
 
    #Создадим список, который послужит основной для создания DataFrame
    rows = []
    for line in data:
        rows.append(Row(
            id=line['Id'],
            name=line['Name'],
            parentname=line['ParentName'],
            lifecyclename=line['LifeCycleName'],
            # systemstartdate=str(datetime.datetime.fromisoformat(line['SystemStartDate']).replace(tzinfo=datetime.timezone.utc)),
            # systemenddate=str(datetime.datetime.fromisoformat(line['SystemEndDate']).replace(tzinfo=datetime.timezone.utc)),
            # actualstartdate=str(datetime.datetime.fromisoformat(line['ActualStartDate']).replace(tzinfo=datetime.timezone.utc)),
            # actualenddate=str(datetime.datetime.fromisoformat(line['ActualEndDate']).replace(tzinfo=datetime.timezone.utc)),
            # baselineplanstartdate=str(datetime.datetime.fromisoformat(line['BaselinePlanStartDate']).replace(tzinfo=datetime.timezone.utc)),
            # baselineplanenddate=str(datetime.datetime.fromisoformat(line['BaselinePlanEndDate']).replace(tzinfo=datetime.timezone.utc)),
 
            systemstartdate=line['SystemStartDate'],
            systemenddate=line['SystemEndDate'],
            actualstartdate=line['ActualStartDate'],
            actualenddate=line['ActualEndDate'],
            baselineplanstartdate=line['BaselinePlanStartDate'],
            baselineplanenddate=line['BaselinePlanEndDate'],
 
            bossfio=line['BossFIO'],
            rpfio=line['RPFIO'],
            description=line['Description'],
            projectscalename=line['ProjectScaleName'],
            projectgainname=line['ProjectGainName'],
 
            # statusreport_date=str(datetime.datetime.fromisoformat(line['StatusReport_Date']).replace(tzinfo=datetime.timezone.utc)),
            statusreport_date=line['StatusReport_Date'],
 
            statusreport_chto_sdelano_problemi_riski=line['StatusReport_Chto_sdelano_problemi_riski'],
            statusreport_prichina_problemi=line['StatusReport_Prichina_problemi'],
            statusreport_statusname=line['StatusReport_StatusName'],
            budjetsum=line['BudjetSum']
        ))
 
    return spark.createDataFrame(rows)
Пример скрипта для контрольных точек
# # -----------------------------------------------------------------------------------
# Cкрипт для обработки модели * * *
# -----------------------------------------------------------------------------------
 
import requests
import datetime
from pyspark.sql import Row
from pandas import json_normalize 
 
def after_all(df, spark, app, *args, **kwargs):
 
    LOGIN = '* * *'
    PASSWORD = '* * *'
    DOMAIN = 'https://* * *.ru'
 
    AUTH = {
    'Login': LOGIN,
    'Password': PASSWORD,
    }
 
    session = requests.Session()
    # cookies = session.cookies.get_dict() # {}
 
    # авторизация
    response = session.post(url=DOMAIN+'/api/auth/login', json=AUTH, verify=False)
 
    cookies = session.cookies.get_dict()
 
    # print(cookies)
 
    # получение LINQ-запроса
    LINQ = {
        'DataSourceKey': 'BI_milestones',
        'PageSize': 1000,     
    }
 
    r = session.post(url=DOMAIN+'/api/queries/get', cookies=cookies, json=LINQ, verify=False)
 
    if not r.ok:
        raise Exception(f'Ошибка в {r.url}. HTTP {r.status_code}: {r.text}')
 
    data = r.json()
 
    #Создадим список, который послужит основной для создания DataFrame
    rows = []
    for line in data:
        print(line)
        rows.append(Row(
            id=line['Id'],
            name=line['Name'],
            projectid=line['ProjectId'],
            systemstartdate=line['SystemStartDate'],
            systemenddate=line['SystemEndDate'],
            actualstartdate=line['ActualStartDate'],
            actualenddate=line['ActualEndDate'],
            baselineplanstartdate=line['BaselinePlanStartDate'],
            baselineplanenddate=line['BaselinePlanEndDate'],
            bossfio=line['BossFIO'],
            rpfio=line['RPFIO'],
            description=line['Description'],
            result=line['Result'],
            statusreport_date=line['StatusReport_Date'],
            statusreport_chto_sdelano_problemi_riski=line['StatusReport_Chto_sdelano_problemi_riski'],
            statusreport_prichina_problemi=line['StatusReport_Prichina_problemi'],
            statusreport_statusname=line['StatusReport_StatusName']
        ))
 
    return spark.createDataFrame(rows)
Символами * * * в примерах скриптов обозначены автоматически формируемые данные (например, номер модели) или данные, имеющие отношение к конкретной учетной записи в ADVANTA (например, логин, пароль и домен).
Затем необходимо опубликовать скрипт и обновить модель. После обновления данные из ADVANTA автоматически загрузятся в модель.

Пример модели
4) Создать виджеты для отображения данных и дашборды из виджетов

Если у вас остались вопросы по реализации данного процесса или любые другие, вы можете задать их в нашем Telegram-сообществе AW.community.

Пример дашборда «Здоровье портфеля» в Analytic Workspace на основе данных, полученных из ADVANTA.