adunare generala

This commit is contained in:
Marius
2025-12-20 22:23:11 +02:00
parent 90d77704d6
commit 91b9e08e9d
4 changed files with 697 additions and 0 deletions

View File

@@ -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()

View File

@@ -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()