"""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]