Códigos do

Tenho algumas centenas de horas de aulas gravadas ensinando algoritmos e estruturas de dados, padrões de projeto, Domain-driven Design e Reputação e Marketing Pessoal. Também tenho algumas centenas de horas de sessões de mentoria em arquitetura de software.

Nas minhas masterclasses, sempre vou muito além do material preparado.

Todos esses vídeos possuem ideias incríveis, mas que não são fáceis de acessar, sem que, obviamente, alguém assista todos os vídeos novamente, estruturando conteúdo de maneira interessante.

Recentemente, a Google lançou uma ferramenta incrível chamada NotebookLM. Ela permite “conversar com documentos”. Mas, até então, estes documentos precisam ser textos.

Então, o caminho tem sido gerar versões em texto dos meus vídeos. Mas, como? A resposta é utilizar tecnologias de IA como a Whisper da OpenAI.

Preparação do Ambiente

Para gerenciar as dependências de forma eficaz, recomendo criar um ambiente virtual usando conda. Primeiro, instale a última versão do Python e configure o ambiente:

Python
conda create -n transcricao_env python=3.10
conda activate transcricao_env

Instalação dos Pacotes

Com o ambiente configurado, instale os pacotes necessários:

Python
!pip install git+https://github.com/openai/whisper.git
!pip install ffmpeg-python

Passo-a-passo (Jupyter Notebook)

Importação e Carregamento do Modelo

Em um notebook (ou script Python), começo importando os pacotes necessários e carregando o modelo do Whisper. A primeira execução consome um pouco mais de tempo:

Python
import whisper
import ffmpeg
import os

model = whisper.load_model("base")

Extração do Áudio do Vídeo

Em seguida, extraímos o áudio do vídeo. Isso é feito para facilitar a transcrição, uma vez que o Whisper trabalha diretamente com arquivos de áudio:

Python
video_path = r"/Users/elemarjr/Downloads/mentoria_arquitetura_1.mp4"
audio_path = r"/Users/elemarjr/Downloads/mentoria_arquitetura_1.m4a"
ffmpeg.input(video_path).output(audio_path).run(overwrite_output=True)

Geração da Transcrição

Finalmente, obtemos a transcrição e a salvamos em um arquivo de texto:

Python
# Obter a transcrição
output_path = r"/Users/elemarjr/Downloads/mentoria_arquitetura_1.txt"
result = model.transcribe(audio_path, language='pt')

# Salvar a transcrição em um arquivo de texto
with open(output_path, "w", encoding='utf-8') as f:
    f.write(result['text'])

Pronto para o NotebookLM

Pronto. Tenho um arquivo texto com todo o conteúdo da aula que, agora, pode ser usado como source no NotebookLM.

Juntando tudo em um Utilitário de Linha de Comando

Para facilitar o uso do processo descrito acima, criei um utilitário de linha de comando em Python. Este script permite passar o caminho de um vídeo como argumento e obtém a transcrição do vídeo. Aqui está o código:

Python
import whisper
import ffmpeg
import os
import sys

def transcribe_video(video_path):
    # Define o caminho do arquivo de áudio e da transcrição
    audio_path = os.path.splitext(video_path)[0] + '.m4a'
    output_path = os.path.splitext(video_path)[0] + '.txt'

    # Extrai o áudio do vídeo
    ffmpeg.input(video_path).output(audio_path).run(overwrite_output=True)

    # Carrega o modelo Whisper
    model = whisper.load_model("base")

    # Gera a transcrição
    result = model.transcribe(audio_path, language='pt')

    # Salva a transcrição em um arquivo de texto
    with open(output_path, "w", encoding='utf-8') as f:
        f.write(result['text'])

    print(f"Transcrição salva em {output_path}")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Uso: python transcribe.py <caminho_para_o_video>")
        sys.exit(1)

    video_path = sys.argv[1]
    transcribe_video(video_path)

Para usar este utilitário, salve o código acima em um arquivo chamado transcribe.py e execute-o na linha de comando passando o caminho para o vídeo:

python transcribe.py /caminho/para/o/video.mp4

Pronto! Agora você tem um utilitário de linha de comando que transcreve vídeos automaticamente.

Possíveis Erros e Soluções

