stage-1: project bootstrap

Structure, config loader, personality/tools/memory from clawd, venv, 22 tests passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MoltBot Service
2026-02-13 10:20:55 +00:00
commit f2973aa76f
374 changed files with 59557 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
{
"pages": [
{
"id": "D100",
"name": "Declarația 100 - Obligații de plată la bugetul de stat",
"url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/100.html"
},
{
"id": "D101",
"name": "Declarația 101 - Impozit pe profit",
"url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/101.html"
},
{
"id": "D300",
"name": "Declarația 300 - Decont TVA",
"url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/300.html"
},
{
"id": "D390",
"name": "Declarația 390 - Recapitulativă livrări/achiziții intracomunitare",
"url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/390.html"
},
{
"id": "D394",
"name": "Declarația 394 - Informativă livrări/achiziții",
"url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/394.html"
},
{
"id": "D205",
"name": "Declarația 205 - Informativă impozit la sursă",
"url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/205.html"
},
{
"id": "D406",
"name": "Declarația 406 - SAF-T",
"url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/406.html"
},
{
"id": "BILANT_2025",
"name": "Bilanț 31.12.2025 (S1002-S1005)",
"url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/situatiifinanciare/2025/1002_5_2025.html"
},
{
"id": "SIT_FIN_SEM_2025",
"name": "Raportări contabile semestriale 2025",
"url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/situatiifinanciare/2025/semestriale/1012_2025.html"
},
{
"id": "SIT_FIN_AN_2025",
"name": "Situații financiare anuale 2025",
"url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/situatiifinanciare/2025/1030_2025.html"
},
{
"id": "DESCARCARE_DECLARATII",
"name": "Pagina principală descărcare declarații",
"url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/descarcare_declaratii.htm"
}
]
}

View File

@@ -0,0 +1,14 @@
{
"D100": "44c03d855b36c32578b58bef6116e861c1d26ed6b038d732c23334b5d42f20de",
"D101": "937209d4785ca013cbcbe5a0d0aa8ba0e7033d3d8e6c121dadd8e38b20db8026",
"D300": "1349f3b1b4db7fe51ff82b0a91db44b16db83e843c56b0568e42ff3090a94f59",
"D394": "c4c4e62bda30032f12c17edf9a5087b6173a350ccb1fd750158978b3bd0acb7d",
"D406": "5a6712fab7b904ee659282af1b62f8b789aada5e3e4beb9fcce4ea3e0cab6ece",
"SIT_FIN_SEM_2025": "8164843431e6b703a38fbdedc7898ec6ae83559fe10f88663ba0b55f3091d5fe",
"SIT_FIN_AN_2025": "c00c39079482af8b7af6d32ba7b85c7d9e8cb25ebcbd6704adabd0192e1adca8",
"DESCARCARE_DECLARATII": "d66297abcfc2b3ad87f65e4a60c97ddd0a889f493bb7e7c8e6035ef39d55ec3f",
"D205": "f707104acc691cf79fbaa9a80c68bff4a285297f7dd3ab7b7a680715b54fd502",
"D390": "4726938ed5858ec735caefd947a7d182b6dc64009478332c4feabdb36412a84e",
"BILANT_2024": "fbb8d66c2e530d8798362992c6983e07e1250188228c758cb6da4cde4f955950",
"BILANT_2025": "9d66ffa59b8be06a5632b0f23a0354629f175ae5204398d7bb7a4c4734d5275a"
}

View File

@@ -0,0 +1,450 @@
[2026-01-29 00:38:12] === Starting ANAF monitor check ===
[2026-01-29 00:38:12] === Monitor check complete ===
[2026-01-29 00:38:32] === Starting ANAF monitor check ===
[2026-01-29 00:38:32] INIT: D100 - storing initial hash
[2026-01-29 00:38:32] INIT: D101 - storing initial hash
[2026-01-29 00:38:32] INIT: D200 - storing initial hash
[2026-01-29 00:38:32] INIT: D390 - storing initial hash
[2026-01-29 00:38:32] INIT: D406 - storing initial hash
[2026-01-29 00:38:32] INIT: EFACTURA - storing initial hash
[2026-01-29 00:38:32] INIT: SIT_FIN_SEM_2025 - storing initial hash
[2026-01-29 00:38:32] INIT: SIT_FIN_AN_2025 - storing initial hash
[2026-01-29 00:38:33] INIT: SIT_FIN_AN_2024 - storing initial hash
[2026-01-29 00:38:33] INIT: DESCARCARE_DECLARATII - storing initial hash
[2026-01-29 00:38:33] === Monitor check complete ===
[2026-01-29 00:46:30] === Starting ANAF monitor check ===
[2026-01-29 00:46:30] INIT: D100 - storing initial hash
[2026-01-29 00:46:30] INIT: D101 - storing initial hash
[2026-01-29 00:46:30] INIT: D300 - storing initial hash
[2026-01-29 00:46:30] INIT: D394 - storing initial hash
[2026-01-29 00:46:30] INIT: D406 - storing initial hash
[2026-01-29 00:46:30] INIT: SIT_FIN_SEM_2025 - storing initial hash
[2026-01-29 00:46:30] INIT: SIT_FIN_AN_2025 - storing initial hash
[2026-01-29 00:46:31] INIT: DESCARCARE_DECLARATII - storing initial hash
[2026-01-29 00:46:31] === Monitor check complete ===
[2026-01-29 12:31:52] === Starting ANAF monitor check ===
[2026-01-29 12:31:52] OK: D100 - no changes
[2026-01-29 12:31:52] OK: D101 - no changes
[2026-01-29 12:31:53] OK: D300 - no changes
[2026-01-29 12:31:53] OK: D394 - no changes
[2026-01-29 12:31:53] INIT: D205 - storing initial hash
[2026-01-29 12:31:53] OK: D406 - no changes
[2026-01-29 12:31:53] OK: SIT_FIN_SEM_2025 - no changes
[2026-01-29 12:31:53] OK: SIT_FIN_AN_2025 - no changes
[2026-01-29 12:31:53] OK: DESCARCARE_DECLARATII - no changes
[2026-01-29 12:31:53] === Monitor check complete ===
[2026-01-29 12:32:10] === Starting ANAF monitor check ===
[2026-01-29 12:32:10] OK: D100 - no changes
[2026-01-29 12:32:10] OK: D101 - no changes
[2026-01-29 12:32:10] OK: D300 - no changes
[2026-01-29 12:32:11] INIT: D390 - storing initial hash
[2026-01-29 12:32:11] OK: D394 - no changes
[2026-01-29 12:32:11] OK: D205 - no changes
[2026-01-29 12:32:11] OK: D406 - no changes
[2026-01-29 12:32:13] OK: SIT_FIN_SEM_2025 - no changes
[2026-01-29 12:32:13] OK: SIT_FIN_AN_2025 - no changes
[2026-01-29 12:32:14] OK: DESCARCARE_DECLARATII - no changes
[2026-01-29 12:32:14] === Monitor check complete ===
[2026-01-29 12:51:27] === Starting ANAF monitor check ===
[2026-01-29 12:51:27] OK: D100 - no changes
[2026-01-29 12:51:27] OK: D101 - no changes
[2026-01-29 12:51:27] OK: D300 - no changes
[2026-01-29 12:51:27] OK: D390 - no changes
[2026-01-29 12:51:28] OK: D394 - no changes
[2026-01-29 12:51:28] OK: D205 - no changes
[2026-01-29 12:51:28] OK: D406 - no changes
[2026-01-29 12:51:28] INIT: BILANT_2024 - storing initial hash
[2026-01-29 12:51:28] OK: SIT_FIN_SEM_2025 - no changes
[2026-01-29 12:51:28] OK: SIT_FIN_AN_2025 - no changes
[2026-01-29 12:51:28] OK: DESCARCARE_DECLARATII - no changes
[2026-01-29 12:51:28] === Monitor check complete ===
[2026-01-29 12:51:53] === Starting ANAF monitor check ===
[2026-01-29 12:51:53] OK: D100 - no changes
[2026-01-29 12:51:53] OK: D101 - no changes
[2026-01-29 12:51:53] OK: D300 - no changes
[2026-01-29 12:51:53] OK: D390 - no changes
[2026-01-29 12:51:53] OK: D394 - no changes
[2026-01-29 12:51:53] OK: D205 - no changes
[2026-01-29 12:51:53] OK: D406 - no changes
[2026-01-29 12:51:53] INIT: BILANT_2025 - storing initial hash
[2026-01-29 12:51:53] OK: SIT_FIN_SEM_2025 - no changes
[2026-01-29 12:51:53] OK: SIT_FIN_AN_2025 - no changes
[2026-01-29 12:51:54] OK: DESCARCARE_DECLARATII - no changes
[2026-01-29 12:51:54] === Monitor check complete ===
[2026-01-29 12:55:43] === Starting ANAF monitor v2 check ===
[2026-01-29 12:55:43] OK: D100 - no changes
[2026-01-29 12:55:43] OK: D101 - no changes
[2026-01-29 12:55:43] OK: D300 - no changes
[2026-01-29 12:55:43] OK: D390 - no changes
[2026-01-29 12:55:44] OK: D394 - no changes
[2026-01-29 12:55:44] OK: D205 - no changes
[2026-01-29 12:55:44] OK: D406 - no changes
[2026-01-29 12:55:44] OK: BILANT_2025 - no changes
[2026-01-29 12:55:44] OK: SIT_FIN_SEM_2025 - no changes
[2026-01-29 12:55:44] OK: SIT_FIN_AN_2025 - no changes
[2026-01-29 12:55:44] OK: DESCARCARE_DECLARATII - no changes
[2026-01-29 12:55:44] === Monitor check complete ===
[2026-01-29 12:56:25] === Starting ANAF monitor v2 ===
[2026-01-29 12:56:27] INIT: 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'}
[2026-01-29 12:56:27] INIT: 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'}
[2026-01-29 12:56:27] INIT: 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'}
[2026-01-29 12:56:27] INIT: 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'}
[2026-01-29 12:56:27] INIT: 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'}
[2026-01-29 12:56:27] INIT: 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'}
[2026-01-29 12:56:27] INIT: 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'}
[2026-01-29 12:56:27] INIT: 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'}
[2026-01-29 12:56:28] INIT: 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'}
[2026-01-29 12:56:28] INIT: 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'}
[2026-01-29 12:56:28] INIT: DESCARCARE_DECLARATII - {}
[2026-01-29 12:56:28] === Monitor complete ===
[2026-01-29 13:28:32] === Starting ANAF monitor v2 ===
[2026-01-29 13:28:32] OK: D100
[2026-01-29 13:28:32] OK: D101
[2026-01-29 13:28:32] OK: D300
[2026-01-29 13:28:32] OK: D390
[2026-01-29 13:28:32] OK: D394
[2026-01-29 13:28:33] OK: D205
[2026-01-29 13:28:33] OK: D406
[2026-01-29 13:28:33] OK: BILANT_2025
[2026-01-29 13:28:33] OK: SIT_FIN_SEM_2025
[2026-01-29 13:28:33] OK: SIT_FIN_AN_2025
[2026-01-29 13:28:33] INIT: DESCARCARE_DECLARATII - {}
[2026-01-29 13:28:33] === Monitor complete ===
[2026-01-31 06:30:14] === Starting ANAF monitor v2 ===
[2026-01-31 06:30:15] OK: D100
[2026-01-31 06:30:15] OK: D101
[2026-01-31 06:30:15] OK: D300
[2026-01-31 06:30:15] OK: D390
[2026-01-31 06:30:16] OK: D394
[2026-01-31 06:30:16] OK: D205
[2026-01-31 06:30:17] OK: D406
[2026-01-31 06:30:17] OK: BILANT_2025
[2026-01-31 06:30:17] OK: SIT_FIN_SEM_2025
[2026-01-31 06:30:17] OK: SIT_FIN_AN_2025
[2026-01-31 06:30:17] INIT: DESCARCARE_DECLARATII - {}
[2026-01-31 06:30:17] === Monitor complete ===
[2026-01-31 13:43:41] === Starting ANAF monitor v2 ===
[2026-01-31 13:43:41] OK: D100
[2026-01-31 13:43:41] OK: D101
[2026-01-31 13:43:41] OK: D300
[2026-01-31 13:43:42] OK: D390
[2026-01-31 13:43:42] OK: D394
[2026-01-31 13:43:42] OK: D205
[2026-01-31 13:43:42] OK: D406
[2026-01-31 13:43:42] OK: BILANT_2025
[2026-01-31 13:43:42] OK: SIT_FIN_SEM_2025
[2026-01-31 13:43:42] OK: SIT_FIN_AN_2025
[2026-01-31 13:43:42] INIT: DESCARCARE_DECLARATII - {}
[2026-01-31 13:43:42] === Monitor complete ===
[2026-02-01 06:30:07] === Starting ANAF monitor v2 ===
[2026-02-01 06:30:07] OK: D100
[2026-02-01 06:30:07] OK: D101
[2026-02-01 06:30:07] OK: D300
[2026-02-01 06:30:07] OK: D390
[2026-02-01 06:30:08] OK: D394
[2026-02-01 06:30:08] OK: D205
[2026-02-01 06:30:08] OK: D406
[2026-02-01 06:30:08] OK: BILANT_2025
[2026-02-01 06:30:08] OK: SIT_FIN_SEM_2025
[2026-02-01 06:30:08] OK: SIT_FIN_AN_2025
[2026-02-01 06:30:08] INIT: DESCARCARE_DECLARATII - {}
[2026-02-01 06:30:08] === Monitor complete ===
[2026-02-02 06:30:06] === Starting ANAF monitor v2 ===
[2026-02-02 06:30:06] OK: D100
[2026-02-02 06:30:06] OK: D101
[2026-02-02 06:30:07] OK: D300
[2026-02-02 06:30:07] OK: D390
[2026-02-02 06:30:07] OK: D394
[2026-02-02 06:30:07] OK: D205
[2026-02-02 06:30:07] OK: D406
[2026-02-02 06:30:07] OK: BILANT_2025
[2026-02-02 06:30:08] OK: SIT_FIN_SEM_2025
[2026-02-02 06:30:08] OK: SIT_FIN_AN_2025
[2026-02-02 06:30:08] INIT: DESCARCARE_DECLARATII - {}
[2026-02-02 06:30:08] === Monitor complete ===
[2026-02-02 07:11:47] === Starting ANAF monitor v2 ===
[2026-02-02 07:11:47] OK: D100
[2026-02-02 07:11:47] OK: D101
[2026-02-02 07:11:47] OK: D300
[2026-02-02 07:11:47] OK: D390
[2026-02-02 07:11:48] OK: D394
[2026-02-02 07:11:48] OK: D205
[2026-02-02 07:11:48] OK: D406
[2026-02-02 07:11:48] OK: BILANT_2025
[2026-02-02 07:11:48] OK: SIT_FIN_SEM_2025
[2026-02-02 07:11:48] OK: SIT_FIN_AN_2025
[2026-02-02 07:11:48] INIT: DESCARCARE_DECLARATII - {}
[2026-02-02 07:11:48] === Monitor complete ===
[2026-02-02 07:52:10] === Starting ANAF monitor v2 ===
[2026-02-02 07:52:10] OK: D100
[2026-02-02 07:52:10] OK: D101
[2026-02-02 07:52:10] OK: D300
[2026-02-02 07:52:10] OK: D390
[2026-02-02 07:52:10] OK: D394
[2026-02-02 07:52:10] OK: D205
[2026-02-02 07:52:10] OK: D406
[2026-02-02 07:52:10] OK: BILANT_2025
[2026-02-02 07:52:10] OK: SIT_FIN_SEM_2025
[2026-02-02 07:52:11] OK: SIT_FIN_AN_2025
[2026-02-02 07:52:11] INIT: DESCARCARE_DECLARATII - {}
[2026-02-02 07:52:11] === Monitor complete ===
[2026-02-03 21:34:59] === Starting ANAF monitor v2 ===
[2026-02-03 21:35:00] CHANGES in D100: ['Soft A: 26.01.2026 → 02.02.2026']
[2026-02-03 21:35:00] OK: D101
[2026-02-03 21:35:00] OK: D300
[2026-02-03 21:35:00] OK: D390
[2026-02-03 21:35:00] OK: D394
[2026-02-03 21:35:00] OK: D205
[2026-02-03 21:35:00] OK: D406
[2026-02-03 21:35:00] OK: BILANT_2025
[2026-02-03 21:35:00] OK: SIT_FIN_SEM_2025
[2026-02-03 21:35:01] OK: SIT_FIN_AN_2025
[2026-02-03 21:35:01] INIT: DESCARCARE_DECLARATII - {}
[2026-02-03 21:35:01] === Monitor complete ===
[2026-02-03 21:37:42] === Starting ANAF monitor v2.1 ===
[2026-02-03 21:37:43] CHANGES in D100: ['Soft A 10: 17.11.2016 (NOU)', 'Soft A 11: 15.04.2016 (NOU)', 'Soft A 12: 19.01.2015 (NOU)', 'Soft A 14: 28.12.2012 (NOU)', 'Soft A 2: 25.01.2024 (NOU)', 'Soft A 3: 20.01.2022 (NOU)', 'Soft A 4: 23.10.2020 (NOU)', 'Soft A 5: 30.09.2020 (NOU)', 'Soft A 6: 03.12.2019 (NOU)', 'Soft A 7: 01.02.2018 (NOU)', 'Soft A 8: 17.10.2017 (NOU)', 'Soft A 9: 01.08.2017 (NOU)', 'Soft J 10: 19.01.2015 (NOU)', 'Soft J 2: 17.01.2024 (NOU)', 'Soft J 3: 20.01.2022 (NOU)', 'Soft J 4: 30.09.2020 (NOU)', 'Soft J 5: 28.01.2020 (NOU)', 'Soft J 6: 01.02.2018 (NOU)', 'Soft J 7: 17.10.2017 (NOU)', 'Soft J 8: 01.08.2017 (NOU)', 'Soft J 9: 21.03.2016 (NOU)']
[2026-02-03 21:37:43] OK: D101
[2026-02-03 21:37:43] OK: D300
[2026-02-03 21:37:43] OK: D390
[2026-02-03 21:37:43] OK: D394
[2026-02-03 21:37:43] OK: D205
[2026-02-03 21:37:43] OK: D406
[2026-02-03 21:37:43] CHANGES in BILANT_2025: ['Soft J S1002: 28.01.2026 (NOU)', 'Soft J S1003: 04.02.2025 (NOU)', 'Soft J S1004: 04.02.2025 (NOU)']
[2026-02-03 21:37:43] OK: SIT_FIN_SEM_2025
[2026-02-03 21:37:44] OK: SIT_FIN_AN_2025
[2026-02-03 21:37:44] OK: DESCARCARE_DECLARATII
[2026-02-03 21:37:44] === Monitor complete ===
[2026-02-03 21:38:07] === Starting ANAF monitor v2.1 ===
[2026-02-03 21:38:07] OK: D100
[2026-02-03 21:38:08] OK: D101
[2026-02-03 21:38:08] OK: D300
[2026-02-03 21:38:08] OK: D390
[2026-02-03 21:38:08] OK: D394
[2026-02-03 21:38:08] OK: D205
[2026-02-03 21:38:08] OK: D406
[2026-02-03 21:38:08] OK: BILANT_2025
[2026-02-03 21:38:08] OK: SIT_FIN_SEM_2025
[2026-02-03 21:38:08] OK: SIT_FIN_AN_2025
[2026-02-03 21:38:08] OK: DESCARCARE_DECLARATII
[2026-02-03 21:38:08] === Monitor complete ===
[2026-02-03 21:39:16] === Starting ANAF monitor v2.1 ===
[2026-02-03 21:39:16] OK: D100
[2026-02-03 21:39:16] OK: D101
[2026-02-03 21:39:17] OK: D300
[2026-02-03 21:39:17] OK: D390
[2026-02-03 21:39:17] OK: D394
[2026-02-03 21:39:17] OK: D205
[2026-02-03 21:39:17] OK: D406
[2026-02-03 21:39:17] OK: BILANT_2025
[2026-02-03 21:39:17] OK: SIT_FIN_SEM_2025
[2026-02-03 21:39:17] OK: SIT_FIN_AN_2025
[2026-02-03 21:39:17] OK: DESCARCARE_DECLARATII
[2026-02-03 21:39:17] === Monitor complete ===
[2026-02-04 08:00:20] === Starting ANAF monitor v2.1 ===
[2026-02-04 08:00:20] OK: D100
[2026-02-04 08:00:20] OK: D101
[2026-02-04 08:00:20] OK: D300
[2026-02-04 08:00:20] OK: D390
[2026-02-04 08:00:20] OK: D394
[2026-02-04 08:00:20] OK: D205
[2026-02-04 08:00:20] OK: D406
[2026-02-04 08:00:21] OK: BILANT_2025
[2026-02-04 08:00:21] OK: SIT_FIN_SEM_2025
[2026-02-04 08:00:21] OK: SIT_FIN_AN_2025
[2026-02-04 08:00:21] OK: DESCARCARE_DECLARATII
[2026-02-04 08:00:21] === Monitor complete ===
[2026-02-05 14:00:09] === Starting ANAF monitor v2.1 ===
[2026-02-05 14:00:09] OK: D100
[2026-02-05 14:00:09] OK: D101
[2026-02-05 14:00:09] OK: D300
[2026-02-05 14:00:09] OK: D390
[2026-02-05 14:00:09] OK: D394
[2026-02-05 14:00:09] OK: D205
[2026-02-05 14:00:10] OK: D406
[2026-02-05 14:00:10] HASH CHANGED in BILANT_2025 (no version changes detected)
[2026-02-05 14:00:10] OK: SIT_FIN_SEM_2025
[2026-02-05 14:00:10] HASH CHANGED in SIT_FIN_AN_2025 (no version changes detected)
[2026-02-05 14:00:10] OK: DESCARCARE_DECLARATII
[2026-02-05 14:00:10] === Monitor complete ===
[2026-02-06 08:00:10] === Starting ANAF monitor v2.1 ===
[2026-02-06 08:00:10] OK: D100
[2026-02-06 08:00:10] OK: D101
[2026-02-06 08:00:10] OK: D300
[2026-02-06 08:00:10] OK: D390
[2026-02-06 08:00:10] OK: D394
[2026-02-06 08:00:10] OK: D205
[2026-02-06 08:00:10] OK: D406
[2026-02-06 08:00:11] OK: BILANT_2025
[2026-02-06 08:00:11] OK: SIT_FIN_SEM_2025
[2026-02-06 08:00:11] OK: SIT_FIN_AN_2025
[2026-02-06 08:00:11] OK: DESCARCARE_DECLARATII
[2026-02-06 08:00:11] === Monitor complete ===
[2026-02-06 14:00:35] === Starting ANAF monitor v2.1 ===
[2026-02-06 14:00:35] OK: D100
[2026-02-06 14:00:35] OK: D101
[2026-02-06 14:00:35] OK: D300
[2026-02-06 14:00:35] OK: D390
[2026-02-06 14:00:35] OK: D394
[2026-02-06 14:00:36] OK: D205
[2026-02-06 14:00:36] OK: D406
[2026-02-06 14:00:36] OK: BILANT_2025
[2026-02-06 14:00:36] OK: SIT_FIN_SEM_2025
[2026-02-06 14:00:36] OK: SIT_FIN_AN_2025
[2026-02-06 14:00:36] OK: DESCARCARE_DECLARATII
[2026-02-06 14:00:36] === Monitor complete ===
[2026-02-09 08:00:18] === Starting ANAF monitor v2.1 ===
[2026-02-09 08:00:19] OK: D100
[2026-02-09 08:00:19] OK: D101
[2026-02-09 08:00:19] OK: D300
[2026-02-09 08:00:19] OK: D390
[2026-02-09 08:00:19] OK: D394
[2026-02-09 08:00:19] OK: D205
[2026-02-09 08:00:19] OK: D406
[2026-02-09 08:00:19] OK: BILANT_2025
[2026-02-09 08:00:20] OK: SIT_FIN_SEM_2025
[2026-02-09 08:00:20] OK: SIT_FIN_AN_2025
[2026-02-09 08:00:20] OK: DESCARCARE_DECLARATII
[2026-02-09 08:00:20] === Monitor complete ===
[2026-02-09 14:00:20] === Starting ANAF monitor v2.1 ===
[2026-02-09 14:00:20] CHANGES in D100: ['Soft A: 02.02.2026 → 09.02.2026']
[2026-02-09 14:00:20] OK: D101
[2026-02-09 14:00:20] OK: D300
[2026-02-09 14:00:20] OK: D390
[2026-02-09 14:00:20] OK: D394
[2026-02-09 14:00:21] OK: D205
[2026-02-09 14:00:21] OK: D406
[2026-02-09 14:00:21] OK: BILANT_2025
[2026-02-09 14:00:21] OK: SIT_FIN_SEM_2025
[2026-02-09 14:00:21] OK: SIT_FIN_AN_2025
[2026-02-09 14:00:21] OK: DESCARCARE_DECLARATII
[2026-02-09 14:00:21] === Monitor complete ===
[2026-02-10 08:00:17] === Starting ANAF monitor v2.1 ===
[2026-02-10 08:00:17] OK: D100
[2026-02-10 08:00:17] OK: D101
[2026-02-10 08:00:17] OK: D300
[2026-02-10 08:00:17] OK: D390
[2026-02-10 08:00:17] OK: D394
[2026-02-10 08:00:17] OK: D205
[2026-02-10 08:00:17] OK: D406
[2026-02-10 08:00:18] OK: BILANT_2025
[2026-02-10 08:00:18] OK: SIT_FIN_SEM_2025
[2026-02-10 08:00:18] OK: SIT_FIN_AN_2025
[2026-02-10 08:00:18] OK: DESCARCARE_DECLARATII
[2026-02-10 08:00:18] === Monitor complete ===
[2026-02-10 12:39:04] === Starting ANAF monitor v2.1 ===
[2026-02-10 12:39:04] CHANGES in D100: ['Soft A: 09.02.2026 → 10.02.2026']
[2026-02-10 12:39:04] OK: D101
[2026-02-10 12:39:04] OK: D300
[2026-02-10 12:39:05] OK: D390
[2026-02-10 12:39:05] OK: D394
[2026-02-10 12:39:05] OK: D205
[2026-02-10 12:39:05] OK: D406
[2026-02-10 12:39:05] OK: BILANT_2025
[2026-02-10 12:39:05] OK: SIT_FIN_SEM_2025
[2026-02-10 12:39:06] OK: SIT_FIN_AN_2025
[2026-02-10 12:39:06] OK: DESCARCARE_DECLARATII
[2026-02-10 12:39:06] === Monitor complete ===
[2026-02-10 14:00:25] === Starting ANAF monitor v2.1 ===
[2026-02-10 14:00:25] OK: D100
[2026-02-10 14:00:25] OK: D101
[2026-02-10 14:00:25] OK: D300
[2026-02-10 14:00:25] OK: D390
[2026-02-10 14:00:25] OK: D394
[2026-02-10 14:00:25] OK: D205
[2026-02-10 14:00:25] OK: D406
[2026-02-10 14:00:25] OK: BILANT_2025
[2026-02-10 14:00:25] OK: SIT_FIN_SEM_2025
[2026-02-10 14:00:26] OK: SIT_FIN_AN_2025
[2026-02-10 14:00:26] OK: DESCARCARE_DECLARATII
[2026-02-10 14:00:26] === Monitor complete ===
[2026-02-11 08:00:21] === Starting ANAF monitor v2.1 ===
[2026-02-11 08:00:21] OK: D100
[2026-02-11 08:00:21] OK: D101
[2026-02-11 08:00:21] OK: D300
[2026-02-11 08:00:21] OK: D390
[2026-02-11 08:00:22] OK: D394
[2026-02-11 08:00:22] OK: D205
[2026-02-11 08:00:22] OK: D406
[2026-02-11 08:00:22] CHANGES in BILANT_2025: ['Soft J S1003: 04.02.2025 → 10.02.2026']
[2026-02-11 08:00:22] OK: SIT_FIN_SEM_2025
[2026-02-11 08:00:22] OK: SIT_FIN_AN_2025
[2026-02-11 08:00:22] OK: DESCARCARE_DECLARATII
[2026-02-11 08:00:22] === Monitor complete ===
[2026-02-11 14:00:17] === Starting ANAF monitor v2.1 ===
[2026-02-11 14:00:17] OK: D100
[2026-02-11 14:00:17] OK: D101
[2026-02-11 14:00:17] CHANGES in D300: ['Soft A: 16.12.2025 → 11.02.2026', 'Soft J: 10.09.2025 → 11.02.2026']
[2026-02-11 14:00:17] OK: D390
[2026-02-11 14:00:17] OK: D394
[2026-02-11 14:00:17] OK: D205
[2026-02-11 14:00:18] CHANGES in D406: ['Soft J: 30.10.2025 → 11.02.2026']
[2026-02-11 14:00:18] CHANGES in BILANT_2025: ['Soft A: 27.01.2026 → 11.02.2026']
[2026-02-11 14:00:18] OK: SIT_FIN_SEM_2025
[2026-02-11 14:00:18] OK: SIT_FIN_AN_2025
[2026-02-11 14:00:18] OK: DESCARCARE_DECLARATII
[2026-02-11 14:00:18] === Monitor complete ===
[2026-02-11 16:23:14] === Starting ANAF monitor v2.1 ===
[2026-02-11 16:23:14] OK: D100
[2026-02-11 16:23:14] OK: D101
[2026-02-11 16:23:15] HASH CHANGED in D300 (no version changes detected)
[2026-02-11 16:23:15] OK: D390
[2026-02-11 16:23:15] OK: D394
[2026-02-11 16:23:15] OK: D205
[2026-02-11 16:23:15] OK: D406
[2026-02-11 16:23:15] OK: BILANT_2025
[2026-02-11 16:23:15] OK: SIT_FIN_SEM_2025
[2026-02-11 16:23:15] OK: SIT_FIN_AN_2025
[2026-02-11 16:23:15] OK: DESCARCARE_DECLARATII
[2026-02-11 16:23:15] === Monitor complete ===
[2026-02-12 08:00:23] === Starting ANAF monitor v2.1 ===
[2026-02-12 08:00:24] OK: D100
[2026-02-12 08:00:24] OK: D101
[2026-02-12 08:00:24] OK: D300
[2026-02-12 08:00:24] OK: D390
[2026-02-12 08:00:24] OK: D394
[2026-02-12 08:00:25] OK: D205
[2026-02-12 08:00:25] OK: D406
[2026-02-12 08:00:28] OK: BILANT_2025
[2026-02-12 08:00:28] OK: SIT_FIN_SEM_2025
[2026-02-12 08:00:28] OK: SIT_FIN_AN_2025
[2026-02-12 08:00:28] OK: DESCARCARE_DECLARATII
[2026-02-12 08:00:28] === Monitor complete ===
[2026-02-12 14:00:22] === Starting ANAF monitor v2.1 ===
[2026-02-12 14:00:22] OK: D100
[2026-02-12 14:00:22] OK: D101
[2026-02-12 14:00:22] CHANGES in D300: ['Soft A: 11.02.2026 → 12.02.2026']
[2026-02-12 14:00:22] OK: D390
[2026-02-12 14:00:22] OK: D394
[2026-02-12 14:00:22] OK: D205
[2026-02-12 14:00:23] OK: D406
[2026-02-12 14:00:23] HASH CHANGED in BILANT_2025 (no version changes detected)
[2026-02-12 14:00:23] OK: SIT_FIN_SEM_2025
[2026-02-12 14:00:23] HASH CHANGED in SIT_FIN_AN_2025 (no version changes detected)
[2026-02-12 14:00:23] OK: DESCARCARE_DECLARATII
[2026-02-12 14:00:23] === Monitor complete ===
[2026-02-12 14:46:11] === Starting ANAF monitor v2.1 ===
[2026-02-12 14:46:11] OK: D100
[2026-02-12 14:46:11] OK: D101
[2026-02-12 14:46:11] OK: D300
[2026-02-12 14:46:11] OK: D390
[2026-02-12 14:46:11] OK: D394
[2026-02-12 14:46:11] OK: D205
[2026-02-12 14:46:12] OK: D406
[2026-02-12 14:46:12] OK: BILANT_2025
[2026-02-12 14:46:12] OK: SIT_FIN_SEM_2025
[2026-02-12 14:46:12] OK: SIT_FIN_AN_2025
[2026-02-12 14:46:12] OK: DESCARCARE_DECLARATII
[2026-02-12 14:46:12] === Monitor complete ===
[2026-02-13 08:00:14] === Starting ANAF monitor v2.1 ===
[2026-02-13 08:00:14] OK: D100
[2026-02-13 08:00:14] OK: D101
[2026-02-13 08:00:14] OK: D300
[2026-02-13 08:00:14] OK: D390
[2026-02-13 08:00:15] OK: D394
[2026-02-13 08:00:16] OK: D205
[2026-02-13 08:00:16] OK: D406
[2026-02-13 08:00:16] HASH CHANGED in BILANT_2025 (no version changes detected)
[2026-02-13 08:00:16] OK: SIT_FIN_SEM_2025
[2026-02-13 08:00:16] HASH CHANGED in SIT_FIN_AN_2025 (no version changes detected)
[2026-02-13 08:00:16] OK: DESCARCARE_DECLARATII
[2026-02-13 08:00:16] === Monitor complete ===

