fix: address account valuation review findings

This commit is contained in:
2026-10-06 14:43:29 +00:00
parent 399d0923be
commit 6a8a27a74e
34 changed files with 1940 additions and 131 deletions
+218 -1
View File
@@ -40,11 +40,26 @@ def test_requirement_docs_are_canonical_and_traceable() -> None:
assert body.count("**State Invariants**") == 10
assert body.count("**판정 기준**") == 10
assert body.count("**검증**") == 10
classification = re.findall(
r"^\*\*검증 분류\*\*: level=(unit|integration|static); backend=(sqlite|application|dashboard)$",
body,
re.M,
)
assert len(classification) == 10
assert {level for level, _ in classification} >= {"unit", "integration", "static"}
assert {backend for _, backend in classification} >= {"sqlite", "application", "dashboard"}
for marker in (
"v12", "idempotent", "append-only", "future", "unverified", "unknown",
"idempotency_conflict", "correction_conflict", "NULL", "VALUATION_MANAGED",
"conditional_approval_required", "MCP", "Dashboard", "auth-before-DB",
"read-only", "monthly", "synthetic", "실제 credential", "호환성",
"reported principal amount", "outstanding principal", "interest", "fee",
"same account/date/currency/purpose", "correction_reason", "no-write",
"source_ref", "evidence idempotency", "cannot roll back newer current cache",
"incomplete_draft", "explicit dated manual asset", "future unknown",
"reconciliation completeness", "unavailable investment", "loan purpose",
"no fake writes", "provenance", "writer", "bootstrap", "init_db",
"read_db", "mode=ro", "db_not_initialized", "no mkdir", "no migrate", "no commit",
):
assert marker in text, marker
@@ -60,7 +75,12 @@ def test_canonical_docs_have_required_sections_and_no_placeholders() -> None:
text = _text(rel)
assert "${" not in text, rel
data = _text("docs/data-model.md")
for marker in ("## 목적과 범위", "## 정본 엔터티", "## 관계와 불변조건", "## 저장과 마이그레이션", "schema 12", "account-valuations", "asset-snapshot-runs"):
for marker in (
"## 목적과 범위", "## 정본 엔터티", "## 관계와 불변조건", "## 저장과 마이그레이션", "schema 12", "account-valuations", "asset-snapshot-runs",
"reported principal amount", "outstanding principal", "NULL", "0", "source_ref", "evidence idempotency",
"correction parent", "same account/date/currency/purpose", "provenance", "reconciliation completeness",
"unavailable investment", "loan purpose", "no fake writes",
):
assert marker in data
interface = _text("docs/interface-contract.md")
for marker in ("## 목적과 범위", "## 계약", "## 호환성", "## 예시", "## 검증", "REQ-003", "register_account_valuation", "preview_asset_snapshot_close", "conditional_approval_required"):
@@ -115,3 +135,200 @@ def test_existing_auth_menu_evidence_inventory_is_preserved() -> None:
"streamlit_browser_evidence.py", "auth-before-DB", "cookie/cleanup/logout",
):
assert marker in interface + runtime + structure, marker
def test_req003_has_exact_canonical_numbered_sections() -> None:
body = REQ.read_text(encoding="utf-8").split("---", 2)[2]
headings = re.findall(r"^## (\d+)\. ", body, re.M)
assert headings == [str(n) for n in range(1, 12)]
assert not re.search(r"^## (?:8|9)\.(?: D| C)", body, re.M)
assert body.count("### REQ-003.") == 20
assert "### REQ-003.01-AC01" in body
assert "### REQ-003.10-AC01" in body
def test_d01_d06_semantics_are_explicit_and_canonical() -> None:
req = REQ.read_text(encoding="utf-8")
data = _text("docs/data-model.md")
interface = _text("docs/interface-contract.md")
runtime = _text("docs/runtime-flow.md")
all_docs = req + data + interface + runtime
for marker in (
"principal", "reported_principal_kind", "reported_principal_label",
"no generic label", "parent scope", "cyclic", "self parent",
"one child", "reason", "no-write", "account-scoped evidence identity",
"explicit key", "key/source mismatch", "last_balance", "external current comparison",
"opening evidence", "INCOMPLETE_CALCULATION", "investment unavailable",
"loan purpose", "loan definition", "ambiguity", "actual valuation_date",
"age", "carry", "verification", "run", "approval", "correction",
"warnings", "exclusions", "close response", "closed", "live", "timeseries",
"monthly", "monthly_source_snapshot_id", "read-model alias", "not a persistence field",
"init_db()", "read_db()", "mode=ro", "db_not_initialized", "no mkdir", "no migrate", "no commit",
):
assert marker in all_docs, marker
assert "generic `label`" not in all_docs
assert "`monthly_source_snapshot_id`" in all_docs
assert "missing database returns `db_not_initialized`" in interface
assert "initialized database read failures return `db_error`" in interface
assert "top-level `approval`, `correction`, `warnings`, `excluded_items`, and `carried_items`" in interface
assert "nested `provenance`" in interface
def test_documented_functions_fields_and_regression_tests_are_real() -> None:
import ast
expected = {
"jarca_ledger_mcp/application/account_valuation_service.py": {
"register_account_valuation", "list_account_valuations",
"select_account_valuation", "correct_account_valuation",
},
"jarca_ledger_mcp/application/assets_service.py": {
"preview_asset_snapshot_close", "close_asset_snapshot", "get_asset_snapshot_report",
},
}
structure = _text("docs/project-structure.md")
interface = _text("docs/interface-contract.md")
runtime = _text("docs/runtime-flow.md")
for rel, names in expected.items():
tree = ast.parse((ROOT / rel).read_text(encoding="utf-8"))
actual = {n.name: n for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)}
assert set(names) <= set(actual), rel
for name in names:
documented = interface
node = actual[name]
parameter_names = [a.arg for a in (*node.args.posonlyargs, *node.args.args, *node.args.kwonlyargs)]
if node.args.vararg:
parameter_names.append(node.args.vararg.arg)
if node.args.kwarg:
parameter_names.append(node.args.kwarg.arg)
signature_line = next((line for line in documented.splitlines() if line.startswith(name + "(") and ": " in line), "")
assert signature_line, name
assert all(param in signature_line for param in parameter_names), name
assert "jarca_ledger_mcp/application/asset_account_services.py" in structure
assert (ROOT / "jarca_ledger_mcp/application/asset_account_services.py").exists()
tests = "\n".join(p.read_text(encoding="utf-8") for p in (ROOT / "tests").glob("test_issue_12_review59_*.py"))
for name in (
"test_valuation_round_trip_preserves_null_zero_and_physical_amount_mapping",
"test_source_ref_is_account_scoped_evidence_identity_without_explicit_key",
"test_direct_correction_requires_matching_existing_parent_and_reason",
"test_investment_without_verified_valuation_and_without_opening_balance_is_incomplete",
"test_timeseries_and_monthly_expose_canonical_asset_provenance",
"test_read_queries_missing_nested_db_return_stable_error_without_creating_path",
):
assert f"def {name}(" in tests, name
for marker in (
"success", "error_code", "error", "close_status", "warnings", "exclusions",
"snapshot_run_id", "valuation_date", "monthly_source_snapshot_id",
):
assert marker in interface + runtime
def test_d01_preservation_matrix_is_item_by_item_and_evidenced() -> None:
structure = _text("docs/project-structure.md")
required_items = {
"schema 11 legacy rows",
"auth gate and cookie lifecycle",
"REQ-002 menu labels/order",
"MCP envelope and tools",
"reconciliation and monthly read models",
}
matrix = structure.split("## D01 preservation matrix", 1)[1].split("## ", 1)[0]
assert "| item | source evidence | test evidence |" in matrix
for item in required_items:
rows = [line for line in matrix.splitlines() if item in line]
assert len(rows) == 1, item
assert rows[0].count("|") >= 4, item
assert "`" in rows[0], item
def test_d04_close_contract_enumerates_all_fields_and_aliases() -> None:
interface = _text("docs/interface-contract.md")
section = interface.split("## D04 snapshot close output contract", 1)[1].split("## ", 1)[0]
top_level = {
"snapshot_run_id", "close_status", "approval", "correction", "warnings",
"excluded_items", "carried_items", "provenance", "valuation_date", "age_days",
"verification_status", "evidence_kind", "monthly_source_snapshot_id",
}
nested = {"approval", "correction", "warnings", "excluded_items", "carried_items", "valuation_date", "age_days", "verification_status", "evidence_kind"}
for field in top_level:
assert f"`{field}`" in section, field
assert "top-level fields" in section
assert "nested `provenance` fields" in section
for field in nested:
assert f"`{field}`" in section, field
assert "alias" in section and "not a persistence field" in section
def test_d05_structure_maps_canonical_ownership_and_boundaries() -> None:
structure = _text("docs/project-structure.md")
section = structure.split("## D05 canonical ownership", 1)[1].split("## ", 1)[0]
required = {
"monthly settlement", "reconciliation", "policy", "read-only", "redaction",
"monthly_settlement_service.py", "reconciliation_service.py", "assets_service.py",
}
for marker in required:
assert marker in section, marker
assert "owner" in section and "boundary" in section
assert "preview" in section and "no write" in section
def test_d06_runtime_flow_states_cache_reconciliation_and_preview_guards() -> None:
runtime = _text("docs/runtime-flow.md")
section = runtime.split("## D06 runtime decision guards", 1)[1].split("## ", 1)[0]
for marker in (
"current cache", "no rollback", "reconciliation complete", "reconciliation unavailable",
"preview", "drift", "revalidation", "close", "no partial write",
):
assert marker in section, marker
assert "complete" in section and "unavailable" in section
def test_d04_contract_matches_final_close_and_replay_implementation() -> None:
interface = _text("docs/interface-contract.md")
req = _text("docs/requirements/req-003_account-valuation-history.md")
section = interface.split("## D04 snapshot close output contract", 1)[1].split("## ", 1)[0]
for marker in (
"replay payload equality",
"no replay-only reused",
"recursive close_metadata allowlist",
"redaction",
"source_type", "source_id", "authority_source", "valuation_id",
"state_hash", "applied_evidence", "excluded_evidence", "carried_evidence",
):
assert marker in section + req, marker
assert "replay response is reconstructed from the persisted run" in section
assert "unknown nested keys are dropped" in section
def test_d04_preview_fingerprint_signature_and_drift_are_exact() -> None:
interface = _text("docs/interface-contract.md")
runtime = _text("docs/runtime-flow.md")
section = interface.split("## D04 snapshot close output contract", 1)[1].split("## ", 1)[0]
assert "preview_fingerprint: str = \"\"" in interface
assert "preview input includes basis_date, selection_policy, included/excluded evidence state hashes, warnings, and candidate_state_hashes" in section
assert "preview output includes preview_fingerprint" in section
assert "authority_drift" in section + runtime
assert "no partial write" in section + runtime
def test_d05_dashboard_read_contract_and_d06_reconciliation_evidence_are_exact() -> None:
interface = _text("docs/interface-contract.md")
structure = _text("docs/project-structure.md")
runtime = _text("docs/runtime-flow.md")
req = _text("docs/requirements/req-003_account-valuation-history.md")
all_docs = interface + structure + runtime + req
for marker in (
"missing DB stable unavailable",
"no-create",
"initialized DB read failure is db_error",
"initialized read has no write",
"opening evidence",
"coverage",
"INCOMPLETE_CALCULATION",
"reconciliation unavailable",
"no inferred balance",
"no fake transaction",
):
assert marker in all_docs, marker
assert "Dashboard gateway" in interface + structure
assert "read_db()" in interface and "mode=ro" in interface