Durante o processo, alguns erros podem ocorrer. Aqui estão alguns comuns e como resolvê-los:

  • Erro de instalação de pacotes: Certifique-se de que você tem permissão para instalar pacotes e que sua conexão com a internet está estável.
  • Erro ao carregar o modelo: Verifique se você tem espaço suficiente em disco e memória disponível.
  • Erro ao extrair o áudio: Assegure-se de que o caminho do arquivo de vídeo está correto e que o ffmpeg está instalado corretamente.

Contextualização e Aplicações Práticas

A transcrição de áudio e vídeo é extremamente útil para diversas aplicações, como:

  • Análise de conteúdo: Facilita a análise de grandes volumes de informações contidas em vídeos.
  • Acessibilidade: Ajuda na criação de legendas, tornando o conteúdo acessível para pessoas com deficiência auditiva.
  • SEO e Indexação: Melhora o SEO, permitindo que os motores de busca indexem o conteúdo textual.
  • Recuperar conteúdo relevante e contextualizado: Permite encontrar rapidamente trechos importantes e contextualmente ricos dos vídeos gravados.

Com essa abordagem, posso oferecer material de qualidade para quem estuda comigo, transformando horas de vídeo em documentos de texto utilizáveis, prontos para serem analisados e manipulados em ferramentas como o NotebookLM.

Se você deseja estudar comigo e acessar material de alta qualidade e contextualizado, entre em contato. Estou sempre pronto para compartilhar conhecimento e ajudar no seu desenvolvimento profissional.

06/07/2024

WordPress é uma das plataformas mais populares do mundo para construção de Websites e blogs. A Ontologia da EximiaCo foi construída com WordPress.

Os consultores da EximiaCo colaboram com a produção de conteúdo que está devidamente organizado em classes.

Buscando dados do WordPress

O WordPress disponibiliza uma API RESTish que permite obtenção fácil de informações a respeito de um site.

Python
import pandas as pd
import requests
from cachetools import cached, TTLCache
from datetime import datetime


class WordPress:
    def __init__(self, url):
        self.url = url

    @cached(cache=TTLCache(maxsize=100, ttl=300))
    def get_post_types(self):
        endpoint = '/wp-json/wp/v2/types'
        api_url = f'{self.url}{endpoint}'

        response = requests.get(api_url)
        post_types = response.json()

        if response.status_code == 200:
            data = [{'key': key, 'name': value['name']} for key, value in post_types.items()]
            return pd.DataFrame(data)
        else:
            return pd.DataFrame()

    @cached(cache=TTLCache(maxsize=100, ttl=300))
    def get_users(self):
        api_url = f'{self.url}/wp-json/wp/v2/users'

        response = requests.get(api_url)

        if response.status_code == 200:
            users_data = response.json()
            df = pd.DataFrame(users_data)
            return df
        else:
            return pd.DataFrame()

    @cached(cache=TTLCache(maxsize=100, ttl=300))
    def get_posts(self, post_type_slug, start_date, end_date):

        start_iso = (datetime.strptime(start_date, '%Y-%m-%d')
                     .replace(hour=0, minute=0, second=0)
                     .strftime('%Y-%m-%dT%H:%M:%S'))
        end_iso = (datetime.strptime(end_date, '%Y-%m-%d')
                   .replace(hour=23, minute=59, second=59)
                   .strftime('%Y-%m-%dT%H:%M:%S'))

        endpoint = f'/wp-json/wp/v2/{post_type_slug}'
        api_url = f'{self.url}{endpoint}'

        params = {
            'after': start_iso,
            'before': end_iso,
            'date_gmt': ''
        }

        response = requests.get(api_url, params=params)
        posts = response.json()

        if response.status_code == 200 and isinstance(posts, list):
            data = [
                {'ID': post['id'],
                 'Title': post['title']['rendered'],
                 'Modified': post['modified'],
                 'Created': post['date_gmt'],
                 'AuthorId': post.get('author', 1)
                 } for post in posts]
            df = pd.DataFrame(data)
            return df
        else:
            return pd.DataFrame()

Tenho optado por retornar Dataframes do Pandas quase sempre. Isso torna a interação com os dados algo fácil.

Outra coisa que resolvi fazer nessa implementação foi utilizar um cache em memória mesmo. A ideia é reduzir a quantidade de “ataques” no servidor para informações que considero estáveis.

Facilitando as pesquisas envolvendo períodos semanais

