Spaces:
Sleeping
Sleeping
Commit
·
e8efc4f
1
Parent(s):
65fe9aa
chore: Add scaffolded database layer (not yet implemented)
Browse files- database.py: PostgreSQL connection setup (TODO)
- models.py: SQLModel schema for Invoice/LineItem (TODO)
- repository.py: CRUD operations placeholder (TODO)
Scaffolded for future human-in-the-loop persistence feature.
- src/database.py +33 -0
- src/models.py +50 -0
- src/repository.py +41 -0
src/database.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# src/database.py
|
| 2 |
+
|
| 3 |
+
from sqlmodel import SQLModel, create_engine, Session
|
| 4 |
+
from typing import Generator
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
# --- INSTRUCTIONS ---
|
| 8 |
+
# 1. Get credentials from environment variables (Os.getenv)
|
| 9 |
+
# 2. Construct the DATABASE_URL string: postgresql://user:pass@host:port/db
|
| 10 |
+
# 3. Create the SQLModel engine
|
| 11 |
+
# 4. Implement the init_db and get_session functions
|
| 12 |
+
|
| 13 |
+
# TODO: Define constants for DB params (POSTGRES_USER, etc.)
|
| 14 |
+
|
| 15 |
+
# TODO: Define DATABASE_URL
|
| 16 |
+
|
| 17 |
+
# TODO: Create 'engine' using create_engine(DATABASE_URL)
|
| 18 |
+
|
| 19 |
+
def init_db():
|
| 20 |
+
"""
|
| 21 |
+
Idempotent DB initialization.
|
| 22 |
+
Should create all tables defined in SQLModel metadata.
|
| 23 |
+
"""
|
| 24 |
+
# TODO: Call SQLModel.metadata.create_all(engine)
|
| 25 |
+
pass
|
| 26 |
+
|
| 27 |
+
def get_session() -> Generator[Session, None, None]:
|
| 28 |
+
"""
|
| 29 |
+
Dependency for yielding a database session.
|
| 30 |
+
Useful for FastAPI dependencies or context usage.
|
| 31 |
+
"""
|
| 32 |
+
# TODO: Open a session with the engine, yield it, and ensure it closes
|
| 33 |
+
pass
|
src/models.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# src/models.py
|
| 2 |
+
|
| 3 |
+
from typing import List, Optional
|
| 4 |
+
from datetime import date as DateType
|
| 5 |
+
from decimal import Decimal
|
| 6 |
+
from sqlmodel import SQLModel, Field, Relationship
|
| 7 |
+
|
| 8 |
+
# --- INSTRUCTIONS ---
|
| 9 |
+
# SQLModel classes should mirror the Pydantic models in src/schema.py
|
| 10 |
+
# but with database-specific configurations (primary keys, foreign keys).
|
| 11 |
+
|
| 12 |
+
class Invoice(SQLModel, table=True):
|
| 13 |
+
__tablename__ = "invoices"
|
| 14 |
+
|
| 15 |
+
# TODO: Define Primary Key 'id' (int, optional, default=None)
|
| 16 |
+
|
| 17 |
+
# TODO: Add Data Fields
|
| 18 |
+
# - receipt_number (str, indexed)
|
| 19 |
+
# - date (DateType)
|
| 20 |
+
# - total_amount (Decimal, max_digits=10, decimal_places=2)
|
| 21 |
+
# - vendor (str)
|
| 22 |
+
# - address (str)
|
| 23 |
+
# - semantic_hash (str, unique, indexed) -> Critical for deduplication
|
| 24 |
+
|
| 25 |
+
# TODO: Add Metadata Fields
|
| 26 |
+
# - validation_status (str)
|
| 27 |
+
# - validation_errors (str) -> Store as JSON string since we don't need to query inside it yet
|
| 28 |
+
# - created_at (DateType) -> Default to today
|
| 29 |
+
|
| 30 |
+
# TODO: Define relationship to LineItem (One-to-Many)
|
| 31 |
+
# items: List["LineItem"] = Relationship(...)
|
| 32 |
+
pass
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class LineItem(SQLModel, table=True):
|
| 36 |
+
__tablename__ = "line_items"
|
| 37 |
+
|
| 38 |
+
# TODO: Define Primary Key
|
| 39 |
+
|
| 40 |
+
# TODO: Define Foreign Key 'invoice_id' linking to 'invoices.id'
|
| 41 |
+
|
| 42 |
+
# TODO: Add Data Fields
|
| 43 |
+
# - description (str)
|
| 44 |
+
# - quantity (int)
|
| 45 |
+
# - unit_price (Decimal)
|
| 46 |
+
# - total (Decimal)
|
| 47 |
+
|
| 48 |
+
# TODO: Define relationship back to Invoice
|
| 49 |
+
# invoice: Optional[Invoice] = Relationship(...)
|
| 50 |
+
pass
|
src/repository.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# src/repository.py
|
| 2 |
+
|
| 3 |
+
from sqlmodel import Session, select
|
| 4 |
+
from typing import Dict, Any, Optional
|
| 5 |
+
import json
|
| 6 |
+
|
| 7 |
+
from src.models import Invoice, LineItem
|
| 8 |
+
from src.database import get_session, engine
|
| 9 |
+
|
| 10 |
+
class InvoiceRepository:
|
| 11 |
+
def __init__(self, session: Session = None):
|
| 12 |
+
"""
|
| 13 |
+
Initialize with an optional session.
|
| 14 |
+
Allows dependency injection for testing or API usage.
|
| 15 |
+
"""
|
| 16 |
+
self.session = session
|
| 17 |
+
|
| 18 |
+
def save_invoice(self, invoice_data: Dict[str, Any]) -> Invoice:
|
| 19 |
+
"""
|
| 20 |
+
Saves an invoice and its line items to the database.
|
| 21 |
+
|
| 22 |
+
Steps to implement:
|
| 23 |
+
1. Manage Session: If self.session is None, create a new one using 'engine'.
|
| 24 |
+
2. Clean Data: Separate 'items' list from the main invoice properties.
|
| 25 |
+
3. Create Invoice: Instantiate the Invoice SQLModel.
|
| 26 |
+
4. Deserialize Complex Types: e.g. 'validation_errors' list -> JSON string.
|
| 27 |
+
5. Process Items: Iterate 'items', create LineItem models, check keys match, and append to invoice.items.
|
| 28 |
+
6. Commit: Add to session, commit, and refresh.
|
| 29 |
+
7. Error Handling: Wrap in try/except to rollback on failure.
|
| 30 |
+
"""
|
| 31 |
+
# TODO: Implementation
|
| 32 |
+
raise NotImplementedError("Implement the save logic.")
|
| 33 |
+
|
| 34 |
+
def get_by_hash(self, semantic_hash: str) -> Optional[Invoice]:
|
| 35 |
+
"""
|
| 36 |
+
Check if invoice already exists using the semantic hash.
|
| 37 |
+
"""
|
| 38 |
+
# TODO: Create session if needed
|
| 39 |
+
# TODO: Execute SELECT statement filtering by hash
|
| 40 |
+
# TODO: Return first result or None
|
| 41 |
+
raise NotImplementedError("Implement the query logic.")
|