ishaq101's picture
/fix help and active tools (#24)
00ca133
Raw History Blame
3.62 kB
"""Pydantic models for the per-user data catalog (Cs + Ct).
See ARCHITECTURE.md §6 for the full schema definition.
Source.location_ref URI scheme
------------------------------
A `Source` is uniquely addressable by `location_ref`; introspectors and
executors parse it to find the underlying data:
schema sources → "dbclient://{database_client_id}"
Resolves via `database_client_service.get(...)` which
returns a `DatabaseClient` row whose Fernet-encrypted
credentials are decrypted at runtime.
tabular sources → "az_blob://{user_id}/{document_id}"
The Source aggregates one or more sheets as Tables;
each per-sheet Parquet blob is named via
`parquet_service.parquet_blob_name(user_id, document_id, sheet_name)`,
so executors derive the per-Table blob path from
`Source.location_ref` plus `Table.name`.
unstructured → reserved (deferred — see ARCHITECTURE.md §10 q2).
"""
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, Field
SourceType = Literal["schema", "tabular", "unstructured"]
DataType = Literal["int", "decimal", "string", "datetime", "date", "bool", "json"]
class ColumnStats(BaseModel):
min: Any | None = None
max: Any | None = None
mean: float | None = None
median: float | None = None
distinct_count: int | None = None
top_values: list[Any] | None = None
class Column(BaseModel):
column_id: str
name: str
data_type: DataType
nullable: bool
pii_flag: bool = False
sample_values: list[Any] | None = None
stats: ColumnStats | None = None
# Data dictionary (KM-717, Go `887d98a`, 2026-09-29): user-entered column
# metadata, written by Go's `POST /api/v1/data-catalog/{user_id}/sources/
# {source_id}/dictionary` and carried into analysis-scope catalogs. All optional
# and omitted by Go when empty, so pre-dictionary catalogs parse unchanged.
# `classify` is "dimension" | "measure"; `aggregation` is sum|avg|min|max|count
# (FE `DataDictionaryForm.tsx`). Free text — rendered only inside the planner's
# `<data>` block.
description: str | None = None
business_meaning: str | None = None
unit_of_measure: str | None = None
classify: str | None = None
aggregation: str | None = None
is_key: bool = False
data_classification: str | None = None
validation_rules: Any | None = None
class ForeignKey(BaseModel):
"""A FK edge from one column in this table to a column in another table.
All references use stable IDs derived from source/table/column names so
edges survive renames at the `name` level. The target table must belong
to the SAME `Source` — cross-source FKs are not modeled in v1.
"""
column_id: str # the column in this table that holds the FK
target_table_id: str # referenced table_id, within the same Source
target_column_id: str # referenced column_id
class Table(BaseModel):
table_id: str
name: str
row_count: int | None = None
columns: list[Column]
foreign_keys: list[ForeignKey] = Field(default_factory=list)
class Source(BaseModel):
source_id: str
source_type: SourceType
name: str
location_ref: str
updated_at: datetime
tables: list[Table] = Field(default_factory=list)
class Catalog(BaseModel):
user_id: str
schema_version: str = "1.0"
generated_at: datetime
sources: list[Source] = Field(default_factory=list)