Boa parte das consultas que irei fazer contra meu WordPress serão relacionadas a períodos semanais – ou seja, começando em um domingo e encerrando no próximo sábado. Para tornar a especificação desses intervalos mais fácil, criei uma classe helper.

Python
from datetime import timedelta, datetime


class Weeks:
    @staticmethod
    def get_week_dates(reference_date):
        start_of_week = reference_date - timedelta(days=reference_date.weekday() + 1)
        end_of_week = start_of_week + timedelta(days=6)
        return start_of_week.strftime('%Y-%m-%d'), end_of_week.strftime('%Y-%m-%d')

    @staticmethod
    def get_current():
        today = datetime.now()
        return Weeks.get_week_dates(today)

    @staticmethod
    def get_previous(n = 1):
        today = datetime.now()
        last_week_date = today - timedelta(days=7 * n)
        return Weeks.get_week_dates(last_week_date)

    @staticmethod
    def get_week_string_from_date(reference_date):
        start, end = Weeks.get_week_dates(reference_date)
        return Weeks.get_week_string_from_interval(start, end)

    @staticmethod
    def get_week_string_from_interval(start, end):
        star_f = datetime.fromisoformat(start).strftime('%d/%m')
        end_f = datetime.fromisoformat(end).strftime('%d/%m')
        return f'{star_f} - {end_f}'

Adicionando semântica

O WordPress é uma plataforma de propósito geral. A classe que desenvolvi consegue lidar bem com qualquer WordPress, mas deixa um bocado de interpretações a cargo do desenvolvedor.

Quase sempre, considero criar uma “camada semântica” sobre classes de propósito geral como a que desenvolvemos, adicionando significado específico para o contexto (domínio) em que estou trabalhando.

Python
import pandas as pd
from datetime import datetime

from src.wordpress import WordPress
from src.weeks import Weeks

class Ontologia:
    def __init__(self):
        self.wp = WordPress('https://ontologia.eximia.co')

    def get_classes(self):
        df = self.wp.get_post_types()
        classes = df[
            ~(df['key'].str.startswith('wp_') |
              df['key'].str.startswith('nav_') |
              df['key'].str.contains('jet-engine') |
              df['key'].str.contains('assets') |
              df['key'].str.contains('classes') |
              df['key'].str.contains('page') |
              df['key'].str.contains('post') |
              df['key'].str.contains('links') |
              df['key'].str.contains('media') |
              df['key'].str.contains('attachment') |
              df['key'].str.contains('visibility_preset') |
              df['key'].str.endswith('_links') |
              df['key'].str.endswith('-links')
              )
        ]
        return classes

    def get_authors(self):
        df = self.wp.get_users()
        return df[~(df['name'].str.contains('admin'))]

    def get_entries(self, start_date, end_date):
        classes_df = self.get_classes()
        entries_list = []

        for index, row in classes_df.iterrows():
            post_type_slug = row['key']
            posts_df = self.wp.get_posts(post_type_slug, start_date, end_date)

            posts_df['ClassKey'] = row['key']
            posts_df['ClassName'] = row['name']

            entries_list.append(posts_df)

        final_df = pd.concat(entries_list, ignore_index=True)
        final_df = final_df[~(final_df['AuthorId'] == 1)]

        user_name_map = self.get_authors().set_index('id')['name'].to_dict()
        final_df['AuthorName'] = final_df['AuthorId'].map(user_name_map)
        return final_df

    def get_summary_by_author(self, start_date, end_date):
        entries_df = self.get_entries(start_date, end_date)
        summary_df = entries_df.groupby('AuthorName').size().reset_index(name='Count')

        start_date_formatted = pd.to_datetime(start_date).strftime('%d/%m')
        end_date_formatted = pd.to_datetime(end_date).strftime('%d/%m')
        date_column_title = f"{start_date_formatted} - {end_date_formatted}"

        summary_df.columns = ['Author', date_column_title]

        return summary_df

    def get_summary_by_class(self, start_date, end_date):
        entries_df = self.get_entries(start_date, end_date)
        summary_df = entries_df.groupby('ClassName').size().reset_index(name='Count')

        start_date_formatted = pd.to_datetime(start_date).strftime('%d/%m')
        end_date_formatted = pd.to_datetime(end_date).strftime('%d/%m')
        date_column_title = f"{start_date_formatted} - {end_date_formatted}"

        summary_df.columns = ['Class', date_column_title]

        return summary_df

    def get_weekly_summary_by(self, by, alias, number_of_weeks):
        start, _ = Weeks.get_previous(number_of_weeks)
        _, end = Weeks.get_current()

        entries = self.get_entries(start, end)

        def week(row):
            ref = datetime.fromisoformat(row["Created"])
            return Weeks.get_week_string_from_date(ref)

        entries["Week"] = entries.apply(week, axis=1)

        result = pd.DataFrame(columns=[alias])

        for i in range(number_of_weeks):
            start, end = Weeks.get_previous(i)
            week = Weeks.get_week_string_from_interval(start, end)
            df = entries[entries['Week'] == week]

            summary_df = df.groupby(by).size().reset_index(name='Count')
            summary_df.columns = [alias, week]
            result = pd.merge(summary_df, result, on=alias, how='outer')

        result.fillna(0, inplace=True)

        return result

