From 91b9e08e9d097c9ccd0888f10890310d8af30fe4 Mon Sep 17 00:00:00 2001 From: Marius Date: Sat, 20 Dec 2025 22:23:11 +0200 Subject: [PATCH] adunare generala --- .../README.md | 0 .../claude-mcp-toggle | 0 scripts/generare_proces_verbal.py | 373 ++++++++++++++++++ scripts/verificare_voturi_AG.py | 324 +++++++++++++++ 4 files changed, 697 insertions(+) rename {utils/claude-mcp-manager => claude-mcp-manager}/README.md (100%) rename {utils/claude-mcp-manager => claude-mcp-manager}/claude-mcp-toggle (100%) create mode 100644 scripts/generare_proces_verbal.py create mode 100644 scripts/verificare_voturi_AG.py diff --git a/utils/claude-mcp-manager/README.md b/claude-mcp-manager/README.md similarity index 100% rename from utils/claude-mcp-manager/README.md rename to claude-mcp-manager/README.md diff --git a/utils/claude-mcp-manager/claude-mcp-toggle b/claude-mcp-manager/claude-mcp-toggle similarity index 100% rename from utils/claude-mcp-manager/claude-mcp-toggle rename to claude-mcp-manager/claude-mcp-toggle diff --git a/scripts/generare_proces_verbal.py b/scripts/generare_proces_verbal.py new file mode 100644 index 0000000..0ebf313 --- /dev/null +++ b/scripts/generare_proces_verbal.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python3 +""" +Script pentru generare Proces Verbal AG bleuMarin Constanța 25.11.2025 +Format DOCX similar cu modelul PDF +""" + +from docx import Document +from docx.shared import Inches, Pt, Cm +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.enum.table import WD_TABLE_ALIGNMENT +from docx.oxml.ns import qn +from docx.oxml import OxmlElement +import pandas as pd +from pathlib import Path + +# Configurare căi +INPUT_DIR = Path(__file__).parent.parent / "input" +OUTPUT_DIR = Path(__file__).parent.parent / "output" + +# Fișiere de intrare +VOTURI_PRINCIPAL_FILE = INPUT_DIR / "AG bleuMarin Constanța 25.11.2025 (Responses) - Form Responses 1.csv" +VOTURI_SUPLIMENTAR_FILE = INPUT_DIR / "AG bleuMarin Constanța 25.11.2025 Suplimentar (Responses) - Form Responses 1.csv" + + +def set_cell_shading(cell, color): + """Setează culoarea de fundal pentru o celulă""" + shading = OxmlElement('w:shd') + shading.set(qn('w:fill'), color) + cell._tc.get_or_add_tcPr().append(shading) + + +def add_heading_style(paragraph, text, size=14, bold=True, center=True): + """Adaugă un heading formatat""" + run = paragraph.add_run(text) + run.bold = bold + run.font.size = Pt(size) + run.font.name = 'Times New Roman' + if center: + paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER + + +def create_proces_verbal(): + """Creează documentul proces verbal""" + + # Încarcă datele pentru lista participanți + voturi_principal_df = pd.read_csv(VOTURI_PRINCIPAL_FILE) + voturi_suplimentar_df = pd.read_csv(VOTURI_SUPLIMENTAR_FILE) + + # Elimină duplicate pe email, păstrează primul vot + voturi_principal_df = voturi_principal_df.sort_values('Timestamp').drop_duplicates(subset=['Email Address'], keep='first') + voturi_suplimentar_df = voturi_suplimentar_df.sort_values('Timestamp').drop_duplicates(subset=['Email Address'], keep='first') + + # Lista participanți (unici din ambele formulare) + participanti_principal = set(voturi_principal_df['Nume și prenume'].tolist()) + participanti_suplimentar = set(voturi_suplimentar_df['Nume și prenume'].tolist()) + toti_participantii = sorted(participanti_principal.union(participanti_suplimentar)) + + # Crează documentul + doc = Document() + + # Setări pagină + section = doc.sections[0] + section.page_width = Cm(21) + section.page_height = Cm(29.7) + section.left_margin = Cm(2.5) + section.right_margin = Cm(2.5) + section.top_margin = Cm(2) + section.bottom_margin = Cm(2) + + # ==================== TITLU ==================== + p = doc.add_paragraph() + add_heading_style(p, "PROCES VERBAL", size=14, bold=True) + + p = doc.add_paragraph() + add_heading_style(p, "Al ADUNĂRII GENERALE", size=12, bold=True) + + p = doc.add_paragraph() + add_heading_style(p, "a Organizației Naționale Cercetașii României - Filiala bleuMarin Constanța", size=12, bold=True) + + p = doc.add_paragraph() + add_heading_style(p, "Încheiat azi, 25.11.2025", size=12, bold=True) + + doc.add_paragraph() # Spațiu + + # ==================== I. ASPECTE INTRODUCTIVE ==================== + p = doc.add_paragraph() + run = p.add_run("I.\tAspecte Introductive") + run.bold = True + run.font.size = Pt(12) + + doc.add_paragraph() + + p = doc.add_paragraph() + p.add_run("Potrivit organizării agreate, Secretariatul se constituie în sistem online, membri cu drept de vot fiind convocați via rețele de socializare utilizate de membri (grupuri WhatsApp). Data, modul de desfășurare și ordinea de zi a Adunării Generale a fost comunicată către membri prin grupurile de WhatsApp utilizate de membri.") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + p = doc.add_paragraph() + p.add_run("Adunarea Generală s-a organizat ") + run = p.add_run("online, prin formulare Google") + run.bold = True + p.add_run(", votarea fiind deschisă în perioada 25-26 noiembrie 2025.") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + p = doc.add_paragraph() + p.add_run("În condițiile în care Adunarea Generală este organul deliberativ al filialei, întrunirea cvorumului – peste 2/3 din membrii cu drept de vot – asigură legalitatea ședinței și a întrunirii.") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + p = doc.add_paragraph() + p.add_run("Secretariatul ședinței este asigurat de ") + run = p.add_run("Mutu Marius") + run.bold = True + p.add_run(". Președintele ședinței este ") + run = p.add_run("Mutu Marius") + run.bold = True + p.add_run(". Votarea electronică prin formulare Google elimină necesitatea unei comisii de numărare a voturilor.") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + doc.add_paragraph() + + # ==================== II. ORDINEA DE ZI ==================== + p = doc.add_paragraph() + run = p.add_run("II.\tOrdinea de zi") + run.bold = True + run.font.size = Pt(12) + + doc.add_paragraph() + + # Ordinea de zi conform documentului oficial AG Ordine de Zi 25.11.2025.pdf + + # Punct 1 cu subpuncte + p = doc.add_paragraph() + run = p.add_run("1. Votare puncte de pe OZ AG Național 19 decembrie") + run.bold = True + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + p = doc.add_paragraph(" 1.1 Bugetul ONCR pe anul 2026") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + p = doc.add_paragraph(" 1.2 Acordarea dreptului de obținere a Personalității Juridice pentru toate structurile locale") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + p = doc.add_paragraph(" 1.3 Decizie pentru reglementarea transferurilor între Centre Locale") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + # Punct suplimentar ONCR (din formularul suplimentar) + p = doc.add_paragraph(" 1.4 Înființare prin desprindere din alt Centru Local cu Personalitate Juridică: Sfântul Voievod Ștefan cel Mare, București cu desprindere din Orizont, Brașov") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + ordine_zi_rest = [ + "2. Reprezentant bleuMarin la AG Național: Cora - Maria Costache", + "3. Cotizația: 250 lei / 175 frați (70%) / 150 lei adulți / 150 lei copii lideri/lideri asistenți/rol administrativ, din care 150 lei către organizația națională.", + "4. Woodbadge lideri: 20% achitat de participant, 80% achitat de centrul local (hotărâre CCL 21.08.2024)", + "5. Activități de fund-raising efectuate de patrule - 20% din donații vor rămâne la centrul local pentru acțiuni sociale (implicare în comunitate), 80% către patrulă pentru susținerea activităților cercetășești.", + "6. Activități de fund-raising efectuat de un membru pentru participarea la evenimente cercetășești - 20% din donații vor rămâne la centrul local pentru acțiuni sociale (implicare în comunitate), 80% către membru pentru participarea la evenimente cercetășești.", + "7. Sponsorizări atrase de un membru individual - până la 20% din sponsorizare poate fi folosită de membru pentru participarea la evenimente cercetășești și decontată de către centrul local. Se vor avea în vedere limite de sume aprobate de CCL, pentru sponsorizări mai mari de 10.000 lei.", + "8. Telefoanele și alte dispozitive electronice nu vor fi folosite la activități, decât dacă este nevoie în scopul activităților, la indicația liderilor. Se respectă timpul și munca liderilor, a altor participanți și a colegilor de patrulă, cercetașii sunt implicați în activități, nu pe telefoane.", + "9. Întrucât activitățile de fund-raising și caritate/activități sociale implică imaginea și reprezentarea externă a cercetașilor este nevoie ca aceste acțiuni ale unităților/patrulelor/individuale să fie anunțate pe grupul bleuMarin și aprobate de CCL.", + "10. Badge centru local", + "11. Alte subiecte propuse de membri" + ] + + for item in ordine_zi_rest: + p = doc.add_paragraph(item) + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + doc.add_paragraph() + + # ==================== III. ASPECTE PRELIMINARE ==================== + p = doc.add_paragraph() + run = p.add_run("III.\tAspecte preliminare") + run.bold = True + run.font.size = Pt(12) + + doc.add_paragraph() + + p = doc.add_paragraph() + run = p.add_run("Se face prezența. ") + run.bold = True + p.add_run("Se constată un număr de ") + run = p.add_run("53 persoane") + run.bold = True + p.add_run(" care au votat dintr-un număr total de ") + run = p.add_run("75 de membri") + run.bold = True + p.add_run(" cu drept de vot la data Adunării Generale, cvorumul de ") + run = p.add_run("50 persoane (2/3 din 75)") + run.bold = True + p.add_run(" din membrii cu drept de vot fiind îndeplinit.") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + doc.add_paragraph() + + # ==================== VOTAREA PUNCTELOR ==================== + p = doc.add_paragraph() + run = p.add_run("Se votează punctele de pe Ordinea de zi (OZ)") + run.bold = True + run.font.size = Pt(12) + + doc.add_paragraph() + + # Rezultatele voturilor - conform ordinii de zi + + # Punctul 1 - Votare puncte OZ AG Național + p = doc.add_paragraph() + run = p.add_run("1. Votare puncte de pe OZ AG Național 19 decembrie") + run.bold = True + + rezultate_oncr = [ + ("1.1 Bugetul ONCR pe anul 2026", 52, 1, 0), + ("1.2 Acordarea dreptului de obținere a Personalității Juridice pentru toate structurile locale", 52, 1, 0), + ("1.3 Decizie pentru reglementarea transferurilor între Centre Locale", 51, 2, 0), + ("1.4 Înființare prin desprindere CL Sf. Voievod Ștefan cel Mare, București (din Orizont, Brașov)", 15, 2, 10), + ] + + for propunere, da, nu, abtinere in rezultate_oncr: + p = doc.add_paragraph() + p.add_run(f" {propunere}: ") + run = p.add_run("APROBAT") + run.bold = True + p.add_run(" (") + run = p.add_run(f"{da} DA") + run.bold = True + p.add_run(", ") + run = p.add_run(f"{nu} NU") + run.bold = True + p.add_run(", ") + run = p.add_run(f"{abtinere} Abțineri") + run.bold = True + p.add_run(")") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + # Restul punctelor + rezultate_locale = [ + ("2. Reprezentant bleuMarin la AG Național: Cora - Maria Costache", 47, 6, 0), + ("3. Cotizația: 250 lei / 175 frați / 150 lei adulți / 150 lei copii lideri", 52, 1, 0), + ("4. Woodbadge lideri: 20% participant, 80% centru local", 53, 0, 0), + ("5. Fund-raising patrule: 20% acțiuni sociale, 80% patrulă", 52, 1, 0), + ("6. Fund-raising membru: 20% acțiuni sociale, 80% membru", 49, 4, 0), + ("7. Sponsorizări membru individual: până la 20% pentru evenimente", 52, 1, 0), + ("8. Telefoane la activități - restricții de utilizare", 45, 8, 0), + ("9. Fund-raising/caritate - anunțare și aprobare CCL", 53, 0, 0), + ] + + for propunere, da, nu, abtinere in rezultate_locale: + p = doc.add_paragraph() + run = p.add_run("Se votează APROBAT ") + run.bold = True + p.add_run(f"pentru {propunere} (") + run = p.add_run(f"{da} DA") + run.bold = True + p.add_run(", ") + run = p.add_run(f"{nu} NU") + run.bold = True + p.add_run(", ") + run = p.add_run(f"{abtinere} Abțineri") + run.bold = True + p.add_run(")") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + # Punctele 10 și 11 - fără vot explicit în formular + p = doc.add_paragraph() + run = p.add_run("10. Badge centru local") + run.bold = True + p.add_run(" - s-a prezentat propunerea de badge pentru centrul local.") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + p = doc.add_paragraph() + run = p.add_run("11. Alte subiecte propuse de membri") + run.bold = True + p.add_run(" - nu au fost propuse alte subiecte.") + p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + doc.add_paragraph() + doc.add_paragraph() + + # ==================== SEMNĂTURI ==================== + # Crează un tabel pentru semnături + table = doc.add_table(rows=2, cols=2) + table.alignment = WD_TABLE_ALIGNMENT.CENTER + + # Prima linie - titluri + table.cell(0, 0).text = "Secretar" + table.cell(0, 1).text = "Președinte" + + # A doua linie - nume + table.cell(1, 0).text = "Mutu Marius" + table.cell(1, 1).text = "Mutu Marius" + + # Formatare celule + for row in table.rows: + for cell in row.cells: + cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER + for run in cell.paragraphs[0].runs: + run.font.size = Pt(11) + + # Adăugare pagină nouă pentru lista participanți + doc.add_page_break() + + # ==================== LISTA PARTICIPANȚI ==================== + p = doc.add_paragraph() + add_heading_style(p, "ADUNARE GENERALĂ", size=14, bold=True) + + p = doc.add_paragraph() + add_heading_style(p, "ONCR - FILIALA BLEUMARIN CONSTANȚA", size=12, bold=True) + + p = doc.add_paragraph() + add_heading_style(p, "25.11.2025", size=12, bold=True) + + doc.add_paragraph() + + p = doc.add_paragraph() + add_heading_style(p, "LISTA PARTICIPANȚI (VOT ONLINE)", size=12, bold=True) + + doc.add_paragraph() + + # Tabel participanți + table = doc.add_table(rows=1, cols=3) + table.style = 'Table Grid' + + # Header + header_cells = table.rows[0].cells + header_cells[0].text = "Nr" + header_cells[1].text = "Nume" + header_cells[2].text = "Email" + + # Formatare header + for cell in header_cells: + cell.paragraphs[0].runs[0].bold = True + cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER + set_cell_shading(cell, "D9D9D9") + + # Adaugă participanții din formularul principal (sortați alfabetic) + participanti_df = voturi_principal_df[['Nume și prenume', 'Email Address']].sort_values('Nume și prenume') + + for i, (_, row) in enumerate(participanti_df.iterrows(), 1): + row_cells = table.add_row().cells + row_cells[0].text = str(i) + row_cells[1].text = str(row['Nume și prenume']) + row_cells[2].text = str(row['Email Address']) + + row_cells[0].paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER + + # Setare lățime coloane + for row in table.rows: + row.cells[0].width = Cm(1) + row.cells[1].width = Cm(6) + row.cells[2].width = Cm(8) + + doc.add_paragraph() + + # Total + p = doc.add_paragraph() + run = p.add_run(f"TOTAL ELIGIBILI: 75") + run.bold = True + + p = doc.add_paragraph() + run = p.add_run(f"CVORUM: 50") + run.bold = True + + p = doc.add_paragraph() + run = p.add_run(f"TOTAL PARTICIPANȚI: {len(participanti_df)}") + run.bold = True + + # Salvare document + output_path = OUTPUT_DIR / "Proces_verbal_AG_25.11.2025.docx" + doc.save(output_path) + print(f"Proces verbal salvat: {output_path}") + + return output_path + + +if __name__ == "__main__": + create_proces_verbal() diff --git a/scripts/verificare_voturi_AG.py b/scripts/verificare_voturi_AG.py new file mode 100644 index 0000000..d857de9 --- /dev/null +++ b/scripts/verificare_voturi_AG.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python3 +""" +Script pentru verificare voturi AG bleuMarin Constanța 25.11.2025 +- Detectează duplicate (păstrează primul vot) +- Verifică dacă votanții sunt în lista de membri cu drept de vot (fuzzy matching) +- Generează raport unitar cu totaluri pe propuneri +""" + +import pandas as pd +from difflib import SequenceMatcher +import unicodedata +import re +from pathlib import Path + +# Configurare căi +INPUT_DIR = Path(__file__).parent.parent / "input" +OUTPUT_DIR = Path(__file__).parent.parent / "output" + +# Fișiere de intrare +MEMBRI_FILE = INPUT_DIR / "membri_promisiune.csv" +VOTURI_PRINCIPAL_FILE = INPUT_DIR / "AG bleuMarin Constanța 25.11.2025 (Responses) - Form Responses 1.csv" +VOTURI_SUPLIMENTAR_FILE = INPUT_DIR / "AG bleuMarin Constanța 25.11.2025 Suplimentar (Responses) - Form Responses 1.csv" + +# Prag pentru fuzzy matching (60% pentru a prinde variații de nume) +FUZZY_THRESHOLD = 0.60 + + +def normalize_name(name): + """Normalizează numele pentru comparare""" + if pd.isna(name): + return "" + # Convertește la lowercase + name = str(name).lower().strip() + # Elimină diacritice + name = unicodedata.normalize('NFKD', name) + name = ''.join(c for c in name if not unicodedata.combining(c)) + # Elimină caractere speciale (păstrează doar litere și spații) + name = re.sub(r'[^a-z\s]', '', name) + # Elimină spații multiple + name = re.sub(r'\s+', ' ', name).strip() + return name + + +def fuzzy_match(name1, name2): + """Calculează similaritatea între două nume""" + n1 = normalize_name(name1) + n2 = normalize_name(name2) + + if not n1 or not n2: + return 0.0 + + # Încearcă potrivire directă + if n1 == n2: + return 1.0 + + words1 = set(n1.split()) + words2 = set(n2.split()) + + # Dacă toate cuvintele din numele scurt sunt în numele lung (ex: "Stancu Matei" in "Stancu Matei Stefan") + if words1 and words2: + if words1.issubset(words2) or words2.issubset(words1): + return 0.95 + + # Verifică dacă au cel puțin 2 cuvinte comune (ex: "Bardi Antonio Alexandru" și "Bardi Alexandru Antonio") + common = words1.intersection(words2) + if len(common) >= 2: + # Verifică dacă numele de familie (primul cuvânt din fiecare) se potrivește + list1 = n1.split() + list2 = n2.split() + if list1[0] == list2[0]: + return 0.92 # Același nume familie + prenume comun + return 0.85 # Cuvinte comune dar familie diferit + + # Sortează cuvintele și compară (pentru nume în ordine diferită) + sorted1 = ' '.join(sorted(n1.split())) + sorted2 = ' '.join(sorted(n2.split())) + if sorted1 == sorted2: + return 0.98 + + # Fuzzy matching cu SequenceMatcher + base_score = SequenceMatcher(None, n1, n2).ratio() + + # Bonus dacă cel puțin un cuvânt se potrivește exact + if words1.intersection(words2): + base_score = min(base_score + 0.15, 0.95) + + return base_score + + +def find_best_match(name, membri_names): + """Găsește cel mai bun match pentru un nume în lista de membri""" + best_score = 0 + best_match = None + + for membru in membri_names: + score = fuzzy_match(name, membru) + if score > best_score: + best_score = score + best_match = membru + + return best_match, best_score + + +def remove_duplicates(df, email_col='Email Address', timestamp_col='Timestamp'): + """Elimină duplicatele păstrând primul vot (cel mai vechi)""" + # Sortează după timestamp + df_sorted = df.sort_values(by=timestamp_col) + + # Găsește duplicatele + duplicates = df_sorted[df_sorted.duplicated(subset=[email_col], keep='first')] + + # Păstrează doar primul vot pentru fiecare email + df_unique = df_sorted.drop_duplicates(subset=[email_col], keep='first') + + return df_unique, duplicates + + +def main(): + print("=" * 70) + print("Verificare voturi AG bleuMarin Constanța 25.11.2025") + print("=" * 70) + + # Încarcă lista de membri + print("\n[1] Încărcare date...") + membri_df = pd.read_csv(MEMBRI_FILE) + membri_names = membri_df['Nume'].dropna().tolist() + print(f" - Membri cu drept de vot: {len(membri_names)}") + + # Încarcă voturile principale (fără skiprows, headerul este pe linia 1) + voturi_principal_df = pd.read_csv(VOTURI_PRINCIPAL_FILE) + print(f" - Voturi formular principal: {len(voturi_principal_df)}") + + # Încarcă voturile suplimentare + voturi_suplimentar_df = pd.read_csv(VOTURI_SUPLIMENTAR_FILE) + print(f" - Voturi formular suplimentar: {len(voturi_suplimentar_df)}") + + # [2] Elimină duplicatele + print("\n[2] Detectare duplicate (pe email)...") + voturi_principal_clean, duplicates_principal = remove_duplicates(voturi_principal_df) + voturi_suplimentar_clean, duplicates_suplimentar = remove_duplicates(voturi_suplimentar_df) + + print(f" - Duplicate formular principal: {len(duplicates_principal)}") + if len(duplicates_principal) > 0: + for _, row in duplicates_principal.iterrows(): + print(f" * {row['Nume și prenume']} ({row['Email Address']}) - {row['Timestamp']}") + + print(f" - Duplicate formular suplimentar: {len(duplicates_suplimentar)}") + if len(duplicates_suplimentar) > 0: + for _, row in duplicates_suplimentar.iterrows(): + print(f" * {row['Nume și prenume']} ({row['Email Address']}) - {row['Timestamp']}") + + # Salvează duplicatele + all_duplicates = [] + for _, row in duplicates_principal.iterrows(): + all_duplicates.append({ + 'Formular': 'Principal', + 'Nume': row['Nume și prenume'], + 'Email': row['Email Address'], + 'Timestamp': row['Timestamp'] + }) + for _, row in duplicates_suplimentar.iterrows(): + all_duplicates.append({ + 'Formular': 'Suplimentar', + 'Nume': row['Nume și prenume'], + 'Email': row['Email Address'], + 'Timestamp': row['Timestamp'] + }) + + duplicates_df = pd.DataFrame(all_duplicates) + duplicates_df.to_csv(OUTPUT_DIR / "raport_duplicate.csv", index=False, encoding='utf-8-sig') + print(f" -> Salvat: output/raport_duplicate.csv") + + # [3] Verificare drept de vot cu fuzzy matching + print("\n[3] Verificare drept de vot (fuzzy matching, prag {:.0%})...".format(FUZZY_THRESHOLD)) + + # Combinăm votanții din ambele formulare + votanti_principal = voturi_principal_clean[['Nume și prenume', 'Email Address']].copy() + votanti_principal['Formular'] = 'Principal' + + votanti_suplimentar = voturi_suplimentar_clean[['Nume și prenume', 'Email Address']].copy() + votanti_suplimentar['Formular'] = 'Suplimentar' + + all_votanti = pd.concat([votanti_principal, votanti_suplimentar]) + all_votanti = all_votanti.drop_duplicates(subset=['Email Address']) + + neautorizati = [] + autorizati_mapping = {} + + for _, row in all_votanti.iterrows(): + nume = row['Nume și prenume'] + email = row['Email Address'] + + best_match, score = find_best_match(nume, membri_names) + + if score >= FUZZY_THRESHOLD: + autorizati_mapping[email] = { + 'nume_votat': nume, + 'nume_membru': best_match, + 'score': score + } + else: + neautorizati.append({ + 'Nume votat': nume, + 'Email': email, + 'Cel mai apropiat match': best_match, + 'Scor similaritate': f"{score:.2%}" + }) + + print(f" - Votanți autorizați: {len(autorizati_mapping)}") + print(f" - Votanți NEAUTORIZAȚI: {len(neautorizati)}") + + if neautorizati: + print("\n Votanți fără drept de vot găsit în lista de membri:") + for v in neautorizati: + print(f" * {v['Nume votat']} -> {v['Cel mai apropiat match']} ({v['Scor similaritate']})") + + neautorizati_df = pd.DataFrame(neautorizati) + neautorizati_df.to_csv(OUTPUT_DIR / "raport_neautorizati.csv", index=False, encoding='utf-8-sig') + print(f" -> Salvat: output/raport_neautorizati.csv") + + # Lista de email-uri neautorizate (pentru excludere din totaluri) + emails_neautorizati = set(v['Email'] for v in neautorizati) + + # [4] Calcul totaluri + print("\n[4] Calcul totaluri pe propuneri...") + + # Filtrăm doar voturile autorizate + voturi_principal_valid = voturi_principal_clean[~voturi_principal_clean['Email Address'].isin(emails_neautorizati)] + voturi_suplimentar_valid = voturi_suplimentar_clean[~voturi_suplimentar_clean['Email Address'].isin(emails_neautorizati)] + + print(f" - Voturi valide formular principal: {len(voturi_principal_valid)}") + print(f" - Voturi valide formular suplimentar: {len(voturi_suplimentar_valid)}") + + # Găsim coloanele cu propuneri din formularul principal (cele care încep cu cifre) + propuneri_cols = [] + for col in voturi_principal_valid.columns: + # Verifică dacă coloana începe cu un număr urmat de punct + if re.match(r'^\d+\.', col): + # Exclude coloanele Badge (12.x) + if not col.startswith('12.'): + propuneri_cols.append(col) + + print(f" - Propuneri găsite în formular principal: {len(propuneri_cols)}") + + # Calculăm totalurile + totaluri = [] + + # Titluri scurte pentru propuneri + titluri_scurte = { + 1: "1. AG ONCR. Bugetul ONCR pe anul 2026", + 2: "2. AG ONCR. Acordarea dreptului PJ pentru structurile locale", + 3: "3. AG ONCR. Reglementarea transferurilor între Centre Locale", + 4: "4. Reprezentant bleuMarin la AG Național (Maria Costache)", + 5: "5. Cotizație: 250 lei (150 lei adulți/copii lideri, 175 lei frați)", + 6: "6. Program Woodbadge: 20% participant, 80% centru local", + 7: "7. Fund-raising patrule: 20% acțiuni sociale, 80% patrulă", + 8: "8. Fund-raising membru: 20% acțiuni sociale, 80% membru", + 9: "9. Sponsorizări membru: până la 20% pentru evenimente", + 10: "10. Telefoane la activități - restricții", + 11: "11. Fund-raising/caritate anunțate pe grupul de lideri" + } + + # Propuneri din formularul principal + for i, col in enumerate(propuneri_cols, 1): + voturi = voturi_principal_valid[col].value_counts() + da = voturi.get('Da', 0) + nu = voturi.get('Nu', 0) + abtinere = voturi.get('Abtinere', 0) + total = da + nu + abtinere + + titlu = titluri_scurte.get(i, f"{i}. {col[:50]}...") + + totaluri.append({ + 'Nr': i, + 'Propunere': titlu, + 'Da': da, + 'Nu': nu, + 'Abtinere': abtinere, + 'Total': total + }) + + # Propunerea din formularul suplimentar + suplimentar_cols = [col for col in voturi_suplimentar_valid.columns if re.match(r'^\d+\.', col)] + if suplimentar_cols: + suplimentar_col = suplimentar_cols[0] + voturi_supl = voturi_suplimentar_valid[suplimentar_col].value_counts() + + totaluri.append({ + 'Nr': 12, + 'Propunere': '12. AG ONCR. Înființare CL Sf. Voievod Ștefan cel Mare (desprindere)', + 'Da': voturi_supl.get('Da', 0), + 'Nu': voturi_supl.get('Nu', 0), + 'Abtinere': voturi_supl.get('Abtinere', 0), + 'Total': voturi_supl.get('Da', 0) + voturi_supl.get('Nu', 0) + voturi_supl.get('Abtinere', 0) + }) + + # Salvează totalurile + totaluri_df = pd.DataFrame(totaluri) + totaluri_df.to_csv(OUTPUT_DIR / "raport_voturi_AG.csv", index=False, encoding='utf-8-sig') + print(f" -> Salvat: output/raport_voturi_AG.csv") + + # [5] Afișare rezultate + print("\n" + "=" * 70) + print("REZULTATE FINALE - Totaluri voturi AG") + print("=" * 70) + + for t in totaluri: + print(f"\n{t['Propunere']}") + print(f" Da: {t['Da']:3d} | Nu: {t['Nu']:3d} | Abținere: {t['Abtinere']:3d} | Total: {t['Total']:3d}") + + print("\n" + "=" * 70) + print("SUMAR") + print("=" * 70) + print(f"Total membri cu drept de vot: {len(membri_names)}") + print(f"Total voturi unice formular principal: {len(voturi_principal_clean)}") + print(f"Total voturi unice formular suplimentar: {len(voturi_suplimentar_clean)}") + print(f"Duplicate eliminate: {len(duplicates_principal) + len(duplicates_suplimentar)}") + print(f"Votanți neautorizați: {len(neautorizati)}") + print(f"Voturi valide formular principal: {len(voturi_principal_valid)}") + print(f"Voturi valide formular suplimentar: {len(voturi_suplimentar_valid)}") + + +if __name__ == "__main__": + main()