130 lines
4.0 KiB
Python
130 lines
4.0 KiB
Python
"""AgroStar табличные форматы (PDF, xlsx) → те же AgrostarSample, что и XML."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
from app.lab.etl.agrostar_xml_import import (
|
|
AgrostarParseResult,
|
|
AgrostarSample,
|
|
_parse_float,
|
|
_pct_dm_to_g_per_kg_sv,
|
|
finalize_agrostar_sample,
|
|
)
|
|
|
|
# Подпись в отчёте → ключ AgroStar XML (дальше — общий _AGROSTAR_MAP)
|
|
_TABULAR_LABEL_TO_AGRO: dict[str, str] = {
|
|
"Сухое вещество (DM)": "DM",
|
|
"Сырой протеин (Crude Protein)": "CP",
|
|
"КДК (ADF)": "ADF",
|
|
"НДК (aNDF)": "NDF",
|
|
"НДК по орг. веществу (aNDFom)": "aNDFom",
|
|
"Сырой жир (Fat EE)": "Fat_EE",
|
|
"Сырая зола (Ash)": "Ash",
|
|
"Кальций (Ca)": "Ca",
|
|
"Фосфор (P)": "P",
|
|
"Магний (Mg)": "Mg",
|
|
"Калий (K)": "K",
|
|
"Сера (S)": "S",
|
|
"Хлор (Cl)": "Cl",
|
|
"Водорастворимый сахар (WSC)": "Sugar_WSC",
|
|
"Cпирторастворимый сахар (ESC)": "Sugar_ESC",
|
|
"Крахмал (Starch)": "Starch",
|
|
"Безволокнистые углеводы (NFC)": "NFC",
|
|
"Переваримые питательные вещества (TDN)": "TDN",
|
|
"НДК протеин (ND-ICP)": "NDICP_CP",
|
|
"Лизин": "Lys",
|
|
"Метионин": "Met",
|
|
"Лейцин": "Leu",
|
|
"Изолейцин": "Ile",
|
|
"Валин": "Val",
|
|
"Усваиваемость НДК 30 ч (NDFD30)": "NDFDom_IV_30hr",
|
|
}
|
|
|
|
_SECTION_HEADERS = frozenset(
|
|
{
|
|
"Протеин:",
|
|
"Клетчатка:",
|
|
"Сахара и крахмал:",
|
|
"Жир и жирные кислоты:",
|
|
"Минералы:",
|
|
"Энергия:",
|
|
}
|
|
)
|
|
|
|
|
|
def map_tabular_label(label: str) -> str | None:
|
|
text = (label or "").strip()
|
|
if not text or text in _SECTION_HEADERS:
|
|
return None
|
|
if text in _TABULAR_LABEL_TO_AGRO:
|
|
return _TABULAR_LABEL_TO_AGRO[text]
|
|
lowered = text.lower()
|
|
for key, ag_key in sorted(_TABULAR_LABEL_TO_AGRO.items(), key=lambda item: len(item[0]), reverse=True):
|
|
if key.lower() in lowered:
|
|
return ag_key
|
|
return None
|
|
|
|
|
|
def tabular_rows_to_agro_fields(rows: list[tuple[str, str]]) -> dict[str, float]:
|
|
"""[(label, raw_value), ...] → raw_fields AgroStar."""
|
|
out: dict[str, float] = {}
|
|
for label, raw in rows:
|
|
ag_key = map_tabular_label(label)
|
|
if not ag_key:
|
|
continue
|
|
val = _parse_float(raw)
|
|
if val is not None:
|
|
out[ag_key] = val
|
|
return out
|
|
|
|
|
|
def sample_from_agro_fields(
|
|
*,
|
|
sample_no: str,
|
|
farm_name: str,
|
|
farm_id: str = "",
|
|
date_printed: str = "",
|
|
feed_type: str = "Mixed haylage",
|
|
desc_1: str = "",
|
|
desc_2: str = "",
|
|
desc_3: str = "",
|
|
agro_fields: dict[str, float],
|
|
) -> AgrostarSample:
|
|
sample = AgrostarSample(
|
|
sample_no=sample_no,
|
|
farm_name=farm_name,
|
|
farm_id=farm_id,
|
|
date_printed=date_printed,
|
|
feed_type=feed_type,
|
|
desc_1=desc_1,
|
|
desc_2=desc_2,
|
|
desc_3=desc_3,
|
|
dry_matter_pct=agro_fields.get("DM"),
|
|
)
|
|
sample.raw_fields = {k: v for k, v in agro_fields.items() if k != "DM"}
|
|
return finalize_agrostar_sample(sample)
|
|
|
|
|
|
def parse_tabular_triplet_lines(lines: list[str]) -> list[tuple[str, str, str]]:
|
|
"""Строки PDF: name, unit (%DM|%CP|%), value."""
|
|
rows: list[tuple[str, str, str]] = []
|
|
i = 0
|
|
units = frozenset({"%DM", "%CP", "%"})
|
|
while i < len(lines):
|
|
line = lines[i]
|
|
if line in units and i >= 1:
|
|
name = lines[i - 1]
|
|
val = lines[i + 1] if i + 1 < len(lines) else ""
|
|
if val and (val[0].isdigit() or val.startswith("<")):
|
|
rows.append((name, line, val))
|
|
i += 2
|
|
continue
|
|
i += 1
|
|
return rows
|
|
|
|
|
|
def rows_to_label_values(triplets: list[tuple[str, str, str]]) -> list[tuple[str, str]]:
|
|
return [(name, val) for name, _unit, val in triplets]
|