Aqui, a classe semântica substitui a ideia de “usuários” por “autores”, “tipos de postagem” por “classes”, “postagem” por “entradas”.

Além disso, adicionei algumas consultas de sumarização.

01/05/2024

Sempre digo que os repositórios são as “redes sociais” dos desenvolvedores. Nos repositórios, desenvolvedores compartilham código, acessam o código de outras pessoas. Mais do que isso, em uma organização de engenharia, repositórios são a “fonte da verdade”.

Recentemente, desenvolvi um notebook para que os consultores da EximiaCo fizessem análises sobre repositórios. A ideia era identificar arquivos modificados com maior frequência e os contribuidores mais ativos. Para isso, utilizei uma biblioteca, bem bacana, chamada GitPython.

Python
! pip install GitPython --quiet

Abaixo uma método de apoio que retorna a relação de modificações em arquivos realizadas em um repositório nos últimos três meses.

Python
import git
from datetime import datetime, timedelta

def get_commits_last_three_months(repo_path, branch_name):
    repo = git.Repo(repo_path)
    three_months_ago = datetime.now() - timedelta(days=90)
    
    commits = []

    # Iterar sobre os commits na branch especificada
    for commit in repo.iter_commits(branch_name, since=three_months_ago.isoformat()):
        for file_stat in commit.stats.files.items():
            file_name, stats = file_stat
            entry = safe_get(commit.tree, file_name)
            lines = entry.data_stream.read().count(b'\n') + 1 if entry else 0
            commit_info = {
                'sha': commit.hexsha,
                'author': commit.author.name,
                'file': file_name,
                'changes': stats['lines'],
                'insertions' : stats['insertions'],
                'deletions': stats['deletions'],
                'bytes': entry.size if entry else 0,
                'lines': lines
            }
            commits.append(commit_info)
            
    return commits

O código em si é bastante simples. Além das informações que a própria biblioteca fornece, resolvi calcular também o número de linhas de cada arquivo em um commit específico.

A ideia completa de como utilizo esse código está no notebook.

23/04/2024

Everhour é um sistema para registro de atividades popular adotado por empresas de todos os portes. Na EximiaCo, utilizamos o Everhour para registrar nossos engajamentos com nossos clientes.

O Everhour oferece uma API fácil de usar, baseada em HTTP.

Chave de acesso

Para obter a chave de acesso para a API do Everhour, você deve seguir os seguintes passos:

  1. Acesse o aplicativo web do Everhour e faça login na sua conta.
  2. Vá até a seção ‘My Profile’ clicando no seu ícone de perfil.
  3. Dentro da página de configurações, role até o final até encontrar a seção chamada ‘Application Access’.
  4. Nesta seção, você encontrará o seu token de API, que é a chave de acesso necessária para autenticar suas solicitações à API do Everhour.

Lembre-se de manter sua chave de API segura e não compartilhá-la publicamente, pois ela dá acesso aos dados da sua conta no Everhour.

Seu usuário precisa ter acesso administrativo para poder obter esse acesso.

Instalando as dependências

Para fazer uso da API, usaremos o pacote requests. Para poder tratar os dados, usaremos o pandas.

Python
! pip install pandas requests

No código, é importante importar esses dois pacotes.

Python
import pandas as pd
import requests

Obtendo os dados dos usuários

Vamos então iniciar nossa cruzada por obtenção de dados do Everhour. Para começar, vamos escrever código genérico para interagir com endpoints chaves.

