Update manifest.json with new file metadata and hashes

- Updated timestamps and hashes for backend/app/imports/router.py and backend/app/imports/service.py to reflect recent changes.
- Updated metadata for backend/tests/test_imports.py with new timestamps and hashes.
- Added new entries for .kilo/plugins/graphify.js and AGENTS.md, including their respective timestamps and hashes.
This commit is contained in:
k1nq
2026-09-21 15:55:09 +05:00
parent 35b9ea8305
commit 2117029071
14 changed files with 83324 additions and 4383 deletions
+30 -1
View File
@@ -109,8 +109,24 @@ def detail(document_id: uuid.UUID, db: Db, context: Context) -> dict:
base = summary(document)
reconciliation = raw.get("reconciliation") or {}
rows = []
pending_candidate_row_ids: set[uuid.UUID] = set()
for item in raw["records"]:
hold = item.get("hold_resolution")
candidates = [
{
"id": candidate["id"],
"kind": candidate["kind"],
"confidence": candidate["confidence"],
"resolution": candidate["resolution"],
"existingOperationId": candidate["existing_operation_id"],
}
for candidate in item.get("candidates", [])
]
has_pending_candidate = any(
candidate["resolution"] == "pending" for candidate in candidates
)
if has_pending_candidate:
pending_candidate_row_ids.add(item["id"])
rows.append(
{
"id": item["id"],
@@ -122,8 +138,11 @@ def detail(document_id: uuid.UUID, db: Db, context: Context) -> dict:
"description": item["description"],
"payeeHint": None,
"warnings": (item["warnings"] or {}).get("codes", []),
"decision": "pending" if item["critical"] else item["review_decision"],
"decision": "pending"
if item["critical"] or has_pending_candidate
else item["review_decision"],
"duplicateOfOperationId": item["duplicate_operation_id"],
"candidates": candidates,
"holdResolution": None
if hold is None
else {
@@ -141,6 +160,15 @@ def detail(document_id: uuid.UUID, db: Db, context: Context) -> dict:
for item in raw["records"]
if item["critical"]
]
blockers.extend(
{
"code": "IMPORT_DECISION_REQUIRED",
"message": "Possible duplicate or transfer requires a decision",
"rowId": str(item["id"]),
}
for item in raw["records"]
if item["id"] in pending_candidate_row_ids
)
status = (
"matched"
if reconciliation.get("full")
@@ -176,6 +204,7 @@ def detail(document_id: uuid.UUID, db: Db, context: Context) -> dict:
"discard": sum(row["decision"] == "discard" for row in rows),
"duplicate": sum(row["decision"] == "duplicate" for row in rows),
"transfer": sum(row["decision"] == "transfer" for row in rows),
"pending": sum(row["decision"] == "pending" for row in rows),
},
}
+69 -19
View File
@@ -229,41 +229,61 @@ def _build_candidates(db: Session, *, document: StatementDocument, run: ParseRun
continue
duplicate = None
if source.external_id:
matches = (
match_query = (
db.query(SourceRecord)
.join(ParseRun, ParseRun.id == SourceRecord.parse_run_id)
.join(StatementDocument, StatementDocument.id == SourceRecord.document_id)
.join(SourceRecordLink, SourceRecordLink.source_record_id == SourceRecord.id)
.filter(
SourceRecord.space_id == document.space_id,
SourceRecord.document_id != document.id,
SourceRecord.id != source.id,
SourceRecord.external_id == source.external_id,
SourceRecord.bank_status == "posted",
ParseRun.adapter_id == run.adapter_id,
StatementDocument.status == "confirmed",
StatementDocument.active_parse_run_id == SourceRecord.parse_run_id,
)
.all()
)
if document.account_id is not None:
match_query = match_query.filter(
StatementDocument.account_id == document.account_id
)
matches = match_query.all()
duplicate = matches[0] if len(matches) == 1 else None
if duplicate is None:
matches = (
match_query = (
db.query(SourceRecord)
.join(ParseRun, ParseRun.id == SourceRecord.parse_run_id)
.join(StatementDocument, StatementDocument.id == SourceRecord.document_id)
.join(SourceRecordLink, SourceRecordLink.source_record_id == SourceRecord.id)
.filter(
SourceRecord.space_id == document.space_id,
SourceRecord.document_id != document.id,
SourceRecord.id != source.id,
SourceRecord.record_fp == source.record_fp,
SourceRecord.direction == source.direction,
SourceRecord.bank_status == "posted",
ParseRun.adapter_id == run.adapter_id,
StatementDocument.status == "confirmed",
StatementDocument.active_parse_run_id == SourceRecord.parse_run_id,
)
.all()
)
if document.account_id is not None:
match_query = match_query.filter(
StatementDocument.account_id == document.account_id
)
matches = match_query.all()
duplicate = matches[0] if len(matches) == 1 else None
if duplicate is not None:
link = db.query(SourceRecordLink).filter_by(source_record_id=duplicate.id).one_or_none()
link = db.query(SourceRecordLink).filter_by(source_record_id=duplicate.id).one()
db.add(
DedupCandidate(
space_id=document.space_id,
source_record_id=source.id,
candidate_kind="duplicate",
existing_source_record_id=duplicate.id,
existing_operation_id=link.operation_id if link else None,
existing_operation_id=link.operation_id,
confidence="strong" if source.external_id else "possible",
)
)
@@ -272,20 +292,28 @@ def _build_candidates(db: Session, *, document: StatementDocument, run: ParseRun
if source.occurred_on is None:
continue
opposite = "in" if source.direction == "out" else "out"
transfer_matches = (
transfer_query = (
db.query(SourceRecord)
.join(SourceRecordLink, SourceRecordLink.source_record_id == SourceRecord.id)
.join(StatementDocument, StatementDocument.id == SourceRecord.document_id)
.filter(
SourceRecord.space_id == document.space_id,
SourceRecord.document_id != document.id,
SourceRecord.id != source.id,
SourceRecord.amount_kopecks == source.amount_kopecks,
SourceRecord.direction == opposite,
SourceRecord.bank_status == "posted",
SourceRecord.occurred_on >= source.occurred_on - timedelta(days=1),
SourceRecord.occurred_on <= source.occurred_on + timedelta(days=1),
StatementDocument.status == "confirmed",
StatementDocument.active_parse_run_id == SourceRecord.parse_run_id,
)
.all()
)
if document.account_id is not None:
transfer_query = transfer_query.filter(
StatementDocument.account_id != document.account_id
)
transfer_matches = transfer_query.all()
if len(transfer_matches) == 1:
counterpart = transfer_matches[0]
link = db.query(SourceRecordLink).filter_by(source_record_id=counterpart.id).one()
@@ -504,14 +532,15 @@ def document_detail(db: Session, *, space_id: uuid.UUID, document_id: uuid.UUID)
.order_by(SourceRecord.created_at)
.all()
)
hold_ids = [record.id for record in records]
record_ids = [record.id for record in records]
resolutions: dict[uuid.UUID, dict[str, Any]] = {}
if hold_ids:
candidates: dict[uuid.UUID, list[dict[str, Any]]] = {}
if record_ids:
for item in (
db.query(HoldResolution)
.filter(
HoldResolution.space_id == space_id,
HoldResolution.hold_source_record_id.in_(hold_ids),
HoldResolution.hold_source_record_id.in_(record_ids),
)
.all()
):
@@ -523,6 +552,24 @@ def document_detail(db: Session, *, space_id: uuid.UUID, document_id: uuid.UUID)
"reason": item.reason,
"resolved_at": item.resolved_at,
}
for candidate in (
db.query(DedupCandidate)
.filter(
DedupCandidate.space_id == space_id,
DedupCandidate.source_record_id.in_(record_ids),
)
.order_by(DedupCandidate.created_at)
.all()
):
candidates.setdefault(candidate.source_record_id, []).append(
{
"id": candidate.id,
"kind": candidate.candidate_kind,
"confidence": candidate.confidence,
"resolution": candidate.resolution,
"existing_operation_id": candidate.existing_operation_id,
}
)
return {
"id": document.id,
"status": document.status,
@@ -546,6 +593,7 @@ def document_detail(db: Session, *, space_id: uuid.UUID, document_id: uuid.UUID)
"review_decision": item.review_decision,
"duplicate_operation_id": item.duplicate_operation_id,
"hold_resolution": resolutions.get(item.id),
"candidates": candidates.get(item.id, []),
}
for item in records
],
@@ -783,15 +831,8 @@ def confirm_document(
db.refresh(document, with_for_update=True)
if document.status == "confirmed":
return document
if document.status != "ready" or document.account_id is None:
if document.status not in {"ready", "review_required"} or document.account_id is None:
raise conflict("IMPORT_NOT_READY", "Import review is not complete")
reconciliation = document.reconciliation or {}
if not reconciliation.get("full") and not reconciliation.get(
"confirmed_without_full_reconciliation"
):
raise conflict("IMPORT_RECONCILIATION_REQUIRED", "Reconciliation must be confirmed")
if reconciliation.get("matched") is False:
raise conflict("IMPORT_RECONCILIATION_MISMATCH", "Statement arithmetic does not reconcile")
pending = (
db.query(DedupCandidate.id)
.join(SourceRecord, SourceRecord.id == DedupCandidate.source_record_id)
@@ -806,6 +847,15 @@ def confirm_document(
records = db.query(SourceRecord).filter_by(parse_run_id=document.active_parse_run_id).all()
if any(item.critical for item in records):
raise conflict("IMPORT_FIELDS_UNRESOLVED", "Critical source fields remain unresolved")
reconciliation = document.reconciliation or {}
if not reconciliation.get("full") and not reconciliation.get(
"confirmed_without_full_reconciliation"
):
raise conflict("IMPORT_RECONCILIATION_REQUIRED", "Reconciliation must be confirmed")
if reconciliation.get("matched") is False:
raise conflict("IMPORT_RECONCILIATION_MISMATCH", "Statement arithmetic does not reconcile")
if document.status != "ready":
raise conflict("IMPORT_NOT_READY", "Import review is not complete")
run = db.get(ParseRun, document.active_parse_run_id)
if run is not None:
_apply_hold_matches(
+255
View File
@@ -1,12 +1,14 @@
import uuid
from contextlib import contextmanager
from datetime import date
from types import SimpleNamespace
import pytest
from sqlalchemy.orm import Session
from app.errors import AppError
from app.identity.models import User
from app.imports import router as imports_router
from app.imports import service
from app.imports.adapters.base import SourceRow
from app.imports.models import (
@@ -217,6 +219,259 @@ def test_duplicate_candidate_can_reuse_unique_operation_without_client_id(db: Se
assert db.query(Operation).filter_by(space_id=space.id).count() == 1
def test_duplicate_candidates_ignore_rows_from_same_parse_run(db: Session) -> None:
user, space, document = ready_document(db)
service.confirm_document(db, space_id=space.id, document_id=document.id, user=user)
first_source = (
db.query(SourceRecord).filter_by(parse_run_id=document.active_parse_run_id).one()
)
existing_link = db.query(SourceRecordLink).filter_by(source_record_id=first_source.id).one()
second_source = SourceRecord(
space_id=first_source.space_id,
document_id=document.id,
parse_run_id=document.active_parse_run_id,
occurred_on=first_source.occurred_on,
amount_kopecks=first_source.amount_kopecks,
direction=first_source.direction,
row_ref="sheet0:row3",
record_fp=first_source.record_fp,
)
db.add(second_source)
db.flush()
db.add(
SourceRecordLink(
space_id=space.id,
source_record_id=second_source.id,
operation_id=existing_link.operation_id,
kind="duplicate",
)
)
db.flush()
run = db.get(ParseRun, document.active_parse_run_id)
assert run is not None
assert service._build_candidates(db, document=document, run=run) is False
assert db.query(DedupCandidate).filter_by(space_id=document.space_id).count() == 0
def test_duplicate_candidates_ignore_inactive_parse_runs(db: Session) -> None:
user, space, source_document = ready_document(db)
service.confirm_document(db, space_id=space.id, document_id=source_document.id, user=user)
active_source = (
db.query(SourceRecord).filter_by(parse_run_id=source_document.active_parse_run_id).one()
)
active_link = db.query(SourceRecordLink).filter_by(source_record_id=active_source.id).one()
inactive_run = ParseRun(
space_id=space.id,
document_id=source_document.id,
adapter_id="alfa_debit_xlsx",
status="succeeded",
)
db.add(inactive_run)
db.flush()
inactive_source = SourceRecord(
space_id=space.id,
document_id=source_document.id,
parse_run_id=inactive_run.id,
occurred_on=active_source.occurred_on,
amount_kopecks=active_source.amount_kopecks,
direction=active_source.direction,
row_ref="sheet0:old-row",
record_fp="inactive-run-fingerprint",
)
db.add(inactive_source)
db.flush()
db.add(
SourceRecordLink(
space_id=space.id,
source_record_id=inactive_source.id,
operation_id=active_link.operation_id,
kind="duplicate",
)
)
target_document = StatementDocument(
space_id=space.id,
account_id=source_document.account_id,
original_filename="target.xlsx",
size_bytes=100,
content_sha256=uuid.uuid4().hex + uuid.uuid4().hex,
object_key=uuid.uuid4().hex,
status="review_required",
reconciliation={"full": True, "matched": True},
)
db.add(target_document)
db.flush()
target_run = ParseRun(
space_id=space.id,
document_id=target_document.id,
adapter_id="alfa_debit_xlsx",
status="succeeded",
)
db.add(target_run)
db.flush()
target_document.active_parse_run_id = target_run.id
db.add(
SourceRecord(
space_id=space.id,
document_id=target_document.id,
parse_run_id=target_run.id,
occurred_on=inactive_source.occurred_on,
amount_kopecks=inactive_source.amount_kopecks,
direction=inactive_source.direction,
row_ref="sheet0:row2",
record_fp=inactive_source.record_fp,
)
)
db.flush()
assert service._build_candidates(db, document=target_document, run=target_run) is False
assert db.query(DedupCandidate).filter_by(space_id=space.id).count() == 0
def test_duplicate_candidates_ignore_unconfirmed_documents(db: Session) -> None:
_user, space, first_document = ready_document(db)
first_source = (
db.query(SourceRecord).filter_by(parse_run_id=first_document.active_parse_run_id).one()
)
second_document = StatementDocument(
space_id=space.id,
account_id=first_document.account_id,
original_filename="second.xlsx",
size_bytes=100,
content_sha256=uuid.uuid4().hex + uuid.uuid4().hex,
object_key=uuid.uuid4().hex,
status="review_required",
reconciliation={"full": True, "matched": True},
)
db.add(second_document)
db.flush()
second_run = ParseRun(
space_id=space.id,
document_id=second_document.id,
adapter_id="alfa_debit_xlsx",
status="succeeded",
)
db.add(second_run)
db.flush()
second_document.active_parse_run_id = second_run.id
db.add(
SourceRecord(
space_id=space.id,
document_id=second_document.id,
parse_run_id=second_run.id,
occurred_on=first_source.occurred_on,
amount_kopecks=first_source.amount_kopecks,
direction=first_source.direction,
row_ref="sheet0:row2",
record_fp=first_source.record_fp,
)
)
db.flush()
assert service._build_candidates(db, document=second_document, run=second_run) is False
assert db.query(DedupCandidate).filter_by(space_id=space.id).count() == 0
def test_duplicate_candidates_are_retained_before_account_mapping(db: Session) -> None:
user, space, first_document = ready_document(db)
service.confirm_document(db, space_id=space.id, document_id=first_document.id, user=user)
first_source = (
db.query(SourceRecord).filter_by(parse_run_id=first_document.active_parse_run_id).one()
)
second_document = StatementDocument(
space_id=space.id,
account_id=None,
original_filename="unmapped.xlsx",
size_bytes=100,
content_sha256=uuid.uuid4().hex + uuid.uuid4().hex,
object_key=uuid.uuid4().hex,
status="review_required",
reconciliation={"full": True, "matched": True},
)
db.add(second_document)
db.flush()
second_run = ParseRun(
space_id=space.id,
document_id=second_document.id,
adapter_id="alfa_debit_xlsx",
status="succeeded",
)
db.add(second_run)
db.flush()
second_document.active_parse_run_id = second_run.id
source = SourceRecord(
space_id=space.id,
document_id=second_document.id,
parse_run_id=second_run.id,
occurred_on=first_source.occurred_on,
amount_kopecks=first_source.amount_kopecks,
direction=first_source.direction,
row_ref="sheet0:row2",
record_fp=first_source.record_fp,
)
db.add(source)
db.flush()
assert service._build_candidates(db, document=second_document, run=second_run) is True
candidate = db.query(DedupCandidate).filter_by(source_record_id=source.id).one()
assert candidate.existing_source_record_id == first_source.id
def test_import_detail_exposes_pending_candidate_and_blocker(db: Session) -> None:
_user, space, document = ready_document(db)
document.status = "review_required"
source = db.query(SourceRecord).filter_by(parse_run_id=document.active_parse_run_id).one()
candidate = DedupCandidate(
space_id=space.id,
source_record_id=source.id,
candidate_kind="duplicate",
confidence="possible",
)
db.add(candidate)
db.flush()
response = imports_router.detail(document.id, db, SimpleNamespace(space=space))
assert response["rows"][0]["decision"] == "pending"
assert response["rows"][0]["candidates"] == [
{
"id": candidate.id,
"kind": "duplicate",
"confidence": "possible",
"resolution": "pending",
"existingOperationId": None,
}
]
assert response["blockers"] == [
{
"code": "IMPORT_DECISION_REQUIRED",
"message": "Possible duplicate or transfer requires a decision",
"rowId": str(source.id),
}
]
assert response["totals"]["pending"] == 1
def test_confirm_reports_pending_candidate_instead_of_generic_not_ready(db: Session) -> None:
user, space, document = ready_document(db)
document.status = "review_required"
source = db.query(SourceRecord).filter_by(parse_run_id=document.active_parse_run_id).one()
db.add(
DedupCandidate(
space_id=space.id,
source_record_id=source.id,
candidate_kind="duplicate",
confidence="possible",
)
)
db.flush()
with pytest.raises(AppError) as exc:
service.confirm_document(db, space_id=space.id, document_id=document.id, user=user)
assert exc.value.code == "IMPORT_DECISIONS_REQUIRED"
def test_transfer_merge_revert_restores_counterpart_operation(db: Session) -> None:
user, space, first_document = ready_document(db)
service.confirm_document(db, space_id=space.id, document_id=first_document.id, user=user)
+17790 -5
View File
File diff suppressed because it is too large Load Diff
+21 -17
View File
@@ -1,35 +1,35 @@
{
"0": "identity/router.py",
"0": "identity/service.py",
"1": "budgeting/router.py",
"2": "Base",
"3": "set_space_context",
"4": "imports/router.py",
"5": "alfa_xlsx.py",
"6": "Session",
"6": "ledger/service.py",
"7": "types.ts",
"8": "router.tsx",
"9": "ledger/router.py",
"10": "AccountsPage.tsx",
"10": "main.py",
"11": "compilerOptions",
"12": "AnalyticsPage.tsx",
"13": "analytics/router.py",
"13": "exports/router.py",
"14": "devDependencies",
"15": "test_import_holds.py",
"15": "Session",
"16": "dependencies",
"17": "ImportDetailPage.tsx",
"18": "OperationsPage.tsx",
"19": "SettingsPage.tsx",
"19": "useOperations.ts",
"20": "Toaster.tsx",
"21": "ozon_pdf.py",
"21": "adapters/base.py",
"22": "BudgetPage.tsx",
"23": "client.ts",
"24": "fields.tsx",
"24": "AccountsPage.tsx",
"25": "Docker Compose Development Stack",
"26": "Financial Space",
"27": "scripts",
"28": "dependencies.py",
"28": "exports/service.py",
"29": "General Pool",
"30": "TestBaseContract",
"30": "test_adapter_contracts.py",
"31": "Per-File Envelope Encryption",
"32": "Operation",
"33": "Versioned Statement Imports",
@@ -37,7 +37,7 @@
"35": "Closed Beta Launch Gates",
"36": "package.json",
"37": "Financial Tracker MVP",
"38": "test_adapter_contracts.py",
"38": "TestRocketbankDebitContract",
"39": "generate-api.mjs",
"40": "schema.d.ts",
"41": "test_api.py",
@@ -49,7 +49,7 @@
"47": "graphify",
"48": "@types/react-dom",
"49": "typescript-eslint",
"50": "SourceRow",
"50": "OzonDebitPdfAdapter",
"51": "vitest",
"52": "Favicon SVG (ledger lines icon)",
"53": "init-app-role.sh",
@@ -62,7 +62,7 @@
"60": "vite.config.ts",
"61": "finance-backend",
"62": "LocalDirectoryBlobStore",
"63": "account_fingerprint",
"63": "TestOzonDebitContract",
"64": "Gitea CI and real statement imports",
"65": "20260918_0002_hold_lifecycle.py",
"66": "eslint-plugin-react-hooks",
@@ -70,8 +70,12 @@
"68": "@testing-library/dom",
"69": "@testing-library/react",
"70": "vite",
"71": "spaces/router.py",
"72": "identity/service.py",
"73": "get_settings",
"74": "ColumnChart.tsx"
"71": "ColumnChart.tsx",
"72": "conftest.py",
"73": "imports/service.py",
"74": "BlobStore",
"75": "AppError",
"76": "celery_app.py",
"77": "AGENTS.md",
"78": "graphify.js"
}
+1
View File
@@ -0,0 +1 @@
{"0": "5be265b1520d78ac", "1": "202b56dfc67b2426", "2": "e8067d100b0e48e0", "3": "a199479974543630", "4": "70c8a604a894445d", "5": "092f91ab80789a1d", "6": "3dc6d0d50528ba98", "7": "478b34eb0402fa7d", "8": "c73ee613d6cc4603", "9": "5ebc227112f367f4", "10": "a91e7c0bdb81ca71", "11": "f039d542469fefc7", "12": "0f21d202d861324c", "13": "c90eb017c5213760", "14": "603fe8eab561d17f", "15": "115e19466baf98c7", "16": "1ff1ee70001a89de", "17": "1dd8594011ea2d4f", "18": "8d1c055c213ab51a", "19": "806f8f9f67c1321f", "20": "ba5d835cd3756f81", "21": "bedd3bc934b83f58", "22": "e55afacc55f53152", "23": "cf7c706bee4e29cc", "24": "345cd1b7ec82598d", "25": "f01c92b243645b4e", "26": "54c3380f579b6729", "27": "d8d767c824110809", "28": "ad3dccb7bd56195e", "29": "3147966ac3ee3b0f", "30": "5c32e2734dd85352", "31": "099ee7edfccfb30f", "32": "0778a13a682f0cbb", "33": "6be20de557b963e9", "34": "da7b9f31790187c3", "35": "17bba31989deabe4", "36": "267c43d4a566a343", "37": "cff206ec60c41144", "38": "c9cd0ce5f2cf7cba", "39": "0e87c765d5713ad6", "40": "ce680f01cc87ac2b", "41": "24380e4087fb70d5", "42": "c58209407d4237e8", "43": "54905c779f6306ec", "44": "bdb0b28856af8fe2", "45": "e5d51caa27a2a995", "46": "12858d9498b8f2dc", "47": "2f8c667574e48967", "48": "08332675f638af21", "49": "4ed4ad08eaab7c1c", "50": "354367198161115c", "51": "ba6724f5637cd5d9", "52": "fdf8b4d4bf2be849", "53": "66f8ffc9c3fbedf2", "54": "73723cec09ed2417", "55": "f9e8dac18e2e7bdf", "56": "86619fe91fc50452", "57": "8a3d1d863ddb4d2f", "58": "acee339eea91405a", "59": "99c0fcbdc4f53a0e", "60": "cd42295e6b9fcac1", "61": "3712295d0a11267c", "62": "f787d08450bb4ac6", "63": "c0e8c155ec6fcd84", "64": "fa2cf116b4b9871b", "65": "f3a0edb87b006f25", "66": "a0aa708a9e56bca1", "67": "99286ea0671b6a66", "68": "14861c2ceee47357", "69": "2735bfc7a6304c2e", "70": "e049e07b5afc08cf", "71": "c11d4d615ce6b927", "72": "70d2f844bf68b0d0", "73": "c95100868c3ef766", "74": "17c53201f17a093a", "75": "40bdab93c3490c2a", "76": "7ab7989194b62fca", "77": "97ec841e4f367258", "78": "c9051bc3ad4170f1"}
@@ -0,0 +1,81 @@
{
"0": "get_settings",
"1": "budgeting/router.py",
"2": "Base",
"3": "set_space_context",
"4": "imports/router.py",
"5": "alfa_xlsx.py",
"6": "imports/service.py",
"7": "types.ts",
"8": "router.tsx",
"9": "ledger/router.py",
"10": "AccountsPage.tsx",
"11": "compilerOptions",
"12": "AnalyticsPage.tsx",
"13": "SettingsPage.tsx",
"14": "devDependencies",
"15": "Session",
"16": "dependencies",
"17": "ImportDetailPage.tsx",
"18": "OperationsPage.tsx",
"19": "queryKeys.ts",
"20": "Toaster.tsx",
"21": "adapters/base.py",
"22": "BudgetPage.tsx",
"23": "client.ts",
"24": "fields.tsx",
"25": "Docker Compose Development Stack",
"26": "Financial Space",
"27": "scripts",
"28": "dependencies.py",
"29": "General Pool",
"30": "test_adapter_contracts.py",
"31": "Per-File Envelope Encryption",
"32": "Operation",
"33": "Versioned Statement Imports",
"34": "Finance Frontend SPA",
"35": "Closed Beta Launch Gates",
"36": "package.json",
"37": "Financial Tracker MVP",
"38": "TestRocketbankDebitContract",
"39": "generate-api.mjs",
"40": "schema.d.ts",
"41": "test_api.py",
"42": "i18n-parity.test.ts",
"43": "Expected Income",
"44": "test_private_fixtures.py",
"45": "TestAlfaDebitContract",
"46": "jsdom",
"47": "graphify",
"48": "@types/react-dom",
"49": "typescript-eslint",
"50": "rocketbank_pdf.py",
"51": "vitest",
"52": "Favicon SVG (ledger lines icon)",
"53": "init-app-role.sh",
"54": "app/__init__.py",
"55": "spaces/__init__.py",
"56": "Payee",
"57": "eslint.config.js",
"58": "money.test.ts",
"59": "vite-env.d.ts",
"60": "vite.config.ts",
"61": "finance-backend",
"62": "LocalDirectoryBlobStore",
"63": "TestOzonDebitContract",
"64": "Gitea CI and real statement imports",
"65": "20260918_0002_hold_lifecycle.py",
"66": "eslint-plugin-react-hooks",
"67": "openapi-typescript",
"68": "@testing-library/dom",
"69": "@testing-library/react",
"70": "vite",
"71": "spaces/router.py",
"72": "identity/service.py",
"73": "crypto.py",
"74": "current_session",
"75": "AppError",
"76": "me",
"77": "AGENTS.md",
"78": "graphify.js"
}
+370
View File
@@ -0,0 +1,370 @@
# Graph Report - finance (2026-09-21)
## Corpus Check
- 151 files · ~64,579 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 1275 nodes · 3720 edges · 79 communities (56 shown, 16 thin omitted)
- Extraction: 98% EXTRACTED · 2% INFERRED · 0% AMBIGUOUS · INFERRED: 71 edges (avg confidence: 0.93)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `35b9ea83`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
## Community Hubs (Navigation)
- get_settings
- budgeting/router.py
- Base
- set_space_context
- imports/router.py
- alfa_xlsx.py
- imports/service.py
- types.ts
- router.tsx
- ledger/router.py
- AccountsPage.tsx
- compilerOptions
- AnalyticsPage.tsx
- SettingsPage.tsx
- devDependencies
- Session
- dependencies
- ImportDetailPage.tsx
- OperationsPage.tsx
- queryKeys.ts
- Toaster.tsx
- adapters/base.py
- BudgetPage.tsx
- client.ts
- fields.tsx
- Docker Compose Development Stack
- Financial Space
- scripts
- dependencies.py
- General Pool
- test_adapter_contracts.py
- Per-File Envelope Encryption
- Operation
- Versioned Statement Imports
- Finance Frontend SPA
- Closed Beta Launch Gates
- package.json
- Financial Tracker MVP
- TestRocketbankDebitContract
- generate-api.mjs
- schema.d.ts
- test_api.py
- i18n-parity.test.ts
- Expected Income
- test_private_fixtures.py
- TestAlfaDebitContract
- jsdom
- graphify
- @types/react-dom
- typescript-eslint
- rocketbank_pdf.py
- vitest
- Favicon SVG (ledger lines icon)
- init-app-role.sh
- Payee
- finance-backend
- LocalDirectoryBlobStore
- TestOzonDebitContract
- Gitea CI and real statement imports
- 20260918_0002_hold_lifecycle.py
- eslint-plugin-react-hooks
- openapi-typescript
- @testing-library/dom
- @testing-library/react
- vite
- spaces/router.py
- identity/service.py
- crypto.py
- current_session
- AppError
- me
- AGENTS.md
## God Nodes (most connected - your core abstractions)
1. `Session` - 144 edges
2. `AppError` - 54 edges
3. `Base` - 47 edges
4. `PkMixin` - 39 edges
5. `get_settings()` - 38 edges
6. `TimestampMixin` - 38 edges
7. `conflict()` - 35 edges
8. `record()` - 34 edges
9. `UnsupportedStatementLayout` - 30 edges
10. `StatementDocument` - 30 edges
## Surprising Connections (you probably didn't know these)
- `Typed Same-Origin API Client` --conceptually_related_to--> `Same-Origin Reverse Proxy` [INFERRED]
frontend/README.md → infra/README.md
- `Financial Space Ownership` --conceptually_related_to--> `Financial Space` [INFERRED]
docs/adr/0001-financial-space-ownership-and-rls.md → CONTEXT.md
- `Versioned Statement Imports` --conceptually_related_to--> `Statement Import` [INFERRED]
docs/adr/0003-versioned-statement-imports.md → CONTEXT.md
- `MVP Release Gates` --conceptually_related_to--> `Closed Beta Launch Gates` [INFERRED]
.kilo/plans/1789034192214-financial-tracker-mvp.md → docs/operations/beta-release-runbook.md
- `Carry-Over Budget Reserves` --references--> `Savings Envelope` [EXTRACTED]
docs/adr/0002-journal-and-budget-reserves.md → CONTEXT.md
## Import Cycles
- None detected.
## Hyperedges (group relationships)
- **Auditable Financial Model** — context_operation, context_posting, docs_adr_0002_journal_and_budget_reserves_derived_balances, docs_adr_0002_journal_and_budget_reserves_plan_funding_separation, docs_adr_0002_journal_and_budget_reserves_closure_snapshots [EXTRACTED 1.00]
- **Financial Space Tenancy Boundary** — context_financial_space, context_membership, docs_adr_0001_financial_space_ownership_and_rls_defense_in_depth_authorization, docs_adr_0001_financial_space_ownership_and_rls_transaction_local_rls, docs_adr_0001_financial_space_ownership_and_rls_cross_space_integrity [EXTRACTED 1.00]
- **Safe Statement Import Flow** — context_statement_import, context_parse_run, context_source_record, docs_adr_0003_versioned_statement_imports_immutable_statement_document, docs_adr_0003_versioned_statement_imports_conservative_deduplication, docs_adr_0003_versioned_statement_imports_atomic_confirm_revert [EXTRACTED 1.00]
## Communities (79 total, 16 thin omitted)
### Community 0 - "get_settings"
Cohesion: 0.14
Nodes (27): get_settings(), Settings, send_token_email(), accept_invite(), AcceptInviteBody, confirm_password_reset(), create_invite(), InviteBody (+19 more)
### Community 1 - "budgeting/router.py"
Cohesion: 0.14
Nodes (42): balances(), cash_flow(), category_spend(), plan_fact(), Context, date, Db, get (+34 more)
### Community 2 - "Base"
Cohesion: 0.07
Nodes (82): downgrade(), upgrade(), AuditEvent, Append-only domain/security event. Never stores raw financial payloads., ExpectedIncome, ExpectedIncomeMatch, FundingMovement, PeriodClosure (+74 more)
### Community 3 - "set_space_context"
Cohesion: 0.07
Nodes (44): get_db(), get_engine(), get_session_factory(), Any, sessionmaker, Transaction-local RLS context. No-op on SQLite (tests); fail-closed on…, Transactional scope for workers/endpoints that own the transaction lifecycle., session_scope() (+36 more)
### Community 4 - "imports/router.py"
Cohesion: 0.15
Nodes (33): confirm(), ConfirmBody, decide_row(), DecisionBody, delete_source(), detail(), HoldResolveBody, imports() (+25 more)
### Community 5 - "alfa_xlsx.py"
Cohesion: 0.17
Nodes (24): AlfaDebitXlsxAdapter, _AlfaHeaderMeta, AlfaXlsxAdapterBase, _cell_str(), _debit_find_header(), _debit_header_metadata(), _debit_header_value(), _debit_norm() (+16 more)
### Community 6 - "imports/service.py"
Cohesion: 0.08
Nodes (72): Any, record(), datetime, utcnow(), conflict(), not_found(), magic_kind(), BlobStore (+64 more)
### Community 7 - "types.ts"
Cohesion: 0.06
Nodes (50): api, Account, AccountStatus, BalanceItem, BalancesReport, BudgetBlocker, BudgetCategory, BudgetStatus (+42 more)
### Community 8 - "router.tsx"
Cohesion: 0.11
Nodes (31): AcceptInvitePayload, LoginPayload, PasswordResetPayload, AcceptInvitePage, AccountsPage, BudgetPage, LoginPage, OperationsPage (+23 more)
### Community 9 - "ledger/router.py"
Cohesion: 0.15
Nodes (31): account_json(), AccountBody, accounts(), archive_account(), create_account(), create_operation(), delete_account(), operation_json() (+23 more)
### Community 10 - "AccountsPage.tsx"
Cohesion: 0.12
Nodes (22): AccountType, ImportsPage, Button(), ButtonProps, Variant, ConfirmDialog(), ConfirmDialogProps, EmptyState() (+14 more)
### Community 11 - "compilerOptions"
Cohesion: 0.07
Nodes (28): compilerOptions, baseUrl, esModuleInterop, forceConsistentCasingInFileNames, isolatedModules, jsx, lib, module (+20 more)
### Community 12 - "AnalyticsPage.tsx"
Cohesion: 0.12
Nodes (19): AnalyticsPage, CashFlowColumn, ColumnChart(), ColumnChartProps, BarRow, HorizontalBars(), HorizontalBarsProps, PlanFactBars() (+11 more)
### Community 13 - "SettingsPage.tsx"
Cohesion: 0.23
Nodes (10): NotFoundPage, PageHeader(), PageHeaderProps, useCancelDeletion(), useRequestDeletion(), useRequestExport(), useSpaceDeletion(), DeletionForm() (+2 more)
### Community 14 - "devDependencies"
Cohesion: 0.11
Nodes (19): eslint, @eslint/js, eslint-plugin-jsx-a11y, devDependencies, eslint, @eslint/js, eslint-plugin-jsx-a11y, globals (+11 more)
### Community 15 - "Session"
Cohesion: 0.08
Nodes (88): balances(), cash_flow(), category_spend(), plan_fact(), date, UUID, BudgetPeriod, Calendar month budget period in the space timezone (month granularity). (+80 more)
### Community 16 - "dependencies"
Cohesion: 0.11
Nodes (19): dependencies, @hookform/resolvers, i18next, react, react-dom, react-hook-form, react-i18next, react-router-dom (+11 more)
### Community 17 - "ImportDetailPage.tsx"
Cohesion: 0.27
Nodes (15): RowDecision, ImportDetailPage, useToast(), useConfirmImport(), useImport(), useImportErrorToaster(), useReparseImport(), useRevertImport() (+7 more)
### Community 18 - "OperationsPage.tsx"
Cohesion: 0.17
Nodes (21): Badge(), BadgeTone, useActiveAccounts(), useImports(), useCategorizeOperation(), useCreateOperation(), useLedgerMutation(), useOperations() (+13 more)
### Community 19 - "queryKeys.ts"
Cohesion: 0.16
Nodes (13): invalidationRoots, queryKeys, SESSION_ROOT, sessionInvalidationRoot, SPACE_ROOT, spaceInvalidationRoot, CreateAccountPayload, OperationFilters (+5 more)
### Community 20 - "Toaster.tsx"
Cohesion: 0.13
Nodes (14): Locale, App(), queryClient, AppRoutes(), ToastApi, ToastContext, ToastItem, ToastKind (+6 more)
### Community 21 - "adapters/base.py"
Cohesion: 0.10
Nodes (21): account_fingerprint(), amount_to_kopecks(), _mask_digits(), money_to_kopecks(), normalize_description(), date, Parse statement money strings like '-1 234,56 RUR', '+ 500.00 ₽', '12 345'.…, record_fingerprint() (+13 more)
### Community 22 - "BudgetPage.tsx"
Cohesion: 0.36
Nodes (12): useBudgetCategories(), useBudgetErrorToaster(), useBudgetSummary(), useCloseMonth(), useCopyPreviousMonth(), useFundCategory(), usePeriodMutation(), useReopenMonth() (+4 more)
### Community 23 - "client.ts"
Cohesion: 0.23
Nodes (10): apiClient, ApiError, buildUrl(), ErrorEnvelope, parseErrorEnvelope(), QueryValue, readCsrfToken(), request() (+2 more)
### Community 24 - "fields.tsx"
Cohesion: 0.23
Nodes (10): FieldChrome, InputProps, MoneyField(), MoneyFieldProps, SelectFieldProps, SelectProps, TextareaField(), TextareaFieldProps (+2 more)
### Community 25 - "Docker Compose Development Stack"
Cohesion: 0.29
Nodes (8): Commodity Infrastructure Services, Docker Compose Development Stack, Internal Backend Network, Same-Origin Frontend Service, Worker Parsing Isolation, Finance Infrastructure, Same-Origin Reverse Proxy, Backend and Frontend Workstream Contracts
### Community 26 - "Financial Space"
Cohesion: 0.17
Nodes (12): Credit Reserve, Financial Account, Financial Space, Membership, User, Carry-Over Budget Reserves, Provider-Neutral Data Boundary, Russian Federation Data Residency (+4 more)
### Community 27 - "scripts"
Cohesion: 0.22
Nodes (9): scripts, api:generate, build, dev, lint, preview, test, test:watch (+1 more)
### Community 28 - "dependencies.py"
Cohesion: 0.19
Nodes (16): current_context(), Context, CurrentSession, Header, RequestContext, require_csrf(), forbidden(), Any (+8 more)
### Community 29 - "General Pool"
Cohesion: 0.29
Nodes (8): Budget Period, Funding, General Pool, Monthly Category, Plan Assignment, Savings Envelope, Immutable Closure Snapshots, Plan and Funding Separation
### Community 30 - "test_adapter_contracts.py"
Cohesion: 0.13
Nodes (10): AlfaCreditXlsxAdapter, Protocol, StatementAdapter, detect_adapter(), OzonCreditPdfAdapter, make_draft(), make_source_row(), Sanitized synthetic contract tests for the evidenced statement adapters. These… (+2 more)
### Community 31 - "Per-File Envelope Encryption"
Cohesion: 0.29
Nodes (7): Immutable Statement Document, Per-File Envelope Encryption, Backup and Restore Runbook, Mandatory KEK Backup, Quarterly Restore Drill, Disaster Restore Procedure, Post-Restore Deletion Replay
### Community 32 - "Operation"
Cohesion: 0.47
Nodes (6): Internal Transfer, Operation, Posting, Refund, Derived Account Balances, Operations and Postings Journal
### Community 33 - "Versioned Statement Imports"
Cohesion: 0.20
Nodes (11): Parse Run, Source Record, Statement Import, Atomic Import Confirm and Revert, Conservative Deduplication, Parser Versioning and Review Diff, Versioned Statement Imports, Layout Drift Re-Gating (+3 more)
### Community 34 - "Finance Frontend SPA"
Cohesion: 0.29
Nodes (7): Server-Side Secret Boundary, Grossbukh Application Shell, Accessible Responsive UI, Finance Frontend SPA, RU and EN Localization, Space-Scoped Query Keys, Typed Same-Origin API Client
### Community 35 - "Closed Beta Launch Gates"
Cohesion: 0.20
Nodes (11): Crypto-Shredded Deletion, Closed Beta Release Runbook, Forward-Fix Rollback Policy, Financial Data Incident Response, Closed Beta Launch Gates, Production Launch Sequence, Thirty-Day Space Deletion, Durable Deletion Ledger (+3 more)
### Community 36 - "package.json"
Cohesion: 0.40
Nodes (4): name, private, type, version
### Community 37 - "Financial Tracker MVP"
Cohesion: 0.18
Nodes (11): Audited Break-Glass Access, Cross-Space Referential Integrity, Defense-in-Depth Authorization, Financial Space Ownership, Transaction-Local RLS Context, Budget Invariants, Critical User Path, Financial Tracker MVP (+3 more)
### Community 38 - "TestRocketbankDebitContract"
Cohesion: 0.24
Nodes (3): same_page_rocket_lines(), synthetic_rocket_lines(), TestRocketbankDebitContract
### Community 39 - "generate-api.mjs"
Cohesion: 0.40
Nodes (5): firstReadable(), LOCAL_CANDIDATES, main(), OUT_DIR, REMOTE_CANDIDATES
### Community 40 - "schema.d.ts"
Cohesion: 0.33
Nodes (5): components, $defs, operations, paths, webhooks
### Community 41 - "test_api.py"
Cohesion: 0.67
Nodes (3): TestClient, test_invite_login_and_authenticated_space(), test_openapi_contains_critical_paths()
### Community 44 - "test_private_fixtures.py"
Cohesion: 0.13
Nodes (20): Any, Reconcile parsed rows against declared bank controls with stable diagnostics.…, _reconciliation_summary(), _check(), _fail(), _load_entries(), parsed(), fixture (+12 more)
### Community 45 - "TestAlfaDebitContract"
Cohesion: 0.21
Nodes (3): alfa_workbook_bytes(), synthetic_alfa_rows(), TestAlfaDebitContract
### Community 47 - "graphify"
Cohesion: 0.17
Nodes (11): command, enabled, timeout, type, mcp, graphify, plugin, $schema (+3 more)
### Community 50 - "rocketbank_pdf.py"
Cohesion: 0.27
Nodes (6): _date_from(), _PendingRecord, date, Rocketbank debit statement contract, evidenced against the real document.…, RocketbankDebitPdfAdapter, open_pdf()
### Community 62 - "LocalDirectoryBlobStore"
Cohesion: 0.39
Nodes (3): LocalDirectoryBlobStore, Path, S3-neutral local store; production swaps in an S3-compatible client.
### Community 63 - "TestOzonDebitContract"
Cohesion: 0.16
Nodes (6): minimal_pdf(), ozon_extracted(), _QuotaSettings, Build a small valid single-font PDF with empty or ASCII-only pages., TestOzonDebitContract, MonkeyPatch
### Community 64 - "Gitea CI and real statement imports"
Cohesion: 0.29
Nodes (6): Confirmed decisions, Gitea CI and real statement imports, Goal and constraints, Implementation plan, Principal risks, Validation and rollout
### Community 65 - "20260918_0002_hold_lifecycle.py"
Cohesion: 0.80
Nodes (4): downgrade(), _has_check_constraint(), _has_column(), upgrade()
### Community 71 - "spaces/router.py"
Cohesion: 0.18
Nodes (18): cancel_deletion(), current(), deletion_status(), DeletionBody, invite_member(), MemberInviteBody, alias, BaseModel (+10 more)
### Community 72 - "identity/service.py"
Cohesion: 0.28
Nodes (18): unauthorized(), accept_invitation(), authenticate(), create_invitation(), create_password_reset(), create_session(), get_active_session(), normalize_email() (+10 more)
### Community 73 - "crypto.py"
Cohesion: 0.40
Nodes (5): encrypt_file(), EncryptedFile, Per-file random DEK; DEK wrapped with the KEK from the secret manager., Crypto-shred hook: deleting wrapped DEK rows in DB plus blobs removes access., shred_dek_material()
### Community 74 - "current_session"
Cohesion: 0.29
Nodes (7): current_session(), alias, Db, User, Cookie, session_cookie_name, UserSession
### Community 75 - "AppError"
Cohesion: 0.43
Nodes (5): AppError, Domain error rendered as a machine-readable JSON error payload., unprocessable(), _quota_error(), Exception
### Community 76 - "me"
Cohesion: 0.67
Nodes (3): me(), CurrentSession, get
## Knowledge Gaps
- **186 isolated node(s):** `$schema`, `file:///home/k1nq/repos/finance/.kilo/plugins/graphify.js`, `type`, `/home/k1nq/.local/bin/graphify-mcp`, `/home/k1nq/repos/finance/graphify-out/graph.json` (+181 more)
These have ≤1 connection - possible missing edges or undocumented components. (Counts symbols only; 339 node(s) total have ≤1 connection when file, concept and rationale nodes are included.)
- **16 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `Session` connect `Session` to `Base`, `set_space_context`, `imports/service.py`, `identity/service.py`, `dependencies.py`?**
_High betweenness centrality (0.087) - this node is a cross-community bridge._
- **Why does `AppError` connect `AppError` to `get_settings`, `Base`, `set_space_context`, `imports/router.py`, `alfa_xlsx.py`, `imports/service.py`, `TestRocketbankDebitContract`, `identity/service.py`, `crypto.py`, `Session`, `adapters/base.py`, `dependencies.py`, `test_adapter_contracts.py`, `TestOzonDebitContract`?**
_High betweenness centrality (0.067) - this node is a cross-community bridge._
- **Why does `bad_request()` connect `Session` to `budgeting/router.py`, `imports/service.py`, `spaces/router.py`, `identity/service.py`, `ledger/router.py`, `AppError`, `dependencies.py`?**
_High betweenness centrality (0.017) - this node is a cross-community bridge._
- **Are the 2 inferred relationships involving `Session` (e.g. with `UTCDateTime` and `test_postgres_rls_requires_matching_transaction_context()`) actually correct?**
_`Session` has 2 INFERRED edges - model-reasoned connections that need verification._
- **Are the 24 inferred relationships involving `AppError` (e.g. with `request_export()` and `login()`) actually correct?**
_`AppError` has 24 INFERRED edges - model-reasoned connections that need verification._
- **Are the 3 inferred relationships involving `Base` (e.g. with `downgrade()` and `upgrade()`) actually correct?**
_`Base` has 3 INFERRED edges - model-reasoned connections that need verification._
- **What connects `$schema`, `file:///home/k1nq/repos/finance/.kilo/plugins/graphify.js`, `type` to the rest of the system?**
_186 weakly-connected nodes found - possible documentation gaps or missing edges._
File diff suppressed because it is too large Load Diff
+938
View File
@@ -0,0 +1,938 @@
{
"backend/alembic/env.py": {
"mtime": 1789129232.0159023,
"seen": 1789377303.0691774,
"ast_hash": "d78f67ad0caed3f10805122e8d8e82d0",
"semantic_hash": "d78f67ad0caed3f10805122e8d8e82d0"
},
"backend/alembic/versions/20260911_0001_initial_schema.py": {
"mtime": 1789130024.8355556,
"seen": 1789377303.0691814,
"ast_hash": "3a446ae61cbf2362d24dc9ab6e72dd2d",
"semantic_hash": "3a446ae61cbf2362d24dc9ab6e72dd2d"
},
"backend/app/__init__.py": {
"mtime": 1789127291.0254955,
"seen": 1789377303.0691833,
"ast_hash": "d41d8cd98f00b204e9800998ecf8427e",
"semantic_hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"backend/app/analytics/__init__.py": {
"mtime": 1789128680.8117132,
"seen": 1789377303.0691845,
"ast_hash": "d41d8cd98f00b204e9800998ecf8427e",
"semantic_hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"backend/app/analytics/router.py": {
"mtime": 1789129123.9786532,
"seen": 1789377303.0691855,
"ast_hash": "dc4d5f43d1e17ae77b21f774d37c1106",
"semantic_hash": "dc4d5f43d1e17ae77b21f774d37c1106"
},
"backend/app/analytics/service.py": {
"mtime": 1789129314.8210335,
"seen": 1789377303.0691862,
"ast_hash": "0385e76cccd5d140b8cc01af5b74821e",
"semantic_hash": "0385e76cccd5d140b8cc01af5b74821e"
},
"backend/app/audit/__init__.py": {
"mtime": 1789127312.3538313,
"seen": 1789377303.0691874,
"ast_hash": "d41d8cd98f00b204e9800998ecf8427e",
"semantic_hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"backend/app/audit/models.py": {
"mtime": 1789127312.353331,
"seen": 1789377303.069188,
"ast_hash": "84a2cb94c3a10150a76f59ff762efc49",
"semantic_hash": "84a2cb94c3a10150a76f59ff762efc49"
},
"backend/app/audit/service.py": {
"mtime": 1789127312.3538313,
"seen": 1789377303.0691888,
"ast_hash": "791163353fbef85a77ace789c704188c",
"semantic_hash": "791163353fbef85a77ace789c704188c"
},
"backend/app/budgeting/__init__.py": {
"mtime": 1789127598.9634895,
"seen": 1789377303.0691895,
"ast_hash": "d41d8cd98f00b204e9800998ecf8427e",
"semantic_hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"backend/app/budgeting/models.py": {
"mtime": 1789356526.9709191,
"seen": 1789377303.0691907,
"ast_hash": "039081cab3d91704e8f8a7152a981304",
"semantic_hash": "039081cab3d91704e8f8a7152a981304"
},
"backend/app/budgeting/router.py": {
"mtime": 1789129314.8240335,
"seen": 1789377303.0691917,
"ast_hash": "c352243e4fb85ab49f9cf4532c11bd3f",
"semantic_hash": "c352243e4fb85ab49f9cf4532c11bd3f"
},
"backend/app/budgeting/service.py": {
"mtime": 1789730971.072982,
"seen": 1789735433.158112,
"ast_hash": "c30d4c58608f88b81ff05be63a314247",
"semantic_hash": ""
},
"backend/app/categorization/__init__.py": {
"mtime": 1789127372.62572,
"seen": 1789377303.0691931,
"ast_hash": "d41d8cd98f00b204e9800998ecf8427e",
"semantic_hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"backend/app/categorization/models.py": {
"mtime": 1789130236.8679283,
"seen": 1789377303.0691943,
"ast_hash": "922083067aa2a24d67954296844db8f0",
"semantic_hash": "922083067aa2a24d67954296844db8f0"
},
"backend/app/categorization/service.py": {
"mtime": 1789128297.7359908,
"seen": 1789377303.0691953,
"ast_hash": "9590b3c99e7c2b20b40d895d7495b5cf",
"semantic_hash": "9590b3c99e7c2b20b40d895d7495b5cf"
},
"backend/app/config.py": {
"mtime": 1789356583.024539,
"seen": 1789377303.069196,
"ast_hash": "af0c778e49e82763261430b477375048",
"semantic_hash": "af0c778e49e82763261430b477375048"
},
"backend/app/db/__init__.py": {
"mtime": 1789127291.0254955,
"seen": 1789377303.069197,
"ast_hash": "d41d8cd98f00b204e9800998ecf8427e",
"semantic_hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"backend/app/db/base.py": {
"mtime": 1789128247.897287,
"seen": 1789377303.069198,
"ast_hash": "ec981c0f87b0470519cca7727172a950",
"semantic_hash": "ec981c0f87b0470519cca7727172a950"
},
"backend/app/db/models.py": {
"mtime": 1789128813.706167,
"seen": 1789377303.0691986,
"ast_hash": "338516ce68785afd5a7fe37f06e1c56f",
"semantic_hash": "338516ce68785afd5a7fe37f06e1c56f"
},
"backend/app/db/session.py": {
"mtime": 1789128374.1340652,
"seen": 1789377303.0691996,
"ast_hash": "9e29a1eebfec6e48e97baf28e506b037",
"semantic_hash": "9e29a1eebfec6e48e97baf28e506b037"
},
"backend/app/dependencies.py": {
"mtime": 1789128452.8041663,
"seen": 1789377303.0692003,
"ast_hash": "20666502da4de510d1cf6939e35e9c20",
"semantic_hash": "20666502da4de510d1cf6939e35e9c20"
},
"backend/app/errors.py": {
"mtime": 1789127291.0254955,
"seen": 1789377303.0692015,
"ast_hash": "368489412799d564f127259ecc0c474c",
"semantic_hash": "368489412799d564f127259ecc0c474c"
},
"backend/app/exports/__init__.py": {
"mtime": 1789128752.0481794,
"seen": 1789377303.0692022,
"ast_hash": "d41d8cd98f00b204e9800998ecf8427e",
"semantic_hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"backend/app/exports/models.py": {
"mtime": 1789128752.0481794,
"seen": 1789377303.069203,
"ast_hash": "b76e7bca1889bdf298b5158da2f35642",
"semantic_hash": "b76e7bca1889bdf298b5158da2f35642"
},
"backend/app/exports/router.py": {
"mtime": 1789358158.624152,
"seen": 1789377303.0692039,
"ast_hash": "28cc16dfa8d0b6b588e8f3a7d0a2f110",
"semantic_hash": "28cc16dfa8d0b6b588e8f3a7d0a2f110"
},
"backend/app/exports/service.py": {
"mtime": 1789360140.0563023,
"seen": 1789377303.0692048,
"ast_hash": "a9f5a725bd06779f83eb20beda61881c",
"semantic_hash": "a9f5a725bd06779f83eb20beda61881c"
},
"backend/app/exports/tasks.py": {
"mtime": 1789130443.0178635,
"seen": 1789377303.0692055,
"ast_hash": "de092ecca36d64718378a44d06adb5b6",
"semantic_hash": "de092ecca36d64718378a44d06adb5b6"
},
"backend/app/identity/__init__.py": {
"mtime": 1789127312.3538313,
"seen": 1789377303.0692062,
"ast_hash": "d41d8cd98f00b204e9800998ecf8427e",
"semantic_hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"backend/app/identity/mailer.py": {
"mtime": 1789357272.300995,
"seen": 1789377303.069207,
"ast_hash": "1e6fd1b285085e96819214bccde90a34",
"semantic_hash": "1e6fd1b285085e96819214bccde90a34"
},
"backend/app/identity/models.py": {
"mtime": 1789130024.833855,
"seen": 1789377303.069208,
"ast_hash": "f7ad1f0468ad21ac2b86e49ef260f747",
"semantic_hash": "f7ad1f0468ad21ac2b86e49ef260f747"
},
"backend/app/identity/router.py": {
"mtime": 1789356583.0255392,
"seen": 1789377303.0692086,
"ast_hash": "00089d34e9d5d5e965334b4520935bf8",
"semantic_hash": "00089d34e9d5d5e965334b4520935bf8"
},
"backend/app/identity/service.py": {
"mtime": 1789130458.2090783,
"seen": 1789377303.0692096,
"ast_hash": "a2efb5e20098740645da8b58bd95073d",
"semantic_hash": "a2efb5e20098740645da8b58bd95073d"
},
"backend/app/imports/__init__.py": {
"mtime": 1789127900.8512905,
"seen": 1789377303.0692103,
"ast_hash": "d41d8cd98f00b204e9800998ecf8427e",
"semantic_hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"backend/app/imports/adapters/__init__.py": {
"mtime": 1789733796.3306727,
"seen": 1789735433.158127,
"ast_hash": "0d3191f98e2602dcb37b96eefcb9d2ea",
"semantic_hash": ""
},
"backend/app/imports/adapters/alfa_xlsx.py": {
"mtime": 1789736201.215202,
"seen": 1789736272.9455209,
"ast_hash": "31eee0cded845f9ddaff98d2127f4354",
"semantic_hash": ""
},
"backend/app/imports/adapters/base.py": {
"mtime": 1789733778.7145572,
"seen": 1789735433.1581285,
"ast_hash": "4f6364ab07cf38ba2eea1ba94678d784",
"semantic_hash": ""
},
"backend/app/imports/adapters/ozon_pdf.py": {
"mtime": 1789733868.836172,
"seen": 1789735433.158129,
"ast_hash": "dfa6dc9ccf2c655e6c2eeaceea0cf06a",
"semantic_hash": ""
},
"backend/app/imports/adapters/rocketbank_pdf.py": {
"mtime": 1789734555.2246265,
"seen": 1789735433.1581297,
"ast_hash": "b4f28ba73c56da5f5eb96eb4f129e098",
"semantic_hash": ""
},
"backend/app/imports/blob.py": {
"mtime": 1789129216.5581613,
"seen": 1789377303.0692153,
"ast_hash": "60d315883175474711dff503c70400a5",
"semantic_hash": "60d315883175474711dff503c70400a5"
},
"backend/app/imports/celery_app.py": {
"mtime": 1789130443.0194046,
"seen": 1789377303.069216,
"ast_hash": "03e8b50c4722e8252a34508df1164985",
"semantic_hash": "03e8b50c4722e8252a34508df1164985"
},
"backend/app/imports/crypto.py": {
"mtime": 1789127900.8612905,
"seen": 1789377303.0692167,
"ast_hash": "c72d7849c6f46e6c9a9136f3ae9dc92f",
"semantic_hash": "c72d7849c6f46e6c9a9136f3ae9dc92f"
},
"backend/app/imports/models.py": {
"mtime": 1789730695.7355492,
"seen": 1789735433.1581326,
"ast_hash": "b99c93656a17603691838c9e7963d9ed",
"semantic_hash": ""
},
"backend/app/imports/readers.py": {
"mtime": 1789128289.6877162,
"seen": 1789377303.0692189,
"ast_hash": "0904afccafd9eb544b4d6846fc6384c8",
"semantic_hash": "0904afccafd9eb544b4d6846fc6384c8"
},
"backend/app/imports/router.py": {
"mtime": 1789986959.5578556,
"seen": 1789987085.811699,
"ast_hash": "c092553fcb48fbd7a19aa4000f24a5b8",
"semantic_hash": ""
},
"backend/app/imports/service.py": {
"mtime": 1789987052.7950857,
"seen": 1789987085.8116996,
"ast_hash": "361eeb454e2614b61194d3d067b117d5",
"semantic_hash": ""
},
"backend/app/imports/tasks.py": {
"mtime": 1789356921.2930734,
"seen": 1789377303.0692213,
"ast_hash": "1b26900740c5c6905601ba65d645857b",
"semantic_hash": "1b26900740c5c6905601ba65d645857b"
},
"backend/app/ledger/__init__.py": {
"mtime": 1789127415.7777057,
"seen": 1789377303.0692222,
"ast_hash": "d41d8cd98f00b204e9800998ecf8427e",
"semantic_hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"backend/app/ledger/models.py": {
"mtime": 1789130236.8679283,
"seen": 1789377303.069223,
"ast_hash": "81335e0d9f1f5a71be1626ec218da410",
"semantic_hash": "81335e0d9f1f5a71be1626ec218da410"
},
"backend/app/ledger/router.py": {
"mtime": 1789358018.8352346,
"seen": 1789377303.0692236,
"ast_hash": "809411054fbede0694ed6ee863723fe5",
"semantic_hash": "809411054fbede0694ed6ee863723fe5"
},
"backend/app/ledger/service.py": {
"mtime": 1789358018.8349617,
"seen": 1789377303.0692244,
"ast_hash": "96a715590e5af44c494e5c7c817780a3",
"semantic_hash": "96a715590e5af44c494e5c7c817780a3"
},
"backend/app/main.py": {
"mtime": 1789130118.4222198,
"seen": 1789377303.0692253,
"ast_hash": "b4f205a9d87d905121bda1e4a65c20ea",
"semantic_hash": "b4f205a9d87d905121bda1e4a65c20ea"
},
"backend/app/ratelimit.py": {
"mtime": 1789357059.570077,
"seen": 1789377303.069226,
"ast_hash": "e1732ffc6140108ea33bc70591653dd8",
"semantic_hash": "e1732ffc6140108ea33bc70591653dd8"
},
"backend/app/security.py": {
"mtime": 1789360140.0553021,
"seen": 1789377303.069227,
"ast_hash": "0d73845e1a3da2c128cbb1aacd39506d",
"semantic_hash": "0d73845e1a3da2c128cbb1aacd39506d"
},
"backend/app/spaces/__init__.py": {
"mtime": 1789127346.4407892,
"seen": 1789377303.0692277,
"ast_hash": "d41d8cd98f00b204e9800998ecf8427e",
"semantic_hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"backend/app/spaces/models.py": {
"mtime": 1789128247.897287,
"seen": 1789377303.0692286,
"ast_hash": "7bbb57eb5e95d7b59087f478d7c20412",
"semantic_hash": "7bbb57eb5e95d7b59087f478d7c20412"
},
"backend/app/spaces/router.py": {
"mtime": 1789357010.1013982,
"seen": 1789377303.0692296,
"ast_hash": "073fa39a355b257a832ead3830942d8f",
"semantic_hash": "073fa39a355b257a832ead3830942d8f"
},
"backend/app/spaces/service.py": {
"mtime": 1789129887.1068308,
"seen": 1789377303.0692303,
"ast_hash": "99f21c94dad6403b77f4cafed97c1c2e",
"semantic_hash": "99f21c94dad6403b77f4cafed97c1c2e"
},
"backend/app/worker.py": {
"mtime": 1789129206.4285526,
"seen": 1789377303.069231,
"ast_hash": "4788addc575411056226ee892d1e2806",
"semantic_hash": "4788addc575411056226ee892d1e2806"
},
"backend/pyproject.toml": {
"mtime": 1789731892.783645,
"seen": 1789735433.1581438,
"ast_hash": "6ae54b23d066a4a48b7edbbe964af75f",
"semantic_hash": ""
},
"backend/tests/conftest.py": {
"mtime": 1789129509.9246948,
"seen": 1789377303.069233,
"ast_hash": "f74b424930fc6a978616e2e1bd081a0b",
"semantic_hash": "f74b424930fc6a978616e2e1bd081a0b"
},
"backend/tests/test_api.py": {
"mtime": 1789130118.4241138,
"seen": 1789377303.069234,
"ast_hash": "bfec0778131788a91a0bc5c9ec2ad8ba",
"semantic_hash": "bfec0778131788a91a0bc5c9ec2ad8ba"
},
"backend/tests/test_budget.py": {
"mtime": 1789358107.2584474,
"seen": 1789377303.0692346,
"ast_hash": "f6e5c4d1e1d0f2b1941acf8ec9f6678b",
"semantic_hash": "f6e5c4d1e1d0f2b1941acf8ec9f6678b"
},
"backend/tests/test_imports.py": {
"mtime": 1789986980.8700209,
"seen": 1789987085.8117144,
"ast_hash": "8c6aec11825fe46740ad64b658da2a15",
"semantic_hash": ""
},
"backend/tests/test_ledger_properties.py": {
"mtime": 1789129573.0755556,
"seen": 1789377303.0692365,
"ast_hash": "fb7d5e4503c9482b5014e28528f75e2a",
"semantic_hash": "fb7d5e4503c9482b5014e28528f75e2a"
},
"backend/tests/test_postgres_and_s3.py": {
"mtime": 1789734220.6980176,
"seen": 1789735433.1581545,
"ast_hash": "1def023e0a52eed53bcb97557919484b",
"semantic_hash": ""
},
"frontend/eslint.config.js": {
"mtime": 1789128706.6116898,
"seen": 1789377303.069238,
"ast_hash": "ed54799a921d10394f826893acb46876",
"semantic_hash": "ed54799a921d10394f826893acb46876"
},
"frontend/package.json": {
"mtime": 1789378006.2922633,
"seen": 1789735433.1581702,
"ast_hash": "10697e744e175f761f5ddb85c7925350",
"semantic_hash": ""
},
"frontend/scripts/generate-api.mjs": {
"mtime": 1789126898.3307452,
"seen": 1789377303.0692399,
"ast_hash": "61461f226e7d2bba9730be8b7da165f9",
"semantic_hash": "61461f226e7d2bba9730be8b7da165f9"
},
"frontend/src/App.tsx": {
"mtime": 1789127518.1600943,
"seen": 1789377303.0692406,
"ast_hash": "752eea473bb3bb6d8a2dd28f5ae87280",
"semantic_hash": "752eea473bb3bb6d8a2dd28f5ae87280"
},
"frontend/src/api/client.ts": {
"mtime": 1789127044.43022,
"seen": 1789377303.0692413,
"ast_hash": "dbea075a006b7703340f7f93e69d53ac",
"semantic_hash": "dbea075a006b7703340f7f93e69d53ac"
},
"frontend/src/api/endpoints.ts": {
"mtime": 1789357272.302138,
"seen": 1789377303.069242,
"ast_hash": "c4d7620bf50bef28b20286b6093e7e30",
"semantic_hash": "c4d7620bf50bef28b20286b6093e7e30"
},
"frontend/src/api/generated/schema.d.ts": {
"mtime": 1789130160.8338337,
"seen": 1789377303.069243,
"ast_hash": "8857d50fb909f019e1d8c03009c0ada7",
"semantic_hash": "8857d50fb909f019e1d8c03009c0ada7"
},
"frontend/src/api/queryKeys.ts": {
"mtime": 1789127044.43022,
"seen": 1789377303.0692437,
"ast_hash": "5ec23457cf6b0f223df671d3bc0f8455",
"semantic_hash": "5ec23457cf6b0f223df671d3bc0f8455"
},
"frontend/src/api/types.ts": {
"mtime": 1789357272.302138,
"seen": 1789377303.069248,
"ast_hash": "0d3865b4525321cf5a6b360f72de5f5a",
"semantic_hash": "0d3865b4525321cf5a6b360f72de5f5a"
},
"frontend/src/app/router.tsx": {
"mtime": 1789357305.2954476,
"seen": 1789377303.0692487,
"ast_hash": "0366437498b1933d212cd103252a0b11",
"semantic_hash": "0366437498b1933d212cd103252a0b11"
},
"frontend/src/components/charts/ColumnChart.tsx": {
"mtime": 1789127463.8093653,
"seen": 1789377303.0692496,
"ast_hash": "3f2aeaca948431b2fd4cd86bedaa9ede",
"semantic_hash": "3f2aeaca948431b2fd4cd86bedaa9ede"
},
"frontend/src/components/charts/HorizontalBars.tsx": {
"mtime": 1789127423.3778224,
"seen": 1789377303.06925,
"ast_hash": "e341240d131f12fe680424010279ddd3",
"semantic_hash": "e341240d131f12fe680424010279ddd3"
},
"frontend/src/components/charts/PlanFactBars.tsx": {
"mtime": 1789127423.3794281,
"seen": 1789377303.069251,
"ast_hash": "f581486baa48447ec4c573acef3190ef",
"semantic_hash": "f581486baa48447ec4c573acef3190ef"
},
"frontend/src/components/layout/AppLayout.tsx": {
"mtime": 1789127423.3794281,
"seen": 1789377303.0692518,
"ast_hash": "ae7a94355ca990e4efc91664db0d86c2",
"semantic_hash": "ae7a94355ca990e4efc91664db0d86c2"
},
"frontend/src/components/layout/LangSwitcher.tsx": {
"mtime": 1789127423.3794281,
"seen": 1789377303.069253,
"ast_hash": "5be78b1684339d7ea82677fe1828162c",
"semantic_hash": "5be78b1684339d7ea82677fe1828162c"
},
"frontend/src/components/ui/Badge.tsx": {
"mtime": 1789127112.4896402,
"seen": 1789377303.0692537,
"ast_hash": "f1a5dd94fafcaa4ab6edeb2221ffdd18",
"semantic_hash": "f1a5dd94fafcaa4ab6edeb2221ffdd18"
},
"frontend/src/components/ui/Button.tsx": {
"mtime": 1789127112.4896402,
"seen": 1789377303.0692544,
"ast_hash": "ec61aded7bd1c0bc626b9f85243e6d24",
"semantic_hash": "ec61aded7bd1c0bc626b9f85243e6d24"
},
"frontend/src/components/ui/ConfirmDialog.tsx": {
"mtime": 1789127142.0840394,
"seen": 1789377303.0692554,
"ast_hash": "45b3ad6925a6e69045a59593882c7ffc",
"semantic_hash": "45b3ad6925a6e69045a59593882c7ffc"
},
"frontend/src/components/ui/EmptyState.tsx": {
"mtime": 1789127142.0700393,
"seen": 1789377303.0692563,
"ast_hash": "853f2c5b079b2cfa9e4f12363cefba3a",
"semantic_hash": "853f2c5b079b2cfa9e4f12363cefba3a"
},
"frontend/src/components/ui/ErrorState.tsx": {
"mtime": 1789127142.067039,
"seen": 1789377303.069257,
"ast_hash": "6827eb04b914e8c675e842fea74b5ba5",
"semantic_hash": "6827eb04b914e8c675e842fea74b5ba5"
},
"frontend/src/components/ui/Modal.tsx": {
"mtime": 1789127142.054039,
"seen": 1789377303.069259,
"ast_hash": "41b09f3998ca6f6d7ad415cceff4b9ea",
"semantic_hash": "41b09f3998ca6f6d7ad415cceff4b9ea"
},
"frontend/src/components/ui/MonthSwitcher.tsx": {
"mtime": 1789127142.0860393,
"seen": 1789377303.06926,
"ast_hash": "ddb486640fa05b742fa71167949a2aca",
"semantic_hash": "ddb486640fa05b742fa71167949a2aca"
},
"frontend/src/components/ui/PageHeader.tsx": {
"mtime": 1789127142.0810394,
"seen": 1789377303.0692606,
"ast_hash": "332c481c9030b26221b2b080482973a2",
"semantic_hash": "332c481c9030b26221b2b080482973a2"
},
"frontend/src/components/ui/QueryGate.tsx": {
"mtime": 1789128567.5977647,
"seen": 1789377303.0692613,
"ast_hash": "ee535a82d89a1d2bc30c5ec4907817c1",
"semantic_hash": "ee535a82d89a1d2bc30c5ec4907817c1"
},
"frontend/src/components/ui/Spinner.tsx": {
"mtime": 1789127142.061039,
"seen": 1789377303.0692623,
"ast_hash": "40c69d38fa5a96969026c1486ac981a5",
"semantic_hash": "40c69d38fa5a96969026c1486ac981a5"
},
"frontend/src/components/ui/Toaster.tsx": {
"mtime": 1789127142.0780392,
"seen": 1789377303.0692632,
"ast_hash": "d76eafaf1a72d8435cbce2fd9d2f8764",
"semantic_hash": "d76eafaf1a72d8435cbce2fd9d2f8764"
},
"frontend/src/components/ui/fields.tsx": {
"mtime": 1789128567.5907645,
"seen": 1789377303.069264,
"ast_hash": "5e1b157a5cd5b484582ba959049443aa",
"semantic_hash": "5e1b157a5cd5b484582ba959049443aa"
},
"frontend/src/hooks/useAccounts.ts": {
"mtime": 1789127463.8123653,
"seen": 1789377303.0692647,
"ast_hash": "5cbd401bea752132870ca35a0b2cf29f",
"semantic_hash": "5cbd401bea752132870ca35a0b2cf29f"
},
"frontend/src/hooks/useAnalytics.ts": {
"mtime": 1789127476.179531,
"seen": 1789377303.0692656,
"ast_hash": "2a0cad57cdfc7456c967b3fd122e6017",
"semantic_hash": "2a0cad57cdfc7456c967b3fd122e6017"
},
"frontend/src/hooks/useAuth.ts": {
"mtime": 1789357272.302138,
"seen": 1789377303.0692663,
"ast_hash": "40f9f47b108ec0b484f9564bd1d319cb",
"semantic_hash": "40f9f47b108ec0b484f9564bd1d319cb"
},
"frontend/src/hooks/useBudget.ts": {
"mtime": 1789127463.8123653,
"seen": 1789377303.0692673,
"ast_hash": "06c203b89322dca6cf80931f3558b0dc",
"semantic_hash": "06c203b89322dca6cf80931f3558b0dc"
},
"frontend/src/hooks/useImports.ts": {
"mtime": 1789127476.176531,
"seen": 1789377303.0692682,
"ast_hash": "d23f116da79b15438601751b45519268",
"semantic_hash": "d23f116da79b15438601751b45519268"
},
"frontend/src/hooks/useOperations.ts": {
"mtime": 1789127463.8123653,
"seen": 1789377303.069269,
"ast_hash": "3cac9924554a5465276a2c4431121866",
"semantic_hash": "3cac9924554a5465276a2c4431121866"
},
"frontend/src/hooks/useSpaceLifecycle.ts": {
"mtime": 1789356647.3201435,
"seen": 1789377303.0692697,
"ast_hash": "87c19951711d1798821f227eac36d609",
"semantic_hash": "87c19951711d1798821f227eac36d609"
},
"frontend/src/i18n/index.ts": {
"mtime": 1789128261.8073747,
"seen": 1789377303.0692706,
"ast_hash": "243512f6e095deeaa29393b7c831777f",
"semantic_hash": "243512f6e095deeaa29393b7c831777f"
},
"frontend/src/i18n/locales/en.json": {
"mtime": 1789357272.302995,
"seen": 1789377303.0692713,
"ast_hash": "2ce7444db1936d9c5fcca10b901c4afc",
"semantic_hash": "2ce7444db1936d9c5fcca10b901c4afc"
},
"frontend/src/i18n/locales/ru.json": {
"mtime": 1789357272.302995,
"seen": 1789377303.0692723,
"ast_hash": "ae00c080e0b9a10b6004c4094bcdb1ea",
"semantic_hash": "ae00c080e0b9a10b6004c4094bcdb1ea"
},
"frontend/src/main.tsx": {
"mtime": 1789127518.1660943,
"seen": 1789377303.069273,
"ast_hash": "9219312484aae5efecb6534357f9aadf",
"semantic_hash": "9219312484aae5efecb6534357f9aadf"
},
"frontend/src/pages/AcceptInvitePage.tsx": {
"mtime": 1789357272.302138,
"seen": 1789377303.069274,
"ast_hash": "11475c9e3ab1b5f50cbd8e5e63cca4cb",
"semantic_hash": "11475c9e3ab1b5f50cbd8e5e63cca4cb"
},
"frontend/src/pages/AccountsPage.tsx": {
"mtime": 1789127567.9147615,
"seen": 1789377303.0692747,
"ast_hash": "5da5d66e5eb40222e4ceb1f75ca951bd",
"semantic_hash": "5da5d66e5eb40222e4ceb1f75ca951bd"
},
"frontend/src/pages/AnalyticsPage.tsx": {
"mtime": 1789127904.7983484,
"seen": 1789377303.0692756,
"ast_hash": "4a83070566fde66ff6423b1064cde4e8",
"semantic_hash": "4a83070566fde66ff6423b1064cde4e8"
},
"frontend/src/pages/BudgetPage.tsx": {
"mtime": 1789127621.1764753,
"seen": 1789377303.0692763,
"ast_hash": "c1421d4088b0f174dc7ecb082b2a257d",
"semantic_hash": "c1421d4088b0f174dc7ecb082b2a257d"
},
"frontend/src/pages/ImportDetailPage.tsx": {
"mtime": 1789127866.2017796,
"seen": 1789377303.0692773,
"ast_hash": "52adf2e149d39f2b2d77f96640c72b5e",
"semantic_hash": "52adf2e149d39f2b2d77f96640c72b5e"
},
"frontend/src/pages/ImportsPage.tsx": {
"mtime": 1789127830.2842758,
"seen": 1789377303.069278,
"ast_hash": "721376253dda864ff81a5025691947e0",
"semantic_hash": "721376253dda864ff81a5025691947e0"
},
"frontend/src/pages/LoginPage.tsx": {
"mtime": 1789357272.302138,
"seen": 1789377303.069279,
"ast_hash": "6cb36d7a770d36c3dddf1dbb90ebc545",
"semantic_hash": "6cb36d7a770d36c3dddf1dbb90ebc545"
},
"frontend/src/pages/NotFoundPage.tsx": {
"mtime": 1789127518.2310953,
"seen": 1789377303.0692797,
"ast_hash": "53e014a77ff3070820a83458af08a5fd",
"semantic_hash": "53e014a77ff3070820a83458af08a5fd"
},
"frontend/src/pages/OperationsPage.tsx": {
"mtime": 1789128734.6790774,
"seen": 1789377303.0692806,
"ast_hash": "f4ae42adff88554aef7c522ce79daca3",
"semantic_hash": "f4ae42adff88554aef7c522ce79daca3"
},
"frontend/src/pages/ResetPasswordPage.tsx": {
"mtime": 1789357305.2944477,
"seen": 1789377303.0692816,
"ast_hash": "4ecaf25ff754290936fd87db84f19414",
"semantic_hash": "4ecaf25ff754290936fd87db84f19414"
},
"frontend/src/pages/ReviewQueuePage.tsx": {
"mtime": 1789127802.1288989,
"seen": 1789377303.0692823,
"ast_hash": "b3f57d73ef457c9b2427d74603972213",
"semantic_hash": "b3f57d73ef457c9b2427d74603972213"
},
"frontend/src/pages/SettingsPage.tsx": {
"mtime": 1789128572.7868366,
"seen": 1789377303.069283,
"ast_hash": "6a35c2799ba84fdbc1367d768907e8fa",
"semantic_hash": "6a35c2799ba84fdbc1367d768907e8fa"
},
"frontend/src/test/accounts-page.test.tsx": {
"mtime": 1789128626.8475864,
"seen": 1789377303.0692837,
"ast_hash": "d17270fb5ca22286d3d62d7e2679fbcb",
"semantic_hash": "d17270fb5ca22286d3d62d7e2679fbcb"
},
"frontend/src/test/budget-plan.test.tsx": {
"mtime": 1789128448.8001103,
"seen": 1789377303.0692847,
"ast_hash": "b2abf4ff7b3ea5b6f730cddd021b4127",
"semantic_hash": "b2abf4ff7b3ea5b6f730cddd021b4127"
},
"frontend/src/test/chart-accessible-table.test.tsx": {
"mtime": 1789128448.8031104,
"seen": 1789377303.0692854,
"ast_hash": "e8f2187db8969b2fb4975775e3dbdcb9",
"semantic_hash": "e8f2187db8969b2fb4975775e3dbdcb9"
},
"frontend/src/test/helpers.tsx": {
"mtime": 1789128456.9108279,
"seen": 1789377303.0692863,
"ast_hash": "041d8de84ce39044e32d9a4e45f176a0",
"semantic_hash": "041d8de84ce39044e32d9a4e45f176a0"
},
"frontend/src/test/i18n-parity.test.ts": {
"mtime": 1789128572.7505064,
"seen": 1789377303.069287,
"ast_hash": "cc870c8a667b6982c9fabd654468a46f",
"semantic_hash": "cc870c8a667b6982c9fabd654468a46f"
},
"frontend/src/test/import-polling.test.tsx": {
"mtime": 1789128692.423248,
"seen": 1789377303.0692883,
"ast_hash": "2b83dee550ca31b0d5980c8ce3235fd0",
"semantic_hash": "2b83dee550ca31b0d5980c8ce3235fd0"
},
"frontend/src/test/login-page.test.tsx": {
"mtime": 1789128286.0698264,
"seen": 1789377303.069289,
"ast_hash": "52ebeeb4fb554b4c830113cebcf9f530",
"semantic_hash": "52ebeeb4fb554b4c830113cebcf9f530"
},
"frontend/src/test/money.test.ts": {
"mtime": 1789128261.8154838,
"seen": 1789377303.0692897,
"ast_hash": "8faf4d33972c1583baa98fcd8160dbd0",
"semantic_hash": "8faf4d33972c1583baa98fcd8160dbd0"
},
"frontend/src/test/query-keys.test.ts": {
"mtime": 1789128626.8519824,
"seen": 1789377303.0692904,
"ast_hash": "554aa60181736710caf28df6f9b62a66",
"semantic_hash": "554aa60181736710caf28df6f9b62a66"
},
"frontend/src/test/setup.ts": {
"mtime": 1789128261.8154838,
"seen": 1789377303.0692916,
"ast_hash": "5b0ef0fbf28fe880eac77ca470cdb72f",
"semantic_hash": "5b0ef0fbf28fe880eac77ca470cdb72f"
},
"frontend/src/vite-env.d.ts": {
"mtime": 1789127028.213317,
"seen": 1789377303.0692923,
"ast_hash": "0352474ba2918efe13895edbc3780d94",
"semantic_hash": "0352474ba2918efe13895edbc3780d94"
},
"frontend/tsconfig.json": {
"mtime": 1789126877.2434592,
"seen": 1789377303.069293,
"ast_hash": "c57160ea7b6323de41adceed69142721",
"semantic_hash": "c57160ea7b6323de41adceed69142721"
},
"frontend/vite.config.ts": {
"mtime": 1789126877.239459,
"seen": 1789377303.0692937,
"ast_hash": "b99eeb04d213e3680abe9c3854521e80",
"semantic_hash": "b99eeb04d213e3680abe9c3854521e80"
},
"infra/postgres/init-app-role.sh": {
"mtime": 1789129887.1068308,
"seen": 1789377303.069295,
"ast_hash": "4c2b103cde06bc6df773f005c00b73d6",
"semantic_hash": "4c2b103cde06bc6df773f005c00b73d6"
},
".kilo/plans/1789034192214-financial-tracker-mvp.md": {
"mtime": 1789099088.960013,
"seen": 1789377303.0692966,
"ast_hash": "a2f21668b5ef4c0005316ef3e54b6a28",
"semantic_hash": "a2f21668b5ef4c0005316ef3e54b6a28"
},
"CONTEXT.md": {
"mtime": 1789126288.30262,
"seen": 1789377303.0692973,
"ast_hash": "c8211b75ee8dfba83e37f06ff219d2b1",
"semantic_hash": "c8211b75ee8dfba83e37f06ff219d2b1"
},
"README.md": {
"mtime": 1789358273.3585796,
"seen": 1789377303.0692983,
"ast_hash": "79eb6002a05286e6655e6fe3904f5b29",
"semantic_hash": "79eb6002a05286e6655e6fe3904f5b29"
},
"docs/adr/0001-financial-space-ownership-and-rls.md": {
"mtime": 1789126779.7591345,
"seen": 1789377303.0692992,
"ast_hash": "8fbddf58406caccd2d5a5b0e5f9ffab4",
"semantic_hash": "8fbddf58406caccd2d5a5b0e5f9ffab4"
},
"docs/adr/0002-journal-and-budget-reserves.md": {
"mtime": 1789126779.7821348,
"seen": 1789377303.0693,
"ast_hash": "6ca9f9b8f91733ce37e15f339a656726",
"semantic_hash": "6ca9f9b8f91733ce37e15f339a656726"
},
"docs/adr/0003-versioned-statement-imports.md": {
"mtime": 1789732137.8203447,
"seen": 1789735433.1582236,
"ast_hash": "e05b45d637e097a3898bc1b2f2160fe7",
"semantic_hash": ""
},
"docs/adr/0004-provider-neutral-encrypted-data-boundary.md": {
"mtime": 1789126790.5422814,
"seen": 1789377303.0693016,
"ast_hash": "460e37c748836b116a4ea820768a8007",
"semantic_hash": "460e37c748836b116a4ea820768a8007"
},
"docs/operations/backup-and-restore.md": {
"mtime": 1789126817.735651,
"seen": 1789377303.0693026,
"ast_hash": "188110b691922b9a23f4efa2ef4627e2",
"semantic_hash": "188110b691922b9a23f4efa2ef4627e2"
},
"docs/operations/beta-release-runbook.md": {
"mtime": 1789126842.5229878,
"seen": 1789377303.0693033,
"ast_hash": "00dada41b2d3425da89bcd277acce41e",
"semantic_hash": "00dada41b2d3425da89bcd277acce41e"
},
"docs/operations/deletion-ledger.md": {
"mtime": 1789126842.4969876,
"seen": 1789377303.0693042,
"ast_hash": "1aff4ccf6a2dbcf86ed72fd34bb69c6f",
"semantic_hash": "1aff4ccf6a2dbcf86ed72fd34bb69c6f"
},
"docs/parsers/private-fixture-gate.md": {
"mtime": 1789734164.3005245,
"seen": 1789735433.1582274,
"ast_hash": "02086c7b911cf06d3d170ac04be7592e",
"semantic_hash": ""
},
"frontend/README.md": {
"mtime": 1789128756.9573848,
"seen": 1789377303.069306,
"ast_hash": "3c43f67a81ce2c7b0cd18aeab85ed2c2",
"semantic_hash": "3c43f67a81ce2c7b0cd18aeab85ed2c2"
},
"frontend/index.html": {
"mtime": 1789126886.1155794,
"seen": 1789377303.0693069,
"ast_hash": "42788af442bf05e487937f435141f84b",
"semantic_hash": "42788af442bf05e487937f435141f84b"
},
"infra/README.md": {
"mtime": 1789357215.8087454,
"seen": 1789377303.0693076,
"ast_hash": "0a7bb82cce04631f36f2d561a1665478",
"semantic_hash": "0a7bb82cce04631f36f2d561a1665478"
},
"infra/docker-compose.yml": {
"mtime": 1789400438.2433548,
"seen": 1789735433.1582313,
"ast_hash": "9f04dd1906326ddea306584fe68a2ae4",
"semantic_hash": ""
},
"frontend/public/favicon.svg": {
"mtime": 1789126886.1235795,
"seen": 1789377303.0693095,
"ast_hash": "83637b50c3736f9f0675b7f23ff5a51e",
"semantic_hash": "83637b50c3736f9f0675b7f23ff5a51e"
},
".kilo/kilo.json": {
"mtime": 1789377478.0622306,
"seen": 1789735433.1580946,
"ast_hash": "1839ad09cc2e72bfeb650219265f2e20",
"semantic_hash": ""
},
"backend/alembic/versions/20260918_0002_hold_lifecycle.py": {
"mtime": 1789735291.8675542,
"seen": 1789735433.1581025,
"ast_hash": "2a669c07ecd8d473e703bc823cb26183",
"semantic_hash": ""
},
"backend/tests/test_adapter_contracts.py": {
"mtime": 1789735944.366427,
"seen": 1789736272.9455402,
"ast_hash": "c3f63850c85b7c5262e66370ab7ab8a9",
"semantic_hash": ""
},
"backend/tests/test_import_holds.py": {
"mtime": 1789736201.2172022,
"seen": 1789736272.9455423,
"ast_hash": "6f14226a83e65efbaaee6446d4ec4cb6",
"semantic_hash": ""
},
"backend/tests/test_private_fixtures.py": {
"mtime": 1789737822.4871244,
"seen": 1789737916.7915628,
"ast_hash": "f14cd118f1e8d6b5088536d50d2b965a",
"semantic_hash": ""
},
".gitea/workflows/ci.yml": {
"mtime": 1789736949.8123386,
"seen": 1789737916.791613,
"ast_hash": "f0891b841c5e24c761f5067a4ea23637",
"semantic_hash": ""
},
".kilo/plans/1789727434954-gitea-ci-real-statement-imports.md": {
"mtime": 1789729895.9594803,
"seen": 1789735433.1582198,
"ast_hash": "eb38e75994c5fc29d4137adb7e0e37f8",
"semantic_hash": ""
},
"frontend/pnpm-workspace.yaml": {
"mtime": 1789377936.9976747,
"seen": 1789735433.1582298,
"ast_hash": "2ae2370fa527b015cd7bd98dc7ca2a6b",
"semantic_hash": ""
},
".kilo/plugins/graphify.js": {
"mtime": 1789967188.4955833,
"seen": 1789987085.8116593,
"ast_hash": "af79b534b1739d62b450e39c9b50e362",
"semantic_hash": ""
},
"AGENTS.md": {
"mtime": 1789967188.495481,
"seen": 1789987085.8117683,
"ast_hash": "6a47d64fcc136324ca395065a707ef76",
"semantic_hash": ""
}
}
+112 -109
View File
@@ -1,51 +1,51 @@
# Graph Report - finance (2026-09-18)
# Graph Report - finance (2026-09-21)
## Corpus Check
- 149 files · ~63,842 words
- 151 files · ~64,809 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 1267 nodes · 3697 edges · 75 communities (54 shown, 15 thin omitted)
- Extraction: 98% EXTRACTED · 2% INFERRED · 0% AMBIGUOUS · INFERRED: 70 edges (avg confidence: 0.93)
- 1277 nodes · 3734 edges · 79 communities (54 shown, 18 thin omitted)
- Extraction: 98% EXTRACTED · 2% INFERRED · 0% AMBIGUOUS · INFERRED: 71 edges (avg confidence: 0.93)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `fb91da79`
- Built from commit: `35b9ea83`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
## Community Hubs (Navigation)
- identity/router.py
- identity/service.py
- budgeting/router.py
- Base
- set_space_context
- imports/router.py
- alfa_xlsx.py
- Session
- ledger/service.py
- types.ts
- router.tsx
- ledger/router.py
- AccountsPage.tsx
- main.py
- compilerOptions
- AnalyticsPage.tsx
- analytics/router.py
- exports/router.py
- devDependencies
- test_import_holds.py
- Session
- dependencies
- ImportDetailPage.tsx
- OperationsPage.tsx
- SettingsPage.tsx
- useOperations.ts
- Toaster.tsx
- ozon_pdf.py
- adapters/base.py
- BudgetPage.tsx
- client.ts
- fields.tsx
- AccountsPage.tsx
- Docker Compose Development Stack
- Financial Space
- scripts
- dependencies.py
- exports/service.py
- General Pool
- TestBaseContract
- test_adapter_contracts.py
- Per-File Envelope Encryption
- Operation
- Versioned Statement Imports
@@ -53,7 +53,7 @@
- Closed Beta Launch Gates
- package.json
- Financial Tracker MVP
- test_adapter_contracts.py
- TestRocketbankDebitContract
- generate-api.mjs
- schema.d.ts
- test_api.py
@@ -65,14 +65,14 @@
- graphify
- @types/react-dom
- typescript-eslint
- SourceRow
- OzonDebitPdfAdapter
- vitest
- Favicon SVG (ledger lines icon)
- init-app-role.sh
- Payee
- finance-backend
- LocalDirectoryBlobStore
- account_fingerprint
- TestOzonDebitContract
- Gitea CI and real statement imports
- 20260918_0002_hold_lifecycle.py
- eslint-plugin-react-hooks
@@ -80,22 +80,25 @@
- @testing-library/dom
- @testing-library/react
- vite
- spaces/router.py
- identity/service.py
- get_settings
- ColumnChart.tsx
- conftest.py
- imports/service.py
- BlobStore
- AppError
- celery_app.py
- AGENTS.md
## God Nodes (most connected - your core abstractions)
1. `Session` - 140 edges
2. `AppError` - 53 edges
1. `Session` - 146 edges
2. `AppError` - 54 edges
3. `Base` - 47 edges
4. `PkMixin` - 39 edges
5. `get_settings()` - 38 edges
6. `TimestampMixin` - 38 edges
7. `conflict()` - 35 edges
8. `record()` - 34 edges
9. `UnsupportedStatementLayout` - 30 edges
10. `bad_request()` - 29 edges
9. `StatementDocument` - 32 edges
10. `UnsupportedStatementLayout` - 30 edges
## Surprising Connections (you probably didn't know these)
- `Typed Same-Origin API Client` --conceptually_related_to--> `Same-Origin Reverse Proxy` [INFERRED]
@@ -117,107 +120,107 @@
- **Financial Space Tenancy Boundary** — context_financial_space, context_membership, docs_adr_0001_financial_space_ownership_and_rls_defense_in_depth_authorization, docs_adr_0001_financial_space_ownership_and_rls_transaction_local_rls, docs_adr_0001_financial_space_ownership_and_rls_cross_space_integrity [EXTRACTED 1.00]
- **Safe Statement Import Flow** — context_statement_import, context_parse_run, context_source_record, docs_adr_0003_versioned_statement_imports_immutable_statement_document, docs_adr_0003_versioned_statement_imports_conservative_deduplication, docs_adr_0003_versioned_statement_imports_atomic_confirm_revert [EXTRACTED 1.00]
## Communities (75 total, 15 thin omitted)
## Communities (79 total, 18 thin omitted)
### Community 0 - "identity/router.py"
Cohesion: 0.20
Nodes (22): accept_invite(), AcceptInviteBody, confirm_password_reset(), create_invite(), InviteBody, login(), LoginBody, logout() (+14 more)
### Community 0 - "identity/service.py"
Cohesion: 0.06
Nodes (83): current_context(), current_session(), alias, Context, CurrentSession, Db, Header, User (+75 more)
### Community 1 - "budgeting/router.py"
Cohesion: 0.20
Nodes (34): AssignmentItem, assignments(), AssignmentsBody, categories(), CategoryBody, CategoryFundingBody, close(), close_frontend() (+26 more)
Cohesion: 0.14
Nodes (43): balances(), cash_flow(), category_spend(), plan_fact(), Context, date, Db, get (+35 more)
### Community 2 - "Base"
Cohesion: 0.06
Nodes (87): downgrade(), upgrade(), AuditEvent, Append-only domain/security event. Never stores raw financial payloads., ExpectedIncome, ExpectedIncomeMatch, FundingMovement, PeriodClosure (+79 more)
Cohesion: 0.07
Nodes (76): downgrade(), upgrade(), AuditEvent, Append-only domain/security event. Never stores raw financial payloads., ExpectedIncome, ExpectedIncomeMatch, FundingMovement, PeriodClosure (+68 more)
### Community 3 - "set_space_context"
Cohesion: 0.07
Nodes (43): get_db(), get_engine(), get_session_factory(), Any, sessionmaker, Transaction-local RLS context. No-op on SQLite (tests); fail-closed on…, Transactional scope for workers/endpoints that own the transaction lifecycle., session_scope() (+35 more)
Cohesion: 0.22
Nodes (14): get_db(), get_session_factory(), Any, sessionmaker, Transaction-local RLS context. No-op on SQLite (tests); fail-closed on…, Transactional scope for workers/endpoints that own the transaction lifecycle., session_scope(), set_space_context() (+6 more)
### Community 4 - "imports/router.py"
Cohesion: 0.15
Cohesion: 0.16
Nodes (33): confirm(), ConfirmBody, decide_row(), DecisionBody, delete_source(), detail(), HoldResolveBody, imports() (+25 more)
### Community 5 - "alfa_xlsx.py"
Cohesion: 0.14
Nodes (29): AlfaCreditXlsxAdapter, AlfaDebitXlsxAdapter, _AlfaHeaderMeta, AlfaXlsxAdapterBase, _cell_str(), _debit_find_header(), _debit_header_metadata(), _debit_header_value() (+21 more)
Cohesion: 0.17
Nodes (24): AlfaDebitXlsxAdapter, _AlfaHeaderMeta, AlfaXlsxAdapterBase, _cell_str(), _debit_find_header(), _debit_header_metadata(), _debit_header_value(), _debit_norm() (+16 more)
### Community 6 - "Session"
Cohesion: 0.07
Nodes (112): balances(), plan_fact(), Any, record(), BudgetPeriod, Calendar month budget period in the space timezone (month granularity)., add_expected_income(), archive_category() (+104 more)
### Community 6 - "ledger/service.py"
Cohesion: 0.11
Nodes (62): Any, record(), get_income_source(), bad_request(), conflict(), not_found(), Any, _apply_hold_matches() (+54 more)
### Community 7 - "types.ts"
Cohesion: 0.05
Nodes (53): api, AcceptInvitePayload, Account, AccountStatus, BalanceItem, BalancesReport, BudgetBlocker, BudgetCategory (+45 more)
Nodes (52): api, AcceptInvitePayload, Account, AccountStatus, BalanceItem, BalancesReport, BudgetBlocker, BudgetCategory (+44 more)
### Community 8 - "router.tsx"
Cohesion: 0.09
Nodes (35): AcceptInvitePage, AccountsPage, BudgetPage, ImportDetailPage, ImportsPage, LoginPage, OperationsPage, RequireAuth() (+27 more)
Cohesion: 0.11
Nodes (35): AcceptInvitePage, LoginPage, RequireAuth(), ResetPasswordPage, SettingsPage, AppLayout(), NAV_ITEMS, LangSwitcher() (+27 more)
### Community 9 - "ledger/router.py"
Cohesion: 0.15
Nodes (31): account_json(), AccountBody, accounts(), archive_account(), create_account(), create_operation(), delete_account(), operation_json() (+23 more)
### Community 10 - "AccountsPage.tsx"
### Community 10 - "main.py"
Cohesion: 0.21
Nodes (13): AccountType, CreateAccountPayload, ConfirmDialog(), ConfirmDialogProps, Modal(), ModalProps, useAccounts(), useArchiveAccount() (+5 more)
Nodes (14): get_engine(), app_error_handler(), health(), healthz(), get, Request, readiness(), security_headers() (+6 more)
### Community 11 - "compilerOptions"
Cohesion: 0.07
Nodes (28): compilerOptions, baseUrl, esModuleInterop, forceConsistentCasingInFileNames, isolatedModules, jsx, lib, module (+20 more)
### Community 12 - "AnalyticsPage.tsx"
Cohesion: 0.14
Nodes (15): AnalyticsPage, NotFoundPage, PlanFactBars(), PlanFactBarsProps, PlanFactRow, MonthSwitcher(), MonthSwitcherProps, PageHeader() (+7 more)
Cohesion: 0.21
Nodes (12): AnalyticsPage, PlanFactBars(), PlanFactBarsProps, PlanFactRow, MonthSwitcher(), MonthSwitcherProps, useAnalyticsBalances(), useAnalyticsCashFlow() (+4 more)
### Community 13 - "analytics/router.py"
### Community 13 - "exports/router.py"
Cohesion: 0.27
Nodes (12): balances(), cash_flow(), category_spend(), plan_fact(), Context, date, Db, get (+4 more)
Nodes (10): download(), export_status(), Context, Csrf, Db, get, post, Response (+2 more)
### Community 14 - "devDependencies"
Cohesion: 0.11
Nodes (19): eslint, @eslint/js, eslint-plugin-jsx-a11y, devDependencies, eslint, @eslint/js, eslint-plugin-jsx-a11y, globals (+11 more)
### Community 15 - "test_import_holds.py"
Cohesion: 0.13
Nodes (43): AppError, Domain error rendered as a machine-readable JSON error payload., normalize_description(), date, record_fingerprint(), add_record(), _draft(), hold_document() (+35 more)
### Community 15 - "Session"
Cohesion: 0.09
Nodes (87): balances(), cash_flow(), category_spend(), plan_fact(), date, UUID, BudgetPeriod, Calendar month budget period in the space timezone (month granularity). (+79 more)
### Community 16 - "dependencies"
Cohesion: 0.11
Nodes (19): dependencies, @hookform/resolvers, i18next, react, react-dom, react-hook-form, react-i18next, react-router-dom (+11 more)
### Community 17 - "ImportDetailPage.tsx"
Cohesion: 0.33
Nodes (13): useToast(), useConfirmImport(), useImport(), useImportErrorToaster(), useReparseImport(), useRevertImport(), useSaveRowDecision(), useUploadImport() (+5 more)
Cohesion: 0.27
Nodes (15): RowDecision, ImportDetailPage, useToast(), useConfirmImport(), useImport(), useImportErrorToaster(), useReparseImport(), useRevertImport() (+7 more)
### Community 18 - "OperationsPage.tsx"
Cohesion: 0.14
Nodes (27): CreateOperationPayload, Badge(), BadgeTone, EmptyState(), EmptyStateProps, QueryGate(), QueryGateProps, useActiveAccounts() (+19 more)
Cohesion: 0.11
Nodes (29): Operation, ImportsPage, NotFoundPage, OperationsPage, ReviewQueuePage, Badge(), BadgeTone, EmptyState() (+21 more)
### Community 19 - "SettingsPage.tsx"
### Community 19 - "useOperations.ts"
Cohesion: 0.16
Nodes (16): invalidationRoots, queryKeys, SESSION_ROOT, sessionInvalidationRoot, SPACE_ROOT, spaceInvalidationRoot, OperationFilters, useCancelDeletion() (+8 more)
Nodes (14): invalidationRoots, queryKeys, SESSION_ROOT, sessionInvalidationRoot, SPACE_ROOT, spaceInvalidationRoot, OperationFilters, useCategorizeOperation() (+6 more)
### Community 20 - "Toaster.tsx"
Cohesion: 0.12
Nodes (15): Locale, App(), queryClient, AppRoutes(), ToastApi, ToastContext, ToastItem, ToastKind (+7 more)
### Community 21 - "ozon_pdf.py"
### Community 21 - "adapters/base.py"
Cohesion: 0.14
Nodes (13): Protocol, StatementAdapter, StatementDraft, _first_page_text(), _ozon_cell(), _ozon_normalize(), OzonCreditPdfAdapter, OzonDebitPdfAdapter (+5 more)
Nodes (18): account_fingerprint(), amount_to_kopecks(), _mask_digits(), money_to_kopecks(), Parse statement money strings like '-1 234,56 RUR', '+ 500.00 ₽', '12 345'.…, SourceRow, StatementDraft, _first_page_text() (+10 more)
### Community 22 - "BudgetPage.tsx"
Cohesion: 0.36
Nodes (12): useBudgetCategories(), useBudgetErrorToaster(), useBudgetSummary(), useCloseMonth(), useCopyPreviousMonth(), useFundCategory(), usePeriodMutation(), useReopenMonth() (+4 more)
Cohesion: 0.21
Nodes (17): BudgetPage, ConfirmDialog(), ConfirmDialogProps, Modal(), ModalProps, useBudgetCategories(), useBudgetErrorToaster(), useBudgetSummary() (+9 more)
### Community 23 - "client.ts"
Cohesion: 0.23
Nodes (10): apiClient, ApiError, buildUrl(), ErrorEnvelope, parseErrorEnvelope(), QueryValue, readCsrfToken(), request() (+2 more)
### Community 24 - "fields.tsx"
Cohesion: 0.21
Nodes (11): FieldChrome, InputProps, MoneyField(), MoneyFieldProps, SelectField(), SelectFieldProps, SelectProps, TextareaField() (+3 more)
### Community 24 - "AccountsPage.tsx"
Cohesion: 0.13
Nodes (21): AccountType, CreateAccountPayload, AccountsPage, FieldChrome, InputProps, MoneyField(), MoneyFieldProps, SelectField() (+13 more)
### Community 25 - "Docker Compose Development Stack"
Cohesion: 0.29
@@ -231,17 +234,17 @@ Nodes (12): Credit Reserve, Financial Account, Financial Space, Membership, User
Cohesion: 0.22
Nodes (9): scripts, api:generate, build, dev, lint, preview, test, test:watch (+1 more)
### Community 28 - "dependencies.py"
Cohesion: 0.15
Nodes (21): current_context(), current_session(), alias, Context, CurrentSession, Db, Header, User (+13 more)
### Community 28 - "exports/service.py"
Cohesion: 0.40
Nodes (9): build_export(), consume_export(), _csv_bytes(), get_export(), _json_rows(), datetime, UUID, request_export() (+1 more)
### Community 29 - "General Pool"
Cohesion: 0.29
Nodes (8): Budget Period, Funding, General Pool, Monthly Category, Plan Assignment, Savings Envelope, Immutable Closure Snapshots, Plan and Funding Separation
### Community 30 - "TestBaseContract"
Cohesion: 0.17
Nodes (5): detect_adapter(), make_draft(), make_source_row(), TestBaseContract, Workbook
### Community 30 - "test_adapter_contracts.py"
Cohesion: 0.13
Nodes (10): AlfaCreditXlsxAdapter, Protocol, StatementAdapter, detect_adapter(), OzonCreditPdfAdapter, make_draft(), make_source_row(), Sanitized synthetic contract tests for the evidenced statement adapters. These… (+2 more)
### Community 31 - "Per-File Envelope Encryption"
Cohesion: 0.29
@@ -271,9 +274,9 @@ Nodes (4): name, private, type, version
Cohesion: 0.18
Nodes (11): Audited Break-Glass Access, Cross-Space Referential Integrity, Defense-in-Depth Authorization, Financial Space Ownership, Transaction-Local RLS Context, Budget Invariants, Critical User Path, Financial Tracker MVP (+3 more)
### Community 38 - "test_adapter_contracts.py"
Cohesion: 0.11
Nodes (9): minimal_pdf(), _QuotaSettings, Sanitized synthetic contract tests for the evidenced statement adapters. These…, Build a small valid single-font PDF with empty or ASCII-only pages., same_page_rocket_lines(), synthetic_rocket_lines(), TestMoneyParsing, TestRocketbankDebitContract (+1 more)
### Community 38 - "TestRocketbankDebitContract"
Cohesion: 0.24
Nodes (3): same_page_rocket_lines(), synthetic_rocket_lines(), TestRocketbankDebitContract
### Community 39 - "generate-api.mjs"
Cohesion: 0.40
@@ -288,28 +291,28 @@ Cohesion: 0.67
Nodes (3): TestClient, test_invite_login_and_authenticated_space(), test_openapi_contains_critical_paths()
### Community 44 - "test_private_fixtures.py"
Cohesion: 0.12
Nodes (22): magic_kind(), _draft_json(), Any, Reconcile parsed rows against declared bank controls with stable diagnostics.…, _reconciliation_summary(), _check(), _fail(), _load_entries() (+14 more)
Cohesion: 0.13
Nodes (20): Any, Reconcile parsed rows against declared bank controls with stable diagnostics.…, _reconciliation_summary(), _check(), _fail(), _load_entries(), parsed(), fixture (+12 more)
### Community 45 - "TestAlfaDebitContract"
Cohesion: 0.21
Cohesion: 0.19
Nodes (3): alfa_workbook_bytes(), synthetic_alfa_rows(), TestAlfaDebitContract
### Community 47 - "graphify"
Cohesion: 0.17
Nodes (11): command, enabled, timeout, type, mcp, graphify, plugin, $schema (+3 more)
### Community 50 - "SourceRow"
Cohesion: 0.19
Nodes (9): SourceRow, _date_from(), _PendingRecord, date, Rocketbank debit statement contract, evidenced against the real document.…, RocketbankDebitPdfAdapter, open_pdf(), _quota_error() (+1 more)
### Community 50 - "OzonDebitPdfAdapter"
Cohesion: 0.29
Nodes (5): _ozon_cell(), _ozon_normalize(), OzonDebitPdfAdapter, Ozon debit statement contract, evidenced against the real document. A 'Справка…, Pattern
### Community 62 - "LocalDirectoryBlobStore"
Cohesion: 0.39
Nodes (3): LocalDirectoryBlobStore, Path, S3-neutral local store; production swaps in an S3-compatible client.
### Community 63 - "account_fingerprint"
Cohesion: 0.25
Nodes (3): account_fingerprint(), ozon_extracted(), TestOzonDebitContract
### Community 63 - "TestOzonDebitContract"
Cohesion: 0.15
Nodes (6): minimal_pdf(), ozon_extracted(), _QuotaSettings, Build a small valid single-font PDF with empty or ASCII-only pages., TestOzonDebitContract, MonkeyPatch
### Community 64 - "Gitea CI and real statement imports"
Cohesion: 0.29
@@ -319,41 +322,41 @@ Nodes (6): Confirmed decisions, Gitea CI and real statement imports, Goal and co
Cohesion: 0.80
Nodes (4): downgrade(), _has_check_constraint(), _has_column(), upgrade()
### Community 71 - "spaces/router.py"
Cohesion: 0.16
Nodes (19): send_token_email(), cancel_deletion(), current(), deletion_status(), DeletionBody, invite_member(), MemberInviteBody, alias (+11 more)
### Community 72 - "identity/service.py"
Cohesion: 0.28
Nodes (18): unauthorized(), accept_invitation(), authenticate(), create_invitation(), create_password_reset(), create_session(), get_active_session(), normalize_email() (+10 more)
### Community 73 - "get_settings"
Cohesion: 0.16
Nodes (12): get_settings(), Settings, create_celery_app(), encrypt_file(), EncryptedFile, Per-file random DEK; DEK wrapped with the KEK from the secret manager., Crypto-shred hook: deleting wrapped DEK rows in DB plus blobs removes access., shred_dek_material() (+4 more)
### Community 74 - "ColumnChart.tsx"
### Community 71 - "ColumnChart.tsx"
Cohesion: 0.24
Nodes (7): CashFlowColumn, ColumnChart(), ColumnChartProps, BarRow, HorizontalBars(), HorizontalBarsProps, rows
### Community 72 - "conftest.py"
Cohesion: 0.52
Nodes (6): client(), db(), db_factory(), fixture, sessionmaker, TestClient
### Community 73 - "imports/service.py"
Cohesion: 0.13
Nodes (20): get_settings(), Settings, normalize_description(), date, record_fingerprint(), magic_kind(), get_blob_store(), new_object_key() (+12 more)
### Community 75 - "AppError"
Cohesion: 0.43
Nodes (5): AppError, Domain error rendered as a machine-readable JSON error payload., unprocessable(), _quota_error(), Exception
## Knowledge Gaps
- **185 isolated node(s):** `$schema`, `file:///home/k1nq/repos/finance/.kilo/plugins/graphify.js`, `type`, `/home/k1nq/.local/bin/graphify-mcp`, `/home/k1nq/repos/finance/graphify-out/graph.json` (+180 more)
These have ≤1 connection - possible missing edges or undocumented components. (Counts symbols only; 335 node(s) total have ≤1 connection when file, concept and rationale nodes are included.)
- **15 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
- **186 isolated node(s):** `$schema`, `file:///home/k1nq/repos/finance/.kilo/plugins/graphify.js`, `type`, `/home/k1nq/.local/bin/graphify-mcp`, `/home/k1nq/repos/finance/graphify-out/graph.json` (+181 more)
These have ≤1 connection - possible missing edges or undocumented components. (Counts symbols only; 339 node(s) total have ≤1 connection when file, concept and rationale nodes are included.)
- **18 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `AppError` connect `test_import_holds.py` to `identity/router.py`, `Base`, `set_space_context`, `imports/router.py`, `alfa_xlsx.py`, `Session`, `test_adapter_contracts.py`, `identity/service.py`, `get_settings`, `SourceRow`, `dependencies.py`, `account_fingerprint`?**
_High betweenness centrality (0.078) - this node is a cross-community bridge._
- **Why does `Session` connect `Session` to `Base`, `set_space_context`, `identity/service.py`, `analytics/router.py`, `test_import_holds.py`, `dependencies.py`?**
_High betweenness centrality (0.074) - this node is a cross-community bridge._
- **Why does `bad_request()` connect `Session` to `budgeting/router.py`, `spaces/router.py`, `identity/service.py`, `ledger/router.py`, `test_import_holds.py`?**
_High betweenness centrality (0.025) - this node is a cross-community bridge._
- **Why does `Session` connect `Session` to `identity/service.py`, `Base`, `set_space_context`, `ledger/service.py`, `conftest.py`, `imports/service.py`, `exports/service.py`?**
_High betweenness centrality (0.085) - this node is a cross-community bridge._
- **Why does `AppError` connect `AppError` to `identity/service.py`, `Base`, `imports/router.py`, `alfa_xlsx.py`, `ledger/service.py`, `TestRocketbankDebitContract`, `imports/service.py`, `main.py`, `exports/router.py`, `Session`, `adapters/base.py`, `test_adapter_contracts.py`, `TestOzonDebitContract`?**
_High betweenness centrality (0.069) - this node is a cross-community bridge._
- **Why does `bad_request()` connect `ledger/service.py` to `identity/service.py`, `budgeting/router.py`, `imports/service.py`, `ledger/router.py`, `AppError`, `Session`?**
_High betweenness centrality (0.017) - this node is a cross-community bridge._
- **Are the 2 inferred relationships involving `Session` (e.g. with `UTCDateTime` and `test_postgres_rls_requires_matching_transaction_context()`) actually correct?**
_`Session` has 2 INFERRED edges - model-reasoned connections that need verification._
- **Are the 23 inferred relationships involving `AppError` (e.g. with `request_export()` and `login()`) actually correct?**
_`AppError` has 23 INFERRED edges - model-reasoned connections that need verification._
- **Are the 24 inferred relationships involving `AppError` (e.g. with `request_export()` and `login()`) actually correct?**
_`AppError` has 24 INFERRED edges - model-reasoned connections that need verification._
- **Are the 3 inferred relationships involving `Base` (e.g. with `downgrade()` and `upgrade()`) actually correct?**
_`Base` has 3 INFERRED edges - model-reasoned connections that need verification._
- **What connects `$schema`, `file:///home/k1nq/repos/finance/.kilo/plugins/graphify.js`, `type` to the rest of the system?**
_185 weakly-connected nodes found - possible documentation gaps or missing edges._
_186 weakly-connected nodes found - possible documentation gaps or missing edges._
File diff suppressed because one or more lines are too long
+4772 -4218
View File
File diff suppressed because it is too large Load Diff
+22 -10
View File
@@ -264,15 +264,15 @@
"semantic_hash": "0904afccafd9eb544b4d6846fc6384c8"
},
"backend/app/imports/router.py": {
"mtime": 1789730952.1978154,
"seen": 1789735433.1581337,
"ast_hash": "5bd2a94019f9cbda6d0dc617dc82cd41",
"mtime": 1789987595.224261,
"seen": 1789987623.1673603,
"ast_hash": "561967cd45b365aeeb5f06dcba55e0a3",
"semantic_hash": ""
},
"backend/app/imports/service.py": {
"mtime": 1789736072.1450737,
"seen": 1789736272.9455285,
"ast_hash": "16c0182cdcfb0aee9a7a5a895e3c86b2",
"mtime": 1789987595.224261,
"seen": 1789987623.1673615,
"ast_hash": "d78aede9091025373227eeea80758ffb",
"semantic_hash": ""
},
"backend/app/imports/tasks.py": {
@@ -378,10 +378,10 @@
"semantic_hash": "f6e5c4d1e1d0f2b1941acf8ec9f6678b"
},
"backend/tests/test_imports.py": {
"mtime": 1789358115.6295624,
"seen": 1789377303.0692356,
"ast_hash": "34ad7bb7ce983ff5fd4f8b3afc3d9f8b",
"semantic_hash": "34ad7bb7ce983ff5fd4f8b3afc3d9f8b"
"mtime": 1789987559.2557845,
"seen": 1789987623.1673782,
"ast_hash": "775b6bb13c49cfd933fdfe981142dc41",
"semantic_hash": ""
},
"backend/tests/test_ledger_properties.py": {
"mtime": 1789129573.0755556,
@@ -922,5 +922,17 @@
"seen": 1789735433.1582298,
"ast_hash": "2ae2370fa527b015cd7bd98dc7ca2a6b",
"semantic_hash": ""
},
".kilo/plugins/graphify.js": {
"mtime": 1789967188.4955833,
"seen": 1789987085.8116593,
"ast_hash": "af79b534b1739d62b450e39c9b50e362",
"semantic_hash": ""
},
"AGENTS.md": {
"mtime": 1789967188.495481,
"seen": 1789987085.8117683,
"ast_hash": "6a47d64fcc136324ca395065a707ef76",
"semantic_hash": ""
}
}