fix: align reporting forms and demo analytics
All checks were successful
CI/CD Pipeline / Code Quality Checks (push) Successful in 2m21s
CI/CD Pipeline / Run Tests (push) Successful in 4m23s
CI/CD Pipeline / Build and Push Dev Images (push) Successful in 32s
CI/CD Pipeline / Deploy Dev via Compose (push) Successful in 52s

This commit is contained in:
2026-07-28 11:07:47 +02:00
parent ff0b2bdfca
commit df0f41b9c0
31 changed files with 1627 additions and 328 deletions

View File

@@ -0,0 +1,41 @@
"""Integration checks for the versioned Excel demo-period fixtures."""
import re
from pathlib import Path
from apps.form_3.services import FormF3Parser
from apps.form_4.services import FormF4Parser
from apps.form_6.services import FormF6Parser
from django.test import TestCase
DEMO_DIR = Path(__file__).resolve().parents[3] / "input" / "demo-periods"
class DemoPeriodWorkbooksTest(TestCase):
def test_form_f3_workbooks_match_current_schema(self):
for path in sorted(DEMO_DIR.glob("Ф-3_*.xlsx")):
year = int(re.search(r"_(\d{4})", path.name).group(1))
with self.subTest(path=path.name), path.open("rb") as file:
result = FormF3Parser(report_year=year).parse(file)
self.assertEqual(result.loaded_count, 20)
self.assertEqual(result.skipped_count, 0)
def test_form_f4_workbooks_match_current_schema(self):
for path in sorted(DEMO_DIR.glob("Ф-4_*.xlsx")):
match = re.search(r"_(\d{4})-H([12])", path.name)
year, half_year = map(int, match.groups())
with self.subTest(path=path.name), path.open("rb") as file:
result = FormF4Parser(
report_year=year,
report_half_year=half_year,
).parse(file)
self.assertEqual(result.loaded_count, 20)
self.assertEqual(result.skipped_count, 0)
def test_form_f6_workbooks_are_structurally_consistent(self):
for path in sorted(DEMO_DIR.glob("Ф-6_*.xlsx")):
year = int(re.search(r"_(\d{4})", path.name).group(1))
with self.subTest(path=path.name), path.open("rb") as file:
result = FormF6Parser(report_year=year).parse(file)
self.assertEqual(result.loaded_count, 20)
self.assertEqual(result.skipped_count, 0)

View File

@@ -59,6 +59,51 @@ class GenerateTestReportsCommandTest(TestCase):
self.assertEqual(PublicProcurement.objects.count(), 3)
self.assertEqual(ArbitrationCase.objects.count(), 3)
for record in FormF3Record.objects.all():
self.assertEqual(
sum(
(
record.employees_under_20,
record.employees_20_29,
record.employees_30_39,
record.employees_40_49,
record.employees_50_59,
record.employees_over_60,
)
),
int(record.avg_employees),
)
self.assertEqual(
record.machine_tools_and_equipment,
record.total_equipment,
)
for record in FormF6Record.objects.all():
self.assertEqual(
sum(
(
record.age_under_5,
record.age_5_10,
record.age_10_15,
record.age_15_20,
record.age_over_20,
)
),
record.total_equipment,
)
self.assertEqual(
sum(
(
record.age_under_5_imported,
record.age_5_10_imported,
record.age_10_15_imported,
record.age_15_20_imported,
record.age_over_20_imported,
)
),
record.imported_equipment,
)
self.assertIn(
"Ф-1: создано 12 записей, активных 9, архивных 3", stdout.getvalue()
)

View File