Python
def fetch_everhour(api_token, entity, params = {}):
    url = f"https://api.everhour.com/team/{entity}"
    
    headers = {
      'Content-Type': 'application/json',
      'X-Api-Key': api_token
    }

    response = requests.get(url, params=params, headers=headers)
    if response.status_code == 200:
        return response.json()
    else:
        print (response)
        return None

Você pode aprender um bocado sobre os endpoints do Everhour na documentação oficial.

Python
def convert_to_dataframe(source):
    if source:
        df = pd.DataFrame(source)
        return df
    else:
        return pd.DataFrame() 

Por exemplo, o código abaixo resgata todas os usuários definidas no Everhour da organização.

Python
api_token = "SEU-API-TOKEN"
users = convert_to_dataframe(fetch_everhour(api_token, "users"))
users.head()

Felizmente, Pandas ajuda na hora de entender quais são os dados que foram retornados.

Com os dados obtidos, interessante resgatar apenas informações mais interessantes.

Python
users_f = users[['id', 'name', 'email']]
users_f

Obtendo os dados de apontamentos

Agora que conhecemos os usuários, podemos obter também dados relacionados aos apontamentos que já foram realizados.

Python
params = {
    "from": "2024-04-01",
    "to": "2024-04-16",
    "limit": 10000,
    "page": 1
}
times = convert_to_dataframe(fetch_everhour(api_token, "time", params))

Aqui é interessante observar a possibilidade de passar informações de intervalo.

Novamente, o Everhour fornece provavelmente mais informações do que necessitamos.

Python
times_f = times[['user', 'date', 'time']]
times_f

O campo “user” possui um id.

Consolidando apontamos em um período

De um lado, temos dia e volume de horas lançados. De outro, temos os dados dos usuários (pessoas que apontam horas). Vamos consolidar os dados em uma consulta só.

Python
data = pd.merge(times_f, users_f, left_on="user", right_on="id", how="left")
summary = data[['name', 'time']]
summary.groupby("name").sum().plot(kind="bar")

Bacana, não?

Uma classe para acelerar as consultas

No lugar de passar tanto trabalho com tantos dados, podemos criar uma classe que acelera o processo como um todo.

Python
import requests
import pandas as pd

class EverhourClient:
    def __init__(self, api_token):
        self.api_token = api_token
        self.base_url = "https://api.everhour.com/team/"

    def fetch_everhour(self, entity, params={}):
        url = f"{self.base_url}{entity}"
        headers = {
            'Content-Type': 'application/json',
            'X-Api-Key': self.api_token
        }
        response = requests.get(url, params=params, headers=headers)
        if response.status_code == 200:
            return response.json()
        else:
            print(response)
            return None

    def convert_to_dataframe(self, source):
        if source:
            return pd.DataFrame(source)
        else:
            return pd.DataFrame()

    def get_all_users(self):
        users_json = self.fetch_everhour("users")
        users_df = self.convert_to_dataframe(users_json)
        return users_df[['id', 'name', 'email']]

    def get_appointments(self, start, end):
        params = {
            "from": start,
            "to": end,
            "limit": 10000,
            "page": 1
        }
        times_json = self.fetch_everhour("time", params)
        times_df = self.convert_to_dataframe(times_json)
        return times_df[['id', 'user', 'date', 'time']]

    def get_summary(self, start, end):
        times_df = self.get_appointments(start, end)
        users_df = self.get_all_users()
        data = pd.merge(times_df, users_df, left_on="user", right_on="id", how="left")
        summary = data[['name', 'time']]
        return summary.groupby("name").sum()

Esse código torna a utilização da API algo muito mais fácil.

Python
api_token = "SEU-API-TOKEN"
client = EverhourClient(api_token)

# Get all users
users = client.get_all_users()
print(users.head())

# Get appointments between dates
appointments = client.get_appointments("2024-04-01", "2024-04-16")
print(appointments.head())

# Get time summary between dates
summary = client.get_summary("2024-04-01", "2024-04-16")
print(summary)
16/04/2024

Pipedrive é um sistema de CRM popular adotado por empresas de todos os portes. Na EximiaCo, utilizamos o PipeDrive para controlar a relação com nossos clientes.

O Pipedrive oferece uma API fácil de usar, baseada em HTTP.

Chave de acesso

Se você utiliza Pipedrive em sua organização e deseja interagir com seus dados, primeiro precisa obter a chave de acesso a API. Isso é fácil de fazer.

