Update .trash, dashboard, root +1 more (+1 ~5 -18)

This commit is contained in:
Echo
2026-02-03 21:48:03 +00:00
parent 0ba44b52a0
commit 75c400009a
23 changed files with 215 additions and 129 deletions

6
.gitignore vendored
View File

@@ -24,3 +24,9 @@ Thumbs.db
.vscode/ .vscode/
.idea/ .idea/
credentials/ credentials/
# ANAF Monitor state files
tools/anaf-monitor/hashes.json
tools/anaf-monitor/versions.json
tools/anaf-monitor/snapshots/
tools/anaf-monitor/monitor.log

View File

@@ -153,8 +153,15 @@ memory_get path="memory/file.md" from=1 lines=50
- **Commit:** `python3 tools/git_commit.py --push` - **Commit:** `python3 tools/git_commit.py --push`
### ANAF Monitor ### ANAF Monitor
- **Script:** `python3 tools/anaf-monitor/monitor_v2.py` - **Script:** `python3 tools/anaf-monitor/monitor_v2.py` (v2.2)
- **Monitorizează:** D100, D101, D200, D390, D406, situații financiare, E-Factura - **Job:** `anaf-monitor` (10:00 și 16:00 București, luni-vineri)
- **Monitorizează:** D100, D101, D300, D390, D394, D205, D406, Bilanț 2025, situații financiare
- **Funcții:**
- Hash detection - detectează ORICE schimbare pe pagină
- Version extraction - extrage date soft A/J (toate variantele: S1002, S1003, etc.)
- Text snapshots - salvează textul în `snapshots/*.txt`
- Diff - arată exact ce s-a modificat
- **Output:** JSON cu changes + diff (dacă există)
### Google Calendar ### Google Calendar
- **Credentials:** `credentials/google-calendar.json` (OAuth client) - **Credentials:** `credentials/google-calendar.json` (OAuth client)
@@ -238,6 +245,7 @@ create_event(
| 01:00 | 03:00 | night-execute-late | #echo-work | Continuă execuția task-uri (run 2) | | 01:00 | 03:00 | night-execute-late | #echo-work | Continuă execuția task-uri (run 2) |
| 03:00 | 05:00 | archive-tasks | #echo-work | Arhivează task-uri vechi | | 03:00 | 05:00 | archive-tasks | #echo-work | Arhivează task-uri vechi |
| 06:00,17:00 | 08:00,19:00 | insights-extract | - | Extrage insights din memory/kb/ + actualizează tehnici-pauza.md | | 06:00,17:00 | 08:00,19:00 | insights-extract | - | Extrage insights din memory/kb/ + actualizează tehnici-pauza.md |
| 08:00,14:00 | 10:00,16:00 | anaf-monitor | #echo-work (doar alerte) | Verifică modificări ANAF (hash + diff) |
| 06:30 | 08:30 | morning-report | 📧 EMAIL | Raport dimineață - vezi [FLUX-JOBURI.md](memory/kb/projects/FLUX-JOBURI.md) | | 06:30 | 08:30 | morning-report | 📧 EMAIL | Raport dimineață - vezi [FLUX-JOBURI.md](memory/kb/projects/FLUX-JOBURI.md) |
| 07:00 | 09:00 | morning-coaching | #echo-self + 📧 | Gând + provocare → memory/kb/coaching/ | | 07:00 | 09:00 | morning-coaching | #echo-self + 📧 | Gând + provocare → memory/kb/coaching/ |
| 07-17 | 09-19 | respiratie-orar | #echo-self | Pauze orare (skip dacă busy în calendar) | | 07-17 | 09-19 | respiratie-orar | #echo-self | Pauze orare (skip dacă busy în calendar) |

View File

@@ -13,7 +13,7 @@
"ok": true, "ok": true,
"status": "OK", "status": "OK",
"message": "Nicio modificare detectată", "message": "Nicio modificare detectată",
"lastCheck": "02 Feb 2026, 07:52", "lastCheck": "03 Feb 2026, 21:39",
"changesCount": 0 "changesCount": 0
} }
} }

View File

