fec-lint 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fec_lint-0.1.0/.gitignore +8 -0
- fec_lint-0.1.0/LICENSE +21 -0
- fec_lint-0.1.0/PKG-INFO +147 -0
- fec_lint-0.1.0/README.md +125 -0
- fec_lint-0.1.0/pyproject.toml +43 -0
- fec_lint-0.1.0/src/fec_lint/__init__.py +8 -0
- fec_lint-0.1.0/src/fec_lint/cli.py +63 -0
- fec_lint-0.1.0/src/fec_lint/core.py +359 -0
- fec_lint-0.1.0/src/fec_lint/mcp_server.py +32 -0
- fec_lint-0.1.0/tests/conftest.py +36 -0
- fec_lint-0.1.0/tests/fixtures/bad_dates/123456782FEC20251231.txt +215 -0
- fec_lint-0.1.0/tests/fixtures/bad_headers/123456782FEC20251231.txt +215 -0
- fec_lint-0.1.0/tests/fixtures/numbering_gap/123456782FEC20251231.txt +215 -0
- fec_lint-0.1.0/tests/fixtures/unbalanced/123456782FEC20251231.txt +215 -0
- fec_lint-0.1.0/tests/fixtures/valid/123456782FEC20251231.txt +215 -0
- fec_lint-0.1.0/tests/test_cli_and_mcp.py +58 -0
- fec_lint-0.1.0/tests/test_core.py +160 -0
fec_lint-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vincent
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
fec_lint-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fec-lint
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Free structural linter for French FEC files (Fichier des Écritures Comptables), CLI + MCP server
|
|
5
|
+
Project-URL: Homepage, https://github.com/vincenz-cdf/fec-lint
|
|
6
|
+
Project-URL: Full pre-audit, https://apify.com/highfiddle/fec-checker
|
|
7
|
+
Project-URL: Issues, https://github.com/vincenz-cdf/fec-lint/issues
|
|
8
|
+
Author: Vincent
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: accounting,comptabilite,dgfip,fec,france,mcp
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Natural Language :: French
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: mcp<3,>=2
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# fec-lint
|
|
24
|
+
|
|
25
|
+
**[English](#english) · [Français](#français)**
|
|
26
|
+
|
|
27
|
+
<!-- mcp-name: io.github.vincenz-cdf/fec-lint -->
|
|
28
|
+
|
|
29
|
+
> **Technical check, not tax advice.** fec-lint checks the structure of a file against the published FEC format. It does not certify a file or replace an accountant's review.
|
|
30
|
+
>
|
|
31
|
+
> **Contrôle technique, pas un conseil fiscal.** fec-lint vérifie la structure d'un fichier au regard du format FEC publié. Il ne certifie pas un fichier et ne remplace pas la revue d'un expert-comptable.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## English
|
|
36
|
+
|
|
37
|
+
**Free structural linter for French FEC files (*Fichier des Écritures Comptables*, art. A47 A-1 LPF), as a command line tool and an MCP server.**
|
|
38
|
+
|
|
39
|
+
### Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pipx install fec-lint
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Command line
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
fec-lint 123456782FEC20251231.txt
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
123456782FEC20251231.txt: FAILED (encoding utf-8, separator pipe, lines 214)
|
|
53
|
+
error date_invalid x3: Non-existent date in EcritureDate. - line(s) 21, 22, 23
|
|
54
|
+
error date_format x3: Wrong date format in EcritureDate (expected AAAAMMJJ). - line(s) 64, 65, 66
|
|
55
|
+
Structure has errors. Full pre-audit (balance, numbering, risk signals): https://apify.com/highfiddle/fec-checker
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `--json` prints the full result: `{ok, file, errors[], warnings[], stats}`.
|
|
59
|
+
- `--name 123456782FEC20251231.txt` sets the name checked against `SIRENFECAAAAMMJJ.txt`.
|
|
60
|
+
- `--quiet` hides the last line.
|
|
61
|
+
- Exit code: `0` when the structure is OK, `1` on errors, `2` when a file cannot be read. Several files can be given at once.
|
|
62
|
+
- A `.zip` holding exactly one `.txt` FEC is unpacked first.
|
|
63
|
+
|
|
64
|
+
### MCP server
|
|
65
|
+
|
|
66
|
+
Add it to your MCP client (Claude Desktop, Claude Code, Cursor…):
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"fec-lint": {
|
|
72
|
+
"command": "uvx",
|
|
73
|
+
"args": ["--from", "fec-lint", "fec-lint-mcp"]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Without `uv`, install with `pipx install fec-lint` and use `"command": "fec-lint-mcp"`. The server exposes one tool, `lint_fec(path)`, which takes a local file path and returns the same JSON as `--json`.
|
|
80
|
+
|
|
81
|
+
### Free vs full pre-audit
|
|
82
|
+
|
|
83
|
+
| Check | fec-lint (free) | [FEC Checker on Apify](https://apify.com/highfiddle/fec-checker) |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| File name `SIRENFECAAAAMMJJ.txt`, SIREN key | ✅ | ✅ |
|
|
86
|
+
| Encoding, separator, 18 headers | ✅ | ✅ |
|
|
87
|
+
| Field formats: dates, amounts, mandatory fields, pairs | ✅ | ✅ |
|
|
88
|
+
| Balance per entry and for the whole file | — | ✅ |
|
|
89
|
+
| EcritureNum gaps and duplicates, ValidDate chronology | — | ✅ |
|
|
90
|
+
| Risk signals: weekends, holidays, round amounts, Benford, cash 53x, suspense 471, after year end | — | ✅ |
|
|
91
|
+
| Verdict, 0-100 score, AI-written fix list (FR/EN) | — | ✅ |
|
|
92
|
+
| Runs | locally, nothing leaves your machine | on Apify, pay per file |
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Français
|
|
97
|
+
|
|
98
|
+
**Linter structurel gratuit pour les FEC (*Fichier des Écritures Comptables*, art. A47 A-1 LPF), en ligne de commande et en serveur MCP.**
|
|
99
|
+
|
|
100
|
+
### Installation
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pipx install fec-lint
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Ligne de commande
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
fec-lint 123456782FEC20251231.txt
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- `--json` affiche le résultat complet : `{ok, file, errors[], warnings[], stats}`.
|
|
113
|
+
- `--name 123456782FEC20251231.txt` indique le nom à contrôler selon `SIRENFECAAAAMMJJ.txt`.
|
|
114
|
+
- `--quiet` masque la dernière ligne.
|
|
115
|
+
- Code de sortie : `0` si la structure est correcte, `1` en cas d'erreur, `2` si un fichier est illisible. Plusieurs fichiers peuvent être passés à la fois.
|
|
116
|
+
- Un `.zip` contenant un seul FEC `.txt` est décompressé d'abord.
|
|
117
|
+
|
|
118
|
+
Les messages sont en anglais ; les codes de règle (`date_invalid`, `header_missing`…) sont stables.
|
|
119
|
+
|
|
120
|
+
### Serveur MCP
|
|
121
|
+
|
|
122
|
+
Ajoutez-le à votre client MCP (Claude Desktop, Claude Code, Cursor…) avec la configuration ci-dessus. L'outil `lint_fec(path)` prend le chemin d'un fichier local et renvoie le même JSON que `--json`.
|
|
123
|
+
|
|
124
|
+
### Gratuit ou pré-audit complet
|
|
125
|
+
|
|
126
|
+
| Contrôle | fec-lint (gratuit) | [FEC Checker sur Apify](https://apify.com/highfiddle/fec-checker) |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| Nom `SIRENFECAAAAMMJJ.txt`, clé du SIREN | ✅ | ✅ |
|
|
129
|
+
| Encodage, séparateur, 18 colonnes | ✅ | ✅ |
|
|
130
|
+
| Formats : dates, montants, champs obligatoires, couples de champs | ✅ | ✅ |
|
|
131
|
+
| Équilibre par écriture et du fichier | — | ✅ |
|
|
132
|
+
| Ruptures et doublons d'EcritureNum, chronologie de ValidDate | — | ✅ |
|
|
133
|
+
| Signaux de risque : week-ends, jours fériés, montants ronds, Benford, caisse 53x, attente 471, après clôture | — | ✅ |
|
|
134
|
+
| Verdict, score 0-100, plan de correction rédigé par IA (FR/EN) | — | ✅ |
|
|
135
|
+
| Exécution | en local, rien ne quitte votre machine | sur Apify, paiement par fichier |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Development
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
python -m venv .venv
|
|
143
|
+
.venv/bin/pip install -e ".[test]" # Windows: .venv\Scripts\pip
|
|
144
|
+
pytest
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The fixtures in `tests/fixtures/` are synthetic FEC files (fictitious SIREN 123456782). MIT license.
|
fec_lint-0.1.0/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# fec-lint
|
|
2
|
+
|
|
3
|
+
**[English](#english) · [Français](#français)**
|
|
4
|
+
|
|
5
|
+
<!-- mcp-name: io.github.vincenz-cdf/fec-lint -->
|
|
6
|
+
|
|
7
|
+
> **Technical check, not tax advice.** fec-lint checks the structure of a file against the published FEC format. It does not certify a file or replace an accountant's review.
|
|
8
|
+
>
|
|
9
|
+
> **Contrôle technique, pas un conseil fiscal.** fec-lint vérifie la structure d'un fichier au regard du format FEC publié. Il ne certifie pas un fichier et ne remplace pas la revue d'un expert-comptable.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## English
|
|
14
|
+
|
|
15
|
+
**Free structural linter for French FEC files (*Fichier des Écritures Comptables*, art. A47 A-1 LPF), as a command line tool and an MCP server.**
|
|
16
|
+
|
|
17
|
+
### Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pipx install fec-lint
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Command line
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
fec-lint 123456782FEC20251231.txt
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
123456782FEC20251231.txt: FAILED (encoding utf-8, separator pipe, lines 214)
|
|
31
|
+
error date_invalid x3: Non-existent date in EcritureDate. - line(s) 21, 22, 23
|
|
32
|
+
error date_format x3: Wrong date format in EcritureDate (expected AAAAMMJJ). - line(s) 64, 65, 66
|
|
33
|
+
Structure has errors. Full pre-audit (balance, numbering, risk signals): https://apify.com/highfiddle/fec-checker
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `--json` prints the full result: `{ok, file, errors[], warnings[], stats}`.
|
|
37
|
+
- `--name 123456782FEC20251231.txt` sets the name checked against `SIRENFECAAAAMMJJ.txt`.
|
|
38
|
+
- `--quiet` hides the last line.
|
|
39
|
+
- Exit code: `0` when the structure is OK, `1` on errors, `2` when a file cannot be read. Several files can be given at once.
|
|
40
|
+
- A `.zip` holding exactly one `.txt` FEC is unpacked first.
|
|
41
|
+
|
|
42
|
+
### MCP server
|
|
43
|
+
|
|
44
|
+
Add it to your MCP client (Claude Desktop, Claude Code, Cursor…):
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"fec-lint": {
|
|
50
|
+
"command": "uvx",
|
|
51
|
+
"args": ["--from", "fec-lint", "fec-lint-mcp"]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Without `uv`, install with `pipx install fec-lint` and use `"command": "fec-lint-mcp"`. The server exposes one tool, `lint_fec(path)`, which takes a local file path and returns the same JSON as `--json`.
|
|
58
|
+
|
|
59
|
+
### Free vs full pre-audit
|
|
60
|
+
|
|
61
|
+
| Check | fec-lint (free) | [FEC Checker on Apify](https://apify.com/highfiddle/fec-checker) |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| File name `SIRENFECAAAAMMJJ.txt`, SIREN key | ✅ | ✅ |
|
|
64
|
+
| Encoding, separator, 18 headers | ✅ | ✅ |
|
|
65
|
+
| Field formats: dates, amounts, mandatory fields, pairs | ✅ | ✅ |
|
|
66
|
+
| Balance per entry and for the whole file | — | ✅ |
|
|
67
|
+
| EcritureNum gaps and duplicates, ValidDate chronology | — | ✅ |
|
|
68
|
+
| Risk signals: weekends, holidays, round amounts, Benford, cash 53x, suspense 471, after year end | — | ✅ |
|
|
69
|
+
| Verdict, 0-100 score, AI-written fix list (FR/EN) | — | ✅ |
|
|
70
|
+
| Runs | locally, nothing leaves your machine | on Apify, pay per file |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Français
|
|
75
|
+
|
|
76
|
+
**Linter structurel gratuit pour les FEC (*Fichier des Écritures Comptables*, art. A47 A-1 LPF), en ligne de commande et en serveur MCP.**
|
|
77
|
+
|
|
78
|
+
### Installation
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pipx install fec-lint
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Ligne de commande
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
fec-lint 123456782FEC20251231.txt
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- `--json` affiche le résultat complet : `{ok, file, errors[], warnings[], stats}`.
|
|
91
|
+
- `--name 123456782FEC20251231.txt` indique le nom à contrôler selon `SIRENFECAAAAMMJJ.txt`.
|
|
92
|
+
- `--quiet` masque la dernière ligne.
|
|
93
|
+
- Code de sortie : `0` si la structure est correcte, `1` en cas d'erreur, `2` si un fichier est illisible. Plusieurs fichiers peuvent être passés à la fois.
|
|
94
|
+
- Un `.zip` contenant un seul FEC `.txt` est décompressé d'abord.
|
|
95
|
+
|
|
96
|
+
Les messages sont en anglais ; les codes de règle (`date_invalid`, `header_missing`…) sont stables.
|
|
97
|
+
|
|
98
|
+
### Serveur MCP
|
|
99
|
+
|
|
100
|
+
Ajoutez-le à votre client MCP (Claude Desktop, Claude Code, Cursor…) avec la configuration ci-dessus. L'outil `lint_fec(path)` prend le chemin d'un fichier local et renvoie le même JSON que `--json`.
|
|
101
|
+
|
|
102
|
+
### Gratuit ou pré-audit complet
|
|
103
|
+
|
|
104
|
+
| Contrôle | fec-lint (gratuit) | [FEC Checker sur Apify](https://apify.com/highfiddle/fec-checker) |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| Nom `SIRENFECAAAAMMJJ.txt`, clé du SIREN | ✅ | ✅ |
|
|
107
|
+
| Encodage, séparateur, 18 colonnes | ✅ | ✅ |
|
|
108
|
+
| Formats : dates, montants, champs obligatoires, couples de champs | ✅ | ✅ |
|
|
109
|
+
| Équilibre par écriture et du fichier | — | ✅ |
|
|
110
|
+
| Ruptures et doublons d'EcritureNum, chronologie de ValidDate | — | ✅ |
|
|
111
|
+
| Signaux de risque : week-ends, jours fériés, montants ronds, Benford, caisse 53x, attente 471, après clôture | — | ✅ |
|
|
112
|
+
| Verdict, score 0-100, plan de correction rédigé par IA (FR/EN) | — | ✅ |
|
|
113
|
+
| Exécution | en local, rien ne quitte votre machine | sur Apify, paiement par fichier |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
python -m venv .venv
|
|
121
|
+
.venv/bin/pip install -e ".[test]" # Windows: .venv\Scripts\pip
|
|
122
|
+
pytest
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The fixtures in `tests/fixtures/` are synthetic FEC files (fictitious SIREN 123456782). MIT license.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fec-lint"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Free structural linter for French FEC files (Fichier des Écritures Comptables), CLI + MCP server"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Vincent" }]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
dependencies = ["mcp>=2,<3"]
|
|
15
|
+
keywords = ["fec", "comptabilite", "dgfip", "accounting", "france", "mcp"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Topic :: Office/Business :: Financial :: Accounting",
|
|
20
|
+
"Natural Language :: French",
|
|
21
|
+
"Natural Language :: English",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
test = ["pytest>=8"]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
fec-lint = "fec_lint.cli:main"
|
|
29
|
+
fec-lint-mcp = "fec_lint.mcp_server:main"
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/vincenz-cdf/fec-lint"
|
|
33
|
+
"Full pre-audit" = "https://apify.com/highfiddle/fec-checker"
|
|
34
|
+
Issues = "https://github.com/vincenz-cdf/fec-lint/issues"
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.wheel]
|
|
37
|
+
packages = ["src/fec_lint"]
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.sdist]
|
|
40
|
+
include = ["src/fec_lint", "tests", "README.md", "LICENSE"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""fec-lint command line: lint one or more FEC files, print a summary or JSON."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from fec_lint import FULL_PRE_AUDIT_URL, __version__
|
|
10
|
+
from fec_lint.core import lint_file
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _print_result(result: dict) -> None:
|
|
14
|
+
status = 'OK' if result['ok'] else 'FAILED'
|
|
15
|
+
stats = result['stats']
|
|
16
|
+
details = ', '.join(f'{k} {v}' for k, v in (('encoding', stats['encoding']), ('separator', stats['separator']),
|
|
17
|
+
('lines', stats['lineCount'])) if v)
|
|
18
|
+
print(f'{result["file"]}: {status}' + (f' ({details})' if details else ''))
|
|
19
|
+
for label, groups in (('error', result['errors']), ('warning', result['warnings'])):
|
|
20
|
+
for group in groups:
|
|
21
|
+
first = group['occurrences'][0] if group['occurrences'] else {}
|
|
22
|
+
lines = [str(o['line']) for o in group['occurrences'] if o.get('line')]
|
|
23
|
+
where = f' - line(s) {", ".join(lines[:10])}{"..." if group["truncated"] or len(lines) > 10 else ""}' if lines else ''
|
|
24
|
+
print(f' {label} {group["rule"]} x{group["count"]}: {first.get("message", "")}{where}')
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main(argv: list[str] | None = None) -> int:
|
|
28
|
+
parser = argparse.ArgumentParser(
|
|
29
|
+
prog='fec-lint',
|
|
30
|
+
description='Structural check of French FEC files: name, encoding, separator, 18 headers, field formats.',
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument('files', nargs='+', help='FEC files (.txt, or a .zip holding one)')
|
|
33
|
+
parser.add_argument('--json', action='store_true', help='print the full result as JSON')
|
|
34
|
+
parser.add_argument('--name', help='file name to check against SIRENFECAAAAMMJJ.txt (one file only)')
|
|
35
|
+
parser.add_argument('--quiet', action='store_true', help='hide the closing line about the full pre-audit')
|
|
36
|
+
parser.add_argument('--version', action='version', version=f'fec-lint {__version__}')
|
|
37
|
+
args = parser.parse_args(argv)
|
|
38
|
+
if args.name and len(args.files) > 1:
|
|
39
|
+
parser.error('--name works with a single file')
|
|
40
|
+
|
|
41
|
+
results = []
|
|
42
|
+
for path in args.files:
|
|
43
|
+
try:
|
|
44
|
+
results.append(lint_file(path, args.name))
|
|
45
|
+
except OSError as e:
|
|
46
|
+
print(f'{path}: cannot read ({e.strerror or e})', file=sys.stderr)
|
|
47
|
+
return 2
|
|
48
|
+
|
|
49
|
+
if args.json:
|
|
50
|
+
print(json.dumps(results[0] if len(results) == 1 else results, ensure_ascii=False, indent=2))
|
|
51
|
+
else:
|
|
52
|
+
for result in results:
|
|
53
|
+
_print_result(result)
|
|
54
|
+
|
|
55
|
+
all_ok = all(r['ok'] for r in results)
|
|
56
|
+
if not args.quiet and not args.json:
|
|
57
|
+
verdict = 'Structure OK.' if all_ok else 'Structure has errors.'
|
|
58
|
+
print(f'{verdict} Full pre-audit (balance, numbering, risk signals): {FULL_PRE_AUDIT_URL}')
|
|
59
|
+
return 0 if all_ok else 1
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == '__main__':
|
|
63
|
+
sys.exit(main())
|