A chave pode ser encontrada nas configurações do aplicativo web do Pipedrive, em “Settings > Personal preferences > API“. É importante não compartilhar essa chave com ninguém que você não confie, pois ela dá acesso aos dados da sua conta.

Instalando as dependências

Para fazer uso da API, usaremos o pacote requests. Para poder tratar os dados, usaremos o pandas.

Python
! pip install pandas requests

No código, é importante importar esses dois pacotes.

Python
import pandas as pd
import requests

Obtendo os dados

Vamos então iniciar nossa cruzada por obtenção de dados do Pipedrive. Para começar, vamos escrever código genérico para interagir com endpoints chaves.

Python
def fetch_pipedrive(api_token, entity, start = 0, params = {}):
    url = f"https://api.pipedrive.com/v1/{entity}"
    
    params['api_token'] = api_token
    params['limit'] = 500
    params['start'] = start

    response = requests.get(url, params=params)
    if response.status_code == 200:
        return response.json()
    else:
        return None
    
def convert_to_dataframe(source):
    if source and 'data' in source:
        df = pd.DataFrame(source['data'])
        return df
    else:
        return pd.DataFrame() 

Esse código busca informações a partir de um endpoint relevante dentro do Pipedrive. Abaixo a lista de referência:

  1. Deals: Uma relação das negociações/oportunidades.
  2. Persons: Usado para gerenciar informações sobre pessoas (contatos) no PipeDrive. Permite listar, criar, atualizar e deletar contatos.
  3. Organizations: Similar ao endpoint de pessoas, mas focado em organizações. Você pode gerenciar informações sobre empresas e outras organizações que estão registradas no seu CRM.
  4. Activities: Permite gerenciar atividades associadas aos negócios, como reuniões, chamadas, tarefas, e-mails, entre outros. É um componente crucial para o acompanhamento de interações com clientes.
  5. Products: Usado para gerenciar os produtos que sua empresa oferece. Este endpoint permite listar produtos, associá-los a negócios, e gerenciar informações relacionadas a esses produtos.
  6. Pipelines: Cada pipeline representa um conjunto de estágios que define um processo de vendas específico. Este endpoint permite manipular esses pipelines, que são essenciais para visualizar o fluxo de negócios.
  7. Stages: Estágios de uma pipeline.
  8. Webhooks: Permite configurar webhooks para eventos no PipeDrive, o que é útil para integrações em tempo real e reações automatizadas a mudanças no CRM.
  9. Files: Gerencia arquivos associados a negócios, pessoas ou organizações. É possível fazer upload e download de arquivos, o que facilita a gestão documental integrada ao CRM.
  10. Notes: Permite adicionar e gerenciar notas associadas a negócios, contatos e organizações. Notas são úteis para manter registros detalhados das interações e pensamentos relacionados aos clientes.
  11. User: Para gerenciar informações sobre os usuários do sistema, como obter detalhes do usuário atualmente autenticado e listar todos os usuários da empresa.
  12. Search: Um endpoint de busca que permite realizar buscas complexas e específicas através dos dados no PipeDrive, incluindo negócios, contatos, atividades e muito mais.

Por exemplo, o código abaixo resgata todas as pipelines definidas no Pipedrive da organização.

Python
api_token = "SEU-API-TOKEN"
pipelines = convert_to_dataframe(fetch_pipedrive(api_token, "pipelines"))
pipelines

Obtendo listas completas de consultas paginadas

Um problema potencial da consulta que implementamos até aqui é o limite de elementos retornados em uma única cosulta. Atualmente, estamos buscando o total possível (500, que é restrição da própria Pipedrive).

A solução é iterar na busca enquanto houverem páginas novas.

Python
def has_next_page(data):
    return data.get('additional_data', {}).get('pagination', {}).get('more_items_in_collection', False)

def fetch_next_page(api_token, entity, current_page):
    if not has_next_page(current_page):
        return None
    
    next_start = current_page.get('additional_data', {}).get('pagination', {}).get('next_start')
    return fetch_pipedrive(api_token, entity, next_start)
    
def get_all_deals(api_token):
    data = fetch_pipedrive(api_token, 'deals')
    df = convert_to_dataframe(data)
    while has_next_page(data):
        data = fetch_next_page(api_token, 'deals', data)
        other_df = convert_to_dataframe(data)
        df = pd.concat([df, other_df])
    return df