View File

@@ -0,0 +1,372 @@
#!/usr/bin/env python3
"""
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
def log(msg):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(LOG_FILE, "a") as f:
f.write(f"[{timestamp}] {msg}\n")
def load_json(path, default=None):
try:
with open(path) as f:
return json.load(f)
except:
return default if default is not None else {}
def save_json(path, data):
with open(path, "w") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
def fetch_page(url, timeout=30):
try:
req = urllib.request.Request(url, headers={
'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()
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
match = re.search(r'_(\d{8})[\._]', filename)
if match:
d = match.group(1)
# Verifică dacă e DDMMYYYY sau YYYYMMDD
if int(d[:2]) <= 31 and int(d[2:4]) <= 12:
return f"{d[:2]}.{d[2:4]}.{d[4:]}"
elif int(d[4:6]) <= 12 and int(d[6:]) <= 31:
return f"{d[6:]}.{d[4:6]}.{d[:4]}"
# Pattern: _DDMMYY
match = re.search(r'_(\d{6})[\._]', filename)
if match:
d = match.group(1)
if int(d[:2]) <= 31 and int(d[2:4]) <= 12:
return f"{d[:2]}.{d[2:4]}.20{d[4:]}"
return None
def extract_versions(html):
"""Extrage soft A/J din HTML - primul generic + toate cele cu label (S1002, etc.)"""
versions = {}
# 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
)
if soft_a_match:
url = soft_a_match.group(1)
versions['soft_a_url'] = url
date = parse_date_from_filename(url)
if date:
versions['soft_a_date'] = date
# 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
)
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
publish_match = re.search(
r'publicat\s+[îi]n\s*(?:data\s+de\s*)?(\d{2}[./]\d{2}[./]\d{4})',
html, re.IGNORECASE
)
if publish_match:
versions['published'] = publish_match.group(1).replace('/', '.')
return versions
def compare_versions(old, new):
"""Compară versiunile și returnează diferențele"""
changes = []
# 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 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)")
return changes
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"]
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, {})
# Î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
# Compară hash-uri
hash_changed = new_hash != old_hash
# 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"]
result = {
"id": page_id,
"name": name,
"url": url,
"changes": version_changes,
"current": format_current_versions(new_versions)
}
if diff:
result["diff"] = diff
return result
log(f"OK: {page_id}")
return None
def update_dashboard_status(has_changes, changes_count, changes_list=None):
"""Actualizează status.json pentru dashboard"""
try:
status = load_json(DASHBOARD_STATUS, {})
anaf_status = {
'ok': not has_changes,
'status': 'MODIFICĂRI' if has_changes else 'OK',
'message': f'{changes_count} modificări detectate' if has_changes else 'Nicio modificare detectată',
'lastCheck': datetime.now().strftime('%d %b %Y, %H:%M'),
'changesCount': changes_count
}
# Adaugă detaliile modificărilor pentru dashboard
if has_changes and changes_list:
anaf_status['changes'] = []
for change in changes_list:
change_detail = {
'name': change.get('name', ''),
'url': change.get('url', ''),
'summary': []
}
# Ia primele 3 modificări ca rezumat
if change.get('changes'):
change_detail['summary'] = change['changes'][:3]
anaf_status['changes'].append(change_detail)
status['anaf'] = anaf_status
save_json(DASHBOARD_STATUS, status)
except Exception as e:
log(f"ERROR updating dashboard status: {e}")
def main():
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, 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), all_changes)
log("=== Monitor complete ===")
print(json.dumps({"changes": all_changes}, ensure_ascii=False, indent=2))
return len(all_changes)
if __name__ == "__main__":
exit(main())

View File

@@ -0,0 +1,40 @@
S1002-S1003-S1004-S1005
S1002-S1003-S1004-S1005
S1011
S1019
S1025
S1030
S1039
S1040-S1041
S1042
S1046
S1051-S1052-S1053-S1054
Tabel
codificări
tipuri de situaţii financiare şi raportări anuale
Denumire formular
Programe asistenţă
Instrucţiuni/ Documentaţie
PDF
JAVA
Atenție! Momentan se pot depune doar S1002,S1003 şi S1005.
S1002-S1005
Situaţii financiare anuale la
31 decembrie 2025
întocmite de către entităţile al căror exerciţiu financiar coincide cu anul calendaristic, prevăzute la pct. 1.1 din Anexa 1 la
OMF nr. 2036/23.12.2025
privind principalele aspecte legate de întocmirea şi depunerea situaţiilor financiare anuale şi a raportărilor contabile anuale ale operatorilor economici la Agentia Nationala de Administrare Fiscala. Potrivit art. 185 din Legea societăţilor nr. 31/1990, republicată, cu modificările şi completările ulterioare, consiliul de administraţie, respectiv directoratul, este obligat să depună la ANAF numai în format electronic pe portalul www.e-guvernare.ro, având ataşată o semnătură electronică extinsă, situaţiile financiare anuale, raportul lor, raportul cenzorilor sau raportul auditorilor financiari, după caz. Formatul electronic al situaţiilor financiare anuale la 31 decembrie 2023, generat prin programele de asistenţă, constă într-un fişier PDF având ataşat un fişier xml (care conţine formularele cod 10, cod 20, cod 30 şi cod 40), la care trebuie ataşat şi un fişier cu extensia zip.
Fişierul cu extensia zip va conţine prima pagină din situaţiile financiare anuale listată cu ajutorul programului de asistenţă elaborat de Ministerul Finanţelor, semnată, potrivit legii, precum şi documentele cerute de lege (de exemplu: note explicative la situaţiile financiare anuale, situaţia modificărilor capitalului propriu şi situaţia fluxurilor de numerar, după caz; raportul administratorilor; raportul de audit sau raportul comisiei de cenzori, după caz; propunerea de distribuire a profitului sau de acoperire a pierderii contabile; declaraţia scrisă a persoanelor prevăzute la art. 10 alin. (1) din legea contabilităţii, prin care îşi asumă răspunderea pentru întocmirea situaţiilor financiare anuale), aşa cum acestea sunt întocmite de entităţi, toate acestea fiind scanate, alb-negru, lizibil şi cu o rezoluţie care să permită încadrarea în limita a 9,5 MB a fişierului PDF la care este ataşat fişierul zip. Fişierele zip ataşate situaţiilor financiare anuale nu vor conţine parolă.
- publicat în data de
28.01.2026
soft A
actualizat în data
11.02.2026
soft J - S1002
soft J - S1003
soft J - S1005
Schema XSD 1002
Schema XSD 1003
Schema XSD 1005
Structura
*softul J se adresează doar contribuabililor care îşi generează fişierul xml din aplicaţiile informatice proprii

View File

@@ -0,0 +1,263 @@

100
017
060
093
100
101
101 G
104
106
107
108
109
110
112
114
119
120
130
177
179
180
182
205
207
216
217
222
300
301
307
311
390
392
393
394
395
397
398
401
402
403
404
405
406
407
700
708
710
711
900
901
Denumire formular
Programe asistenţă
Instrucţiuni/ Documentaţie
PDF
JAVA
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform
OPANAF nr. 57/19.01.2026,
valabil începand cu
01/2024 - publicat în data de 09.02.2024
soft A
actualizat în data de
10.02.2026
soft J*
actualizat în data de
23.01.2026
Anexa
validări
actualizat în data de
09.02.2026
Schema
XSD
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform OPANAF nr. 64/18.01.2022, OPANAF
nr. 237/18.02.2022, OPANAF nr. 449/23.03.2022,
OPANAF nr. 1150/ 2022, OPANAF nr. 1341/2022, OPANAF nr. 1542/23.08.2022,
OPANAF nr. 1635/12.09.2022
, OPANAF nr. 172/08.02.2023,OPANAF nr. 188/10.02.2023,
OPANAF nr. 1090/07.07.2023,OPANAF nr. 1857/06.11.2023
valabil începand cu
12/2021
- publicat în data de
24.02.2022
soft A
actualizat în data de
25.01.2024
soft J*
actualizat în data de
17.01.2024
Anexa
validări
actualizat în data de
21.11.2023
Schema
XSD
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform OPANAF nr. 51/18.01.2021, valabil începand cu 12/2020 - publicat în data de
20.01.2021
Formularul nu conţine modificările conform OUG 153/2020.
Termenul legal de depunere
pentru impozitul pe veniturile microîntreprinderilor - trim. IV 2021 şi impozitul specific - sem. II 2021
este 25 iunie 2022.
soft A
actualizat în data de
20.01.2022
soft J*
actualizat în data de
20.01.2022
Anexa
validări
actualizat în data de
20.01.2022
Schema
XSD
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform OPANAF
nr. 935/ 14.04.2020
, valabil începand cu
09/2020
- publicat în data de
30.09.2020
soft A
actualizat în data de
23.10.2020
soft J*
actualizat în data de
30.09.2020
Anexa
validări
actualizat în data de
30.09.2020
Schema
XSD
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform OPANAF
nr. 935/ 14.04.2020
, valabil începand cu
03/2020
- publicat în data de
22.04.2020
soft A
actualizat în data de
30.09.2020
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform OPANAF nr. 1203/ 10.05.2018, valabil începand cu 05/2018 - publicat în data de
07.06.2018
soft A
actualizat în data de
05.12.2019
soft J*
actualizat în data de
28.01.2020
Anexa
validări
actualizat în data de
21.02.2019
Schema
XSD
100
- Declaraţie privind obligaţiile
de plată la bugetul de stat, conform OPANAF 3781/ 22.12.2017,  -
publicat în data de
01.02.2018
soft A
soft J*
Anexa
validări
Schema
XSD
100
- Declaraţie privind obligaţiile
de plată la bugetul de stat, conform OPANAF 2935/ 11.10.2017, valabil începand cu 09/ 2017 -
publicat în data de
17.10.2017
soft A
soft J*
Anexa
validări
Schema
XSD
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform OPANAF 869/ 08.03.2017, valabil începand cu 02/ 2017 -
publicat în data de
16.03.2017
soft A
actualizat în data de
01.08.2017
soft J*
actualizat în data de
01.08.2017
Anexa
validări
actualizat în data de
01.08.2017
Schema
XSD
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform OPANAF 587/2016 
cu modificările şi completările ulterioare, valabil începand cu 01/2016 -
publicat în data de
07.12.2016 - versiune bilingvă română - engleză
soft A
soft J*
actualizat în data de
21.03.2016
Anexa
validări
actualizat în data de
21.03.2016
Schema
XSD
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform OPANAF 587/2016 
cu modificările şi completările ulterioare, valabil începand cu 01/2016 -
publicat în data de
15.02.2016
soft A
actualizat în data de
15.04.2016
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform OPANAF 123/ 29.01.2014 valabil incepand cu 01/2014 - actualizat în data de
19.01.2015
soft A
soft J*
Anexa
validări
Schema
XSD
Ghid
de depunere a declaraţiei D100
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform OPANAF 3136/ 26.09.2013 - actualizat în
20.01.2014
soft A
Anexa
validări
Schema
XSD
Ghid
de depunere a declaraţiei D100
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform OPANAF 1135/ 30.07.2012 pentru an 2012 - actualizat în
28.12.2012
soft A
Anexa
validări
Schema
XSD
Ghid
de depunere a declaraţiei D100
100
- Declaraţie privind obligaţiile de plată la bugetul de stat, conform OPANAF 1932/2011, utilizată începând cu declararea obligaţiilor fiscale aferente lunii noiembrie 2011 - actualizat în
12.01.2012
soft A
Anexa
validări
Schema
XSD
Ghid
de depunere a declaraţiei D100
*softul J se adresează doar contribuabililor care îşi generează fişierul xml din aplicaţiile informatice proprii

View File

@@ -0,0 +1,196 @@
101
Ascultă
Denumire formular
Programe asistenţă
Instrucţiuni/ Documentaţie
PDF
JAVA
101
- Declaraţie privind impozitul pe
profit conform
OPANAF nr. 206/11.02.2025.
Valabil pentru an sfârşit de perioada >= 2024
publicat în 14.02.2025
.
soft A
actualizat în data de
26.01.2026
soft J*
actualizat în data de
23.01.2026
Anexa
validări
actualizat în data de
26.01.2026
Schema
XSD
actualizat în data de
14.02.2025
101
- Declaraţie privind impozitul pe
profit conform
OPANAF nr. 310/07.03.2023,
OPANAF nr. 423/17.03.2022. Valabil pentru an sfârşit de perioada >= 2022
publicat în 22.03.2022
.
soft A
actualizat în data de
04.03.2024
soft J*
actualizat în data de
24.04.2023
Anexa
validări
actualizat în data de
24.04.2023
Schema
XSD
actualizat în data de
24.04.2023
101
- Declaraţie privind impozitul pe
profit conform OPANAF nr. 423/ 17.03.2020, publicat în MO nr.1246/
17.12.2020. Valabil pentru an sfârşit de perioada >= 2021
publicat în 29.03.2022
.
soft A
actualizat în data de
10.10.2022
soft J*
Anexa
validări
Schema
XSD
101
- Declaraţie privind impozitul pe
profit conform OPANAF nr. 4072/ 09.12.2020, publicat în MO nr.1246/
17.12.2020. Valabil pentru an sfârşit de perioada >= 2020
publicat în 12.01.2021
.
soft A
actualizat în data de
15.02.2021
soft J*
actualizat în data de
03.02.2021
Anexa
validări
Schema
XSD
101
- Declaraţie privind impozitul pe profit conform OPANAF nr.
3200/20.12.2018.
Valabil pentru an sfârşit de perioada >= 2018 -
publicat în 21.01.2019
.
soft A
actualizat în data de
21.06.2019
soft J*
actualizat în data de
25.04.2019
Anexa
validări
actualizat în data de
12.02.2019
Schema
XSD
101
- Declaraţie privind impozitul pe profit conform OPANAF nr.4160/ 28.12.2017.
Valabil pentru an sfârşit de perioada >= 2017 -
publicat în 14.02.2018
.
soft A
actualizat în data de
27.07.2018
soft J*
actualizat în data de
27.07.2018
Anexa
validări
actualizat în data de
27.07.2018
Schema
XSD
101
- Declaraţie privind impozitul pe
profit conform OPANAF nr.3386/2016, publicat în M.O. nr.950/25.11.2016
Ca urmare a modificării plafonului de venituri, vor depune D101
la 01/2017 respectiv 07/2017, persoanele juridice romane care la data de
31.12.2016 au realizat venituri cuprinse între
100001-500000
euro,inclusiv, si care :
- sunt plătitoare de impozit pe profit la
data de 31.01.2017 si vor intra în categoria microintreprinderilor
încep<65>nd cu 01.02.2017 (scadenta 25.02.2017)
- sunt plătitoare de
impozit pe profit la data de 31.07.2017 si vor intra în categoria
microintreprinderilor încep<65>nd cu 01.08.2017 (scadenta 25.08.2017)
Valabil pentru lunile ianuarie 2017 si iulie 2017 - actualizat în
data de
31.07.2017
soft A
actualizat în data de
21.11.2017
soft J*
actualizat în data de
21.11.2017
Anexa
validări
actualizat în data de
31.07.2017
Schema
XSD
101
- Declaraţie privind impozitul pe profit,
conform OPANAF nr. 3386/ 25.11.2016 publicat în M.O. nr.950/25.11.2016  - valabil începând cu anul 2016 - publicat în data de
27.01.2017
soft A
actualizat în data de
08.02.2017
Anexa
validări
actualizat în data de
08.02.2017
Schema
XSD
actualizat în data de
06.02.2017
101
- Declaraţie privind impozitul pe profit, conform OPANAF nr. 3250/ 2015 (M.OF. nr.905/ 07.12.2015) - valabil încep<65>nd cu anul 2015 - publicat
07.12.2016 versiune bilingvă română - engleză
soft A
Anexa
validări
Schema
XSD
101
- Declaraţie privind impozitul pe profit, conform OPANAF nr. 3250/ 2015 (M.OF. nr.905/ 07.12.2015) - valabil încep<65>nd cu anul 2015 - publicat
11.01.2016
soft A
actualizat în data de
07.12.2016
101
- Declaraţie privind impozitul pe profit, conform OPANAF nr. 4024/ 23.12.2014 (M.OF. nr.2/ 05.01.2015) - valabil începând cu anul 2014 - actualizat
23.09.2015
soft A
actualizat în data de
23.09.2015
Anexa
validări
Schema
XSD
101
- Declaraţie privind impozitul pe profit, conform OPANAF nr. 1950/2012 - actualizat în
data de 24.01.2014
(actualizare pentru persoanele juridice care la 31.12 îndeplinesc condiţiile de plătitor de impozit pe veniturile microintrep. cf. art. 112^2 alin.(3) din L. 571/2003)
Pentru perioadele de raportare anterioare anului 2012, declaraţia 101 se poate descărca de pe
portalul ANAF
secţiunea programe utile
şi se pot depune numai la ghişeu.
soft A
Anexa
validări
Schema
XSD
*softul J se adresează doar contribuabililor care îşi generează fişierul xml din aplicaţiile informatice proprii

View File

@@ -0,0 +1,144 @@

205
Denumire formular
Programe asistenţă
Instrucţiuni/ Documentaţie
PDF
JAVA
205
- Declaraţie  informativă privind impozitul reţinut la sursă şi câştigurile/pierderile
din investii, pe beneficiari de venit,
conform OPANAF nr. 102/2025( începând cu anul de raportare 2024) - publicat în
11.02.2025
soft A
actualizat în data de
15.01.2026
soft J*
Anexa
validări
Schema XSD
205
- Declaraţie  informativă privind impozitul reţinut la sursă şi câştigurile/pierderile
din investii, pe beneficiari de venit,
conform OPANAF nr. 154/29.01.2024( începând cu anul de raportare 2023) - publicat în
05.02.2024
soft A
actualizat în data de
12.04.2024
soft J*
Anexa
validări
Schema XSD
205
- Declaraţie  informativă privind impozitul reţinut la sursă şi câştigurile/pierderile
din investii, pe beneficiari de venit,
conform OPANAF nr. 18/10.01.2023( începând cu anul de raportare 2022) - publicat în
20.01.2023
soft A
actualizat în data de
20.02.2023
soft J*
actualizat în data de
11.12.2023
Anexa
validări
Schema XSD
actualizat în data de
10.02.2023
205
- Declaraţie  informativă privind impozitul reţinut la sursă şi câştigurile/pierderile realizate, pe beneficiari de venit,
conform OPANAF ( începând cu anul de raportare 2021) - publicat în
09.02.2022
soft A
soft J*
actualizat în data de
25.02.2022
Anexa
validări
Schema
XSD
205
- Declaraţie  informativă privind impozitul reţinut la sursă şi câştigurile/pierderile realizate, pe beneficiari de venit,
conform OPANAF 48/11.01.2019 ( începând cu anul de raportare 2018) - publicat în
17.01.2019
soft A
actualizat în data de
25.02.2021
soft J*
actualizat în data de
25.02.2021
Anexa
validări
actualizat în data de
25.02.2021
Schema
XSD
205
-Declaraţie  informativă privind impozitul reţinut la sursă şi câştigurile/pierderile realizate, pe beneficiari de venit,
conform OPANAF 3726/ 19.12.2017 ( începând cu anul de raportare 2017 ) - publicat în
16.01.2018
soft A
actualizat în data de
23.02.2018
soft J*
actualizat în data de
08.02.2018
Anexa
validări
actualizat în data de
19.02.2018
Schema
XSD
actualizat în data de
22.02.2018
205
-Declaraţie  informativă privind impozitul reţinut la sursă şi câştigurile/pierderile realizate, pe beneficiari de venit, conform OPANAF 3695/ 27.12.2016 ( începând cu anul de raportare 2016 ) - publicat în
19.01.2017
soft A
publicat în data de
28.02.2017
soft J*
actualizat în data de
16.02.2017
Anexa
validări
publicat în data de
19.01.2017
Schema
XSD
publicat în data de
19.01.2017
205
- Declaraţie  informativă privind impozitul reţinut la sursă şi câştigurile/pierderile realizate, pe beneficiari de venit, conform Ordinului ANAF 3605/ 2015 ( începând cu anul de raportare 2015 ) - publicat în
11.01.2016
soft A
actualizat în data de
18.01.2016
soft J*
actualizat în data de
12.09.2016
Anexa
validări
Schema
XSD
actualizat în data de
04.02.2016
205
- Declaraţie  informativă privind impozitul reţinut la sursă şi câştigurile/pierderile realizate, pe beneficiari de venit, conform Ordinului ANAF 3883/2013 ( începând cu anul de raportare 2013 ) - actualizat în
20.02.2015
soft A
soft J*
Anexa
validări
Schema
XSD
205
- Declaraţie informativă privind impozitul reţinut la sursă şi câştigurile/pierderile realizate, pe beneficiari de venit, conform Ordinului ANAF 1913/2012 -( valabil pentru an raportare 2012 ) actualizat în
09.04.2013
soft A
soft J*
Anexa
validări
Schema
XSD
*softul J se adresează doar contribuabililor care îşi generează fişierul xml din aplicaţiile informatice proprii

View File

@@ -0,0 +1,180 @@

300
Denumire formular
Programe asistenţă
Instrucţiuni/ Documentaţie
PDF
JAVA
300
- Decont de taxă pe valoarea adăugată conform
OPANAF nr. 2131/02.09.2025, utilizat începând cu declararea obligaţiilor fiscale aferente lunii ianuarie 2026 - publicat în data
11.02.2026
soft A
soft J*
Anexa
validări
Schema
XSD
300
- Decont de taxă pe valoarea adăugată conform
OPANAF nr. 2131/02.09.2025, utilizat începând cu declararea obligaţiilor fiscale aferente lunii august 2025 - publicat în data
03.09.2025
soft A
actualizat în data de
16.12.2025
soft J*
Anexa
validări
Schema
XSD
publicat în data de
23.09.2025
300
- Decont de taxă pe valoarea adăugată conform
OPANAF nr. 888/29.04.2024, utilizat începând cu declararea obligaţiilor fiscale aferente lunii ianuarie 2025 (Contine cod CAEN Rev 3. Pentru cod CAEN Rev 2 va rugam sa utilizati versiunea anterioara)- publicat în data
12.02.2025
soft A
soft J*
Anexa
validări
Schema
XSD
300
- Decont de taxă pe valoarea adăugată conform
OPANAF nr. 888/29.04.2024, utilizat începând cu declararea obligaţiilor fiscale aferente lunii mai 2024 - publicat în data
30.05.2024
soft A
soft J*
Anexa
validări
Schema
XSD
actualizat în data de
05.06.2024
300
- Decont de taxă pe valoarea adăugată conform
OPANAF nr. 1176/04.08.2023, utilizat începând cu declararea obligaţiilor fiscale aferente lunii august 2023 - publicat în data
28.08.2023
soft A
actualizat în data de
22.09.2023
soft J*
Anexa
validări
actualizat în data de
28.08.2023
Schema
XSD
actualizat în data de
25.09.2023
300
- Decont de taxă pe valoarea adăugată conform
OPANAF nr. 1253/2021, utilizat începând cu declararea obligaţiilor fiscale aferente lunii iulie 2021 valabil de la data 17.08.2021 - publicat în data
18.08.2021
soft A
actualizat în data de
28.03.2022
soft J*
Anexa
validări
actualizat în data de
08.07.2021
Schema
XSD
actualizat în data de
12.07.2021
300
- Decont de taxă pe valoarea adăugată conform
OPANAF nr. 632/2021, utilizat începând cu declararea obligaţiilor fiscale aferente lunii iunie 2021 valabil de la data 01.07.2021 - publicat în data
01.07.2021
soft A
soft J*
Anexa
validări
actualizat în data de
08.07.2021
Schema
XSD
actualizat în data de
12.07.2021
300
- Decont de taxă pe valoarea adăugată conform
OPANAF nr. 2227/2019, utilizat începând cu declararea obligaţiilor fiscale valabil de la data 01.07.2019 - publicat în data
13.08.2019
soft A
actualizat în data de
02.09.2019
soft J*
actualizat în data de
18.09.2019
Anexa
validări
actualizat în data de
14.08.2019
Schema
XSD
actualizat în data de
19.08.2019
300
- Decont de taxă pe valoarea adăugată conform
OPANAF nr. 591/2017, utilizat începând cu declararea obligaţiilor fiscale valabil de la data 01.01.2017 - publicat în data
31.01.2017
soft A
soft J*
Anexa
validări
Schema
XSD
300
- Decont de taxă pe valoarea adăugată conform OPANAF nr. 588/2016, utilizat începând cu declararea obligaţiilor fiscale valabil de la data 01.01.2016 - publicat în data
15.02.2016
soft A
23.03.2016
soft J*
24.02.2016
Anexa
validări
Schema
XSD
300
- Decont de taxă pe valoarea adăugată conform OPANAF
nr. 1790/2012, utilizat începând cu declararea obligaţiilor fiscale
valabil de la data 01.01.2013
-
actualizat în data de
24.01.2014
soft A
soft J*
Anexa
validări
Schema
XSD
Ghid
de depunere a declaraţiei D300
300
- Decont de taxă pe valoarea adăugată conform OPANAF nr.
3665/22.12.2011, utilizat începând cu declararea obligaţiilor fiscale
valabil de la data 01.01.2012
-
actualizat în data de
06.03.2012
soft A
Anexa
validări
Schema
XSD
Ghid
de depunere a declaraţiei D300
300
- Decont de taxă pe valoarea adăugată conform OPANAF nr. 183/31.01.2011, utilizat începând cu declararea obligaţiilor fiscale aferente lunii noiembrie 2011
-
actualizat în data de
15.01.2012
soft A
Anexa
validări
Schema
XSD
Ghid
de depunere a declaraţiei D300
*softul J se adresează doar contribuabililor care îşi generează fişierul xml din aplicaţiile informatice proprii

View File

@@ -0,0 +1,68 @@

390
Denumire formular
Programe asistenţă
Instrucţiuni/ Documentaţie
PDF
JAVA
390
- Declaraţia recapitulativă privind livrările/achiziţiile/prestările intracomunitare
 -
conform OPANAF nr. 705/11.03.2020 - incepand cu perioada de raportare
02/2020
-
publicat în data de
18.03.2020
soft A
actualizat în data de
07.05.2024
soft J*
actualizat în data de
25.06.2025
Anexa
validări
actualizat în data de
07.05.2024
Schema
XSD
actualizat în data de
12.02.2021
390
- Declaraţia recapitulativă privind livrările/achiziţiile/prestările intracomunitare
 -
incepand cu anul de raportare 2017
-
publicat în data de
01.02.2017
soft A
actualizat în data de
30.01.2020
soft J*
actualizat în data de
12.02.2020
Anexa
validări
Schema
XSD
390
- Declaraţia recapitulativă privind livrările/achiziţiile/prestările intracomunitare
-conform OPANAF nr.591/03.02 2016 (publicat în M.O. nr.94/ 08.02.2016)
-începand cu anul de raportare 2015
-conform OPANAF nr.76/21.01.2010 - incepand cu anul de raportare 2011
-
actualizat în data de
09.02.2016
soft A
actualizat în data de
09.02.2016
soft J*
actualizat în data de
23.04.2015
Anexa
validări
Schema
XSD
Ghid
de depunere a declaraţiei D390
Prevederi legale de completare a formularului 390
*softul J se adresează doar contribuabililor care îşi generează fişierul xml din aplicaţiile informatice proprii

View File

@@ -0,0 +1,141 @@

394
Denumire formular
Programe asistenţă
Instrucţiuni/ Documentaţie
PDF
JAVA
394
Declaraţie informativă privind livrările/prestările şi achiziţiile efectuate pe teritoriul naţional - utilizat începând cu declararea obligaţiilor fiscale aferente lunii august 2025 - publicat în
15.09.2025
soft A
soft J*
Anexa
validări
Schema
XSD
394
Declaraţie informativă privind livrările/prestările şi achiziţiile efectuate pe teritoriul naţional conform  OPANAF 77/2022- utilizat începând cu declararea obligaţiilor fiscale aferente lunii ianuarie 2025 (Contine cod CAEN Rev 3. Pentru cod CAEN Rev 2 va rugam sa utilizati versiunea anterioara) - publicat în
12.02.2025
soft A
soft J*
Anexa
validări
Schema
XSD
394
Declaraţie informativă privind livrările/prestările şi achiziţiile efectuate pe teritoriul naţional conform  OPANAF 77/2022- utilizat începând cu declararea obligaţiilor fiscale aferente lunii aprilie 2022  - publicat în
10.05.2022
soft A
soft J*
actualizat în data de
30.05.2022
Anexa
validări
Schema
XSD
394
Declaraţie informativă privind livrările/prestările şi achiziţiile efectuate pe teritoriul naţional conform  OPANAF 3281/2020- utilizat începând cu declararea obligaţiilor fiscale aferente lunii septembrie 2020  - publicat în
02.09.2020
soft A
soft J*
Anexa
validări
Schema
XSD
actualizat în data de
25.09.2020
394
Declaraţie informativă privind
livrările/prestările şi achiziţiile efectuate pe teritoriul naţional
conform  OPANAF 2264/2016  - utilizat începând cu declararea
obligaţiilor fiscale aferente lunii ianuarie 2017  - publicat în
31.01.2017
soft A
actualizat în data de
13.02.2018
soft J*
actualizat în data de
09.09.2019
Anexa
validări
actualizat în data de
07.09.2017
Schema
XSD
actualizat în data de
09.09.2019
394
Declaraţie informativă privind
livrările/prestările şi achiziţiile efectuate pe teritoriul naţional
conform  OPANAF 2264/2016  - utilizat începând cu declararea
obligaţiilor fiscale aferente lunii iulie 2016  - publicat în
02.08.2016
soft A
actualizat în data de
08.12.2016
soft J*
actualizat în data de
19.01.2017
Anexa
validări
actualizat în data de
07.12.2016
Schema
XSD
actualizat în data de
18.11.2016
394
Declaraţie informativă privind livrările/prestarile
şi achiziţiile efectuate pe teritoriul naţional conform 
OPANAF 3806/2013  - utilizat începând cu declararea obligaţiilor fiscale aferente lunii
ianuarie 2016 - actualizat în
06.04.2016
soft A
actualizat în data de
06.04.2016
soft J*
actualizat în data de
06.04.2016
Anexa
validări
Schema
XSD
-
actualizat în data de
06.04.2016
Ghid
de depunere a declaraţiei D394
Prevederi legale de completare a formularului 394
394
Declaraţie informativă privind livrările/prestarile
şi achiziţiile efectuate pe teritoriul naţional conform 
OPANAF 3806/2013  - utilizat începând cu declararea obligaţiilor fiscale aferente lunii
decembrie 2013 - actualizat în
19.03.2015
soft A
actualizat în data de
29.01.2016
Anexa
validări
Schema
XSD
-
actualizat în data de
06.04.2016
Ghid
de depunere a declaraţiei D394
Prevederi legale de completare a formularului 394
394
Declaraţie informativă privind livrările/prestarile şi achiziţiile efectuate pe teritoriul naţional conform OPANAF 3596/2011 publicat în MO 927/28.12.2011 - utilizat începând cu declararea obligaţiilor fiscale aferente lunii ianuarie 2012 - actualizat în
28.10.2013
soft A
soft J*
Anexa
validări
Schema
XSD
Ghid
de depunere a declaraţiei D394
Prevederi legale de completare a formularului 394
*softul J se adresează doar contribuabililor care îşi generează fişierul xml din aplicaţiile informatice proprii

View File

@@ -0,0 +1,21 @@

ANAF
Denumire formular
Programe asistenţă
Instrucţiuni/ Documentaţie
PDF
JAVA
D406
- Declaraţia SAF-T conform OPANAF 1783 din 04.11.2021 privind natura informaţiilor pe care contribuabilul/platitorul trebuie să le declare prin fişierul standard de control fiscal,
modelul de raportare, procedura şi condiţiile de transmitere, precum şi termenele de transmitere şi data/datele de la care categoriile de contribuabili/plătitori sunt obligate să transmită fişierul standard de control fiscal - publicat în
28.02.2022
Soft J*
actualizat în data de
11.02.2026
Schema xsd
actualizat în data de
08.07.2025
Schema xlsx
actualizat în data de
11.02.2026
*softul J se adresează doar contribuabililor care îşi generează fişierul xml din aplicaţiile informatice proprii

View File

@@ -0,0 +1,566 @@
Descarcare declaratii
Ca urmare a ultimei
actualizări realizate de catre ADOBE, există posibilitatea să apară erori în
procesul de semnare electronică a declaraţiilor fiscale. în aceste situaţii,
pentru remediere trebuie să urmaţi procedurile indicate de furnizorul
certificatului dumneavoastră.
Dacă aveţi probleme cu descărcarea formularelor, vă rugăm să folosiţi
Formularul de contact
alegând categoria "Asistenţă tehnică servicii
informatice".
Data ultimei actualizări: 19.05.2023
Pentru a putea folosi softul J trebuie să aveţi instalat programul
DUKIntegrator
- actualizat în data de 20.08.2021
Dezvoltatorii de sisteme informatice pot descărca
sursele
aplicaţiei DUKIntegrator  - actualizat în data de 11.09.2018
Document de confirmare
- actualizat în data de 29.06.2021
Cu scopul de a mări gradul de rezilienţă şi de a asigura securitatea resurselor şi a sistemelor informatice, la nivel global au fost actualizate procedurile
şi politicile de securitate. În acest scop, pentru a nu afecta procesul de digitalizare a Ministerului Finanţelor (ANAF) şi pentru a urma standardele înalte de securitate stabilite
la nivel mondial, vă informăm că începând cu 08.01.2023 aplicaţiile puse la dispoziţia contribuabililor (pdf-urile inteligente) vor putea fi utilizate numai pe sisteme de operare
aflate în suport la producător şi cu toate update-urile la zi.
D11
- CERERE DE ANULARE a unor obligații bugetare/ NOTIFICARE privind intenția de a beneficia de anularea unor obligații bugetare
- publicat în data de
20.09.2024
Se transmit prin portalul SPV
CA300
Cerere de acord pentru finanţare, în baza H.G. nr. 300/2024 - publicat în data de
16.01.2026
C300
Formular ataşare documente, în baza H.G. nr. 300/2024 - publicat în data de
18.12.2025
CP300
Cerere de plată, în baza H.G. nr. 300/2024 - publicat în data de
16.12.2025
Se transmit prin portalul
e-guvernare.ro
CA807
Cerere de acord pentru finanţare, în baza H.G. nr. 807/2014 - publicat în data de
12.04.2021
C807
Formular ataşare documente, în baza H.G. nr. 807/2014 - publicat în data de
12.04.2021
CP807
Cerere de plată a ajutorului de stat, în baza H.G. nr.807/2014 - publicat în data de
12.04.2021
Se transmit prin SPV
F1129
-  Ordinul de plată multiplu electronic (OPME) V.2.0.45 dată
actualizare
25.11.2025
Formularul se depune on-line prin Sistemul naţional de raportare
FOREXEBUG
de către instituţiile publice şi, respectiv, prin portalul
e-guvernare.ro
de către operatorii economici şi alte entităţi decât instituţii
publice
173
- Cerere privind grupul fiscal în domeniul impozitului pe profit. -
publicat în data de
29.10.2021
Se transmit prin portalul
e-guvernare.ro
D708
- INFORMARE privind alegerea
efectuării în România a procedurilor de raportare, conform
prevederilor secţiunii IV din anexa 5 la legea nr.207/2015 privind
Codul de Procedură Fiscală, pentru operatorii de platformă care îndeplinesc condiţiile
menţionate la pct.4, lit. a, subsecţiunea A, secţiunea I din anexa V la legea nr. 207/2015 privind Codul de Procedură Fiscală
şi în România şi în alte state membre. -
publicat în data de
28.12.2023
Formulare utilizate de operatorii de platforme digitale
Se transmit prin portalul
e-guvernare.ro
Z01
- Cerere pentru eliberarea certificatului de rezidenţă fiscală
pentru persoane juridice rezidente în România / Application for the issuance of the certificate of tax residence for resident legal persons in Romania - publicat în data de
26.04.2021
Z03
- Cerere pentru eliberarea certificatului de rezidenţă fiscală pentru persoane fizice rezidente în România /
Application for the issuance of the certificate of tax residence for individuals resident in Romania - publicat în data de
26.04.2021
Z05
- Cerere pentru eliberarea certificatului de rezidenţă fiscală pentru persoane fizice rezidente în România care desfăşoară activitate independentă /
Application for the issuance of the certificate of tax residence for individuals resident in Romania carrying on an independent activity - publicat în data de
26.04.2021
Z07
- Cerere pentru eliberarea certificatului de rezidenţă fiscală pentru persoane rezidente în România / Application for the issuance of the certificate of tax residence for persons resident in Romania - publicat în data de
26.04.2021
Z09
- Cerere pentru eliberarea certificatului privind atestarea impozitului plătit în România de persoane juridice
străine / Application for the issuance of the certificate attesting the tax paid in Romania by foreign legal persons - publicat în data de
26.04.2021
Z11
- Cerere pentru eliberarea certificatului privind atestarea impozitului plătit în România de persoane fizice nerezidente / Application for the issuance of the certificate attesting the tax paid in Romania by nonresident individuals - publicat în data de
26.04.2021
Z13
- Cerere referitoare la eliberarea
certificatului privind atestarea desfăşurării activităţii în România de
către sediul permanent al unei persoane juridice străine / Application for the issuance of the certificate attesting the activity carried on in Romania by the permanent establishment/designated permanent establishment of a foreign legal person - publicat în data de
26.04.2021
Se transmit prin portalul
e-guvernare.ro
şi prin link-ul
Depunere Declaraţie unică şi alte formulare SPV-PF
It can be sent through
e-guvernare.ro
and  link
"
Depunere Declaraţie unică şi alte formulare SPV-PF
"
Z015
- Chestionar pentru stabilirea rezidenţei fiscale a
persoanei fizice la sosirea în România
Set of questions for determining the fiscal residence of the individual on the arrival in Romania - publicat în data de
07.04.2020
Z017
- Chestionar pentru stabilirea rezidenţei fiscale a
persoanei fizice la plecarea din România
Set of questions for determining the
fiscal residence of the individual when leaving Romania - publicat
în data de
07.04.2020
Se transmit prin portalul
e-guvernare.ro
şi prin link-ul
Depunere Declaraţie unică şi alte formulare SPV-PF
It can be sent through
e-guvernare.ro
and  link
"
Depunere Declaraţie unică şi alte formulare SPV-PF
"
163
- Cerere de înscriere /radiere în /din Registrul entităţilor
/unităţilor de cult pentru care se acorda deduceri fiscale -
publicat în data de
01.04.2019
Se transmit prin portalul
e-guvernare.ro
168
-
Cerere de înregistrare a contractelor de locaţiune - actualizat în data de
04.01.2023
Se transmit prin portalul
e-guvernare.ro
şi prin link-ul
Depunere Declaraţie unică şi alte formulare SPV-PF
169
-
DECLARAŢIE DE ÎNREGISTRARE A CONTRACTELOR DE FIDUCIE (anexa 3) conform cu prevederile OPANAF 1193/ 2021 - publicat în data de
18.04.2022
169N
-
Declaraţie privind neconcordanţele între informaţiile privind beneficiarii reali, disponibile în Registrul central al fiduciilor şi al construcţiilor
juridice similare fiduciilor şi informaţiile deţinute de autorităţi/ entităţi raportoare (anexa 6) în conformitate cu OPANAF nr.1193/ 2021 - publicat în data de
18.04.2022
Se transmit prin portalul
e-guvernare.ro
Declaraţii electronice
(098)
Se
depun la organul fiscal competent. Nu se transmit on-line.
Declaraţie privind nedeductibilitatea TVA aferente cheltuielilor
efectuate în cadrul operaţiunii finanţate din FEDR, FSE şi FC 2014 -
2020
- publicat în data de
28.09.2016
Se depun la organul
fiscal competent. Nu se transmit on-line.
Declaraţii
electronice
(
017
,
060
,
093
,
100
,
101
,
101G
,
104
,
106
,
107
,
108
,
109
,
110
,
112
,
114
,
119
,
120
,
130
,
177
,
179
,
180
,
182
,
205
,
207
,
213
,
214
,
216
,
217
,
222
,
300
,
301
,
307
,
311
,
390
,
392
,
393
,
394
,
395
,
397
,
398
,
401
,
402
,
403
,
404
,
405
,
406
,
407
,
408
,
700
,
710,
711,
B900,
B901,
B902
- actualizat în data de
28.10.2024
(C182)
Se transmit prin portalul
e-guvernare.ro
Declarațiile 216 şi 217 se pot transmite și prin SPV de către persoanele fizice.
Cererea C182 se poate transmite si prin SPV.
Declaraţie 089
Declaraţie pe propria răspundere pentru îndeplinirea condiţiei prevăzute la art. 331 alin. (2) lit. e) pct. 2 şi/sau art. 331 alin. (2) lit. l) pct. 2 din Codul fiscal
Se transmit prin portalul
e-guvernare.ro
Formular C310
Cerere pentru aprobarea transferului sumelor din contul de TVA
Declaraţie
pe propria răspundere pentru înregistrarea în scopuri
de TVA, potrivit art. 316 alin. (1) lit. c) din Legea nr. 227/2015
privind Codul Fiscal, cu modificările şi completările ulterioare -
publicat în data de
01.02.2019
Se transmit prin portalul
e-guvernare.ro
Declaraţie D318
Cerere de rambursare a TVA pentru persoanele impozabile stabilite în
România, depusă potrivit art. 302 alin.(2) din Codul fiscal -
publicat în
19.10.2017
Declaraţie 319
Declaraţie de ajustare a pro-ratei - publicat în
04.12.2017
Se transmit prin portalul
e-guvernare.ro
Formular S1055
înştiinţare privind modificarea exerciţiului
financiar în baza art. 27 din legea contabilităţii
Se transmit prin portalul
e-guvernare.ro
Notificare
privind modificarea anului fiscal -
publicat în data de
02.04.2020
Notificare
privind modificarea sistemului
anual/trimestrial de declarare şi plată a impozitului pe profit -
publicat în data de
13.04.2020
Se
transmit prin portalul
e-guvernare.ro
Notificare/Cerere de anulare a obligaţiilor de plată accesorii
(D2
,
D3)
-
actualizat în data de
07.08.2020
Se transmit prin SPV
Cerere de acordare a eşalonării la plată
(D5)
- publicat în data de
19.11.2020
Se transmit prin SPV
Cerere de restructurare a obligaţiilor bugetare/Notificare privind
intenţia de restructurare a obligaţiilor bugetare
(D6)
- publicat în data de
14.12.2020
Se transmit prin SPV
Cerere de acordare a eşalonării la plată, în forma simplificată
(D7)
- publicat în data de
29.12.2021
Se transmit prin SPV
Situaţia eliberărilor pentru consum de ţigarete, ţigări şi ţigări de foi, tutun
fin tăiat destinat rulării în ţigarete şi alte tutunuri de fumat
(D9)
- publicat în data de
11.01.2023
Se transmit prin portalul
e-guvernare.ro
Declaraţie privind preţurile de vânzare cu amănuntul
pe sortimente de ţigarete
(D10)
- publicat în data de
11.01.2023
Se transmit prin portalul
e-guvernare.ro
Cerere de acordare a eşalonării la plată
(D8)
- publicat în data de
12.07.2022
Se transmit prin SPV
Situaţii financiare interimare trimestriale
<
=2024
- actualizat în data de
20.04.2023
Se transmit prin portalul
e-guvernare.ro
Situaţii financiare interimare trimestriale =2025
Se transmit prin portalul
e-guvernare.ro
Raportări contabile semestriale 2024
- publicat în data de
17.07.2024
Se transmit prin portalul
e-guvernare.ro
Raportări contabile semestriale 2025
- publicat în data de
18.07.2025
Se transmit prin portalul
e-guvernare.ro
Situaţii financiare anuale/Raportări anuale an 2022
- publicat în data de
20.01.2023
Se transmit prin portalul
e-guvernare.ro
Situaţii financiare anuale/Raportări anuale an 2023
- publicat în data de
17.11.2023
Se transmit prin portalul
e-guvernare.ro
Situaţii financiare anuale/Raportări anuale an 2024
- publicat în data de
28.01.2025
Se transmit prin portalul
e-guvernare.ro
Situaţii financiare anuale/Raportări anuale an 2025
- publicat în data de
10.02.2025
Se transmit prin portalul
e-guvernare.ro
Arhivă situaţii financiare anuale/Raportări anuale
Se transmit prin portalul
e-guvernare.ro
Arhivă raportări contabile semestriale
Se transmit prin portalul
e-guvernare.ro
Formulare S1001
,
1100
Se transmit prin portalul
e-guvernare.ro
Declaraţii electronice
(
Declaraţia unică (212)
,
200
,
201
,
204
,
208
,
209
,
220
,
221
,
223
,
224
,
230
,
600
603
)
- actualizat în data de
28.04.2022
(D603)
Se transmit prin portalul
e-guvernare.ro
Declaraţie unică se depune  accesând link-ul:
Depunere Declaraţie unică şi alte formulare SPV-PF
D200 se poate depune prin SPV
DAC6
- Formular utilizat de intermediarii sau contribuabilii relevanţi, după caz, în vederea raportării informaţiilor cu privire la aranjamentele transfrontaliere care fac obiectul raportării
- publicat în data de
20.01.2021
Se transmit prin
portalul
e-guvernare.ro
Formulare pentru sistemul PATRIMVEN:
P1000
- actualizat în data de
28.02.2022
,
P2000
Se transmit prin portalul
e-guvernare.ro
sau prin sistemul PATRIMVEN:
epatrim.fiscnet.ro
sau
epatrim.anaf.ro
Formularul
F3000
- publicat în data de
24.08.2016
Formularul
F7000
- publicat în data de
12.01.2024
Se transmit prin portalul
e-guvernare.ro
Formularul
P4000
,
P5000
Formularul
L153
Se transmit prin
portalul
e-guvernare.ro
C801
- Cerere de atribuire a numărului unic de identificare din aplicaţia
informatică a ANAF - publicat în data de
16.07.2018
C802
- Cerere pentru obţinerea profilurilor de trecere a aparatelor de marcat electronice fiscale în funcţionare online - publicat în data de
30.03.2021
Se transmit prin SPV
F4101
- Formulare utilizate pentru declararea în etapa
pre-operaţionalizare a Registrului Aparatelor de Marcat Electronice
Fiscale - publicat în data de
29.05.2018
F4102
- Formular pentru înregistrarea aparatelor de marcat electronice
fiscale (AMEF) instalate - publicat în data de
11.06.2018
F4103
- Formular pentru înregistrarea unor schimbări privind starea/
situaţia precum şi livrarea/achiziţia aparatelor de marcat
electronice fiscale - publicat în data de
31.07.2018
F4105
- Notificare privind situaţia aparatului de marcat electronic fiscal
(Anexa 2E la Metodologia şi procedura de înregistrare a aparatelor
de marcat electronice fiscale în etapa pre-operaţionalizare a
Registrului), în conformitate cu OPANAF nr.4156/ 28.12.2017 -
publicat în data de
07.08.2018
F4109
- Declaraţie privind aparatele de marcat electronice fiscale (AMEF)
neutilizate - publicat în data de
17.09.2018
F4110
- Declaraţie pe propria răspundere prin care utilizatorul declară că utilizează aparatul de marcat electronic fiscal instalat în zone nedeservite de reţele de comunicaţii electronice, în conformitate cu OPANAF nr. 435/2021 - publicat în data de
30.03.2021
A4200
- aplicaţie pentru validarea fişierului XML creat de aparatele de
marcat electronice fiscale, conţinând registrul rapoartelor Z şi
bonurilor fiscale pentru perioada de raportare curentă, prevăzut în
secţiunea II.12 din anexa nr. 2 la OPANAF nr. 146/2018 - publicat
în data de
10.09.2018
A4201
- aplicaţie pentru validarea fişierului XML creat de aparatele de
marcat electronice fiscale, în activitatea de schimb valutar,
conţinând bonurile fiscale (sectiunea II.1) şi raportul Z (secţiunea
II.5) pentru o zi fiscală, în conformitate cu anexa 2 la OPANAF nr.
146/2018 - publicat în data de
14.01.2019
A4202
- aplicaţie pentru validarea fişierului XML creat de aparatele de
marcat electronice fiscale, utilizate în activitatea de taximetrie
conţinând registrul rapoartelor Z şi bonurilor fiscale pentru
perioada de raportare curentă, prevazut în secţiunea II.2 din anexa
nr. 2 la OPANAF nr. 146/2018 - publicat în data de
14.01.2019
A4203
- aplicaţie pentru validarea fişierelor XML create de aparatele  de
marcat electronice fiscale altele decât cele utilizate în
activitatea de schimb valutar sau în activitatea de taximetrie
(inclusiv cele utilizate în aeroporturi), conţinând bonurile fiscale
(secţiunea II.3) şi raportul Z (secţiunea II.7) pentru o zi fiscală,
în conformitate cu anexa nr. 2 la OPANAF nr. 146/2018 - publicat în
data de
10.09.2018
Se transmit prin
portalul
e-guvernare.ro
^

View File

@@ -0,0 +1,33 @@
S1030
S1002-S1003-S1004-S1005
S1011
S1019
S1025
S1030
S1039
S1040-S1041
S1042
S1046
S1051-S1052-S1053-S1054
Tabel
codificări
tipuri de situaţii financiare şi raportări anuale
Denumire formular
Programe asistenţă
Instrucţiuni/ Documentaţie
PDF
JAVA
S1030
- Situaţii financiare anuale consolidate La art. 185 alin. (1) - (2) din Legea societăţilor nr. 31/1990 republicată, modificată şi completată prin Ordonanţa de urgenţă a Guvernului nr. 90/2010, se prevăd următoarele: "(1) în condiţiile prevăzute de Legea contabilităţii nr. 82/1991, republicată, consiliul de administraţie, respectiv directoratul, este obligat să depună la unităţile teritoriale ale Ministerului Finanţelor, în formă electronică, având atasată o semnătură electronică extinsă, situaţiile financiare anuale, raportul lor, raportul cenzorilor sau raportul auditorilor financiari, după caz. (2) Consiliul de administraţie, respectiv directoratul societătii-mamă, definită astfel de reglementările contabile aplicabile, este obligat să depună la unităţile teritoriale ale Ministerului Finanţelor copii ale situaţiilor financiare anuale consolidate, prevederile alin. (1) urmând a fi aplicate în mod corespunzător. Totodată, potrivit art. 29 alin. (2), art. 33 alin. (2) şi art. 36 alin. (5) din Legea contabilităţii nr. 82/1991, republicată, cu modificările şi completările ulterioare, o societate-mamă trebuie să întocmească atât situaţii financiare anuale pentru propria activitate, cât şi situaţii financiare anuale consolidate, în condiţiile prevăzute de reglementările contabile aplicabile. Situaţiile financiare anuale consolidate constituie un tot unitar şi se întocmesc în termen de 8 luni de la încheierea exerciţiului financiar al societăţii-mamă. Acestea cuprind bilanţul consolidat, contul de profit şi pierdere consolidat, precum şi celelalte componente, respectiv informaţii referitoare la activitatea grupului, potrivit reglementărilor contabile aplicabile, şi note explicative la situaţiile financiare anuale consolidate. Conform art. 185 alin. (2) şi alin. (3) din Legea societăţilor nr. 31/1990, modificată şi completată prin Ordonanţa de urgenţă a Guvernului nr. 90/2010 şi art. 31 din Legea contabilităţii nr. 82/1991, republicată, cu modificările şi completările ulterioare, situaţiile financiare anuale consolidate depuse la unităţile teritoriale ale Ministerului Finanţelor sunt însoţite de raportul consolidat al administratorilor, raportul de audit şi de o declaraţie scrisă a persoanelor prevăzute de legea contabilităţii. Consiliul de administraţie, respectiv directoratul societăţii-mamă, definită astfel de reglementările contabile aplicabile, este obligat ca în termen de 15 zile de la data aprobării acestora să depună la unităţile teritoriale ale Ministerului Finanţelor situaţiile financiare anuale consolidate, potrivit prevederilor legale în vigoare.
Conform reglementărilor contabile aplicabile, entităţile care au obligaţia să întocmească situaţii financiare anuale consolidate pot întocmi aceste situaţii fie potrivit Reglementărilor contabile privind situaţiile financiare anuale individuale şi situaţiile financiare anuale consolidate, aprobate prin Ordinul ministrului finanţelor publice nr. 1802/2014, cu modificările şi completările ulterioare, fie în baza Standardelor Internaţionale de Raportare Financiară (IFRS), după caz. În vederea depunerii situaţiilor financiare anuale consolidate în formă electronică, având atasată o semnătură electronică, se foloseste programul de asistentă pus la dispozitie gratuit de către Ministerul Finanţelor pe site-ul ANAF, prin care se generează un fisier de tip PDF, având atasat un fisier xml, care conţine datele de identificare a societăţii-mamă şi ale entităţilor care sunt cuprinse în situaţiile financiare anuale consolidate precum şi un fisier cu extensia .zip. Fisierul cu extensia .zip va conţine situaţiile financiare anuale consolidate şi documentele cerute de lege scanate alb-negru, lizibil şi cu o rezoluţie care să permită încadrarea în limita a 9,5 MB a fisierului PDF la care este atasat fisierul zip. Fişierul zip ataşat situaţiilor financiare anuale nu va conţine parolă - publicat în data de
05.02.2025
soft A
actualizat în data
27.01.2026
S1030 -Soft J
Schema XSD
:
S1030
Structura
S1030
*softul J se adresează doar contribuabililor care îşi generează fişierul xml din aplicaţiile informatice proprii

View File

@@ -0,0 +1,37 @@
S1012
S1012
S1013
S1027-S1050
S1029
S1031-S1032-S1033
S1034
S1035-S1078
S1036-S1038
S1045
S1057
S1058
S1059-S1060
S1074
Tabel codificări
tipuri de situaţii financiare şi raportări anuale
Denumire formular
Programe asistenţă
Instrucţiuni/ Documentaţie
PDF
JAVA
S1012
- Sistemul de raportare contabilă la
30 iunie 2025
pentru
societăţile din domeniul asigurărilor
(asiguratori)  - publicat în data de
18.07.2025
soft  A
Soft J - 1012
Schema XSD
: S1012
Structura
S1012
*softul J se adresează doar contribuabililor care îşi
generează fişierul xml din aplicaţiile informatice
proprii

View File

@@ -0,0 +1,62 @@
{
"D100": {
"soft_a_url": "http://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D100_710_XML_0126_100226.pdf",
"soft_a_date": "10.02.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_v12.0.2_12022026.pdf",
"soft_a_date": "12.02.2026",
"soft_j_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D300Validator_11022026_2.zip",
"soft_j_date": "11.02.2026"
},
"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_20260211.zip",
"soft_j_date": "11.02.2026"
},
"BILANT_2025": {
"soft_a_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/bilant_SC_1225_XML_110226.pdf",
"soft_a_date": "11.02.2026",
"soft_j_S1002_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/S1002_20260128.zip",
"soft_j_S1002_date": "28.01.2026",
"soft_j_S1004_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/S1004_20250204.zip",
"soft_j_S1004_date": "04.02.2025",
"soft_j_S1003_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/S1003_20260210.zip",
"soft_j_S1003_date": "10.02.2026",
"soft_j_S1005_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/S1005_202060203.zip"
},
"SIT_FIN_SEM_2025": {
"soft_j_1012_url": "https://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/S1012_20250723.zip",
"soft_j_1012_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": {}
}

20
tools/backup_config.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Backup config cu retenție: 1 zilnic, 1 săptămânal, 1 lunar
BACKUP_DIR="/home/moltbot/backups"
CONFIG="$HOME/.clawdbot/clawdbot.json"
# Backup zilnic (suprascrie)
cp "$CONFIG" "$BACKUP_DIR/clawdbot-daily.json"
# Backup săptămânal (duminică)
if [ "$(date +%u)" -eq 7 ]; then
cp "$CONFIG" "$BACKUP_DIR/clawdbot-weekly.json"
fi
# Backup lunar (ziua 1)
if [ "$(date +%d)" -eq 01 ]; then
cp "$CONFIG" "$BACKUP_DIR/clawdbot-monthly.json"
fi
echo "Backup done: $(date)"

66
tools/calendar_auth.py Normal file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""
Google Calendar OAuth2 Authorization.
Run this once to generate token.json for calendar access.
"""
import os
from pathlib import Path
from google_auth_oauthlib.flow import Flow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
# Scopes needed for calendar access (read + write events)
SCOPES = ['https://www.googleapis.com/auth/calendar.events']
CREDENTIALS_FILE = Path(__file__).parent.parent / 'credentials' / 'google-calendar.json'
TOKEN_FILE = Path(__file__).parent.parent / 'credentials' / 'google-calendar-token.json'
def main():
creds = None
# Check if token already exists
if TOKEN_FILE.exists():
creds = Credentials.from_authorized_user_file(str(TOKEN_FILE), SCOPES)
# If no valid credentials, do the OAuth flow
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
print("Refreshing expired token...")
creds.refresh(Request())
else:
print(f"Starting OAuth flow...")
print(f"Using credentials: {CREDENTIALS_FILE}\n")
flow = Flow.from_client_secrets_file(
str(CREDENTIALS_FILE),
scopes=SCOPES,
redirect_uri='urn:ietf:wg:oauth:2.0:oob'
)
auth_url, _ = flow.authorization_url(prompt='consent')
print("="*60)
print("AUTHORIZATION REQUIRED")
print("="*60)
print("\n1. Open this URL in your browser:\n")
print(auth_url)
print("\n2. Sign in and authorize access")
print("3. Copy the authorization code and paste it below\n")
code = input("Enter authorization code: ").strip()
flow.fetch_token(code=code)
creds = flow.credentials
# Save the credentials for next run
TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(TOKEN_FILE, 'w') as token:
token.write(creds.to_json())
print(f"\nToken saved to: {TOKEN_FILE}")
print("\n✅ Authorization successful!")
print("You can now use the calendar tools.")
if __name__ == '__main__':
main()

