Programming

1. django_setup

"""
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}]"
Helpful? Dislike 0 Log in to react