adds-up 0.1.0__py3-none-any.whl
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.
- adds_up/__init__.py +9 -0
- adds_up/__main__.py +4 -0
- adds_up/analyse.py +91 -0
- adds_up/checks.py +934 -0
- adds_up/cli.py +178 -0
- adds_up/columns.py +377 -0
- adds_up/models.py +118 -0
- adds_up/numbers.py +323 -0
- adds_up/report.py +230 -0
- adds_up/tables.py +580 -0
- adds_up-0.1.0.dist-info/METADATA +839 -0
- adds_up-0.1.0.dist-info/RECORD +16 -0
- adds_up-0.1.0.dist-info/WHEEL +5 -0
- adds_up-0.1.0.dist-info/entry_points.txt +2 -0
- adds_up-0.1.0.dist-info/licenses/LICENSE +21 -0
- adds_up-0.1.0.dist-info/top_level.txt +1 -0
adds_up/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Reads a price list and reports the arithmetic in it that contradicts itself.
|
|
2
|
+
|
|
3
|
+
Everything here runs on values already printed in the document. No cost, no
|
|
4
|
+
margin, no benchmark, no opinion about whether a price is good.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
__all__ = ["__version__"]
|
adds_up/__main__.py
ADDED
adds_up/analyse.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Putting a run together: read the file, name the columns, run the checks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from . import checks as check_module
|
|
8
|
+
from . import tables
|
|
9
|
+
from .columns import classify
|
|
10
|
+
from .models import Result, TableResult
|
|
11
|
+
from .numbers import convention_of
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _conventions(table: tables.Table, columns) -> tuple[dict, dict]:
|
|
15
|
+
"""Decide, per column, how to read the separators in it.
|
|
16
|
+
|
|
17
|
+
Only columns a check might do arithmetic on are decided, because deciding
|
|
18
|
+
the convention of a description column is meaningless and its explanation
|
|
19
|
+
would be noise in the report.
|
|
20
|
+
"""
|
|
21
|
+
numeric_roles = {
|
|
22
|
+
"quantity", "unit_price", "list_price", "net_price",
|
|
23
|
+
"discount_percent", "percent_of_list", "adjustment",
|
|
24
|
+
"minimum", "maximum",
|
|
25
|
+
}
|
|
26
|
+
conventions: dict[int, object] = {}
|
|
27
|
+
reasons: dict[int, str] = {}
|
|
28
|
+
for column in columns:
|
|
29
|
+
if column.role not in numeric_roles:
|
|
30
|
+
continue
|
|
31
|
+
convention, reason = convention_of(table.column(column.index))
|
|
32
|
+
conventions[column.index] = convention
|
|
33
|
+
reasons[column.index] = reason
|
|
34
|
+
return conventions, reasons
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def analyse(
|
|
38
|
+
path: Path,
|
|
39
|
+
skip: set[str] | None = None,
|
|
40
|
+
sheet: str | None = None,
|
|
41
|
+
header_row: int | None = None,
|
|
42
|
+
overrides: dict[int, str] | None = None,
|
|
43
|
+
) -> Result:
|
|
44
|
+
skip = skip or set()
|
|
45
|
+
result = Result(path=str(path))
|
|
46
|
+
|
|
47
|
+
def names_recognised(row: list[str]) -> int:
|
|
48
|
+
"""How many of a candidate header row's labels this tool has a name for.
|
|
49
|
+
|
|
50
|
+
This is what settles a file with both a title block above the header
|
|
51
|
+
and a sub-header below it. It reads names, never contents, so it does
|
|
52
|
+
not weaken the rule that a column's role comes from what it is called.
|
|
53
|
+
"""
|
|
54
|
+
return sum(1 for column in classify(row, overrides) if column.role)
|
|
55
|
+
|
|
56
|
+
for table in tables.read(
|
|
57
|
+
path, sheet=sheet, header_row=header_row, score=names_recognised
|
|
58
|
+
):
|
|
59
|
+
columns = classify(table.header, overrides)
|
|
60
|
+
conventions, reasons = _conventions(table, columns)
|
|
61
|
+
table_result = TableResult(
|
|
62
|
+
sheet=table.name,
|
|
63
|
+
header_row_number=table.header_row_number,
|
|
64
|
+
header_reason=table.header_reason,
|
|
65
|
+
row_count=len(table.rows),
|
|
66
|
+
columns=columns,
|
|
67
|
+
conventions=reasons,
|
|
68
|
+
preamble=table.preamble,
|
|
69
|
+
header_width=len(table.header),
|
|
70
|
+
rows_wider_than_header=table.wider_than_header,
|
|
71
|
+
rows_narrower_than_header=table.narrower_than_header,
|
|
72
|
+
)
|
|
73
|
+
reader = check_module.Reader(
|
|
74
|
+
sheet=table.name,
|
|
75
|
+
header=table.header,
|
|
76
|
+
rows=table.rows,
|
|
77
|
+
columns=columns,
|
|
78
|
+
conventions=conventions,
|
|
79
|
+
header_row_number=table.header_row_number,
|
|
80
|
+
reasons=reasons,
|
|
81
|
+
row_numbers=table.row_numbers,
|
|
82
|
+
)
|
|
83
|
+
for name in check_module.CHECK_NAMES:
|
|
84
|
+
if name in skip:
|
|
85
|
+
table_result.checks.append(
|
|
86
|
+
check_module.CheckRun(name, False, "switched off with --skip")
|
|
87
|
+
)
|
|
88
|
+
continue
|
|
89
|
+
table_result.checks.append(check_module.RUNNERS[name](reader))
|
|
90
|
+
result.tables.append(table_result)
|
|
91
|
+
return result
|