Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .ai-context/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ New data homes use:
- `bank/inbox`, `bank/processed`, `bank/duplicates`, and `bank/exports` for
banking statements and exports.
- Matching `tax/` folders for tax document workflows.
- `financial/inbox`, `financial/canonical`, `financial/failed`,
`financial/duplicates`, `financial/review`, `financial/views`, and
`financial/exports` for v2 document-first workflows.

## Banking Import Model

Expand Down Expand Up @@ -91,3 +94,9 @@ exists. Early v2 work should add `BB_` foundation tables beside existing legacy
tables, keep new SQL centralized behind DAO modules, and focus on generic
document storage/import, provenance, and inspect/report foundations before
adding a user-facing infer workflow.

V2 document storage should treat `financial/canonical` as the source of truth
and `financial/views` as generated human-readable copies. Domain tables should
link to `BB_DOCUMENT`, `BB_DOCUMENT_OBJECT`, or `BB_DOCUMENT_VIEW` rather than
storing filesystem paths directly. The legacy `bank/` and `tax/` trees remain
available for v1 workflows while v2 matures side by side.
10 changes: 10 additions & 0 deletions .ai-context/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ as account identity.
Imports, repairs, and storage-layout migration expose dry-run paths so parser,
duplicate, archive, and migration decisions can be reviewed before writes.

## V2 Canonical Storage And Human Views

The financial-intelligence v2 model stores authoritative document objects under
`financial/canonical` and exposes user-friendly generated copies under
`financial/views`. The database centralizes path metadata in storage roots,
document objects, and document views; domain facts reference document/object
rows instead of embedding filesystem paths. Human views are copies by default,
not symlinks or hardlinks, so they can be rebuilt, reconciled, and repaired
without making user-browsable folders the heart of the system.

## Mask Sensitive Account Details

Full account numbers can be stored for validation and exact matching, but
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ and versions are tracked in the repo-root `VERSION` file.

### Added

- Added v2 financial document storage roots, document object metadata,
generated human-readable document view metadata, path-resolution helpers,
and read-only managed-file guardrails under the `financial/` namespace.
- Added additive financial intelligence v2 foundation tables, seed type
dictionaries, indexes, and DAO-based read/write helpers without dropping
existing banking or tax tables.
Expand Down
76 changes: 67 additions & 9 deletions docs/financial_intelligence_architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,17 @@ The following decisions define the current v2 direction:
foundation, while allowing details to evolve during implementation.
- Introduce the v2 foundation schema additively beside the existing legacy
tables. Do not silently discard existing tables or data.
- Keep v2 documents in a separate `financial/` storage namespace so early v2
work does not mix with the existing `bank/` and `tax/` trees.
- Move imported documents into managed storage when an import activity finishes,
whether the import succeeds or fails.
- Treat `financial/canonical` as the authoritative document-object store and
`financial/views` as generated human-readable copies for transparency.
- Store canonical/view path metadata in `BB_STORAGE_ROOT`,
`BB_DOCUMENT_OBJECT`, and `BB_DOCUMENT_VIEW`. Other v2 domain tables should
reference document/object rows rather than storing filesystem paths.
- Do not use symlinks or hardlinks as the default human-view mechanism. Views
are copies that can be reconciled, repaired, or regenerated.
- Treat future `infer` work as read-only with respect to source documents unless
a later design explicitly says otherwise.
- Pause TaxBuddy issue #100 until the v2 document/entity/observation model
Expand Down Expand Up @@ -262,10 +271,11 @@ W-2, Form 26AS, AIS, mortgage statement, property tax bill, insurance policy.

Documents should track:

- original filename/path;
- canonical filename/path when copied into managed storage;
- original filename;
- SHA-256 hash;
- MIME/file type;
- authoritative storage object metadata;
- generated human-readable view metadata;
- document type;
- source institution when known;
- jurisdiction;
Expand Down Expand Up @@ -342,6 +352,8 @@ Conceptual ER:

