histlearn commited on
Commit
7df5762
·
verified ·
1 Parent(s): cf44738

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -38
app.py CHANGED
@@ -86,11 +86,13 @@ def temp_file(suffix=None):
86
  os.unlink(temp.name)
87
 
88
  class PDFReport(FPDF):
 
89
  def __init__(self):
90
  super().__init__()
91
  self.set_auto_page_break(auto=True, margin=15)
92
 
93
  def header_footer(self):
 
94
  self.set_y(-30)
95
  self.line(10, self.get_y(), 200, self.get_y())
96
  self.ln(5)
@@ -99,9 +101,52 @@ class PDFReport(FPDF):
99
  'Este relatório é uma análise automática e deve ser validado junto à secretaria da escola.',
100
  0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C')
101
 
102
- # Função de extração de tabelas do PDF
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  def extrair_tabelas_pdf(pdf_path: str) -> pd.DataFrame:
 
104
  try:
 
105
  tables_header = camelot.read_pdf(
106
  pdf_path,
107
  pages='1',
@@ -110,6 +155,8 @@ def extrair_tabelas_pdf(pdf_path: str) -> pd.DataFrame:
110
  )
111
 
112
  info_aluno = {}
 
 
113
  for table in tables_header:
114
  df = table.df
115
  for i in range(len(df)):
@@ -127,12 +174,14 @@ def extrair_tabelas_pdf(pdf_path: str) -> pd.DataFrame:
127
  except:
128
  continue
129
 
 
130
  tables_notas = camelot.read_pdf(
131
  pdf_path,
132
  pages='all',
133
  flavor='lattice'
134
  )
135
 
 
136
  df_notas = None
137
  max_rows = 0
138
 
@@ -153,6 +202,7 @@ def extrair_tabelas_pdf(pdf_path: str) -> pd.DataFrame:
153
  if df_notas is None:
154
  raise ValueError("Tabela de notas não encontrada")
155
 
 
156
  df_notas.attrs['nome'] = info_aluno.get('nome', 'Nome não encontrado')
157
 
158
  return df_notas
@@ -161,8 +211,14 @@ def extrair_tabelas_pdf(pdf_path: str) -> pd.DataFrame:
161
  logger.error(f"Erro na extração das tabelas: {str(e)}")
162
  raise
163
 
164
- # Função para identificar disciplinas válidas
 
 
 
 
 
165
  def obter_disciplinas_validas(df: pd.DataFrame) -> List[Dict]:
 
166
  colunas_notas = ['Nota B1', 'Nota B2', 'Nota B3', 'Nota B4']
167
  colunas_freq = ['%Freq B1', '%Freq B2', '%Freq B3', '%Freq B4']
168
 
@@ -204,48 +260,47 @@ def obter_disciplinas_validas(df: pd.DataFrame) -> List[Dict]:
204
 
205
  return disciplinas_dados
206
 
207
- # Função de conversão de nota
208
- def converter_nota(valor) -> Optional[float]:
209
- if pd.isna(valor) or valor == '-' or valor == 'N' or valor == '' or valor == 'None':
210
- return None
211
 
212
- if isinstance(valor, str):
213
- valor_limpo = valor.strip().upper()
214
- if valor_limpo in CONCEITOS_VALIDOS:
215
- conceitos_map = {'ET': 10, 'ES': 8, 'EP': 6}
216
- return conceitos_map.get(valor_limpo)
217
-
218
- try:
219
- return float(valor_limpo.replace(',', '.'))
220
- except:
221
- return None
222
 
223
- if isinstance(valor, (int, float)):
224
- return float(valor)
 
 
 
225
 
226
- return None
 
 
 
227
 
