Загрузка данных


import builtins
import json
import os
import sys
import logging
import traceback
import requests
import uuid
from datetime import datetime, timedelta
from calendar import monthrange
from io import StringIO
import csv
import time
import gc
import ctypes
from lxml.builder import E
from lxml import etree

from umdap_config import LOG_FOLDER, ENV_FOLDER, EROUTER_HOST, PRIMARY_MADAB, UMDAP_ETL_URL_LIST

APPLICATION_NAME = 'externalGTT'
LOG_FILE = os.path.join(LOG_FOLDER, "externalGTT_test.log")

BASE_URL = 'https://www.globaltradetracker.com/api/rest'
builtins.BASE_URL = BASE_URL
builtins.XML_DATE_FMT = '%d.%m.%Y %H:%M:%S'
builtins.DELIMITER = ','

# === НАСТРОЙКИ ===
TARGET_ROWS = 100000          # сколько строк спарсить
OUTPUT_XML = 'test.xml'       # куда сохранить XML
CSV_TEMP = 'test_batch.csv'   # временный CSV

session = requests.Session()
session.headers.update({
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Accept': 'application/json, text/plain, */*',
    'Accept-Language': 'en-US,en;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'Connection': 'keep-alive',
})

# ID приложения
APPLICATION_NAME = 'externalGTT'


def log(msg=''):
    """Логирование"""
    str_with_time = f'{datetime.now().strftime("%Y/%m/%d %H:%M:%S")} {msg}'
    print(str_with_time)
    with open(LOG_FILE, "a", encoding="utf-8") as file:
        file.write(f'{str_with_time}\n')

builtins.log = log


def generate_id():
    """Генерация 32-символьного ID"""
    return uuid.uuid4().hex


def get_token():
    """Получение токена GTT"""
    try:
        with open('config.json', 'r') as f:
            credentials = json.load(f)
        link = f"{BASE_URL}/gettoken?userid={credentials['user_id']}&password={credentials['password']}"
        response = session.get(link, timeout=30)
        if response.status_code == 200:
            token = response.content.decode().strip()
            if token:
                log("Token received")
                return token
        return None
    except Exception as e:
        log(f"ERROR get_token: {e}")
        return None


def get_subscription_countries(token):
    """Получение списка стран"""
    try:
        response = session.get(f"{BASE_URL}/countries?token={token}", timeout=30)
        if response.status_code == 200:
            data = response.json()
            countries = []
            if isinstance(data, list):
                for item in data:
                    if isinstance(item, dict):
                        country_data = item.get('country', item)
                        if country_data.get('reportingcountry') == 'true' and country_data.get('defaultReporter') == True:
                            code = country_data.get('reportercode', '')
                            if code:
                                countries.append(code)
            countries = list(dict.fromkeys(countries))
            log(f"Available reporters: {len(countries)}")
            return countries
        return []
    except Exception as e:
        log(f"ERROR get_countries: {e}")
        return []


def parse_period(year, month):
    """Период → YYYY-MM-DD"""
    try:
        year = int(year)
        month = int(month)
        last_day = monthrange(year, month)[1]
        return datetime(year, month, last_day).strftime('%Y-%m-%d')
    except:
        return '2000-01-01'


def parse_record_to_dict(record, reporter_code="", trade_flow=""):
    """Парсинг записи в словарь"""
    try:
        if not isinstance(record, dict):
            return None
        year = record.get('year', 2000)
        month = record.get('month', 1)
        return {
            'ID': generate_id(),
            'Reporter_Code': record.get('reporterCode', reporter_code),
            'Trade_Flow': record.get('tradeFlow', trade_flow),
            'Is_Mirror_Data': record.get('isMirror', ''),
            'Period': parse_period(year, month),
            'Reporter_Name': record.get('reporterName', ''),
            'Reporter_Description': record.get('reporterDescription', '')[:120],
            'Reporter_Source': record.get('reporterSource', '')[:120],
            'Incoterm': record.get('incoterm', '')[:60],
            'Partner_Code': record.get('partnerCode', '')[:60],
            'Partner_Name': record.get('partnerName', '')[:120],
            'Commodity_HS_Code': record.get('hsCode', '')[:60],
            'Commodity_Description': record.get('commodityDescriptionOriginal', '')[:250],
            'HS6_Code': record.get('hs6Code', '')[:60],
            'HS6_Code_Description': record.get('hs6DescriptionOriginal', '')[:250],
            'Subdivision': record.get('subdivision', '')[:120],
            'Port': record.get('port', '')[:120],
            'Transport': record.get('transport', '')[:120],
            'Foreign_Port': record.get('foreignPort', '')[:120],
            'US_State': record.get('usState', '')[:120],
            'Customs_Regime': record.get('customsRegime', '')[:120],
            'Suppression': record.get('suppression', '')[:3900],
            'Monetary_Value': record.get('value', 0),
            'Currency': record.get('currency', 'USD')[:60],
            'Secondary_Monetary_Value': record.get('value2', 0),
            'Secondary_Currency': record.get('currency2', '')[:60],
            'Primary_Quantity': record.get('quantity1', 0),
            'Primary_Quantity_Unit': record.get('unit1', '')[:60],
            'Secondary_Quantity': record.get('quantity2', 0),
            'Secondary_Quantity_Unit': record.get('unit2', '')[:60],
            'Primary_Quantity_Price': record.get('price1', 0),
            'Primary_Quantity_Price_Unit': record.get('priceunit1', '')[:60],
            'Secondary_Quantity_Price': record.get('price2', 0),
            'Secondary_Quantity_Price_Unit': record.get('priceunit2', '')[:60],
        }
    except Exception as e:
        log(f"ERROR parse: {e}")
        return None


def parse_data_to_rows(data, reporter_code="", trade_flow=""):
    """Парсит data в список словарей"""
    rows = []

    if isinstance(data, list):
        records = data
    elif isinstance(data, dict):
        if 'reportData' in data:
            records = data['reportData']
        elif 'data' in data:
            records = data['data']
        else:
            return rows
    else:
        return rows

    for record in records:
        if isinstance(record, dict):
            parsed = parse_record_to_dict(record, reporter_code, trade_flow)
            if parsed:
                rows.append(parsed)

    return rows


def save_csv(rows, filepath):
    """Сохранить строки в CSV (для проверки)"""
    if not rows:
        return
    with open(filepath, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=rows[0].keys())
        writer.writeheader()
        writer.writerows(rows)
    log(f"CSV saved: {filepath} ({len(rows)} rows)")


def generate_xml(rows):
    """Генерирует XML из rows (как etl_common.py)"""
    xml_rows = []

    for row in rows:
        # Формируем <row><field>value</field>...</row>
        row_elems = []
        for key, value in row.items():
            # Заменяем пробелы и спецсимволы в тегах
            tag = str(key).replace(' ', '_')
            # Экранирование
            if value is None:
                value = ''
            row_elems.append(E(tag, str(value)))

        xml_rows.append(E.row(*row_elems))

    # Корневой элемент
    root = E.madabContent(
        E.type('GTT'),
        *xml_rows
    )

    return etree.tostring(root, encoding='utf-8', pretty_print=True)


def get_historical_data(token, countries, target_rows=100000):
    """Парсит target_rows строк"""

    log("=" * 60)
    log(f"Starting test parsing: {target_rows} rows")
    log("=" * 60)

    all_rows = []
    total_requests = 0
    hs_filter = '31,25'

    fields = 'reporterCode,tradeFlow,isMirror,year,month,reporterName,reporterDescription,reporterSource,incoterm,partnerCode,partnerName,hsCode,commodityDescriptionOriginal,hs6Code,hs6DescriptionOriginal,value,currency,value2,currency2,quantity1,unit1,quantity2,unit2,price1,priceunit1,price2,priceunit2,port,transport,subdivision'

    trade_details = 'PORT,SUBDIVISION,TRANSPORT,CUSTOMS_REGIME,FOREIGN_PORT,FOREIGN_SUBDIVISION'

    for country in countries:
        if len(all_rows) >= target_rows:
            log(f"Reached target rows: {len(all_rows)}")
            break

        for impexp in ['E', 'I']:
            if len(all_rows) >= target_rows:
                break

            try:
                params = {
                    'token': token,
                    'hscode': hs_filter,
                    'reporter': country,
                    'impexp': impexp,
                    'from': '2013-01',
                    'to': '2026-08',
                    'layout': 'brief',
                    'hslevel': '6',
                    'field': fields,
                    'tradedetails': trade_details,
                    'currency2': 'EUR',
                    'decimalscale': '2',
                    'format': 'json'
                }

                total_requests += 1

                start = time.time()
                response = session.get(f"{BASE_URL}/getreport", params=params, timeout=600)
                end = time.time()

                log(f"[{total_requests}] {country} {impexp} Status={response.status_code} Time={end-start:.1f}s Total rows={len(all_rows)}")

                if response.status_code == 200:
                    data = response.json()
                    new_rows = parse_data_to_rows(data, country, impexp)

                    # Добавляем только до target_rows
                    remaining = target_rows - len(all_rows)
                    if len(new_rows) > remaining:
                        new_rows = new_rows[:remaining]

                    all_rows.extend(new_rows)
                    log(f"  Added {len(new_rows)} rows, total: {len(all_rows)}")

            except Exception as e:
                log(f"✗ {str(e)}")
                continue

    return all_rows


def main():
    """Основная функция"""

    log('--------------------- TEST SCRIPT started ------------------------------')

    try:
        # 1. Токен
        token = get_token()
        if not token:
            log("ERROR: Failed to get token")
            sys.exit(1)

        # 2. Страны
        countries = get_subscription_countries(token)
        if not countries:
            log("ERROR: Failed to get countries")
            sys.exit(1)

        # 3. Парсим 100k строк
        log(f"Parsing {TARGET_ROWS} rows...")
        rows = get_historical_data(token, countries, TARGET_ROWS)

        if not rows:
            log("ERROR: No rows parsed")
            sys.exit(1)

        log(f"Parsed {len(rows)} rows")

        # 4. Сохраняем CSV (для проверки)
        save_csv(rows, CSV_TEMP)

        # 5. Генерируем XML (как etl_common.py)
        log("Generating XML...")
        xml_bytes = generate_xml(rows)

        # 6. Сохраняем XML
        with open(OUTPUT_XML, 'wb') as f:
            f.write(xml_bytes)

        size_mb = len(xml_bytes) / 1024 / 1024
        log(f"XML saved: {OUTPUT_XML}")
        log(f"XML size: {size_mb:.2f} MB")
        log(f"Rows in XML: {len(rows)}")

        log('--------------------- TEST SCRIPT finished -----------------------------')
        log(f"Now you can try: curl -X POST 'https://e-router.eurochem.ru/procedure/WHDEV/umdap.etl.content/call?application=externalGTT' --data-binary @{OUTPUT_XML} -k -v")

    except Exception as e:
        log(f'Error: {traceback.format_exception(*sys.exc_info())}')
        sys.exit(1)


if __name__ == "__main__":
    main()