No exemplo acima, temos o código que recupera todos os deals no Pipedrive consolidando tudo em um único dataframe.

Python
deals = get_all_deals(api_token)
deals

E é só o começo…

Com as informações em dataframes podemos qualificar os dados e fazer análises refinadas. O uso das APIs garante sempre os “dados quentes”.

Um ponto a se considerar é a combinação dos dados do Pipedrive com outras fontes, mas esse é tema para outro post.

15/04/2024

O YouTube oferece acesso gratuito às melhores palestras de eventos internacionais renomados. Assim, as principais limitações que enfrento para aproveitar tanto conteúdo de qualidade são minha capacidade e o tempo disponível, que está cada vez mais escasso.

Felizmente, utilizando criatividade, conhecimentos em Python e o Jupyter Notebook, consigo otimizar o tempo para capturar a essência do conteúdo de cada palestra.

Obtendo os metadados de um vídeo do YouTube

Assumindo que você tem Python instalado e Jupyter funcionando, começo um novo notebook instalando os pacotes requests e bs4.

Python
! pip install --upgrade --quiet  requests bs4

O código que segue consegue extrair, a partir de um link, o título do vídeo, seu código e o nome do canal onde está publicado. Ele faz isso dividindo a URL para obter o código do vídeo, enviando uma solicitação para a URL, e, se bem-sucedido, analisa o HTML para encontrar os metadados do vídeo e do canal.

Python
import requests
from bs4 import BeautifulSoup

# URL do vídeo no YouTube
video_url = 'https://www.youtube.com/watch?v=GYJ77F_8kq0'

# Extrai o código do vídeo da URL
video_code = video_url.split('=')[-1]

# Faz a solicitação para a página do vídeo
response = requests.get(video_url)

# Se a solicitação foi bem-sucedida
if response.status_code == 200:
    # Analisa o HTML da página
    soup = BeautifulSoup(response.text, 'html.parser')
    
    # Extrai o nome do vídeo
    # O título geralmente está dentro de uma tag <meta> com o atributo name="title"
    video_title = soup.find('meta', {'name': 'title'})['content']
    
    # Extrai o nome do canal
    # O nome do canal geralmente está dentro de uma tag <meta> com o atributo itemprop="channelId"
    channel_name = soup.find('link', {'itemprop': 'name'})['content']
    
    print(f'Nome do vídeo: {video_title}')
    print(f'Código do vídeo: {video_code}')
    print(f'Canal: {channel_name}')
else:
    print('Falha ao fazer a solicitação para a página do vídeo.')

Executando o código, obtenho, os dados que desejo.

Plaintext

Nome do vídeo: "Picasso, Geometry, Jupyter" by Ryan Herr
Código do vídeo: GYJ77F_8kq0
Canal: Strange Loop Conference

Obtendo a transcrição de um vídeo do YouTube

Já tenho os metadados de um vídeo, agora, desejo obter a transcrição. Para isso, vamos utilizar o pacote youtube-transcript-api.

Python
! pip install --upgrade --quiet youtube-transcript-api

O código para obter a transcrição é bem simples.

Python
from youtube_transcript_api import YouTubeTranscriptApi

transcript = YouTubeTranscriptApi.get_transcript(video_code)
full_text = ' '.join([entry['text'] for entry in transcript])
print(full_text)

O resultado é a transcrição do vídeo que o YouTube disponibiliza para gerar legendas. É importante observar, no entanto, que nem todos os vídeos possuem transcrição.

Plaintext
hi I'm Ryan her and this talk is about Picasso and programming in 1945 Picasso 
created a series of bulls where he started with a fairly realistic bull like you see 
on the left and through a gradual progressive process he deconstructed the bull abstracting
it to its essence to a simplified geometric form so in 1945 Picasso did that in 2010 an 
anonymous illustrator posted a meme on tumblr and reddit how to draw an owl again we see a 
transition between two states step one draw some circles step two draw the rest of the owl
so the interesting thing when we compare these two sets of images well they're moving in
opposite directions right the the owl is moving from simple to detailed but the bull Picasso's Bulls actually moved in the opposite direction it went from more complex more detailed to 
...

Preparando a interação com a transcrição e com os metados usando IA

Agora que temos a transcrição do vídeo, podemos utilizar a API da OpenAI para “conversar” com a transcrição obtendo as informações que desejamos. Para isso, irei utilizar LangChain para facilitar a interação. Vamos instalar os pacotes langchain, langchain-openai, langchain-core e langchain-community.