304
tools/calendar_check.py Normal file
View File

@@ -0,0 +1,304 @@
#!/usr/bin/env python3
"""
Google Calendar checker for Echo.
Returns events for today, tomorrow, this week, or upcoming travel needs.
"""
import sys
import json
from pathlib import Path
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
TOKEN_FILE = Path(__file__).parent.parent / 'credentials' / 'google-calendar-token.json'
TZ = ZoneInfo('Europe/Bucharest')
# Keywords that indicate travel to București (needs train + accommodation)
TRAVEL_KEYWORDS = ['nlp', 'bucuresti', 'bucurești', 'bucharest']
def get_service():
"""Get authenticated Calendar service."""
creds = Credentials.from_authorized_user_file(str(TOKEN_FILE))
return build('calendar', 'v3', credentials=creds)
def get_events(service, time_min, time_max, max_results=20):
"""Get events between time_min and time_max."""
events_result = service.events().list(
calendarId='primary',
timeMin=time_min.isoformat(),
timeMax=time_max.isoformat(),
maxResults=max_results,
singleEvents=True,
orderBy='startTime'
).execute()
return events_result.get('items', [])
def format_event(event):
"""Format event for display."""
start = event['start'].get('dateTime', event['start'].get('date'))
summary = event.get('summary', '(fără titlu)')
# Parse start time
if 'T' in start:
dt = datetime.fromisoformat(start)
time_str = dt.strftime('%H:%M')
date_str = dt.strftime('%d %b')
else:
time_str = 'toată ziua'
date_str = datetime.fromisoformat(start).strftime('%d %b')
return {
'summary': summary,
'date': date_str,
'time': time_str,
'start': start,
'is_travel': any(kw in summary.lower() for kw in TRAVEL_KEYWORDS)
}
def check_today_tomorrow():
"""Get events for today and tomorrow."""
service = get_service()
now = datetime.now(TZ)
# Today
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
today_end = today_start + timedelta(days=1)
# Tomorrow
tomorrow_end = today_end + timedelta(days=1)
today_events = get_events(service, today_start, today_end)
tomorrow_events = get_events(service, today_end, tomorrow_end)
result = {
'today': [format_event(e) for e in today_events],
'tomorrow': [format_event(e) for e in tomorrow_events]
}
return result
def check_week():
"""Get events for this week (Mon-Sun)."""
service = get_service()
now = datetime.now(TZ)
# Start of this week (Monday)
days_since_monday = now.weekday()
week_start = now.replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=days_since_monday)
week_end = week_start + timedelta(days=7)
events = get_events(service, week_start, week_end)
return {
'week_start': week_start.strftime('%d %b'),
'week_end': (week_end - timedelta(days=1)).strftime('%d %b'),
'events': [format_event(e) for e in events]
}
def check_travel_upcoming():
"""Check for travel events in next 14 days that need booking (7-11 days out)."""
service = get_service()
now = datetime.now(TZ)
# Look 14 days ahead
future = now + timedelta(days=14)
events = get_events(service, now, future)
reminders = []
for event in events:
formatted = format_event(event)
if formatted['is_travel']:
# Calculate days until event
start_str = event['start'].get('dateTime', event['start'].get('date'))
if 'T' in start_str:
event_date = datetime.fromisoformat(start_str).date()
else:
event_date = datetime.fromisoformat(start_str).date()
days_until = (event_date - now.date()).days
# Remind if 7-11 days away (booking window)
if 7 <= days_until <= 11:
reminders.append({
**formatted,
'days_until': days_until,
'action': 'Cumpără bilete tren + asigură cazare București'
})
# Urgent if 3-6 days away and might have missed window
elif 3 <= days_until <= 6:
reminders.append({
**formatted,
'days_until': days_until,
'action': '⚠️ URGENT: Verifică dacă ai bilete și cazare!'
})
return {'travel_reminders': reminders}
def is_busy_now():
"""Check if there's an event happening RIGHT NOW."""
service = get_service()
now = datetime.now(TZ)
# Check events that started before now and end after now
events_result = service.events().list(
calendarId='primary',
timeMin=(now - timedelta(hours=4)).isoformat(),
timeMax=(now + timedelta(minutes=30)).isoformat(),
singleEvents=True,
orderBy='startTime'
).execute()
for event in events_result.get('items', []):
start_str = event['start'].get('dateTime', event['start'].get('date'))
end_str = event['end'].get('dateTime', event['end'].get('date'))
# Skip all-day events for "busy now" check
if 'T' not in start_str:
continue
start = datetime.fromisoformat(start_str)
end = datetime.fromisoformat(end_str)
if start <= now <= end:
return {
'busy': True,
'event': event.get('summary', '(fără titlu)'),
'ends': end.strftime('%H:%M')
}
return {'busy': False}
def check_upcoming_hours(hours=2):
"""Check for events in the next N hours."""
service = get_service()
now = datetime.now(TZ)
future = now + timedelta(hours=hours)
events = get_events(service, now, future)
alerts = []
for event in events:
start_str = event['start'].get('dateTime', event['start'].get('date'))
summary = event.get('summary', '(fără titlu)')
if 'T' in start_str:
start = datetime.fromisoformat(start_str)
minutes_until = int((start - now).total_seconds() / 60)
if minutes_until > 0:
alerts.append({
'summary': summary,
'minutes_until': minutes_until,
'time': start.strftime('%H:%M')
})
return {'upcoming': alerts}
def main():
if len(sys.argv) < 2:
print("Usage: calendar_check.py [today|week|travel|busy|soon|all]")
sys.exit(1)
mode = sys.argv[1].lower()
if mode == 'today':
result = check_today_tomorrow()
elif mode == 'week':
result = check_week()
elif mode == 'travel':
result = check_travel_upcoming()
elif mode == 'busy':
result = is_busy_now()
elif mode == 'soon':
hours = int(sys.argv[2]) if len(sys.argv) > 2 else 2
result = check_upcoming_hours(hours)
elif mode == 'all':
result = {
**check_today_tomorrow(),
**check_week(),
**check_travel_upcoming()
}
else:
print(f"Unknown mode: {mode}")
sys.exit(1)
print(json.dumps(result, ensure_ascii=False, indent=2))
if __name__ == '__main__':
main()
def create_event(summary, start_datetime, duration_minutes=60, description=None,
reminders=None, is_travel=False):
"""
Create a calendar event with reminders.
Args:
summary: Event title
start_datetime: datetime object or ISO string (e.g., "2026-02-05T15:00:00")
duration_minutes: Duration in minutes (default 60)
description: Optional description
reminders: List of minutes before event for reminders, or None for defaults
e.g., [120, 30] = 2 hours and 30 min before
is_travel: If True, uses travel reminders (evening before + 2h before)
Returns:
Created event details
"""
service = get_service()
# Parse start time if string
if isinstance(start_datetime, str):
start = datetime.fromisoformat(start_datetime)
else:
start = start_datetime
# Ensure timezone
if start.tzinfo is None:
start = start.replace(tzinfo=TZ)
end = start + timedelta(minutes=duration_minutes)
# Set up reminders
if reminders is None:
if is_travel:
# Travel: evening before (calculate minutes to 18:00 day before) + 2h before
evening_before = start.replace(hour=18, minute=0, second=0) - timedelta(days=1)
minutes_to_evening = int((start - evening_before).total_seconds() / 60)
reminders = [minutes_to_evening, 120] # Evening before + 2 hours
else:
# Default: 30 min before
reminders = [30]
event = {
'summary': summary,
'start': {
'dateTime': start.isoformat(),
'timeZone': 'Europe/Bucharest',
},
'end': {
'dateTime': end.isoformat(),
'timeZone': 'Europe/Bucharest',
},
'reminders': {
'useDefault': False,
'overrides': [
{'method': 'popup', 'minutes': m} for m in reminders
],
},
}
if description:
event['description'] = description
created = service.events().insert(calendarId='primary', body=event).execute()
return {
'id': created['id'],
'summary': created['summary'],
'start': created['start'].get('dateTime'),
'link': created.get('htmlLink'),
'reminders': reminders
}

