Extend organization analytics contract and fix test warnings
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 1m42s
CI/CD Pipeline / Run Tests (push) Successful in 1m45s
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-04-19 12:23:38 +02:00
parent f662e55139
commit 8ead5ebadc
14 changed files with 1348 additions and 130 deletions

View File

@@ -51,6 +51,7 @@ class OrganizationAnalyticsApiTest(APITestCase):
organization=self.organization,
report_year=2026,
report_quarter=1,
avg_payroll_employees=995,
payroll_fund=Decimal("1000000.00"),
military_output_actual=Decimal("11000000.00"),
civilian_output_actual=Decimal("7000000.00"),
@@ -65,6 +66,7 @@ class OrganizationAnalyticsApiTest(APITestCase):
organization=self.organization,
report_year=2025,
report_quarter=1,
avg_payroll_employees=970,
payroll_fund=Decimal("900000.00"),
military_output_actual=Decimal("9000000.00"),
civilian_output_actual=Decimal("6000000.00"),
@@ -151,6 +153,9 @@ class OrganizationAnalyticsApiTest(APITestCase):
report_year=2026,
equipment_category="Станочное оборудование",
is_domestic=True,
commissioning_date=date(2026, 1, 15),
is_operational=False,
utilization_rate=Decimal("92.00"),
physical_wear_percent=Decimal("28.40"),
)
FormF6RecordFactory.create(
@@ -218,6 +223,12 @@ class OrganizationAnalyticsApiTest(APITestCase):
self.assertIn("ros", response.data["ratios"][0])
self.assertIn("roa", response.data["ratios"][0])
self.assertIn("roe", response.data["ratios"][0])
self.assertIn("ebitda_margin", response.data["ratios"][0])
self.assertEqual(
set(response.data["ratio_normatives"]),
{"ros", "roa", "roe", "ebitda_margin"},
)
self.assertTrue(all(value is not None for value in response.data["ratio_normatives"].values()))
def test_personnel_contract(self):
personnel_response = self.client.get(
@@ -233,6 +244,11 @@ class OrganizationAnalyticsApiTest(APITestCase):
)
self.assertEqual(len(personnel_response.data["history"]), 2)
self.assertEqual(len(personnel_response.data["age_distribution"]), 3)
self.assertIn("average_age", personnel_response.data)
self.assertEqual(
personnel_response.data["headcount"]["avg_payroll_employees"], 995
)
self.assertEqual(personnel_response.data["headcount"]["payroll_fund"], 1000000)
self.assertEqual(
personnel_response.data["age_distribution"][0]["age_group"],
"under_30",
@@ -255,27 +271,38 @@ class OrganizationAnalyticsApiTest(APITestCase):
"domestic_equipment",
"imported_equipment",
"physical_wear_percent",
"weighted_wear_percent",
"utilization_rate",
"avg_shift_work",
"equipment_needed",
"average_age_years",
"commissioned_equipment",
"decommissioned_equipment",
},
)
self.assertEqual(response.data["summary"]["total_equipment"], 187)
self.assertEqual(response.data["summary"]["physical_wear_percent"], 32.0)
self.assertEqual(response.data["summary"]["weighted_wear_percent"], 32.0)
self.assertEqual(response.data["summary"]["utilization_rate"], 0.92)
self.assertEqual(response.data["summary"]["commissioned_equipment"], 1)
self.assertEqual(response.data["summary"]["decommissioned_equipment"], 1)
self.assertEqual(
response.data["age_distribution"][0]["bucket"], "under_5_years"
)
self.assertEqual(len(response.data["age_distribution"]), 5)
self.assertGreaterEqual(len(response.data["categories"]), 1)
self.assertEqual(len(response.data["dynamics_series"]), 3)
self.assertEqual(
response.data["categories"][0],
{
"category": "Станочное оборудование",
"total_equipment": 1,
"domestic_equipment": 1,
"imported_equipment": 0,
"total_equipment": 54,
"domestic_equipment": 31,
"imported_equipment": 23,
"physical_wear_percent": 28.4,
"weighted_wear_percent": 28.4,
"utilization_rate": 0.92,
"lease_share_itn_percent": None,
},
)
@@ -293,8 +320,12 @@ class OrganizationAnalyticsApiTest(APITestCase):
self.assertEqual(products_response.data["summary"]["civilian_output_amount"], 7000000)
self.assertEqual(products_response.data["summary"]["hightech_output_amount"], 1500000)
self.assertEqual(products_response.data["summary"]["rd_volume_amount"], 900000)
self.assertEqual(products_response.data["summary"]["shipped_goods_amount"], 18000000)
self.assertEqual(len(products_response.data["production_series"]), 1)
self.assertEqual(len(products_response.data["sales_series"]), 1)
self.assertEqual(len(products_response.data["rd_volume_series"]), 1)
self.assertEqual(len(products_response.data["shipped_goods_series"]), 1)
self.assertEqual(len(products_response.data["table"]), 1)
first_production = products_response.data["production_series"][0]
first_sales = products_response.data["sales_series"][0]
@@ -302,6 +333,59 @@ class OrganizationAnalyticsApiTest(APITestCase):
self.assertEqual(first_production["military_output_amount"], 11000000)
self.assertEqual(first_sales["military_domestic_amount"], 9000000)
self.assertEqual(first_sales["civilian_export_amount"], 2000000)
self.assertEqual(
products_response.data["rd_volume_series"][0]["rd_volume_amount"], 900000
)
self.assertEqual(
products_response.data["shipped_goods_series"][0]["shipped_goods_amount"],
18000000,
)
self.assertEqual(
products_response.data["table"][0]["shipped_goods_amount"], 18000000
)
def test_products_supports_monthly_and_semiannual_frequency(self):
FormF1RecordFactory.create(
organization=self.organization,
report_year=2026,
report_quarter=2,
military_output_actual=Decimal("12000000.00"),
civilian_output_actual=Decimal("6000000.00"),
hightech_output_actual=Decimal("1800000.00"),
rd_volume_actual=Decimal("600000.00"),
military_domestic_actual=Decimal("9600000.00"),
military_export_actual=Decimal("2400000.00"),
civilian_domestic_actual=Decimal("4200000.00"),
civilian_export_actual=Decimal("1800000.00"),
)
semiannual_response = self.client.get(
f"/api/v1/organizations/{self.organization.id}/analytics/products/"
"?frequency=semiannual&price_mode=actual&report_year=2026"
)
self.assertEqual(semiannual_response.status_code, status.HTTP_200_OK)
self.assertEqual(semiannual_response.data["frequency"], "semiannual")
self.assertEqual(len(semiannual_response.data["production_series"]), 1)
self.assertEqual(
semiannual_response.data["production_series"][0]["period"], "2026-H1"
)
self.assertEqual(
semiannual_response.data["production_series"][0]["military_output_amount"],
23000000,
)
monthly_response = self.client.get(
f"/api/v1/organizations/{self.organization.id}/analytics/products/"
"?frequency=monthly&price_mode=actual&report_year=2026"
)
self.assertEqual(monthly_response.status_code, status.HTTP_200_OK)
self.assertEqual(monthly_response.data["frequency"], "monthly")
self.assertEqual(len(monthly_response.data["production_series"]), 6)
self.assertEqual(monthly_response.data["production_series"][0]["period"], "2026-01")
self.assertEqual(
monthly_response.data["production_series"][0]["military_output_amount"],
3666666,
)
def test_forecast_contract(self):
response = self.client.get(
@@ -314,15 +398,24 @@ class OrganizationAnalyticsApiTest(APITestCase):
self.assertEqual(response.data["scenario"], "base")
self.assertEqual(response.data["horizon_years"], 3)
self.assertEqual(response.data["base_year"], 2026)
self.assertIn("summary", response.data)
self.assertEqual(len(response.data["forecast"]), 3)
self.assertEqual(response.data["forecast"][0]["year"], 2027)
self.assertIn("revenue_amount", response.data["forecast"][0])
self.assertIn("net_profit_amount", response.data["forecast"][0])
self.assertIn("margin_percent", response.data["forecast"][0])
self.assertEqual(len(response.data["table"]), 3)
self.assertEqual(len(response.data["productivity_series"]), 3)
self.assertEqual(len(response.data["headcount_series"]), 3)
self.assertEqual(len(response.data["investment_series"]), 3)
self.assertEqual(len(response.data["rd_series"]), 3)
self.assertGreaterEqual(len(response.data["risk_factors"]), 1)
self.assertGreaterEqual(len(response.data["opportunity_factors"]), 1)
self.assertIn("code", response.data["risk_factors"][0])
self.assertIn("name", response.data["risk_factors"][0])
self.assertIn("impact_level", response.data["risk_factors"][0])
self.assertIn("revenue_cagr_percent", response.data["summary"])
self.assertIn("horizon_revenue_amount", response.data["summary"])
def test_risk_profile_contract(self):
response = self.client.get(

View File

@@ -70,6 +70,8 @@ class OrganizationApiTest(APITestCase):
response.data["results"][0]["corporation_scope_label"],
"Организации ОПК",
)
self.assertEqual(response.data["results"][0]["registry_category"], "opk")
self.assertEqual(response.data["results"][0]["registry_category_label"], "ОПК")
self.assertEqual(response.data["results"][0]["short_name"], "АО «Альфа»")
def test_detail_includes_active_registries(self):
@@ -111,6 +113,8 @@ class OrganizationApiTest(APITestCase):
response.data["corporation_scope_label"],
"Госкорпорация «Роскосмос»",
)
self.assertEqual(response.data["registry_category"], "other")
self.assertEqual(response.data["registry_category_label"], "Прочие")
self.assertEqual(
response.data["general_director"]["full_name"],
"Иванов Иван Иванович",
@@ -189,6 +193,53 @@ class OrganizationApiTest(APITestCase):
self.assertEqual(len(response.data["results"]), 1)
self.assertEqual(response.data["results"][0]["id"], str(rosatom_org.id))
def test_registry_category_filters_support_snake_and_camel_case(self):
opk_org = OrganizationFactory.create(name="АО ОПК")
goz_org = OrganizationFactory.create(name="АО ГОЗ", in_275_fz_registry=True)
other_org = OrganizationFactory.create(name="АО Прочие")
opk_register = Register.objects.create(name="Реестр предприятий ОПК")
goz_register = Register.objects.create(name="Реестр госкорпорации Росатом ГОЗ")
upload = RegisterUpload.objects.create(
registry=opk_register,
actual_date=date(2026, 4, 1),
file_name="registry-category.xlsx",
file_hash="registry-category-hash",
rows_count=2,
)
goz_upload = RegisterUpload.objects.create(
registry=goz_register,
actual_date=date(2026, 4, 1),
file_name="registry-category-goz.xlsx",
file_hash="registry-category-goz-hash",
rows_count=1,
)
RegistryMembershipPeriod.objects.create(
registry=opk_register,
organization=opk_org,
started_at=date(2026, 4, 1),
started_by_upload=upload,
)
RegistryMembershipPeriod.objects.create(
registry=goz_register,
organization=goz_org,
started_at=date(2026, 4, 1),
started_by_upload=goz_upload,
)
response = self.client.get("/api/v1/organizations/?registry_category=goz")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual([item["id"] for item in response.data["results"]], [str(goz_org.id)])
response = self.client.get("/api/v1/organizations/?registryCategory=opk")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual([item["id"] for item in response.data["results"]], [str(opk_org.id)])
response = self.client.get("/api/v1/organizations/?registry_category=other")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn(str(other_org.id), [item["id"] for item in response.data["results"]])
class OrganizationDictionaryApiTest(APITestCase):
"""Tests for organization dictionaries endpoints."""