fix: serve latest organization analytics data
Some checks failed
CI/CD Pipeline / Run Tests (push) Failing after 2m53s
CI/CD Pipeline / Code Quality Checks (push) Successful in 3m12s
CI/CD Pipeline / Build Docker Images (push) Has been skipped
CI/CD Pipeline / Push to Gitea Registry (push) Has been skipped
CI/CD Pipeline / Deploy to Server (push) Has been skipped

This commit is contained in:
2026-06-01 12:09:03 +02:00
parent cd74427741
commit b64425b31d
5 changed files with 178 additions and 11 deletions

View File

@@ -14,6 +14,11 @@ from apps.form_3.models import FormF3Record
from apps.form_4.models import FormF4Record
from apps.form_5.models import FormF5Record
from apps.form_6.models import FormF6Record
from apps.organization.availability import (
has_financial_reports,
has_tax_reports,
risk_level_for_availability,
)
from apps.organization.models import IndustryCluster, Organization
from apps.organization.scope_utils import filter_queryset_by_scopes
from django.db.models import Avg, Case, Count, IntegerField, Q, Sum, When
@@ -103,6 +108,26 @@ def _best_records_by_year(
return resolved
def _latest_report_year(records: Iterable) -> int | None:
years = [
record.report_year
for record in records
if getattr(record, "report_year", None) is not None
]
return max(years, default=None)
def _resolve_report_year(records: Iterable, requested_year: int) -> int:
years = {
record.report_year
for record in records
if getattr(record, "report_year", None) is not None
}
if requested_year in years:
return requested_year
return max(years, default=requested_year)
def _weighted_average_age(age_distribution: list[dict[str, int]]) -> float:
bucket_midpoints = {
"under_30": 25,
@@ -331,7 +356,26 @@ class OrganizationAnalyticsService:
periods = sorted(set(f2_by_year) | set(f4_by_year))
if not periods:
raise NotFoundError(message="Economics data is not available")
latest_year = max(
filter(
None,
(
_latest_report_year(cls._f2_records(organization)),
_latest_report_year(cls._f4_records(organization)),
),
),
default=None,
)
if latest_year is None:
raise NotFoundError(message="Economics data is not available")
f2_by_year = _best_records_by_year(
cls._f2_records(organization), latest_year, latest_year
)
f4_by_year = _best_records_by_year(
cls._f4_records(organization), latest_year, latest_year
)
periods = sorted(set(f2_by_year) | set(f4_by_year))
metric_units = cls._economics_metric_units()
selected_metrics = cls._economics_metric_groups()[group]
@@ -407,6 +451,7 @@ class OrganizationAnalyticsService:
history_years: int,
) -> dict[str, object]:
f3_records = cls._f3_records(organization)
report_year = _resolve_report_year(f3_records, report_year)
current_f3 = cls._require_record(
_pick_record(f3_records, report_year),
entity="Personnel",
@@ -460,6 +505,7 @@ class OrganizationAnalyticsService:
report_year: int,
) -> dict[str, object]:
f3_records = cls._f3_records(organization)
report_year = _resolve_report_year(f3_records, report_year)
current_f3 = cls._require_record(
_pick_record(f3_records, report_year),
entity="Equipment",
@@ -813,11 +859,9 @@ class OrganizationAnalyticsService:
frequency: str,
price_mode: str,
) -> dict[str, object]:
records = [
record
for record in cls._f1_records(organization)
if record.report_year == report_year
]
f1_records = cls._f1_records(organization)
report_year = _resolve_report_year(f1_records, report_year)
records = [record for record in f1_records if record.report_year == report_year]
if not records:
raise NotFoundError(message="Products data is not available")
@@ -917,14 +961,21 @@ class OrganizationAnalyticsService:
@classmethod
def get_risk_profile(cls, *, organization: Organization) -> dict[str, object]:
financial_reports_available = has_financial_reports(organization)
tax_reports_available = has_tax_reports(organization)
return {
"organization_id": str(organization.id),
"financial_reports_available": organization.financial_reports_available,
"tax_reports_available": organization.tax_reports_available,
"financial_reports_available": financial_reports_available,
"tax_reports_available": tax_reports_available,
"in_defense_unreliable_suppliers_registry": organization.in_defense_unreliable_suppliers_registry,
"in_275_fz_registry": organization.in_275_fz_registry,
"bankruptcy_messages_found": organization.bankruptcy_messages_found,
"risk_level": organization.risk_level,
"risk_level": risk_level_for_availability(
organization,
financial_reports_available=financial_reports_available,
tax_reports_available=tax_reports_available,
),
"updated_at": organization.updated_at.isoformat(),
}

View File

@@ -0,0 +1,48 @@
"""Availability helpers for organization-facing contracts."""
from __future__ import annotations
from apps.organization.models import Organization
def has_financial_reports(organization: Organization) -> bool:
"""Return whether financial data is effectively available for an organization."""
if organization.financial_reports_available:
return True
return (
organization.financial_reports.filter(status="success").exists()
or organization.form_f2_records.filter(is_active_version=True).exists()
)
def has_tax_reports(organization: Organization) -> bool:
"""Return whether tax-related report data is effectively available."""
if organization.tax_reports_available:
return True
return organization.form_f2_records.filter(
is_active_version=True,
income_tax__isnull=False,
).exists()
def risk_level_for_availability(
organization: Organization,
*,
financial_reports_available: bool,
tax_reports_available: bool,
) -> str:
"""Calculate risk level with effective availability flags."""
risk_score = 0
risk_score += 3 if organization.in_defense_unreliable_suppliers_registry else 0
risk_score += 2 if organization.in_275_fz_registry else 0
risk_score += 2 if organization.bankruptcy_messages_found else 0
risk_score += 1 if not financial_reports_available else 0
risk_score += 1 if not tax_reports_available else 0
if risk_score >= 5:
return "high"
if risk_score >= 2:
return "medium"
return "low"

View File

@@ -6,6 +6,7 @@
- frontend-facing serializers for organization catalog endpoints.
"""
from apps.organization.availability import has_financial_reports, has_tax_reports
from apps.organization.models import Organization
from apps.organization.scope_utils import (
SCOPE_LABELS,
@@ -192,8 +193,8 @@ class OrganizationCatalogDetailSerializer(OrganizationCatalogBaseSerializer):
@staticmethod
def get_summary(obj: Organization) -> dict[str, object]:
return {
"financial_reports_available": obj.financial_reports_available,
"tax_reports_available": obj.tax_reports_available,
"financial_reports_available": has_financial_reports(obj),
"tax_reports_available": has_tax_reports(obj),
"active_registry_names": obj.get_active_registry_names(),
}