@@ -1,7 +1,12 @@
"""Tests for FormF3 services."""
from decimal import Decimal
from io import BytesIO
from apps.form_3.models import FormF3Record
from apps.form_3.services import FormF3Parser, FormF3Service
from django.test import TestCase
from openpyxl import Workbook
from tests.apps.organization.factories import OrganizationFactory
@@ -48,6 +53,72 @@ class FormF3ServiceTest(TestCase):
class FormF3ParserTest(TestCase):
"""Tests for FormF3Parser."""
@staticmethod
def _current_template_file() -> BytesIO:
headers = [
"Наименование организации",
"ОКПО",
"ОГРН",
"ИНН",
"Отгруженные товары собственного производства, выполненные работы и услуги собственными силами в фактических ценах - всего",
"Отгруженные товары собственного производства, выполненные работы и услуги собственными силами в фактических ценах, из них инновационные товары, работы, услуги",
"Всего основных фондов (без незавершенных активов и не включая земельные участки и объекты природопользования)",
"Станочный парк и оборудование, всего по предприятию",
"Станочный парк и оборудование - импортное оборудование",
"Оборудование с возрастом от 5 до 10 лет",
"Оборудование с возрастом до 5 лет",
"Фактический (физический) износ оборудования, %",
"Средний уровень загрузки производственных мощностей (всего)",
"Средний уровень загрузки производственных мощностей военного производства",
"Средний возраст сотрудников",
"Количество работников предприятия до 20 лет",
"Количество работников предприятия 20-29 лет",
"Количество работников предприятия 30-39 лет",
"Количество работников предприятия 40-49 лет",
"Количество работников предприятия 50-59 лет",
"Количество работников предприятия старше 60 лет",
]
workbook = Workbook()
sheet = workbook.active
sheet.append(headers)
sheet.append(
[None, None, None, None]
+ ["тыс. руб."] * 3
+ ["ед."] * 4
+ ["%"] * 3
+ ["лет"]
+ ["чел."] * 6
)
sheet.append(
[
"Тестовая организация Ф-3",
"90000001",
"1267700000017",
"3234567890",
352,
222,
85,
46,
17,
13,
14,
21,
79,
65,
42,
4,
18,
28,
26,
17,
7,
]
)
file = BytesIO()
workbook.save(file)
file.seek(0)
return file
def test_get_column_mappings_returns_mappings(self):
"""Test get_column_mappings returns correct mappings."""
parser = FormF3Parser(report_year=2026, report_quarter=1)
@@ -57,8 +128,24 @@ class FormF3ParserTest(TestCase):
self.assertTrue(len(mappings) > 0)
field_names = [m.field_name for m in mappings]
self.assertIn("avg_employees", field_names)
self.assertIn("total_equipment", field_names)
self.assertIn("shipped_goods_total", field_names)
self.assertIn("machine_tools_and_equipment", field_names)
self.assertIn("average_employee_age", field_names)
self.assertNotIn("avg_employees", field_names)
def test_parse_current_template_maps_equipment_and_personnel_columns(self):
OrganizationFactory.create(inn="3234567890")
parser = FormF3Parser(report_year=2026, report_quarter=1)
result = parser.parse(self._current_template_file())
self.assertEqual(result.loaded_count, 1)
record = FormF3Record.objects.get()
self.assertEqual(record.shipped_goods_total, Decimal("352"))
self.assertEqual(record.machine_tools_and_equipment, 46)
self.assertEqual(record.imported_equipment, 17)
self.assertEqual(record.average_employee_age, Decimal("42"))
self.assertEqual(record.employees_30_39, 28)
def test_create_record_uses_existing_organization(self):
"""Report imports reuse existing organizations when available."""
@@ -69,8 +156,8 @@ class FormF3ParserTest(TestCase):
row_data = {
"inn": "3234567890",
"name": "Тестовая организация Ф-3",
"avg_employees": 100,
"total_equipment": 50,
"machine_tools_and_equipment": 50,
"average_employee_age": 42,
}
record = parser.create_record(row_data)

View File

