| | """Módulo de conexão e operações com o banco de dados PostgreSQL""" |
| |
|
| | import psycopg2 |
| | from psycopg2.extras import RealDictCursor |
| | import uuid |
| | from datetime import datetime |
| | from contextlib import contextmanager |
| | import streamlit as st |
| | import os |
| | import re |
| |
|
| | def extrair_credenciais(): |
| | """Extrai as credenciais do secret db_checklist""" |
| | |
| | db_checklist = os.getenv('db_checklist', '') |
| | |
| | if db_checklist: |
| | |
| | credenciais = {} |
| | |
| | |
| | padroes = { |
| | 'host': r'Db_host:\s*([\d\.]+)', |
| | 'port': r'Porta:\s*(\d+)', |
| | 'user': r'Username:\s*(\w+)', |
| | 'database': r'Database:\s*(\w+)', |
| | 'password': r'Password:\s*(\w+)' |
| | } |
| | |
| | for key, padrao in padroes.items(): |
| | match = re.search(padrao, db_checklist) |
| | if match: |
| | if key == 'port': |
| | credenciais[key] = int(match.group(1)) |
| | else: |
| | credenciais[key] = match.group(1) |
| | |
| | return credenciais |
| | |
| | |
| | credenciais = { |
| | 'host': os.getenv('DB_HOST'), |
| | 'port': int(os.getenv('DB_PORT', '5432')), |
| | 'database': os.getenv('DB_NAME'), |
| | 'user': os.getenv('DB_USER'), |
| | 'password': os.getenv('DB_PASSWORD') |
| | } |
| | |
| | |
| | if not credenciais['host']: |
| | credenciais.update({ |
| | 'host': '77.37.43.160', |
| | 'port': 5432, |
| | 'database': 'checklist', |
| | 'user': 'abimael', |
| | 'password': 'ctweek' |
| | }) |
| | |
| | return credenciais |
| |
|
| | def criar_conexao(): |
| | """Cria uma nova conexão com o banco de dados""" |
| | try: |
| | creds = extrair_credenciais() |
| | |
| | |
| | print(f"Conectando com: host={creds.get('host')}, port={creds.get('port')} (tipo: {type(creds.get('port'))}), database={creds.get('database')}, user={creds.get('user')}") |
| | |
| | |
| | porta = creds.get('port') |
| | if isinstance(porta, str): |
| | porta = int(porta) |
| | |
| | conn = psycopg2.connect( |
| | host=creds.get('host'), |
| | port=porta, |
| | database=creds.get('database'), |
| | user=creds.get('user'), |
| | password=creds.get('password') |
| | ) |
| | print("✅ Conexão estabelecida com sucesso!") |
| | return conn |
| | except Exception as e: |
| | print(f"❌ Erro ao conectar com PostgreSQL: {e}") |
| | print(f"Tipo do erro: {type(e).__name__}") |
| | return None |
| |
|
| | @contextmanager |
| | def get_db_connection(): |
| | """Context manager para conexão com o banco de dados""" |
| | conn = None |
| | try: |
| | conn = criar_conexao() |
| | if not conn: |
| | raise Exception("Não foi possível estabelecer conexão com o banco") |
| | yield conn |
| | except Exception as e: |
| | if conn: |
| | conn.rollback() |
| | raise e |
| | finally: |
| | if conn: |
| | conn.close() |
| |
|
| |
|
| | def create_tables(): |
| | """Cria as tabelas do banco se não existirem""" |
| | |
| | sql_script = """ |
| | -- Criar tabela de checklists |
| | CREATE TABLE IF NOT EXISTS checklists ( |
| | id VARCHAR(36) PRIMARY KEY, |
| | name VARCHAR(255) NOT NULL, |
| | numero_processo VARCHAR(100), |
| | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP |
| | ); |
| | |
| | -- Criar tabela de itens do checklist |
| | CREATE TABLE IF NOT EXISTS checklist_items ( |
| | id VARCHAR(36) PRIMARY KEY, |
| | checklist_id VARCHAR(36) REFERENCES checklists(id) ON DELETE CASCADE, |
| | text TEXT NOT NULL, |
| | position INTEGER NOT NULL |
| | ); |
| | |
| | -- Criar tabela de interações |
| | CREATE TABLE IF NOT EXISTS item_interactions ( |
| | id SERIAL PRIMARY KEY, |
| | item_id VARCHAR(36) REFERENCES checklist_items(id) ON DELETE CASCADE, |
| | checklist_id VARCHAR(36) REFERENCES checklists(id) ON DELETE CASCADE, |
| | action VARCHAR(20) NOT NULL, |
| | timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP |
| | ); |
| | |
| | -- Criar view para estados atuais dos itens |
| | CREATE OR REPLACE VIEW current_item_states AS |
| | WITH latest_interactions AS ( |
| | SELECT |
| | item_id, |
| | action, |
| | timestamp, |
| | ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY timestamp DESC) as rn |
| | FROM item_interactions |
| | ) |
| | SELECT |
| | item_id, |
| | CASE WHEN action = 'checked' THEN true ELSE false END as is_checked |
| | FROM latest_interactions |
| | WHERE rn = 1; |
| | |
| | -- Criar view para análise de tempo por item |
| | CREATE OR REPLACE VIEW item_time_analysis AS |
| | WITH item_sessions AS ( |
| | SELECT |
| | ii.item_id, |
| | ci.text as item_text, |
| | ci.position, |
| | ci.checklist_id, |
| | ii.timestamp, |
| | ii.action, |
| | LAG(ii.timestamp) OVER ( |
| | PARTITION BY ii.item_id |
| | ORDER BY ii.timestamp |
| | ) as prev_timestamp, |
| | LAG(ii.action) OVER ( |
| | PARTITION BY ii.item_id |
| | ORDER BY ii.timestamp |
| | ) as prev_action |
| | FROM item_interactions ii |
| | JOIN checklist_items ci ON ii.item_id = ci.id |
| | ORDER BY ii.item_id, ii.timestamp |
| | ), |
| | work_sessions AS ( |
| | SELECT |
| | item_id, |
| | item_text, |
| | position, |
| | checklist_id, |
| | CASE |
| | WHEN prev_action = 'unchecked' AND action = 'checked' AND prev_timestamp IS NOT NULL |
| | THEN EXTRACT(EPOCH FROM (timestamp - prev_timestamp)) |
| | ELSE 0 |
| | END as session_seconds |
| | FROM item_sessions |
| | WHERE prev_timestamp IS NOT NULL |
| | ) |
| | SELECT |
| | item_id, |
| | item_text, |
| | position, |
| | checklist_id, |
| | COUNT(CASE WHEN session_seconds > 0 THEN 1 END) as times_worked, |
| | COALESCE(SUM(session_seconds), 0) as total_seconds_spent, |
| | CASE |
| | WHEN COUNT(CASE WHEN session_seconds > 0 THEN 1 END) > 0 |
| | THEN COALESCE(SUM(session_seconds), 0) / COUNT(CASE WHEN session_seconds > 0 THEN 1 END) |
| | ELSE 0 |
| | END as avg_seconds_per_completion |
| | FROM work_sessions |
| | GROUP BY item_id, item_text, position, checklist_id; |
| | """ |
| | |
| | with get_db_connection() as conn: |
| | with conn.cursor() as cur: |
| | cur.execute(sql_script) |
| | conn.commit() |
| | |
| |
|
| | def save_checklist(name, items, numero_processo=None): |
| | """Salva um novo checklist no banco de dados""" |
| | checklist_id = str(uuid.uuid4()) |
| | |
| | with get_db_connection() as conn: |
| | with conn.cursor() as cur: |
| | |
| | cur.execute(""" |
| | INSERT INTO checklists (id, name, numero_processo) |
| | VALUES (%s, %s, %s) |
| | """, (checklist_id, name, numero_processo)) |
| | |
| | |
| | for position, item_text in enumerate(items): |
| | if item_text.strip(): |
| | item_id = str(uuid.uuid4()) |
| | cur.execute(""" |
| | INSERT INTO checklist_items (id, checklist_id, text, position) |
| | VALUES (%s, %s, %s, %s) |
| | """, (item_id, checklist_id, item_text.strip(), position)) |
| | |
| | conn.commit() |
| | |
| | return checklist_id |
| |
|
| | def get_all_checklists(): |
| | """Retorna todos os checklists do banco""" |
| | with get_db_connection() as conn: |
| | with conn.cursor(cursor_factory=RealDictCursor) as cur: |
| | cur.execute(""" |
| | SELECT id, name, numero_processo, created_at |
| | FROM checklists |
| | ORDER BY created_at DESC |
| | """) |
| | return cur.fetchall() |
| |
|
| | def get_checklist_with_items(checklist_id): |
| | """Retorna um checklist com seus itens e estados atuais""" |
| | with get_db_connection() as conn: |
| | with conn.cursor(cursor_factory=RealDictCursor) as cur: |
| | |
| | cur.execute(""" |
| | SELECT id, name, numero_processo, created_at |
| | FROM checklists |
| | WHERE id = %s |
| | """, (checklist_id,)) |
| | checklist = cur.fetchone() |
| | |
| | if not checklist: |
| | return None |
| | |
| | |
| | cur.execute(""" |
| | SELECT |
| | ci.id, |
| | ci.text, |
| | ci.position, |
| | COALESCE(cis.is_checked, false) as is_checked |
| | FROM checklist_items ci |
| | LEFT JOIN current_item_states cis ON ci.id = cis.item_id |
| | WHERE ci.checklist_id = %s |
| | ORDER BY ci.position |
| | """, (checklist_id,)) |
| | |
| | checklist['items'] = cur.fetchall() |
| | |
| | return checklist |
| |
|
| | def toggle_item(item_id, checklist_id, new_state): |
| | """Registra a mudança de estado de um item""" |
| | action = 'checked' if new_state else 'unchecked' |
| | |
| | with get_db_connection() as conn: |
| | with conn.cursor() as cur: |
| | cur.execute(""" |
| | INSERT INTO item_interactions (item_id, checklist_id, action) |
| | VALUES (%s, %s, %s) |
| | """, (item_id, checklist_id, action)) |
| | conn.commit() |
| |
|
| | def get_checklist_analytics(checklist_id): |
| | """Retorna análises de tempo do checklist""" |
| | with get_db_connection() as conn: |
| | with conn.cursor(cursor_factory=RealDictCursor) as cur: |
| | |
| | cur.execute(""" |
| | SELECT |
| | COUNT(DISTINCT ci.id) as total_items, |
| | COUNT(DISTINCT CASE WHEN cis.is_checked THEN ci.id END) as completed_items, |
| | MIN(ii.timestamp) as first_interaction, |
| | MAX(ii.timestamp) as last_interaction |
| | FROM checklist_items ci |
| | LEFT JOIN current_item_states cis ON ci.id = cis.item_id |
| | LEFT JOIN item_interactions ii ON ci.id = ii.item_id |
| | WHERE ci.checklist_id = %s |
| | """, (checklist_id,)) |
| | |
| | stats = cur.fetchone() |
| | |
| | |
| | cur.execute(""" |
| | SELECT |
| | item_text, |
| | position, |
| | times_worked, |
| | total_seconds_spent, |
| | avg_seconds_per_completion |
| | FROM item_time_analysis |
| | WHERE checklist_id = %s |
| | ORDER BY position |
| | """, (checklist_id,)) |
| | |
| | time_analysis = cur.fetchall() |
| | |
| | return { |
| | 'stats': stats, |
| | 'time_analysis': time_analysis |
| | } |
| |
|
| | def delete_checklist(checklist_id): |
| | """Deleta um checklist e todos os seus dados relacionados""" |
| | with get_db_connection() as conn: |
| | with conn.cursor() as cur: |
| | cur.execute("DELETE FROM checklists WHERE id = %s", (checklist_id,)) |
| | conn.commit() |
| |
|
| | def get_all_checklists_with_stats(): |
| | """Retorna todos os checklists com estatísticas de progresso""" |
| | with get_db_connection() as conn: |
| | with conn.cursor(cursor_factory=RealDictCursor) as cur: |
| | cur.execute(""" |
| | SELECT |
| | c.id, |
| | c.name, |
| | c.numero_processo, |
| | c.created_at, |
| | COUNT(ci.id) as total_items, |
| | COUNT(CASE WHEN cis.is_checked THEN 1 END) as completed_items, |
| | CASE |
| | WHEN COUNT(ci.id) > 0 THEN |
| | ROUND((COUNT(CASE WHEN cis.is_checked THEN 1 END)::decimal / COUNT(ci.id)) * 100, 1) |
| | ELSE 0 |
| | END as progress_percentage |
| | FROM checklists c |
| | LEFT JOIN checklist_items ci ON c.id = ci.checklist_id |
| | LEFT JOIN current_item_states cis ON ci.id = cis.item_id |
| | GROUP BY c.id, c.name, c.numero_processo, c.created_at |
| | ORDER BY c.created_at DESC |
| | """) |
| | return cur.fetchall() |
| |
|
| | def get_general_stats(): |
| | """Retorna estatísticas gerais do sistema""" |
| | with get_db_connection() as conn: |
| | with conn.cursor(cursor_factory=RealDictCursor) as cur: |
| | cur.execute(""" |
| | WITH checklist_progress AS ( |
| | SELECT |
| | c.id, |
| | c.numero_processo, |
| | c.created_at, |
| | COUNT(ci.id) as total_items, |
| | COUNT(CASE WHEN cis.is_checked THEN 1 END) as completed_items, |
| | CASE |
| | WHEN COUNT(ci.id) > 0 THEN |
| | ROUND((COUNT(CASE WHEN cis.is_checked THEN 1 END)::decimal / COUNT(ci.id)) * 100, 1) |
| | ELSE 0 |
| | END as progress_percentage |
| | FROM checklists c |
| | LEFT JOIN checklist_items ci ON c.id = ci.checklist_id |
| | LEFT JOIN current_item_states cis ON ci.id = cis.item_id |
| | GROUP BY c.id, c.numero_processo, c.created_at |
| | ) |
| | SELECT |
| | COUNT(*) as total_checklists, |
| | COUNT(DISTINCT numero_processo) as total_processos, |
| | SUM(total_items) as total_items, |
| | SUM(completed_items) as completed_items, |
| | COUNT(CASE WHEN progress_percentage = 100 THEN 1 END) as completed_checklists, |
| | MIN(created_at) as first_checklist_date, |
| | MAX(created_at) as last_checklist_date, |
| | ROUND(AVG(progress_percentage), 1) as avg_progress |
| | FROM checklist_progress |
| | """) |
| | return cur.fetchone() |
| |
|
| | def get_checklist_interactions_summary(): |
| | """Retorna resumo de interações por checklist""" |
| | with get_db_connection() as conn: |
| | with conn.cursor(cursor_factory=RealDictCursor) as cur: |
| | cur.execute(""" |
| | SELECT |
| | c.id, |
| | c.name, |
| | c.numero_processo, |
| | c.created_at, |
| | COUNT(ii.id) as total_interactions, |
| | COUNT(DISTINCT ii.item_id) as items_with_interactions, |
| | MIN(ii.timestamp) as first_interaction, |
| | MAX(ii.timestamp) as last_interaction |
| | FROM checklists c |
| | LEFT JOIN item_interactions ii ON c.id = ii.checklist_id |
| | GROUP BY c.id, c.name, c.numero_processo, c.created_at |
| | HAVING COUNT(ii.id) > 0 |
| | ORDER BY c.created_at DESC |
| | """) |
| | return cur.fetchall() |
| |
|
| | def get_process_summary(): |
| | """Retorna resumo por número de processo""" |
| | with get_db_connection() as conn: |
| | with conn.cursor(cursor_factory=RealDictCursor) as cur: |
| | cur.execute(""" |
| | WITH checklist_progress AS ( |
| | SELECT |
| | c.id, |
| | c.numero_processo, |
| | COUNT(ci.id) as total_items, |
| | COUNT(CASE WHEN cis.is_checked THEN 1 END) as completed_items, |
| | CASE |
| | WHEN COUNT(ci.id) > 0 THEN |
| | ROUND((COUNT(CASE WHEN cis.is_checked THEN 1 END)::decimal / COUNT(ci.id)) * 100, 1) |
| | ELSE 0 |
| | END as progress_percentage |
| | FROM checklists c |
| | LEFT JOIN checklist_items ci ON c.id = ci.checklist_id |
| | LEFT JOIN current_item_states cis ON ci.id = cis.item_id |
| | WHERE c.numero_processo IS NOT NULL |
| | GROUP BY c.id, c.numero_processo |
| | ) |
| | SELECT |
| | numero_processo, |
| | COUNT(*) as total_checklists, |
| | SUM(total_items) as total_items, |
| | SUM(completed_items) as completed_items, |
| | ROUND(AVG(progress_percentage), 1) as avg_progress |
| | FROM checklist_progress |
| | GROUP BY numero_processo |
| | ORDER BY avg_progress DESC |
| | """) |
| | return cur.fetchall() |
| |
|
| | def get_fastest_processes(): |
| | """Retorna os processos mais rápidos baseado no tempo médio de conclusão""" |
| | with get_db_connection() as conn: |
| | with conn.cursor(cursor_factory=RealDictCursor) as cur: |
| | cur.execute(""" |
| | WITH checklist_completion_times AS ( |
| | SELECT |
| | c.id, |
| | c.numero_processo, |
| | c.name, |
| | c.created_at, |
| | MIN(ii.timestamp) as first_interaction, |
| | MAX(ii.timestamp) as last_interaction, |
| | COUNT(DISTINCT ci.id) as total_items, |
| | COUNT(DISTINCT CASE WHEN cis.is_checked THEN ci.id END) as completed_items, |
| | CASE |
| | WHEN COUNT(ci.id) > 0 THEN |
| | ROUND((COUNT(CASE WHEN cis.is_checked THEN 1 END)::decimal / COUNT(ci.id)) * 100, 1) |
| | ELSE 0 |
| | END as progress_percentage |
| | FROM checklists c |
| | LEFT JOIN checklist_items ci ON c.id = ci.checklist_id |
| | LEFT JOIN current_item_states cis ON ci.id = cis.item_id |
| | LEFT JOIN item_interactions ii ON c.id = ii.checklist_id |
| | WHERE c.numero_processo IS NOT NULL |
| | GROUP BY c.id, c.numero_processo, c.name, c.created_at |
| | ), |
| | process_times AS ( |
| | SELECT |
| | numero_processo, |
| | COUNT(*) as total_checklists, |
| | COUNT(CASE WHEN progress_percentage = 100 THEN 1 END) as completed_checklists, |
| | AVG(progress_percentage) as avg_progress, |
| | AVG( |
| | CASE |
| | WHEN first_interaction IS NOT NULL AND last_interaction IS NOT NULL |
| | THEN EXTRACT(EPOCH FROM (last_interaction - first_interaction)) / 3600.0 -- horas |
| | ELSE NULL |
| | END |
| | ) as avg_completion_hours, |
| | AVG( |
| | CASE |
| | WHEN first_interaction IS NOT NULL |
| | THEN EXTRACT(EPOCH FROM (first_interaction - created_at)) / 3600.0 -- horas até primeira interação |
| | ELSE NULL |
| | END |
| | ) as avg_start_hours |
| | FROM checklist_completion_times |
| | GROUP BY numero_processo |
| | ) |
| | SELECT |
| | numero_processo, |
| | total_checklists, |
| | completed_checklists, |
| | ROUND(avg_progress::numeric, 1) as avg_progress, |
| | ROUND(avg_completion_hours::numeric, 2) as avg_completion_hours, |
| | ROUND(avg_start_hours::numeric, 2) as avg_start_hours, |
| | ROUND((avg_completion_hours + avg_start_hours)::numeric, 2) as total_avg_time_hours |
| | FROM process_times |
| | WHERE avg_completion_hours IS NOT NULL |
| | OR avg_start_hours IS NOT NULL |
| | ORDER BY total_avg_time_hours ASC NULLS LAST |
| | LIMIT 10 |
| | """) |
| | return cur.fetchall() |
| |
|
| | def get_process_deadline_analysis(): |
| | """Analisa prazos dos processos com deadline de 6 meses""" |
| | with get_db_connection() as conn: |
| | with conn.cursor(cursor_factory=RealDictCursor) as cur: |
| | cur.execute(""" |
| | WITH process_progress AS ( |
| | SELECT |
| | c.numero_processo, |
| | c.created_at, |
| | COUNT(ci.id) as total_items, |
| | COUNT(CASE WHEN cis.is_checked THEN 1 END) as completed_items, |
| | CASE |
| | WHEN COUNT(ci.id) > 0 THEN |
| | ROUND((COUNT(CASE WHEN cis.is_checked THEN 1 END)::decimal / COUNT(ci.id)) * 100, 1) |
| | ELSE 0 |
| | END as progress_percentage, |
| | MIN(ii.timestamp) as first_interaction, |
| | MAX(ii.timestamp) as last_interaction, |
| | COUNT(DISTINCT c.id) as total_checklists |
| | FROM checklists c |
| | LEFT JOIN checklist_items ci ON c.id = ci.checklist_id |
| | LEFT JOIN current_item_states cis ON ci.id = cis.item_id |
| | LEFT JOIN item_interactions ii ON c.id = ii.checklist_id |
| | WHERE c.numero_processo IS NOT NULL |
| | GROUP BY c.numero_processo, c.created_at |
| | ), |
| | process_summary AS ( |
| | SELECT |
| | numero_processo, |
| | MIN(created_at) as process_start_date, |
| | MAX(created_at) as latest_checklist_date, |
| | SUM(total_items) as total_items, |
| | SUM(completed_items) as completed_items, |
| | ROUND(AVG(progress_percentage), 1) as avg_progress, |
| | MIN(first_interaction) as first_interaction, |
| | MAX(last_interaction) as last_interaction, |
| | SUM(total_checklists) as total_checklists |
| | FROM process_progress |
| | GROUP BY numero_processo |
| | ) |
| | SELECT |
| | numero_processo, |
| | process_start_date, |
| | latest_checklist_date, |
| | total_items, |
| | completed_items, |
| | avg_progress, |
| | first_interaction, |
| | last_interaction, |
| | total_checklists, |
| | -- Cálculos de prazo (6 meses = 180 dias) |
| | (process_start_date + INTERVAL '180 days')::date as deadline_date, |
| | CURRENT_DATE - process_start_date::date as days_elapsed, |
| | (process_start_date + INTERVAL '180 days')::date - CURRENT_DATE as days_remaining, |
| | -- Velocidade e projeção |
| | CASE |
| | WHEN (CURRENT_DATE - process_start_date::date) > 0 AND avg_progress > 0 THEN |
| | ROUND((avg_progress / (CURRENT_DATE - process_start_date::date)) * |
| | (100 - avg_progress), 0) |
| | ELSE NULL |
| | END as projected_days_to_complete, |
| | -- Status do prazo |
| | CASE |
| | WHEN avg_progress = 100 THEN 'CONCLUIDO' |
| | WHEN (CURRENT_DATE - process_start_date::date) > 180 THEN 'ATRASADO' |
| | WHEN (CURRENT_DATE - process_start_date::date) > 0 AND avg_progress > 0 THEN |
| | CASE |
| | WHEN ((avg_progress / (CURRENT_DATE - process_start_date::date)) * |
| | (100 - avg_progress) + (CURRENT_DATE - process_start_date::date)) > 180 |
| | THEN 'RISCO_ATRASO' |
| | WHEN ((avg_progress / (CURRENT_DATE - process_start_date::date)) * |
| | (100 - avg_progress) + (CURRENT_DATE - process_start_date::date)) > 150 |
| | THEN 'EM_RISCO' |
| | ELSE 'NO_PRAZO' |
| | END |
| | ELSE 'SEM_DADOS' |
| | END as status_prazo |
| | FROM process_summary |
| | ORDER BY days_remaining ASC NULLS LAST |
| | """) |
| | return cur.fetchall() |
| |
|
| | def get_comprehensive_analysis_data(): |
| | """Retorna dados completos para análise com IA""" |
| | with get_db_connection() as conn: |
| | with conn.cursor(cursor_factory=RealDictCursor) as cur: |
| | |
| | cur.execute(""" |
| | SELECT |
| | c.numero_processo, |
| | COUNT(DISTINCT c.id) as total_checklists, |
| | MIN(c.created_at) as process_start_date, |
| | MAX(c.created_at) as latest_checklist_date, |
| | COUNT(ci.id) as total_items, |
| | COUNT(CASE WHEN cis.is_checked THEN 1 END) as completed_items, |
| | CASE |
| | WHEN COUNT(ci.id) > 0 THEN |
| | ROUND((COUNT(CASE WHEN cis.is_checked THEN 1 END)::decimal / COUNT(ci.id)) * 100, 1) |
| | ELSE 0 |
| | END as avg_progress_percentage, |
| | CURRENT_DATE - MIN(c.created_at)::date as days_elapsed, |
| | (MIN(c.created_at) + INTERVAL '180 days')::date - CURRENT_DATE as days_remaining_to_deadline |
| | FROM checklists c |
| | LEFT JOIN checklist_items ci ON c.id = ci.checklist_id |
| | LEFT JOIN current_item_states cis ON ci.id = cis.item_id |
| | WHERE c.numero_processo IS NOT NULL |
| | GROUP BY c.numero_processo |
| | ORDER BY days_remaining_to_deadline ASC |
| | """) |
| | |
| | basic_process_data = cur.fetchall() |
| | |
| | |
| | process_data = [] |
| | for process in basic_process_data: |
| | cur.execute(""" |
| | SELECT |
| | COUNT(ii.id) as total_interactions, |
| | MIN(ii.timestamp) as first_interaction, |
| | MAX(ii.timestamp) as last_interaction |
| | FROM checklists c |
| | LEFT JOIN item_interactions ii ON c.id = ii.checklist_id |
| | WHERE c.numero_processo = %s |
| | """, (process['numero_processo'],)) |
| | |
| | interaction_data = cur.fetchone() |
| | |
| | |
| | combined_process = dict(process) |
| | combined_process.update(interaction_data) |
| | |
| | |
| | combined_process['status_prazo'] = ( |
| | 'CONCLUIDO' if combined_process['avg_progress_percentage'] == 100 |
| | else 'ATRASADO' if combined_process['days_remaining_to_deadline'] < 0 |
| | else 'EM_RISCO' if combined_process['days_remaining_to_deadline'] < 30 |
| | else 'NO_PRAZO' |
| | ) |
| | |
| | if combined_process['days_elapsed'] > 0 and combined_process['avg_progress_percentage'] > 0: |
| | combined_process['projected_days_to_complete'] = round( |
| | (combined_process['avg_progress_percentage'] / combined_process['days_elapsed']) * |
| | (100 - combined_process['avg_progress_percentage']) |
| | ) |
| | else: |
| | combined_process['projected_days_to_complete'] = None |
| | |
| | process_data.append(combined_process) |
| | |
| | |
| | cur.execute(""" |
| | SELECT |
| | COUNT(DISTINCT c.id) as total_checklists_global, |
| | COUNT(DISTINCT c.numero_processo) as total_processes, |
| | COUNT(ci.id) as total_items_global, |
| | COUNT(CASE WHEN cis.is_checked THEN 1 END) as completed_items_global, |
| | COUNT(ii.id) as total_interactions_global, |
| | MIN(c.created_at) as earliest_process, |
| | MAX(c.created_at) as latest_process |
| | FROM checklists c |
| | LEFT JOIN checklist_items ci ON c.id = ci.checklist_id |
| | LEFT JOIN current_item_states cis ON ci.id = cis.item_id |
| | LEFT JOIN item_interactions ii ON c.id = ii.checklist_id |
| | """) |
| | |
| | global_stats = cur.fetchone() |
| | |
| | return { |
| | 'process_data': process_data, |
| | 'global_stats': global_stats |
| | } |
| |
|
| | |
| | def test_connection(): |
| | """Testa a conexão com o banco de dados""" |
| | try: |
| | conn = criar_conexao() |
| | if conn: |
| | conn.close() |
| | return True |
| | return False |
| | except Exception as e: |
| | print(f"❌ Erro no teste de conexão: {e}") |
| | return False |