179
tools/content_discovery.py Executable file
View File

@@ -0,0 +1,179 @@
#!/usr/bin/env python3
"""
Content Discovery - Căutare automată de conținut bazată pe interese.
Rulează noaptea, pregătește note pentru morning report.
Usage: python3 content_discovery.py [--dry-run]
"""
import os
import json
import re
from datetime import datetime, timedelta
from pathlib import Path
WORKSPACE = Path(__file__).parent.parent
MEMORY_DIR = WORKSPACE / "memory"
INSIGHTS_DIR = WORKSPACE / "kb" / "insights"
USER_MD = WORKSPACE / "USER.md"
# Interese de bază (fallback)
BASE_INTERESTS = [
"NLP Sleight of Mouth patterns",
"comunicare nonviolentă Marshall Rosenberg",
"James Clear atomic habits productivity",
"Monica Ion mindset antreprenor",
"dezvoltare personală coaching",
"Rumi quotes wisdom philosophy",
"stoicism practical philosophy",
"noua medicină germanică",
"post negru fasting benefits",
"80/20 principle productivity",
"leadership entrepreneurship",
]
def get_recent_files(directory: Path, days: int = 3) -> list:
"""Get files modified in last N days"""
cutoff = datetime.now() - timedelta(days=days)
files = []
if directory.exists():
for f in directory.glob("*.md"):
if f.stat().st_mtime > cutoff.timestamp():
files.append(f)
return sorted(files, key=lambda x: x.stat().st_mtime, reverse=True)
def extract_topics_from_file(filepath: Path) -> list:
"""Extract potential topics/keywords from a markdown file"""
topics = []
try:
content = filepath.read_text(encoding='utf-8')
# Extract from headers
headers = re.findall(r'^##?\s+(.+)$', content, re.MULTILINE)
topics.extend(headers[:5])
# Extract YouTube video titles
yt_titles = re.findall(r'^#\s+(.+)$', content, re.MULTILINE)
topics.extend(yt_titles[:3])
# Extract @tags
tags = re.findall(r'@(\w+)', content)
topics.extend(tags[:5])
# Extract bold terms
bold = re.findall(r'\*\*([^*]+)\*\*', content)
topics.extend([b for b in bold if len(b) < 50][:5])
except Exception as e:
print(f" Warning: Could not read {filepath}: {e}")
return list(set(topics))
def get_recent_topics() -> list:
"""Analyze recent memory and insights to find current interests"""
recent_topics = []
# Check recent memory files
print("Scanning recent memory...")
for f in get_recent_files(MEMORY_DIR, days=3):
topics = extract_topics_from_file(f)
recent_topics.extend(topics)
print(f" {f.name}: {len(topics)} topics")
# Check recent insights
print("Scanning recent insights...")
for f in get_recent_files(INSIGHTS_DIR, days=3):
topics = extract_topics_from_file(f)
recent_topics.extend(topics)
print(f" {f.name}: {len(topics)} topics")
# Check recent YouTube notes
yt_dir = WORKSPACE / "kb" / "youtube"
print("Scanning recent YouTube notes...")
for f in get_recent_files(yt_dir, days=3):
topics = extract_topics_from_file(f)
recent_topics.extend(topics)
print(f" {f.name}: {len(topics)} topics")
return list(set(recent_topics))
def build_search_queries(recent_topics: list, base_interests: list) -> list:
"""Build search queries with 60% recent, 40% base interests"""
queries = []
# Filter and clean topics
recent_clean = [t for t in recent_topics if len(t) > 3 and len(t) < 100][:10]
# 60% from recent (if available)
if recent_clean:
# Pick top 2-3 recent topics
for topic in recent_clean[:3]:
queries.append({
"query": f"{topic} YouTube tutorial",
"source": "recent",
"topic": topic
})
# 40% from base interests (rotate based on day)
day_of_year = datetime.now().timetuple().tm_yday
rotated_base = base_interests[day_of_year % len(base_interests):]
rotated_base.extend(base_interests[:day_of_year % len(base_interests)])
for interest in rotated_base[:2]:
queries.append({
"query": interest,
"source": "base",
"topic": interest
})
return queries[:5] # Max 5 queries
def save_discovery_plan(queries: list):
"""Save the discovery plan for the agent to execute"""
plan = {
"generated_at": datetime.now().isoformat(),
"queries": queries,
"status": "pending",
"results": []
}
plan_file = WORKSPACE / "memory" / "content-discovery-plan.json"
with open(plan_file, 'w', encoding='utf-8') as f:
json.dump(plan, f, indent=2, ensure_ascii=False)
print(f"\n✅ Plan saved to {plan_file}")
return plan_file
def main(dry_run: bool = False):
print("=" * 50)
print("🔍 Content Discovery - Pregătire căutare")
print(f" Data: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
print("=" * 50)
# 1. Get recent topics
recent_topics = get_recent_topics()
print(f"\n📌 Topics recente găsite: {len(recent_topics)}")
if recent_topics:
print(f" Exemple: {recent_topics[:5]}")
# 2. Build search queries
queries = build_search_queries(recent_topics, BASE_INTERESTS)
print(f"\n🔎 Queries generate: {len(queries)}")
for i, q in enumerate(queries, 1):
print(f" {i}. [{q['source']}] {q['query'][:60]}...")
if dry_run:
print("\n⚠️ DRY RUN - nu salvez planul")
return
# 3. Save plan for agent execution
plan_file = save_discovery_plan(queries)
print("\n📋 Următorul pas:")
print(" Agentul va citi planul și va executa căutările")
print(" Rezultatele vor fi în morning report")
if __name__ == "__main__":
import sys
dry_run = "--dry-run" in sys.argv
main(dry_run)

101
tools/email_check.py Normal file
View File

@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""
IMAP inbox checker for moltbot@romfast.ro
Returns unread emails as JSON
"""
import imaplib
import email
from email.header import decode_header
import json
import sys
from datetime import datetime
# IMAP Configuration
IMAP_SERVER = "mail.romfast.ro"
IMAP_PORT = 993
IMAP_USER = "moltbot@romfast.ro"
IMAP_PASS = "parola281234"
def decode_mime_header(header):
"""Decode MIME encoded header"""
if not header:
return ""
decoded = decode_header(header)
result = []
for part, encoding in decoded:
if isinstance(part, bytes):
result.append(part.decode(encoding or 'utf-8', errors='replace'))
else:
result.append(part)
return ''.join(result)
def get_email_body(msg):
"""Extract email body text"""
body = ""
if msg.is_multipart():
for part in msg.walk():
content_type = part.get_content_type()
if content_type == "text/plain":
try:
body = part.get_payload(decode=True).decode('utf-8', errors='replace')
break
except:
pass
else:
try:
body = msg.get_payload(decode=True).decode('utf-8', errors='replace')
except:
pass
return body[:2000] # Limit body length
def check_inbox(unread_only=True, limit=10):
"""Check inbox and return emails"""
try:
# Connect to IMAP
mail = imaplib.IMAP4_SSL(IMAP_SERVER, IMAP_PORT)
mail.login(IMAP_USER, IMAP_PASS)
mail.select("INBOX")
# Search for emails
criteria = "UNSEEN" if unread_only else "ALL"
status, messages = mail.search(None, criteria)
if status != "OK":
return {"ok": False, "error": "Search failed"}
email_ids = messages[0].split()
email_ids = email_ids[-limit:] # Get last N
emails = []
for eid in reversed(email_ids): # Newest first
status, msg_data = mail.fetch(eid, "(RFC822)")
if status != "OK":
continue
raw_email = msg_data[0][1]
msg = email.message_from_bytes(raw_email)
emails.append({
"id": eid.decode(),
"from": decode_mime_header(msg["From"]),
"subject": decode_mime_header(msg["Subject"]),
"date": msg["Date"],
"body_preview": get_email_body(msg)[:500]
})
mail.logout()
return {
"ok": True,
"unread_count": len(emails),
"emails": emails
}
except Exception as e:
return {"ok": False, "error": str(e)}
if __name__ == "__main__":
unread = "--all" not in sys.argv
result = check_inbox(unread_only=unread)
print(json.dumps(result, indent=2, ensure_ascii=False))

238
tools/email_process.py Executable file
View File

@@ -0,0 +1,238 @@
#!/usr/bin/env python3
"""
Process emails from echo@romfast.ro inbox.
Saves emails as notes in memory/kb/emails/ for further insight extraction.
Usage:
python3 email_process.py # List unread emails
python3 email_process.py --save # Save unread emails as notes
python3 email_process.py --all # List all emails
"""
import imaplib
import email
import os
import sys
import re
import json
from email.header import decode_header
from datetime import datetime
from pathlib import Path
# Load .env
env_path = Path(__file__).parent.parent / '.env'
if env_path.exists():
with open(env_path) as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
os.environ.setdefault(key, value)
# Config
IMAP_SERVER = os.environ.get('EMAIL_SERVER', 'mail.romfast.ro')
IMAP_PORT = 993
IMAP_USER = os.environ.get('EMAIL_USER', 'echo@romfast.ro')
IMAP_PASS = os.environ.get('EMAIL_PASSWORD', '')
# Whitelist - only process emails from these addresses
WHITELIST = [
'mmarius28@gmail.com',
'marius.mutu@romfast.ro',
]
KB_PATH = Path(__file__).parent.parent / 'kb' / 'emails'
def slugify(text: str, max_len: int = 50) -> str:
"""Convert text to URL-friendly slug"""
text = text.lower()
text = re.sub(r'[^\w\s-]', '', text)
text = re.sub(r'[\s_]+', '-', text)
text = re.sub(r'-+', '-', text).strip('-')
return text[:max_len]
def decode_mime_header(header):
"""Decode MIME encoded header"""
if not header:
return ""
decoded_parts = []
for part, encoding in decode_header(header):
if isinstance(part, bytes):
decoded_parts.append(part.decode(encoding or 'utf-8', errors='replace'))
else:
decoded_parts.append(part)
return ' '.join(decoded_parts)
def get_email_body(msg):
"""Extract plain text body from email"""
body = ""
if msg.is_multipart():
for part in msg.walk():
content_type = part.get_content_type()
if content_type == "text/plain":
payload = part.get_payload(decode=True)
if payload:
charset = part.get_content_charset() or 'utf-8'
body = payload.decode(charset, errors='replace')
break
elif content_type == "text/html" and not body:
# Fallback to HTML if no plain text
payload = part.get_payload(decode=True)
if payload:
charset = part.get_content_charset() or 'utf-8'
body = payload.decode(charset, errors='replace')
else:
payload = msg.get_payload(decode=True)
if payload:
charset = msg.get_content_charset() or 'utf-8'
body = payload.decode(charset, errors='replace')
return body.strip()
def extract_sender_email(from_header: str) -> str:
"""Extract just the email address from From header"""
match = re.search(r'<([^>]+)>', from_header)
if match:
return match.group(1).lower()
return from_header.lower().strip()
def list_emails(show_all=False):
"""List emails in inbox"""
mail = imaplib.IMAP4_SSL(IMAP_SERVER, IMAP_PORT)
mail.login(IMAP_USER, IMAP_PASS)
mail.select('INBOX')
search_criteria = 'ALL' if show_all else 'UNSEEN'
status, messages = mail.search(None, search_criteria)
email_ids = messages[0].split() if messages[0] else []
emails = []
for eid in email_ids:
status, data = mail.fetch(eid, '(RFC822)')
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'])
date = msg['Date']
emails.append({
'id': eid.decode(),
'from': from_addr,
'sender_email': sender_email,
'subject': subject,
'date': date,
'whitelisted': sender_email in WHITELIST
})
mail.logout()
return emails
def save_email_as_note(eid: str) -> dict:
"""Save a single email as a markdown note"""
mail = imaplib.IMAP4_SSL(IMAP_SERVER, IMAP_PORT)
mail.login(IMAP_USER, IMAP_PASS)
mail.select('INBOX')
status, data = mail.fetch(eid.encode(), '(RFC822)')
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'])
date_str = msg['Date']
body = get_email_body(msg)
# Check whitelist
if sender_email not in WHITELIST:
mail.logout()
return {'ok': False, 'error': f'Sender {sender_email} not in whitelist'}
# Parse date
try:
# Try common date formats
for fmt in ['%a, %d %b %Y %H:%M:%S %z', '%d %b %Y %H:%M:%S %z']:
try:
parsed_date = datetime.strptime(date_str.split(' (')[0].strip(), fmt)
break
except:
continue
else:
parsed_date = datetime.now()
except:
parsed_date = datetime.now()
date_prefix = parsed_date.strftime('%Y-%m-%d')
slug = slugify(subject) or 'email'
filename = f"{date_prefix}_{slug}.md"
filepath = KB_PATH / filename
# Create markdown note
content = f"""# {subject}
**De la:** {from_addr}
**Data:** {date_str}
**Salvat:** {datetime.now().strftime('%Y-%m-%d %H:%M')}
---
{body}
---
## TL;DR
<!-- Echo: completează cu rezumat -->
## Insights
<!-- Echo: extrage idei acționabile cu tag-uri @work @health @growth etc -->
"""
KB_PATH.mkdir(parents=True, exist_ok=True)
filepath.write_text(content, encoding='utf-8')
# Mark as seen
mail.store(eid.encode(), '+FLAGS', '\\Seen')
mail.logout()
return {
'ok': True,
'file': str(filepath),
'subject': subject,
'from': sender_email
}
def save_unread_emails():
"""Save all unread whitelisted emails as notes"""
emails = list_emails(show_all=False)
results = []
for em in emails:
if em['whitelisted']:
result = save_email_as_note(em['id'])
results.append(result)
return results
if __name__ == "__main__":
if '--save' in sys.argv:
results = save_unread_emails()
for r in results:
if r['ok']:
print(f"✅ Salvat: {r['file']}")
else:
print(f"❌ Eroare: {r['error']}")
if not results:
print("Niciun email nou de la adrese whitelisted.")
else:
show_all = '--all' in sys.argv
emails = list_emails(show_all=show_all)
if not emails:
print("Inbox gol." if show_all else "Niciun email necitit.")
else:
for em in emails:
wl = "" if em['whitelisted'] else "⚠️"
print(f"{wl} [{em['id']}] {em['subject']}")
print(f" De la: {em['from']}")
print(f" Data: {em['date']}")
print()

86
tools/email_send.py Normal file
View File

@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""
Simple SMTP email sender for echo@romfast.ro
Usage: python3 email_send.py "recipient@email.com" "Subject" "Body text"
"""
import smtplib
import ssl
import sys
import os
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.utils import formataddr
from pathlib import Path
# Load .env file
env_path = Path(__file__).parent.parent / '.env'
if env_path.exists():
with open(env_path) as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
os.environ.setdefault(key, value)
# SMTP Configuration from environment
# Try Gmail first, fall back to romfast
if os.environ.get('GMAIL_PASSWORD'):
SMTP_SERVER = 'smtp.gmail.com'
SMTP_PORT = 465
SMTP_USER = os.environ.get('GMAIL_USER', 'mmarius28@gmail.com')
SMTP_PASS = os.environ.get('GMAIL_PASSWORD', '')
else:
SMTP_SERVER = os.environ.get('EMAIL_SERVER', 'mail.romfast.ro')
SMTP_PORT = 465
SMTP_USER = os.environ.get('EMAIL_USER', 'echo@romfast.ro')
SMTP_PASS = os.environ.get('EMAIL_PASSWORD', '')
FROM_NAME = "Echo"
def send_email(to_email: str, subject: str, body: str, html: bool = False) -> dict:
"""Send an email via SMTP SSL"""
try:
# Create message
msg = MIMEMultipart("alternative")
msg["Subject"] = Header(subject, 'utf-8')
msg["From"] = formataddr((FROM_NAME, SMTP_USER))
msg["To"] = to_email
msg["Reply-To"] = "echo@romfast.ro"
# Attach body
if html:
msg.attach(MIMEText(body, "html", "utf-8"))
else:
msg.attach(MIMEText(body, "plain", "utf-8"))
# Connect and send
context = ssl.create_default_context()
with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context) as server:
server.login(SMTP_USER, SMTP_PASS)
server.sendmail(SMTP_USER, to_email, msg.as_string())
return {"ok": True, "to": to_email, "subject": subject}
except Exception as e:
return {"ok": False, "error": str(e)}
if __name__ == "__main__":
if len(sys.argv) < 4:
print("Usage: python3 email_send.py <to> <subject> <body> [--html]")
sys.exit(1)
to = sys.argv[1]
subject = sys.argv[2]
body = sys.argv[3]
# Auto-detect HTML or use --html flag
# Check for common HTML patterns, not just doctype/html tags
body_lower = body.strip().lower()
has_html_tags = any(tag in body_lower for tag in ['<html', '<!doctype', '<div', '<p>', '<br', '<table', '<h1', '<h2', '<h3', '<span', '<style'])
is_html = "--html" in sys.argv or has_html_tags
result = send_email(to, subject, body, html=is_html)
import json
print(json.dumps(result))