@@ -1,7 +1,12 @@
"""Tests for FormF4 services."""
from decimal import Decimal
from io import BytesIO
from apps.form_4.models import FormF4Record
from apps.form_4.services import FormF4Parser, FormF4Service
from django.test import TestCase
from openpyxl import Workbook
from tests.apps.organization.factories import OrganizationFactory
@@ -48,6 +53,47 @@ class FormF4ServiceTest(TestCase):
class FormF4ParserTest(TestCase):
"""Tests for FormF4Parser."""
@staticmethod
def _current_template_file() -> BytesIO:
headers = [
"Наименование организации",
"ОКПО",
"ОГРН",
"ИНН",
"Выручка, в соответствии с РСБУ",
"Выручка, в соответствии с МСФО",
"Чистая прибыль (убыток), в соответствии с РСБУ",
"Чистая прибыль (убыток), в соответствии с МСФО",
"Кредиты и займы, в соответствии с РСБУ",
"Кредиты и займы, в соответствии с МСФО",
"EBITDA, в соответствии с РСБУ",
"EBITDA, в соответствии с МСФО",
]
workbook = Workbook()
sheet = workbook.active
sheet.append(headers)
sheet.append([None, None, None, None] + ["тыс. руб."] * 8)
sheet.append(
[
"Тестовая организация Ф-4",
"90000001",
"1267700000017",
"4234567890",
477807,
499308,
442414,
462322,
42047,
43729,
138564,
62115,
]
)
file = BytesIO()
workbook.save(file)
file.seek(0)
return file
def test_get_column_mappings_returns_mappings(self):
"""Test get_column_mappings returns correct mappings."""
parser = FormF4Parser(report_year=2026, report_half_year=1)
@@ -59,6 +105,20 @@ class FormF4ParserTest(TestCase):
field_names = [m.field_name for m in mappings]
self.assertIn("revenue_rsbu", field_names)
self.assertIn("net_profit_rsbu", field_names)
self.assertIn("ebitda_rsbu", field_names)
def test_parse_current_template_maps_profit_loans_and_ebitda_columns(self):
OrganizationFactory.create(inn="4234567890")
parser = FormF4Parser(report_year=2025, report_half_year=2)
result = parser.parse(self._current_template_file())
self.assertEqual(result.loaded_count, 1)
record = FormF4Record.objects.get()
self.assertEqual(record.revenue_rsbu, Decimal("477807"))
self.assertEqual(record.net_profit_rsbu, Decimal("442414"))
self.assertEqual(record.loans_rsbu, Decimal("42047"))
self.assertEqual(record.ebitda_rsbu, Decimal("138564"))
def test_create_record_uses_existing_organization(self):
"""Report imports reuse existing organizations when available."""

View File

@@ -1,7 +1,12 @@
"""Tests for FormF6 services."""
from io import BytesIO
from apps.core.excel import ExcelValidationError
from apps.form_6.models import FormF6Record
from apps.form_6.services import FormF6Parser, FormF6Service
from django.test import TestCase
from openpyxl import Workbook
from tests.apps.organization.factories import OrganizationFactory
@@ -48,6 +53,82 @@ class FormF6ServiceTest(TestCase):
class FormF6ParserTest(TestCase):
"""Tests for FormF6Parser."""
@staticmethod
def _current_template_file(
*,
total_equipment: int = 100,
age_under_5: int | float = 20,
total_header: str = "Всего",
) -> BytesIO:
workbook = Workbook()
sheet = workbook.active
row_1 = [None] * 38
row_2 = [None] * 38
row_3 = [None] * 38
row_4 = [None] * 38
row_1[:6] = [
"Наименование организации",
"ОКПО",
"ОГРН",
"ИНН",
"Код строки",
"Категория",
]
row_1[6] = "Количество оборудования на конец года ед."
row_1[10] = "Из установленного оборудования оборудование в возрасте, единиц"
row_1[35:38] = [
"Средняя сменность работы",
"Количество рабочих мест без оборудования",
"Примечание",
]
row_2[10], row_2[15], row_2[20], row_2[25], row_2[30] = (
"До 5 лет",
"От 5 до 10 лет",
"От 10 до 15 лет",
"От 15 до 20 лет",
"Свыше 20 лет",
)
row_3[6:10] = [
total_header,
"Введенное в эксплуатацию в отчетном году",
"Выведенное из эксплуатации в отчетном году",
"Импортное оборудование",
]
for start in (10, 15, 20, 25, 30):
row_3[start : start + 5] = [
"Всего",
"Импортного оборудования",
"Коэффициент использования оборудования в производстве",
"Доля фактического времени работы оборудования, использованного для производства ПВН, %",
"Средневзвешенный коэффициент загрузки",
]
for row in (row_1, row_2, row_3, row_4):
sheet.append(row)
ages = [age_under_5, 25, 20, 20, 15]
imported = [4, 5, 4, 4, 3]
values = [
"Тестовая организация Ф-6",
"90000001",
"1267700000017",
"6234567890",
"101",
"Основное технологическое оборудование",
total_equipment,
12,
7,
sum(imported),
]
for total, imported_total in zip(ages, imported, strict=True):
values.extend([total, imported_total, 75, 20, 0.8])
values.extend([1.5, 4, "Тестовые согласованные данные"])
sheet.append(values)
file = BytesIO()
workbook.save(file)
file.seek(0)
return file
def test_get_column_mappings_returns_mappings(self):
"""Test get_column_mappings returns correct mappings."""
parser = FormF6Parser(report_year=2026, report_quarter=1)
@@ -59,6 +140,41 @@ class FormF6ParserTest(TestCase):
field_names = [m.field_name for m in mappings]
self.assertIn("row_code", field_names)
self.assertIn("total_equipment", field_names)
self.assertIn("commissioned_equipment", field_names)
self.assertIn("age_over_20_imported", field_names)
def test_parse_current_template_maps_all_age_buckets(self):
OrganizationFactory.create(inn="6234567890")
parser = FormF6Parser(report_year=2026, report_quarter=1)
result = parser.parse(self._current_template_file())
self.assertEqual(result.loaded_count, 1)
record = FormF6Record.objects.get()
self.assertEqual(record.total_equipment, 100)
self.assertEqual(record.commissioned_equipment, 12)
self.assertEqual(record.decommissioned_equipment, 7)
self.assertEqual(record.age_under_5, 20)
self.assertEqual(record.age_over_20, 15)
self.assertEqual(record.age_over_20_imported, 3)
def test_rejects_non_integral_equipment_counts_before_creating_records(self):
OrganizationFactory.create(inn="6234567890")
parser = FormF6Parser(report_year=2026, report_quarter=1)
with self.assertRaises(ExcelValidationError):
parser.parse(self._current_template_file(age_under_5=20.5))
self.assertFalse(FormF6Record.objects.exists())
def test_rejects_incompatible_template_before_creating_records(self):
OrganizationFactory.create(inn="6234567890")
parser = FormF6Parser(report_year=2026, report_quarter=1)
with self.assertRaises(ExcelValidationError):
parser.parse(self._current_template_file(total_header="Импортное"))
self.assertFalse(FormF6Record.objects.exists())
def test_create_record_uses_existing_organization(self):
"""Report imports reuse existing organizations when available."""

