{"id":30092748,"url":"https://github.com/mateusherrera/feedback-classifier","last_synced_at":"2026-04-06T02:33:19.037Z","repository":{"id":307588525,"uuid":"1029112229","full_name":"mateusherrera/feedback-classifier","owner":"mateusherrera","description":"Flask API para ingestão, classificação e análise de comentários usando OpenAI API.","archived":false,"fork":false,"pushed_at":"2025-08-28T13:51:50.000Z","size":404,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-28T20:56:12.517Z","etag":null,"topics":["api-rest","celery","celerybeat","docker","docker-compose","flask","flask-restful","github-actions","gpt-35-turbo","html","javascript","jenkins","llm","nginx","openai","postgresql","pytest","python","redis","redis-cache"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mateusherrera.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-30T14:38:55.000Z","updated_at":"2025-08-06T14:32:10.000Z","dependencies_parsed_at":"2025-08-28T15:24:05.148Z","dependency_job_id":"81465dd4-a980-4116-ba4a-adb66975a4c1","html_url":"https://github.com/mateusherrera/feedback-classifier","commit_stats":null,"previous_names":["mateusherrera/flask-llm-api","mateusherrera/feedback-classifier"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mateusherrera/feedback-classifier","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mateusherrera%2Ffeedback-classifier","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mateusherrera%2Ffeedback-classifier/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mateusherrera%2Ffeedback-classifier/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mateusherrera%2Ffeedback-classifier/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mateusherrera","download_url":"https://codeload.github.com/mateusherrera/feedback-classifier/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mateusherrera%2Ffeedback-classifier/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31457708,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"online","status_checked_at":"2026-04-06T02:00:07.287Z","response_time":112,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-rest","celery","celerybeat","docker","docker-compose","flask","flask-restful","github-actions","gpt-35-turbo","html","javascript","jenkins","llm","nginx","openai","postgresql","pytest","python","redis","redis-cache"],"created_at":"2025-08-09T08:04:03.423Z","updated_at":"2026-04-06T02:33:19.016Z","avatar_url":"https://github.com/mateusherrera.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AluMusic Feedback Classifier\n\n![Python](https://img.shields.io/badge/python-3.11%2B-blue?logo=python\u0026logoColor=white)\n![OpenAI API](https://img.shields.io/badge/OpenAI-API-brightgreen?logo=openai\u0026logoColor=white)\n![Model GPT‐3.5‐turbo](https://img.shields.io/badge/Model-gpt--3.5--turbo-blueviolet)\n![License](https://img.shields.io/github/license/mateusherrera/feedback-classifier.svg)\n\nFoi desenvolvida uma API RESTful em Flask, integrada à API da OpenAI, para classificar automaticamente os comentários da plataforma AluMusic. O sistema foi projetado para escalar desde o início, com processamento paralelo em lote e autenticação via JWT, assegurando segurança e performance.\n\nTodo o ambiente está conteinerizado com Docker e servido por Gunicorn e NGINX, além de contar com uma pipeline de CI/CD orquestrada por Jenkins que aciona workflows no GitHub Actions, garantindo builds e testes automatizados.\n\n## Sumário\n\n- [Ferramentas Utilizadas](#ferramentas-utilizadas)\n- [Descrição do Projeto](#descrição-do-projeto)\n- [Documentação](#documentação)\n- [Como executar o Projeto](#como-executar-o-projeto)\n- [Testes e Métricas (EVALs)](#testes-e-métricas-evals)\n- [Endpoints](#endpoints)\n- [Painel e Relatórios](#painel-e-relátorios)\n- [Resumo por E-mail](#resumo-por-e-mail)\n- [Extra: Insights](#extra-insights)\n\n## Ferramentas Utilizadas\n\n| Categoria             | Ferramenta                                                                               |\n|-----------------------|------------------------------------------------------------------------------------------|\n| Linguagem             | [Python 3.11+](https://www.python.org/)                                                  |\n| Web                   | [Flask](https://flask.palletsprojects.com/en/stable/#user-s-guide)                       |\n| API                   | [Flask-RESTful](https://flask-restful.readthedocs.io/en/latest/)                         |\n| Migrations            | [Flask-Migrate / Alembic](https://flask-migrate.readthedocs.io/)                         |\n| Auth                  | [Flask-JWT-Extended](https://flask-jwt-extended.readthedocs.io/)                         |\n| Banco de Dados        | [PostgreSQL](https://www.postgresql.org/)                                                |\n| ORM                   | [SQLAlchemy](https://www.sqlalchemy.org/)                                                |\n| LLM                   | [OpenAI](https://platform.openai.com/docs)                                               |\n| Cache / Broker        | [Redis](https://redis.io/)                                                               |\n| Tarefas Assíncronas   | [Celery](https://docs.celeryq.dev/en/stable/)                                            |\n| Agendamento de Tarefas| [Celery Beat](https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html)          |\n| Envio de E-mail       | [Flask-Mail](https://flask-mail.readthedocs.io/en/latest/)                               |\n| Servidor WSGI         | [Gunicorn](https://gunicorn.org/)                                                        |\n| Reverse Proxy         | [NGINX](https://nginx.org/en/docs/)                                                      |\n| Containers            | [Docker Compose](https://docs.docker.com/compose/)                                       |\n| CI/CD                 | [Jenkins](https://www.jenkins.io/) + [GitHub Actions](https://docs.github.com/actions)   |\n| Métricas \u0026 EVALs      | [scikit-learn](https://scikit-learn.org/stable/)                                         |\n| Testes                | [PyTest](https://docs.pytest.org/en/latest/)                                             |\n\n## Descrição do Projeto\n\nEsse projeto se trata de um API REST, desenvolvida em Flask + Flask RESTful, com o objetivo de fornecer endpoints para analise e acopanhamento de tendencias nos cometário da AluMusic.\n\nPara foram feitos:\n1. Endpoint para classficação (unitário ou em lote) de comentário quanto a sua categoria.\n2. Endpoint que fornece JSONs (atualizado a cada 60s) para contrução de 5 gráficos diferentes.\n3. Interface simples e privada para consulta dos cometários classificados.\n4. Resumo semanal dos  cometários enviado por e-mail aos stakeholders.\n5. Método para avaliação dos dados classificados, considerando recall, precision e f1-score.\n6. *Extra:* Endpoint para pergunta livre em linguagem natural. A resposta tem como base os últimos 3 relatórios semanais enviados.\n\n## Documentação\n\n### Overview\n\nO repositório possui build e testes automatizados com Pytest e CI de Jenkins + Github Actions.\n\nPara construção da API foi utilizado, principalmente, Flask. Para implantação são utilizados Docker, NGINX e Gunicorn.\n\nAlém disso, a persistência se dá com um PostgreSQL, cache com Redis e agendamento de tarefas com Celery Beat, todas essas já disponiveis no `docker-compose.yml`. A administração do Banco de Dados é feita em código com Flask-migrations e Object Relation Mapper (ORM) do SQLAlchemy.\n\n### Docker Compose\n\nO Docker Compose (`docker-compose.yml`) principal desse repositório sobe todos os serviços necessários para disponibilizar os recursos dessa API.\n\nEntre os serviços temos os seguintes:\n\n* `redis`: Serviço Redis para utilização de cache no Flask.\n* `db`: Serviço respons ável por subir o PostgreSQL usado para persistência de dados.\n* `api`: Serviço que sobe a API Flask, por meio do gunicorn. A api escuta a porta 5000 internamente, ou seja, a porta só escuta dentro do container.\n* `celery worker`: Serviço responsável pelo envio do resumo semanal por e-mail (Dispara a função).\n* `celery beat`: Serviço que agenda e dispara tarefas periódicas.\n* `nginx`: Serviço de reverse proxy que recebe as requisições externas e as encaminha para a API Flask (porta 5000).\n\nNeste repositório, ainda há um Docker Compose que pode ser usado para subir uma instância de Jenkins em `http://localhost:8080`. Ele está na pasta `jenkins/docker-compose.yml`.\n\n### Nginx\n\nO `nginx.conf` é o arquivo de configuração do NGINX para proxy reverso da API.\n\nEsse serviço escuta a porta 443 e envia as requisições para localhost:5000 (Flask com Gunicorn). Além disso, ele está preparado para servir a API com certificado SSL, para mais informações verifique a seção: [Como executar o Projeto](#como-executar-o-projeto).\n\n### Jenkins\n\nNeste repositório, além do arquivo `Jenkinsfile` que aciona os testes via Github Actions, também há um `docker-compose.yml` na pasta `jenkins/` para subir uma instância de Jenkins em `http://localhost:8080/`. Mais a frente haverão instruções para sua configuração.\n\n### PostgreSQL\n\nO gerenciamento do banco em si acontence por meio do Docker, porém no diretório `db/` há o script para criação do Schema utilizado pela Flask. Esse script só é executado apenas na primeira vez que o build do serviço é feito e o volume de persistencia dos dado não existe ou está vazio.\n\n### Flask\n\nAqui é onde está o código da API Flask, nela existem os seguintes modulos:\n* `app/`: Módulo principal (raiz) do projeto, onde todos os outros módulos e arquivos estão presentes.\n    * `app/main.py`: Arquivo principal da API Flask, onde o app flask é instanciados e os objetos de configuração são importados e definidos para o app.\n    * `app/extensions.py`: Arquivo para instanciar extensões para o flask, como, `JWTManager()`, `Cache()`, `Mail()`, etc.\n    * `app/config.py`: Arquivo para carregar e centralizar as variáveis de ambiente disponíveis no arquivo `.env`.\n    * `app/tasks.py`: Arquivo para 'agendar' a execução de tarefas por meio do Celery. Onde o resumo semanal está implementado.\n    * `app/evals.py`: Arquivo para implementar lógica com `scikit-learn` para calcular métricas do classificador de comentários.\n    * `app/api/`: Módulo para agrupar arquivos referentes a roteamente e lógicas de endpoints do Flask.\n        * `app/api/routes.py`: Arquivo para realizar o roteamento de endpoints. Difinição de rotas.\n        * `app/api/resources/`: Módulo para agrupar lógicas de endpoints.\n            * `app/api/resources/auth.py`: Arquivo para implementação de endpoints para autenticaçao JWT.\n            * `app/api/resources/comentarios_export.py`: Arquivo para implementação de endpoint de exportação de comentários classificados em CSV.\n            * `app/api/resources/comentarios.py`: Arquivo para implementação de endpoints de listagem e criação de comentários classificados.\n            * `app/api/resources/insights.py`: Arquivo para implementação de endpoint para criação de resposta, via LLM, para pergunta feita via requisição, usando resumos semanais anteriores como contexto.\n            * `app/api/resources/relatorios.py`: Arquivo para implementação de endpoint de geração de relatórios (JSON de gráficos).\n    * `app/models/`: Módulo para definiição de models do Flask (esses models refletem as tabelas no PostgreSQL).\n        * `app/models/comentario.py`: Arquivo para definir classe e atribuitos do model de comentários classificados.\n        * `app/models/resumo.py`: Arquivo para definir classe e atribuitos do model de resumo semanal.\n        * `app/models/user.py`: Arquivo para definir classe e atribuitos do model de users. Gerenciamento com JWT.\n    * `app/services/`: Módulo para implementação de lógicas de serviços ('regras de negócio').\n        * `app/services/classifier.py`: Implementação de métodos para classificação de comentário (texto) por meio de modelo de LLM.\n        * `app/services/insights.py`:  Implementação de método para responder pergunta feita em linguagem natural, pelo usuário via requisição, por meio de modelo de LLM, com contexto dos últimos resumos semanais enviados.\n        * `app/services/summary.py`: Implementação de método para gerar resumo semanal com base em comentários classficados através de modelo de LLM.\n    * `app/templates/`: Módulo para implementação de telas simples em HTML para exibição de comentários classificados.\n        * `app/templates/dashboard.html`: Tela para exibição e filtrar comentários filtrados.\n        * `app/templates/login.html`: Tela de login para parte privada (exibição de comentários classificados).\n    * `app/views/`: Módulo para roteamento de views (tela de login e dashboard).\n        * `app/views/dashboard.py`: Arquivo para instânciamento de blueprint que faz o roteamento dos caminhos para HTMLs.\n* `data/`: Pasta para salvar datasets (CSV) usados para gerar métricas do modelo de classificação de comentários.\n* `migrations/`: Pasta para armazenar migrations gerados automaticamente pelo Flask-migrate, que aplicam os models no banco de dados configurado.\n\n### Pytest\n\nNesta pasta, estão todos os testes automaticos do Flask. Nele serão definidos testes para garantir o bom funcionamente da API e cálculo de métricas de EVALs mínimas. Além disso uma pasta dedicada para testes unitários.\n\n\u003e De maneira geral, utilizei Mock para todos os momentos que a API da `openai` é chamada. A única excessão são os testes de EVALs do prompt de classificação de comnetários, que devem atender um valor mínimo de recall, precision e f1-score do modelo.\n\n## Como executar o Projeto\n\nNessa seção será listado o passo-a-passo para executar o projeto utilizando Docker.\n\n### Requisitos:\n\n* Python 3.11+\n* Git\n* Linux ou WSL2 (Para instalar WSL2 no Windows, caso seja o caso você [pode seguir esse tutorial](https://medium.com/@habbema/desvendando-o-wsl-2-no-windows-11-c7649545026d))\n* Docker Desktop no Windows integrado com WSL2, caso necessário[utilize o tutorial oficial](https://docs.docker.com/desktop/features/wsl/)\n\n### Clone o repositório\n\nClone o repositório e entre na pasta:\n\n```sh\ngit clone https://github.com/mateusherrera/feedback-classifier.git\ncd feedback-classifier\n```\n\n### Para preparar o ambiente de Dev\n\nCaso queria subir a venv Python se Docker, você pode rodar:\n\n```sh\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r flask/requirements.txt\n```\n\n### Variáveis de ambiente\n\nO primeiro passo é definir as variáveis de ambiente.\nNa pasta raiz do repostório há o template, para preencher com seus valores faça:\n\n```sh\ncp .env.template .env\n```\n\nEm seguida preenchar os valores adequados. Para isso, pode fazer pelo deu editor preferido, ou pelo terminal com `nano .env`.\n\nAbaixo seguem as descrições da cada variáveis que deve ser fornecida para o projeto:\n\n\u003e Os itens marcados com '**' precisam ser preenchidos. Ou seja, não podem ser usados com valor default\n\n* Variáveis para o Flask:\n    * ``FLASK_APP``: Caminho para o módulo para app flask, caso não tenha alteração na estrutura do projeto, pode ser deixada como valor default: `app.main:app`.\n    * ``FLASK_ENV``: Indica se o flask será rodado em `production`, `development` ou `testing`. Em execução local pode ser deixado como `development`.\n    * ** ``SECRET_KEY``: Chave aleatória gerada para utilização do Flask App, para gera pode seguir [esses passos](#para-gerar-as-chaves-aleatórias)\n* ** ``OPENAI_API_KEY``: Chave válida para utilização da API da OpenAI. Pode ser gera na [plataforma oficial da OpenAi](https://platform.openai.com)\n* ** ``JWT_SECRET_KEY``: Chave aleatória para utilização nos métodos do JWT. Pode ser gerada seguindo o mesmo método da SECRET_KEY do Flask, [aqui](#para-gerar-as-chaves-aleatórias).\n* Variáveis de Banco de Dados:\n    * ``SQLALCHEMY_DATABASE_URI``: Conn string para conexão com o banco a ser utilizado pelo Flask. Exemplo: `postgresql://usuario:senha@host:5432/nome_do_banco`. PS.: para utilizar o banco gerado pelo Docker, host será o mesmo nome do container, no caso `db`.\n        \u003e Importante: A connstring será usada para conexão. As variáveis abaixo são apenas para criação do banco com o docker compose.\n        \u003e Então caso queira conectar a um banco proprio, pode preencher apenas a connstring com as suas configurações, e as debaixo podem ser ignoradas. Para esse último caso, não suba o serviço db, quando fazer o build dos containers.\n    * ``POSTGRES_DB``: Nome do Banco de Dados que será gerado pelo docker ao fazer o build do PostgreSQL. Nesse caso deve ser `feedback_classifier`.\n    * ``POSTGRES_HOST``: Host do Banco de Dados que será gerado pelo docker ao fazer o build do PostgreSQL. De acordo com a estrutura do `docker-compose.yml` o valor deve ser o nome do service, no caso `db`.\n    * ``POSTGRES_PORT``: Número da porta para conexão com o Banco de Dados.\n    * ``POSTGRES_USER``: Usuário admin que será gerado no build do container.\n    * ``POSTGRES_PASSWORD``: Senha para o usuário admin.\n    * ``PGDATA_PATH``: Caminho local para armazenamento dos dados do banco de dados. Pode ser o default: `/var/lib/postgresql/data`. Tenha em mente que ela precisa existir e a permissão deve ser concedida para o docker: `sudo mkdir /var/lib/postgresql/data` e `sudo chown -R 999:999 /var/lib/postgresql/data/`.\n* Variáveis para configuração do Redis (Cache):\n    * ``CACHE_TYPE``: Tipo de cache a ser utilizado para o cache. Pode ser deixado o valor default: `RedisCache`, para utilização do Redis.\n    * ``CACHE_REDIS_HOST``: Host para o redis, segue a lógica do postgres, usando o Docker, deixe o nome do service: `redis`.\n    * ``CACHE_REDIS_PORT``: Número da porta para conexão com o redis.\n    * ``CACHE_REDIS_DB``: Indice do Banco de Dados, pode ser deixado em default: `0`.\n    * ``CACHE_DEFAULT_TIMEOUT``: Timeout padrão para o redis, pode ser deixado também com valor default: `60`.\n* Variávies para configuração de envio de e-mail automáticos:\n    \u003e Se usar Gmail, MAIL_SERVER e MAIL_PORT podem ser deixados default.\n    * ** ``MAIL_SERVER``: Servidor de STMP do e-mail que será utilizado para envio automático de resumo semanal.\n    * ** ``MAIL_PORT``: Porta para o servidor STMP do e-mail que será utilizado para envio automático de resumo semanal.\n    * ** ``MAIL_USERNAME``: Usuário de e-mail que será utilizado para envio automático de resumo semanal.\n    * ** ``MAIL_PASSWORD``: Senha de app do e-mail que será utilizado para envio automático de resumo semanal. No caso do GMail, pode ser [esse documento](https://support.google.com/accounts/answer/185833?hl=pt-BR) para gerar a senha de app.\n    * ** ``MAIL_DEFAULT_SENDER``: E-mail remetente da mensagem, pode ser o mesmo que o USERNAME.\n    * ** ``STAKEHOLDERS_EMAILS``: E-mail que receberão o resumo semanal separados por `,` (vírgula), por exemplo: `example1@mail.com,example1@mail.com`\n    * ``CELERY_BROKER_URL``: Connstring para redis, por exemplo `redis://host_redis:porta_redis/numero_db_redis`. Lembrando que assim como no postges, o host é o nome do serviço no Docker, no caso `redis`.\n.* Nomes dos container, esse valores são os nomes dos containers de cada serviço, pode ser deixado o valor default:\n    * ``REDIS_CONTAINER_NAME``: Nome do container de Redis (`feedback-classifier_redis`).\n    * ``DB_CONTAINER_NAME``: Nome do container do PostgreSQl (`feedback-classifier_db`).\n    * ``API_CONTAINER_NAME``: Nome do container do Flask (`feedback-classifier_api`).\n    * ``NGINX_CONTAINER_NAME``: Nome do container do NGINX (`feedback-classifier_nginx`).\n\n#### Para gerar as chaves aleatórias\n\nPara gerar as chaves aleatórias use o interpretador do python com `python3` e rode:\n\n```python\nimport secrets\nprint(secrets.token_urlsafe(32))\n```\n\n\u003e Importante: Usar chaves destintas para SECRET_KEY e JWT_SECRET_KEY\n\n### Gerar certificado\n\nPara subir o NGINX corretamente, na raiz do repositório faça:\n\n```\ncd nginx\nmkdir ssl\ncd ssl\nopenssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout private-key.key -out fullchain.pem -subj \"/C=BR/ST=SP/L=SaoPaulo/O=Dev/OU=Dev/CN=localhost\"\n```\n\nPara verificar o comando `ls` deve retornar algo como:\n\n```sh\nfullchain.pem  private-key.key\n```\n\nPor fim volte para a raiz `cd ../..`\n\n### Docker Compose\n\nCom o `.env` devidamente criado e preenchido basta fazer o build do Docker, rodando:\n\n```sh\ndocker compose up -d\n```\n\nPara verificar se tudo ocorreu bem rode:\n\n```sh\ndocker ps\n```\n\nE a saida deve ser algo como:\n\n```sh\nCONTAINER ID   IMAGE                               COMMAND                  CREATED        STATUS             PORTS                                         NAMES\n252a2ca89c1e   feedback-classifier-nginx           \"/docker-entrypoint.…\"   46 hours ago   Up About an hour   0.0.0.0:443-\u003e443/tcp, [::]:443-\u003e443/tcp       feedback-classifier_nginx\n0c254b54dcf5   feedback-classifier-celery_worker   \"celery -A app.tasks…\"   46 hours ago   Up About an hour                                                 celery_worker\na4cef3afbb0e   feedback-classifier-celery_beat     \"celery -A app.tasks…\"   46 hours ago   Up About an hour                                                 celery_beat\nf99f6fbe771e   feedback-classifier-api             \"gunicorn -w 4 -b 0.…\"   46 hours ago   Up About an hour   0/tcp                                         feedback-classifier_api\na1ba3bd2e5ad   postgres:16                         \"docker-entrypoint.s…\"   2 days ago     Up About an hour   0.0.0.0:5432-\u003e5432/tcp, [::]:5432-\u003e5432/tcp   feedback-classifier_db\n9d76af791392   redis:7                             \"docker-entrypoint.s…\"   2 days ago     Up About an hour   0.0.0.0:6379-\u003e6379/tcp, [::]:6379-\u003e6379/tcp   feedback-classifier_redis\n```\n\n### Aplicando migrate\n\nPara finalizar aplique as migrates no banco de dados com:\n\n```sh\ndocker compose exec api flask db upgrade\n```\n\nFeito isso, a API Flask está UP!\n\n### Jenkins\n\nCaso queira, é possível é subir um Jenkins com o `docker-compose.yml` disponível em `jenkins/docker-compose.yml`. Basta rodar:\n\n```sh\ncd jenkins\ndocker compose up -d\n```\n\nSe você optou por subir o jenkins do repositório, faça o seu primeiro acesso, em `http://localhost:8080/`, com a senha disponível com o comando abaixo, e crie o seu usuário de administrador.\n\n```sh\ndocker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword\n```\n\nInstale os plugins sugeridos.\n\nFeito isso, ou usando um Jekins proprio. Logado, faça as seguinte configurações para rodar os testes automáticos com Github Actions:\n\n1. Adicione seu Github Personal Access Token:\n\n    \u003e Para esse caso, crie o token com repo e admin:repo_hook.\n\n    1. Acesse Manage Jenkins → Manage Credentials → selecione o store global.\n    2. Clique em Add Credentials e preencha:\n        * Kind: Secret text\n        * Secret: cole o seu GitHub Personal Access Token\n        * ID: github-token\n        * Description: “Token para acionar GitHub Actions”\n    3. Salve.\n\n2. Adicione suas credencias do Github:\n    1. Vá em Manage Jenkins → Manage Credentials → selecione o escopo (global).\n    2. Clique em Add Credentials e preencha:\n        * Kind: Username with password\n        * Username: seu usuário GitHub (ex.: mateusherrera)\n        * Password: seu GitHub Personal Access Token (o mesmo criado no passo 1)\n        * ID: github-credentials\n        * Description: “Credenciais GitHub (usuário + token)”\n    3. Salve.\n\n3. Crie o pipeline para o repositório\n    1. No dashboard do Jenkins, clique em “New Item” (ou “Novo Item”) no menu lateral.\n    2. Dê um nome ao job, por exemplo feedback-classifier-ci, selecione “Pipeline” e clique em OK.\n    3. Na tela de configuração do job:\n        1. Em Pipeline faça:\n            * Definition: selecione “Pipeline script from SCM”.\n            * SCM: escolha “Git”.\n            * Repository URL: https://github.com/mateusherrera/feedback-classifier.git ou repo de fork caso tenha feito.\n            * Credentials: selecione github-credentials.\n            * Branches to build: */main.\n            * Script Path: Jenkinsfile.\n    4. Clique em Save.\n\n## Testes e Métricas (EVALs)\n\nPara realizar testes locais rode o comando:\n\n```sh\nPYTHONPATH=flask OPENAI_API_KEY=\u003ctoken-openai\u003e LLM_MODEL=gpt-3.5-turbo pytest -v\n```\n\nPara realizar testes de EVALs com um único comando rode:\n\n```sh\nPYTHONPATH=flask dotenv run -- python -m app.evals --all\n```\n\n### Flags Disponíveis\n\nSegue lista de flags possíveis para evals:\n\n| Flag                            | Descrição                                                                 |\n|---------------------------------|---------------------------------------------------------------------------|\n| `-d, --data-csv PATH`           | Caminho para o CSV (default: `flask/data/test_comments.csv`)              |\n| `--all`                         | Avalia todas as categorias                                               |\n| `--\u003cslug\u003e`                      | Avalia somente a categoria cujo slug é `\u003cslug\u003e` (ex.: `--elogio`)         |\n| `--\u003cslug\u003e-min-recall FLOAT`     | Recall mínimo aceito em `\u003cslug\u003e`                                          |\n| `--\u003cslug\u003e-max-recall FLOAT`     | Recall máximo aceito em `\u003cslug\u003e`                                          |\n| `--\u003cslug\u003e-min-precision FLOAT`  | Precisão mínima aceita em `\u003cslug\u003e`                                        |\n| `--\u003cslug\u003e-max-precision FLOAT`  | Precisão máxima aceita em `\u003cslug\u003e`                                        |\n| `--\u003cslug\u003e-min-f1 FLOAT`         | F1 mínima aceita em `\u003cslug\u003e`                                              |\n| `--\u003cslug\u003e-max-f1 FLOAT`         | F1 máxima aceita em `\u003cslug\u003e`                                              |\n\n\n### Testes automatizados de EVALs\n\nSobre as métricas mínimas definidas no GitHub Actions, o CI não passará caso os valores fiquem abaixo dos thresholds:\n\n| Categoria   | Recall Mínimo | Precisão Mínima | F1-Score Mínimo |\n|-------------|---------------|-----------------|-----------------|\n| ELOGIO      | 0.60          | 0.60            | 0.60            |\n| CRÍTICA     | 0.65          | 0.65            | 0.65            |\n| SUGESTÃO    | 0.50          | 0.60            | 0.70            |\n| DÚVIDA      | 0.65          | 0.65            | 0.65            |\n| SPAM        | 0.80          | 0.80            | 0.80            |\n\nSobre as decisões de valores:\n\n* **Elogio**: Para elogio escolhi deixar um valor mais baixo para os thresholds, uma vez que não há identificações de melhorias, concertos, etc.\n* **Crítica**: É um feedback que pode ou não ser interessante para identificação de pontos de atenção para o sistema.\n* **Sugestão**: Categorita importante para identificação de melhorias de UX no sistema deve ser identificadas com mais precisão e recall.\n* **Dúvida**: Podem ser interessante para identificação de pontos de interesse no UX do sistema, semelhante a **Crítica**.\n* **Spam**: É muito importante identificar esses comentários, pois podem afetar na analise de insights e levantamentos importantes para analise do sistema.\n\n## Endpoints\n\nEm `docs/postman-collection/` há o arquivo `Flask - LLM API.postman_collection.json` que pode ser importado no, idalmente, no Postman ou no Insomnia, para carregar os endpoits no seu cliet de preferência.\n\n### Lista de Endpoints:\n\n\u003e host: https://localhost/\n\n\u003e Para testar os endpoints passe o access token em Authorization com Berear.\n\n\u003e Exemplos de request e response, podem ser visto no collection do Postman.\n\n* Auth:\n    * POST `api/auth/register`: Endpoint para criar usuários.\n    * POST `api/auth/login`: Endpoint para fazer login. Ao passar credenciais (user e senha) válidas, retorna o refresh e o access token.\n    * POST `api/auth/refresh`: Endpoint para regerar um access token (vale por um minuto, em dev um dia), com refresh token (vale por um dia) válido.\n* Classificação de comentários:\n    * POST `api/comentarios`: Endpoint para classificar comentário com modelo LLM.\n    * GET `api/comentarios`: Listar todos os comentários classificados.\n    * GET `api/comentarios/export`: Exportar dados de comentários classificados em CSV.\n* Relatório Semanal:\n    * GET `api/relatorio/semana`: Retorna os dados (em JSON) cálculados para os dashboards.\n* Insights:\n    * POST `api/insights/perguntar`: Responde pergunta em linguagem natural com base nos últimos 8 relatórios semanais.\n\n## Classificador de comentário\n\nO classificador de comentários funciona passando um JSON com as tags 'id' (uuid) e 'texto' conteúdo do comentários, ou lista disso (em lote). E o retorno será a classificação disso pelo modelo de LLM da OpenAI 'gpt-35-turbo', quando a sua categoria entre [ELOGIO, CRÍTICA, SUGESTÃO, DÚVIDA, SPAM], tags indicando a(s) funcionalidade(s) que referencia aquele comentário e a confiabilidade da classficação pelo modelo.\n\n* Exemplo de chamada unitária:\n\n![Exemplo de requisição de classificação de comentário via Postman](images/exemplo-classifier-unico.png)\n\n* Exemplo de chamada em lote:\n\n![Exemplo de requisição de classificação de comentários em lote via Postman](images/exemplo-classifier-batch.png)\n\n## Painel e Relátorios\n\n### Painel\n\nPara exibição dos comentários classificados é necessários fazer o login em ``https://localhost/`` com usuário e senha criado com a api `api/auth/register`. Esse é a parte privada para exibição das classificações.\n\nO portal foi feito com HTML, CSS e JS puro, para agilizar o desenvolvimento, em um cenário real, provavelmente, gastaria mais um tempos para desenvolver algo com React, e garantir maior qualidade par o portal de curadoria. Mas, da forma como está, o login funciona com JWT, e exibe os comentários classificados para o usuário, sendo possível fazer alguns filtros simples.\n\n### Relátorios\n\nOs relátorios são os resultados obtidos pelos cálculos dos dados obtidos da classificação de comentários para criação de gráficos.\n\nOptei po deixar os valores apenas em JSON, mas eles representam os seguintes dados:\n1. Frenquencia de categorias, ou seja, porcentagem de quantos comentários foram classificados para cada categoria.\n2. Evolução da quantidade de comentários classificados em cada categorias nos úlitmos 7 dias.\n3. Tags mais recorrente nas últimas 48 horas.\n4. Média de confiabilidade da classificação, por categoria.\n5. Quantidade de comentários classificados por hora.\n\n## Resumo por E-mail\n\nO envio do e-mail acontece com a execução semanal de uma task com Clery-beat. O texto é gerado com os comentários classificados na semana através do LLM da OpenAI. Esse texto é enviado por e-mail, para os endereços cadastrados no `.env` ([verificar seção 'Como executar o projeto'](#como-executar-o-projeto)).\n\nSe quiser testar *StandAlone*, pode rodar:\n\n```sh\ndocker compose exec api bash -c \"\\\npython - \u003c\u003c 'EOF'\\ \nfrom app.main import create_app \napp = create_app() \nwith app.app_context(): \n    from app.tasks import enviar_resumo_semanal \n    enviar_resumo_semanal() \nEOF\"\n```\n\n* Exemplo de e-mail gerado:\n\n![E-mail gerado com modelo de LLM enviado automaticamente pela task](\u003cimages/exemplo-email.jpg\u003e)\n\n## Extra: Insights\n\nO endpoint extra foi implementado no caminho `api/insights/perguntar`, passando um JSON como:\n```json\n{\n    \"pergunta\": \"Conteúdo da pergunta em linguagem natual aqui?\"\n}\n```\n\nO response será construido pelo LLM da OpenAI, usando como contexto os últimos 8 relatórios semanais enviados, sendo que, no mínimo **3 devem existir**, parao endpoint funcionar.\n\n* Exemplo de insight:\n\n![Exemplo de requisição de insight em resumos semanais via Postman](images/exemplo-insight.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmateusherrera%2Ffeedback-classifier","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmateusherrera%2Ffeedback-classifier","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmateusherrera%2Ffeedback-classifier/lists"}