chore(kb): note noi (youtube, facebook, fișe grup sprijin, email) + tool scout_voluntar_check
Reindexare KB, timestamps cron/habits actualizate din rulările automate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
209
tools/scout_voluntar_check.py
Normal file
209
tools/scout_voluntar_check.py
Normal file
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Verifica emailuri noi de inscriere cercetasi bleuMarin Constanta (voluntari +
|
||||
lista de asteptare copii) si formateaza un mesaj WhatsApp gata de trimis
|
||||
pentru colegul care se ocupa de voluntari noi.
|
||||
|
||||
Nu salveaza nimic in memory/kb/ - doar citeste emailurile nou-sosite (unread),
|
||||
extrage campurile si le marcheaza ca citite.
|
||||
|
||||
Usage:
|
||||
python3 tools/scout_voluntar_check.py
|
||||
"""
|
||||
|
||||
import imaplib
|
||||
import email
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from tools.email_process import (
|
||||
IMAP_SERVER, IMAP_PORT, IMAP_USER, IMAP_PASS, WHITELIST,
|
||||
decode_mime_header, get_email_body, extract_sender_email,
|
||||
)
|
||||
|
||||
# Ordinea exacta a campurilor asa cum apar in notificarea Gravity Forms
|
||||
# (text simplu, fara separatori intre eticheta si valoare) pentru fiecare
|
||||
# formular de pe scout-bleumarin.ro. Daca site-ul schimba formularul,
|
||||
# aceste liste trebuie actualizate.
|
||||
LABELS_VOLUNTAR = [
|
||||
"Email",
|
||||
"Nume și prenume",
|
||||
"Vârsta (peste 18 ani)",
|
||||
"Telefon",
|
||||
"Câte ore pe lună ești dispus să investești pentru activitățile de cercetași?",
|
||||
"Skill profesional",
|
||||
"De unde ai aflat de cercetași?",
|
||||
"Care este motivația ta de a te înscrie ca voluntar adult?",
|
||||
"Avem beneficiari copii și tineri din fiecare ciclu de școală (clasele 1-4, clasele 5-8 si liceu), unde consideri că ai putea ajuta?",
|
||||
"În ce categorie de activități crezi că ai putea aduce un plus de valoare?",
|
||||
"Care sunt primele 3 valori morale ce te vor însoți în drumul tău alături de cercetași?",
|
||||
"Care sunt valorile (principile) ce te aștepți să le găsești la ceilalți voluntari?",
|
||||
]
|
||||
|
||||
LABELS_COPIL = [
|
||||
"Nume și prenume copil",
|
||||
"Nume și prenume părinte",
|
||||
"Număr telefon părinte",
|
||||
"Adresă e-mail părinte",
|
||||
"În ce clasă va fi la școală în luna octombrie? (începutul anului cercetășesc următor)",
|
||||
"Data nașterii",
|
||||
"Care este motivația înscrierii la cercetași?",
|
||||
"Copilul are frați, colegi de școală sau prieteni la cercetași? Dacă da, completați numele.",
|
||||
"De unde ați aflat de activitatea noastră?",
|
||||
]
|
||||
|
||||
|
||||
def extract_fields(body: str, labels: list[str]) -> dict:
|
||||
"""Extrage valorile dintre etichete consecutive dintr-un text Gravity Forms.
|
||||
|
||||
Emailul e text simplu cu line-wrap la ~70 caractere, care poate rupe o
|
||||
eticheta la mijloc (ex. "Vârsta (peste 18\\nani)") - normalizam orice
|
||||
secventa de whitespace la un singur spatiu inainte de a cauta etichetele.
|
||||
"""
|
||||
body = re.sub(r"\s+", " ", body)
|
||||
pattern = "|".join(re.escape(l) for l in labels)
|
||||
matches = list(re.finditer(pattern, body))
|
||||
fields = {}
|
||||
for i, m in enumerate(matches):
|
||||
label = m.group(0)
|
||||
start = m.end()
|
||||
end = matches[i + 1].start() if i + 1 < len(matches) else len(body)
|
||||
fields[label] = body[start:end].strip(" \n\r\t:")
|
||||
return fields
|
||||
|
||||
|
||||
def extract_id(body: str) -> str:
|
||||
m = re.search(r"Răspuns #(\d+)", body)
|
||||
return m.group(1) if m else "?"
|
||||
|
||||
|
||||
def digits_only(s: str) -> str:
|
||||
return re.sub(r"\D", "", s or "")
|
||||
|
||||
|
||||
def parse_email(body: str, subject: str) -> dict | None:
|
||||
body = re.sub(r"\s+", " ", body)
|
||||
subj = subject.lower()
|
||||
if "voluntari" in subj:
|
||||
f = extract_fields(body, LABELS_VOLUNTAR)
|
||||
return {
|
||||
"tip": "voluntar",
|
||||
"id": extract_id(body),
|
||||
"nume": f.get("Nume și prenume", "?"),
|
||||
"varsta": f.get("Vârsta (peste 18 ani)", "?"),
|
||||
"telefon": f.get("Telefon", "?"),
|
||||
"email": f.get("Email", "?"),
|
||||
"disponibilitate": f.get("Câte ore pe lună ești dispus să investești pentru activitățile de cercetași?", "?"),
|
||||
"skill": f.get("Skill profesional", "?"),
|
||||
"sursa": f.get("De unde ai aflat de cercetași?", "?"),
|
||||
"interes_varsta": f.get("Avem beneficiari copii și tineri din fiecare ciclu de școală (clasele 1-4, clasele 5-8 si liceu), unde consideri că ai putea ajuta?", "?"),
|
||||
"interes_activitati": f.get("În ce categorie de activități crezi că ai putea aduce un plus de valoare?", "?"),
|
||||
}
|
||||
if "așteptare" in subj or "asteptare" in subj:
|
||||
f = extract_fields(body, LABELS_COPIL)
|
||||
return {
|
||||
"tip": "copil",
|
||||
"id": extract_id(body),
|
||||
"nume_copil": f.get("Nume și prenume copil", "?"),
|
||||
"nume_parinte": f.get("Nume și prenume părinte", "?"),
|
||||
"telefon": f.get("Număr telefon părinte", "?"),
|
||||
"email": f.get("Adresă e-mail părinte", "?"),
|
||||
"clasa": f.get("În ce clasă va fi la școală în luna octombrie? (începutul anului cercetășesc următor)", "?"),
|
||||
"data_nasterii": f.get("Data nașterii", "?"),
|
||||
"sursa": f.get("De unde ați aflat de activitatea noastră?", "?"),
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
def fetch_unseen_scout_emails() -> list[dict]:
|
||||
mail = imaplib.IMAP4_SSL(IMAP_SERVER, IMAP_PORT)
|
||||
mail.login(IMAP_USER, IMAP_PASS)
|
||||
mail.select("INBOX")
|
||||
|
||||
status, messages = mail.search(None, "UNSEEN")
|
||||
email_ids = messages[0].split() if messages[0] else []
|
||||
|
||||
records = []
|
||||
seen_ids = []
|
||||
for eid in email_ids:
|
||||
status, data = mail.fetch(eid, "(BODY.PEEK[])")
|
||||
if status != "OK":
|
||||
continue
|
||||
msg = email.message_from_bytes(data[0][1])
|
||||
from_addr = decode_mime_header(msg["From"])
|
||||
sender_email = extract_sender_email(from_addr)
|
||||
subject = decode_mime_header(msg["Subject"])
|
||||
body = get_email_body(msg)
|
||||
|
||||
is_forward = "fwd:" in subject.lower()
|
||||
if sender_email not in WHITELIST and not is_forward:
|
||||
continue
|
||||
if "scout-bleumarin.ro" not in body:
|
||||
continue
|
||||
|
||||
parsed = parse_email(body, subject)
|
||||
if parsed:
|
||||
records.append(parsed)
|
||||
seen_ids.append(eid)
|
||||
|
||||
# Marcheaza doar emailurile procesate ca citite
|
||||
for eid in seen_ids:
|
||||
mail.store(eid, "+FLAGS", "\\Seen")
|
||||
mail.logout()
|
||||
return records
|
||||
|
||||
|
||||
def format_message(records: list[dict]) -> str:
|
||||
if not records:
|
||||
return "Niciun email nou de inscriere cercetasi."
|
||||
|
||||
voluntari = [r for r in records if r["tip"] == "voluntar"]
|
||||
copii = [r for r in records if r["tip"] == "copil"]
|
||||
|
||||
blocks = []
|
||||
for v in voluntari:
|
||||
copii_asociati = [c for c in copii if digits_only(c["telefon"]) == digits_only(v["telefon"])]
|
||||
lines = [
|
||||
f"Voluntar nou inscris - bleuMarin Constanta (ID #{v['id']})",
|
||||
"",
|
||||
f"Nume: {v['nume']}, {v['varsta']} ani",
|
||||
f"Tel: {v['telefon']}",
|
||||
f"Email: {v['email']}",
|
||||
"",
|
||||
f"Disponibilitate: {v['disponibilitate']}",
|
||||
f"Skill: {v['skill']}",
|
||||
f"Interes: {v['interes_varsta']} - {v['interes_activitati']}",
|
||||
f"De unde a aflat: {v['sursa']}",
|
||||
]
|
||||
for c in copii_asociati:
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"Notă: are și un copil pe lista de așteptare (ID #{c['id']}) - "
|
||||
f"{c['nume_copil']} (n. {c['data_nasterii']}, clasa din octombrie: {c['clasa']}). "
|
||||
f"De unde a aflat: {c['sursa']}"
|
||||
)
|
||||
blocks.append("\n".join(lines))
|
||||
copii = [c for c in copii if c not in copii_asociati]
|
||||
|
||||
for c in copii:
|
||||
blocks.append(
|
||||
"\n".join([
|
||||
f"Copil nou pe lista de asteptare - bleuMarin Constanta (ID #{c['id']})",
|
||||
"",
|
||||
f"Copil: {c['nume_copil']} (n. {c['data_nasterii']})",
|
||||
f"Parinte: {c['nume_parinte']}",
|
||||
f"Tel: {c['telefon']}",
|
||||
f"Email: {c['email']}",
|
||||
f"Clasa din octombrie: {c['clasa']}",
|
||||
f"De unde a aflat: {c['sursa']}",
|
||||
])
|
||||
)
|
||||
|
||||
return "\n\n---\n\n".join(blocks)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
records = fetch_unseen_scout_emails()
|
||||
print(format_message(records))
|
||||
70
tools/test_scout_voluntar_check.py
Normal file
70
tools/test_scout_voluntar_check.py
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Self-check pentru parsarea emailurilor de inscriere cercetasi.
|
||||
Ruleaza: python3 tools/test_scout_voluntar_check.py
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from tools.scout_voluntar_check import parse_email
|
||||
|
||||
BODY_VOLUNTAR = """
|
||||
Organizația Natională Cercetașii României - Centrul Local bleuMarin
|
||||
Constanța <https://scout-bleumarin.ro/ro>
|
||||
Formularul "Înscriere voluntari bleuMarin Constanța" a primit un nou
|
||||
răspuns.
|
||||
|
||||
Răspunsul a fost stocat și în baza de date cu următorul id:
|
||||
Răspuns #516 <https://scout-bleumarin.ro/admin/form_submission/516> Email
|
||||
ureche.irina94@yahoo.com Nume și prenume Purcărea Irina Vârsta (peste 18
|
||||
ani) 32 Telefon 0729127010 Câte ore pe lună ești dispus să investești
|
||||
pentru activitățile de cercetași? 12-15 (3 ore pe săptămână) - minim pentru
|
||||
lider full-time Skill profesional Experiență în antreprenoriat, organizarea
|
||||
și gestionarea unei afaceri și comunicare. De unde ai aflat de cercetași? Un
|
||||
eveniment în complex Tom. Care este motivația ta de a te înscrie ca
|
||||
voluntar adult? Îmi doresc să mă înscriu deoarece vreau să contribui la
|
||||
dezvoltarea copiilor. Avem beneficiari copii și tineri din fiecare ciclu de școală
|
||||
(clasele 1-4, clasele 5-8 si liceu), unde consideri că ai putea ajuta? clasele
|
||||
1-4 (lupisori) În ce categorie de activități crezi că ai putea aduce un
|
||||
plus de valoare? Comunicare și organizare evenimente. Care sunt primele 3 valori morale ce te vor
|
||||
însoți în drumul tău alături de cercetași? Respectul, responsabilitatea și
|
||||
empatia. Care sunt valorile (principile) ce te aștepți să le găsești la
|
||||
ceilalți voluntari? Respect și deschidere.
|
||||
"""
|
||||
|
||||
BODY_COPIL = """
|
||||
Formularul "Listă așteptare" a primit un nou răspuns.
|
||||
|
||||
Răspunsul a fost stocat și în baza de date cu următorul id:
|
||||
Răspuns #517 <https://scout-bleumarin.ro/admin/form_submission/517> Nume și
|
||||
prenume copil Purcărea Amelia Ioana Nume și prenume părinte Purcărea
|
||||
Irina Număr
|
||||
telefon părinte 0729127010 Adresă e-mail părinte ureche.irina94@yahoo.com În
|
||||
ce clasă va fi la școală în luna octombrie? (începutul anului cercetășesc
|
||||
următor) clasa I Data nașterii 2018-10-10 Care este motivația înscrierii la
|
||||
cercetași? Îmi doresc. Copilul are frați, colegi de școală sau prieteni la
|
||||
cercetași? Dacă da, completați numele. Nu din câte știm. De unde ați aflat de
|
||||
activitatea noastră? O activitate din centrul comercial Tom.
|
||||
"""
|
||||
|
||||
|
||||
def demo():
|
||||
v = parse_email(BODY_VOLUNTAR, "Fwd: Înscriere voluntari bleuMarin Constanța")
|
||||
assert v is not None
|
||||
assert v["id"] == "516"
|
||||
assert v["nume"] == "Purcărea Irina"
|
||||
assert v["telefon"] == "0729127010"
|
||||
assert v["sursa"] == "Un eveniment în complex Tom."
|
||||
|
||||
c = parse_email(BODY_COPIL, "Fwd: Listă așteptare")
|
||||
assert c is not None
|
||||
assert c["id"] == "517"
|
||||
assert c["nume_copil"] == "Purcărea Amelia Ioana"
|
||||
assert c["telefon"] == "0729127010"
|
||||
assert c["sursa"] == "O activitate din centrul comercial Tom."
|
||||
|
||||
print("OK - parsare voluntar + copil corecta")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo()
|
||||
Reference in New Issue
Block a user