@@ -1,5 +1,5 @@
{ {
"lastUpdated": "2026-02-03T07:00:00.000Z", "lastUpdated": "2026-02-03T21:16:13.452Z",
"items": [ "items": [
{ {
"id": "prov-2026-02-03", "id": "prov-2026-02-03",
@@ -8,8 +8,8 @@
"example": "Exemplu de umbră: 'Nu mă consider destul de deștept ca antreprenor' - asta e o parte pe care o ascunzi. Când o accepți ('ok, am și limite'), eliberezi energia pe care o consumi să o maschezi cu scuze sau evitare.", "example": "Exemplu de umbră: 'Nu mă consider destul de deștept ca antreprenor' - asta e o parte pe care o ascunzi. Când o accepți ('ok, am și limite'), eliberezi energia pe care o consumi să o maschezi cu scuze sau evitare.",
"domain": "self", "domain": "self",
"dueDate": "2026-02-03", "dueDate": "2026-02-03",
"done": false, "done": true,
"doneAt": null, "doneAt": "2026-02-03T21:16:13.452Z",
"source": "Zoltan Vereș - Umbrele Workshop", "source": "Zoltan Vereș - Umbrele Workshop",
"sourceUrl": "https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/2026-02-02_zoltan-veres-umbrele-workshop-complet.md", "sourceUrl": "https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/youtube/2026-02-02_zoltan-veres-umbrele-workshop-complet.md",
"createdAt": "2026-02-03T07:00:00.000Z" "createdAt": "2026-02-03T07:00:00.000Z"
@@ -28,4 +28,4 @@
"createdAt": "2026-02-02T09:00:00Z" "createdAt": "2026-02-02T09:00:00Z"
} }
] ]
} }

View File

@@ -1,14 +0,0 @@
{
"D100": "10d051263016cb5ef71a883b7dc3b1d8d2f9ff29909740a74b729d3e980b6460",
"D101": "937209d4785ca013cbcbe5a0d0aa8ba0e7033d3d8e6c121dadd8e38b20db8026",
"D300": "0623da0873a893fc3b1635007a32059804d94b740ec606839f471b895e774c60",
"D394": "c4c4e62bda30032f12c17edf9a5087b6173a350ccb1fd750158978b3bd0acb7d",
"D406": "b3c621b61771d7b678b4bb0946a2f47434abbc332091c84de91e7dcb4effaab6",
"SIT_FIN_SEM_2025": "8164843431e6b703a38fbdedc7898ec6ae83559fe10f88663ba0b55f3091d5fe",
"SIT_FIN_AN_2025": "4294ca9271da15b9692c3efc126298fd3a89b0c68e0df9e2a256f50ad3d46b77",
"DESCARCARE_DECLARATII": "d66297abcfc2b3ad87f65e4a60c97ddd0a889f493bb7e7c8e6035ef39d55ec3f",
"D205": "f707104acc691cf79fbaa9a80c68bff4a285297f7dd3ab7b7a680715b54fd502",
"D390": "4726938ed5858ec735caefd947a7d182b6dc64009478332c4feabdb36412a84e",
"BILANT_2024": "fbb8d66c2e530d8798362992c6983e07e1250188228c758cb6da4cde4f955950",
"BILANT_2025": "3d4e363b0f352e0b961474bca6bfa99ae44a591959210f7db8b10335f4ccede6"
}

View File

@@ -1,21 +1,67 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
ANAF Monitor v2 - Extrage și compară versiuni soft A/J din numele fișierelor ANAF Monitor v2.2 - Hash detection + version extraction + text diff
- Hash-based change detection (catches ANY change)
- Extracts ALL soft A/J versions from page
- Saves page text and shows diff on changes
""" """
import json import json
import re import re
import hashlib
import urllib.request import urllib.request
import ssl import ssl
import difflib
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from html.parser import HTMLParser
SCRIPT_DIR = Path(__file__).parent SCRIPT_DIR = Path(__file__).parent
CONFIG_FILE = SCRIPT_DIR / "config.json" CONFIG_FILE = SCRIPT_DIR / "config.json"
VERSIONS_FILE = SCRIPT_DIR / "versions.json" VERSIONS_FILE = SCRIPT_DIR / "versions.json"
HASHES_FILE = SCRIPT_DIR / "hashes.json"
SNAPSHOTS_DIR = SCRIPT_DIR / "snapshots"
LOG_FILE = SCRIPT_DIR / "monitor.log" LOG_FILE = SCRIPT_DIR / "monitor.log"
DASHBOARD_STATUS = SCRIPT_DIR.parent.parent / "dashboard" / "status.json" DASHBOARD_STATUS = SCRIPT_DIR.parent.parent / "dashboard" / "status.json"
# Ensure snapshots directory exists
SNAPSHOTS_DIR.mkdir(exist_ok=True)
class TextExtractor(HTMLParser):
"""Extract visible text from HTML"""
def __init__(self):
super().__init__()
self.text = []
self.skip_tags = {'script', 'style', 'head', 'meta', 'link'}
self.current_tag = None
def handle_starttag(self, tag, attrs):
self.current_tag = tag.lower()
def handle_endtag(self, tag):
self.current_tag = None
def handle_data(self, data):
if self.current_tag not in self.skip_tags:
text = data.strip()
if text:
self.text.append(text)
def get_text(self):
return '\n'.join(self.text)
def html_to_text(html):
"""Convert HTML to plain text"""
parser = TextExtractor()
try:
parser.feed(html)
return parser.get_text()
except:
# Fallback: just strip tags
return re.sub(r'<[^>]+>', ' ', html)
SSL_CTX = ssl.create_default_context() SSL_CTX = ssl.create_default_context()
SSL_CTX.check_hostname = False SSL_CTX.check_hostname = False
SSL_CTX.verify_mode = ssl.CERT_NONE SSL_CTX.verify_mode = ssl.CERT_NONE
@@ -39,14 +85,58 @@ def save_json(path, data):
def fetch_page(url, timeout=30): def fetch_page(url, timeout=30):
try: try:
req = urllib.request.Request(url, headers={ req = urllib.request.Request(url, headers={
'User-Agent': 'Mozilla/5.0 (compatible; ANAF-Monitor/2.0)' 'User-Agent': 'Mozilla/5.0 (compatible; ANAF-Monitor/2.1)'
}) })
with urllib.request.urlopen(req, timeout=timeout, context=SSL_CTX) as resp: with urllib.request.urlopen(req, timeout=timeout, context=SSL_CTX) as resp:
return resp.read().decode('utf-8', errors='ignore') return resp.read()
except Exception as e: except Exception as e:
log(f"ERROR fetching {url}: {e}") log(f"ERROR fetching {url}: {e}")
return None return None
def compute_hash(content):
"""Compute SHA256 hash of content"""
return hashlib.sha256(content).hexdigest()
def load_snapshot(page_id):
"""Load previous page text snapshot"""
snapshot_file = SNAPSHOTS_DIR / f"{page_id}.txt"
try:
return snapshot_file.read_text(encoding='utf-8')
except:
return None
def save_snapshot(page_id, text):
"""Save page text snapshot"""
snapshot_file = SNAPSHOTS_DIR / f"{page_id}.txt"
snapshot_file.write_text(text, encoding='utf-8')
def generate_diff(old_text, new_text, context_lines=3):
"""Generate unified diff between old and new text"""
if not old_text:
return None
old_lines = old_text.splitlines(keepends=True)
new_lines = new_text.splitlines(keepends=True)
diff = list(difflib.unified_diff(
old_lines, new_lines,
fromfile='anterior',
tofile='actual',
n=context_lines
))
if not diff:
return None
# Limitează diff-ul la maxim 50 linii pentru output
if len(diff) > 50:
diff = diff[:50] + ['... (truncat)\n']
return ''.join(diff)
def parse_date_from_filename(filename): def parse_date_from_filename(filename):
"""Extrage data din numele fișierului (ex: D394_26092025.pdf -> 26.09.2025)""" """Extrage data din numele fișierului (ex: D394_26092025.pdf -> 26.09.2025)"""
# Pattern: _DDMMYYYY. sau _DDMMYYYY_ sau _YYYYMMDD # Pattern: _DDMMYYYY. sau _DDMMYYYY_ sau _YYYYMMDD
@@ -69,10 +159,10 @@ def parse_date_from_filename(filename):
return None return None
def extract_versions(html): def extract_versions(html):
"""Extrage primul soft A și soft J din HTML""" """Extrage soft A/J din HTML - primul generic + toate cele cu label (S1002, etc.)"""
versions = {} versions = {}
# Găsește primul link soft A (PDF) # Găsește PRIMUL link soft A (PDF) - versiunea curentă
soft_a_match = re.search( soft_a_match = re.search(
r'<a[^>]+href=["\']([^"\']*\.pdf)["\'][^>]*>\s*soft\s*A\s*</a>', r'<a[^>]+href=["\']([^"\']*\.pdf)["\'][^>]*>\s*soft\s*A\s*</a>',
html, re.IGNORECASE html, re.IGNORECASE
@@ -84,17 +174,33 @@ def extract_versions(html):
if date: if date:
versions['soft_a_date'] = date versions['soft_a_date'] = date
# Găsește primul link soft J (ZIP) # Găsește soft J-uri CU LABEL (ex: "soft J - S1002") - toate
soft_j_match = re.search( soft_j_labeled = re.findall(
r'<a[^>]+href=["\']([^"\']*\.zip)["\'][^>]*>\s*soft\s*J', r'<a[^>]+href=["\']([^"\']*\.zip)["\'][^>]*>\s*soft\s*J\s*-\s*([^<]+)',
html, re.IGNORECASE html, re.IGNORECASE
) )
if soft_j_match:
url = soft_j_match.group(1) if soft_j_labeled:
versions['soft_j_url'] = url # Pagină cu soft-uri denumite (bilanț)
date = parse_date_from_filename(url) for url, label in soft_j_labeled:
if date: label = label.strip()
versions['soft_j_date'] = date key = f'soft_j_{label.replace(" ", "_")}'
versions[f'{key}_url'] = url
date = parse_date_from_filename(url)
if date:
versions[f'{key}_date'] = date
else:
# Pagină cu soft J simplu - ia doar primul
soft_j_match = re.search(
r'<a[^>]+href=["\']([^"\']*\.zip)["\'][^>]*>\s*soft\s*J',
html, re.IGNORECASE
)
if soft_j_match:
url = soft_j_match.group(1)
versions['soft_j_url'] = url
date = parse_date_from_filename(url)
if date:
versions['soft_j_date'] = date
# Găsește data publicării din text # Găsește data publicării din text
publish_match = re.search( publish_match = re.search(
@@ -106,71 +212,106 @@ def extract_versions(html):
return versions return versions
def format_date(d): def compare_versions(old, new):
"""Formatează data pentru afișare"""
if not d:
return "N/A"
return d
def compare_versions(old, new, page_name):
"""Compară versiunile și returnează diferențele""" """Compară versiunile și returnează diferențele"""
changes = [] changes = []
fields = [ # Colectează toate cheile unice
('soft_a_date', 'Soft A'), all_keys = set(old.keys()) | set(new.keys())
('soft_j_date', 'Soft J'), date_keys = sorted([k for k in all_keys if k.endswith('_date') or k == 'published'])
('published', 'Publicat')
]
for field, label in fields: for key in date_keys:
old_val = old.get(field) old_val = old.get(key)
new_val = new.get(field) new_val = new.get(key)
# Formatează label-ul
label = key.replace('_date', '').replace('_', ' ').title()
if new_val and old_val != new_val: if new_val and old_val != new_val:
if old_val: if old_val:
changes.append(f"{label}: {old_val}{new_val}") changes.append(f"{label}: {old_val}{new_val}")
else: else:
changes.append(f"{label}: {new_val} (nou)") changes.append(f"{label}: {new_val} (NOU)")
return changes return changes
def check_page(page, saved_versions): def format_current_versions(versions):
"""Formatează versiunile curente pentru output"""
result = {}
for key, val in versions.items():
if key.endswith('_date'):
label = key.replace('_date', '')
result[label] = val
return result
def check_page(page, saved_versions, saved_hashes):
"""Verifică o pagină și returnează modificările""" """Verifică o pagină și returnează modificările"""
page_id = page["id"] page_id = page["id"]
name = page["name"] name = page["name"]
url = page["url"] url = page["url"]
html = fetch_page(url) content = fetch_page(url)
if html is None: if content is None:
return None return None
# 1. Verifică hash-ul mai întâi (detectează ORICE schimbare)
new_hash = compute_hash(content)
old_hash = saved_hashes.get(page_id)
html = content.decode('utf-8', errors='ignore')
new_text = html_to_text(html)
new_versions = extract_versions(html) new_versions = extract_versions(html)
old_versions = saved_versions.get(page_id, {}) old_versions = saved_versions.get(page_id, {})
# Prima rulare - doar salvează, nu raportează # Încarcă snapshot-ul anterior
if not old_versions: old_text = load_snapshot(page_id)
log(f"INIT: {page_id} - {new_versions}")
# Prima rulare - inițializare
if not old_hash:
log(f"INIT: {page_id}")
saved_hashes[page_id] = new_hash
saved_versions[page_id] = new_versions saved_versions[page_id] = new_versions
save_snapshot(page_id, new_text)
return None return None
changes = compare_versions(old_versions, new_versions, name) # Compară hash-uri
saved_versions[page_id] = new_versions hash_changed = new_hash != old_hash
if changes: # Compară versiuni pentru detalii
log(f"CHANGES in {page_id}: {changes}") version_changes = compare_versions(old_versions, new_versions)
return {
# Generează diff dacă s-a schimbat
diff = None
if hash_changed and old_text:
diff = generate_diff(old_text, new_text)
# Actualizează starea
saved_hashes[page_id] = new_hash
saved_versions[page_id] = new_versions
save_snapshot(page_id, new_text)
if hash_changed:
if version_changes:
log(f"CHANGES in {page_id}: {version_changes}")
else:
log(f"HASH CHANGED in {page_id} (no version changes detected)")
version_changes = ["Pagina s-a modificat (vezi diff)"]
result = {
"id": page_id, "id": page_id,
"name": name, "name": name,
"url": url, "url": url,
"changes": changes, "changes": version_changes,
"current": { "current": format_current_versions(new_versions)
"soft_a": new_versions.get('soft_a_date', 'N/A'),
"soft_j": new_versions.get('soft_j_date', 'N/A')
}
} }
else:
log(f"OK: {page_id}") if diff:
return None result["diff"] = diff
return result
log(f"OK: {page_id}")
return None
def update_dashboard_status(has_changes, changes_count): def update_dashboard_status(has_changes, changes_count):
"""Actualizează status.json pentru dashboard""" """Actualizează status.json pentru dashboard"""
@@ -188,18 +329,20 @@ def update_dashboard_status(has_changes, changes_count):
log(f"ERROR updating dashboard status: {e}") log(f"ERROR updating dashboard status: {e}")
def main(): def main():
log("=== Starting ANAF monitor v2 ===") log("=== Starting ANAF monitor v2.1 ===")
config = load_json(CONFIG_FILE, {"pages": []}) config = load_json(CONFIG_FILE, {"pages": []})
saved_versions = load_json(VERSIONS_FILE, {}) saved_versions = load_json(VERSIONS_FILE, {})
saved_hashes = load_json(HASHES_FILE, {})
all_changes = [] all_changes = []
for page in config["pages"]: for page in config["pages"]:
result = check_page(page, saved_versions) result = check_page(page, saved_versions, saved_hashes)
if result: if result:
all_changes.append(result) all_changes.append(result)
save_json(VERSIONS_FILE, saved_versions) save_json(VERSIONS_FILE, saved_versions)
save_json(HASHES_FILE, saved_hashes)
# Update dashboard status # Update dashboard status
update_dashboard_status(len(all_changes) > 0, len(all_changes)) update_dashboard_status(len(all_changes) > 0, len(all_changes))

View File

@@ -1,57 +0,0 @@
{
"D100": {
"soft_a_url": "http://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D100_710_XML_0126_260126.pdf",
"soft_a_date": "26.01.2026",
"soft_j_url": "http://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D100_22012026.zip",
"soft_j_date": "22.01.2026"
},
"D101": {
"soft_a_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D101_XML_2025_260126.pdf",
"soft_a_date": "26.01.2026",
"soft_j_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D101_J1102.zip"
},
"D300": {
"soft_a_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D300_v11.0.7_16122025.pdf",
"soft_a_date": "16.12.2025",
"soft_j_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D300_20250910.zip",
"soft_j_date": "10.09.2025"
},
"D390": {
"soft_a_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D390_XML_2020_300424.pdf",
"soft_a_date": "30.04.2024",
"soft_j_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D390_20250625.zip",
"soft_j_date": "25.06.2025"
},
"D394": {
"soft_a_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D394_26092025.pdf",
"soft_a_date": "26.09.2025",
"soft_j_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D394_17092025.zip",
"soft_j_date": "17.09.2025"
},
"D205": {
"soft_a_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D205_XML_2025_150126.pdf",
"soft_a_date": "15.01.2026",
"soft_j_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D205_J901_P400.zip"
},
"D406": {
"soft_a_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/R405_XML_2017_080321.pdf",
"soft_a_date": "08.03.2021",
"soft_j_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D406_20251030.zip",
"soft_j_date": "30.10.2025"
},
"BILANT_2025": {
"soft_a_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/bilant_SC_1225_XML_270126.pdf",
"soft_a_date": "27.01.2026",
"soft_j_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/S1002_20260128.zip",
"soft_j_date": "28.01.2026"
},
"SIT_FIN_SEM_2025": {
"soft_j_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/S1012_20250723.zip",
"soft_j_date": "23.07.2025"
},
"SIT_FIN_AN_2025": {
"soft_a_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/bilant_S1030_XML_consolidare_270126_bis.pdf",
"soft_a_date": "27.01.2026"
},
"DESCARCARE_DECLARATII": {}
}