228
- # Funções de cálculo de médias e frequências
229
- def calcular_media_bimestres(notas: List[float]) -> float:
230
- notas_validas = [nota for nota in notas if nota is not None]
231
- return sum(notas_validas) / len(notas_validas) if notas_validas else 0
232
-
233
- def calcular_frequencia_media(frequencias: List[str]) -> float:
234
- freq_validas = []
235
- for freq in frequencias:
236
- try:
237
- if isinstance(freq, str):
238
- freq = freq.strip().replace('%', '').replace(',', '.')
239
- if freq and freq != '-':
240
- valor = float(freq)
241
- if valor > 0:
242
- freq_validas.append(valor)
243
- except:
244
- continue
245
 
246
- return sum(freq_validas) / len(freq_validas) if freq_validas else 0
 
 
 
 
 
 
247
 
248
- # Funções de plotagem
249
  def plotar_evolucao_bimestres(disciplinas_dados: List[Dict], temp_dir: str,
250
  titulo: Optional[str] = None,
251
  nome_arquivo: Optional[str] = None) -> str:
 
86
  os.unlink(temp.name)
87
 
88
  class PDFReport(FPDF):
89
+ """Classe personalizada para geração do relatório PDF."""
90
  def __init__(self):
91
  super().__init__()
92
  self.set_auto_page_break(auto=True, margin=15)
93
 
94
  def header_footer(self):
95
+ """Adiciona header e footer padrão nas páginas."""
96
  self.set_y(-30)
97
  self.line(10, self.get_y(), 200, self.get_y())
98
  self.ln(5)
 
101
  'Este relatório é uma análise automática e deve ser validado junto à secretaria da escola.',
102
  0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C')
103
 
104
+ def converter_nota(valor) -> Optional[float]:
105
+ """Converte valor de nota para float, tratando casos especiais e conceitos."""
106
+ if pd.isna(valor) or valor == '-' or valor == 'N' or valor == '' or valor == 'None':
107
+ return None
108
+
109
+ if isinstance(valor, str):
110
+ valor_limpo = valor.strip().upper()
111
+ if valor_limpo in CONCEITOS_VALIDOS:
112
+ conceitos_map = {'ET': 10, 'ES': 8, 'EP': 6}
113
+ return conceitos_map.get(valor_limpo)
114
+
115
+ try:
116
+ return float(valor_limpo.replace(',', '.'))
117
+ except:
118
+ return None
119
+
120
+ if isinstance(valor, (int, float)):
121
+ return float(valor)
122
+
123
+ return None
124
+
125
+ def calcular_media_bimestres(notas: List[float]) -> float:
126
+ """Calcula média considerando apenas bimestres com notas válidas."""
127
+ notas_validas = [nota for nota in notas if nota is not None]
128
+ return sum(notas_validas) / len(notas_validas) if notas_validas else 0
129
+
130
+ def calcular_frequencia_media(frequencias: List[str]) -> float:
131
+ """Calcula média de frequência considerando apenas bimestres cursados."""
132
+ freq_validas = []
133
+ for freq in frequencias:
134
+ try:
135
+ if isinstance(freq, str):
136
+ freq = freq.strip().replace('%', '').replace(',', '.')
137
+ if freq and freq != '-':
138
+ valor = float(freq)
139
+ if valor > 0:
140
+ freq_validas.append(valor)
141
+ except:
142
+ continue
143
+
144
+ return sum(freq_validas) / len(freq_validas) if freq_validas else 0
145
+
146
  def extrair_tabelas_pdf(pdf_path: str) -> pd.DataFrame:
147
+ """Extrai tabelas do PDF usando stream para o nome e lattice para notas."""
148
  try:
149
+ # Extrair nome do aluno usando stream
150
  tables_header = camelot.read_pdf(
151
  pdf_path,
152
  pages='1',
 
155
  )
156
 
157
  info_aluno = {}
158
+
159
+ # Procurar nome do aluno
160
  for table in tables_header:
161
  df = table.df
162
  for i in range(len(df)):
 
174
  except:
175
  continue
176
 
