Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- app.py +175 -0
- requirements.txt +5 -0
app.py
ADDED
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import pandas as pd
|
3 |
+
import re
|
4 |
+
import os
|
5 |
+
from datetime import timedelta
|
6 |
+
|
7 |
+
# --- Funções auxiliares ---
|
8 |
+
|
9 |
+
def parse_duration(duration_str):
|
10 |
+
try:
|
11 |
+
h, m, s = map(int, duration_str.split(':'))
|
12 |
+
return timedelta(hours=h, minutes=m, seconds=s)
|
13 |
+
except:
|
14 |
+
return timedelta(0)
|
15 |
+
|
16 |
+
def format_timedelta(td):
|
17 |
+
total_seconds = int(td.total_seconds())
|
18 |
+
hours, remainder = divmod(total_seconds, 3600)
|
19 |
+
minutes, seconds = divmod(remainder, 60)
|
20 |
+
return f"{hours:02}:{minutes:02}:{seconds:02}"
|
21 |
+
|
22 |
+
def normalize_html_to_csv(input_html_path, output_csv_path):
|
23 |
+
html_data = pd.read_html(input_html_path)
|
24 |
+
data = html_data[0]
|
25 |
+
data.to_csv(output_csv_path, index=False)
|
26 |
+
|
27 |
+
def normalize_multiple_excel_to_csv(input_directory, output_directory):
|
28 |
+
input_excel_paths = [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith('.xlsx')]
|
29 |
+
output_csv_paths = [os.path.join(output_directory, os.path.splitext(f)[0] + '.csv') for f in os.listdir(input_directory) if f.endswith('.xlsx')]
|
30 |
+
|
31 |
+
for input_excel_path, output_csv_path in zip(input_excel_paths, output_csv_paths):
|
32 |
+
excel_data = pd.read_excel(input_excel_path)
|
33 |
+
unnecessary_columns = [col for col in excel_data.columns if 'Unnamed' in col]
|
34 |
+
if unnecessary_columns:
|
35 |
+
excel_data = excel_data.drop(columns=unnecessary_columns)
|
36 |
+
excel_data.to_csv(output_csv_path, index=False)
|
37 |
+
|
38 |
+
def extract_aluno_pattern(nome):
|
39 |
+
if isinstance(nome, str):
|
40 |
+
match = re.search(r"(\d{8,9}-\w{2})", nome.lower())
|
41 |
+
return match.group(1) if match else None
|
42 |
+
return None
|
43 |
+
|
44 |
+
def match_alunos(tarefas_csv_path, alunos_csv_path, contador_csv_path):
|
45 |
+
try:
|
46 |
+
tarefas_df = pd.read_csv(tarefas_csv_path)
|
47 |
+
alunos_df = pd.read_csv(alunos_csv_path)
|
48 |
+
except pd.errors.EmptyDataError:
|
49 |
+
print(f"Arquivo {tarefas_csv_path} ou {alunos_csv_path} está vazio. Pulando...")
|
50 |
+
return
|
51 |
+
|
52 |
+
tarefas_df.columns = tarefas_df.columns.str.strip()
|
53 |
+
alunos_df.columns = alunos_df.columns.str.strip()
|
54 |
+
|
55 |
+
if 'Aluno' not in tarefas_df.columns or 'Nota' not in tarefas_df.columns or 'Duração' not in tarefas_df.columns:
|
56 |
+
print(f"Colunas 'Aluno', 'Nota' ou 'Duração' não encontradas no arquivo {tarefas_csv_path}. Pulando este arquivo.")
|
57 |
+
return
|
58 |
+
|
59 |
+
try:
|
60 |
+
contador_df = pd.read_csv(contador_csv_path)
|
61 |
+
except FileNotFoundError:
|
62 |
+
contador_df = pd.DataFrame(columns=['Nome do Aluno', 'Tarefas Completadas', 'Acertos Absolutos', 'Total Tempo'])
|
63 |
+
|
64 |
+
if 'Tarefas Completadas' not in contador_df.columns:
|
65 |
+
contador_df['Tarefas Completadas'] = 0
|
66 |
+
if 'Acertos Absolutos' not in contador_df.columns:
|
67 |
+
contador_df['Acertos Absolutos'] = 0
|
68 |
+
if 'Total Tempo' not in contador_df.columns:
|
69 |
+
contador_df['Total Tempo'] = '00:00:00'
|
70 |
+
|
71 |
+
def generate_aluno_pattern(ra, dig_ra):
|
72 |
+
ra_str = str(ra).zfill(9)
|
73 |
+
ra_without_first_two_digits = ra_str[2:]
|
74 |
+
return f"{ra_str[1]}{ra_without_first_two_digits}{dig_ra}-sp".lower()
|
75 |
+
|
76 |
+
alunos_df['Aluno_Pattern'] = alunos_df.apply(lambda row: generate_aluno_pattern(row['RA'], row['Dig. RA']), axis=1)
|
77 |
+
|
78 |
+
tarefas_df['Aluno_Pattern'] = tarefas_df['Aluno'].apply(extract_aluno_pattern)
|
79 |
+
tarefas_df['Duração'] = tarefas_df['Duração'].apply(parse_duration)
|
80 |
+
|
81 |
+
matched_alunos = alunos_df[alunos_df['Aluno_Pattern'].isin(tarefas_df['Aluno_Pattern'])]
|
82 |
+
|
83 |
+
# Incluir todos os alunos, mesmo os que não têm tarefas correspondentes
|
84 |
+
todos_alunos = pd.DataFrame({'Nome do Aluno': alunos_df['Nome do Aluno'], 'Aluno_Pattern': alunos_df['Aluno_Pattern']})
|
85 |
+
todos_alunos = todos_alunos.drop_duplicates()
|
86 |
+
|
87 |
+
for aluno in todos_alunos['Nome do Aluno']:
|
88 |
+
aluno_pattern = todos_alunos.loc[todos_alunos['Nome do Aluno'] == aluno, 'Aluno_Pattern'].values[0]
|
89 |
+
aluno_tarefas = tarefas_df[tarefas_df['Aluno_Pattern'] == aluno_pattern]
|
90 |
+
nota_total = aluno_tarefas['Nota'].sum() if not aluno_tarefas.empty else 0
|
91 |
+
tempo_total = aluno_tarefas['Duração'].sum() if not aluno_tarefas.empty else timedelta(0)
|
92 |
+
|
93 |
+
if aluno in contador_df['Nome do Aluno'].values:
|
94 |
+
contador_df.loc[contador_df['Nome do Aluno'] == aluno, 'Tarefas Completadas'] += aluno_tarefas.shape[0]
|
95 |
+
contador_df.loc[contador_df['Nome do Aluno'] == aluno, 'Acertos Absolutos'] += nota_total
|
96 |
+
current_total_tempo = pd.to_timedelta(contador_df.loc[contador_df['Nome do Aluno'] == aluno, 'Total Tempo'].values[0])
|
97 |
+
contador_df.loc[contador_df['Nome do Aluno'] == aluno, 'Total Tempo'] = str(current_total_tempo + tempo_total)
|
98 |
+
else:
|
99 |
+
contador_df = pd.concat([contador_df, pd.DataFrame({'Nome do Aluno': [aluno], 'Tarefas Completadas': [aluno_tarefas.shape[0]], 'Acertos Absolutos': [nota_total], 'Total Tempo': [str(tempo_total)]})], ignore_index=True)
|
100 |
+
|
101 |
+
contador_df.to_csv(contador_csv_path, index=False)
|
102 |
+
|
103 |
+
return todos_alunos
|
104 |
+
|
105 |
+
def process_all_tarefas_in_directory(directory, alunos_csv_path, contador_csv_path, relatorio_csv_path):
|
106 |
+
tarefas_files = [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith('.csv') and f not in ['alunos_fim.csv', 'contador_tarefas.csv']]
|
107 |
+
|
108 |
+
for i, tarefas_file in enumerate(tarefas_files):
|
109 |
+
print(f"Processando arquivo {i+1}/{len(tarefas_files)}: {tarefas_file}")
|
110 |
+
match_alunos(tarefas_file, alunos_csv_path, contador_csv_path)
|
111 |
+
print(f"Arquivo {tarefas_file} processado.")
|
112 |
+
|
113 |
+
process_relatorios(contador_csv_path, relatorio_csv_path)
|
114 |
+
|
115 |
+
def process_relatorios(contador_csv_path, relatorio_csv_path):
|
116 |
+
contador_df = pd.read_csv(contador_csv_path)
|
117 |
+
contador_df['Média de Acertos'] = ((contador_df['Acertos Absolutos'] / (contador_df['Tarefas Completadas'] * 2)) * 100).round(2).astype(str) + '%'
|
118 |
+
contador_df['Total Tempo'] = pd.to_timedelta(contador_df['Total Tempo'])
|
119 |
+
|
120 |
+
# Evitar divisão por zero para calcular 'Tempo Médio por Tarefa'
|
121 |
+
contador_df['Tempo Médio por Tarefa'] = contador_df.apply(
|
122 |
+
lambda row: format_timedelta(row['Total Tempo'] / row['Tarefas Completadas']) if row['Tarefas Completadas'] > 0 else '00:00:00', axis=1
|
123 |
+
)
|
124 |
+
contador_df['Total Tempo'] = contador_df['Total Tempo'].apply(format_timedelta)
|
125 |
+
contador_df = contador_df.sort_values(by='Tarefas Completadas', ascending=False)
|
126 |
+
contador_df.to_csv(relatorio_csv_path, index=False)
|
127 |
+
return contador_df
|
128 |
+
|
129 |
+
def process_inputs(html_file, tarefa_files):
|
130 |
+
input_directory = "temp_files"
|
131 |
+
output_directory = "temp_files"
|
132 |
+
os.makedirs(input_directory, exist_ok=True)
|
133 |
+
os.makedirs(output_directory, exist_ok=True)
|
134 |
+
|
135 |
+
html_path = os.path.join(input_directory, "alunos.htm")
|
136 |
+
with open(html_path, "wb") as f:
|
137 |
+
f.write(html_file)
|
138 |
+
alunos_csv_path = os.path.join(output_directory, "alunos_fim.csv")
|
139 |
+
normalize_html_to_csv(html_path, alunos_csv_path)
|
140 |
+
|
141 |
+
for idx, tarefa_file in enumerate(tarefa_files):
|
142 |
+
tarefa_path = os.path.join(input_directory, f"tarefa_{idx}.xlsx")
|
143 |
+
with open(tarefa_path, "wb") as f:
|
144 |
+
f.write(tarefa_file)
|
145 |
+
normalize_multiple_excel_to_csv(input_directory, output_directory)
|
146 |
+
|
147 |
+
contador_csv_path = os.path.join(output_directory, "contador_tarefas.csv")
|
148 |
+
relatorio_csv_path = os.path.join(output_directory, "relatorio_final.csv")
|
149 |
+
process_all_tarefas_in_directory(output_directory, alunos_csv_path, contador_csv_path, relatorio_csv_path)
|
150 |
+
|
151 |
+
df = process_relatorios(contador_csv_path, relatorio_csv_path)
|
152 |
+
html_output_path = os.path.join(output_directory, "relatorio_final.html")
|
153 |
+
df.to_html(html_output_path, index=False)
|
154 |
+
return df.to_html(index=False), html_output_path
|
155 |
+
|
156 |
+
def download_html_file(file_path):
|
157 |
+
return file_path
|
158 |
+
|
159 |
+
# --- Interface Gradio ---
|
160 |
+
|
161 |
+
with gr.Blocks() as interface:
|
162 |
+
gr.Markdown("# Processamento de Relatórios de Tarefas")
|
163 |
+
html_file = gr.File(label="Upload HTML File (alunos.htm)", type="binary")
|
164 |
+
excel_files = gr.Files(label="Upload Excel Files (Relatórios de Tarefas)", type="binary", file_count="multiple")
|
165 |
+
generate_btn = gr.Button("Generate Report")
|
166 |
+
output_html = gr.HTML()
|
167 |
+
download_btn = gr.File(label="Download Report")
|
168 |
+
|
169 |
+
def process_and_prepare_download(html_file, tarefa_files):
|
170 |
+
html_content, file_path = process_inputs(html_file, tarefa_files)
|
171 |
+
return html_content, file_path
|
172 |
+
|
173 |
+
generate_btn.click(fn=process_and_prepare_download, inputs=[html_file, excel_files], outputs=[output_html, download_btn])
|
174 |
+
|
175 |
+
interface.launch()
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
pandas
|
2 |
+
gradio
|
3 |
+
openpyxl
|
4 |
+
html5lib
|
5 |
+
lxml
|