Update .trash, dashboard, root +1 more (+1 ~5 -18)
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -24,3 +24,9 @@ Thumbs.db
|
||||
.vscode/
|
||||
.idea/
|
||||
credentials/
|
||||
|
||||
# ANAF Monitor state files
|
||||
tools/anaf-monitor/hashes.json
|
||||
tools/anaf-monitor/versions.json
|
||||
tools/anaf-monitor/snapshots/
|
||||
tools/anaf-monitor/monitor.log
|
||||
|
||||
12
TOOLS.md
12
TOOLS.md
@@ -153,8 +153,15 @@ memory_get path="memory/file.md" from=1 lines=50
|
||||
- **Commit:** `python3 tools/git_commit.py --push`
|
||||
|
||||
### ANAF Monitor
|
||||
- **Script:** `python3 tools/anaf-monitor/monitor_v2.py`
|
||||
- **Monitorizează:** D100, D101, D200, D390, D406, situații financiare, E-Factura
|
||||
- **Script:** `python3 tools/anaf-monitor/monitor_v2.py` (v2.2)
|
||||
- **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
|
||||
- **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) |
|
||||
| 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 |
|
||||
| 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) |
|
||||
| 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) |
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"ok": true,
|
||||
"status": "OK",
|
||||
"message": "Nicio modificare detectată",
|
||||
"lastCheck": "02 Feb 2026, 07:52",
|
||||
"lastCheck": "03 Feb 2026, 21:39",
|
||||
"changesCount": 0
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"lastUpdated": "2026-02-03T07:00:00.000Z",
|
||||
"lastUpdated": "2026-02-03T21:16:13.452Z",
|
||||
"items": [
|
||||
{
|
||||
"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.",
|
||||
"domain": "self",
|
||||
"dueDate": "2026-02-03",
|
||||
"done": false,
|
||||
"doneAt": null,
|
||||
"done": true,
|
||||
"doneAt": "2026-02-03T21:16:13.452Z",
|
||||
"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",
|
||||
"createdAt": "2026-02-03T07:00:00.000Z"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -1,21 +1,67 @@
|
||||
#!/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 re
|
||||
import hashlib
|
||||
import urllib.request
|
||||
import ssl
|
||||
import difflib
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from html.parser import HTMLParser
|
||||
|
||||
SCRIPT_DIR = Path(__file__).parent
|
||||
CONFIG_FILE = SCRIPT_DIR / "config.json"
|
||||
VERSIONS_FILE = SCRIPT_DIR / "versions.json"
|
||||
HASHES_FILE = SCRIPT_DIR / "hashes.json"
|
||||
SNAPSHOTS_DIR = SCRIPT_DIR / "snapshots"
|
||||
LOG_FILE = SCRIPT_DIR / "monitor.log"
|
||||
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.check_hostname = False
|
||||
SSL_CTX.verify_mode = ssl.CERT_NONE
|
||||
@@ -39,14 +85,58 @@ def save_json(path, data):
|
||||
def fetch_page(url, timeout=30):
|
||||
try:
|
||||
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:
|
||||
return resp.read().decode('utf-8', errors='ignore')
|
||||
return resp.read()
|
||||
except Exception as e:
|
||||
log(f"ERROR fetching {url}: {e}")
|
||||
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):
|
||||
"""Extrage data din numele fișierului (ex: D394_26092025.pdf -> 26.09.2025)"""
|
||||
# Pattern: _DDMMYYYY. sau _DDMMYYYY_ sau _YYYYMMDD
|
||||
@@ -69,10 +159,10 @@ def parse_date_from_filename(filename):
|
||||
return None
|
||||
|
||||
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 = {}
|
||||
|
||||
# Găsește primul link soft A (PDF)
|
||||
# Găsește PRIMUL link soft A (PDF) - versiunea curentă
|
||||
soft_a_match = re.search(
|
||||
r'<a[^>]+href=["\']([^"\']*\.pdf)["\'][^>]*>\s*soft\s*A\s*</a>',
|
||||
html, re.IGNORECASE
|
||||
@@ -84,7 +174,23 @@ def extract_versions(html):
|
||||
if 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_labeled = re.findall(
|
||||
r'<a[^>]+href=["\']([^"\']*\.zip)["\'][^>]*>\s*soft\s*J\s*-\s*([^<]+)',
|
||||
html, re.IGNORECASE
|
||||
)
|
||||
|
||||
if soft_j_labeled:
|
||||
# Pagină cu soft-uri denumite (bilanț)
|
||||
for url, label in soft_j_labeled:
|
||||
label = label.strip()
|
||||
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
|
||||
@@ -106,69 +212,104 @@ def extract_versions(html):
|
||||
|
||||
return versions
|
||||
|
||||
def format_date(d):
|
||||
"""Formatează data pentru afișare"""
|
||||
if not d:
|
||||
return "N/A"
|
||||
return d
|
||||
|
||||
def compare_versions(old, new, page_name):
|
||||
def compare_versions(old, new):
|
||||
"""Compară versiunile și returnează diferențele"""
|
||||
changes = []
|
||||
|
||||
fields = [
|
||||
('soft_a_date', 'Soft A'),
|
||||
('soft_j_date', 'Soft J'),
|
||||
('published', 'Publicat')
|
||||
]
|
||||
# Colectează toate cheile unice
|
||||
all_keys = set(old.keys()) | set(new.keys())
|
||||
date_keys = sorted([k for k in all_keys if k.endswith('_date') or k == 'published'])
|
||||
|
||||
for field, label in fields:
|
||||
old_val = old.get(field)
|
||||
new_val = new.get(field)
|
||||
for key in date_keys:
|
||||
old_val = old.get(key)
|
||||
new_val = new.get(key)
|
||||
|
||||
# Formatează label-ul
|
||||
label = key.replace('_date', '').replace('_', ' ').title()
|
||||
|
||||
if new_val and old_val != new_val:
|
||||
if old_val:
|
||||
changes.append(f"{label}: {old_val} → {new_val}")
|
||||
else:
|
||||
changes.append(f"{label}: {new_val} (nou)")
|
||||
changes.append(f"{label}: {new_val} (NOU)")
|
||||
|
||||
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"""
|
||||
page_id = page["id"]
|
||||
name = page["name"]
|
||||
url = page["url"]
|
||||
|
||||
html = fetch_page(url)
|
||||
if html is None:
|
||||
content = fetch_page(url)
|
||||
if content is 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)
|
||||
old_versions = saved_versions.get(page_id, {})
|
||||
|
||||
# Prima rulare - doar salvează, nu raportează
|
||||
if not old_versions:
|
||||
log(f"INIT: {page_id} - {new_versions}")
|
||||
# Încarcă snapshot-ul anterior
|
||||
old_text = load_snapshot(page_id)
|
||||
|
||||
# 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
|
||||
save_snapshot(page_id, new_text)
|
||||
return None
|
||||
|
||||
changes = compare_versions(old_versions, new_versions, name)
|
||||
saved_versions[page_id] = new_versions
|
||||
# Compară hash-uri
|
||||
hash_changed = new_hash != old_hash
|
||||
|
||||
if changes:
|
||||
log(f"CHANGES in {page_id}: {changes}")
|
||||
return {
|
||||
# Compară versiuni pentru detalii
|
||||
version_changes = compare_versions(old_versions, new_versions)
|
||||
|
||||
# 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,
|
||||
"name": name,
|
||||
"url": url,
|
||||
"changes": changes,
|
||||
"current": {
|
||||
"soft_a": new_versions.get('soft_a_date', 'N/A'),
|
||||
"soft_j": new_versions.get('soft_j_date', 'N/A')
|
||||
"changes": version_changes,
|
||||
"current": format_current_versions(new_versions)
|
||||
}
|
||||
}
|
||||
else:
|
||||
|
||||
if diff:
|
||||
result["diff"] = diff
|
||||
|
||||
return result
|
||||
|
||||
log(f"OK: {page_id}")
|
||||
return None
|
||||
|
||||
@@ -188,18 +329,20 @@ def update_dashboard_status(has_changes, changes_count):
|
||||
log(f"ERROR updating dashboard status: {e}")
|
||||
|
||||
def main():
|
||||
log("=== Starting ANAF monitor v2 ===")
|
||||
log("=== Starting ANAF monitor v2.1 ===")
|
||||
|
||||
config = load_json(CONFIG_FILE, {"pages": []})
|
||||
saved_versions = load_json(VERSIONS_FILE, {})
|
||||
saved_hashes = load_json(HASHES_FILE, {})
|
||||
|
||||
all_changes = []
|
||||
for page in config["pages"]:
|
||||
result = check_page(page, saved_versions)
|
||||
result = check_page(page, saved_versions, saved_hashes)
|
||||
if result:
|
||||
all_changes.append(result)
|
||||
|
||||
save_json(VERSIONS_FILE, saved_versions)
|
||||
save_json(HASHES_FILE, saved_hashes)
|
||||
|
||||
# Update dashboard status
|
||||
update_dashboard_status(len(all_changes) > 0, len(all_changes))
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
Reference in New Issue
Block a user