113
tools/generate_pdf.py Normal file
View File

@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""
Generate PDF from markdown content.
Outputs PDF to stdout as binary.
Simple, robust approach focusing on text content.
"""
import sys
import json
from pathlib import Path
# Read JSON from stdin
input_data = json.load(sys.stdin)
markdown_content = input_data.get('markdown', '')
filename = input_data.get('filename', 'document.pdf')
try:
from fpdf import FPDF
import re
# Create PDF
pdf = FPDF(format='A4')
pdf.add_page()
pdf.set_margins(12, 12, 12)
# Try to use DejaVu font for Romanian support
try:
dejavu_path = Path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
if dejavu_path.exists():
pdf.add_font("DejaVu", "", str(dejavu_path))
pdf.add_font("DejaVu", "B", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf")
pdf.set_font("DejaVu", "", 10)
use_dejavu = True
else:
raise Exception("DejaVu font not found")
except:
pdf.set_font("Helvetica", "", 10)
use_dejavu = False
# Parse markdown line by line
lines = markdown_content.split('\n')
i = 0
while i < len(lines):
line = lines[i]
# Skip empty lines but add spacing
if not line.strip():
pdf.ln(2)
i += 1
continue
# H1 - Main heading
if line.startswith('# '):
pdf.set_font("DejaVu" if use_dejavu else "Helvetica", "B", 16)
text = line.replace('# ', '', 1).strip()
pdf.multi_cell(0, 7, text, ln=True)
pdf.ln(1)
pdf.set_font("DejaVu" if use_dejavu else "Helvetica", "", 10)
# H2 - Section heading
elif line.startswith('## '):
pdf.set_font("DejaVu" if use_dejavu else "Helvetica", "B", 12)
text = line.replace('## ', '', 1).strip()
pdf.multi_cell(0, 6, text, ln=True)
pdf.ln(0.5)
pdf.set_font("DejaVu" if use_dejavu else "Helvetica", "", 10)
# H3 - Subsection
elif line.startswith('### '):
pdf.set_font("DejaVu" if use_dejavu else "Helvetica", "B", 11)
text = line.replace('### ', '', 1).strip()
pdf.multi_cell(0, 5, text, ln=True)
pdf.ln(0.3)
pdf.set_font("DejaVu" if use_dejavu else "Helvetica", "", 10)
# Bullet point
elif line.strip().startswith('- ') or line.strip().startswith('* '):
text = line.strip().lstrip('-*').strip()
# Use simple dash for bullet
pdf.multi_cell(0, 5, '- ' + text, ln=True)
# Numbered list
elif re.match(r'^\s*\d+\.\s', line):
text = re.sub(r'^\s*\d+\.\s', '', line)
pdf.multi_cell(0, 5, text, ln=True)
# Regular text with formatting
else:
# Clean up markdown markers but keep structure
text = line.strip()
# Remove inline markdown
text = re.sub(r'\*\*(.*?)\*\*', r'\1', text) # Bold
text = re.sub(r'__(.*?)__', r'\1', text) # Bold
text = re.sub(r'\*(.*?)\*', r'\1', text) # Italic
text = re.sub(r'_(.*?)_', r'\1', text) # Italic
text = re.sub(r'\[(.*?)\]\(.*?\)', r'\1', text) # Links
if text:
pdf.multi_cell(0, 5, text, ln=True)
i += 1
# Output PDF
pdf_bytes = pdf.output()
sys.stdout.buffer.write(pdf_bytes)
sys.exit(0)
except Exception as e:
error_json = json.dumps({'error': str(e)})
sys.stderr.write(error_json)
sys.exit(1)

96
tools/git_commit.py Executable file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""
Git commit helper - auto-generates commit message from changed files
Usage: python3 git_commit.py [--push] [--dry-run]
"""
import subprocess
import sys
import os
from datetime import datetime
REPO_PATH = os.path.expanduser("~/clawd")
def run(cmd, capture=True):
result = subprocess.run(cmd, shell=True, cwd=REPO_PATH,
capture_output=capture, text=True)
return result.stdout.strip() if capture else result.returncode
def get_status():
"""Get git status summary"""
status = run("git status --porcelain")
if not status:
return None, []
files = []
for line in status.split('\n'):
if line.strip():
status_code = line[:2]
filename = line[3:]
files.append((status_code.strip(), filename))
return status, files
def generate_message(files):
"""Generate commit message from changed files"""
# Group by directory/type
areas = set()
for _, f in files:
if '/' in f:
areas.add(f.split('/')[0])
else:
areas.add('root')
# Count changes
added = len([f for s, f in files if 'A' in s or '?' in s])
modified = len([f for s, f in files if 'M' in s])
deleted = len([f for s, f in files if 'D' in s])
parts = []
if added: parts.append(f"+{added}")
if modified: parts.append(f"~{modified}")
if deleted: parts.append(f"-{deleted}")
change_summary = " ".join(parts) if parts else "changes"
area_list = ", ".join(sorted(areas)[:3])
if len(areas) > 3:
area_list += f" +{len(areas)-3} more"
return f"Update {area_list} ({change_summary})"
def main():
dry_run = "--dry-run" in sys.argv
push = "--push" in sys.argv
status, files = get_status()
if not status:
print('{"status": "clean", "message": "Nothing to commit"}')
return 0
print(f"Files changed: {len(files)}")
for s, f in files[:10]:
print(f" [{s}] {f}")
if len(files) > 10:
print(f" ... and {len(files)-10} more")
message = generate_message(files)
print(f"\nCommit message: {message}")
if dry_run:
print("\n[DRY RUN - no changes made]")
return 0
# Stage all and commit
run("git add -A", capture=False)
result = run(f'git commit -m "{message}"')
print(f"\n{result}")
if push:
print("\nPushing...")
push_result = run("git push 2>&1")
print(push_result)
return 0
if __name__ == "__main__":
sys.exit(main())

68
tools/lead-gen/README.md Normal file
View File