177
+ # Extrair tabela de notas usando lattice
178
  tables_notas = camelot.read_pdf(
179
  pdf_path,
180
  pages='all',
181
  flavor='lattice'
182
  )
183
 
184
+ # Encontrar tabela de notas
185
  df_notas = None
186
  max_rows = 0
187
 
 
202
  if df_notas is None:
203
  raise ValueError("Tabela de notas não encontrada")
204
 
205
+ # Adicionar informações do aluno ao DataFrame
206
  df_notas.attrs['nome'] = info_aluno.get('nome', 'Nome não encontrado')
207
 
208
  return df_notas
 
211
  logger.error(f"Erro na extração das tabelas: {str(e)}")
212
  raise
213
 
214
+ def detectar_nivel_ensino(disciplinas: List[str]) -> str:
215
+ """Detecta se é ensino fundamental ou médio baseado nas disciplinas."""
216
+ disciplinas_set = set(disciplinas)
217
+ disciplinas_exclusivas_medio = {'BIOLOGIA', 'FISICA', 'QUIMICA', 'FILOSOFIA', 'SOCIOLOGIA'}
218
+ return 'medio' if any(d in disciplinas_set for d in disciplinas_exclusivas_medio) else 'fundamental'
219
+
220
  def obter_disciplinas_validas(df: pd.DataFrame) -> List[Dict]:
221
+ """Identifica disciplinas válidas no boletim com seus dados."""
222
  colunas_notas = ['Nota B1', 'Nota B2', 'Nota B3', 'Nota B4']
223
  colunas_freq = ['%Freq B1', '%Freq B2', '%Freq B3', '%Freq B4']
224
 
 
260
 
261
  return disciplinas_dados
262
 
263
+ def separar_disciplinas_por_categoria(disciplinas_dados: List[Dict]) -> Dict:
264
+ """Separa as disciplinas em formação básica e diversificada."""
265
+ disciplinas = [d['disciplina'] for d in disciplinas_dados]
266
+ nivel = detectar_nivel_ensino(disciplinas)
267
 
268
+ formacao_basica = []
269
+ diversificada = []
 
 
 
 
 
 
 
 
270
 
271
+ for disc_data in disciplinas_dados:
272
+ if disc_data['disciplina'] in FORMACAO_BASICA[nivel]:
273
+ formacao_basica.append(disc_data)
274
+ else:
275
+ diversificada.append(disc_data)
276
 
277
+ return {
278
+ 'nivel': nivel,
279
+ 'formacao_basica': formacao_basica,
280
+ 'diversificada': diversificada
281
 
282
+ # Funções de plotagem
283
+ def gerar_paleta_cores(n_cores: int) -> List[str]:
284
+ """Gera uma paleta de cores harmoniosa."""
285
+ cores_formacao_basica = [
286
+ '#2E86C1', # Azul royal
287
+ '#2ECC71', # Verde esmeralda
288
+ '#E74C3C', # Vermelho coral
289
+ '#F1C40F', # Amarelo ouro
290
+ '#8E44AD', # Roxo médio
291
+ '#E67E22', # Laranja escuro
292
+ '#16A085', # Verde-água
293
+ '#D35400' # Laranja queimado
294
+ ]
 
 
 
 
295
 
296
+ if n_cores <= len(cores_formacao_basica):
297
+ return cores_formacao_basica[:n_cores]
298
+
299
+ # Gerar cores adicionais se necessário
300
+ HSV_tuples = [(x/n_cores, 0.8, 0.9) for x in range(n_cores)]
301
+ return ['#%02x%02x%02x' % tuple(int(x*255) for x in colorsys.hsv_to_rgb(*hsv))
302
+ for hsv in HSV_tuples]
303
 
 
304
  def plotar_evolucao_bimestres(disciplinas_dados: List[Dict], temp_dir: str,
305
  titulo: Optional[str] = None,
306
  nome_arquivo: Optional[str] = None) -> str: