Jak stworzyć (inteligentne) raporty na żądanie

Dzisiaj przygotujemy(quasi) inteligentnie narzędzie do automatycznego generowania raportów, które będzie odpowiadać na pytania zadane przez email. Za jego pomocą pozbędziemy się upierdliwej pracy. Wraz z aktualizacją pakietu gmailr do wersji 1.0 prezentowany poniżej kod związany z logowaniem do konta może nie działać. Przeczytaj proszę informację o tym, jak zmienić kod, ten na GitHubie powinien być ok.

Łukasz Prokulski. Data analyst / R developer / PMO Manager w PZU. Uwielbia zajmować się liczbami, ich wizualizacją i wyciąganiem wniosków na podstawie ich analizy. Zna doskonale Excela, potrafi przygotować raporty w kilku narzędziach, które wykorzystują dane z różnych źródeł. Jest fanem i popularyzatorem języka R, czego dowody znajdziecie na blogu.


Problem z jakim można się często spotkać w korporacjach to wysyłanie tych samych raportów mailem. Wyobraźmy sobie taki scenariusz:

  • są trzy biura sprzedaży, każde jest odpowiedzialne za inny region,
  • jesteś specem od generowania raportów ze sprzedaży dla tych biur,
  • wszystkie raporty są takie same, różnią się tylko regionem,
  • co chwilę ktoś pyta: podeślij mi aktualny raport dla mojego regionu,
  • więc generujesz ten raport i odsyłasz mailem w odpowiedzi.

Znasz to? Ja znam. Upierdliwe jak cholera. Można zrobić jakiś dashboard (w Shiny czy innym ustrojstwie), ale ludzie zapominają, gdzie można go znaleźć (jak zrobiłem kilka takich, to teraz dzwonią z pytaniem o link do tych dashboardów…). A gdyby tak samo się robiło? Z aktualnych danych? Dzisiaj coś takiego napiszemy!

Założenie jest takie, że pytanie należy zadać poprzez wysłanie maila o odpowiednim tytule na odpowiednią skrzynkę mailową.

Zaczniemy od algorytmu:

  • sprawdź, czy są nowe pytania na skrzynce,
  • sprawdź, czego dotyczy pytanie (jaki temat ma mail),
  • przygotuj odpowiedni raport,
  • odeślij do pytającego.

W pierwszej kolejności trzeba dostać się do skrzynki i sprawdzić czy są nowe maile. Już prawie rok temu o tym pisałem, wykorzystałem wówczas pakiet gmailr. Tym razem będzie tak samo, też wykorzystamy konto na Gmailu.

W przypadku korporacyjnej poczty trzeba użyć innych rozwiązań, ale na pewno się da. Osobiście tego nie badałem, ale na StackOverflow jest kod z użyciem Pythona (osadzony przez rJython – można przepisać na reticulate) oraz jest pakiet imapr, który robi coś ze skrzynkami IMAP.

Wróćmy jednak do Gmaila. We wspomnianym wpisie znajdziecie minimalne informacje jak w konsoli dla developerów wyklikać odpowiednią aplikację pozwalającą na dostęp do skrzynki poprzez Gmail API – potrzebujemy odczytywać, wysyłać i modyfikować (przenosić do kosza) maile. Odpowiednie uprawnienia dostaniemy w pliku JSON (w kodzie niżej jest to plik gmail_api_key.json).

Żeby cała maszyneria działała potrzebujemy:

  • dostępu do skrzynki Gmail po API,
  • serwera z Linuxem (z takiego założenia wychodzę) albo czegoś, co pozwoli nam uruchomić w zadanych odstępach czasu skrypty R – potrzebny będzie więc cron albo inny task scheduler (może być w Windowsie),
  • skonfigurowanego dla R renderera PDFów z plików markdown (przy instalacji RStudio to się chyba samo instaluje w Linuxie, na Windows zdaje się trzeba doinstalować ręcznie pandoc).

Teraz czas na kodowanie. Najpierw autoryzujemy się w Gmailu i pobieramy najnowsze maile (skrypt process_inbox.R):

Mając identyfikatory maili możemy pobrać interesujące nas informacje – wystarczy nadawca i temat. Odpowiednia funkcja zrobi to dla podanego ID:

Teraz możemy przepuścić wszystkie identyfikatory maili przez tę funkcję:

Jeśli nie dbamy o inbox:0 to chwilę to może potrwać (chyba, że odpowiednio skonstruowaliśmy filtr)… ale po to był warunek wyszukiwania ograniczający zapytanie do jedynie dzisiejszych maili, co w zupełności wystarczy, bo sprawdzanie skrzynki będzie odbywało się często (co kilka minut).

Zatem mamy listę maili ze skrzynki. W założeniu przyjęliśmy, że pytania zawarte są w tytule maila. Zanim jednak będziemy odpowiadać na maile – przygotujmy odpowiednie odpowiedzi.

Chcę pokazać Wam dwa rozwiązania – jedno odpowiedź w treści maila, a drugie – jako załącznik z pełnym raportem.

Zacznijmy od odpowiedzi w treści. Zakładać można sytuację, w której raport nie musi być wygenerowany online, bo dane są wolno zmienne. Taki wariant przyjmiemy. Odpowiedzią na pytanie daj wykop będzie lista wykopalisk nie starszych niż 12 godzin z głównej strony Wykop.pl. Przygotujmy taką dość statyczną odpowiedź (to nowy, oddzielny skrypt – make_wykop_mail.R) przy okazji poznając odrobinę API Wykopu:

Uruchomienie powyższego skryptu (w założeniu co jakiś czas) przygotuje statyczny plik z HTMLem gotowym do wklejenia w treść maila.

Drugie zadanie to raport z danych online (tutaj też trochę wolno zmiennych, ale nie ma to znaczenia). Tym razem ma to być załącznik w PDFie, więc przygotujemy odpowiedni dokument RMarkdown, który po skompilowaniu da PDFa.

Jak pewnie wiecie RMarkdown pozwala na mieszanie treści z kodem (w R, ale też np. w Pythonie przy pomocy pakietu reticulate), co więcej – wynik działania kodu pojawia się w treści dokumentu. Cały dokument bitcoin.Rmd poniżej:

Mam nadzieję, że kod jest wystarczająco skomentowany i poradzicie sobie ze zrozumieniem. Proszę zwrócić uwagę na początek:

i gdzieś dalej r params$odbiorca_raportu. To zapewni nam, że każdy raport będzie generowany inaczej, w zależności od podanego parametru odbiorca_raportu. Oczywiście parametry mogą być różne: może to być region, może to być zakres dat albo właśnie zamawiający (i na tej podstawie określamy region czy jakiś inny poziom dostępu do danych i uprawnień). Parametrów tych będziemy używać za chwilę przy renderowaniu raportu.

Nasz przykładowy raport pobiera dane z API serwisu nomics.com dotyczący notować bitcoinów (przed sprawdzeniem czy to działa potrzebujecie pliku nomics_apikey.rda z zapisaną zmienną zawierającą klucz API). Przy każdym renderowaniu dane są pobierane, nieco przetwarzane i rysowane. Wynik trafia do PDFa. O tym, że raport jest wygenerowany online w tym przypadku świadczy informacja w treści samego raportu (czas aktualizacji). Po zapisaniu tego dokumentu na dysku możecie go knit-nąć – powinien powstać gotowy dokument.

Oczywiście mogą to być inne dane – z bazy SQL, z jakiegoś Hadoopa czy Kafki. Może to być wynik działania jakiegoś modelu na zebranych danych – do wyboru, do koloru.

Krótkie podsumowanie tego co mamy w tym momencie:

  • pobieramy dzisiejsze maile z inboxa skrzynki na Gmailu,
  • generujemy statyczne kawałki HTMLa z informacjami z Wykopu,
  • mamy pełny raport (do wyrenderowania) z danych online generowany w locie do PDF.

Jesteśmy w więcej niż połowie drogi. Pozostało sprawdzenie co wysłać (na podstawie tematu otrzymanego maila), przygotowanie odpowiedzi i wysyłka. Zacznijmy od dwóch funkcji, które przygotują i wyślą nam odpowiednią treść. Piszemy więc ciąg dalszy skryptu process_inbox.R.

Najpierw to co prostsze – Wykop:

Druga wersja wymaga wygenerowania dedykowanego (pamiętajcie o parametrach!) PDFa:

Wszystko gotowe, tylko nie działa. Dlaczego? Ano dlatego, że jeszcze nie sprawdzamy czy mamy odpowiednie pytanie w tytule maila oraz nie reagujemy na nie. Prosta pętla, która przejdzie przez wszystkie maile na koniec skryptu process_inbox.R:

Już za chwileczkę, już za momencik zacznie się kręcić! Właściwie już działa, o ile poczynimy ręcznie następujące kroki (w tej właśnie kolejności):

  • uruchomimy make_wykop_mail.R żeby przygotować treść maila dotyczącego Wykopu,
  • wyślemy sobie (na skrzynkę do której mamy dostęp przez API Gmaila) wiadomość z tytułem “daj wykop”,
  • wyślemy sobie maila z tytułem “daj bitcoin”,
  • uruchomimy process_inbox.R.

W odpowiedzi powinniśmy dostać dwa maile – z newsletterem Wykopu oraz drugi z raportem w PDFie o cenie bitcoinów. No chyba, że coś nie będzie działało.

Ale przecież nie po to robi się automaty, żeby je ręcznie uruchamiać! Dodajemy więc do crona (albo innego task schedulera) dwa zadania: jedno to uruchomienie make_wykop_mail.R na przykład co 15 minut, drugie – uruchomienie process_inbox.R na przykład co 3 minuty (to określi jak często będziemy sprawdzać inbox i odpowiadać). U mnie te wpisy wyglądają tak:

Cała maszyna działa na moim serwerze (na przykład tu możesz sobie taki kupić i tak skonfigurować) i odpowiada na maile wysłane na adres prokulski@gmail.com z tytułem (bez dodatkowych znaków, spacji na końcu itd.) daj wykop lub daj bitcoin – możecie sprawdzić. Uwaga – nie wiem jak długo w cronie to będzie wisiało, pewnie kiedyś wyłączę.

Gotowe pliki znajdziecie w repozytorium automatic_reports na moim GitHubie. Jeśli budujecie to rozwiązanie u siebie to warto w odpowiednich skryptach zadbać o to, aby pliki zapisywały i pobierały się z odpowiednich folderów (zwykłe i niezbyt koszerne setwd() na początek skryptów wystarczy). Oczywiście trzeba dostosować odpowiednio też wpisy w cronie.

Można przygotować kolejne rozwiązania:

  • sprawdzać treść maila, a nie tylko jego tytuł. A w treści, w kolejnych liniach mogą być na przykład określone parametry,
  • można pobierać dane z załączników przychodzących maili i przetwarzać te załączniki: wyobrażam sobie, że wysyłamy plik Excela, w treści maila piszemy, które kolumny nas interesują, a w odpowiedzi dostajemy stosowny wykres z danych zawartych w Excelu,
  • można zaprząc sztuczną inteligencję czy jakieś metody machine learningowe do analizowania treści maila i na podstawie tej analizy odpowiadać – automatyczne, inteligentne odpowiedzi w biurach obsługi klienta to byłoby coś!

Inspiracją do tego wpisu był tekst Automated Report Generation with Papermill oraz jego druga część, gdzie podobny problem rozwiązany jest inaczej (według mnie nieco gorzej).

Jeśli Ci się podobało albo przyda się do czegoś to podziel się wpisem ze światem (odpowiednie guziczki poniżej). Wpadnij też na Dane i Analizy na Facebooku – tam więcej takich smaczków (szczególnie dla praktyków). Nieco więcej smaczków znajdziesz też w nie-tak-bardzo cyklicznym newsletterze, którego archiwum tutaj.

Możesz też rzucić piniądz autorowi, czy tam postawić witrualną kawę – czy Wykop daje Ci newsletter? No właśnie. A serwerki same się nie opłacą…


Artykuł został pierwotnie opublikowany na blog.prokulski.science. Zdjęcie główne artykułu pochodzi z unsplash.com.

 

Patronujemy

 
 
Polecamy
Wyniki rekrutacji Ruby Developera powyżej oczekiwań. Case study DataFeedWatch