@@ -0,0 +1,68 @@
# Lead Generation Minimal - ROA
Sistem simplu pentru găsirea companiilor care au nevoie de soluții ERP/contabilitate.
## Cum funcționează
1. **find_leads.py** - Caută companii care angajează contabili/economiști
- Folosește Brave Search API
- Extrage nume de companii din rezultatele de pe eJobs, BestJobs, etc.
- Salvează în CSV pentru review manual
2. **templates/** - Template-uri email pentru outreach
- `template_general.md` - Template generic pentru cold email
## Usage
```bash
# Activează venv
cd ~/clawd && source venv/bin/activate
# Rulează căutarea
python tools/lead-gen/find_leads.py --limit 10
# Output: tools/lead-gen/output/leads.csv
```
## Workflow manual
1. **Rulează scriptul** (5 min/săptămână)
```bash
python tools/lead-gen/find_leads.py
```
2. **Review CSV** (5 min)
- Deschide `output/leads.csv`
- Marchează companiile interesante
- Completează CUI, email, website (căutare manuală Google)
3. **Trimite email** (per companie)
- Folosește template din `templates/`
- Personalizează cu numele companiei și jobul
## Status tracking
În CSV, câmpul `status`:
- `new` - proaspăt găsit
- `researched` - am completat datele
- `contacted` - am trimis email
- `replied` - au răspuns
- `converted` - client nou! 🎉
- `rejected` - nu sunt interesați
## TODO (îmbunătățiri viitoare)
- [ ] Auto-enrich cu CUI de pe ANAF
- [ ] Validare email (MX lookup)
- [ ] Template-uri per industrie
- [ ] Integrare cu Gmail pentru trimitere
- [ ] Dashboard cu statistici
## De ce funcționează
Companiile care **angajează contabili** au nevoie de:
- Software de contabilitate
- Poate nu sunt mulțumiți de soluția curentă
- Sau cresc și au nevoie de ceva mai robust
Targetarea lor e mult mai eficientă decât cold email random.

View File

@@ -0,0 +1,26 @@
NUME,RULAJ_ANUAL,COD_FISCAL,RAMURA,NOTE
XENOTI S.R.L.,83099.1,RO6743250,HOTEL,"VALENTINA ESTE EXPERT CONTABIL SI DIRECTOR LA XENOTI. SUNTEM CUNOSTINTE VECHI, LA FEL CA SI CU ALEX STEFAN. IN VIRTUTEA RELATIEI VECHI DE PE ALTE FIRME, A VRUT PROGRAMUL ROA SI LA XENOTI. VALENTINA ARE SI ALTE FIRME. FACTUREZ TOTUL PE XENOTI, INCLUSIV PROIECTE DE MIGRARE ALE UNOR FIRME ALE VALENTINEI PE ROA SI DE ACEEA ARE CEA MAI MARE VALOARE. INTRE TIMP AM 3 FACTURI NEACHITATE DE LA XENOTI. NU A AVUT VANZARI BUNE IN ULTIMUL SEZON ESTIVAL"
ROMPETROL ENERGY SA,57928.23,RO29923675,ENERGIE TERMICA,"FOSTA UTMIDIA NAVODARI, CLIENT VECHI, CLIENT IDEAL, FACTURI PUTINE, VALOARE MARE, CLIENTI PUTINI DAR MARI, LE-AM FACUT MIGRARE ROA DE LA UTMIDIA NAVODARI. AU SPUS CA TREC LA PROGRAMELE INTERNATIONALE FOLOSITE DE ROMPETROL SA, DAR INCA NU AU TRECUT DE CATIVA ANI"
ADMINISTRATIA CANALELOR NAVIGABILE,39907.84,RO11087755,COMPANIE NATIONALA,"ACUM 15 ANI AU AVUT PROGRAME CONTABILE VECHI SI DE FACTURARE SI AU AVUT NEVOIE DE ALTE PROGRAME. CLIENTI VECHI, STABILI. ACUM NECESITA MENTENANTA PUTINA. A FOST MAI GREU SA LE CRESC PRETURILE. SUNT RETICENTI. CLIENT IDEAL"
VENDING MASTER SRL,39270,RO33137200,"DISTRIBUTIE CAFEA, AUTOMATE CAFEA","AVIS DATABASE ACCOUNTING ESTE CONTABILUL LUI, ALEX STEFAN, EXPERTUL CONTABIL DE LA AVIS DATABASE ESTE UN FOST COLEG SI PRIN EL AM LUAT FIRMA VENDING CLIENT, DE CAND ERA MAI MICA CA DIMENSIUNE. I-AM CRESCUT TARIFUL IN DECURS DE MAI MULTI ANI PENTRU CA I-A CRESCUT ACTIVITATEA SI SUPORTUL TEHNIC"
VADECO SRL,34012.67,RO14707452,SERVICII TRANSPORT,"CLIENT IDEAL, SERVICII, ARE CLIENTI FOARTE PUTINI, DAR DE VALOARE MARE, 4 PERSOANE LA FACTURARE, AGENTI, CONTABIL SI DIRECTOR ECONOMIC CU CARE LUCREZ"
EUROPEAN METAL SERVICES SA,32584.67,RO12629765,SERVICII FIER VECHI,"CLIENT IDEAL, SERVICII, ARE CLIENTI FOARTE PUTINI, DAR DE VALOARE MARE, 1 CONTABIL SI DIRECTOR ECONOMIC CU CARE LUCREZ. ARE SI ALTI ANGAJATI BINEINTELES"
SOUTH EAST TRUCK SERVICES S.R.L.,31136.42,RO43525632,SERVICE AUTO,"CLIENT IDEAL. CLIENTI PUTINI (POLARIS M HOLDING) DAR MARI. UN RECEPTIONER/GESTIONAR, MECANICI SI PATRONUL CARE ESTE INGINER AUTO"
MIDIA GREEN ENERGY SA,19269.2,RO14325363,ENERGIE FOTOVOLTAICA,CLIENT FOARTE VECHI. S-A DESPRINS DIN UTMIDIA NAVODARI CARE A FOST CONVERTITA IN ROMPETROL ENERGY SA. CLIENT IDEAL. LUCREZ DOAR CU DIRECTORUL ECONOMIC CAND ARE NEVOIE DE ASISTENTA LA DECLARATII SAU OPERATII IMOBILIZARI
INTREPRINDEREA METALURGICA PENTRU AERONAUTICA META,18102.62,RO16036329,PRODUCTIE,"CLIENT FOARTE VECHI. IDEAL. CA SI EDUARD PUBLISHING A AVUT NEVOI CONTABILE SPORITE PENTRU URMARIREA PRODUCTIEI, COSTURILOR SI ANALIZA CHELTUIELILOR, BUGETELOR"
ARGENTA SRL,14482.07,RO3959705,CONSTRUCTII,"CLIENT IDEAL. CLIENTI PUTINI, VALOARE MARE. ARE UN PROGRAM SPECIAL DE URMARIRE PROIECTE/DEVIZE/OFERTE COMPARATIE COSTURI FATA DE OFERTA, PENTRU ANALIZA RANDAMENT LUCRARI CONSTRUCTIE"
ETALON DISTRIBUTION S R L,14380,RO42158724,DISTRIBUTIE PRESA,CLIENT VECHI PROVENIT DIN ALT CLIENT - PRIN TRANSFER ACTIVE. VALENTINA ESTE DIRECTOR ECONOMIC SI LA ETALON. AM CONTINUAT ACTIVITATEA CU ETALON PRIN VALENTINA
A.B.C. VAL,13487.42,RO3853010,CONSTRUCTII,"A INTRAT IN FALIMENT. NU MAI ESTE CLIENT. CA SI ARGENTA SA, AVEA NEVOI DE URMARIRE PROIECTE, LUCRARI, DEVIZ, OFERTA - AM PROGRAMUL RESPECTIV"
EDUARD PUBLISHING,12084.4,RO 25629015,"EDITURA CARTE, PRODUCTIE CARTE","ACUM O DUCE MAI GREU. LA INCEPUTUL RELATIEI, ACUM 15 ANI AVEA ACTIVITATE MAI MARE SI AVEA NEVOI DE URMARIRE PRODUCTIE DE CARTE"
AUTOMOTIVE SERVICE SRL,10165,RO18448482,SERVICE AUTO,"CLIENT IDEAL. UN PATRON, UN INGINER, PLUS MECANICI. PLATESTE PROMPT. AM PUTINA ACTIVITATE CU EL CA SUPORT TEHNIC. DE VAZUT CE CIFRA DE AFACERI ARE"
CLEVER MOTORS SRL,10165,RO44234984,SERVICE AUTO,"NU PLATESTE LA TIMP, RESTANTE 3-4 LUNI. ESTE UN SERVICE AUTO CARE CRED CA NU ISI PRIMESTE BANII LA TIMP DE LA ASIGURATORI, SAU ARE COMENZI PUTINE. DE VAZUT CE CIFRA DE AFACERI ARE"
ALMMA CONTRACTORS GROUP S.R.L.,9223.46,RO37165512,CONSTRUCTII,A DAT FALIMENT. NU MAI ESTE CLIENT. ERA IN GRUPUL A.B.C VAL
ROMCONSTRUCT GLASS S.R.L.,8615,RO24498302,PRODUCTIE,"PRODUCTIE GEAMURI, MONTAJ. CLIENT IDEAL DAR IN ULTIMII ANI SI-A REDUS ACTIVITATEA. AVEA NEVOIE DE PROGRAM DE PRODUCTIE, CONSUM, FACTURARE. AM FACUT PROGRAMUL DAR S-A DOVEDIT FOARTE COMPLEX FATA DE CALCULELE LOR DIN EXCEL. FOLOSESC DOAR FACTURAREA DE SERVICII, IN LOC DE PRODUCTIE, CONSUM SI FACTURARE PRODUCTIE. PROBLEMA CEA MAI MARE ERA SI ESTE GESTIUNEA STOCURILOR, DEVIZELE CARE SE FAC IN EXCEL, DAR NU SE VAD IN SOFT. LE LUA PREA MULT TIMP SA FOLOSEASCA PROGRAMELE CONSTRUITE SPECIAL PENTRU PRODUCTIE, DESCARCARE PRODUCTIE SI NU LE-AU FOLOSIT."
SIGMA L.C. SERVICE,7274.66,RO21773785,SERVICE AUTO,ARE RESTANTE 3-6 LUNI. E OK CA NIVEL DE CERERI
WERT SRL,6000,RO7435479,PRODUCTIE,ESTE CLIENTUL CARAPETRU CONTAB SI FOLOSESTE PROGRAMELE ROA PENTRU CONTABILITATE PRIMARA. NU EMITE FACTURI DIN ROA.
DIRECTIA DE PAZA A JUDETULUI CONSTANTA,4314,5639774,SERVICII,A RAMAS DOAR CU PROGRAMUL DE GESTIUNE OBIECTE DE INVENTAR. NU MAI ARE ACTIVITATE
DRAFT CONSTRUCT SRL,3600,RO16322932,CONSTRUCTII,A INTRAT IN INSOLVENTA. CLIENT CU PROBLEME FINANCIARE. ESTE CLIENTUL CARAPETRU CONTAB SI FOLOSESTE PROGRAMELE ROA PENTRU CONTABILITATE PRIMARA. NU EMITE FACTURI DIN ROA.
DRAFT DINAMIC CONSTRUCT S.R.L.,3600,RO45269115,CONSTRUCTII,A INTRAT IN INSOLVENTA. CLIENT CU PROBLEME FINANCIARE. ESTE CLIENTUL CARAPETRU CONTAB SI FOLOSESTE PROGRAMELE ROA PENTRU CONTABILITATE PRIMARA. NU EMITE FACTURI DIN ROA.
BIG TRADE S.A.,2958.38,RO7436261,SERVICII,"NU MAI ESTE CLIENTUL MEU. PLATEA PENTRU UN GRUP DE FIRME MICI, CARE AU TRECUT PE SAGA PENTRU CA NU AVEAU ACTIVITATE"
AVIS DATABASE ACCOUNTING S.R.L.,1776.82,40023585,CONTABILITATE,ALEX STEFAN DE LA AVIS DATABASE ACCOUNTING A FOST COLEG CU MINE. SI-A FACUT FIRMA DE CONTABILITATE. VENDING MASTER ESTE CLIENTUL LUI. PRIN ALEX STEFAN L-AM OBTINUT ACUM MULTI ANI.
CARAPETRU CONTAB SRL,300,35295575,CONTABILITATE,"SILVIA DE LA CARAPETRU CONTAB ESTE CONTABILA MEA SI FOLOSESTE PROGRAMELE ROA PENTRU FIRMELE EI. WERT, DRAFT DINAMIC, DRAFT CONSTRUCT, SUNT CLIENTII EI"
1 NUME RULAJ_ANUAL COD_FISCAL RAMURA NOTE
2 XENOTI S.R.L. 83099.1 RO6743250 HOTEL VALENTINA ESTE EXPERT CONTABIL SI DIRECTOR LA XENOTI. SUNTEM CUNOSTINTE VECHI, LA FEL CA SI CU ALEX STEFAN. IN VIRTUTEA RELATIEI VECHI DE PE ALTE FIRME, A VRUT PROGRAMUL ROA SI LA XENOTI. VALENTINA ARE SI ALTE FIRME. FACTUREZ TOTUL PE XENOTI, INCLUSIV PROIECTE DE MIGRARE ALE UNOR FIRME ALE VALENTINEI PE ROA SI DE ACEEA ARE CEA MAI MARE VALOARE. INTRE TIMP AM 3 FACTURI NEACHITATE DE LA XENOTI. NU A AVUT VANZARI BUNE IN ULTIMUL SEZON ESTIVAL
3 ROMPETROL ENERGY SA 57928.23 RO29923675 ENERGIE TERMICA FOSTA UTMIDIA NAVODARI, CLIENT VECHI, CLIENT IDEAL, FACTURI PUTINE, VALOARE MARE, CLIENTI PUTINI DAR MARI, LE-AM FACUT MIGRARE ROA DE LA UTMIDIA NAVODARI. AU SPUS CA TREC LA PROGRAMELE INTERNATIONALE FOLOSITE DE ROMPETROL SA, DAR INCA NU AU TRECUT DE CATIVA ANI
4 ADMINISTRATIA CANALELOR NAVIGABILE 39907.84 RO11087755 COMPANIE NATIONALA ACUM 15 ANI AU AVUT PROGRAME CONTABILE VECHI SI DE FACTURARE SI AU AVUT NEVOIE DE ALTE PROGRAME. CLIENTI VECHI, STABILI. ACUM NECESITA MENTENANTA PUTINA. A FOST MAI GREU SA LE CRESC PRETURILE. SUNT RETICENTI. CLIENT IDEAL
5 VENDING MASTER SRL 39270 RO33137200 DISTRIBUTIE CAFEA, AUTOMATE CAFEA AVIS DATABASE ACCOUNTING ESTE CONTABILUL LUI, ALEX STEFAN, EXPERTUL CONTABIL DE LA AVIS DATABASE ESTE UN FOST COLEG SI PRIN EL AM LUAT FIRMA VENDING CLIENT, DE CAND ERA MAI MICA CA DIMENSIUNE. I-AM CRESCUT TARIFUL IN DECURS DE MAI MULTI ANI PENTRU CA I-A CRESCUT ACTIVITATEA SI SUPORTUL TEHNIC
6 VADECO SRL 34012.67 RO14707452 SERVICII TRANSPORT CLIENT IDEAL, SERVICII, ARE CLIENTI FOARTE PUTINI, DAR DE VALOARE MARE, 4 PERSOANE LA FACTURARE, AGENTI, CONTABIL SI DIRECTOR ECONOMIC CU CARE LUCREZ
7 EUROPEAN METAL SERVICES SA 32584.67 RO12629765 SERVICII FIER VECHI CLIENT IDEAL, SERVICII, ARE CLIENTI FOARTE PUTINI, DAR DE VALOARE MARE, 1 CONTABIL SI DIRECTOR ECONOMIC CU CARE LUCREZ. ARE SI ALTI ANGAJATI BINEINTELES
8 SOUTH EAST TRUCK SERVICES S.R.L. 31136.42 RO43525632 SERVICE AUTO CLIENT IDEAL. CLIENTI PUTINI (POLARIS M HOLDING) DAR MARI. UN RECEPTIONER/GESTIONAR, MECANICI SI PATRONUL CARE ESTE INGINER AUTO
9 MIDIA GREEN ENERGY SA 19269.2 RO14325363 ENERGIE FOTOVOLTAICA CLIENT FOARTE VECHI. S-A DESPRINS DIN UTMIDIA NAVODARI CARE A FOST CONVERTITA IN ROMPETROL ENERGY SA. CLIENT IDEAL. LUCREZ DOAR CU DIRECTORUL ECONOMIC CAND ARE NEVOIE DE ASISTENTA LA DECLARATII SAU OPERATII IMOBILIZARI
10 INTREPRINDEREA METALURGICA PENTRU AERONAUTICA META 18102.62 RO16036329 PRODUCTIE CLIENT FOARTE VECHI. IDEAL. CA SI EDUARD PUBLISHING A AVUT NEVOI CONTABILE SPORITE PENTRU URMARIREA PRODUCTIEI, COSTURILOR SI ANALIZA CHELTUIELILOR, BUGETELOR
11 ARGENTA SRL 14482.07 RO3959705 CONSTRUCTII CLIENT IDEAL. CLIENTI PUTINI, VALOARE MARE. ARE UN PROGRAM SPECIAL DE URMARIRE PROIECTE/DEVIZE/OFERTE COMPARATIE COSTURI FATA DE OFERTA, PENTRU ANALIZA RANDAMENT LUCRARI CONSTRUCTIE
12 ETALON DISTRIBUTION S R L 14380 RO42158724 DISTRIBUTIE PRESA CLIENT VECHI PROVENIT DIN ALT CLIENT - PRIN TRANSFER ACTIVE. VALENTINA ESTE DIRECTOR ECONOMIC SI LA ETALON. AM CONTINUAT ACTIVITATEA CU ETALON PRIN VALENTINA
13 A.B.C. VAL 13487.42 RO3853010 CONSTRUCTII A INTRAT IN FALIMENT. NU MAI ESTE CLIENT. CA SI ARGENTA SA, AVEA NEVOI DE URMARIRE PROIECTE, LUCRARI, DEVIZ, OFERTA - AM PROGRAMUL RESPECTIV
14 EDUARD PUBLISHING 12084.4 RO 25629015 EDITURA CARTE, PRODUCTIE CARTE ACUM O DUCE MAI GREU. LA INCEPUTUL RELATIEI, ACUM 15 ANI AVEA ACTIVITATE MAI MARE SI AVEA NEVOI DE URMARIRE PRODUCTIE DE CARTE
15 AUTOMOTIVE SERVICE SRL 10165 RO18448482 SERVICE AUTO CLIENT IDEAL. UN PATRON, UN INGINER, PLUS MECANICI. PLATESTE PROMPT. AM PUTINA ACTIVITATE CU EL CA SUPORT TEHNIC. DE VAZUT CE CIFRA DE AFACERI ARE
16 CLEVER MOTORS SRL 10165 RO44234984 SERVICE AUTO NU PLATESTE LA TIMP, RESTANTE 3-4 LUNI. ESTE UN SERVICE AUTO CARE CRED CA NU ISI PRIMESTE BANII LA TIMP DE LA ASIGURATORI, SAU ARE COMENZI PUTINE. DE VAZUT CE CIFRA DE AFACERI ARE
17 ALMMA CONTRACTORS GROUP S.R.L. 9223.46 RO37165512 CONSTRUCTII A DAT FALIMENT. NU MAI ESTE CLIENT. ERA IN GRUPUL A.B.C VAL
18 ROMCONSTRUCT GLASS S.R.L. 8615 RO24498302 PRODUCTIE PRODUCTIE GEAMURI, MONTAJ. CLIENT IDEAL DAR IN ULTIMII ANI SI-A REDUS ACTIVITATEA. AVEA NEVOIE DE PROGRAM DE PRODUCTIE, CONSUM, FACTURARE. AM FACUT PROGRAMUL DAR S-A DOVEDIT FOARTE COMPLEX FATA DE CALCULELE LOR DIN EXCEL. FOLOSESC DOAR FACTURAREA DE SERVICII, IN LOC DE PRODUCTIE, CONSUM SI FACTURARE PRODUCTIE. PROBLEMA CEA MAI MARE ERA SI ESTE GESTIUNEA STOCURILOR, DEVIZELE CARE SE FAC IN EXCEL, DAR NU SE VAD IN SOFT. LE LUA PREA MULT TIMP SA FOLOSEASCA PROGRAMELE CONSTRUITE SPECIAL PENTRU PRODUCTIE, DESCARCARE PRODUCTIE SI NU LE-AU FOLOSIT.
19 SIGMA L.C. SERVICE 7274.66 RO21773785 SERVICE AUTO ARE RESTANTE 3-6 LUNI. E OK CA NIVEL DE CERERI
20 WERT SRL 6000 RO7435479 PRODUCTIE ESTE CLIENTUL CARAPETRU CONTAB SI FOLOSESTE PROGRAMELE ROA PENTRU CONTABILITATE PRIMARA. NU EMITE FACTURI DIN ROA.
21 DIRECTIA DE PAZA A JUDETULUI CONSTANTA 4314 5639774 SERVICII A RAMAS DOAR CU PROGRAMUL DE GESTIUNE OBIECTE DE INVENTAR. NU MAI ARE ACTIVITATE
22 DRAFT CONSTRUCT SRL 3600 RO16322932 CONSTRUCTII A INTRAT IN INSOLVENTA. CLIENT CU PROBLEME FINANCIARE. ESTE CLIENTUL CARAPETRU CONTAB SI FOLOSESTE PROGRAMELE ROA PENTRU CONTABILITATE PRIMARA. NU EMITE FACTURI DIN ROA.
23 DRAFT DINAMIC CONSTRUCT S.R.L. 3600 RO45269115 CONSTRUCTII A INTRAT IN INSOLVENTA. CLIENT CU PROBLEME FINANCIARE. ESTE CLIENTUL CARAPETRU CONTAB SI FOLOSESTE PROGRAMELE ROA PENTRU CONTABILITATE PRIMARA. NU EMITE FACTURI DIN ROA.
24 BIG TRADE S.A. 2958.38 RO7436261 SERVICII NU MAI ESTE CLIENTUL MEU. PLATEA PENTRU UN GRUP DE FIRME MICI, CARE AU TRECUT PE SAGA PENTRU CA NU AVEAU ACTIVITATE
25 AVIS DATABASE ACCOUNTING S.R.L. 1776.82 40023585 CONTABILITATE ALEX STEFAN DE LA AVIS DATABASE ACCOUNTING A FOST COLEG CU MINE. SI-A FACUT FIRMA DE CONTABILITATE. VENDING MASTER ESTE CLIENTUL LUI. PRIN ALEX STEFAN L-AM OBTINUT ACUM MULTI ANI.
26 CARAPETRU CONTAB SRL 300 35295575 CONTABILITATE SILVIA DE LA CARAPETRU CONTAB ESTE CONTABILA MEA SI FOLOSESTE PROGRAMELE ROA PENTRU FIRMELE EI. WERT, DRAFT DINAMIC, DRAFT CONSTRUCT, SUNT CLIENTII EI

View File

@@ -0,0 +1,229 @@
#!/usr/bin/env python3
"""
Lead Generator Minimal - Găsește companii care au nevoie de soluții ERP/contabilitate.
Folosește Brave Search API pentru a găsi companii care angajează contabili/economiști.
Output: leads.csv cu companii pentru review manual
Usage:
python find_leads.py [--limit N]
Necesită: BRAVE_API_KEY în environment sau ~/.clawdbot/clawdbot.json
"""
import os
import re
import csv
import json
import argparse
from datetime import datetime
from pathlib import Path
import requests
OUTPUT_DIR = Path(__file__).parent / "output"
OUTPUT_DIR.mkdir(exist_ok=True)
def get_brave_api_key():
"""Get Brave API key from clawdbot config."""
config_path = Path.home() / ".clawdbot" / "clawdbot.json"
if config_path.exists():
with open(config_path) as f:
config = json.load(f)
# Try tools.web.search.apiKey (clawdbot format)
api_key = config.get("tools", {}).get("web", {}).get("search", {}).get("apiKey", "")
if api_key:
return api_key
# Fallback to brave.apiKey
return config.get("brave", {}).get("apiKey", "")
return os.getenv("BRAVE_API_KEY", "")
def search_brave(query, count=10):
"""Search using Brave Search API."""
api_key = get_brave_api_key()
if not api_key:
print("[!] Nu am găsit Brave API key")
return []
url = "https://api.search.brave.com/res/v1/web/search"
headers = {
"X-Subscription-Token": api_key,
"Accept": "application/json"
}
params = {
"q": query,
"count": count
}
try:
resp = requests.get(url, headers=headers, params=params, timeout=15)
data = resp.json()
return data.get("web", {}).get("results", [])
except Exception as e:
print(f"[!] Brave search error: {e}")
return []
def extract_companies_from_results(results):
"""Extract company names from search results."""
companies = []
# Patterns for Romanian companies
patterns = [
r'([A-Z][A-Z\s\-\.&]+(?:S\.R\.L\.|SRL|S\.A\.|SA|S\.C\.))', # COMPANY S.R.L.
r'(SC\s+[A-Z][A-Z\s\-\.&]+(?:S\.R\.L\.|SRL|S\.A\.|SA))', # SC COMPANY SRL
r'([A-Z][a-zA-Z\s\-\.&]{2,30}(?:S\.R\.L\.|SRL|S\.A\.|SA))', # Mixed case
]
for result in results:
text = f"{result.get('title', '')} {result.get('description', '')}"
for pattern in patterns:
matches = re.findall(pattern, text)
for match in matches:
company = match.strip()
# Clean up
company = re.sub(r'\s+', ' ', company)
if len(company) > 5 and len(company) < 80:
companies.append({
"company": company,
"source_url": result.get("url", ""),
"context": text[:200]
})
return companies
def clean_company_name(name):
"""Clean company name from prefix garbage."""
# Remove common prefixes that get caught by regex
prefixes_to_remove = [
r'^(?:Senior|Junior|Contabil|Economist|Director\s+Economic|Expert|Specialist)\s+',
r'^(?:RON|EUR|USD)\s+',
r'^(?:Bucuresti|Cluj|Iasi|Brasov|Constanta)\s+',
r'^\d+[\s\-]+',
]
result = name.strip()
for pattern in prefixes_to_remove:
result = re.sub(pattern, '', result, flags=re.IGNORECASE)
# Clean trailing garbage
result = re.sub(r'\s*-\s*$', '', result)
result = re.sub(r'\s+', ' ', result).strip()
return result
def deduplicate(leads):
"""Elimină duplicate după numele companiei."""
seen = set()
unique = []
for lead in leads:
# Clean company name
lead["company"] = clean_company_name(lead["company"])
# Normalize for comparison
company_norm = re.sub(r'[^a-z0-9]', '', lead["company"].lower())
# Skip too short or invalid
if len(company_norm) < 5:
continue
# Skip obvious non-companies
skip_patterns = [
r'^emea\s',
r'^staff\s',
r'accountant',
r'^bestjobs',
r'^ejobs',
r'^hipo',
]
if any(re.search(p, lead["company"], re.IGNORECASE) for p in skip_patterns):
continue
if company_norm not in seen:
seen.add(company_norm)
unique.append(lead)
return unique
def enrich_leads(leads):
"""Adaugă câmpuri pentru tracking."""
for lead in leads:
lead["found_date"] = datetime.now().isoformat()[:10]
lead["cui"] = ""
lead["email"] = ""
lead["website"] = ""
lead["phone"] = ""
lead["status"] = "new" # new, researched, contacted, replied, converted, rejected
lead["notes"] = ""
lead["industry"] = ""
return leads
def save_leads(leads, filename="leads.csv"):
"""Salvează leads în CSV pentru review."""
output_file = OUTPUT_DIR / filename
fieldnames = ["company", "industry", "source_url", "found_date",
"cui", "email", "website", "phone", "status", "notes"]
# Remove context from output (used only for extraction)
for lead in leads:
lead.pop("context", None)
with open(output_file, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction='ignore')
writer.writeheader()
writer.writerows(leads)
return output_file
def main():
parser = argparse.ArgumentParser(description="Lead Generator Minimal")
parser.add_argument("--limit", type=int, default=10, help="Results per search query")
args = parser.parse_args()
print("🔍 Căutare leads via Brave Search...")
# Search queries - companii care angajează contabili
queries = [
'site:ejobs.ro contabil angajare 2026',
'site:ejobs.ro economist angajare',
'site:bestjobs.eu contabil Romania',
'site:hipo.ro contabil angajare',
'"angajam contabil" Romania firma',
'"cautam economist" Romania SRL',
]
all_leads = []
for query in queries:
print(f"{query[:50]}...")
results = search_brave(query, count=args.limit)
companies = extract_companies_from_results(results)
all_leads.extend(companies)
print(f" Găsite: {len(companies)} companii")
# Deduplică
unique_leads = deduplicate(all_leads)
print(f"\n📊 Total: {len(all_leads)}{len(unique_leads)} unice")
# Îmbogățește
enriched = enrich_leads(unique_leads)
# Salvează
output_file = save_leads(enriched)
print(f"\n✅ Salvat: {output_file}")
# Afișează lista
print(f"\n📋 {len(enriched)} companii găsite:")
for i, lead in enumerate(enriched, 1):
print(f" {i}. {lead['company']}")
print(f"\n💡 Următorii pași:")
print(f" 1. Deschide {output_file}")
print(f" 2. Completează CUI, email, website pentru cele interesante")
print(f" 3. Marchează status: researched → contacted → replied")
return enriched
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,20 @@
company,industry,source_url,found_date,cui,email,website,phone,status,notes
TAXASIST ADVICES S.R.L.,,https://www.ejobs.ro/en/jobs/contabil,2026-02-04,,,,,new,
FINANCE CONSULTING S.R.L.,,https://www.ejobs.ro/en/jobs/contabil-junior,2026-02-04,,,,,new,
CABINET EXPERT CONTABIL DOCHIAN SI ASOCIATII SRL,,https://www.ejobs.ro/en/jobs/contabil-junior,2026-02-04,,,,,new,
TPA Transilvania Contax S.R.L.,,https://www.ejobs.ro/en/jobs/contabil-junior,2026-02-04,,,,,new,
DOCHIAN SI ASOCIATII SRL,,https://www.ejobs.ro/en/jobs/contabil-junior,2026-02-04,,,,,new,
SC COMGABY MOLN SRL,,https://www.ejobs.ro/en/jobs/cluj-napoca/contabil-junior,2026-02-04,,,,,new,
MOL ROMANIA Petroleum Products SRL,,https://www.ejobs.ro/en/jobs/cluj-napoca/contabil-junior,2026-02-04,,,,,new,
TRAIN TO PERFORM SRL,,https://www.ejobs.ro/en/jobs/iasi/contabil,2026-02-04,,,,,new,
SC PANIFCOM SRL,,https://www.ejobs.ro/en/jobs/iasi/contabil,2026-02-04,,,,,new,
SWISS MANAGEMENT COMPANY SRL,,https://www.ejobs.ro/en/jobs/brasov/contabil,2026-02-04,,,,,new,
SC CROCO SRL,,https://www.ejobs.ro/en/jobs/brasov/contabil,2026-02-04,,,,,new,
OV UNITY PLACE S.R.L.,,https://www.ejobs.ro/en/jobs/brasov/contabil,2026-02-04,,,,,new,
EREN CONS SRL,,https://www.ejobs.ro/en/jobs/brasov/contabil,2026-02-04,,,,,new,
ADECCO RESURSE UMANE SRL,,https://www.ejobs.ro/en/jobs/financiar-contabilitate/no-experience,2026-02-04,,,,,new,
Global Services Bucharest SRL,,https://www.ejobs.ro/en/jobs/financiar-contabilitate/no-experience,2026-02-04,,,,,new,
TRAVEL BRANDS S.A.,,https://www.ejobs.ro/en/jobs/bucuresti/contabil,2026-02-04,,,,,new,
B BUSINESS GOLD S.R.L.,,https://www.ejobs.ro/en/jobs/financiar-contabilitate,2026-02-04,,,,,new,
Contab SRL,,https://www.ejobs.ro/en/jobs/financiar-contabilitate,2026-02-04,,,,,new,
MASPEX ROMANIA SRL,,https://www.bestjobs.eu/en/jobs/contabil+fara+experienta,2026-02-04,,,,,new,
1 company industry source_url found_date cui email website phone status notes
2 TAXASIST ADVICES S.R.L. https://www.ejobs.ro/en/jobs/contabil 2026-02-04 new
3 FINANCE CONSULTING S.R.L. https://www.ejobs.ro/en/jobs/contabil-junior 2026-02-04 new
4 CABINET EXPERT CONTABIL DOCHIAN SI ASOCIATII SRL https://www.ejobs.ro/en/jobs/contabil-junior 2026-02-04 new
5 TPA Transilvania Contax S.R.L. https://www.ejobs.ro/en/jobs/contabil-junior 2026-02-04 new
6 DOCHIAN SI ASOCIATII SRL https://www.ejobs.ro/en/jobs/contabil-junior 2026-02-04 new
7 SC COMGABY MOLN SRL https://www.ejobs.ro/en/jobs/cluj-napoca/contabil-junior 2026-02-04 new
8 MOL ROMANIA Petroleum Products SRL https://www.ejobs.ro/en/jobs/cluj-napoca/contabil-junior 2026-02-04 new
9 TRAIN TO PERFORM SRL https://www.ejobs.ro/en/jobs/iasi/contabil 2026-02-04 new
10 SC PANIFCOM SRL https://www.ejobs.ro/en/jobs/iasi/contabil 2026-02-04 new
11 SWISS MANAGEMENT COMPANY SRL https://www.ejobs.ro/en/jobs/brasov/contabil 2026-02-04 new
12 SC CROCO SRL https://www.ejobs.ro/en/jobs/brasov/contabil 2026-02-04 new
13 OV UNITY PLACE S.R.L. https://www.ejobs.ro/en/jobs/brasov/contabil 2026-02-04 new
14 EREN CONS SRL https://www.ejobs.ro/en/jobs/brasov/contabil 2026-02-04 new
15 ADECCO RESURSE UMANE SRL https://www.ejobs.ro/en/jobs/financiar-contabilitate/no-experience 2026-02-04 new
16 Global Services Bucharest SRL https://www.ejobs.ro/en/jobs/financiar-contabilitate/no-experience 2026-02-04 new
17 TRAVEL BRANDS S.A. https://www.ejobs.ro/en/jobs/bucuresti/contabil 2026-02-04 new
18 B BUSINESS GOLD S.R.L. https://www.ejobs.ro/en/jobs/financiar-contabilitate 2026-02-04 new
19 Contab SRL https://www.ejobs.ro/en/jobs/financiar-contabilitate 2026-02-04 new
20 MASPEX ROMANIA SRL https://www.bestjobs.eu/en/jobs/contabil+fara+experienta 2026-02-04 new

View File

@@ -0,0 +1,30 @@
# Template Email - General
**Subiect:** Soluție software contabilitate - {company}
---
Bună ziua,
Am văzut că {company} caută {job_title}.
De peste 25 de ani dezvoltăm **ROA** - un sistem ERP complet pentru contabilitate și gestiune, folosit de companii din România care au nevoie de:
- ✅ Contabilitate completă (generală + analitică)
- ✅ Gestiune stocuri și facturare
- ✅ Rapoarte ANAF automate (D300, D394, D390, e-Factura)
- ✅ Interfață web pentru acces de oriunde
**De ce ROA:**
- Soluție românească, adaptată legislației locale
- Bază de date Oracle - performanță și securitate
- Suport tehnic direct de la dezvoltator
- Prețuri competitive vs. soluțiile multinaționale
Dacă vă interesează o demonstrație gratuită de 30 minute, răspundeți la acest email sau sunați la {phone}.
Cu respect,
{sender_name}
{sender_company}
{sender_phone}
{sender_email}

155
tools/pauza_random.py Normal file
View File

@@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""
Alege o tehnică de pauză random din memory/tehnici-pauza.md
În funcție de ora curentă (București = UTC+2):
- 09:00-17:00 → BIROU
- 18:00+ → ACASĂ
Output include: tehnica + rezultat + sursă (pentru context)
"""
import random
import re
from datetime import datetime, timezone, timedelta
from pathlib import Path
# Timezone București (UTC+2, simplificat)
TZ_OFFSET = timedelta(hours=2)
def get_bucharest_hour():
"""Returnează ora curentă în București."""
utc_now = datetime.now(timezone.utc)
bucharest_now = utc_now + TZ_OFFSET
return bucharest_now.hour
def parse_tehnici(filepath):
"""Parsează fișierul și returnează dict cu BIROU și ACASA."""
content = Path(filepath).read_text(encoding='utf-8')
tehnici = {'BIROU': [], 'ACASA': []}
current_section = None
current_tehnica = None
current_detalii = []
current_rezultat = None
current_sursa = None
for line in content.split('\n'):
# Detectează secțiunea
if '## BIROU' in line:
current_section = 'BIROU'
continue
elif '## ACASĂ' in line or '## ACASA' in line:
current_section = 'ACASA'
continue
elif line.startswith('## ') or line.startswith('---'):
# Altă secțiune (Surse, etc.) - salvează și oprește
if current_section and current_tehnica:
tehnici[current_section].append({
'titlu': current_tehnica,
'detalii': current_detalii,
'rezultat': current_rezultat,
'sursa': current_sursa
})
current_section = None
continue
if not current_section:
continue
# Detectează titlu tehnică (### Titlu)
if line.startswith('### '):
# Salvează tehnica anterioară
if current_tehnica:
tehnici[current_section].append({
'titlu': current_tehnica,
'detalii': current_detalii,
'rezultat': current_rezultat,
'sursa': current_sursa
})
current_tehnica = line[4:].strip()
current_detalii = []
current_rezultat = None
current_sursa = None
elif line.startswith('- **Rezultat:**'):
current_rezultat = line.replace('- **Rezultat:**', '').strip()
elif line.startswith('- **Sursă:**'):
current_sursa = line.replace('- **Sursă:**', '').strip()
elif line.startswith('- ') and current_tehnica:
# Detaliu (bullet point, exclude doar Rezultat și Sursă)
detaliu = line[2:].strip()
# Curăță bold markers
detaliu = re.sub(r'\*\*([^*]+)\*\*:', r'\1:', detaliu)
if detaliu:
current_detalii.append(detaliu)
# Salvează ultima tehnică
if current_section and current_tehnica:
tehnici[current_section].append({
'titlu': current_tehnica,
'detalii': current_detalii,
'rezultat': current_rezultat,
'sursa': current_sursa
})
return tehnici
def formateaza_mesaj(tehnica):
"""Formatează tehnica pentru mesaj Discord cu context."""
titlu = tehnica['titlu']
detalii = tehnica['detalii']
rezultat = tehnica.get('rezultat')
sursa = tehnica.get('sursa')
# Alege un detaliu random dacă sunt mai multe
if detalii:
detaliu = random.choice(detalii)
else:
detaliu = ""
# Construiește mesajul
lines = [f"**{titlu}**"]
if detaliu:
lines.append(detaliu)
if rezultat:
lines.append(f"📊 _{rezultat}_")
if sursa:
# Extrage link-uri din markdown [text](url) și afișează separat
link_pattern = re.compile(r'\[([^\]]+)\]\(([^)]+)\)')
links = link_pattern.findall(sursa)
# Curăță sursa de markdown links pentru text simplu
sursa_clean = link_pattern.sub(r'\1', sursa)
lines.append(f"📚 {sursa_clean}")
# Adaugă link-urile pe linii separate
for text, url in links:
lines.append(f"🔗 <{url}>")
return '\n'.join(lines)
def main():
filepath = Path(__file__).parent.parent / 'memory/kb/tehnici-pauza.md'
if not filepath.exists():
print("Fișierul tehnici-pauza.md nu există!")
return
hora = get_bucharest_hour()
tehnici = parse_tehnici(filepath)
# Alege secțiunea în funcție de oră
if 9 <= hora <= 17:
sectiune = 'BIROU'
else:
sectiune = 'ACASA'
if not tehnici[sectiune]:
print(f"Nu am tehnici pentru secțiunea {sectiune}!")
return
# Alege o tehnică random
tehnica = random.choice(tehnici[sectiune])
mesaj = formateaza_mesaj(tehnica)
print(mesaj)
if __name__ == '__main__':
main()

595
tools/process_bon.py Normal file
View File

@@ -0,0 +1,595 @@
#!/usr/bin/env python3
"""
Procesare bon fiscal: PDF → OCR API → SQLite API → Oracle
Usage:
python process_bon.py <pdf_path> [--save]
--save Salvează efectiv în Oracle (altfel dry run)
Fluxul:
1. OCR extract via API (http://10.0.20.171:8000/api/data-entry/ocr/extract)
2. Save receipt via API (http://10.0.20.171:8000/api/data-entry/receipts/) - TOATE datele
3. Save to Oracle:
- Verifică/creează partener
- Verifică TVA la încasare (CALENDAR.TVA_INCASARE)
- Generează note contabile corecte
"""
import sys
import os
import json
import time
import argparse
from pathlib import Path
from datetime import datetime
from decimal import Decimal
import requests
import oracledb
from dotenv import load_dotenv
# Load .env from parent directory
load_dotenv(Path(__file__).parent.parent / ".env")
# === CONFIG ===
API_BASE = "http://10.0.20.171:8000"
API_USER = "MARIUS M"
API_PASS = os.getenv("ROA_API_PASSWORD", "")
SERVER_ID = "central"
COMPANY_ID = 110 # MARIUSM AUTO
ORACLE_CONFIG = {
"user": "MARIUSM_AUTO",
"password": os.getenv("ORACLE_PASSWORD", ""),
"dsn": "10.0.20.121:1521/ROA"
}
# Mapare CUI → cont cheltuială
CUI_TO_CONT = {
"11201891": "6022", # MOL
"1590082": "6022", # OMV Petrom
"14991381": "6022", # Rompetrol
"10562600": "6021", # Dedeman / Five Holding (Brick)
"1879865": "6021", # Five Holding
}
# Mapare cotă TVA → (ID_JTVA baza, ID_JTVA tva, TAXCODE, TAXCODE_TVAI)
# Pentru achiziții interne neexigibile (TVA la încasare)
JTVA_NEEX = {
21: (210, 211, 301104, 301305), # ACH. INT. NEEX. 21%
19: (188, 189, 301101, 301301), # ACH. INT. NEEX. 19%
11: (214, 215, 301105, 301306), # ACH. INT. NEEX. 11%
9: (172, 173, 301102, 301302), # ACH. INT. NEEX. 9%
5: (174, 175, 301103, 301303), # ACH. INT. NEEX. 5%
}
# Pentru achiziții interne normale (fără TVA la încasare)
JTVA_NORMAL = {
21: (208, 209, 301104, 301305), # ACH. INT. 21%
19: (None, None, 301101, 301301),
9: (None, None, 301102, 301302),
}
def get_cont(cui: str) -> str:
"""Mapare CUI → cont cheltuială."""
cui_clean = (cui or "").upper().replace("RO", "").strip()
return CUI_TO_CONT.get(cui_clean, "6028") # 6028 = alte cheltuieli
class APIClient:
"""Client pentru roa2web API."""
def __init__(self, base_url: str):
self.base_url = base_url.rstrip("/")
self.token = None
self.session = requests.Session()
def login(self, username: str, password: str, server_id: str) -> bool:
"""Login și obține token."""
r = self.session.post(
f"{self.base_url}/api/auth/login",
json={"username": username, "password": password, "server_id": server_id}
)
if r.status_code == 200:
data = r.json()
self.token = data.get("access_token")
self.session.headers["Authorization"] = f"Bearer {self.token}"
return True
print(f"Login failed: {r.status_code} - {r.text}")
return False
def ocr_extract(self, file_path: Path) -> dict:
"""Submit OCR job și așteaptă rezultatul."""
# Determine mime type
suffix = file_path.suffix.lower()
if suffix == ".pdf":
mime_type = "application/pdf"
elif suffix in (".jpg", ".jpeg"):
mime_type = "image/jpeg"
elif suffix == ".png":
mime_type = "image/png"
else:
# Try to detect from content
with open(file_path, "rb") as f:
header = f.read(8)
if header[:4] == b'%PDF':
mime_type = "application/pdf"
suffix = ".pdf"
elif header[:3] == b'\xff\xd8\xff':
mime_type = "image/jpeg"
suffix = ".jpg"
elif header[:8] == b'\x89PNG\r\n\x1a\n':
mime_type = "image/png"
suffix = ".png"
else:
mime_type = "application/pdf" # default
suffix = ".pdf"
# Use proper filename with extension
filename = file_path.stem + suffix if not file_path.suffix else file_path.name
# Submit
with open(file_path, "rb") as f:
r = self.session.post(
f"{self.base_url}/api/data-entry/ocr/extract",
files={"file": (filename, f, mime_type)}
)
if r.status_code != 200:
return {"success": False, "error": f"OCR submit failed: {r.text}"}
job_id = r.json().get("job_id")
print(f" OCR job: {job_id}")
# Wait for result (max 60s per request, retry if pending)
for _ in range(4): # Max 4 retries = ~240s total
r = self.session.get(
f"{self.base_url}/api/data-entry/ocr/jobs/{job_id}/wait",
params={"timeout": 60, "wait_for_terminal": "true"},
timeout=70
)
if r.status_code != 200:
return {"success": False, "error": f"OCR wait failed: {r.text}"}
data = r.json()
status = data.get("status")
if status == "completed":
return {"success": True, "result": data.get("result"), "time_ms": data.get("processing_time_ms")}
elif status == "failed":
return {"success": False, "error": data.get("error") or "OCR failed"}
# Still pending/processing - will retry
return {"success": False, "error": "OCR timeout"}
def create_receipt(self, ocr_result: dict, company_id: int) -> dict:
"""Creează receipt în SQLite via API cu TOATE datele."""
# Parse date
date_str = ocr_result.get("receipt_date")
if date_str:
receipt_date = date_str[:10] # YYYY-MM-DD
else:
receipt_date = datetime.now().strftime("%Y-%m-%d")
# Build TVA breakdown from OCR
tva_breakdown = []
for tva_entry in (ocr_result.get("tva_entries") or []):
tva_breakdown.append({
"code": tva_entry.get("code"),
"percent": tva_entry.get("percent"),
"amount": float(tva_entry.get("amount") or 0)
})
# Build payment methods from OCR
payment_methods = []
for pm in (ocr_result.get("payment_methods") or []):
payment_methods.append({
"method": pm.get("method"),
"amount": float(pm.get("amount") or 0)
})
# Determine payment mode
payment_mode = ocr_result.get("suggested_payment_mode") or "casa"
# If has CARD payment, it's "banca"
if any(pm.get("method", "").upper() == "CARD" for pm in payment_methods):
payment_mode = "banca"
elif any(pm.get("method", "").upper() == "NUMERAR" for pm in payment_methods):
payment_mode = "casa"
payload = {
"receipt_type": "bon_fiscal",
"direction": "cheltuiala",
"receipt_number": ocr_result.get("receipt_number"),
"receipt_series": ocr_result.get("receipt_series"),
"receipt_date": receipt_date,
"amount": float(ocr_result.get("amount") or 0),
"partner_name": ocr_result.get("partner_name"),
"cui": ocr_result.get("cui"),
"tva_total": float(ocr_result.get("tva_total") or 0),
"tva_breakdown": tva_breakdown if tva_breakdown else None,
"payment_methods": payment_methods if payment_methods else None,
"payment_mode": payment_mode,
"company_id": company_id,
"vendor_address": ocr_result.get("address"),
"items_count": ocr_result.get("items_count"),
"ocr_raw_text": ocr_result.get("raw_text"),
}
# Remove None values
payload = {k: v for k, v in payload.items() if v is not None}
self.session.headers["X-Selected-Company"] = str(company_id)
r = self.session.post(
f"{self.base_url}/api/data-entry/receipts/",
json=payload
)
if r.status_code in (200, 201):
return {"success": True, "receipt": r.json()}
else:
return {"success": False, "error": f"Create receipt failed: {r.text}"}
def get_or_create_partner(cursor, cui: str, name: str, address: str = None) -> int:
"""Găsește sau creează partener în Oracle. Returnează ID_PART."""
cui_clean = (cui or "").upper().replace("RO", "").strip()
if not cui_clean:
return 0 # No CUI, no partner
# Try to find existing partner
cursor.execute("""
SELECT ID_PART FROM NOM_PARTENERI
WHERE COD_FISCAL = :cui OR COD_FISCAL = :cui2
""", cui=cui_clean, cui2="RO" + cui_clean)
row = cursor.fetchone()
if row:
return row[0] # Found existing partner
# Create new partner
cursor.execute("SELECT SEQ_NOM_PARTENERI.NEXTVAL FROM DUAL")
new_id = cursor.fetchone()[0]
# Clean name
partner_name = (name or f"PARTENER {cui_clean}")[:100]
partner_address = (address or "")[:200]
cursor.execute("""
INSERT INTO NOM_PARTENERI (ID_PART, NUME, COD_FISCAL, ADRESA, STERS, INACTIV)
VALUES (:id_part, :nume, :cui, :adresa, 0, 0)
""", id_part=new_id, nume=partner_name, cui=cui_clean, adresa=partner_address)
print(f" Partener nou creat: ID={new_id}, CUI={cui_clean}, Nume={partner_name}")
return new_id
def check_tva_incasare(cursor, an: int, luna: int) -> bool:
"""Verifică dacă firma e plătitoare de TVA la încasare în perioada dată."""
cursor.execute("""
SELECT NVL(TVA_INCASARE, 0) FROM CALENDAR
WHERE AN = :an AND LUNA = :luna
""", an=an, luna=luna)
row = cursor.fetchone()
return row[0] == 1 if row else False
def save_to_oracle(ocr_result: dict, do_commit: bool = False) -> dict:
"""Salvează nota contabilă în Oracle cu toate regulile."""
conn = oracledb.connect(**ORACLE_CONFIG)
cursor = conn.cursor()
try:
# Parse date
date_str = ocr_result.get("receipt_date")
if date_str:
receipt_date = datetime.strptime(date_str[:10], "%Y-%m-%d").date()
else:
receipt_date = datetime.now().date()
an, luna = receipt_date.year, receipt_date.month
# 1. Get or create partner
id_part = get_or_create_partner(
cursor,
ocr_result.get("cui"),
ocr_result.get("partner_name"),
ocr_result.get("address")
)
print(f" Partner ID: {id_part}")
# 2. Check TVA la încasare
tva_incasare = check_tva_incasare(cursor, an, luna)
cont_tva = "4428" if tva_incasare else "4426"
print(f" TVA la încasare: {'DA (4428)' if tva_incasare else 'NU (4426)'}")
# 3. Determine payment type
payment_methods = ocr_result.get("payment_methods") or []
has_cash = any(pm.get("method", "").upper() == "NUMERAR" for pm in payment_methods)
has_card = any(pm.get("method", "").upper() == "CARD" for pm in payment_methods)
# If no payment info, assume cash
if not payment_methods:
has_cash = True
print(f" Plată: {'NUMERAR' if has_cash else ''}{' + ' if has_cash and has_card else ''}{'CARD' if has_card else ''}")
# 4. Init PACK_CONTAFIN
cursor.callproc('PACK_CONTAFIN.INITIALIZEAZA_SCRIERE_ACT_RUL',
[0, datetime.now(), an, luna, 0, 0, 0, 0])
# 5. Get next COD
cursor.execute(
"SELECT NVL(MAX(COD), 0) + 1 FROM ACT WHERE AN = :an AND LUNA = :luna",
an=an, luna=luna
)
cod = cursor.fetchone()[0]
# 6. Calculate amounts
total = float(ocr_result.get("amount") or 0)
tva = float(ocr_result.get("tva_total") or 0)
fara_tva = total - tva
nract = ocr_result.get("receipt_number", "")
nract = int(nract) if str(nract).isdigit() else 0
cont_cheltuiala = get_cont(ocr_result.get("cui") or "")
expl = f"OCR: {ocr_result.get('partner_name') or 'N/A'}"[:100]
print(f" COD: {cod}, Cont: {cont_cheltuiala}")
print(f" Total: {total}, Bază: {fara_tva}, TVA: {tva}")
# 7. Process TVA entries from OCR (pot fi mai multe cote TVA)
tva_entries = ocr_result.get("tva_entries") or []
# 8. Build accounting lines
lines = []
# Calculate base for each TVA rate
if tva_entries:
# Process each TVA entry separately
for tva_entry in tva_entries:
tva_rate = tva_entry.get("percent") or 21
tva_amount = float(tva_entry.get("amount") or 0)
if tva_amount <= 0:
continue
# Calculate base for this TVA rate
base_amount = tva_amount / (tva_rate / 100)
# Get ID_JTVA_COLOANA and TAXCODE based on TVA rate and TVA la încasare
if tva_incasare:
jtva_data = JTVA_NEEX.get(tva_rate, (210, 211, 301104, 301305))
else:
jtva_data = JTVA_NORMAL.get(tva_rate, (208, 209, 301104, 301305))
jtva_baza, jtva_tva, taxcode_normal, taxcode_tvai = jtva_data
taxcode = taxcode_tvai if tva_incasare else taxcode_normal
print(f" TVA {tva_rate}%: baza={base_amount:.2f}, tva={tva_amount:.2f}, JTVA=({jtva_baza},{jtva_tva}), TAXCODE={taxcode}")
# Linia cheltuială pentru această cotă
lines.append({
"scd": cont_cheltuiala, "scc": "401",
"suma": base_amount, "expl": expl,
"id_partc": id_part, "id_partd": 0,
"id_jtva": jtva_baza,
"taxcode": taxcode
})
# Linia TVA pentru această cotă
proc_tva = 1 + tva_rate / 100 # 1.21, 1.19, etc.
lines.append({
"scd": cont_tva, "scc": "401",
"suma": tva_amount, "expl": f"TVA {tva_rate}% {expl}"[:100],
"id_partc": id_part, "id_partd": 0,
"proc_tva": proc_tva,
"id_jtva": jtva_tva,
"taxcode": taxcode
})
else:
# Fallback: use total amounts if no tva_entries
if fara_tva > 0:
tva_rate = round(tva / fara_tva * 100) if fara_tva > 0 else 21
else:
tva_rate = 21
if tva_incasare:
jtva_data = JTVA_NEEX.get(tva_rate, (210, 211, 301104, 301305))
else:
jtva_data = JTVA_NORMAL.get(tva_rate, (208, 209, 301104, 301305))
jtva_baza, jtva_tva, taxcode_normal, taxcode_tvai = jtva_data
taxcode = taxcode_tvai if tva_incasare else taxcode_normal
print(f" TVA {tva_rate}% (estimat): JTVA=({jtva_baza},{jtva_tva}), TAXCODE={taxcode}")
lines.append({
"scd": cont_cheltuiala, "scc": "401",
"suma": fara_tva, "expl": expl,
"id_partc": id_part, "id_partd": 0,
"id_jtva": jtva_baza,
"taxcode": taxcode
})
if tva > 0:
proc_tva = 1 + tva_rate / 100
lines.append({
"scd": cont_tva, "scc": "401",
"suma": tva, "expl": f"TVA {tva_rate}% {expl}"[:100],
"id_partc": id_part, "id_partd": 0,
"proc_tva": proc_tva,
"id_jtva": jtva_tva,
"taxcode": taxcode
})
# Linia plată din casă (DOAR dacă plată numerar)
if has_cash and not has_card:
lines.append({
"scd": "401", "scc": "5311",
"suma": total, "expl": f"Plata {expl}"[:100],
"id_partc": 0, "id_partd": id_part,
"id_jtva": None, # Nu are JTVA pentru plată
"taxcode": None
})
# Dacă plată CARD - nu se face nota 401=5311 (se face la extras bancar)
# ID_FDOC = 17 pentru BON FISCAL
id_fdoc = 17
# 9. Insert lines
for line in lines:
proc_tva = line.get("proc_tva") or 0 # Default 0 for non-TVA lines
id_jtva = line.get("id_jtva") # Poate fi None pentru plăți
taxcode = line.get("taxcode") # Poate fi None pentru plăți
cursor.execute("""
INSERT INTO ACT_TEMP (
LUNA, AN, COD, DATAIREG, DATAACT, NRACT,
EXPLICATIA, SCD, SCC, SUMA, PROC_TVA,
ID_PARTC, ID_PARTD, ID_FDOC, ID_JTVA_COLOANA, TAXCODE, ID_UTIL, DATAORA,
STERS, ID_SET, ID_SUCURSALA
) VALUES (
:luna, :an, :cod, TRUNC(SYSDATE), :dataact, :nract,
:expl, :scd, :scc, :suma, :proc_tva,
:id_partc, :id_partd, :id_fdoc, :id_jtva, :taxcode, 0, SYSDATE,
0, 0, 0
)
""",
luna=luna, an=an, cod=cod, dataact=receipt_date, nract=nract,
expl=line["expl"], scd=line["scd"], scc=line["scc"],
suma=line["suma"], proc_tva=proc_tva,
id_partc=line["id_partc"], id_partd=line["id_partd"],
id_fdoc=id_fdoc, id_jtva=id_jtva, taxcode=taxcode
)
jtva_info = f" [JTVA={id_jtva}]" if id_jtva else ""
taxcode_info = f" [TAX={taxcode}]" if taxcode else ""
print(f" {line['scd']} = {line['scc']}: {line['suma']:.2f}{jtva_info}{taxcode_info}")
# 9. Finalize
mesaj = cursor.var(oracledb.STRING, 4000)
cursor.callproc('PACK_CONTAFIN.FINALIZEAZA_SCRIERE_ACT_RUL',
[0, cod, 0, 0, 0, mesaj])
if do_commit:
conn.commit()
return {"success": True, "cod": cod, "luna": luna, "an": an, "saved": True,
"id_part": id_part, "tva_incasare": tva_incasare}
else:
conn.rollback()
return {"success": True, "cod": cod, "luna": luna, "an": an, "saved": False,
"id_part": id_part, "tva_incasare": tva_incasare}
except Exception as e:
conn.rollback()
import traceback
return {"success": False, "error": str(e), "traceback": traceback.format_exc()}
finally:
cursor.close()
conn.close()
def process_bon(file_path: Path, do_save: bool = False, company_id: int = COMPANY_ID,
api_user: str = API_USER, api_pass: str = API_PASS):
"""Procesează un bon fiscal: OCR → SQLite → Oracle."""
print("=" * 60)
print(f"📄 Procesez: {file_path.name}")
print("=" * 60)
# 1. Login
print("\n🔑 Login API...")
client = APIClient(API_BASE)
if not client.login(api_user, api_pass, SERVER_ID):
print("❌ Login failed!")
return None
print(" ✅ OK")
# 2. OCR
print("\n🔍 OCR extract...")
ocr_result = client.ocr_extract(file_path)
if not ocr_result["success"]:
print(f"{ocr_result['error']}")
return None
ocr = ocr_result["result"]
print(f" ✅ OK ({ocr_result.get('time_ms', '?')}ms)")
print(f" CUI: {ocr.get('cui')}")
print(f" Partner: {ocr.get('partner_name')}")
print(f" Data: {ocr.get('receipt_date')}")
print(f" Total: {ocr.get('amount')} RON")
print(f" TVA: {ocr.get('tva_total')} RON")
# Show payment methods
payment_methods = ocr.get("payment_methods") or []
if payment_methods:
pm_str = ", ".join(f"{pm.get('method')}: {pm.get('amount')}" for pm in payment_methods)
print(f" Plăți: {pm_str}")
# Show TVA breakdown
tva_entries = ocr.get("tva_entries") or []
if tva_entries:
tva_str = ", ".join(f"{t.get('code')}({t.get('percent')}%): {t.get('amount')}" for t in tva_entries)
print(f" TVA detaliat: {tva_str}")
# 3. SQLite (via API) - cu TOATE datele
print("\n💾 Save SQLite (via API)...")
sqlite_result = client.create_receipt(ocr, company_id)
if not sqlite_result["success"]:
print(f"{sqlite_result['error']}")
return None
receipt = sqlite_result["receipt"]
print(f" ✅ Receipt ID: {receipt.get('id')}")
print(f" Payment mode: {receipt.get('payment_mode')}")
# 4. Oracle (direct)
mode = "SAVE" if do_save else "DRY RUN"
print(f"\n🗄️ Save Oracle ({mode})...")
oracle_result = save_to_oracle(ocr, do_commit=do_save)
if oracle_result["success"]:
if oracle_result["saved"]:
print(f" ✅ SALVAT: COD={oracle_result['cod']}, {oracle_result['luna']:02d}/{oracle_result['an']}")
else:
print(f" ⚠️ DRY RUN: ar fi COD={oracle_result['cod']}")
else:
print(f"{oracle_result.get('error')}")
if oracle_result.get("traceback"):
print(oracle_result["traceback"])
print("\n" + "=" * 60)
return {
"ocr": ocr,
"sqlite_receipt_id": receipt.get("id"),
"oracle": oracle_result
}
def main():
parser = argparse.ArgumentParser(description="Procesare bon fiscal: OCR → SQLite → Oracle")
parser.add_argument("file", help="Path către PDF sau imagine")
parser.add_argument("--save", action="store_true", help="Salvează efectiv în Oracle")
parser.add_argument("--company", type=int, default=COMPANY_ID, help="Company ID")
parser.add_argument("--user", default=API_USER, help="API username")
parser.add_argument("--password", default=API_PASS, help="API password")
args = parser.parse_args()
file_path = Path(args.file)
if not file_path.exists():
print(f"❌ File not found: {file_path}")
sys.exit(1)
result = process_bon(file_path, do_save=args.save, company_id=args.company,
api_user=args.user, api_pass=args.password)
if result:
print("\n✅ Done!")
else:
print("\n❌ Failed!")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,488 @@
#!/usr/bin/env python3
"""
Ralph PRD Generator - implementare Python a skill-ului ralph-prd
Generează PRD și prd.json fără să apeleze Claude Code
"""
import json
import re
from pathlib import Path
from datetime import datetime
def detect_project_context(project_dir: Path):
"""Detectează tech stack și mod (NEW_PROJECT vs FEATURE)"""
context = {
"mode": "NEW_PROJECT",
"stack_type": None,
"config_file": None,
"scripts": {},
"dependencies": []
}
# Verifică fișiere de config
config_files = {
"package.json": "nodejs",
"pyproject.toml": "python",
"requirements.txt": "python-legacy",
"go.mod": "go",
"Cargo.toml": "rust",
"pom.xml": "java-maven",
"build.gradle": "java-gradle",
"composer.json": "php",
"Gemfile": "ruby"
}
for filename, stack in config_files.items():
config_path = project_dir / filename
if config_path.exists():
context["mode"] = "FEATURE"
context["stack_type"] = stack
context["config_file"] = filename
# Extrage info din package.json
if filename == "package.json":
try:
with open(config_path) as f:
data = json.load(f)
context["scripts"] = data.get("scripts", {})
context["dependencies"] = list(data.get("dependencies", {}).keys())
except:
pass
break
return context
def generate_prd_markdown(project_name: str, description: str, context: dict):
"""Generează PRD markdown bazat pe descriere și context"""
# Parse descriere pentru a extrage info
lines = description.strip().split('\n')
features = []
requirements = []
for line in lines:
line = line.strip()
if line.startswith('Features:') or line.startswith('Feature:'):
continue
if line.startswith('-'):
features.append(line[1:].strip())
elif line and not line.endswith(':'):
requirements.append(line)
if not features:
# Dacă nu sunt features explicit, folosim descrierea ca feature
features = [description.split('.')[0]]
# Detectează tip proiect din descriere
desc_lower = description.lower()
if 'cli' in desc_lower or 'command' in desc_lower:
project_type = "CLI Tool"
elif 'api' in desc_lower or 'backend' in desc_lower:
project_type = "API / Backend"
elif 'web' in desc_lower or 'app' in desc_lower:
project_type = "Web Application"
else:
project_type = "Application"
# Detectează stack din descriere sau context
if 'python' in desc_lower:
stack = "Python"
test_framework = "pytest"
elif 'javascript' in desc_lower or 'node' in desc_lower or 'typescript' in desc_lower:
stack = "Node.js / TypeScript"
test_framework = "jest"
elif context["stack_type"] == "nodejs":
stack = "Node.js / TypeScript"
test_framework = "jest"
elif context["stack_type"] and context["stack_type"].startswith("python"):
stack = "Python"
test_framework = "pytest"
else:
stack = "Python" # Default
test_framework = "pytest"
# Template PRD
prd = f"""# PRD: {project_name.replace('-', ' ').title()}
## 1. Introducere
{description.split('.')[0]}.
**Data:** {datetime.now().strftime('%Y-%m-%d')}
**Status:** Draft
**Mode:** {context['mode']}
## 2. Context Tehnic
"""
if context["mode"] == "FEATURE":
prd += f"""**Proiect existent detectat:**
- Stack: {context['stack_type']}
- Config: {context['config_file']}
- Scripts: {', '.join(context['scripts'].keys())}
"""
else:
prd += f"""**Proiect nou:**
- Tip: {project_type}
- Stack recomandat: {stack}
- Test framework: {test_framework}
"""
prd += f"""## 3. Obiective
### Obiectiv Principal
{features[0] if features else description.split('.')[0]}
### Obiective Secundare
"""
for i, feature in enumerate(features[1:] if len(features) > 1 else features, 1):
prd += f"- {feature}\n"
prd += f"""
### Metrici de Succes
- Toate funcționalitățile implementate conform spec
- Tests passing (coverage > 80%)
- Code quality: lint + typecheck pass
## 4. User Stories
"""
# Generează user stories din features
for i, feature in enumerate(features, 1):
story_id = f"US-{i:03d}"
title = feature.strip()
prd += f"""### {story_id}: {title}
**Ca** utilizator
**Vreau** {title.lower()}
**Pentru că** pot folosi aplicația eficient
**Acceptance Criteria:**
- [ ] Funcționalitatea implementată conform descrierii
- [ ] Input validation în loc
- [ ] Error handling pentru cazuri edge
- [ ] Tests cu {test_framework} (coverage > 80%)
- [ ] Code quality: lint + typecheck pass
**Priority:** {i * 10}
"""
# Dacă nu sunt multe features, adaugă story pentru tests
if len(features) < 3:
prd += f"""### US-{len(features)+1:03d}: Tests și Documentație
**Ca** developer
**Vreau** teste comprehensive și documentație
**Pentru că** asigur calitatea codului
**Acceptance Criteria:**
- [ ] Unit tests pentru toate funcțiile (coverage > 80%)
- [ ] Integration tests pentru flow-uri principale
- [ ] README cu instrucțiuni de utilizare
- [ ] Docstrings pentru funcții publice
- [ ] {test_framework} rulează fără erori
**Priority:** {(len(features)+1) * 10}
"""
prd += f"""## 5. Cerințe Funcționale
"""
for i, req in enumerate(requirements if requirements else features, 1):
prd += f"{i}. [REQ-{i:03d}] {req}\n"
prd += f"""
## 6. Non-Goals (Ce NU facem)
- Interfață grafică (GUI) - doar CLI/API
- Suport multiple limbaje - doar {stack}
- Deployment infrastructure - doar cod functional
## 7. Considerații Tehnice
### Stack/Tehnologii
- Limbaj: {stack}
- Testing: {test_framework}
- Linting: pylint / eslint (depinde de stack)
- Type checking: mypy / typescript
### Patterns de Urmat
- Clean code principles
- SOLID principles unde aplicabil
- Error handling consistent
- Input validation strict
### Riscuri Tehnice
- Edge cases la input validation
- Performance pentru volume mari de date (dacă aplicabil)
## 8. Considerații Security
- Input validation pentru toate datele externe
- Error messages fără info sensibilă
- Principle of least privilege
## 9. Open Questions
- [ ] Performance requirements specifice?
- [ ] Limite pe input sizes?
- [ ] Specific error handling patterns preferați?
---
**Generated by:** Echo (Ralph PRD Generator)
**Date:** {datetime.now().strftime('%Y-%m-%d %H:%M')}
"""
return prd
def prd_to_stories(prd_content: str, project_name: str):
"""Extrage user stories din PRD și le convertește în format prd.json"""
stories = []
# Parse PRD pentru stories
story_pattern = r'### (US-\d+): (.+?)\n\*\*Ca\*\* (.+?)\n\*\*Vreau\*\* (.+?)\n\*\*Pentru că\*\* (.+?)\n\n\*\*Acceptance Criteria:\*\*\n(.*?)\n\n\*\*Priority:\*\* (\d+)'
matches = re.finditer(story_pattern, prd_content, re.DOTALL)
for match in matches:
story_id = match.group(1)
title = match.group(2).strip()
user_type = match.group(3).strip()
want = match.group(4).strip()
because = match.group(5).strip()
criteria_text = match.group(6).strip()
priority = int(match.group(7))
# Parse acceptance criteria
criteria = []
for line in criteria_text.split('\n'):
line = line.strip()
if line.startswith('- [ ]'):
criteria.append(line[5:].strip())
# Detectează dacă necesită browser check (pentru UI)
requires_browser = 'ui' in title.lower() or 'interface' in title.lower()
story = {
"id": story_id,
"title": title,
"description": f"Ca {user_type}, vreau {want} pentru că {because}",
"priority": priority,
"acceptanceCriteria": criteria,
"requiresBrowserCheck": requires_browser,
"passes": False,
"notes": ""
}
stories.append(story)
# Dacă nu găsim stories (regex failed), generăm basic
if not stories:
stories = [{
"id": "US-001",
"title": "Implementare funcționalitate principală",
"description": f"Implementează {project_name}",
"priority": 10,
"acceptanceCriteria": [
"Funcționalitatea implementată",
"Tests passing",
"Lint + typecheck pass"
],
"requiresBrowserCheck": False,
"passes": False,
"notes": ""
}]
return stories
def detect_tech_stack_commands(project_dir: Path, context: dict):
"""Detectează comenzile tech stack pentru prd.json"""
stack_type = context.get("stack_type", "python")
scripts = context.get("scripts", {})
# Default commands per stack
if stack_type == "nodejs" or "package.json" in str(context.get("config_file", "")):
commands = {
"start": scripts.get("dev", scripts.get("start", "npm run dev")),
"build": scripts.get("build", "npm run build"),
"lint": scripts.get("lint", "npm run lint"),
"typecheck": scripts.get("typecheck", "npm run typecheck"),
"test": scripts.get("test", "npm test")
}
port = 3000
stack_name = "nextjs" if "next" in str(context.get("dependencies", [])) else "nodejs"
elif stack_type and stack_type.startswith("python"):
commands = {
"start": "python main.py",
"build": "",
"lint": "ruff check .",
"typecheck": "mypy .",
"test": "pytest"
}
port = 8000
stack_name = "python"
else:
# Generic/fallback
commands = {
"start": "python main.py",
"build": "",
"lint": "echo 'No linter configured'",
"typecheck": "echo 'No typecheck configured'",
"test": "pytest"
}
port = 8000
stack_name = "python"
return {
"type": stack_name,
"commands": commands,
"port": port
}
def create_prd_and_json(project_name: str, description: str, workspace_dir: Path):
"""
Generează PRD markdown și prd.json pentru un proiect
Returns:
tuple: (prd_file_path, prd_json_path) sau (None, None) dacă eroare
"""
project_dir = workspace_dir / project_name
project_dir.mkdir(parents=True, exist_ok=True)
# Detectează context
context = detect_project_context(project_dir)
print(f"📊 Context detectat:")
print(f" Mode: {context['mode']}")
if context['stack_type']:
print(f" Stack: {context['stack_type']}")
print(f" Config: {context['config_file']}")
# Generează PRD markdown
prd_content = generate_prd_markdown(project_name, description, context)
# Salvează PRD
tasks_dir = project_dir / "tasks"
tasks_dir.mkdir(exist_ok=True)
prd_file = tasks_dir / f"prd-{project_name}.md"
with open(prd_file, 'w', encoding='utf-8') as f:
f.write(prd_content)
print(f"✅ PRD salvat: {prd_file}")
# Generează prd.json
stories = prd_to_stories(prd_content, project_name)
tech_stack = detect_tech_stack_commands(project_dir, context)
prd_json_data = {
"projectName": project_name,
"branchName": f"ralph/{project_name}",
"description": description.split('\n')[0],
"techStack": tech_stack,
"userStories": stories
}
# Creează structura ralph
ralph_dir = project_dir / "scripts" / "ralph"
ralph_dir.mkdir(parents=True, exist_ok=True)
(ralph_dir / "logs").mkdir(exist_ok=True)
(ralph_dir / "archive").mkdir(exist_ok=True)
(ralph_dir / "screenshots").mkdir(exist_ok=True)
# Salvează prd.json
prd_json_file = ralph_dir / "prd.json"
with open(prd_json_file, 'w', encoding='utf-8') as f:
json.dump(prd_json_data, f, indent=2, ensure_ascii=False)
print(f"✅ prd.json salvat: {prd_json_file}")
print(f"📋 Stories: {len(stories)}")
for story in stories:
print(f" - {story['id']}: {story['title']}")
# Copiază template-uri ralph
templates_dir = Path.home() / ".claude" / "skills" / "ralph" / "templates"
if not templates_dir.exists():
templates_dir = Path.home() / "clawd" / "skills" / "ralph" / "templates"
if templates_dir.exists():
# Copiază ralph.sh
ralph_sh_src = templates_dir / "ralph.sh"
if ralph_sh_src.exists():
ralph_sh_dst = ralph_dir / "ralph.sh"
with open(ralph_sh_src) as f:
content = f.read()
with open(ralph_sh_dst, 'w') as f:
f.write(content)
ralph_sh_dst.chmod(0o755)
print(f"✅ ralph.sh copiat")
# Copiază prompt.md
prompt_src = templates_dir / "prompt.md"
if prompt_src.exists():
prompt_dst = ralph_dir / "prompt.md"
with open(prompt_src) as f:
content = f.read()
with open(prompt_dst, 'w') as f:
f.write(content)
print(f"✅ prompt.md copiat")
# Init progress.txt
progress_file = ralph_dir / "progress.txt"
with open(progress_file, 'w') as f:
f.write(f"# Ralph Progress Log\n")
f.write(f"Started: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n")
f.write(f"Project: {project_name}\n")
f.write(f"---\n")
print(f"✅ Structură Ralph completă în {ralph_dir}")
return prd_file, prd_json_file
if __name__ == "__main__":
import sys
if len(sys.argv) < 3:
print("Usage: python ralph_prd_generator.py PROJECT_NAME 'description'")
sys.exit(1)
project_name = sys.argv[1]
description = sys.argv[2]
workspace = Path.home() / "workspace"
print(f"🔄 Generez PRD pentru {project_name}")
print("=" * 70)
prd_file, prd_json = create_prd_and_json(project_name, description, workspace)
if prd_file and prd_json:
print("\n" + "=" * 70)
print("✅ PRD și prd.json generate cu succes!")
print(f"📄 PRD: {prd_file}")
print(f"📋 JSON: {prd_json}")
print("\n📌 Următorii pași:")
print(f" 1. Revizuiește PRD în {prd_file}")
print(f" 2. Rulează Ralph: cd {prd_file.parent.parent} && ./scripts/ralph/ralph.sh 20")
print("=" * 70)
else:
print("\n❌ Eroare la generare PRD")
sys.exit(1)

209
tools/ralph_workflow.py Executable file
View File

@@ -0,0 +1,209 @@
#!/usr/bin/env python3
"""
Ralph Workflow Helper - pentru Echo
Gestionează workflow-ul complet de creare și execuție proiecte cu Ralph + Claude Code
Folosește ralph_prd_generator.py pentru PRD, apoi ralph.sh pentru implementare
"""
import os
import sys
import json
import subprocess
from pathlib import Path
# Import generator PRD
sys.path.insert(0, str(Path(__file__).parent))
from ralph_prd_generator import create_prd_and_json
WORKSPACE = Path.home() / "workspace"
def run_ralph(prd_json: Path, max_iterations: int = 20, background: bool = False):
"""
Rulează ralph.sh pentru execuție autonomă
Args:
prd_json: Path către prd.json
max_iterations: Max iterații Ralph
background: Dacă True, rulează în background
Returns:
subprocess.Popen object dacă background, altfel exit code
"""
project_dir = prd_json.parent.parent.parent
ralph_script = prd_json.parent / "ralph.sh"
if not ralph_script.exists():
print(f"❌ ralph.sh nu există în {ralph_script.parent}")
return None
print(f"\n🚀 Lansez Ralph loop")
print(f"📁 Project: {project_dir}")
print(f"🔁 Max iterations: {max_iterations}")
print(f"🌙 Background: {background}")
cmd = [str(ralph_script), str(max_iterations)]
if background:
log_file = ralph_script.parent / "logs" / "ralph.log"
log_file.parent.mkdir(exist_ok=True)
with open(log_file, 'w') as f:
process = subprocess.Popen(
cmd,
cwd=project_dir,
stdout=f,
stderr=subprocess.STDOUT,
start_new_session=True
)
print(f"✅ Ralph pornit în background (PID: {process.pid})")
print(f"📋 Log: {log_file}")
pid_file = ralph_script.parent / ".ralph.pid"
with open(pid_file, 'w') as f:
f.write(str(process.pid))
return process
else:
print("⚠️ Rulare în foreground")
result = subprocess.run(cmd, cwd=project_dir)
return result.returncode
def check_status(project_dir: Path):
"""Verifică status Ralph pentru un proiect"""
prd_json = project_dir / "scripts" / "ralph" / "prd.json"
progress_file = project_dir / "scripts" / "ralph" / "progress.txt"
pid_file = project_dir / "scripts" / "ralph" / ".ralph.pid"
status = {
"project": project_dir.name,
"running": False,
"complete": [],
"incomplete": [],
"learnings": []
}
if pid_file.exists():
with open(pid_file) as f:
pid = int(f.read().strip())
try:
os.kill(pid, 0)
status["running"] = True
status["pid"] = pid
except OSError:
status["running"] = False
if prd_json.exists():
with open(prd_json) as f:
data = json.load(f)
for story in data.get('userStories', []):
if story.get('passes'):
status["complete"].append({
"id": story['id'],
"title": story['title']
})
else:
status["incomplete"].append({
"id": story['id'],
"title": story['title'],
"priority": story.get('priority', 999)
})
if progress_file.exists():
with open(progress_file) as f:
lines = f.readlines()
status["learnings"] = [l.strip() for l in lines[-10:] if l.strip()]
return status
def main():
"""CLI pentru testing"""
if len(sys.argv) < 2:
print("Usage:")
print(" python ralph_workflow.py create PROJECT_NAME 'description'")
print(" python ralph_workflow.py status PROJECT_NAME")
sys.exit(1)
command = sys.argv[1]
if command == "create":
if len(sys.argv) < 4:
print("Usage: python ralph_workflow.py create PROJECT_NAME 'description'")
sys.exit(1)
project_name = sys.argv[2]
description = sys.argv[3]
print("=" * 70)
print(f"🧪 Ralph workflow: {project_name}")
print("=" * 70)
# Generează PRD și prd.json
prd_file, prd_json = create_prd_and_json(project_name, description, WORKSPACE)
if not prd_file or not prd_json:
print("\n❌ Eroare la generare PRD")
sys.exit(1)
# Lansează Ralph în background
process = run_ralph(prd_json, max_iterations=20, background=True)
if process:
print("\n" + "=" * 70)
print("✅ Workflow complet!")
print(f"📁 Project: {prd_json.parent.parent.parent}")
print(f"🔄 Ralph PID: {process.pid}")
print(f"📋 Monitor: tail -f {prd_json.parent}/logs/ralph.log")
print("=" * 70)
else:
print("\n❌ Eroare la lansare Ralph")
sys.exit(1)
elif command == "status":
if len(sys.argv) < 3:
print("Usage: python ralph_workflow.py status PROJECT_NAME")
sys.exit(1)
project_name = sys.argv[2]
project_dir = WORKSPACE / project_name
if not project_dir.exists():
print(f"❌ Proiect nu există: {project_dir}")
sys.exit(1)
status = check_status(project_dir)
print("\n" + "=" * 70)
print(f"📊 Status: {status['project']}")
print("=" * 70)
print(f"🔄 Running: {'DA (PID: ' + str(status.get('pid', '')) + ')' if status['running'] else 'NU'}")
print(f"✅ Complete: {len(status['complete'])}")
print(f"🔄 Incomplete: {len(status['incomplete'])}")
if status['complete']:
print("\n✅ Stories complete:")
for s in status['complete'][:5]:
print(f" - {s['id']}: {s['title']}")
if status['incomplete']:
print("\n🔄 Stories incomplete:")
for s in sorted(status['incomplete'], key=lambda x: x['priority'])[:5]:
print(f" - {s['id']} (P{s['priority']}): {s['title']}")
if status['learnings']:
print("\n📚 Recent learnings:")
for l in status['learnings'][-5:]:
print(f" {l}")
print("=" * 70)
else:
print(f"❌ Comandă necunoscută: {command}")
sys.exit(1)
if __name__ == "__main__":
main()

128
tools/security_audit.py Executable file
View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""
Security audit script - verifică zilnic:
1. Permisiuni fișiere sensibile (600)
2. Parole hardcoded în cod
3. Fișiere sensibile în git tracking
4. Porturi expuse neașteptat
Exit codes:
0 = OK
1 = Warnings found
2 = Critical issues found
"""
import os
import sys
import subprocess
from pathlib import Path
CLAWD_DIR = Path(__file__).parent.parent
SENSITIVE_FILES = [".env", "credentials"]
REQUIRED_PERMS = 0o600
REQUIRED_DIR_PERMS = 0o700
issues = []
warnings = []
def check_permissions():
"""Check sensitive files have 600 permissions"""
env_file = CLAWD_DIR / ".env"
if env_file.exists():
mode = env_file.stat().st_mode & 0o777
if mode != REQUIRED_PERMS:
issues.append(f".env has {oct(mode)} permissions (should be 0o600)")
creds_dir = CLAWD_DIR / "credentials"
if creds_dir.exists():
for f in creds_dir.iterdir():
if f.is_file():
mode = f.stat().st_mode & 0o777
if mode != REQUIRED_PERMS:
issues.append(f"credentials/{f.name} has {oct(mode)} (should be 0o600)")
def check_hardcoded_secrets():
"""Scan Python files for potential hardcoded secrets"""
patterns = [
'password.*=.*"[^"]{4,}"',
'api_key.*=.*"[^"]{8,}"',
'secret.*=.*"[^"]{8,}"',
]
for py_file in CLAWD_DIR.rglob("*.py"):
if "venv" in str(py_file) or "__pycache__" in str(py_file):
continue
try:
content = py_file.read_text()
for i, line in enumerate(content.split('\n'), 1):
line_lower = line.lower()
# Skip comments and env reads
if line.strip().startswith('#') or 'os.getenv' in line or 'environ' in line:
continue
# Check for hardcoded passwords (excluding empty strings and placeholders)
if ('password' in line_lower or 'api_pass' in line_lower) and '= "' in line and 'in line' not in line_lower:
if '= ""' not in line and '= "***"' not in line:
# Check if it's actually setting a value, not reading
if 'getenv' not in line and 'environ' not in line:
rel_path = py_file.relative_to(CLAWD_DIR)
warnings.append(f"Potential hardcoded secret in {rel_path}:{i}")
except Exception:
pass
def check_git_tracking():
"""Check if sensitive files are tracked by git"""
try:
result = subprocess.run(
["git", "ls-files", ".env", "credentials/"],
cwd=CLAWD_DIR,
capture_output=True,
text=True
)
if result.stdout.strip():
for f in result.stdout.strip().split('\n'):
issues.append(f"Sensitive file tracked by git: {f}")
except Exception:
pass
def check_gitignore():
"""Verify .gitignore contains sensitive patterns"""
gitignore = CLAWD_DIR / ".gitignore"
if gitignore.exists():
content = gitignore.read_text()
required = [".env", "credentials/"]
for pattern in required:
if pattern not in content:
warnings.append(f"Missing from .gitignore: {pattern}")
def main():
print("🔒 Security Audit - Echo")
print("=" * 40)
check_permissions()
check_hardcoded_secrets()
check_git_tracking()
check_gitignore()
if issues:
print("\n🔴 CRITICAL ISSUES:")
for issue in issues:
print(f" - {issue}")
if warnings:
print("\n🟠 WARNINGS:")
for warning in warnings:
print(f" - {warning}")
if not issues and not warnings:
print("\n✅ All checks passed!")
return 0
print(f"\n📊 Summary: {len(issues)} critical, {len(warnings)} warnings")
if issues:
return 2
return 1
if __name__ == "__main__":
sys.exit(main())

289
tools/update_notes_index.py Normal file
View File

@@ -0,0 +1,289 @@
#!/usr/bin/env python3
"""
Generează index.json pentru KB din fișierele .md
Scanează: memory/kb/, memory/, conversations/
Extrage titlu, dată, tags, și domenii (@work, @health, etc.)
"""
import os
import re
import json
from pathlib import Path
from datetime import datetime
BASE_DIR = Path(__file__).parent.parent
KB_ROOT = BASE_DIR / "memory" / "kb"
MEMORY_DIR = BASE_DIR / "memory"
CONVERSATIONS_DIR = BASE_DIR / "conversations"
INDEX_FILE = KB_ROOT / "index.json"
# Domenii de agenți
VALID_DOMAINS = ['work', 'health', 'growth', 'sprijin', 'scout']
# Tipuri speciale (pentru grup-sprijin etc.)
VALID_TYPES = ['exercitiu', 'meditatie', 'reflectie', 'intrebare', 'fisa', 'project', 'memory', 'conversation', 'coaching']
# Cache for rules files
_rules_cache = {}
def load_rules(filepath):
"""Încarcă regulile din .rules.json din directorul fișierului sau părinți"""
dir_path = filepath.parent
# Check cache
if str(dir_path) in _rules_cache:
return _rules_cache[str(dir_path)]
# Look for .rules.json in current dir and parents (up to memory/kb/)
rules = {
"defaultDomains": [],
"defaultTypes": [],
"defaultTags": [],
"inferTypeFromFilename": False,
"filenameTypeMap": {}
}
# Collect rules from all levels (child rules override parent)
rules_chain = []
current = dir_path
while current >= KB_ROOT:
rules_file = current / ".rules.json"
if rules_file.exists():
try:
with open(rules_file, 'r', encoding='utf-8') as f:
rules_chain.insert(0, json.load(f)) # Parent first
except:
pass
current = current.parent
# Merge rules (child overrides parent)
for r in rules_chain:
for key in rules:
if key in r:
if isinstance(rules[key], list):
# Extend lists (don't override)
rules[key] = list(set(rules[key] + r[key]))
else:
rules[key] = r[key]
_rules_cache[str(dir_path)] = rules
return rules
def extract_metadata(filepath, category, subcategory=None):
"""Extrage metadata din fișierul markdown"""
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Extrage titlul (prima linie cu #)
title_match = re.search(r'^#\s+(.+)$', content, re.MULTILINE)
title = title_match.group(1) if title_match else filepath.stem
# Extrage tags (linia cu **Tags:** sau tags:)
tags = []
domains = []
types = []
tags_match = re.search(r'\*\*Tags?:\*\*\s*(.+)$|^Tags?:\s*(.+)$', content, re.MULTILINE | re.IGNORECASE)
if tags_match:
tags_str = tags_match.group(1) or tags_match.group(2)
# Extrage domenii (@work, @health, etc.)
domain_matches = re.findall(r'@(\w+)', tags_str)
domains = [d for d in domain_matches if d in VALID_DOMAINS]
types = [d for d in domain_matches if d in VALID_TYPES]
# Extrage tags normale (#tag)
all_tags = re.findall(r'#([\w-]+)', tags_str)
tags = [t for t in all_tags if t not in VALID_DOMAINS and t not in VALID_TYPES]
# Aplică reguli din .rules.json (dacă există)
rules = load_rules(filepath)
# Adaugă domains implicite (dacă nu sunt deja)
for d in rules.get("defaultDomains", []):
if d not in domains:
domains.append(d)
# Adaugă types implicite
for t in rules.get("defaultTypes", []):
if t not in types:
types.append(t)
# Adaugă tags implicite
for t in rules.get("defaultTags", []):
if t not in tags:
tags.append(t)
# Inferă type din filename (dacă e configurat)
if rules.get("inferTypeFromFilename"):
filename_lower = filepath.stem.lower()
for pattern, type_name in rules.get("filenameTypeMap", {}).items():
if pattern in filename_lower and type_name not in types:
types.append(type_name)
break
# Extrage data din filename (YYYY-MM-DD_slug.md sau YYYY-MM-DD.md)
date_match = re.match(r'(\d{4}-\d{2}-\d{2})', filepath.name)
date = date_match.group(1) if date_match else ""
# Pentru fișiere fără dată în nume, folosește mtime
if not date:
mtime = filepath.stat().st_mtime
date = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d')
# Extrage video URL
video_match = re.search(r'\*\*(?:Video|Link):\*\*\s*(https?://[^\s]+)', content)
video_url = video_match.group(1) if video_match else ""
# Extrage TL;DR sau primele 200 caractere de conținut
tldr = ""
tldr_match = re.search(r'##\s*📋?\s*TL;DR\s*\n+(.+?)(?=\n##|\n---|\Z)', content, re.DOTALL)
if tldr_match:
tldr = tldr_match.group(1).strip()[:200]
else:
# Fallback: primul paragraf după titlu
para_match = re.search(r'^#.+\n+(.+?)(?=\n\n|\n#|\Z)', content, re.DOTALL)
if para_match:
tldr = para_match.group(1).strip()[:200]
if len(tldr) >= 200:
tldr += "..."
# Construiește path-ul relativ pentru web (din dashboard/)
# Dashboard are symlinks: notes-data -> ../kb, memory -> ../memory, conversations -> ../conversations
rel_path = str(filepath.relative_to(BASE_DIR))
# Transformă memory/kb/... în notes-data/... pentru web
if rel_path.startswith('memory/kb/'):
rel_path = 'notes-data/' + rel_path[10:] # strip 'memory/kb/'
return {
"file": rel_path,
"title": title,
"date": date,
"tags": tags,
"domains": domains,
"types": types,
"category": category,
"project": subcategory, # primul nivel sub projects/ (grup-sprijin, vending-master)
"subdir": None, # se setează în scan_directory pentru niveluri mai adânci
"video": video_url,
"tldr": tldr
}
def scan_directory(dir_path, category, subcategory=None, recursive=False):
"""Scanează un director pentru fișiere .md"""
notes = []
if not dir_path.exists():
return notes
# Defaults pentru categorii speciale (memory/, conversations/)
category_defaults = {
"memory": {"types": ["memory"], "domains": []},
"conversations": {"types": ["conversation"], "domains": []}
}
if recursive:
# Scanează recursiv
for filepath in dir_path.rglob("*.md"):
if filepath.name.startswith('.') or 'template' in filepath.name.lower():
continue
try:
# Determină project și subdir din path
# Ex: projects/grup-sprijin/biblioteca/file.md
# project = grup-sprijin, subdir = biblioteca
rel_to_dir = filepath.relative_to(dir_path)
parts = rel_to_dir.parts[:-1] # exclude filename
project = parts[0] if len(parts) > 0 else None
subdir = parts[1] if len(parts) > 1 else None
metadata = extract_metadata(filepath, category, project)
metadata['subdir'] = subdir
notes.append(metadata)
except Exception as e:
print(f" ! Error processing {filepath}: {e}")
else:
# Scanează doar fișierele din director (nu subdirectoare)
for filepath in sorted(dir_path.glob("*.md"), reverse=True):
if filepath.name.startswith('.') or 'template' in filepath.name.lower():
continue
try:
metadata = extract_metadata(filepath, category, subcategory)
# Aplică defaults pentru categoria specială
if category in category_defaults:
defaults = category_defaults[category]
for t in defaults.get("types", []):
if t not in metadata["types"]:
metadata["types"].append(t)
for d in defaults.get("domains", []):
if d not in metadata["domains"]:
metadata["domains"].append(d)
notes.append(metadata)
except Exception as e:
print(f" ! Error processing {filepath}: {e}")
return notes
def generate_index():
"""Generează index.json din toate sursele"""
all_notes = []
# Stats
domain_stats = {d: 0 for d in VALID_DOMAINS}
category_stats = {}
# Scanează TOATE subdirectoarele din memory/kb/ recursiv
print("Scanning memory/kb/ (all subdirectories)...")
for subdir in sorted(KB_ROOT.iterdir()):
if subdir.is_dir() and not subdir.name.startswith('.'):
category = subdir.name
print(f" [{category}]")
notes = scan_directory(subdir, category, recursive=True)
all_notes.extend(notes)
category_stats[category] = len(notes)
for n in notes:
sub = f"/{n['subcategory']}" if n.get('subcategory') else ""
print(f" + {n['title'][:42]}...")
for d in n['domains']:
domain_stats[d] += 1
# 4. Scanează memory/
print("Scanning memory/...")
memory_notes = scan_directory(MEMORY_DIR, "memory")
all_notes.extend(memory_notes)
category_stats["memory"] = len(memory_notes)
for n in memory_notes:
print(f" + {n['title'][:45]}...")
# 5. Scanează conversations/
print("Scanning conversations/...")
conv_notes = scan_directory(CONVERSATIONS_DIR, "conversations")
all_notes.extend(conv_notes)
category_stats["conversations"] = len(conv_notes)
for n in conv_notes:
print(f" + {n['title'][:45]}...")
# Sortează după dată descrescător
all_notes.sort(key=lambda x: x['date'], reverse=True)
# Adaugă metadata globală
output = {
"notes": all_notes,
"stats": {
"total": len(all_notes),
"by_domain": domain_stats,
"by_category": category_stats
},
"domains": VALID_DOMAINS,
"types": VALID_TYPES,
"categories": list(category_stats.keys())
}
with open(INDEX_FILE, 'w', encoding='utf-8') as f:
json.dump(output, f, indent=2, ensure_ascii=False)
print(f"\n✅ Generated {INDEX_FILE} with {len(all_notes)} notes")
print(f" Categories: {category_stats}")
return output
if __name__ == "__main__":
generate_index()

124
tools/youtube_subs.py Executable file
View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""
Download YouTube subtitles/transcript for summarization.
Usage: python3 youtube_subs.py <video_url> [language]
"""
import subprocess
import sys
import os
import json
import re
from pathlib import Path
def clean_vtt(content):
"""Convert VTT to plain text, removing timestamps and duplicates."""
lines = []
seen = set()
for line in content.split('\n'):
# Skip VTT headers, timestamps, positioning
if line.startswith('WEBVTT') or line.startswith('Kind:') or line.startswith('Language:'):
continue
if '-->' in line: # Timestamp line
continue
if line.strip().startswith('<'): # Positioning tags
continue
if not line.strip():
continue
if re.match(r'^\d+$', line.strip()): # Sequence numbers
continue
# Clean HTML tags
clean = re.sub(r'<[^>]+>', '', line).strip()
if clean and clean not in seen:
seen.add(clean)
lines.append(clean)
return ' '.join(lines)
def get_subtitles(url, lang='en'):
"""Download subtitles for a YouTube video."""
yt_dlp = os.path.expanduser('~/.local/bin/yt-dlp')
temp_dir = Path('/tmp/yt_subs')
temp_dir.mkdir(exist_ok=True)
# Clean old files
for f in temp_dir.glob('*'):
f.unlink()
# First, get video info
info_cmd = [yt_dlp, '--dump-json', '--no-download', url]
try:
result = subprocess.run(info_cmd, capture_output=True, text=True, timeout=30)
if result.returncode == 0:
info = json.loads(result.stdout)
title = info.get('title', 'Unknown')
duration = info.get('duration', 0)
print(f"Title: {title}", file=sys.stderr)
print(f"Duration: {duration//60}:{duration%60:02d}", file=sys.stderr)
except Exception as e:
title = "Unknown"
print(f"Could not get video info: {e}", file=sys.stderr)
# Try to get subtitles in order of preference
lang_preferences = [lang, 'ro', 'en', 'en-US', 'en-GB']
for try_lang in lang_preferences:
# Try manual subtitles first
cmd = [
yt_dlp,
'--write-subs',
'--sub-langs', try_lang,
'--skip-download',
'-o', str(temp_dir / '%(id)s.%(ext)s'),
url
]
subprocess.run(cmd, capture_output=True, timeout=60)
# Check if we got subtitles
for ext in ['vtt', 'srt', 'ass']:
for sub_file in temp_dir.glob(f'*.{try_lang}*.{ext}'):
content = sub_file.read_text(encoding='utf-8', errors='replace')
return title, clean_vtt(content)
# Try auto-generated subtitles
for try_lang in lang_preferences:
cmd = [
yt_dlp,
'--write-auto-subs',
'--sub-langs', try_lang,
'--skip-download',
'-o', str(temp_dir / '%(id)s.%(ext)s'),
url
]
subprocess.run(cmd, capture_output=True, timeout=60)
for ext in ['vtt', 'srt', 'ass']:
for sub_file in temp_dir.glob(f'*.{ext}'):
content = sub_file.read_text(encoding='utf-8', errors='replace')
text = clean_vtt(content)
if text:
return title, text
return title, None
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python3 youtube_subs.py <video_url> [language]")
sys.exit(1)
url = sys.argv[1]
lang = sys.argv[2] if len(sys.argv) > 2 else 'en'
title, transcript = get_subtitles(url, lang)
if transcript:
print(f"\n=== {title} ===\n")
print(transcript)
else:
print(f"No subtitles found for: {title}", file=sys.stderr)
sys.exit(1)