Update wehago matching logic and exclude reports

This commit is contained in:
b17301
2026-07-03 09:07:04 +09:00
parent a09190ecd3
commit da073ad6ef
62 changed files with 36940 additions and 1554 deletions
@@ -0,0 +1,174 @@
from __future__ import annotations
import argparse
import sqlite3
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from runtime_config import DB_PATH
DEFAULT_STATUSES = (
"voucher_matched",
"voucher_unmatched",
"voucher_recheck",
"voucher_excepted",
)
def latest_signature(conn: sqlite3.Connection, start_year: int, end_year: int) -> str:
row = conn.execute(
"""
SELECT signature
FROM wehago_compare_query_groups
WHERE start_year = ?
AND end_year = ?
GROUP BY signature
ORDER BY MAX(updated_at) DESC
LIMIT 1
""",
(start_year, end_year),
).fetchone()
return str(row[0] or "") if row else ""
def status_summary(
conn: sqlite3.Connection,
start_year: int,
end_year: int,
signature: str,
status_key: str,
) -> dict[str, int | float]:
row = conn.execute(
"""
WITH per_group AS (
SELECT
g.group_index,
COUNT(DISTINCT CASE
WHEN COALESCE(r.ledger_account_name, '') <> ''
OR COALESCE(r.ledger_desc, '') <> ''
OR ABS(COALESCE(r.ledger_debit, 0)) >= 0.5
OR ABS(COALESCE(r.ledger_credit, 0)) >= 0.5
THEN COALESCE(r.ledger_date, '') || '|' || COALESCE(r.voucher_no, '')
END) AS wehago_keys,
COUNT(DISTINCT CASE
WHEN COALESCE(r.voucher_account_name, '') <> ''
OR COALESCE(r.voucher_desc, '') <> ''
OR ABS(COALESCE(r.voucher_debit, 0)) >= 0.5
OR ABS(COALESCE(r.voucher_credit, 0)) >= 0.5
THEN COALESCE(r.draft_no, '')
END) AS erp_drafts,
COUNT(*) AS row_count
FROM wehago_compare_query_groups g
LEFT JOIN wehago_compare_query_rows r
ON r.start_year = g.start_year
AND r.end_year = g.end_year
AND r.signature = g.signature
AND r.status_key = g.status_key
AND r.group_index = g.group_index
WHERE g.start_year = ?
AND g.end_year = ?
AND g.signature = ?
AND g.status_key = ?
GROUP BY g.group_index
)
SELECT
COUNT(*) AS groups,
SUM(CASE WHEN wehago_keys > 1 THEN 1 ELSE 0 END) AS mixed_wehago_groups,
SUM(CASE WHEN erp_drafts > 1 THEN 1 ELSE 0 END) AS multi_erp_groups,
COALESCE(MAX(wehago_keys), 0) AS max_wehago_keys,
COALESCE(MAX(erp_drafts), 0) AS max_erp_drafts,
COALESCE(MAX(row_count), 0) AS max_rows,
COALESCE(AVG(row_count), 0) AS avg_rows
FROM per_group
""",
(start_year, end_year, signature, status_key),
).fetchone()
return {
"groups": int(row["groups"] or 0),
"mixed_wehago_groups": int(row["mixed_wehago_groups"] or 0),
"multi_erp_groups": int(row["multi_erp_groups"] or 0),
"max_wehago_keys": int(row["max_wehago_keys"] or 0),
"max_erp_drafts": int(row["max_erp_drafts"] or 0),
"max_rows": int(row["max_rows"] or 0),
"avg_rows": round(float(row["avg_rows"] or 0), 2),
}
def sample_mixed_groups(
conn: sqlite3.Connection,
start_year: int,
end_year: int,
signature: str,
status_key: str,
limit: int,
) -> list[dict[str, object]]:
rows = conn.execute(
"""
SELECT
g.group_index,
g.ledger_date,
g.voucher_no,
COUNT(DISTINCT CASE
WHEN COALESCE(r.ledger_account_name, '') <> ''
OR COALESCE(r.ledger_desc, '') <> ''
OR ABS(COALESCE(r.ledger_debit, 0)) >= 0.5
OR ABS(COALESCE(r.ledger_credit, 0)) >= 0.5
THEN COALESCE(r.ledger_date, '') || '|' || COALESCE(r.voucher_no, '')
END) AS wehago_keys,
COUNT(*) AS row_count
FROM wehago_compare_query_groups g
JOIN wehago_compare_query_rows r
ON r.start_year = g.start_year
AND r.end_year = g.end_year
AND r.signature = g.signature
AND r.status_key = g.status_key
AND r.group_index = g.group_index
WHERE g.start_year = ?
AND g.end_year = ?
AND g.signature = ?
AND g.status_key = ?
GROUP BY g.group_index
HAVING wehago_keys > 1
ORDER BY wehago_keys DESC, row_count DESC, g.group_index ASC
LIMIT ?
""",
(start_year, end_year, signature, status_key, limit),
).fetchall()
return [dict(row) for row in rows]
def main() -> None:
parser = argparse.ArgumentParser(description="Validate WEHAGO voucher projection anchoring.")
parser.add_argument("--start-year", type=int, default=2025)
parser.add_argument("--end-year", type=int, default=2025)
parser.add_argument("--signature", default="")
parser.add_argument("--sample", type=int, default=5)
parser.add_argument("--statuses", nargs="*", default=list(DEFAULT_STATUSES))
args = parser.parse_args()
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
signature = args.signature or latest_signature(conn, args.start_year, args.end_year)
if not signature:
raise SystemExit("No query projection signature found.")
print({"db": str(DB_PATH), "signature": signature})
for status_key in args.statuses:
summary = status_summary(conn, args.start_year, args.end_year, signature, status_key)
print({status_key: summary})
samples = sample_mixed_groups(
conn,
args.start_year,
args.end_year,
signature,
status_key,
args.sample,
)
if samples:
print({"mixed_samples": status_key, "rows": samples})
if __name__ == "__main__":
main()