emidiosouza commited on
Commit
fe69607
·
1 Parent(s): af87f53

refact: v2.1.0

Browse files
Dockerfile ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use a imagem base do Python
2
+ FROM python:3.9-slim
3
+
4
+ # Defina um diretório de trabalho padrão dentro do container
5
+ WORKDIR /app
6
+
7
+ # Copie os arquivos necessários para o container
8
+ COPY requirements.txt /app/requirements.txt
9
+ COPY . /app
10
+
11
+ # Instale as dependências do sistema e do Python
12
+ RUN apt-get update && apt-get install -y \
13
+ build-essential \
14
+ curl \
15
+ software-properties-common \
16
+ git \
17
+ && rm -rf /var/lib/apt/lists/*
18
+
19
+ RUN pip3 install --no-cache-dir -r requirements.txt
20
+
21
+ # Exponha a porta onde o Streamlit será executado
22
+ EXPOSE 8501
23
+
24
+ # Adicione uma verificação de saúde (healthcheck)
25
+ HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health || exit 1
26
+
27
+ # Comando para iniciar o Streamlit
28
+ ENTRYPOINT ["streamlit", "run", "main.py", "--server.port=8501", "--server.address=0.0.0.0"]
README.md CHANGED
@@ -1,12 +1,83 @@
1
- ---
2
- title: GeoCosmos 2.0 - Banco de Dados
3
- emoji: 🏔
4
- colorFrom: gray
5
- colorTo: green
6
- sdk: streamlit
7
- sdk_version: 1.37.0
8
- app_file: Início.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # GeoCosmos
3
+
4
+ GeoCosmos é uma plataforma desenvolvida pela Kukac para a Future Mining, especializada em extração de dados de artigos científicos de geologia utilizando Inteligência Artificial. O objetivo deste projeto é proporcionar uma interface amigável para acesso e manipulação dos dados extraídos de artigos, incluindo a possibilidade de filtrar, visualizar e excluir registros diretamente de um banco de dados SQL.
5
+
6
+ ## Getting Started
7
+
8
+ Para começar a utilizar o projeto, siga as etapas abaixo:
9
+
10
+ 1. **Clone o repositório**
11
+
12
+ Clone este repositório para sua máquina local:
13
+
14
+ ```bash
15
+ git clone https://gitlab.kukac.com.br/kukac/future-mining/geocosmos.git
16
+ ```
17
+
18
+ 2. **Configuração de ambiente**
19
+
20
+ Certifique-se de que você tem o Python 3.8+ instalado em sua máquina. Em seguida, instale as dependências necessárias utilizando o `pip`:
21
+
22
+ ```bash
23
+ pip install -r requirements.txt
24
+ ```
25
+
26
+ 3. **Configuração das variáveis de ambiente**
27
+
28
+ O projeto utiliza o arquivo `.env` para carregar as variáveis de ambiente, como credenciais de acesso ao banco de dados. Certifique-se de configurar corretamente este arquivo com as variáveis `username`, `password` e `DATABASE_URL`.
29
+
30
+ ## Instalação
31
+
32
+ Para rodar o projeto localmente, execute o seguinte comando:
33
+
34
+ ```bash
35
+ streamlit run app.py
36
+ ```
37
+
38
+ Isso iniciará o servidor do Streamlit e abrirá a interface no seu navegador.
39
+
40
+ ## Funcionalidades
41
+
42
+ - **Autenticação de usuário**: Um sistema de login que protege o acesso à plataforma.
43
+ - **Visualização de dados**: Exibição de artigos científicos de geologia extraídos de um banco de dados SQL.
44
+ - **Filtros avançados**: Permite filtrar dados com base em colunas numéricas, categóricas ou de data.
45
+ - **Edição de dados**: Interface para selecionar, visualizar e excluir registros do banco de dados.
46
+ - **Download de dados**: Exporte os dados visualizados como arquivos CSV.
47
+
48
+ ## Como usar
49
+
50
+ 1. **Login**: Para acessar a plataforma, insira o nome de usuário e senha fornecidos.
51
+ 2. **Filtrar dados**: Escolha as colunas e os filtros que deseja aplicar aos dados.
52
+ 3. **Editar dados**: Selecione as linhas que deseja editar ou excluir e execute as ações necessárias.
53
+ 4. **Exportar dados**: Baixe os dados filtrados ou modificados como CSV.
54
+
55
+ ## Suporte
56
+
57
+ Se precisar de ajuda, abra uma issue no repositório ou entre em contato com a equipe de suporte da Kukac.
58
+
59
+ ## Roadmap
60
+
61
+ - **Próximas funcionalidades**:
62
+ - Integração com novos bancos de dados geológicos.
63
+ - Melhoria na interface de filtragem e visualização de dados.
64
+
65
+ ## Contribuindo
66
+
67
+ Este projeto é mantido pela Kukac e está aberto a contribuições. Caso queira colaborar, siga os passos abaixo:
68
+
69
+ 1. Fork este repositório.
70
+ 2. Crie uma nova branch para suas alterações.
71
+ 3. Envie um pull request com suas melhorias.
72
+
73
+ ## Autores e Agradecimentos
74
+
75
+ Este projeto foi desenvolvido pela equipe de inovação da Kukac, com o apoio da Future Mining.
76
+
77
+ ## Licença
78
+
79
+ Este projeto é de propriedade da Future Mining.
80
+
81
+ ## Status do Projeto
82
+
83
+ O desenvolvimento está ativo e contínuo.
__pycache__/filter.cpython-311.pyc ADDED
Binary file (4.76 kB). View file
 
image.png DELETED
Binary file (6.88 kB)
 
Início.py → main.py RENAMED
@@ -1,14 +1,17 @@
 
 
 
1
  import streamlit as st
2
  import pandas as pd
3
  from sqlalchemy import create_engine, MetaData, Table, delete
4
  from sqlalchemy.orm import sessionmaker
5
- from filter import filter_dataframe
6
  import os
7
  import time
8
 
9
  # Dados de login
10
- USERNAME = os.getenv("username")
11
- PASSWORD = os.getenv("password")
12
 
13
  # Configurações da página
14
  st.set_page_config(
@@ -25,6 +28,7 @@ def authenticate_user(username: str, password: str) -> bool:
25
  def login_screen():
26
  st.subheader('⛰️ Banco de Dados - GeoCosmos')
27
 
 
28
  with st.form(key='login_form'):
29
  username = st.text_input("Nome de usuário")
30
  password = st.text_input("Senha", type='password')
@@ -41,41 +45,27 @@ def login_screen():
41
 
42
  # Função para ler dados da tabela SQL a partir de um arquivo
43
  def load_data() -> pd.DataFrame:
44
- # Acesse as variáveis de ambiente
45
- dialect = os.getenv("DB_DIALECT")
46
- driver = os.getenv("DB_DRIVER")
47
- username = os.getenv("DB_USERNAME")
48
- password = os.getenv("DB_PASSWORD")
49
- host = os.getenv("DB_HOST")
50
- port = os.getenv("DB_PORT")
51
- database = os.getenv("DB_DATABASE")
52
-
53
- connection_url = f"{dialect}+{driver}://{username}:{password}@{host}:{port}/{database}"
54
-
55
  engine = create_engine(connection_url)
56
- Session = sessionmaker(bind=engine)
57
- session = Session()
58
-
59
- with open('query.sql', 'r', encoding='utf-8') as file:
60
- query = file.read()
61
-
62
- df = pd.read_sql(query, engine)
63
-
64
- return df, engine, session
65
 
66
  # Função para deletar linhas selecionadas
67
- def delete_selected_rows(selected_ids, engine, session):
68
  meta = MetaData()
69
- table = Table('Extraction', meta, autoload_with=engine, schema="public")
70
- with session.begin():
71
- for id_value in selected_ids:
72
- stmt = delete(table).where(table.c.id == id_value)
73
- session.execute(stmt)
74
- session.commit()
 
 
 
 
 
 
75
 
76
- # Função para download dos dados como CSV
77
- def convert_df_to_csv(df):
78
- return df.to_csv(index=False).encode('utf-8')
79
 
80
  # Verificar o estado de login
81
  if 'logged_in' not in st.session_state:
@@ -86,55 +76,72 @@ if not st.session_state['logged_in']:
86
  login_screen()
87
  else:
88
  st.header('⛰️ Banco de Dados - GeoCosmos')
89
-
90
- df, engine, session = load_data()
91
-
 
 
 
 
 
 
 
 
 
 
92
  # Aplica o filtro ao dataframe
93
  filtered_df = filter_dataframe(df)
94
 
 
95
  all_columns = filtered_df.columns.tolist()
96
  selected_columns = st.multiselect(
97
  'Escolha as colunas para exibir:',
98
- options=[column for column in all_columns if column != 'id'],
99
  placeholder="Selecione uma ou mais colunas",
100
- default=[]
 
101
  )
102
 
103
-
104
-
105
  if selected_columns:
106
- if 'id' not in selected_columns:
107
- selected_columns.append('id')
108
- if 'Nome do documento' not in selected_columns:
109
- selected_columns.append('Nome do documento')
110
- displayed_df = filtered_df[selected_columns]
 
111
  else:
112
  displayed_df = filtered_df
113
 
114
- # Adiciona a coluna de seleção
115
- displayed_df.insert(0, 'Seleção', False)
116
-
117
- # Exibe o dataframe filtrado com checkboxes para seleção
118
- edited_df = st.data_editor(displayed_df, disabled=[col for col in displayed_df.columns if col != 'Seleção'], hide_index=False, column_order = [col for col in displayed_df.columns if col != 'id'])
119
 
120
- # Verifica se há pelo menos um checkbox selecionado
121
- selected_ids = edited_df.loc[edited_df['Seleção'], "id"].tolist()
122
 
123
- # Pega os nomes dos documentos selecionados
124
- selected_docs = edited_df.loc[edited_df['Seleção'], 'Nome do documento'].tolist()
 
 
 
 
 
 
 
125
 
 
 
 
 
 
126
  if selected_ids:
127
  with st.popover("APAGAR"):
128
  st.warning("Você está prestes a apagar dados. Esta ação não pode ser desfeita.")
129
  confirmation = st.text_input("Digite 'APAGAR' e pressione ENTER para confirmar a deleção:")
130
-
131
  if confirmation == "APAGAR":
132
  if st.button("CONFIRMAR"):
133
  with st.spinner('Deletando arquivos...'):
134
- delete_selected_rows(selected_ids, engine, session)
135
  st.success(f'Os seguintes documentos foram apagados: {selected_docs}')
136
- selected_ids = []
137
- time.sleep(3)
138
  st.rerun()
139
-
140
-
 
1
+ from dotenv import load_dotenv
2
+ load_dotenv()
3
+
4
  import streamlit as st
5
  import pandas as pd
6
  from sqlalchemy import create_engine, MetaData, Table, delete
7
  from sqlalchemy.orm import sessionmaker
8
+ from utils.filter import filter_dataframe
9
  import os
10
  import time
11
 
12
  # Dados de login
13
+ USERNAME = os.getenv('username')
14
+ PASSWORD = os.getenv('password')
15
 
16
  # Configurações da página
17
  st.set_page_config(
 
28
  def login_screen():
29
  st.subheader('⛰️ Banco de Dados - GeoCosmos')
30
 
31
+
32
  with st.form(key='login_form'):
33
  username = st.text_input("Nome de usuário")
34
  password = st.text_input("Senha", type='password')
 
45
 
46
  # Função para ler dados da tabela SQL a partir de um arquivo
47
  def load_data() -> pd.DataFrame:
48
+ connection_url = os.getenv("DATABASE_URL")
 
 
 
 
 
 
 
 
 
 
49
  engine = create_engine(connection_url)
50
+ df = pd.read_sql_table(table_name=os.getenv("TABLE_NAME"), con=engine)
51
+ return df, engine # Removido o retorno da sessão
 
 
 
 
 
 
 
52
 
53
  # Função para deletar linhas selecionadas
54
+ def delete_selected_rows(selected_ids, engine, table):
55
  meta = MetaData()
56
+ table = Table(table, meta, autoload_with=engine, schema="public")
57
+
58
+ Session = sessionmaker(bind=engine)
59
+ session = Session()
60
+
61
+ try:
62
+ with session.begin():
63
+ for id_value in selected_ids:
64
+ stmt = delete(table).where(table.c.id == id_value)
65
+ session.execute(stmt)
66
+ finally:
67
+ session.close() # Garante o fechamento da sessão
68
 
 
 
 
69
 
70
  # Verificar o estado de login
71
  if 'logged_in' not in st.session_state:
 
76
  login_screen()
77
  else:
78
  st.header('⛰️ Banco de Dados - GeoCosmos')
79
+
80
+ placeholder = st.empty()
81
+
82
+ with st.spinner("Carregando dados..."):
83
+ # Carrega os dados apenas uma vez
84
+ if 'df' not in st.session_state:
85
+ df, engine = load_data()
86
+ st.session_state.df = df
87
+ st.session_state.engine = engine
88
+ else:
89
+ df = st.session_state.df
90
+ engine = st.session_state.engine
91
+
92
  # Aplica o filtro ao dataframe
93
  filtered_df = filter_dataframe(df)
94
 
95
+
96
  all_columns = filtered_df.columns.tolist()
97
  selected_columns = st.multiselect(
98
  'Escolha as colunas para exibir:',
99
+ options=[col for col in all_columns if col not in ['id', 'Seleção']],
100
  placeholder="Selecione uma ou mais colunas",
101
+ default=[],
102
+ key="column_selector"
103
  )
104
 
105
+
 
106
  if selected_columns:
107
+ displayed_columns = selected_columns + ['id']
108
+ if 'Nome do documento' not in displayed_columns:
109
+ displayed_columns.insert(1, 'Nome do documento')
110
+ # Remove duplicatas e garante a ordem
111
+ displayed_columns = list(dict.fromkeys(displayed_columns))
112
+ displayed_df = filtered_df[displayed_columns]
113
  else:
114
  displayed_df = filtered_df
115
 
116
+ if st.button("🔄", type="tertiary"):
117
+ del st.session_state["df"]
118
+ st.rerun()
 
 
119
 
 
 
120
 
121
+ # Exibe o editor com ordem fixa e key estável
122
+ selected_df = st.dataframe(
123
+ displayed_df,
124
+ column_order=[col for col in displayed_df.columns if col != 'Seleção'],
125
+ hide_index=False,
126
+ key="stable_data_editor",
127
+ on_select="rerun",
128
+ selection_mode="multi-row"
129
+ )
130
 
131
+ selected_rows = selected_df['selection']['rows']
132
+ selected_ids = displayed_df.iloc[selected_rows]["id"].tolist()
133
+
134
+ # Pega os nomes dos documentos selecionados
135
+ selected_docs = displayed_df.iloc[selected_rows]["Nome do documento"].tolist()
136
  if selected_ids:
137
  with st.popover("APAGAR"):
138
  st.warning("Você está prestes a apagar dados. Esta ação não pode ser desfeita.")
139
  confirmation = st.text_input("Digite 'APAGAR' e pressione ENTER para confirmar a deleção:")
 
140
  if confirmation == "APAGAR":
141
  if st.button("CONFIRMAR"):
142
  with st.spinner('Deletando arquivos...'):
143
+ delete_selected_rows(selected_ids, engine, os.getenv("TABLE_NAME"))
144
  st.success(f'Os seguintes documentos foram apagados: {selected_docs}')
145
+ del st.session_state["df"]
 
146
  st.rerun()
147
+
 
query.sql DELETED
@@ -1,221 +0,0 @@
1
- SELECT
2
- id,
3
- e.id AS "Id da entrada no banco",
4
- e."createdAt" AS "Data Criação",
5
- e.name AS "Nome do documento",
6
-
7
- -- Alvo ou depósito
8
- COALESCE(data->'responseData'->0->'response'->>'target_or_deposit', 'Não especificado') AS "Alvo ou depósito",
9
-
10
- -- Tipo de mineralização
11
- COALESCE(
12
- array_to_string(
13
- ARRAY(
14
- SELECT DISTINCT CONCAT(
15
- mineralization->>'type'
16
- )
17
- FROM jsonb_array_elements(data->'responseData'->0->'response'->'mineralizations') AS mineralization
18
- ), ', '
19
- ), 'Não especificado'
20
- ) AS "Tipo de mineralização",
21
-
22
- -- cidade
23
- COALESCE(data->'responseData'->0->'response'->'region'->>'city', 'Não especificado') AS "Cidades",
24
- -- estado
25
- COALESCE(data->'responseData'->0->'response'->'region'->>'state', 'Não especificado') AS "Estado",
26
- -- país
27
- COALESCE(data->'responseData'->0->'response'->'region'->>'country', 'Não especificado') AS "País",
28
- -- região
29
- COALESCE(data->'responseData'->0->'response'->'region'->>'region_name', 'Não especificado') AS "Região",
30
- -- Consolidar quantidade, tipo de mineralização e minerais com concentração
31
- -- Consolidar quantidade, tipo de mineralização e minerais com concentração
32
- CASE
33
- WHEN jsonb_typeof(data->'responseData'->0->'response'->'mineralizations') = 'array' THEN
34
- array_to_string(
35
- ARRAY(
36
- SELECT CONCAT(
37
- -- Mostra a quantidade e o tipo de mineralização
38
- mineralization->>'quantity', ' de ',
39
- mineralization->>'type',
40
-
41
- -- Condicional para incluir minerais apenas se existirem
42
- CASE
43
- WHEN jsonb_typeof(mineralization->'minerals') = 'array'
44
- AND jsonb_array_length(mineralization->'minerals') > 0 THEN
45
- ': ' || array_to_string(
46
- ARRAY(
47
- SELECT CONCAT(
48
- mineral->>'name',
49
- ' (', mineral->>'concentration', ')'
50
- )
51
- FROM jsonb_array_elements(mineralization->'minerals') AS mineral
52
- WHERE mineral IS NOT NULL -- Ignora nulos
53
- ), ', '
54
- )
55
- ELSE '' -- Não mostra nada se não houver minerais
56
- END
57
- )
58
- FROM jsonb_array_elements(data->'responseData'->0->'response'->'mineralizations') AS mineralization
59
- WHERE mineralization IS NOT NULL -- Ignora nulos
60
- ), '; '
61
- )
62
- ELSE NULL -- Retorna NULL se o array não existir
63
- END AS "Quantidades e concentrações",
64
-
65
-
66
- -- extra_info
67
- COALESCE(data->'responseData'->0->'response'->'region'->>'extra_info', 'Não especificado') AS "Informações adicionais",
68
-
69
- COALESCE(NULLIF(data->'responseData'->4->'response'->>'mineralization_signatures', '[]'), 'Não especificado') AS "Assinaturas de mineralização", -- Foi decidido que este era melhor
70
-
71
- COALESCE(NULLIF(data->'responseData'->0->'response'->'geological_context'->>'tectonic_context', ''), 'Não especificado') AS "Contexto tectônico", -- OK
72
- COALESCE(NULLIF(data->'responseData'->0->'response'->'geological_context'->>'geological_context_description', ''), 'Não especificado') AS "Contexto geológico", -- OK
73
- COALESCE(NULLIF(data->'responseData'->0->'response'->'geological_context'->>'context_of_present_rocks', ''), 'Não especificado') AS "Rochas presentes", -- OK
74
-
75
- -- Rochas sedimentares
76
- CASE
77
- WHEN jsonb_typeof(data->'responseData'->1->'response'->'rockTypes'->'sedimentares') = 'array' THEN
78
- array_to_string(
79
- ARRAY(
80
- SELECT CONCAT(
81
- sedimentares->>'name',
82
- ' (Tipo: ', sedimentares->>'type',
83
- ', Relações de contato: ', COALESCE(sedimentares->>'contact_relations', 'ND'), ')'
84
- )
85
- FROM jsonb_array_elements(data->'responseData'->1->'response'->'rockTypes'->'sedimentares') AS sedimentares
86
- WHERE sedimentares IS NOT NULL -- Ignora elementos nulos
87
- ), '; '
88
- )
89
- ELSE '' -- Retorna vazio se o array não existir
90
- END AS "Rochas sedimentares",
91
- -- Rochas metamórficas
92
- CASE
93
- WHEN jsonb_typeof(data->'responseData'->1->'response'->'rockTypes'->'metamórficas') = 'array' THEN
94
- array_to_string(
95
- ARRAY(
96
- SELECT CONCAT(
97
- metam->>'name',
98
- ' (Tipo: ', metam->>'type',
99
- ', Relações de contato: ', COALESCE(metam->>'contact_relations', 'ND'), ')'
100
- )
101
- FROM jsonb_array_elements(data->'responseData'->1->'response'->'rockTypes'->'metamórficas') AS metam
102
- WHERE metam IS NOT NULL
103
- ), '; '
104
- )
105
- ELSE ''
106
- END AS "Rochas metamórficas",
107
-
108
- -- Rochas ígneas intrusivas
109
- CASE
110
- WHEN jsonb_typeof(data->'responseData'->1->'response'->'rockTypes'->'ígneas_intrusivas') = 'array' THEN
111
- array_to_string(
112
- ARRAY(
113
- SELECT CONCAT(
114
- igneas_i->>'name',
115
- ' (Tipo: ', igneas_i->>'type',
116
- ', Relações de contato: ', COALESCE(igneas_i->>'contact_relations', 'ND'), ')'
117
- )
118
- FROM jsonb_array_elements(data->'responseData'->1->'response'->'rockTypes'->'ígneas_intrusivas') AS igneas_i
119
- WHERE igneas_i IS NOT NULL
120
- ), '; '
121
- )
122
- ELSE ''
123
- END AS "Rochas ígneas intrusivas",
124
-
125
- -- Rochas ígneas extrusivas
126
- CASE
127
- WHEN jsonb_typeof(data->'responseData'->1->'response'->'rockTypes'->'ígneas_extrusivas') = 'array' THEN
128
- array_to_string(
129
- ARRAY(
130
- SELECT CONCAT(
131
- igneas_e->>'name',
132
- ' (Tipo: ', igneas_e->>'type',
133
- ', Relações de contato: ', COALESCE(igneas_e->>'contact_relations', 'ND'), ')'
134
- )
135
- FROM jsonb_array_elements(data->'responseData'->1->'response'->'rockTypes'->'ígneas_extrusivas') AS igneas_e
136
- WHERE igneas_e IS NOT NULL
137
- ), '; '
138
- )
139
- ELSE ''
140
- END AS "Rochas ígneas extrusivas",
141
-
142
- -- Rochas ígneas subvulcânicas
143
- CASE
144
- WHEN jsonb_typeof(data->'responseData'->1->'response'->'rockTypes'->'ígneas_subvulcânicas') = 'array' THEN
145
- array_to_string(
146
- ARRAY(
147
- SELECT CONCAT(
148
- igneas_sv->>'name',
149
- ' (Tipo: ', igneas_sv->>'type',
150
- ', Relações de contato: ', COALESCE(igneas_sv->>'contact_relations', 'ND'), ')'
151
- )
152
- FROM jsonb_array_elements(data->'responseData'->1->'response'->'rockTypes'->'ígneas_subvulcânicas') AS igneas_sv
153
- WHERE igneas_sv IS NOT NULL
154
- ), '; '
155
- )
156
- ELSE ''
157
- END AS "Rochas ígneas subvulcânicas",
158
-
159
- -- Rochas hospedeiras
160
- COALESCE(NULLIF(data->'responseData'->2->'response'->'host_rocks'->>'name', '[null]'), 'Não identificado') AS "Nomes de rochas hospedeiras",
161
- COALESCE(NULLIF(data->'responseData'->2->'response'->'host_rocks'->>'geologicalEnvironments', '[null]'), 'Não identificado') AS "Ambiente geológico",
162
-
163
- COALESCE(NULLIF(array_to_string(ARRAY(SELECT jsonb_array_elements_text(data->'responseData'->2->'response'->'mineralFabric')), ', '), ''), 'Não identificado') AS "Textura de mineralização",
164
- COALESCE(NULLIF(array_to_string(ARRAY(
165
- SELECT jsonb_extract_path_text(associatedMinerals, 'name') || ' (' || 'Cor: ' || jsonb_extract_path_text(associatedMinerals, 'color') || ', Textura: ' || jsonb_extract_path_text(associatedMinerals, 'textures') || ', Nível de Alteração: ' || jsonb_extract_path_text(associatedMinerals, 'alterationLevel') || ')'
166
- FROM jsonb_array_elements(data->'responseData'->2->'response'->'associatedMinerals') AS associatedMinerals
167
- ), ', '), ''), 'Não identificado') AS "Minerais associados",
168
-
169
- -- Tipos de processos alterações
170
- COALESCE(
171
- NULLIF(
172
- array_to_string(
173
- ARRAY(
174
- SELECT alteration->>'alterationType'
175
- FROM jsonb_array_elements(data->'responseData'->2->'response'->'alteration_processes') AS alteration
176
- ), ', '
177
- ), ''
178
- ), 'Não identificado'
179
- ) AS "Tipos de processos de alterações",
180
-
181
- -- Minerais associados a alterações hidrotermais
182
- COALESCE(
183
- NULLIF(
184
- array_to_string(
185
- ARRAY(
186
- SELECT jsonb_array_elements_text(alteration->'associatedMinerals')
187
- FROM jsonb_array_elements(data->'responseData'->2->'response'->'alteration_processes') AS alteration
188
- ), ', '
189
- ), ''
190
- ), 'Não identificado'
191
- ) AS "Minerais associados a processos de alteração",
192
-
193
- COALESCE(NULLIF(data->'responseData'->2->'response'->>'chemical_pattern_analysis', '[]'), 'Não especificado') AS "Análise de padrão químico",
194
-
195
-
196
- COALESCE(NULLIF(data->'responseData'->3->'response'->>'stable_isotopes', '[]'), 'Não especificado') AS "Isótopos Estáveis e Radiogênicos",
197
- COALESCE(NULLIF(data->'responseData'->3->'response'->>'trace_elements_and_rare_earths', '[]'), 'Não especificado') AS "Elementos traço e terras raras",
198
-
199
- -- COALESCE(NULLIF(data->'responseData'->3->'response'->>'structural_mapping', '[]'), 'Não especificado') AS "Geologia estrutural da região",
200
- -- COALESCE(NULLIF(data->'responseData'->4->'response'->>'structureMineralizationRelation', '[]'), 'Não especificado') AS "Relação estrutura-mineralização",
201
-
202
- COALESCE(NULLIF(data->'responseData'->4->'response'->>'geophysical_signatures', '[]'), 'Não especificado') AS "Assinaturas geofísicas",
203
-
204
- COALESCE(NULLIF(data->'responseData'->5->'response'->>'additional_information', '[]'), 'Não especificado') AS "Informações adicionais de mineralização",
205
- COALESCE(NULLIF(data->'responseData'->6->'response'->>'estimates', '[]'), 'Não especificado') AS "Estimativas de quantidade de mineralização",
206
-
207
- COALESCE(NULLIF(data->'responseData'->7->'response'->>'potential', '[]'), 'Não especificado') AS "Potencial de descoberta",
208
- COALESCE(NULLIF(data->'responseData'->8->'response'->>'strengths', '[]'), 'Não especificado') AS "Pontos fortes",
209
- COALESCE(NULLIF(data->'responseData'->8->'response'->>'investment_risk', '[]'), 'Não especificado') AS "Risco de investimento",
210
- COALESCE(NULLIF(data->'responseData'->8->'response'->>'economic_potential', '[]'), 'Não especificado') AS "Potencial econômico",
211
- COALESCE(NULLIF(data->'responseData'->9->'response'->>'explanation', '[]'), 'Não especificado') AS "Explicação da favorabilidade",
212
- COALESCE(NULLIF(data->'responseData'->9->'response'->>'favorability', '[]'), 'Não especificado') AS "Favorabilidade"
213
-
214
-
215
- FROM
216
- public."Extraction" e
217
-
218
- ORDER BY
219
- e."createdAt";
220
-
221
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -2,4 +2,5 @@ pandas==2.2.2
2
  SQLAlchemy
3
  streamlit
4
  toml==0.10.2
5
- psycopg2
 
 
2
  SQLAlchemy
3
  streamlit
4
  toml==0.10.2
5
+ psycopg2-binary
6
+ python-dotenv
utils/__pycache__/filter.cpython-310.pyc ADDED
Binary file (4.22 kB). View file
 
utils/__pycache__/filter.cpython-311.pyc ADDED
Binary file (4.8 kB). View file
 
filter.py → utils/filter.py RENAMED
@@ -7,37 +7,84 @@ from pandas.api.types import (
7
  is_object_dtype,
8
  )
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  def filter_dataframe(df: pd.DataFrame) -> pd.DataFrame:
11
  df = df.copy()
12
 
13
- # Tentar converter datas para um formato padrão (datetime, sem fuso horário)
 
 
 
 
14
  for col in df.columns:
15
  if is_object_dtype(df[col]):
16
  try:
17
- df[col] = pd.to_datetime(df[col])
18
  except Exception:
19
  pass
20
-
21
  if is_datetime64_any_dtype(df[col]):
22
  df[col] = df[col].dt.tz_localize(None)
23
 
24
  modification_container = st.container()
25
 
26
  with modification_container:
27
- to_filter_columns = st.multiselect("Filtrar por valor", [column for column in df.columns if column != 'id'], placeholder="Selecione um ou mais itens para filtrar")
 
 
 
 
28
  for column in to_filter_columns:
29
  left, right = st.columns((1, 20))
30
  left.write("↳")
31
- # Tratar colunas com < 10 valores únicos como categóricos
 
32
  if is_categorical_dtype(df[column]) or df[column].nunique() < 5000:
 
 
 
 
 
 
 
 
 
33
  user_cat_input = right.multiselect(
34
  f"Valores para {column}",
35
- df[column].unique(),
36
- default=[], # Lista vazia para não ter valores pré-selecionados
37
  placeholder="Escolha uma opção"
38
  )
39
  if user_cat_input: # Filtrar apenas se houver seleção
40
- df = df[df[column].isin(user_cat_input)]
 
 
 
 
 
41
  elif is_numeric_dtype(df[column]):
42
  _min = float(df[column].min())
43
  _max = float(df[column].max())
@@ -59,22 +106,33 @@ def filter_dataframe(df: pd.DataFrame) -> pd.DataFrame:
59
  ),
60
  format="YYYY-MM-DD",
61
  )
62
- if len(user_date_input) == 2:
63
- user_date_input = tuple(map(pd.to_datetime, user_date_input))
64
- start_date, end_date = user_date_input
65
- df = df.loc[df[column].between(start_date, end_date)]
66
  else:
67
- # Para colunas de texto, mostre uma seleção múltipla se houver poucos valores únicos
68
- unique_values = df[column].dropna().unique()
69
- if len(unique_values) < 5000: # Ajuste o limite conforme necessário
 
 
 
 
 
 
 
70
  user_text_input = right.multiselect(
71
  f"Valores para {column}",
72
  unique_values,
73
  default=[],
74
  placeholder="Escolha uma opção",
75
  )
76
- if user_text_input: # Filtrar apenas se houver seleção
77
- df = df[df[column].isin(user_text_input)]
 
 
 
 
 
78
  else:
79
  user_text_input = right.text_input(
80
  f"Substring ou regex em {column}",
 
7
  is_object_dtype,
8
  )
9
 
10
+ def make_hashable(x):
11
+ """
12
+ Converte recursivamente listas (e, se necessário, dicionários) em tipos hashable.
13
+ """
14
+ if isinstance(x, list):
15
+ return tuple(make_hashable(e) for e in x)
16
+ # Se precisar, trate dicionários também:
17
+ if isinstance(x, dict):
18
+ return tuple(sorted((k, make_hashable(v)) for k, v in x.items()))
19
+ return x
20
+
21
+ def flatten_unique_values(series: pd.Series) -> list:
22
+ """
23
+ Achata os valores únicos de uma série.
24
+ Se um valor for uma tupla, extrai cada item individualmente.
25
+ """
26
+ unique_values_set = set()
27
+ for val in series.dropna().unique():
28
+ # Se o valor for uma tupla, adicione cada item separadamente
29
+ if isinstance(val, tuple):
30
+ unique_values_set.update(val)
31
+ else:
32
+ unique_values_set.add(val)
33
+ return list(unique_values_set)
34
+
35
  def filter_dataframe(df: pd.DataFrame) -> pd.DataFrame:
36
  df = df.copy()
37
 
38
+ # Primeiro, converta todos os valores da DataFrame para hashable
39
+ for col in df.columns:
40
+ df[col] = df[col].apply(make_hashable)
41
+
42
+ # Tenta converter strings para datetime e remover fuso horário
43
  for col in df.columns:
44
  if is_object_dtype(df[col]):
45
  try:
46
+ df[col] = pd.to_datetime(df[col], format="%d-%m-%Y")
47
  except Exception:
48
  pass
 
49
  if is_datetime64_any_dtype(df[col]):
50
  df[col] = df[col].dt.tz_localize(None)
51
 
52
  modification_container = st.container()
53
 
54
  with modification_container:
55
+ to_filter_columns = st.multiselect(
56
+ "Filtrar por valor",
57
+ [column for column in df.columns if column != 'id'],
58
+ placeholder="Selecione um ou mais itens para filtrar"
59
+ )
60
  for column in to_filter_columns:
61
  left, right = st.columns((1, 20))
62
  left.write("↳")
63
+
64
+ # Para colunas categóricas ou com poucos valores únicos, use multiselect
65
  if is_categorical_dtype(df[column]) or df[column].nunique() < 5000:
66
+ raw_unique_values = df[column].dropna().unique()
67
+ # Verifica se há valores do tipo tupla (decorrentes de listas convertidas)
68
+ if any(isinstance(val, tuple) for val in raw_unique_values):
69
+ unique_values = flatten_unique_values(df[column])
70
+ is_flattened = True
71
+ else:
72
+ unique_values = list(raw_unique_values)
73
+ is_flattened = False
74
+
75
  user_cat_input = right.multiselect(
76
  f"Valores para {column}",
77
+ unique_values,
78
+ default=[], # Sem valores pré-selecionados
79
  placeholder="Escolha uma opção"
80
  )
81
  if user_cat_input: # Filtrar apenas se houver seleção
82
+ if is_flattened:
83
+ df = df[df[column].apply(
84
+ lambda x: any(item in x for item in user_cat_input) if isinstance(x, tuple) else x in user_cat_input
85
+ )]
86
+ else:
87
+ df = df[df[column].isin(user_cat_input)]
88
  elif is_numeric_dtype(df[column]):
89
  _min = float(df[column].min())
90
  _max = float(df[column].max())
 
106
  ),
107
  format="YYYY-MM-DD",
108
  )
109
+ if isinstance(user_date_input, tuple) and len(user_date_input) == 2:
110
+ start_date, end_date = map(pd.to_datetime, user_date_input)
111
+ df = df[df[column].between(start_date, end_date)]
 
112
  else:
113
+ # Para colunas de texto
114
+ raw_unique_values = df[column].dropna().unique()
115
+ if any(isinstance(val, tuple) for val in raw_unique_values):
116
+ unique_values = flatten_unique_values(df[column])
117
+ is_flattened = True
118
+ else:
119
+ unique_values = list(raw_unique_values)
120
+ is_flattened = False
121
+
122
+ if len(unique_values) < 5000:
123
  user_text_input = right.multiselect(
124
  f"Valores para {column}",
125
  unique_values,
126
  default=[],
127
  placeholder="Escolha uma opção",
128
  )
129
+ if user_text_input:
130
+ if is_flattened:
131
+ df = df[df[column].apply(
132
+ lambda x: any(item in x for item in user_text_input) if isinstance(x, tuple) else x in user_text_input
133
+ )]
134
+ else:
135
+ df = df[df[column].isin(user_text_input)]
136
  else:
137
  user_text_input = right.text_input(
138
  f"Substring ou regex em {column}",