View File

@@ -41,6 +41,8 @@ class OrganizationAnalyticsApiTest(APITestCase):
organization=self.organization,
report_year=2026,
report_quarter=1,
report_month=6,
avg_employees=1050,
avg_payroll_employees=995,
payroll_fund=Decimal("1000000.00"),
military_output_actual=Decimal("11000000.00"),
@@ -56,6 +58,8 @@ class OrganizationAnalyticsApiTest(APITestCase):
organization=self.organization,
report_year=2025,
report_quarter=1,
report_month=12,
avg_employees=1020,
avg_payroll_employees=970,
payroll_fund=Decimal("900000.00"),
military_output_actual=Decimal("9000000.00"),
@@ -88,11 +92,19 @@ class OrganizationAnalyticsApiTest(APITestCase):
FormF3RecordFactory.create(
organization=self.organization,
report_year=2026,
avg_employees=1050,
production_workers=620,
engineering_workers=210,
administrative_workers=220,
workers_needed=35,
avg_employees=None,
production_workers=None,
engineering_workers=None,
administrative_workers=None,
workers_needed=None,
average_employee_age=Decimal("42.00"),
employees_under_20=4,
employees_20_29=18,
employees_30_39=28,
employees_40_49=26,
employees_50_59=17,
employees_over_60=7,
machine_tools_and_equipment=46,
total_equipment=187,
domestic_equipment=91,
imported_equipment=96,
@@ -109,14 +121,16 @@ class OrganizationAnalyticsApiTest(APITestCase):
FormF3RecordFactory.create(
organization=self.organization,
report_year=2025,
avg_employees=1020,
avg_employees=None,
average_employee_age=Decimal("41.00"),
)
FormF4RecordFactory.create(
organization=self.organization,
report_year=2026,
revenue_rsbu=Decimal("1100000000.00"),
net_profit_rsbu=Decimal("320000000.00"),
ebitda_rsbu=Decimal("480000000.00"),
report_half_year=2,
revenue_rsbu=Decimal("477807.00"),
net_profit_rsbu=Decimal("442414.00"),
ebitda_rsbu=Decimal("138564.00"),
gross_profit_rsbu=Decimal("520000000.00"),
operating_profit_rsbu=Decimal("300000000.00"),
net_debt_rsbu=Decimal("200000000.00"),
@@ -131,9 +145,10 @@ class OrganizationAnalyticsApiTest(APITestCase):
FormF4RecordFactory.create(
organization=self.organization,
report_year=2025,
revenue_rsbu=Decimal("980000000.00"),
net_profit_rsbu=Decimal("250000000.00"),
ebitda_rsbu=Decimal("410000000.00"),
report_half_year=2,
revenue_rsbu=Decimal("450000.00"),
net_profit_rsbu=Decimal("400000.00"),
ebitda_rsbu=Decimal("120000.00"),
ros=Decimal("21.00"),
roa=Decimal("10.50"),
roe=Decimal("14.10"),
@@ -152,14 +167,31 @@ class OrganizationAnalyticsApiTest(APITestCase):
organization=self.organization,
report_year=2026,
category="Станочное оборудование",
total_equipment=54,
domestic_equipment=31,
imported_equipment=23,
total_equipment=187,
domestic_equipment=None,
imported_equipment=96,
commissioned_equipment=12,
decommissioned_equipment=7,
age_under_5=70,
age_under_5_imported=35,
age_under_5_utilization_rate=Decimal("92.00"),
age_under_5_lease_share_itn_percent=Decimal("20.00"),
age_5_10=41,
age_5_10_imported=21,
age_5_10_utilization_rate=Decimal("92.00"),
age_5_10_lease_share_itn_percent=Decimal("20.00"),
age_10_15=33,
age_10_15_imported=17,
age_10_15_utilization_rate=Decimal("92.00"),
age_10_15_lease_share_itn_percent=Decimal("20.00"),
age_15_20=22,
age_15_20_imported=12,
age_15_20_utilization_rate=Decimal("92.00"),
age_15_20_lease_share_itn_percent=Decimal("20.00"),
age_over_20=21,
age_over_20_imported=11,
age_over_20_utilization_rate=Decimal("92.00"),
age_over_20_lease_share_itn_percent=Decimal("20.00"),
physical_wear_percent=Decimal("28.40"),
utilization_rate=Decimal("92.00"),
avg_shift_work=Decimal("1.80"),
@@ -231,14 +263,24 @@ class OrganizationAnalyticsApiTest(APITestCase):
self.assertEqual(response.data["organization_id"], str(self.organization.id))
self.assertEqual(response.data["group"], "efficiency")
self.assertEqual(response.data["periods"], [2025, 2026])
self.assertEqual(
response.data["report_periods"],
[
{"year": 2025, "report_half_year": 2},
{"year": 2026, "report_half_year": 2},
],
)
self.assertEqual(
response.data["kpis"].keys(),
{"revenue", "ebitda", "net_profit", "revenue_per_employee"},
)
self.assertGreater(response.data["kpis"]["revenue_per_employee"]["value"], 0)
self.assertEqual(response.data["kpis"]["revenue"]["value"], 477807)
self.assertEqual(response.data["kpis"]["net_profit"]["value"], 442414)
self.assertEqual(response.data["kpis"]["ebitda"]["value"], 138564)
self.assertEqual(response.data["kpis"]["revenue_per_employee"]["value"], 455)
self.assertEqual(
response.data["kpis"]["revenue_per_employee"]["unit"],
"rub_per_employee",
"rub_thousands_per_employee",
)
for kpi in response.data["kpis"].values():
self.assertIn("previous_value", kpi)
@@ -251,7 +293,7 @@ class OrganizationAnalyticsApiTest(APITestCase):
self.assertIn("unit", series)
self.assertIn("points", series)
expected_unit = (
"rub_per_employee"
"rub_thousands_per_employee"
if series["metric"] == "revenue_per_employee"
else "rub_thousands"
)
@@ -283,10 +325,43 @@ class OrganizationAnalyticsApiTest(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["periods"], [])
self.assertEqual(response.data["report_periods"], [])
self.assertEqual(response.data["kpis"], {})
self.assertFalse(response.data["data_available"])
self.assertEqual(response.data["message"], "Данные отсутствуют")
def test_economics_normalizes_f2_rubles_to_thousands_without_f4(self):
organization = OrganizationFactory.create()
FormF1RecordFactory.create(
organization=organization,
report_year=2026,
report_month=6,
avg_employees=100,
)
FormF2RecordFactory.create(
organization=organization,
report_year=2026,
revenue=Decimal("1200000.00"),
net_profit=Decimal("300000.00"),
ebitda=Decimal("480000.00"),
)
response = self.client.get(
f"/api/v1/organizations/{organization.id}/analytics/economics/"
"?group=efficiency&from_year=2026&to_year=2026"
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["kpis"]["revenue"]["value"], 1200)
self.assertEqual(
response.data["kpis"]["revenue_per_employee"]["value"],
12,
)
self.assertEqual(
response.data["kpis"]["revenue"]["unit"],
"rub_thousands",
)
def test_personnel_contract(self):
personnel_response = self.client.get(
f"/api/v1/organizations/{self.organization.id}/analytics/personnel/"
@@ -297,6 +372,7 @@ class OrganizationAnalyticsApiTest(APITestCase):
personnel_response.data["organization_id"], str(self.organization.id)
)
self.assertEqual(personnel_response.data["report_year"], 2026)
self.assertEqual(personnel_response.data["report_month"], 6)
self.assertEqual(
personnel_response.data["headcount"]["average_employees"],
1050,
@@ -306,6 +382,7 @@ class OrganizationAnalyticsApiTest(APITestCase):
set(personnel_response.data["history"][0]),
{
"year",
"report_month",
"average_employees",
"avg_payroll_employees",
"average_age",
@@ -313,15 +390,22 @@ class OrganizationAnalyticsApiTest(APITestCase):
},
)
self.assertEqual(len(personnel_response.data["age_distribution"]), 3)
self.assertIn("average_age", personnel_response.data)
self.assertEqual(personnel_response.data["average_age"], 42.0)
self.assertEqual(
personnel_response.data["headcount"]["avg_payroll_employees"], 995
)
self.assertEqual(personnel_response.data["headcount"]["payroll_fund"], 1000000)
self.assertEqual(
personnel_response.data["headcount"]["payroll_fund"], 1000000000
)
self.assertIsNone(personnel_response.data["headcount"]["production_workers"])
self.assertEqual(
personnel_response.data["age_distribution"][0]["age_group"],
"under_30",
)
self.assertEqual(
personnel_response.data["age_distribution"][0]["employees_count"],
22,
)
self.assertIn("employees_count", personnel_response.data["age_distribution"][0])
def test_yearly_analytics_use_latest_available_year_when_requested_year_is_empty(
@@ -374,15 +458,19 @@ class OrganizationAnalyticsApiTest(APITestCase):
},
)
self.assertEqual(response.data["summary"]["total_equipment"], 187)
self.assertEqual(response.data["summary"]["machine_tools_and_equipment"], 46)
self.assertIsNone(response.data["summary"]["domestic_equipment"])
self.assertEqual(response.data["summary"]["imported_equipment"], 96)
self.assertEqual(response.data["summary"]["physical_wear_percent"], 32.0)
self.assertEqual(response.data["summary"]["weighted_wear_percent"], 32.0)
self.assertIsNone(response.data["summary"]["weighted_wear_percent"])
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["summary"]["commissioned_equipment"], 12)
self.assertEqual(response.data["summary"]["decommissioned_equipment"], 7)
self.assertEqual(
response.data["age_distribution"][0]["bucket"], "under_5_years"
)
self.assertEqual(len(response.data["age_distribution"]), 5)
self.assertEqual(response.data["age_distribution"][0]["imported_equipment"], 35)
self.assertEqual(
set(response.data["age_distribution"][0]),
{
@@ -400,13 +488,13 @@ class OrganizationAnalyticsApiTest(APITestCase):
response.data["categories"][0],
{
"category": "Станочное оборудование",
"total_equipment": 54,
"domestic_equipment": 31,
"imported_equipment": 23,
"physical_wear_percent": 28.4,
"weighted_wear_percent": 28.4,
"total_equipment": 187,
"domestic_equipment": None,
"imported_equipment": 96,
"physical_wear_percent": None,
"weighted_wear_percent": None,
"utilization_rate": 0.92,
"lease_share_itn_percent": None,
"lease_share_itn_percent": 20.0,
},
)