```text
BB_DOCUMENT
-> BB_DOCUMENT_OBJECT
-> BB_DOCUMENT_VIEW
-> BB_IMPORT_ATTEMPT
-> BB_EXTRACTION_RUN
-> BB_OBSERVATION
Expand Down Expand Up @@ -414,24 +426,62 @@ BB_DOCUMENT(
document_id,
file_hash,
original_file_name,
original_path,
canonical_file_name,
canonical_path,
source_uri,
document_type,
file_type,
jurisdiction_code,
period_start,
period_end,
tax_year,
document_status,
created_at,
updated_at
)

BB_STORAGE_ROOT(
storage_root_id,
storage_root_code,
root_kind,
base_path_key,
relative_root,
permissions_mode,
active,
created_at,
updated_at
)

BB_DOCUMENT_OBJECT(
document_object_id,
document_id,
storage_root_id,
object_key,
object_role,
content_hash,
byte_size,
media_type,
original_file_name,
created_at,
updated_at
)

BB_DOCUMENT_VIEW(
document_view_id,
document_id,
document_object_id,
storage_root_id,
view_name,
view_key,
materialization_kind,
expected_hash,
byte_size,
status,
last_materialized_at,
created_at,
updated_at
)

BB_IMPORT_ATTEMPT(
import_attempt_id,
document_id,
source_path,
document_object_id,
import_status,
started_at,
finished_at,
Expand Down Expand Up @@ -750,6 +800,12 @@ Create indexes alongside the v2 schema rather than as an afterthought:
BB_DOCUMENT(file_hash)
BB_DOCUMENT(document_type, tax_year)
BB_DOCUMENT(jurisdiction_code, tax_year)
BB_STORAGE_ROOT(root_kind, active)
BB_DOCUMENT_OBJECT(document_id, object_role)
BB_DOCUMENT_OBJECT(content_hash)
BB_DOCUMENT_VIEW(document_id)
BB_DOCUMENT_VIEW(document_object_id)
BB_DOCUMENT_VIEW(status)
BB_IMPORT_ATTEMPT(document_id, import_status)
BB_ENTITY(entity_type, status)
BB_ENTITY_ATTRIBUTE(entity_id, entity_attribute_type_id)
Expand Down Expand Up @@ -792,7 +848,9 @@ Responsibilities:
- hash files;
- identify duplicates;
- create document records;
- plan/copy canonical storage;
- plan/copy canonical storage objects;
- materialize human-readable document views;
- reconcile missing or modified generated views against canonical objects;
- list/show documents;
- record import attempts.
- move completed import inputs into managed storage for both successful and
Expand Down
31 changes: 18 additions & 13 deletions docs/financial_intelligence_open_questions.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,28 +50,33 @@ Current bias:
needed for tests, provenance, or inspect commands.
- Keep debug logs and normal CLI output free of raw document content.

## Managed Document Storage Layout
## Managed Document View Reconciliation

BankBuddy already moves completed imports into managed storage. The v2 model
needs a generic document layout that works for banking, tax, investment,
property, insurance, failed imports, duplicates, and review-needed documents.
BankBuddy v2 separates authoritative canonical document objects from generated
human-readable views. This keeps the database and canonical store authoritative
while preserving the transparent browsable filesystem experience that made v1
easy to understand.

Open questions:

- What is the exact directory layout for successful documents?
- Should failed documents live under a shared failure area or under a
domain-specific path?
- Should exact duplicates continue to be preserved under a duplicates
directory, or eventually be deleted after recording the duplicate attempt?
- How should review-needed documents be named and grouped?
- How should BankBuddy detect that a user edited, moved, or deleted generated
view copies?
- Should view reconciliation run automatically during status/import, or only
through an explicit inspect/repair command?
- What should the repair UX look like when a view is missing but the canonical
object is intact?
- Should exact duplicates continue to be preserved under the v2 duplicates root,
or eventually be deleted after recording the duplicate attempt?

Current bias:

- Preserve source documents after every completed import attempt.
- Keep `financial/canonical` as the source of truth.
- Treat `financial/views` as generated copies that can be rebuilt.
- Preserve source documents after every completed import attempt by recording a
canonical object or managed failed/duplicate object.
- Keep duplicate preservation for now because it is useful while the product is
still young.
- Store canonical paths in the database, but keep directory-shape assumptions
in path/layout services rather than spreading them through domain logic.
- Use explicit inspect/repair commands before adding automatic repair behavior.

## Type Dictionary Seed Values

Expand Down
6 changes: 1 addition & 5 deletions src/bankbuddy/financial/dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,19 +118,17 @@ def create_document(self, record: DocumentCreate) -> DocumentRecord:
file_hash,
original_file_name,
canonical_file_name,
storage_path,
source_uri,
document_type,
jurisdiction_code,
tax_year,
document_status
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
) values (?, ?, ?, ?, ?, ?, ?, ?)
""",
(
record.file_hash,
record.original_file_name,
record.canonical_file_name,
record.storage_path,
record.source_uri,
record.document_type,
record.jurisdiction_code,
Expand All @@ -150,7 +148,6 @@ def find_document_by_hash(self, file_hash: str) -> DocumentRecord | None:
file_hash,
original_file_name,
canonical_file_name,
storage_path,
source_uri,
document_type,
jurisdiction_code,
Expand Down Expand Up @@ -327,7 +324,6 @@ def _document_from_row(row: sqlite3.Row) -> DocumentRecord:
file_hash=str(row["file_hash"]),
original_file_name=str(row["original_file_name"]),
canonical_file_name=row["canonical_file_name"],
storage_path=row["storage_path"],
source_uri=row["source_uri"],
document_type=row["document_type"],
jurisdiction_code=row["jurisdiction_code"],
Expand Down
60 changes: 59 additions & 1 deletion src/bankbuddy/financial/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class DocumentCreate:
file_hash: str
original_file_name: str
canonical_file_name: str | None = None
storage_path: str | None = None
source_uri: str | None = None
document_type: str | None = None
jurisdiction_code: str | None = None
Expand All @@ -72,6 +71,65 @@ class DocumentRecord(DocumentCreate):
document_id: int = 0


@dataclass(frozen=True)
class StorageRootRecord:
"""A configured v2 document storage root."""

storage_root_id: int
storage_root_code: str
root_kind: str
base_path_key: str
relative_root: str
permissions_mode: str
active: bool


@dataclass(frozen=True)
class DocumentObjectCreate:
"""Input data for a stored document object."""

document_id: int
storage_root_code: str
object_key: str
object_role: str
content_hash: str
byte_size: int | None = None
media_type: str | None = None
original_file_name: str | None = None


@dataclass(frozen=True)
class DocumentObjectRecord(DocumentObjectCreate):
"""Stored document object metadata."""

document_object_id: int = 0
storage_root_id: int = 0


@dataclass(frozen=True)
class DocumentViewCreate:
"""Input data for a generated human-readable document view."""

document_id: int
document_object_id: int
storage_root_code: str
view_name: str
view_key: str
materialization_kind: str = "copy"
expected_hash: str | None = None
byte_size: int | None = None
status: str = "current"
last_materialized_at: str | None = None


@dataclass(frozen=True)
class DocumentViewRecord(DocumentViewCreate):
"""Stored generated document view metadata."""

document_view_id: int = 0
storage_root_id: int = 0


@dataclass(frozen=True)
class EntityCreate:
"""Input data for creating a v2 entity record."""
Expand Down
Loading
Loading