Programming
1. Models
"""
What lives in the database and what does not.
The loan-month panel (8M+ rows) NEVER goes into SQLite. It stays in parquet.
SQLite holds only the marts — roughly 8,000 rows in total across every metric —
plus the registry of what has been loaded and what the quality gate said.
That split is the whole reason this is fast. A page load is one indexed query
returning a few hundred rows, not an aggregation over millions.
"""
from __future__ import annotations
from django.db import models
class Snapshot(models.Model):
"""One monthly file. The registry of what the warehouse actually contains."""
STATUS = [("ok", "ok"), ("stale", "stale"), ("failed", "failed")]
report_date = models.DateField(unique=True)
period = models.IntegerField(db_index=True, help_text="YYYYMM of the month described")
source_name = models.CharField(max_length=255, blank=True)
source_sha256 = models.CharField(max_length=64, blank=True,
help_text="re-ingesting an identical file is a no-op")
row_count = models.BigIntegerField(default=0)
loan_count = models.BigIntegerField(default=0)
exposure_total = models.FloatField(default=0.0)
npl_ratio = models.FloatField(null=True, blank=True)
status = models.CharField(max_length=12, choices=STATUS, default="ok")
ingested_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["-period"]
def __str__(self):
return f"{self.period} ({self.row_count:,} rows)"
class MartRow(models.Model):
"""
One row of one mart, with its numeric payload in JSON.
Wide-JSON rather than one row per metric: a mart has 5–20 numeric columns
and the report always wants all of them together, so this is one query
instead of twenty, and adding a metric needs no migration.
The five key columns cover every mart shape in the pipeline:
portfolio_monthly period, dim_name, dim_value
vintages period=vintage, k1=mob
transitions period, k1=from_bucket, k2=to_bucket
cure_rates period, k1=from_bucket, k2=outcome
client_burden period, k1=burden
"""
mart = models.CharField(max_length=40, db_index=True)
period = models.IntegerField(db_index=True)
dim_name = models.CharField(max_length=40, default="__all__")
dim_value = models.CharField(max_length=120, default="__all__")
k1 = models.CharField(max_length=40, blank=True, default="")
k2 = models.CharField(max_length=40, blank=True, default="")
data = models.JSONField(default=dict)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["mart", "period", "dim_name", "dim_value", "k1", "k2"],
name="uniq_mart_row"),
]
indexes = [
models.Index(fields=["mart", "period"]),
models.Index(fields=["mart", "dim_name", "dim_value"]),
]
def __str__(self):
return f"{self.mart}/{self.period}/{self.dim_name}={self.dim_value}"
class QualityCheck(models.Model):
"""Gate results, kept so the report can show its own caveats."""
run_period = models.IntegerField(db_index=True)
check_name = models.CharField(max_length=60, help_text="`check` is reserved by Django's model API")
severity = models.CharField(max_length=8)
status = models.CharField(max_length=8)
detail = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-run_period", "check_name"]
@property
def passed(self) -> bool:
return self.status == "pass"
class ReportRun(models.Model):
"""A rendered deliverable, so the last good report is always retrievable."""
period = models.IntegerField(db_index=True)
lang = models.CharField(max_length=5, default="ru")
fx_mode = models.CharField(max_length=12, default="nominal")
html_path = models.CharField(max_length=500, blank=True)
generated_at = models.DateTimeField(auto_now_add=True)
notes = models.TextField(blank=True)
class Meta:
ordering = ["-period", "-generated_at"]
get_latest_by = "generated_at"
def __str__(self):
return f"{self.period} [{self.lang}]"
PO
powerty
Author
· Staff
Aug. 26, 2026
Aug. 26, 2026
2
Views
0
Likes
2m
Read