Python
! pip install --upgrade --quiet langchain langchain-openai langchain-core langchain-community

O código monta um modelo de prompt com os metadados e com a transcrição que obtemos anteriormente e a solicitação do usuário. Utilizo um modelo da OpenAI com uma janela de 128K que permite tratamento da transcrição completa, sem perdas. Também crio uma função helper para o modelo.

Python
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate

gpt4 = ChatOpenAI(model_name="gpt-4-0125-preview",temperature=0)

chat_with_document = ChatPromptTemplate.from_template(f"""
O texto que segue é uma transcrição do vídeo 
intitulado {video_title} disponível no canal {channel_name}

Leia o texto atentamente e realize da forma mais completa 
possível a tarefa também indicada abaixo.
Use, na resposta, apenas informações contidas no texto, 
sem adicionar, em hipótese alguma, qualquer informação adicional.

Você responde em português, usando linguagem simples, com 
vocabulário simples. Estrutura seu texto em
frases curtas, em parágrafos com não mais do que 300 caracteres.

TEXTO:
{{text}}

TAREFA:
{{task}}

SUA RESPOSTA:
""")

output_parser = StrOutputParser()

chat_with_document_chain = chat_with_document | gpt4 | output_parser

def do(task):
    result = chat_with_document_chain.invoke({"text": full_text, "task": task})
    print(result)
    return result

Obtendo um resumo e outras informações relevantes

Tudo pronto! Agora, vamos estudar. Comecemos, com um bom resumo.

Python
summary = do("Forneça-me um resumo detalhado do que é discutido no vídeo.")

O resultado é um resumo, em português, do conteúdo que está na transcrição, que está em inglês.

Plaintext
O vídeo apresentado por Ryan Herr aborda a relação entre a arte de Picasso, a 
programação e a busca pela simplificação e abstração tanto na arte quanto no 
software. Em 1945, Picasso criou uma série de imagens de touros, começando 
com uma representação realista e, progressivamente, simplificando-a até chegar 
a uma forma geométrica simplificada. Esse processo de abstração de Picasso é 
comparado a um meme de 2010 sobre desenhar uma coruja, mostrando a dificuldade 
de simplificar ou detalhar imagens.

Ryan Herr explora como o processo de Picasso pode ser aplicado ao 
desenvolvimento de software e matemática, onde a busca por abstrações concisas 
e elegantes é constante. Ele menciona que Picasso, antes de criar sua série de 
touros, enfrentou um bloqueio criativo enquanto trabalhava em outra pintura, 
"Charnel-House". Picasso expressou o desejo de poder trabalhar em estados 
progressivos sem finalizar a tela, algo que ele conseguiu explorar com a 
série de touros usando a litografia, uma técnica que permite fazer alterações.

Herr utiliza papel vegetal para entender o processo de Picasso, traçando as 
mudanças entre os estados dos touros. Ele então usa programação para analisar 
computacionalmente essas mudanças, criando representações coloridas que mostram 
o que foi adicionado, removido ou mantido entre cada estado. Esse processo 
revela que inicialmente Picasso adicionou detalhes ao touro, mas progressivamente 
simplificou a imagem, removendo elementos até chegar a uma forma quase linear.

Explorando ainda mais, Herr utiliza o algoritmo de simplificação de linhas 
Douglas-Peucker para tentar recriar a abstração de Picasso computacionalmente, 
resultando em uma forma que, embora diferente, oferece insights sobre o processo 
de simplificação. Ele discute a dificuldade de capturar a abstração única de Picasso, 
que combina elementos de abstração pictórica e icônica.

Por fim, Herr reflete sobre como a exploração computacional do processo de Picasso 
não apenas oferece uma nova compreensão da arte de Picasso, mas também pode inspirar 
desenvolvedores de software e matemáticos a enfrentar bloqueios criativos, sugerindo 
que começar com uma versão mais complexa e simplificá-la progressivamente pode ser 
uma estratégia eficaz para superar desafios criativos.

E esse foi só o começo…

Para interagir com outra palestra, basta-me substituir o link do vídeo que quero estudar e executar novamente o código.

A produção de resumos qualificados assim facilita a produção de anotações e me permite saber rapidamente quando interesse tenho em assistir uma palestra ou não.

25/02/2024