Compare commits
No commits in common. "m.milnikov-patch-1" and "main" have entirely different histories.
m.milnikov
...
main
|
|
@ -0,0 +1,88 @@
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"source": "Таблица основных типов ISO-контейнеров (внешние/внутренние размеры, дверной проём, масса, объём)",
|
||||||
|
"usage": "Сверка п.21 FCL (проходимость груза в дверной проём 20DV, 40DV, 40HC) и оценка payload/объёма."
|
||||||
|
},
|
||||||
|
"types": {
|
||||||
|
"20DV": {
|
||||||
|
"label": "20' Standard (Dry Cube) — соответствует 20DV в критериях",
|
||||||
|
"external_mm": { "length": 6058, "width": 2438, "height": 2591 },
|
||||||
|
"internal_mm": { "length": 5905, "width": 2350, "height": 2381 },
|
||||||
|
"door_opening_mm": { "width": 2336, "height": 2291 },
|
||||||
|
"max_gross_kg": 30480,
|
||||||
|
"tare_kg": 2370,
|
||||||
|
"payload_kg": 28110,
|
||||||
|
"volume_m3": 33.2
|
||||||
|
},
|
||||||
|
"20HC": {
|
||||||
|
"label": "20' High Cube",
|
||||||
|
"external_mm": { "length": 6058, "width": 2438, "height": 2896 },
|
||||||
|
"internal_mm": { "length": 5905, "width": 2350, "height": 2693 },
|
||||||
|
"door_opening_mm": { "width": 2340, "height": 2597 },
|
||||||
|
"max_gross_kg": 30480,
|
||||||
|
"tare_kg": 2340,
|
||||||
|
"payload_kg": 28140,
|
||||||
|
"volume_m3": 37.5
|
||||||
|
},
|
||||||
|
"20HC_PW": {
|
||||||
|
"label": "20' High Cube Pallet Wide",
|
||||||
|
"external_mm": { "length": 6058, "width": 2484, "height": 2896 },
|
||||||
|
"internal_mm": { "length": 5898, "width": 2426, "height": 2698 },
|
||||||
|
"door_opening_mm": { "width": 2374, "height": 2585 },
|
||||||
|
"max_gross_kg": 30480,
|
||||||
|
"tare_kg": 2580,
|
||||||
|
"payload_kg": 27900,
|
||||||
|
"volume_m3": 38.6
|
||||||
|
},
|
||||||
|
"40DV": {
|
||||||
|
"label": "40' Standard (Dry Van) — соответствует 40DV в критериях",
|
||||||
|
"external_mm": { "length": 12192, "width": 2438, "height": 2591 },
|
||||||
|
"internal_mm": { "length": 12039, "width": 2350, "height": 2372 },
|
||||||
|
"door_opening_mm": { "width": 2336, "height": 2291 },
|
||||||
|
"max_gross_kg": 30480,
|
||||||
|
"tare_kg": 4000,
|
||||||
|
"payload_kg": 26480,
|
||||||
|
"volume_m3": 67.8
|
||||||
|
},
|
||||||
|
"40HC": {
|
||||||
|
"label": "40' High Cube — соответствует 40HC в критериях",
|
||||||
|
"external_mm": { "length": 12192, "width": 2438, "height": 2896 },
|
||||||
|
"internal_mm": { "length": 12039, "width": 2350, "height": 2693 },
|
||||||
|
"door_opening_mm": { "width": 2340, "height": 2597 },
|
||||||
|
"max_gross_kg": 32500,
|
||||||
|
"tare_kg": 4200,
|
||||||
|
"payload_kg": 28300,
|
||||||
|
"volume_m3": 76.5
|
||||||
|
},
|
||||||
|
"40HC_PW": {
|
||||||
|
"label": "40' High Cube Pallet Wide",
|
||||||
|
"external_mm": { "length": 12192, "width": 2500, "height": 2896 },
|
||||||
|
"internal_mm": { "length": 12039, "width": 2432, "height": 2693 },
|
||||||
|
"door_opening_mm": { "width": 2432, "height": 2597 },
|
||||||
|
"max_gross_kg": 35000,
|
||||||
|
"tare_kg": 4400,
|
||||||
|
"payload_kg": 30600,
|
||||||
|
"volume_m3": 79.3
|
||||||
|
},
|
||||||
|
"45HC_PW_A": {
|
||||||
|
"label": "45' High Cube Pallet Wide (Type A)",
|
||||||
|
"external_mm": { "length": 13716, "width": 2500, "height": 2750 },
|
||||||
|
"internal_mm": { "length": 13513, "width": 2444, "height": 2549 },
|
||||||
|
"door_opening_mm": { "width": 2416, "height": 2439 },
|
||||||
|
"max_gross_kg": 34000,
|
||||||
|
"tare_kg": 4180,
|
||||||
|
"payload_kg": 29820,
|
||||||
|
"volume_m3": 85.1
|
||||||
|
},
|
||||||
|
"45HC_PW_B": {
|
||||||
|
"label": "45' High Cube Pallet Wide (Type B)",
|
||||||
|
"external_mm": { "length": 13716, "width": 2500, "height": 2896 },
|
||||||
|
"internal_mm": { "length": 13513, "width": 2444, "height": 2670 },
|
||||||
|
"door_opening_mm": { "width": 2416, "height": 2580 },
|
||||||
|
"max_gross_kg": 34000,
|
||||||
|
"tare_kg": 5080,
|
||||||
|
"payload_kg": 28920,
|
||||||
|
"volume_m3": 89.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -416,7 +416,7 @@
|
||||||
"label": "Страна, город, адрес забора"
|
"label": "Страна, город, адрес забора"
|
||||||
},
|
},
|
||||||
"right": {
|
"right": {
|
||||||
"num": "18",
|
"num": "19",
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -483,17 +483,17 @@
|
||||||
{
|
{
|
||||||
"type": "pair",
|
"type": "pair",
|
||||||
"left": {
|
"left": {
|
||||||
"num": "19",
|
"num": "18",
|
||||||
"label": "Место таможенного оформления при экспорте из РФ"
|
"label": "Место таможенного оформления при экспорте из РФ"
|
||||||
},
|
},
|
||||||
"right": {
|
"right": {
|
||||||
"num": "20",
|
"num": "19",
|
||||||
"label": "Наличие Фумигации на деревянной таре при экспорте из РФ"
|
"label": "Наличие Фумигации на деревянной таре при экспорте из РФ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "left_12",
|
"type": "left_12",
|
||||||
"num": "21",
|
"num": "20",
|
||||||
"label": "Превышение габаритов дверного проема контейнеров 20DV, 40DV, 40HC"
|
"label": "Превышение габаритов дверного проема контейнеров 20DV, 40DV, 40HC"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -508,7 +508,7 @@
|
||||||
"label": "Страна, город, адрес забора"
|
"label": "Страна, город, адрес забора"
|
||||||
},
|
},
|
||||||
"right": {
|
"right": {
|
||||||
"num": "18",
|
"num": "19",
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -575,17 +575,17 @@
|
||||||
{
|
{
|
||||||
"type": "pair",
|
"type": "pair",
|
||||||
"left": {
|
"left": {
|
||||||
"num": "19",
|
"num": "18",
|
||||||
"label": "Место таможенного оформления при экспорте из РФ"
|
"label": "Место таможенного оформления при экспорте из РФ"
|
||||||
},
|
},
|
||||||
"right": {
|
"right": {
|
||||||
"num": "20",
|
"num": "19",
|
||||||
"label": "Наличие Фумигации на деревянной таре при экспорте из РФ"
|
"label": "Наличие Фумигации на деревянной таре при экспорте из РФ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "left_12",
|
"type": "left_12",
|
||||||
"num": "21",
|
"num": "20",
|
||||||
"label": "Превышение габаритов дверного проема контейнеров 20DV, 40DV, 40HC"
|
"label": "Превышение габаритов дверного проема контейнеров 20DV, 40DV, 40HC"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -600,7 +600,7 @@
|
||||||
"label": "Страна, город, адрес забора"
|
"label": "Страна, город, адрес забора"
|
||||||
},
|
},
|
||||||
"right": {
|
"right": {
|
||||||
"num": "18",
|
"num": "19",
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -667,17 +667,17 @@
|
||||||
{
|
{
|
||||||
"type": "pair",
|
"type": "pair",
|
||||||
"left": {
|
"left": {
|
||||||
"num": "19",
|
"num": "18",
|
||||||
"label": "Место таможенного оформления при экспорте из РФ"
|
"label": "Место таможенного оформления при экспорте из РФ"
|
||||||
},
|
},
|
||||||
"right": {
|
"right": {
|
||||||
"num": "20",
|
"num": "19",
|
||||||
"label": "Наличие Фумигации на деревянной таре при экспорте из РФ"
|
"label": "Наличие Фумигации на деревянной таре при экспорте из РФ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "left_12",
|
"type": "left_12",
|
||||||
"num": "21",
|
"num": "20",
|
||||||
"label": "Превышение габаритов дверного проема контейнеров 20DV, 40DV, 40HC"
|
"label": "Превышение габаритов дверного проема контейнеров 20DV, 40DV, 40HC"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -692,7 +692,7 @@
|
||||||
"label": "Страна, город, адрес забора"
|
"label": "Страна, город, адрес забора"
|
||||||
},
|
},
|
||||||
"right": {
|
"right": {
|
||||||
"num": "21",
|
"num": "19",
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,801 +0,0 @@
|
||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"source_xlsx": "Запрос для ИИ Общий(3).xlsx",
|
|
||||||
"by_shipping_type": {
|
|
||||||
"Автомобильная перевозка (LTL)": {
|
|
||||||
"sheet": "Авто LTL интерфейс",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "4",
|
|
||||||
"label": "Страна, город, адрес забора"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "19",
|
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "6",
|
|
||||||
"label": "Количество грузовых мест и вес, объем"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "7",
|
|
||||||
"label": "Габариты грузовых мест ДхШхВ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "10",
|
|
||||||
"label": "Штабелирвоание с другими отправками"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "11",
|
|
||||||
"label": "Штабелирвоание между собой"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "9",
|
|
||||||
"label": "Наличие батарейк, газов, жидкостей, аэрозолей"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "12",
|
|
||||||
"label": "Батарейки в составе груза / отдельно"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "13",
|
|
||||||
"label": "MSDS"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "14",
|
|
||||||
"label": "DGM"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "15",
|
|
||||||
"label": "Название бренда"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "16",
|
|
||||||
"label": "Авторизационное письмо/разрешение на вывоз этих брендов из Китая"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "17",
|
|
||||||
"label": "Требуется ли дополнительный сервис - фитоконтроль, иное"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "18",
|
|
||||||
"label": "Требуется ли замена документов"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Автомобильная перевозка (FTL)": {
|
|
||||||
"sheet": "Авто FTL интерфейс",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "4",
|
|
||||||
"label": "Страна, город, адрес забора"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "19",
|
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "6",
|
|
||||||
"label": "Количество и типоразмер машин"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "7",
|
|
||||||
"label": "Количество грузовых мест и вес, объем"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "left_12",
|
|
||||||
"num": "8",
|
|
||||||
"label": "Габариты грузовых мест ДхШхВ"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "10",
|
|
||||||
"label": "Наличие батарейк, газов, жидкостей, аэрозолей"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "12",
|
|
||||||
"label": "Батарейки в составе груза / отдельно"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "11",
|
|
||||||
"label": "MSDS"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "13",
|
|
||||||
"label": "DGM"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "14",
|
|
||||||
"label": "Название бренда"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "15",
|
|
||||||
"label": "Авторизационное письмо/разрешение на вывоз этих брендов из Китая"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "16",
|
|
||||||
"label": "Требуется ли дополнительный сервис - фитоконтроль, иное"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "17",
|
|
||||||
"label": "Требуется ли замена документов"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Морская перевозка (LCL)": {
|
|
||||||
"sheet": "Море, море+жд, жд LCL интерфейc",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "4",
|
|
||||||
"label": "Страна, город, адрес забора"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "19",
|
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "6",
|
|
||||||
"label": "Количество и типоразмер машин"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "7",
|
|
||||||
"label": "Количество грузовых мест и вес, объем"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "10",
|
|
||||||
"label": "Штабелирвоание с другими отправками"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "11",
|
|
||||||
"label": "Штабелирвоание между собой"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "9",
|
|
||||||
"label": "Наличие батарейк, газов, жидкостей, аэрозолей"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "12",
|
|
||||||
"label": "Батарейки в составе груза / отдельно"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "13",
|
|
||||||
"label": "MSDS"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "14",
|
|
||||||
"label": "DGM"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "15",
|
|
||||||
"label": "Название бренда"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "16",
|
|
||||||
"label": "Авторизационное письмо/разрешение на вывоз этих брендов из Китая"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "17",
|
|
||||||
"label": "Требуется ли дополнительный сервис - фитоконтроль, иное"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "18",
|
|
||||||
"label": "Требуется ли замена документов"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Железнодорожная перевозка (LCL)": {
|
|
||||||
"sheet": "Море, море+жд, жд LCL интерфейc",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "4",
|
|
||||||
"label": "Страна, город, адрес забора"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "19",
|
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "6",
|
|
||||||
"label": "Количество и типоразмер машин"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "7",
|
|
||||||
"label": "Количество грузовых мест и вес, объем"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "10",
|
|
||||||
"label": "Штабелирвоание с другими отправками"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "11",
|
|
||||||
"label": "Штабелирвоание между собой"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "9",
|
|
||||||
"label": "Наличие батарейк, газов, жидкостей, аэрозолей"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "12",
|
|
||||||
"label": "Батарейки в составе груза / отдельно"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "13",
|
|
||||||
"label": "MSDS"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "14",
|
|
||||||
"label": "DGM"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "15",
|
|
||||||
"label": "Название бренда"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "16",
|
|
||||||
"label": "Авторизационное письмо/разрешение на вывоз этих брендов из Китая"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "17",
|
|
||||||
"label": "Требуется ли дополнительный сервис - фитоконтроль, иное"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "18",
|
|
||||||
"label": "Требуется ли замена документов"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Мультимодальная перевозка море + ж/д (LCL)": {
|
|
||||||
"sheet": "Море, море+жд, жд LCL интерфейc",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "4",
|
|
||||||
"label": "Страна, город, адрес забора"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "19",
|
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "6",
|
|
||||||
"label": "Количество и типоразмер машин"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "7",
|
|
||||||
"label": "Количество грузовых мест и вес, объем"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "10",
|
|
||||||
"label": "Штабелирвоание с другими отправками"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "11",
|
|
||||||
"label": "Штабелирвоание между собой"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "9",
|
|
||||||
"label": "Наличие батарейк, газов, жидкостей, аэрозолей"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "12",
|
|
||||||
"label": "Батарейки в составе груза / отдельно"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "13",
|
|
||||||
"label": "MSDS"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "14",
|
|
||||||
"label": "DGM"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "15",
|
|
||||||
"label": "Название бренда"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "16",
|
|
||||||
"label": "Авторизационное письмо/разрешение на вывоз этих брендов из Китая"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "17",
|
|
||||||
"label": "Требуется ли дополнительный сервис - фитоконтроль, иное"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "18",
|
|
||||||
"label": "Требуется ли замена документов"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Морская перевозка (FCL)": {
|
|
||||||
"sheet": "Море, море+жд, жд FCL интерфейc",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "4",
|
|
||||||
"label": "Страна, город, адрес забора"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "18",
|
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "6",
|
|
||||||
"label": "Количество и типоразмер контейнеров"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "7",
|
|
||||||
"label": "Количество грузовых мест и вес, объем"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "left_12",
|
|
||||||
"num": "8",
|
|
||||||
"label": "Габариты грузовых мест ДхШхВ"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "10",
|
|
||||||
"label": "Наличие батарейк, газов, жидкостей, аэрозолей"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "12",
|
|
||||||
"label": "Батарейки в составе груза / отдельно"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "11",
|
|
||||||
"label": "MSDS"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "13",
|
|
||||||
"label": "Technical Description of Goods in Railway Transport"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "14",
|
|
||||||
"label": "Название бренда"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "15",
|
|
||||||
"label": "Авторизационное письмо/разрешение на вывоз этих брендов из Китая"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "16",
|
|
||||||
"label": "Требуется ли дополнительный сервис - фитоконтроль, иное"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "17",
|
|
||||||
"label": "Требуется ли замена документов"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "19",
|
|
||||||
"label": "Место таможенного оформления при экспорте из РФ"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "20",
|
|
||||||
"label": "Наличие Фумигации на деревянной таре при экспорте из РФ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "left_12",
|
|
||||||
"num": "21",
|
|
||||||
"label": "Превышение габаритов дверного проема контейнеров 20DV, 40DV, 40HC"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Железнодорожная перевозка (FCL)": {
|
|
||||||
"sheet": "Море, море+жд, жд FCL интерфейc",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "4",
|
|
||||||
"label": "Страна, город, адрес забора"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "18",
|
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "6",
|
|
||||||
"label": "Количество и типоразмер контейнеров"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "7",
|
|
||||||
"label": "Количество грузовых мест и вес, объем"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "left_12",
|
|
||||||
"num": "8",
|
|
||||||
"label": "Габариты грузовых мест ДхШхВ"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "10",
|
|
||||||
"label": "Наличие батарейк, газов, жидкостей, аэрозолей"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "12",
|
|
||||||
"label": "Батарейки в составе груза / отдельно"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "11",
|
|
||||||
"label": "MSDS"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "13",
|
|
||||||
"label": "Technical Description of Goods in Railway Transport"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "14",
|
|
||||||
"label": "Название бренда"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "15",
|
|
||||||
"label": "Авторизационное письмо/разрешение на вывоз этих брендов из Китая"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "16",
|
|
||||||
"label": "Требуется ли дополнительный сервис - фитоконтроль, иное"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "17",
|
|
||||||
"label": "Требуется ли замена документов"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "18",
|
|
||||||
"label": "Место таможенного оформления при экспорте из РФ"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "19",
|
|
||||||
"label": "Наличие Фумигации на деревянной таре при экспорте из РФ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "left_12",
|
|
||||||
"num": "20",
|
|
||||||
"label": "Превышение габаритов дверного проема контейнеров 20DV, 40DV, 40HC"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Мультимодальная перевозка море + ж/д (FCL)": {
|
|
||||||
"sheet": "Море, море+жд, жд FCL интерфейc",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "4",
|
|
||||||
"label": "Страна, город, адрес забора"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "19",
|
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "6",
|
|
||||||
"label": "Количество и типоразмер контейнеров"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "7",
|
|
||||||
"label": "Количество грузовых мест и вес, объем"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "left_12",
|
|
||||||
"num": "8",
|
|
||||||
"label": "Габариты грузовых мест ДхШхВ"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "10",
|
|
||||||
"label": "Наличие батарейк, газов, жидкостей, аэрозолей"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "12",
|
|
||||||
"label": "Батарейки в составе груза / отдельно"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "11",
|
|
||||||
"label": "MSDS"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "13",
|
|
||||||
"label": "Technical Description of Goods in Railway Transport"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "14",
|
|
||||||
"label": "Название бренда"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "15",
|
|
||||||
"label": "Авторизационное письмо/разрешение на вывоз этих брендов из Китая"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "16",
|
|
||||||
"label": "Требуется ли дополнительный сервис - фитоконтроль, иное"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "17",
|
|
||||||
"label": "Требуется ли замена документов"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "18",
|
|
||||||
"label": "Место таможенного оформления при экспорте из РФ"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "19",
|
|
||||||
"label": "Наличие Фумигации на деревянной таре при экспорте из РФ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "left_12",
|
|
||||||
"num": "20",
|
|
||||||
"label": "Превышение габаритов дверного проема контейнеров 20DV, 40DV, 40HC"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Авиаперевозка": {
|
|
||||||
"sheet": "Авиа Интерфейс",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "4",
|
|
||||||
"label": "Страна, город, адрес забора"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "21",
|
|
||||||
"label": "Страна, город, точный адрес доставки, Место ТО в РФ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "6",
|
|
||||||
"label": "Количество грузовых мест и вес, объем"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "7",
|
|
||||||
"label": "Габариты грузовых мест ДхШхВ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "10",
|
|
||||||
"label": "Штабелирвоание с другими отправками"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "11",
|
|
||||||
"label": "Штабелирвоание между собой"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "9",
|
|
||||||
"label": "Наличие батарейк, газов, жидкостей, аэрозолей"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "13",
|
|
||||||
"label": "Батарейки в составе груза / отдельно"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "12",
|
|
||||||
"label": "MSDS"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "14",
|
|
||||||
"label": "DGM"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "15",
|
|
||||||
"label": "Название бренда"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "16",
|
|
||||||
"label": "Авторизационное письмо/разрешение на вывоз этих брендов из Китая"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "17",
|
|
||||||
"label": "Перелет через третью страну со сменой авинаклданых"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "18",
|
|
||||||
"label": "Наличие экспортной лицезии у отправителя"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "19",
|
|
||||||
"label": "Дополнительный сервис"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "20",
|
|
||||||
"label": "Экспедирование в аэропорту назначения"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "22",
|
|
||||||
"label": "Спец требования при доставке до адреса в РФ"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "23",
|
|
||||||
"label": "Таможенное оформление"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "pair",
|
|
||||||
"left": {
|
|
||||||
"num": "24",
|
|
||||||
"label": "Место таможенного оформления при экспорте из РФ"
|
|
||||||
},
|
|
||||||
"right": {
|
|
||||||
"num": "25",
|
|
||||||
"label": "Наличие Фумигации на деревянной таре при экспорте из РФ"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
from pypdf import PdfReader
|
||||||
|
from docx import Document
|
||||||
|
import openpyxl
|
||||||
|
from typing import Union
|
||||||
|
import io
|
||||||
|
import logging
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class DocumentProcessor:
|
||||||
|
@staticmethod
|
||||||
|
def _cell_to_text(v) -> str:
|
||||||
|
if v is None:
|
||||||
|
return ""
|
||||||
|
s = str(v).strip()
|
||||||
|
return s if s else ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sheet_rows_text_openpyxl(sheet) -> list[str]:
|
||||||
|
out: list[str] = []
|
||||||
|
for row in sheet.iter_rows(values_only=True):
|
||||||
|
vals = [DocumentProcessor._cell_to_text(c) for c in row]
|
||||||
|
vals = [x for x in vals if x]
|
||||||
|
if vals:
|
||||||
|
out.append(" | ".join(vals))
|
||||||
|
return out
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sheet_cols_text_openpyxl(sheet) -> list[str]:
|
||||||
|
out: list[str] = []
|
||||||
|
max_col = sheet.max_column or 0
|
||||||
|
max_row = sheet.max_row or 0
|
||||||
|
for c in range(1, max_col + 1):
|
||||||
|
vals: list[str] = []
|
||||||
|
for r in range(1, max_row + 1):
|
||||||
|
v = DocumentProcessor._cell_to_text(sheet.cell(r, c).value)
|
||||||
|
if v:
|
||||||
|
vals.append(v)
|
||||||
|
if vals:
|
||||||
|
col_letter = openpyxl.utils.get_column_letter(c)
|
||||||
|
out.append(f"COL {col_letter} TOP_DOWN: " + " || ".join(vals))
|
||||||
|
out.append(f"COL {col_letter} BOTTOM_UP: " + " || ".join(reversed(vals)))
|
||||||
|
return out
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sheet_rows_text_xlrd(sheet) -> list[str]:
|
||||||
|
out: list[str] = []
|
||||||
|
for row_idx in range(sheet.nrows):
|
||||||
|
row = sheet.row_values(row_idx)
|
||||||
|
vals = [DocumentProcessor._cell_to_text(c) for c in row]
|
||||||
|
vals = [x for x in vals if x]
|
||||||
|
if vals:
|
||||||
|
out.append(" | ".join(vals))
|
||||||
|
return out
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sheet_cols_text_xlrd(sheet) -> list[str]:
|
||||||
|
out: list[str] = []
|
||||||
|
for c in range(sheet.ncols):
|
||||||
|
vals: list[str] = []
|
||||||
|
for r in range(sheet.nrows):
|
||||||
|
v = DocumentProcessor._cell_to_text(sheet.cell_value(r, c))
|
||||||
|
if v:
|
||||||
|
vals.append(v)
|
||||||
|
if vals:
|
||||||
|
out.append(f"COL {c+1} TOP_DOWN: " + " || ".join(vals))
|
||||||
|
out.append(f"COL {c+1} BOTTOM_UP: " + " || ".join(reversed(vals)))
|
||||||
|
return out
|
||||||
|
|
||||||
|
def normalize_email_html(html_content: str) -> str:
|
||||||
|
"""
|
||||||
|
Очищает HTML письма и нормализует таблицы
|
||||||
|
"""
|
||||||
|
|
||||||
|
soup = BeautifulSoup(html_content, "html.parser")
|
||||||
|
|
||||||
|
# удаляем скрипты и стили
|
||||||
|
for tag in soup(["script", "style"]):
|
||||||
|
tag.decompose()
|
||||||
|
|
||||||
|
# нормализуем таблицы
|
||||||
|
for table in soup.find_all("table"):
|
||||||
|
table["style"] = "border-collapse: collapse; width: 100%;"
|
||||||
|
|
||||||
|
for cell in table.find_all(["td", "th"]):
|
||||||
|
cell["style"] = "border:1px solid #ccc;padding:6px;"
|
||||||
|
|
||||||
|
return str(soup)
|
||||||
|
def extract_text(self, content: bytes, filename: str) -> str:
|
||||||
|
ext = filename.lower().split('.')[-1]
|
||||||
|
|
||||||
|
# Изображения не обрабатываем
|
||||||
|
if ext in ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'webp']:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if ext == 'pdf':
|
||||||
|
return self._extract_pdf(content)
|
||||||
|
elif ext in ['docx', 'doc']:
|
||||||
|
return self._extract_docx(content)
|
||||||
|
elif ext in ['xlsx', 'xls']:
|
||||||
|
return self._extract_excel(content)
|
||||||
|
elif ext == 'txt':
|
||||||
|
return content.decode('utf-8')
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported format: {ext}")
|
||||||
|
def _extract_pdf(self, content: bytes) -> str:
|
||||||
|
pdf = PdfReader(io.BytesIO(content))
|
||||||
|
return "\n".join(page.extract_text() for page in pdf.pages)
|
||||||
|
|
||||||
|
def _extract_docx(self, content: bytes) -> str:
|
||||||
|
doc = Document(io.BytesIO(content))
|
||||||
|
return "\n".join(paragraph.text for paragraph in doc.paragraphs)
|
||||||
|
|
||||||
|
def _extract_excel(self, content: bytes) -> str:
|
||||||
|
try:
|
||||||
|
# сначала пробуем openpyxl (для .xlsx)
|
||||||
|
wb = openpyxl.load_workbook(io.BytesIO(content))
|
||||||
|
text = []
|
||||||
|
for sheet in wb.worksheets:
|
||||||
|
text.append(f"=== SHEET: {sheet.title} ===")
|
||||||
|
# Сохраняем совместимость: построчное чтение.
|
||||||
|
rows_text = self._sheet_rows_text_openpyxl(sheet)
|
||||||
|
if rows_text:
|
||||||
|
text.append("[ROWS]")
|
||||||
|
text.extend(rows_text)
|
||||||
|
# Новый режим: чтение по колонкам (общие данные часто внизу столбца).
|
||||||
|
cols_text = self._sheet_cols_text_openpyxl(sheet)
|
||||||
|
if cols_text:
|
||||||
|
text.append("[COLUMNS]")
|
||||||
|
text.extend(cols_text)
|
||||||
|
return "\n".join(text)
|
||||||
|
except Exception as e:
|
||||||
|
# если не получилось, возможно это .xls, пробуем xlrd
|
||||||
|
try:
|
||||||
|
import xlrd
|
||||||
|
workbook = xlrd.open_workbook(file_contents=content)
|
||||||
|
text = []
|
||||||
|
for sheet in workbook.sheets():
|
||||||
|
text.append(f"=== SHEET: {sheet.name} ===")
|
||||||
|
rows_text = self._sheet_rows_text_xlrd(sheet)
|
||||||
|
if rows_text:
|
||||||
|
text.append("[ROWS]")
|
||||||
|
text.extend(rows_text)
|
||||||
|
cols_text = self._sheet_cols_text_xlrd(sheet)
|
||||||
|
if cols_text:
|
||||||
|
text.append("[COLUMNS]")
|
||||||
|
text.extend(cols_text)
|
||||||
|
return "\n".join(text)
|
||||||
|
except ImportError:
|
||||||
|
logger.error("xlrd not installed, cannot parse .xls files")
|
||||||
|
return ""
|
||||||
|
except Exception as e2:
|
||||||
|
logger.error(f"Failed to parse Excel with xlrd: {e2}")
|
||||||
|
return ""
|
||||||
|
|
@ -1,815 +0,0 @@
|
||||||
import io
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import csv
|
|
||||||
import tempfile
|
|
||||||
import subprocess
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
|
|
||||||
from pypdf import PdfReader
|
|
||||||
from docx import Document
|
|
||||||
|
|
||||||
import openpyxl
|
|
||||||
import pdfplumber
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
import pytesseract
|
|
||||||
|
|
||||||
from striprtf.striprtf import rtf_to_text
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentProcessor:
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# HELPERS
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _cleanup_text(text: str) -> str:
|
|
||||||
|
|
||||||
if not text:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
text = text.replace("\xa0", " ")
|
|
||||||
text = text.replace("\t", " ")
|
|
||||||
|
|
||||||
text = re.sub(r"[ \t]+", " ", text)
|
|
||||||
text = re.sub(r"\n{3,}", "\n\n", text)
|
|
||||||
|
|
||||||
return text.strip()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _cell_to_text(v) -> str:
|
|
||||||
|
|
||||||
if v is None:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
s = str(v).strip()
|
|
||||||
|
|
||||||
return s if s else ""
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# HTML
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def normalize_email_html(html_content: str) -> str:
|
|
||||||
"""
|
|
||||||
Очищает HTML письма и нормализует таблицы
|
|
||||||
"""
|
|
||||||
|
|
||||||
soup = BeautifulSoup(
|
|
||||||
html_content,
|
|
||||||
"html.parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
for tag in soup(["script", "style"]):
|
|
||||||
tag.decompose()
|
|
||||||
|
|
||||||
for table in soup.find_all("table"):
|
|
||||||
|
|
||||||
table["style"] = (
|
|
||||||
"border-collapse: collapse; width: 100%;"
|
|
||||||
)
|
|
||||||
|
|
||||||
for cell in table.find_all(["td", "th"]):
|
|
||||||
|
|
||||||
cell["style"] = (
|
|
||||||
"border:1px solid #ccc;padding:6px;"
|
|
||||||
)
|
|
||||||
|
|
||||||
return str(soup)
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# MAIN
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
def extract_text(
|
|
||||||
self,
|
|
||||||
content: bytes,
|
|
||||||
filename: str
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
ext = filename.lower().split(".")[-1]
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
# =================================================
|
|
||||||
# PDF
|
|
||||||
# =================================================
|
|
||||||
|
|
||||||
if ext == "pdf":
|
|
||||||
return self._extract_pdf(content)
|
|
||||||
|
|
||||||
# =================================================
|
|
||||||
# WORD
|
|
||||||
# =================================================
|
|
||||||
|
|
||||||
elif ext in ["docx", "docm"]:
|
|
||||||
return self._extract_docx(content)
|
|
||||||
|
|
||||||
elif ext == "doc":
|
|
||||||
return self._extract_doc(content)
|
|
||||||
|
|
||||||
elif ext == "rtf":
|
|
||||||
return self._extract_rtf(content)
|
|
||||||
|
|
||||||
# =================================================
|
|
||||||
# EXCEL
|
|
||||||
# =================================================
|
|
||||||
|
|
||||||
elif ext in ["xlsx", "xlsm", "xlsxm"]:
|
|
||||||
return self._extract_excel_openpyxl(content)
|
|
||||||
|
|
||||||
elif ext == "xls":
|
|
||||||
return self._extract_excel_xls(content)
|
|
||||||
|
|
||||||
elif ext == "csv":
|
|
||||||
return self._extract_csv(content)
|
|
||||||
|
|
||||||
# =================================================
|
|
||||||
# IMAGES / OCR
|
|
||||||
# =================================================
|
|
||||||
|
|
||||||
elif ext in [
|
|
||||||
"png",
|
|
||||||
"jpg",
|
|
||||||
"jpeg",
|
|
||||||
"gif",
|
|
||||||
"bmp",
|
|
||||||
"webp"
|
|
||||||
]:
|
|
||||||
return self._extract_image(content)
|
|
||||||
|
|
||||||
elif ext in ["tif", "tiff"]:
|
|
||||||
return self._extract_tiff(content)
|
|
||||||
|
|
||||||
# =================================================
|
|
||||||
# TEXT
|
|
||||||
# =================================================
|
|
||||||
|
|
||||||
elif ext == "txt":
|
|
||||||
|
|
||||||
try:
|
|
||||||
return content.decode("utf-8")
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
return content.decode(
|
|
||||||
"cp1251",
|
|
||||||
errors="ignore"
|
|
||||||
)
|
|
||||||
|
|
||||||
# =================================================
|
|
||||||
# UNKNOWN
|
|
||||||
# =================================================
|
|
||||||
|
|
||||||
logger.warning(
|
|
||||||
f"Unsupported attachment format: {ext}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
|
|
||||||
logger.exception(
|
|
||||||
f"Attachment processing failed: {filename}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# PDF
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
def _extract_pdf(
|
|
||||||
self,
|
|
||||||
content: bytes
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
parts = []
|
|
||||||
|
|
||||||
with pdfplumber.open(
|
|
||||||
io.BytesIO(content)
|
|
||||||
) as pdf:
|
|
||||||
|
|
||||||
for page_num, page in enumerate(
|
|
||||||
pdf.pages,
|
|
||||||
start=1
|
|
||||||
):
|
|
||||||
|
|
||||||
parts.append(
|
|
||||||
f"=== PAGE {page_num} ==="
|
|
||||||
)
|
|
||||||
|
|
||||||
# =====================================
|
|
||||||
# TEXT
|
|
||||||
# =====================================
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
text = page.extract_text()
|
|
||||||
|
|
||||||
if text:
|
|
||||||
parts.append(text)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
logger.warning(
|
|
||||||
f"PDF text extraction failed: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# =====================================
|
|
||||||
# TABLES
|
|
||||||
# =====================================
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
tables = page.extract_tables()
|
|
||||||
|
|
||||||
for table_idx, table in enumerate(
|
|
||||||
tables,
|
|
||||||
start=1
|
|
||||||
):
|
|
||||||
|
|
||||||
parts.append(
|
|
||||||
f"=== TABLE {table_idx} ==="
|
|
||||||
)
|
|
||||||
|
|
||||||
for row in table:
|
|
||||||
|
|
||||||
if not row:
|
|
||||||
continue
|
|
||||||
|
|
||||||
cells = []
|
|
||||||
|
|
||||||
for cell in row:
|
|
||||||
|
|
||||||
if cell is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
txt = str(cell).strip()
|
|
||||||
|
|
||||||
txt = " ".join(
|
|
||||||
txt.split()
|
|
||||||
)
|
|
||||||
|
|
||||||
if txt:
|
|
||||||
cells.append(txt)
|
|
||||||
|
|
||||||
if cells:
|
|
||||||
|
|
||||||
parts.append(
|
|
||||||
" | ".join(cells)
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
logger.warning(
|
|
||||||
f"PDF table extraction failed: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
text = "\n".join(parts)
|
|
||||||
|
|
||||||
return self._cleanup_text(text)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
logger.error(f"PDF parse failed: {e}")
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# DOCX / DOCM
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
def _extract_docx(
|
|
||||||
self,
|
|
||||||
content: bytes
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
doc = Document(io.BytesIO(content))
|
|
||||||
|
|
||||||
parts = []
|
|
||||||
|
|
||||||
# =============================================
|
|
||||||
# PARAGRAPHS
|
|
||||||
# =============================================
|
|
||||||
|
|
||||||
for p in doc.paragraphs:
|
|
||||||
|
|
||||||
txt = p.text.strip()
|
|
||||||
|
|
||||||
if txt:
|
|
||||||
parts.append(txt)
|
|
||||||
|
|
||||||
# =============================================
|
|
||||||
# TABLES
|
|
||||||
# =============================================
|
|
||||||
|
|
||||||
for table_idx, table in enumerate(
|
|
||||||
doc.tables
|
|
||||||
):
|
|
||||||
|
|
||||||
parts.append(
|
|
||||||
f"=== TABLE {table_idx + 1} ==="
|
|
||||||
)
|
|
||||||
|
|
||||||
for row in table.rows:
|
|
||||||
|
|
||||||
cells = []
|
|
||||||
|
|
||||||
for cell in row.cells:
|
|
||||||
|
|
||||||
cell_parts = []
|
|
||||||
|
|
||||||
# =================================
|
|
||||||
# DIRECT CELL TEXT
|
|
||||||
# =================================
|
|
||||||
|
|
||||||
txt = "\n".join(
|
|
||||||
p.text.strip()
|
|
||||||
for p in cell.paragraphs
|
|
||||||
if p.text.strip()
|
|
||||||
)
|
|
||||||
|
|
||||||
txt = " ".join(txt.split())
|
|
||||||
|
|
||||||
if txt:
|
|
||||||
cell_parts.append(txt)
|
|
||||||
|
|
||||||
# =================================
|
|
||||||
# NESTED TABLES
|
|
||||||
# =================================
|
|
||||||
|
|
||||||
for nested_table in cell.tables:
|
|
||||||
|
|
||||||
for nested_row in nested_table.rows:
|
|
||||||
|
|
||||||
nested_vals = []
|
|
||||||
|
|
||||||
for nested_cell in nested_row.cells:
|
|
||||||
|
|
||||||
nested_txt = "\n".join(
|
|
||||||
p.text.strip()
|
|
||||||
for p in nested_cell.paragraphs
|
|
||||||
if p.text.strip()
|
|
||||||
)
|
|
||||||
|
|
||||||
nested_txt = " ".join(
|
|
||||||
nested_txt.split()
|
|
||||||
)
|
|
||||||
|
|
||||||
if nested_txt:
|
|
||||||
nested_vals.append(
|
|
||||||
nested_txt
|
|
||||||
)
|
|
||||||
|
|
||||||
if nested_vals:
|
|
||||||
|
|
||||||
cell_parts.append(
|
|
||||||
" || ".join(
|
|
||||||
nested_vals
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# =================================
|
|
||||||
# FINAL CELL
|
|
||||||
# =================================
|
|
||||||
|
|
||||||
if cell_parts:
|
|
||||||
|
|
||||||
cells.append(
|
|
||||||
" <<<TABLE>>> ".join(
|
|
||||||
cell_parts
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if cells:
|
|
||||||
|
|
||||||
parts.append(
|
|
||||||
" | ".join(cells)
|
|
||||||
)
|
|
||||||
|
|
||||||
text = "\n".join(parts)
|
|
||||||
|
|
||||||
return self._cleanup_text(text)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
logger.error(f"DOCX parse failed: {e}")
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# DOC
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
def _extract_doc(
|
|
||||||
self,
|
|
||||||
content: bytes
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
|
||||||
|
|
||||||
input_path = os.path.join(
|
|
||||||
tmpdir,
|
|
||||||
"temp.doc"
|
|
||||||
)
|
|
||||||
|
|
||||||
with open(input_path, "wb") as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
subprocess.run(
|
|
||||||
[
|
|
||||||
"libreoffice",
|
|
||||||
"--headless",
|
|
||||||
"--convert-to",
|
|
||||||
"docx",
|
|
||||||
input_path,
|
|
||||||
"--outdir",
|
|
||||||
tmpdir,
|
|
||||||
],
|
|
||||||
check=True,
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
stderr=subprocess.DEVNULL,
|
|
||||||
)
|
|
||||||
|
|
||||||
converted_path = os.path.join(
|
|
||||||
tmpdir,
|
|
||||||
"temp.docx"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not os.path.exists(
|
|
||||||
converted_path
|
|
||||||
):
|
|
||||||
|
|
||||||
logger.error(
|
|
||||||
"DOC conversion failed"
|
|
||||||
)
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
with open(
|
|
||||||
converted_path,
|
|
||||||
"rb"
|
|
||||||
) as f:
|
|
||||||
|
|
||||||
converted_content = f.read()
|
|
||||||
|
|
||||||
return self._extract_docx(
|
|
||||||
converted_content
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
logger.error(f"DOC parse failed: {e}")
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# RTF
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
def _extract_rtf(
|
|
||||||
self,
|
|
||||||
content: bytes
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
try:
|
|
||||||
text = content.decode("utf-8")
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
text = content.decode(
|
|
||||||
"cp1251",
|
|
||||||
errors="ignore"
|
|
||||||
)
|
|
||||||
|
|
||||||
text = rtf_to_text(text)
|
|
||||||
|
|
||||||
return self._cleanup_text(text)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
logger.error(f"RTF parse failed: {e}")
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# XLSX
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
def _extract_excel_openpyxl(
|
|
||||||
self,
|
|
||||||
content: bytes
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
wb = openpyxl.load_workbook(
|
|
||||||
io.BytesIO(content),
|
|
||||||
data_only=True
|
|
||||||
)
|
|
||||||
|
|
||||||
text = []
|
|
||||||
|
|
||||||
for sheet in wb.worksheets:
|
|
||||||
|
|
||||||
text.append(
|
|
||||||
f"=== SHEET: {sheet.title} ==="
|
|
||||||
)
|
|
||||||
|
|
||||||
# =====================================
|
|
||||||
# MERGED CELLS
|
|
||||||
# =====================================
|
|
||||||
|
|
||||||
merged_ranges = list(
|
|
||||||
sheet.merged_cells.ranges
|
|
||||||
)
|
|
||||||
|
|
||||||
if merged_ranges:
|
|
||||||
|
|
||||||
text.append(
|
|
||||||
"=== MERGED CELLS ==="
|
|
||||||
)
|
|
||||||
|
|
||||||
for r in merged_ranges:
|
|
||||||
text.append(str(r))
|
|
||||||
|
|
||||||
# =====================================
|
|
||||||
# ROWS
|
|
||||||
# =====================================
|
|
||||||
|
|
||||||
text.append("[ROWS]")
|
|
||||||
|
|
||||||
for row in sheet.iter_rows(
|
|
||||||
values_only=True
|
|
||||||
):
|
|
||||||
|
|
||||||
vals = [
|
|
||||||
self._cell_to_text(c)
|
|
||||||
for c in row
|
|
||||||
]
|
|
||||||
|
|
||||||
vals = [x for x in vals if x]
|
|
||||||
|
|
||||||
if vals:
|
|
||||||
|
|
||||||
text.append(
|
|
||||||
" | ".join(vals)
|
|
||||||
)
|
|
||||||
|
|
||||||
# =====================================
|
|
||||||
# COLUMNS
|
|
||||||
# =====================================
|
|
||||||
|
|
||||||
text.append("[COLUMNS]")
|
|
||||||
|
|
||||||
max_col = sheet.max_column or 0
|
|
||||||
max_row = sheet.max_row or 0
|
|
||||||
|
|
||||||
for c in range(1, max_col + 1):
|
|
||||||
|
|
||||||
vals = []
|
|
||||||
|
|
||||||
for r in range(1, max_row + 1):
|
|
||||||
|
|
||||||
v = self._cell_to_text(
|
|
||||||
sheet.cell(r, c).value
|
|
||||||
)
|
|
||||||
|
|
||||||
if v:
|
|
||||||
vals.append(v)
|
|
||||||
|
|
||||||
if vals:
|
|
||||||
|
|
||||||
col_letter = (
|
|
||||||
openpyxl.utils
|
|
||||||
.get_column_letter(c)
|
|
||||||
)
|
|
||||||
|
|
||||||
text.append(
|
|
||||||
f"COL {col_letter}: "
|
|
||||||
+ " || ".join(vals)
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._cleanup_text(
|
|
||||||
"\n".join(text)
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
logger.error(
|
|
||||||
f"XLSX parse failed: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# XLS
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
def _extract_excel_xls(
|
|
||||||
self,
|
|
||||||
content: bytes
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
import xlrd
|
|
||||||
|
|
||||||
workbook = xlrd.open_workbook(
|
|
||||||
file_contents=content
|
|
||||||
)
|
|
||||||
|
|
||||||
text = []
|
|
||||||
|
|
||||||
for sheet in workbook.sheets():
|
|
||||||
|
|
||||||
text.append(
|
|
||||||
f"=== SHEET: {sheet.name} ==="
|
|
||||||
)
|
|
||||||
|
|
||||||
for row_idx in range(
|
|
||||||
sheet.nrows
|
|
||||||
):
|
|
||||||
|
|
||||||
row = sheet.row_values(
|
|
||||||
row_idx
|
|
||||||
)
|
|
||||||
|
|
||||||
vals = [
|
|
||||||
self._cell_to_text(c)
|
|
||||||
for c in row
|
|
||||||
]
|
|
||||||
|
|
||||||
vals = [x for x in vals if x]
|
|
||||||
|
|
||||||
if vals:
|
|
||||||
|
|
||||||
text.append(
|
|
||||||
" | ".join(vals)
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._cleanup_text(
|
|
||||||
"\n".join(text)
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
logger.error(f"XLS parse failed: {e}")
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# CSV
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
def _extract_csv(
|
|
||||||
self,
|
|
||||||
content: bytes
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
try:
|
|
||||||
text = content.decode("utf-8")
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
text = content.decode(
|
|
||||||
"cp1251",
|
|
||||||
errors="ignore"
|
|
||||||
)
|
|
||||||
|
|
||||||
reader = csv.reader(
|
|
||||||
io.StringIO(text)
|
|
||||||
)
|
|
||||||
|
|
||||||
rows = []
|
|
||||||
|
|
||||||
for row in reader:
|
|
||||||
|
|
||||||
vals = [
|
|
||||||
str(x).strip()
|
|
||||||
for x in row
|
|
||||||
if str(x).strip()
|
|
||||||
]
|
|
||||||
|
|
||||||
if vals:
|
|
||||||
|
|
||||||
rows.append(
|
|
||||||
" | ".join(vals)
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._cleanup_text(
|
|
||||||
"\n".join(rows)
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
logger.error(f"CSV parse failed: {e}")
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# OCR
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
def _ocr_image(
|
|
||||||
self,
|
|
||||||
image: Image.Image
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
text = pytesseract.image_to_string(
|
|
||||||
image,
|
|
||||||
lang="rus+eng",
|
|
||||||
config="--psm 6"
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._cleanup_text(text)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
logger.error(f"OCR failed: {e}")
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# IMAGE
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
def _extract_image(
|
|
||||||
self,
|
|
||||||
content: bytes
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
image = Image.open(
|
|
||||||
io.BytesIO(content)
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._ocr_image(image)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
logger.error(
|
|
||||||
f"Image parse failed: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# TIFF
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
def _extract_tiff(
|
|
||||||
self,
|
|
||||||
content: bytes
|
|
||||||
) -> str:
|
|
||||||
|
|
||||||
try:
|
|
||||||
|
|
||||||
image = Image.open(
|
|
||||||
io.BytesIO(content)
|
|
||||||
)
|
|
||||||
|
|
||||||
texts = []
|
|
||||||
|
|
||||||
for frame in range(
|
|
||||||
getattr(image, "n_frames", 1)
|
|
||||||
):
|
|
||||||
|
|
||||||
image.seek(frame)
|
|
||||||
|
|
||||||
texts.append(
|
|
||||||
self._ocr_image(image)
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._cleanup_text(
|
|
||||||
"\n".join(texts)
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
logger.error(
|
|
||||||
f"TIFF parse failed: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
@ -39,16 +39,6 @@ def _row_cells(ws, r: int, max_c: int = 12) -> list[tuple[int, str]]:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _is_num_like(s: str) -> bool:
|
|
||||||
if not isinstance(s, str):
|
|
||||||
return False
|
|
||||||
t = s.strip()
|
|
||||||
if not t:
|
|
||||||
return False
|
|
||||||
# 1, 1., 1.0
|
|
||||||
return t.isdigit() or t.replace(".", "", 1).isdigit()
|
|
||||||
|
|
||||||
|
|
||||||
def parse_interface_sheet(ws) -> list[dict]:
|
def parse_interface_sheet(ws) -> list[dict]:
|
||||||
"""Разбор листа интерфейса в последовательность визуальных блоков."""
|
"""Разбор листа интерфейса в последовательность визуальных блоков."""
|
||||||
blocks: list[dict] = []
|
blocks: list[dict] = []
|
||||||
|
|
@ -60,21 +50,19 @@ def parse_interface_sheet(ws) -> list[dict]:
|
||||||
r += 1
|
r += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Заголовок верхнего ряда:
|
# Заголовок: 7 ячеек в колонках 1,2,3,4,5,7,9 и все значения — «номера пунктов»
|
||||||
# в новых версиях файла набор колонок может отличаться, поэтому
|
if (
|
||||||
# распознаём динамически: строка с номерами + следующая строка с подписями
|
len(cells) == 7
|
||||||
# в тех же колонках.
|
and {c[0] for c in cells} == {1, 2, 3, 4, 5, 7, 9}
|
||||||
cols = [c[0] for c in cells]
|
and all(str(c[1]).strip().isdigit() or c[1].strip().replace(".", "").isdigit() for c in cells)
|
||||||
vals = [c[1] for c in cells]
|
):
|
||||||
if len(cells) >= 4 and all(_is_num_like(v) for v in vals):
|
nums = [c[1] for c in sorted(cells, key=lambda x: (x[0] not in (1, 2, 3, 4, 5, 7, 9), x[0]))]
|
||||||
|
# порядок как в Excel слева направо по колонкам
|
||||||
|
nums = [c[1] for c in sorted(cells, key=lambda x: x[0])]
|
||||||
r2 = r + 1
|
r2 = r + 1
|
||||||
cells2 = _row_cells(ws, r2)
|
cells2 = _row_cells(ws, r2)
|
||||||
map2 = {c: v for c, v in cells2}
|
if len(cells2) == 7 and {c[0] for c in cells2} == {1, 2, 3, 4, 5, 7, 9}:
|
||||||
if all(c in map2 and str(map2[c]).strip() for c in cols):
|
labels = [c[1] for c in sorted(cells2, key=lambda x: x[0])]
|
||||||
labels = [str(map2[c]).strip() for c in cols]
|
|
||||||
# Если в следующей строке снова только числа — это не шапка.
|
|
||||||
if not all(_is_num_like(v) for v in labels):
|
|
||||||
nums = [str(v).strip() for v in vals]
|
|
||||||
blocks.append({"type": "header", "nums": nums, "labels": labels})
|
blocks.append({"type": "header", "nums": nums, "labels": labels})
|
||||||
r += 2
|
r += 2
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
|
||||||
from starlette.requests import Request
|
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.responses import FileResponse, Response
|
from fastapi.responses import FileResponse, Response
|
||||||
from typing import List, Optional, Dict
|
from typing import List, Optional, Dict
|
||||||
|
|
@ -22,7 +20,7 @@ from urllib.parse import quote
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
os.makedirs("/opt/nek/app/reports", exist_ok=True)
|
os.makedirs("/opt/rag-gemini/app/reports", exist_ok=True)
|
||||||
SHIPPING_TYPES_FILE = os.path.join(os.path.dirname(__file__), "shipping_types.json")
|
SHIPPING_TYPES_FILE = os.path.join(os.path.dirname(__file__), "shipping_types.json")
|
||||||
PROCESSED_SHIPPING_TYPES_FILE = os.path.join(
|
PROCESSED_SHIPPING_TYPES_FILE = os.path.join(
|
||||||
os.path.dirname(__file__), "shipping_types_processed.json"
|
os.path.dirname(__file__), "shipping_types_processed.json"
|
||||||
|
|
@ -169,22 +167,6 @@ app.add_middleware(
|
||||||
expose_headers=["Content-Disposition", "Content-Length"]
|
expose_headers=["Content-Disposition", "Content-Length"]
|
||||||
)
|
)
|
||||||
|
|
||||||
class CollapseDuplicatePathSlashesMiddleware(BaseHTTPMiddleware):
|
|
||||||
"""Схлопывает повторяющиеся слэши в path (например POST //process-outlook-emails → /process-outlook-emails)."""
|
|
||||||
|
|
||||||
async def dispatch(self, request: Request, call_next):
|
|
||||||
path = request.scope.get("path") or ""
|
|
||||||
if "//" in path:
|
|
||||||
collapsed = path
|
|
||||||
while "//" in collapsed:
|
|
||||||
collapsed = collapsed.replace("//", "/")
|
|
||||||
request.scope["path"] = collapsed
|
|
||||||
return await call_next(request)
|
|
||||||
|
|
||||||
|
|
||||||
app.add_middleware(CollapseDuplicatePathSlashesMiddleware)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class EmailAttachment(BaseModel):
|
class EmailAttachment(BaseModel):
|
||||||
filename: str
|
filename: str
|
||||||
|
|
@ -225,12 +207,15 @@ class CargoQueryResponse(BaseModel):
|
||||||
class AnalyzeCargoRequest(BaseModel):
|
class AnalyzeCargoRequest(BaseModel):
|
||||||
email_ids: List[str]
|
email_ids: List[str]
|
||||||
|
|
||||||
|
|
||||||
class CargoLearningRequest(BaseModel):
|
class CargoLearningRequest(BaseModel):
|
||||||
"""Сохранение примера для обучения: переписка + структурированный ответ (как в отчёте)."""
|
"""Сохранение примера для обучения: переписка + структурированный ответ (как в отчёте)."""
|
||||||
structured_data: dict
|
structured_data: dict
|
||||||
session_id: Optional[str] = None
|
session_id: Optional[str] = None
|
||||||
context_preview: Optional[str] = None
|
context_preview: Optional[str] = None
|
||||||
notes: Optional[str] = None
|
notes: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
@app.post("/record-cargo-learning")
|
@app.post("/record-cargo-learning")
|
||||||
async def record_cargo_learning(req: CargoLearningRequest):
|
async def record_cargo_learning(req: CargoLearningRequest):
|
||||||
"""
|
"""
|
||||||
83
proxy.py
83
proxy.py
|
|
@ -1,41 +1,92 @@
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, Request
|
||||||
from fastapi.responses import JSONResponse, Response
|
from fastapi.responses import JSONResponse, Response
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
|
try:
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
OPENROUTER_KEY = "sk-9cfed30eb6894df5b9a3ffb4b1fb956d"
|
UPSTREAM_URL = os.getenv(
|
||||||
|
"LLM_UPSTREAM_URL",
|
||||||
|
"https://llm.corp.septem.pro/v1/chat/completions",
|
||||||
|
).strip()
|
||||||
|
OPENROUTER_KEY = os.getenv("OPENROUTER_API_KEY", "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _client_timeout() -> httpx.Timeout:
|
||||||
|
total = float(os.getenv("LLM_PROXY_TIMEOUT_SECONDS", "120"))
|
||||||
|
connect = float(os.getenv("LLM_PROXY_CONNECT_TIMEOUT", "15"))
|
||||||
|
return httpx.Timeout(total, connect=connect)
|
||||||
|
|
||||||
|
|
||||||
@app.post("/v1/chat/completions")
|
@app.post("/v1/chat/completions")
|
||||||
async def proxy(req: Request):
|
async def proxy(req: Request):
|
||||||
body = await req.json()
|
if not OPENROUTER_KEY:
|
||||||
|
logger.error("OPENROUTER_API_KEY is not set (env or .env)")
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=503,
|
||||||
|
content={
|
||||||
|
"error": "Service unavailable",
|
||||||
|
"details": "Set OPENROUTER_API_KEY for the LLM proxy (or add it to .env).",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
try:
|
||||||
|
body = await req.json()
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=400,
|
||||||
|
content={"error": "Invalid JSON", "details": str(e)},
|
||||||
|
)
|
||||||
|
|
||||||
|
verify = os.getenv("HTTPX_VERIFY", "true").lower() not in ("0", "false", "no")
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(verify=verify) as client:
|
||||||
try:
|
try:
|
||||||
r = await client.post(
|
r = await client.post(
|
||||||
"https://api.deepseek.com/chat/completions",
|
UPSTREAM_URL,
|
||||||
headers={
|
headers={
|
||||||
"Authorization": f"Bearer {OPENROUTER_KEY}",
|
"Authorization": f"Bearer {OPENROUTER_KEY}",
|
||||||
"HTTP-Referer": "http://localhost",
|
"HTTP-Referer": os.getenv("LLM_HTTP_REFERER", "http://localhost"),
|
||||||
"model": "deepseek-v4-flash",
|
"X-Title": os.getenv("LLM_PROXY_X_TITLE", "rag-engine"),
|
||||||
"X-Title": "rag-engine"
|
|
||||||
},
|
},
|
||||||
json=body,
|
json=body,
|
||||||
timeout=300,
|
timeout=_client_timeout(),
|
||||||
|
)
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
logger.error(
|
||||||
|
"LLM upstream unreachable (%s): %s",
|
||||||
|
UPSTREAM_URL,
|
||||||
|
e,
|
||||||
|
exc_info=True,
|
||||||
)
|
)
|
||||||
except httpx.HTTPError as e:
|
|
||||||
# Ошибка сети / таймаут
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=502,
|
status_code=502,
|
||||||
content={"error": "Bad gateway", "details": str(e)},
|
content={
|
||||||
|
"error": "Bad gateway",
|
||||||
|
"details": str(e),
|
||||||
|
"upstream": UPSTREAM_URL,
|
||||||
|
"hint": "Проверьте VPN, DNS, доступность хоста и переменную LLM_UPSTREAM_URL.",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
print(r.text)
|
if r.status_code >= 500:
|
||||||
|
logger.warning(
|
||||||
|
"LLM upstream HTTP %s, body (prefix): %s",
|
||||||
|
r.status_code,
|
||||||
|
(r.text or "")[:800],
|
||||||
|
)
|
||||||
|
|
||||||
# Если бэкенд всегда отвечает JSON:
|
|
||||||
# return JSONResponse(status_code=r.status_code, content=r.json())
|
|
||||||
|
|
||||||
# Универсальный вариант — пробрасываем «как есть»
|
|
||||||
return Response(
|
return Response(
|
||||||
content=r.content,
|
content=r.content,
|
||||||
status_code=r.status_code,
|
status_code=r.status_code,
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,24 +3,10 @@
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "Автомобильная перевозка (LTL)",
|
"name": "Автомобильная перевозка (LTL)",
|
||||||
"employee_email": "denis.fomin@timnet.ch, Yana.Schkabrova@timnet.ch",
|
"employee_email": "denis.fomin@timnet.ch, Yana.Schkabrova@timnet.ch",
|
||||||
"extra_required_fields": [
|
"extra_required_fields": ["hs_code", "customs_clearance_place_export_rf"],
|
||||||
"hs_code",
|
|
||||||
"customs_clearance_place_export_rf"
|
|
||||||
],
|
|
||||||
"mandatory_counterparty_criteria": "По макету интерфейса обязательно отражать в письме контрагенту и запрашивать у клиента при отсутствии: название клиента; Incoterms; характер/наименование груза и код ТН ВЭД; страна, город, адрес забора; страна, город, точный адрес доставки; место таможенного оформления в РФ; количество грузовых мест и вес; габариты грузовых мест (Д×Ш×В).",
|
"mandatory_counterparty_criteria": "По макету интерфейса обязательно отражать в письме контрагенту и запрашивать у клиента при отсутствии: название клиента; Incoterms; характер/наименование груза и код ТН ВЭД; страна, город, адрес забора; страна, город, точный адрес доставки; место таможенного оформления в РФ; количество грузовых мест и вес; габариты грузовых мест (Д×Ш×В).",
|
||||||
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест и вес\n7\tГабариты каждого грузового места в см или мм (длина × ширина × высота) и их вес и объём\n8\tХарактер груза или наименование груза, код ТН ВЭД\n9\tСодержатся ли в грузе батарейки, газы под давлением, жидкости\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли груз нельзя штабелировать с другими грузами — можно ли штабелировать грузовые места между собой\n12\tЕсли груз — химия, литиевые батареи, жидкости, аэрозоли, газы или порошки — предоставить MSDS\n13\tЕсли есть батарейки — в составе груза или отдельно упакованы\n14\tЕсли груз из п.13 отгружается из материкового Китая авто транспортом — дополнительно DGM\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tТребуется ли замена документов\n19\tСтрана, город, точный адрес доставки\n20\tМесто таможенного оформления в РФ",
|
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест и вес\n7\tГабариты каждого грузового места в см или мм (длина × ширина × высота)\n8\tХарактер груза или наименование груза, код ТН ВЭД\n9\tСодержатся ли в грузе батарейки, газы под давлением, жидкости\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли груз нельзя штабелировать с другими грузами — можно ли штабелировать грузовые места между собой\n12\tЕсли груз — химия, литиевые батареи, жидкости, аэрозоли, газы или порошки — предоставить MSDS\n13\tЕсли есть батарейки — в составе груза или отдельно упакованы\n14\tЕсли груз из п.13 отгружается из материкового Китая по ж/д — дополнительно DGM\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tТребуется ли замена документов\n19\tСтрана, город, точный адрес доставки\n20\tМесто таможенного оформления в РФ",
|
||||||
"keywords": [
|
"keywords": ["ltl", "сборный трак", "догруз", "группаж авто", "less than truckload", "неполная фура", "авто ltl", "мелкий груз авто", "road ltl", "partial truckload"],
|
||||||
"ltl",
|
|
||||||
"сборный трак",
|
|
||||||
"догруз",
|
|
||||||
"группаж авто",
|
|
||||||
"less than truckload",
|
|
||||||
"неполная фура",
|
|
||||||
"авто ltl",
|
|
||||||
"мелкий груз авто",
|
|
||||||
"road ltl",
|
|
||||||
"partial truckload"
|
|
||||||
],
|
|
||||||
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We are pleased to confirm the possibility of road freight (LTL) for your cargo under the following terms:\n\n- Route: (Pickup address) -> (Delivery address)\n- Cargo weight: (Cargo weight) kg\n- Number of packages: (Number of packages)\n- Volume: (Volume) m3\n- Dimensions: (Dimensions)\n- Cargo description: (Cargo description)\n- HS code: (HS code)\n- Dangerous properties: (Dangerous properties)\n- MSDS required: (MSDS required)\n- Freight cost: (Estimated cost)\n- Estimated transit time: (Transit time)\n\nTo confirm the order and issue the invoice, please reply to this email or contact your manager.\n\nBest regards,\nSEPTEM Cargo Team",
|
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We are pleased to confirm the possibility of road freight (LTL) for your cargo under the following terms:\n\n- Route: (Pickup address) -> (Delivery address)\n- Cargo weight: (Cargo weight) kg\n- Number of packages: (Number of packages)\n- Volume: (Volume) m3\n- Dimensions: (Dimensions)\n- Cargo description: (Cargo description)\n- HS code: (HS code)\n- Dangerous properties: (Dangerous properties)\n- MSDS required: (MSDS required)\n- Freight cost: (Estimated cost)\n- Estimated transit time: (Transit time)\n\nTo confirm the order and issue the invoice, please reply to this email or contact your manager.\n\nBest regards,\nSEPTEM Cargo Team",
|
||||||
"info_request_template": "Уважаемый (Клиент)!\n\nБлагодарим вас за обращение в компанию SEPTEM. Для расчёта автомобильной перевозки (LTL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nПожалуйста, дополните данные по вашему грузу. Вы можете ответить на это письмо или загрузить недостающие документы через личный кабинет.\n\nС уважением,\nКоманда SEPTEM Cargo"
|
"info_request_template": "Уважаемый (Клиент)!\n\nБлагодарим вас за обращение в компанию SEPTEM. Для расчёта автомобильной перевозки (LTL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nПожалуйста, дополните данные по вашему грузу. Вы можете ответить на это письмо или загрузить недостающие документы через личный кабинет.\n\nС уважением,\nКоманда SEPTEM Cargo"
|
||||||
},
|
},
|
||||||
|
|
@ -28,24 +14,10 @@
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"name": "Автомобильная перевозка (FTL)",
|
"name": "Автомобильная перевозка (FTL)",
|
||||||
"employee_email": "denis.fomin@timnet.ch, Yana.Schkabrova@timnet.ch",
|
"employee_email": "denis.fomin@timnet.ch, Yana.Schkabrova@timnet.ch",
|
||||||
"extra_required_fields": [
|
"extra_required_fields": ["hs_code", "vehicle_type", "customs_clearance_place_export_rf"],
|
||||||
"hs_code",
|
|
||||||
"vehicle_type",
|
|
||||||
"customs_clearance_place_export_rf"
|
|
||||||
],
|
|
||||||
"mandatory_counterparty_criteria": "Обязательно в письме и при запросе клиенту: клиент; Incoterms; характер/наименование груза и ТН ВЭД; адрес забора; адрес доставки; место таможенного оформления в РФ; количество и типоразмер машин; вес груза в машине.",
|
"mandatory_counterparty_criteria": "Обязательно в письме и при запросе клиенту: клиент; Incoterms; характер/наименование груза и ТН ВЭД; адрес забора; адрес доставки; место таможенного оформления в РФ; количество и типоразмер машин; вес груза в машине.",
|
||||||
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер машин\n7\tВес груза в машине и объём\n8\tЕсли нет данных о количестве машин — габариты каждого грузового места (Д×Ш×В в см или мм) и их вес и объём\n9\tХарактер груза или наименование груза, код ТН ВЭД\n10\tСодержатся ли в грузе батарейки, газы под давлением, жидкости\n11\tХимия, литиевые батареи, жидкости, аэрозоли, газы или порошки — MSDS\n12\tБатарейки — в составе груза или отдельно упакованы\n13\tОтгрузка из материкового Китая авто транспортом — DGM\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (фитоконтроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления в РФ",
|
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер машин\n7\tВес груза в машине\n8\tЕсли нет данных о количестве машин — габариты каждого грузового места (Д×Ш×В в см или мм) и вес каждого места\n9\tХарактер груза или наименование груза, код ТН ВЭД\n10\tСодержатся ли в грузе батарейки, газы под давлением, жидкости\n11\tХимия, литиевые батареи, жидкости, аэрозоли, газы или порошки — MSDS\n12\tБатарейки — в составе груза или отдельно упакованы\n13\tОтгрузка из материкового Китая по ж/д — DGM\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (фитоконтроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления в РФ",
|
||||||
"keywords": [
|
"keywords": ["ftl", "полная фура", "целая машина", "full truckload", "отдельный автомобиль", "авто ftl", "тент фура целиком", "road ftl", "exclusive truck"],
|
||||||
"ftl",
|
|
||||||
"полная фура",
|
|
||||||
"целая машина",
|
|
||||||
"full truckload",
|
|
||||||
"отдельный автомобиль",
|
|
||||||
"авто ftl",
|
|
||||||
"тент фура целиком",
|
|
||||||
"road ftl",
|
|
||||||
"exclusive truck"
|
|
||||||
],
|
|
||||||
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We are pleased to confirm the possibility of road freight (FTL) for your cargo under the following terms:\n\n- Route: (Pickup address) -> (Delivery address)\n- Vehicle type: (Vehicle type)\n- Cargo weight: (Cargo weight) kg\n- Number of packages: (Number of packages)\n- Volume: (Volume) m3\n- Dimensions: (Dimensions)\n- Cargo description: (Cargo description)\n- HS code: (HS code)\n- Dangerous properties: (Dangerous properties)\n- MSDS required: (MSDS required)\n- Freight cost: (Estimated cost)\n- Transit time: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We are pleased to confirm the possibility of road freight (FTL) for your cargo under the following terms:\n\n- Route: (Pickup address) -> (Delivery address)\n- Vehicle type: (Vehicle type)\n- Cargo weight: (Cargo weight) kg\n- Number of packages: (Number of packages)\n- Volume: (Volume) m3\n- Dimensions: (Dimensions)\n- Cargo description: (Cargo description)\n- HS code: (HS code)\n- Dangerous properties: (Dangerous properties)\n- MSDS required: (MSDS required)\n- Freight cost: (Estimated cost)\n- Transit time: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
||||||
"info_request_template": "Уважаемый (Клиент)!\n\nБлагодарим за обращение в SEPTEM. Для расчёта автомобильной перевозки (FTL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nПожалуйста, дополните данные по грузу.\n\nС уважением,\nКоманда SEPTEM Cargo"
|
"info_request_template": "Уважаемый (Клиент)!\n\nБлагодарим за обращение в SEPTEM. Для расчёта автомобильной перевозки (FTL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nПожалуйста, дополните данные по грузу.\n\nС уважением,\nКоманда SEPTEM Cargo"
|
||||||
},
|
},
|
||||||
|
|
@ -53,24 +25,10 @@
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"name": "Морская перевозка (LCL)",
|
"name": "Морская перевозка (LCL)",
|
||||||
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
|
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
|
||||||
"extra_required_fields": [
|
"extra_required_fields": ["hs_code", "stackable_with_others"],
|
||||||
"hs_code",
|
|
||||||
"stackable_with_others"
|
|
||||||
],
|
|
||||||
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество мест и вес; габариты Д×Ш×В; возможность штабелирования с другими отправками.",
|
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество мест и вес; габариты Д×Ш×В; возможность штабелирования с другими отправками.",
|
||||||
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест и вес с объёмом\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм) и их вес и объём\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, газы под давлением, жидкости в грузе\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли нельзя с другими — можно ли штабелировать места между собой\n12\tХимия, литиевые батареи, жидкости, аэрозоли, газы, порошки — MSDS\n13\tБатарейки — в составе или отдельно\n14\tОтгрузка из материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tСтрана, город, точный адрес доставки\n\nПрименимо к морской сборной перевозке (LCL): порт погрузки/выгрузки и морской этап явно указаны в переписке; без доминирования ж/д как основного этапа.",
|
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест и вес\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм)\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, газы под давлением, жидкости в грузе\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли нельзя с другими — можно ли штабелировать места между собой\n12\tХимия, литиевые батареи, жидкости, аэрозоли, газы, порошки — MSDS\n13\tБатарейки — в составе или отдельно\n14\tОтгрузка из материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tСтрана, город, точный адрес доставки\n\nПрименимо к морской сборной перевозке (LCL): порт погрузки/выгрузки и морской этап явно указаны в переписке; без доминирования ж/д как основного этапа.",
|
||||||
"keywords": [
|
"keywords": ["море lcl", "морская сборная", "морской lcl", "sea lcl", "сборный морской", "консолидация море", "groupage sea", "lcl порт", "морской группаж", "неполный контейнер море"],
|
||||||
"море lcl",
|
|
||||||
"морская сборная",
|
|
||||||
"морской lcl",
|
|
||||||
"sea lcl",
|
|
||||||
"сборный морской",
|
|
||||||
"консолидация море",
|
|
||||||
"groupage sea",
|
|
||||||
"lcl порт",
|
|
||||||
"морской группаж",
|
|
||||||
"неполный контейнер море"
|
|
||||||
],
|
|
||||||
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm the possibility of sea freight (LCL) under the following terms:\n\n- Route: (Port of loading) -> (Port of discharge)\n- Shipment type: LCL\n- Cargo weight: (Cargo weight) kg\n- Packages: (Number of packages)\n- Volume: (Volume) m3\n- Dimensions: (Dimensions)\n- Cargo: (Cargo description)\n- HS code: (HS code)\n- Dangerous properties: (Dangerous properties)\n- MSDS: (MSDS required)\n- Freight: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm the possibility of sea freight (LCL) under the following terms:\n\n- Route: (Port of loading) -> (Port of discharge)\n- Shipment type: LCL\n- Cargo weight: (Cargo weight) kg\n- Packages: (Number of packages)\n- Volume: (Volume) m3\n- Dimensions: (Dimensions)\n- Cargo: (Cargo description)\n- HS code: (HS code)\n- Dangerous properties: (Dangerous properties)\n- MSDS: (MSDS required)\n- Freight: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
||||||
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта морской сборной перевозки (LCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nПожалуйста, дополните данные.\n\nС уважением,\nКоманда SEPTEM Cargo"
|
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта морской сборной перевозки (LCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nПожалуйста, дополните данные.\n\nС уважением,\nКоманда SEPTEM Cargo"
|
||||||
},
|
},
|
||||||
|
|
@ -78,22 +36,10 @@
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"name": "Железнодорожная перевозка (LCL)",
|
"name": "Железнодорожная перевозка (LCL)",
|
||||||
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
|
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
|
||||||
"extra_required_fields": [
|
"extra_required_fields": ["hs_code", "stackable_with_others"],
|
||||||
"hs_code",
|
|
||||||
"stackable_with_others"
|
|
||||||
],
|
|
||||||
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество мест и вес; габариты; штабелирование с другими отправками.",
|
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество мест и вес; габариты; штабелирование с другими отправками.",
|
||||||
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест и вес с объёмом\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм) и их вес и объём\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, газы под давлением, жидкости в грузе\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли нельзя с другими — можно ли штабелировать места между собой\n12\tХимия, литиевые батареи, жидкости, аэрозоли, газы, порошки — MSDS\n13\tБатарейки — в составе или отдельно\n14\tОтгрузка из материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tСтрана, город, точный адрес доставки\n\nПрименимо к ж/д сборной перевозке (LCL): станции/терминалы ж/д, контейнер сборный по ж/д; морской этап в запросе не основной.",
|
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест и вес\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм)\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, газы под давлением, жидкости в грузе\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли нельзя с другими — можно ли штабелировать места между собой\n12\tХимия, литиевые батареи, жидкости, аэрозоли, газы, порошки — MSDS\n13\tБатарейки — в составе или отдельно\n14\tОтгрузка из материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tСтрана, город, точный адрес доставки\n\nПрименимо к ж/д сборной перевозке (LCL): станции/терминалы ж/д, контейнер сборный по ж/д; морской этап в запросе не основной.",
|
||||||
"keywords": [
|
"keywords": ["жд lcl", "ж/д lcl", "rail lcl", "сборный контейнер жд", "сборная жд", "lcl поезд", "группаж жд", "железная дорога сборный"],
|
||||||
"жд lcl",
|
|
||||||
"ж/д lcl",
|
|
||||||
"rail lcl",
|
|
||||||
"сборный контейнер жд",
|
|
||||||
"сборная жд",
|
|
||||||
"lcl поезд",
|
|
||||||
"группаж жд",
|
|
||||||
"железная дорога сборный"
|
|
||||||
],
|
|
||||||
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm rail freight (LCL) under the following terms:\n\n- Route: (Pickup address) -> (Delivery address)\n- Shipment type: LCL\n- Weight: (Cargo weight) kg\n- Packages: (Number of packages)\n- Volume: (Volume) m3\n- Dimensions: (Dimensions)\n- Cargo: (Cargo description)\n- HS: (HS code)\n- MSDS: (MSDS required)\n- Cost: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm rail freight (LCL) under the following terms:\n\n- Route: (Pickup address) -> (Delivery address)\n- Shipment type: LCL\n- Weight: (Cargo weight) kg\n- Packages: (Number of packages)\n- Volume: (Volume) m3\n- Dimensions: (Dimensions)\n- Cargo: (Cargo description)\n- HS: (HS code)\n- MSDS: (MSDS required)\n- Cost: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
||||||
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта железнодорожной сборной перевозки (LCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
|
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта железнодорожной сборной перевозки (LCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
|
||||||
},
|
},
|
||||||
|
|
@ -101,22 +47,10 @@
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"name": "Мультимодальная перевозка море + ж/д (LCL)",
|
"name": "Мультимодальная перевозка море + ж/д (LCL)",
|
||||||
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
|
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
|
||||||
"extra_required_fields": [
|
"extra_required_fields": ["hs_code", "stackable_with_others"],
|
||||||
"hs_code",
|
|
||||||
"stackable_with_others"
|
|
||||||
],
|
|
||||||
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество мест и вес; габариты; штабелирование с другими отправками.",
|
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество мест и вес; габариты; штабелирование с другими отправками.",
|
||||||
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест и вес с объёмом\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм) и их вес и объём\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, газы под давлением, жидкости в грузе\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли нельзя с другими — можно ли штабелировать места между собой\n12\tХимия, литиевые батареи, жидкости, аэрозоли, газы, порошки — MSDS\n13\tБатарейки — в составе или отдельно\n14\tОтгрузка из материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tСтрана, город, точный адрес доставки\n\nКритерии как для сборного груза при цепочке море и ж/д (интермодаль): в переписке явно присутствуют и морской, и железнодорожный этапы.",
|
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест и вес\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм)\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, газы под давлением, жидкости в грузе\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли нельзя с другими — можно ли штабелировать места между собой\n12\tХимия, литиевые батареи, жидкости, аэрозоли, газы, порошки — MSDS\n13\tБатарейки — в составе или отдельно\n14\tОтгрузка из материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tСтрана, город, точный адрес доставки\n\nКритерии как для сборного груза при цепочке море и ж/д (интермодаль): в переписке явно присутствуют и морской, и железнодорожный этапы.",
|
||||||
"keywords": [
|
"keywords": ["море жд", "море+жд", "море и жд", "sea rail", "интермодальн", "multimodal lcl", "морской и жд этап", "порт и станция сборный"],
|
||||||
"море жд",
|
|
||||||
"море+жд",
|
|
||||||
"море и жд",
|
|
||||||
"sea rail",
|
|
||||||
"интермодальн",
|
|
||||||
"multimodal lcl",
|
|
||||||
"морской и жд этап",
|
|
||||||
"порт и станция сборный"
|
|
||||||
],
|
|
||||||
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm multimodal sea+rail (LCL) under the following terms:\n\n- Route: (Pickup address) -> (Delivery address)\n- Modes: sea + rail (LCL)\n- Weight: (Cargo weight) kg\n- Packages: (Number of packages)\n- Volume: (Volume) m3\n- Dimensions: (Dimensions)\n- Cargo: (Cargo description)\n- HS: (HS code)\n- MSDS: (MSDS required)\n- Cost: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm multimodal sea+rail (LCL) under the following terms:\n\n- Route: (Pickup address) -> (Delivery address)\n- Modes: sea + rail (LCL)\n- Weight: (Cargo weight) kg\n- Packages: (Number of packages)\n- Volume: (Volume) m3\n- Dimensions: (Dimensions)\n- Cargo: (Cargo description)\n- HS: (HS code)\n- MSDS: (MSDS required)\n- Cost: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
||||||
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта мультимодальной перевозки (море + ж/д, LCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
|
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта мультимодальной перевозки (море + ж/д, LCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
|
||||||
},
|
},
|
||||||
|
|
@ -124,22 +58,10 @@
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"name": "Морская перевозка (FCL)",
|
"name": "Морская перевозка (FCL)",
|
||||||
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
|
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
|
||||||
"extra_required_fields": [
|
"extra_required_fields": ["hs_code", "container_type"],
|
||||||
"hs_code",
|
|
||||||
"container_type"
|
|
||||||
],
|
|
||||||
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество и типоразмер контейнеров; вес груза в контейнере.",
|
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество и типоразмер контейнеров; вес груза в контейнере.",
|
||||||
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер контейнеров\n7\tВес груза в контейнере и объём\n8\tЕсли нет данных о контейнерах — габариты каждого грузового места (Д×Ш×В) и их вес и объём\n9\tХарактер груза или наименование, код ТН ВЭД\n10\tБатарейки, газы, жидкости\n11\tMSDS при необходимости\n12\tБатарейки в составе или отдельно\n13\tИз материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (крепление в контейнере, фитосанитарный контроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления при экспорте из РФ\n20\tФумигация деревянной тары при экспорте из РФ\n21\tПроверка габаритов грузовых мест на проходимость в дверной проём контейнеров 20DV, 40DV, 40HC\n\nМорской FCL: ISO-контейнеры под морской фрахт и погрузку на судно.",
|
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер контейнеров\n7\tВес груза в контейнере\n8\tЕсли нет данных о контейнерах — габариты каждого грузового места (Д×Ш×В) и вес мест\n9\tХарактер груза или наименование, код ТН ВЭД\n10\tБатарейки, газы, жидкости\n11\tMSDS при необходимости\n12\tБатарейки в составе или отдельно\n13\tИз материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (крепление в контейнере, фитосанитарный контроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления при экспорте из РФ\n20\tФумигация деревянной тары при экспорте из РФ\n21\tПроверка габаритов грузовых мест на проходимость в дверной проём контейнеров 20DV, 40DV, 40HC\n\nМорской FCL: ISO-контейнеры под морской фрахт и погрузку на судно.",
|
||||||
"keywords": [
|
"keywords": ["морской fcl", "море fcl", "sea fcl", "полный контейнер море", "морской контейнер", "fcl порт", "цельный контейнер море", "ocean fcl"],
|
||||||
"морской fcl",
|
|
||||||
"море fcl",
|
|
||||||
"sea fcl",
|
|
||||||
"полный контейнер море",
|
|
||||||
"морской контейнер",
|
|
||||||
"fcl порт",
|
|
||||||
"цельный контейнер море",
|
|
||||||
"ocean fcl"
|
|
||||||
],
|
|
||||||
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm sea freight (FCL):\n\n- Port: (Port of loading) -> (Port of discharge)\n- Container: (Container type)\n- Weight: (Cargo weight) kg\n- Cargo: (Cargo description)\n- HS: (HS code)\n- MSDS/IMDG: (MSDS required)\n- Cost: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm sea freight (FCL):\n\n- Port: (Port of loading) -> (Port of discharge)\n- Container: (Container type)\n- Weight: (Cargo weight) kg\n- Cargo: (Cargo description)\n- HS: (HS code)\n- MSDS/IMDG: (MSDS required)\n- Cost: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
||||||
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта морской перевозки (FCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
|
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта морской перевозки (FCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
|
||||||
},
|
},
|
||||||
|
|
@ -147,21 +69,10 @@
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"name": "Железнодорожная перевозка (FCL)",
|
"name": "Железнодорожная перевозка (FCL)",
|
||||||
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
|
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
|
||||||
"extra_required_fields": [
|
"extra_required_fields": ["hs_code", "container_type"],
|
||||||
"hs_code",
|
|
||||||
"container_type"
|
|
||||||
],
|
|
||||||
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество и типоразмер контейнеров; вес груза в контейнере.",
|
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество и типоразмер контейнеров; вес груза в контейнере.",
|
||||||
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер контейнеров\n7\tВес груза в контейнере и объём\n8\tЕсли нет данных о контейнерах — габариты каждого грузового места (Д×Ш×В) и их вес и объём\n9\tХарактер груза или наименование, код ТН ВЭД\n10\tБатарейки, газы, жидкости\n11\tMSDS при необходимости\n12\tБатарейки в составе или отдельно\n13\tИз материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (крепление в контейнере, фитосанитарный контроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления при экспорте из РФ\n20\tФумигация деревянной тары при экспорте из РФ\n21\tПроверка габаритов грузовых мест на проходимость в дверной проём контейнеров 20DV, 40DV, 40HC\n\nЖ/д FCL: ISO 20'/40' на платформах/вагонных комплектах (1520 мм); без обязательного морского этапа.",
|
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер контейнеров\n7\tВес груза в контейнере\n8\tЕсли нет данных о контейнерах — габариты каждого грузового места (Д×Ш×В) и вес мест\n9\tХарактер груза или наименование, код ТН ВЭД\n10\tБатарейки, газы, жидкости\n11\tMSDS при необходимости\n12\tБатарейки в составе или отдельно\n13\tИз материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (крепление в контейнере, фитосанитарный контроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления при экспорте из РФ\n20\tФумигация деревянной тары при экспорте из РФ\n21\tПроверка габаритов грузовых мест на проходимость в дверной проём контейнеров 20DV, 40DV, 40HC\n\nЖ/д FCL: ISO 20'/40' на платформах/вагонных комплектах (1520 мм); без обязательного морского этапа.",
|
||||||
"keywords": [
|
"keywords": ["жд fcl", "ж/д fcl", "rail fcl", "контейнер по жд целый", "полный контейнер жд", "fcl поезд", "вагон контейнер"],
|
||||||
"жд fcl",
|
|
||||||
"ж/д fcl",
|
|
||||||
"rail fcl",
|
|
||||||
"контейнер по жд целый",
|
|
||||||
"полный контейнер жд",
|
|
||||||
"fcl поезд",
|
|
||||||
"вагон контейнер"
|
|
||||||
],
|
|
||||||
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm rail freight (FCL):\n\n- Route: (Pickup address) -> (Delivery address)\n- Container: (Container type)\n- Weight: (Cargo weight) kg\n- Cargo: (Cargo description)\n- HS: (HS code)\n- MSDS: (MSDS required)\n- Cost: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm rail freight (FCL):\n\n- Route: (Pickup address) -> (Delivery address)\n- Container: (Container type)\n- Weight: (Cargo weight) kg\n- Cargo: (Cargo description)\n- HS: (HS code)\n- MSDS: (MSDS required)\n- Cost: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
||||||
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта железнодорожной перевозки (FCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
|
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта железнодорожной перевозки (FCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
|
||||||
},
|
},
|
||||||
|
|
@ -169,20 +80,10 @@
|
||||||
"id": 8,
|
"id": 8,
|
||||||
"name": "Мультимодальная перевозка море + ж/д (FCL)",
|
"name": "Мультимодальная перевозка море + ж/д (FCL)",
|
||||||
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
|
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
|
||||||
"extra_required_fields": [
|
"extra_required_fields": ["hs_code", "container_type"],
|
||||||
"hs_code",
|
|
||||||
"container_type"
|
|
||||||
],
|
|
||||||
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество и типоразмер контейнеров; вес груза в контейнере.",
|
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество и типоразмер контейнеров; вес груза в контейнере.",
|
||||||
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер контейнеров\n7\tВес груза в контейнере и объём\n8\tЕсли нет данных о контейнерах — габариты каждого грузового места (Д×Ш×В) и их вес и объём\n9\tХарактер груза или наименование, код ТН ВЭД\n10\tБатарейки, газы, жидкости\n11\tMSDS при необходимости\n12\tБатарейки в составе или отдельно\n13\tИз материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (крепление в контейнере, фитосанитарный контроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления при экспорте из РФ\n20\tФумигация деревянной тары при экспорте из РФ\n21\tПроверка габаритов грузовых мест на проходимость в дверной проём контейнеров 20DV, 40DV, 40HC\n\nМультимодальная перевозка море + ж/д (FCL): в запросе явно сочетаются морской и ж/д этапы с полным контейнером (интермодаль).",
|
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер контейнеров\n7\tВес груза в контейнере\n8\tЕсли нет данных о контейнерах — габариты каждого грузового места (Д×Ш×В) и вес мест\n9\tХарактер груза или наименование, код ТН ВЭД\n10\tБатарейки, газы, жидкости\n11\tMSDS при необходимости\n12\tБатарейки в составе или отдельно\n13\tИз материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (крепление в контейнере, фитосанитарный контроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления при экспорте из РФ\n20\tФумигация деревянной тары при экспорте из РФ\n21\tПроверка габаритов грузовых мест на проходимость в дверной проём контейнеров 20DV, 40DV, 40HC\n\nМультимодальная перевозка море + ж/д (FCL): в запросе явно сочетаются морской и ж/д этапы с полным контейнером (интермодаль).",
|
||||||
"keywords": [
|
"keywords": ["море жд fcl", "море+жд fcl", "sea rail fcl", "интермодальн fcl", "контейнер море и жд", "порт станция fcl"],
|
||||||
"море жд fcl",
|
|
||||||
"море+жд fcl",
|
|
||||||
"sea rail fcl",
|
|
||||||
"интермодальн fcl",
|
|
||||||
"контейнер море и жд",
|
|
||||||
"порт станция fcl"
|
|
||||||
],
|
|
||||||
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm multimodal sea+rail (FCL):\n\n- Route: (Port of loading) / rail -> (Delivery address)\n- Container: (Container type)\n- Weight: (Cargo weight) kg\n- Cargo: (Cargo description)\n- HS: (HS code)\n- MSDS: (MSDS required)\n- Cost: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm multimodal sea+rail (FCL):\n\n- Route: (Port of loading) / rail -> (Delivery address)\n- Container: (Container type)\n- Weight: (Cargo weight) kg\n- Cargo: (Cargo description)\n- HS: (HS code)\n- MSDS: (MSDS required)\n- Cost: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
||||||
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта мультимодальной перевозки (море + ж/д, FCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
|
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта мультимодальной перевозки (море + ж/д, FCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
|
||||||
},
|
},
|
||||||
|
|
@ -190,26 +91,10 @@
|
||||||
"id": 9,
|
"id": 9,
|
||||||
"name": "Авиаперевозка",
|
"name": "Авиаперевозка",
|
||||||
"employee_email": "evgeniy.domashnev@timnet.ch, olga.ermakova@timnet.ch, andrey.reshetnyak@timnet.ch, anastasia.fatkina@timnet.ch",
|
"employee_email": "evgeniy.domashnev@timnet.ch, olga.ermakova@timnet.ch, andrey.reshetnyak@timnet.ch, anastasia.fatkina@timnet.ch",
|
||||||
"extra_required_fields": [
|
"extra_required_fields": ["hs_code", "stackable_with_others", "total_volume_cbm", "dangerous_goods_clarification", "customs_clearance_place_export_rf"],
|
||||||
"hs_code",
|
|
||||||
"stackable_with_others",
|
|
||||||
"total_volume_cbm",
|
|
||||||
"dangerous_goods_clarification",
|
|
||||||
"customs_clearance_place_export_rf"
|
|
||||||
],
|
|
||||||
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество мест, вес и объём; габариты Д×Ш×В; штабелирование с другими отправками; сведения о батарейках, газах, жидкостях, аэрозолях; место таможенного оформления при экспорте из РФ (и прочие пункты чек-листа авиа по полному criteria).",
|
"mandatory_counterparty_criteria": "Обязательно: клиент; Incoterms; характер груза и ТН ВЭД; адрес забора; адрес доставки; количество мест, вес и объём; габариты Д×Ш×В; штабелирование с другими отправками; сведения о батарейках, газах, жидкостях, аэрозолях; место таможенного оформления при экспорте из РФ (и прочие пункты чек-листа авиа по полному criteria).",
|
||||||
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест, вес, объём\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм)\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, жидкости, хладоагенты, сухой лёд\n10\tШтабелирование с другими отправками\n11\tШтабелирование мест между собой\n12\tMSDS при химии, батареях, жидкостях, аэрозолях, порошках\n13\tБатарейки — в составе или отдельно\n14\tБатарейки и вылет из материкового Китая — DGM (Dangerous Goods Management) report\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tПерелёт через третью страну со сменой авианакладных\n18\tЭкспортная лицензия у отправителя\n19\t Дополнительные сервисы (фитоконтроль и т.п.)\n20\tКто осуществляет экспедирование в аэропорту прибытия (терминал, сборы авиакомпании)\n21\tСтрана, город, точный адрес доставки\n22\tВывоз из аэропорта — особые требования к автотранспорту, время подачи\n23\tНужно ли таможенное оформление\n24\tМесто таможенного оформления при экспорте из РФ\n25\tФумигация деревянной тары при экспорте из РФ",
|
"criteria": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест, вес, объём\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм)\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, жидкости, хладоагенты, сухой лёд\n10\tШтабелирование с другими отправками\n11\tШтабелирование мест между собой\n12\tMSDS при химии, батареях, жидкостях, аэрозолях, порошках\n13\tБатарейки — в составе или отдельно\n14\tБатарейки и вылет из материкового Китая — DGM (Dangerous Goods Management) report\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tПерелёт через третью страну со сменой авианакладных\n18\tЭкспортная лицензия у отправителя\n19\tДоп. сервис: переупаковка, маркировка, логгеры и т.д.\n20\tКто осуществляет экспедирование в аэропорту прибытия (терминал, сборы авиакомпании)\n21\tСтрана, город, точный адрес доставки\n22\tВывоз из аэропорта — особые требования к автотранспорту, время подачи\n23\tНужно ли таможенное оформление\n24\tМесто таможенного оформления при экспорте из РФ\n25\tФумигация деревянной тары при экспорте из РФ",
|
||||||
"keywords": [
|
"keywords": ["авиа", "авиаперевозка", "самолёт", "air freight", "air cargo", "аэропорт", "flight", "авианакладная", "awb"],
|
||||||
"авиа",
|
|
||||||
"авиаперевозка",
|
|
||||||
"самолёт",
|
|
||||||
"air freight",
|
|
||||||
"air cargo",
|
|
||||||
"аэропорт",
|
|
||||||
"flight",
|
|
||||||
"авианакладная",
|
|
||||||
"awb"
|
|
||||||
],
|
|
||||||
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm air freight:\n\n- Route: (Pickup address) -> (Delivery address)\n- Weight: (Cargo weight) kg\n- Packages: (Number of packages)\n- Volume: (Volume) m3\n- Dimensions: (Dimensions)\n- Cargo: (Cargo description)\n- HS: (HS code)\n- Dangerous goods: (Dangerous properties)\n- MSDS: (MSDS required)\n- DGM (при опасном грузе / вылет из материкового Китая — по правилам перевозчика)\n- Cost: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
"confirmation_template": "Dear Partner!\n\nThank you for contacting SEPTEM. We confirm air freight:\n\n- Route: (Pickup address) -> (Delivery address)\n- Weight: (Cargo weight) kg\n- Packages: (Number of packages)\n- Volume: (Volume) m3\n- Dimensions: (Dimensions)\n- Cargo: (Cargo description)\n- HS: (HS code)\n- Dangerous goods: (Dangerous properties)\n- MSDS: (MSDS required)\n- DGM (при опасном грузе / вылет из материкового Китая — по правилам перевозчика)\n- Cost: (Estimated cost)\n- Transit: (Transit time)\n\nBest regards,\nSEPTEM Cargo Team",
|
||||||
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта авиаперевозки нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
|
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта авиаперевозки нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"Автомобильная перевозка (LTL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест, вес, объём\n7\tГабариты каждого грузового места в см или мм (длина × ширина × высота) и их вес и объём\n8\tХарактер груза или наименование груза, код ТН ВЭД\n9\tСодержатся ли в грузе химия, батарейки, жидкости, аэрозоли, газы или порошки\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли груз нельзя штабелировать с другими грузами — можно ли штабелировать грузовые места между собой\n12\tЕсли груз содержит — химия, батарейки, жидкости, аэрозоли, газы или порошки — предоставить MSDS\n13\tЕсли есть батарейки — в составе груза или отдельно упакованы\n14\tЕсли груз из п.13 отгружается из материкового Китая авто транспортом — дополнительно DGM\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tТребуется ли замена документов\n19\tСтрана, город, точный адрес доставки\n20\tМесто таможенного оформления в РФ",
|
"Автомобильная перевозка (LTL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест и вес\n7\tГабариты каждого грузового места в см или мм (длина × ширина × высота)\n8\tХарактер груза или наименование груза, код ТН ВЭД\n9\tСодержатся ли в грузе батарейки, газы под давлением, жидкости\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли груз нельзя штабелировать с другими грузами — можно ли штабелировать грузовые места между собой\n12\tЕсли груз — химия, литиевые батареи, жидкости, аэрозоли, газы или порошки — предоставить MSDS\n13\tЕсли есть батарейки — в составе груза или отдельно упакованы\n14\tЕсли груз из п.13 отгружается из материкового Китая по ж/д — дополнительно DGM\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tТребуется ли замена документов\n19\tСтрана, город, точный адрес доставки\n20\tМесто таможенного оформления в РФ",
|
||||||
"Автомобильная перевозка (FTL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер машин\n7\tВес груза в машине и объём\n8\tГабариты каждого грузового места (Д×Ш×В в см или мм) и их вес и объём \n9\tХарактер груза или наименование груза, код ТН ВЭД\n10\tСодержатся ли в грузе химия, батарейки, жидкости, аэрозоли, газы или порошки\n11\tЕсли груз содержит — химия, батарейки, жидкости, аэрозоли, газы или порошки — предоставить MSDS\n12\tБатарейки — в составе груза или отдельно упакованы\n13\tОтгрузка из материкового Китая авто транспортом — DGM\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (фитоконтроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\t Страна, город, точный адрес доставки",
|
"Автомобильная перевозка (FTL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер машин\n7\tВес груза в машине\n8\tЕсли нет данных о количестве машин — габариты каждого грузового места (Д×Ш×В в см или мм) и вес каждого места\n9\tХарактер груза или наименование груза, код ТН ВЭД\n10\tСодержатся ли в грузе батарейки, газы под давлением, жидкости\n11\tХимия, литиевые батареи, жидкости, аэрозоли, газы или порошки — MSDS\n12\tБатарейки — в составе груза или отдельно упакованы\n13\tОтгрузка из материкового Китая по ж/д — DGM\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (фитоконтроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления в РФ",
|
||||||
"Морская перевозка (LCL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест, вес и объём\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм) и их вес и объём\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tХимия, батарейки, жидкости, аэрозоли, газы или порошки в грузе\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли нельзя с другими — можно ли штабелировать места между собой\n12\t Если груз содержит — химия, батарейки, жидкости, аэрозоли, газы или порошки — предоставить MSDS\n13\tБатарейки — в составе или отдельно\n14\tОтгрузка из материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tСтрана, город, точный адрес доставки\n\nПрименимо к морской сборной перевозке (LCL): порт погрузки/выгрузки и морской этап явно указаны в переписке; без доминирования ж/д как основного этапа.",
|
"Морская перевозка (LCL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест и вес\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм)\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, газы под давлением, жидкости в грузе\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли нельзя с другими — можно ли штабелировать места между собой\n12\tХимия, литиевые батареи, жидкости, аэрозоли, газы, порошки — MSDS\n13\tБатарейки — в составе или отдельно\n14\tОтгрузка из материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tСтрана, город, точный адрес доставки\n\nПрименимо к морской сборной перевозке (LCL): порт погрузки/выгрузки и морской этап явно указаны в переписке; без доминирования ж/д как основного этапа.",
|
||||||
"Железнодорожная перевозка (LCL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест, вес и объём\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм) и их вес и объём\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tХимия, батарейки, жидкости, аэрозоли, газы или порошки в грузе\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли нельзя с другими — можно ли штабелировать места между собой\n12\t Если груз содержит — химия, батарейки, жидкости, аэрозоли, газы или порошки — предоставить MSDS\n13\tБатарейки — в составе или отдельно\n14\tОтгрузка из материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tСтрана, город, точный адрес доставки\n\nПрименимо к ж/д сборной перевозке (LCL): станции/терминалы ж/д, контейнер сборный по ж/д; морской этап в запросе не основной.",
|
"Железнодорожная перевозка (LCL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест и вес\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм)\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, газы под давлением, жидкости в грузе\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли нельзя с другими — можно ли штабелировать места между собой\n12\tХимия, литиевые батареи, жидкости, аэрозоли, газы, порошки — MSDS\n13\tБатарейки — в составе или отдельно\n14\tОтгрузка из материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tСтрана, город, точный адрес доставки\n\nПрименимо к ж/д сборной перевозке (LCL): станции/терминалы ж/д, контейнер сборный по ж/д; морской этап в запросе не основной.",
|
||||||
"Мультимодальная перевозка море + ж/д (LCL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест, вес и объём\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм) и их вес и объём\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tХимия, батарейки, жидкости, аэрозоли, газы или порошки в грузе\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли нельзя с другими — можно ли штабелировать места между собой\n12\t Если груз содержит — химия, батарейки, жидкости, аэрозоли, газы или порошки — предоставить MSDS\n13\tБатарейки — в составе или отдельно\n14\tОтгрузка из материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tСтрана, город, точный адрес доставки\n\nКритерии как для сборного груза при цепочке море и ж/д (интермодаль): в переписке явно присутствуют и морской, и железнодорожный этапы.",
|
"Мультимодальная перевозка море + ж/д (LCL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест и вес\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм)\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, газы под давлением, жидкости в грузе\n10\tМожно ли штабелировать груз с другими отправками\n11\tЕсли нельзя с другими — можно ли штабелировать места между собой\n12\tХимия, литиевые батареи, жидкости, аэрозоли, газы, порошки — MSDS\n13\tБатарейки — в составе или отдельно\n14\tОтгрузка из материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tДополнительные сервисы (фитоконтроль и т.п.)\n18\tСтрана, город, точный адрес доставки\n\nКритерии как для сборного груза при цепочке море и ж/д (интермодаль): в переписке явно присутствуют и морской, и железнодорожный этапы.",
|
||||||
"Морская перевозка (FCL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер контейнеров\n7\tВес груза в контейнере и объём\n8\tГабариты каждого грузового места (Д×Ш×В в см или мм) и их вес и объём\n9\tХарактер груза или наименование, код ТН ВЭД\n10\tХимия, батарейки, жидкости, аэрозоли, газы или порошки в грузе\n11\t Если груз содержит — химия, батарейки, жидкости, аэрозоли, газы или порошки — предоставить MSDS\n12\tБатарейки в составе или отдельно\n13\tИз материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления при экспорте из РФ\n20\tФумигация деревянной тары при экспорте из РФ\n21\tПроверка габаритов грузовых мест на проходимость в дверной проём контейнеров 20DV, 40DV, 40HC\n\nМорской FCL: ISO-контейнеры под морской фрахт и погрузку на судно.",
|
"Морская перевозка (FCL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер контейнеров\n7\tВес груза в контейнере\n8\tЕсли нет данных о контейнерах — габариты каждого грузового места (Д×Ш×В) и вес мест\n9\tХарактер груза или наименование, код ТН ВЭД\n10\tБатарейки, газы, жидкости\n11\tMSDS при необходимости\n12\tБатарейки в составе или отдельно\n13\tИз материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (крепление в контейнере, фитосанитарный контроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления при экспорте из РФ\n20\tФумигация деревянной тары при экспорте из РФ\n21\tПроверка габаритов грузовых мест на проходимость в дверной проём контейнеров 20DV, 40DV, 40HC\n\nМорской FCL: ISO-контейнеры под морской фрахт и погрузку на судно.",
|
||||||
"Железнодорожная перевозка (FCL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер контейнеров\n7\tВес груза в контейнере и объём\n8\tГабариты каждого грузового места (Д×Ш×В в см или мм) и их вес и объём \n9\tХарактер груза или наименование, код ТН ВЭД\n10\tХимия, батарейки, жидкости, аэрозоли, газы или порошки\n11\t Если груз содержит — химия, батарейки, жидкости, аэрозоли, газы или порошки — предоставить MSDS\n12\tБатарейки в составе или отдельно\n13\tИз материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления при экспорте из РФ\n20\tФумигация деревянной тары при экспорте из РФ\n21\tПроверка габаритов грузовых мест на проходимость в дверной проём контейнеров 20DV, 40DV, 40HC\n\nЖ/д FCL: ISO 20'/40' на платформах/вагонных комплектах (1520 мм); без обязательного морского этапа.",
|
"Железнодорожная перевозка (FCL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер контейнеров\n7\tВес груза в контейнере\n8\tЕсли нет данных о контейнерах — габариты каждого грузового места (Д×Ш×В) и вес мест\n9\tХарактер груза или наименование, код ТН ВЭД\n10\tБатарейки, газы, жидкости\n11\tMSDS при необходимости\n12\tБатарейки в составе или отдельно\n13\tИз материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (крепление в контейнере, фитосанитарный контроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления при экспорте из РФ\n20\tФумигация деревянной тары при экспорте из РФ\n21\tПроверка габаритов грузовых мест на проходимость в дверной проём контейнеров 20DV, 40DV, 40HC\n\nЖ/д FCL: ISO 20'/40' на платформах/вагонных комплектах (1520 мм); без обязательного морского этапа.",
|
||||||
"Мультимодальная перевозка море + ж/д (FCL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер контейнеров\n7\tВес груза в контейнере и объём\n8\tГабариты каждого грузового места (Д×Ш×В в см или мм) и их вес и объём \n9\tХарактер груза или наименование, код ТН ВЭД\n10\tХимия, батарейки, жидкости, аэрозоли, газы или порошки\n11\t Если груз содержит — химия, батарейки, жидкости, аэрозоли, газы или порошки — предоставить MSDS\n12\tБатарейки в составе или отдельно\n13\tИз материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления при экспорте из РФ\n20\tФумигация деревянной тары при экспорте из РФ\n21\tПроверка габаритов грузовых мест на проходимость в дверной проём контейнеров 20DV, 40DV, 40HC\n22\tМультимодальная перевозка море + ж/д (FCL): в запросе явно сочетаются морской и ж/д этапы с полным контейнером (интермодаль).",
|
"Мультимодальная перевозка море + ж/д (FCL)": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество и типоразмер контейнеров\n7\tВес груза в контейнере\n8\tЕсли нет данных о контейнерах — габариты каждого грузового места (Д×Ш×В) и вес мест\n9\tХарактер груза или наименование, код ТН ВЭД\n10\tБатарейки, газы, жидкости\n11\tMSDS при необходимости\n12\tБатарейки в составе или отдельно\n13\tИз материкового Китая по ж/д — Technical Description of Goods in Railway Transport\n14\tНазвание бренда\n15\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n16\tДополнительные сервисы (крепление в контейнере, фитосанитарный контроль и т.п.)\n17\tТребуется ли замена документов\n18\tСтрана, город, точный адрес доставки\n19\tМесто таможенного оформления при экспорте из РФ\n20\tФумигация деревянной тары при экспорте из РФ\n21\tПроверка габаритов грузовых мест на проходимость в дверной проём контейнеров 20DV, 40DV, 40HC\n\nМультимодальная перевозка море + ж/д (FCL): в запросе явно сочетаются морской и ж/д этапы с полным контейнером (интермодаль).",
|
||||||
"Авиаперевозка": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест, вес, объём\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм)и их вес и объём \n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, жидкости, хладоагенты, сухой лёд\n10\tШтабелирование с другими отправками\n11\tШтабелирование мест между собой\n12\t Если груз содержит — химия, батарейки, жидкости, аэрозоли, газы или порошки — предоставить MSDS\n13\tБатарейки — в составе или отдельно\n14\tБатарейки и вылет из материкового Китая — DGM (Dangerous Goods Management) report\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tПерелёт через третью страну со сменой авианакладных\n18\tЭкспортная лицензия у отправителя\n19\tДополнительные сервисы (фитоконтроль и т.п.)\n20\tКто осуществляет экспедирование в аэропорту прибытия (терминал, сборы авиакомпании)\n21\tСтрана, город, точный адрес доставки\n22\tВывоз из аэропорта — особые требования к автотранспорту, время подачи\n23\tНужно ли таможенное оформление\n24\tМесто таможенного оформления при экспорте из РФ\n25\tФумигация деревянной тары при экспорте из РФ"
|
"Авиаперевозка": "1\tНазвание клиента\n2\tУсловия поставки Incoterms\n3\tДата готовности груза\n4\tСтрана, город, адрес забора\n5\tСтоимость груза\n6\tКоличество грузовых мест, вес, объём\n7\tГабариты каждого грузового места (Д×Ш×В в см или мм)\n8\tХарактер груза или наименование, код ТН ВЭД\n9\tБатарейки, газы, жидкости, хладоагенты, сухой лёд\n10\tШтабелирование с другими отправками\n11\tШтабелирование мест между собой\n12\tMSDS при химии, батареях, жидкостях, аэрозолях, порошках\n13\tБатарейки — в составе или отдельно\n14\tБатарейки и вылет из материкового Китая — DGM (Dangerous Goods Management) report\n15\tНазвание бренда\n16\tЕсли бренд зарегистрирован в таможенной системе Китая — есть ли у отправителя авторизационное письмо/разрешение на вывоз бренда\n17\tПерелёт через третью страну со сменой авианакладных\n18\tЭкспортная лицензия у отправителя\n19\tДоп. сервис: переупаковка, маркировка, логгеры и т.д.\n20\tКто осуществляет экспедирование в аэропорту прибытия (терминал, сборы авиакомпании)\n21\tСтрана, город, точный адрес доставки\n22\tВывоз из аэропорта — особые требования к автотранспорту, время подачи\n23\tНужно ли таможенное оформление\n24\tМесто таможенного оформления при экспорте из РФ\n25\tФумигация деревянной тары при экспорте из РФ"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -325,24 +325,11 @@ def collect_extra_required_missing_for_template(shipment: Dict, template_obj: Di
|
||||||
if v is None or (isinstance(v, str) and not str(v).strip()):
|
if v is None or (isinstance(v, str) and not str(v).strip()):
|
||||||
out.append(field)
|
out.append(field)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if field == "customs_clearance_place_export_rf":
|
if field == "customs_clearance_place_export_rf":
|
||||||
|
|
||||||
direction = shipment.get("shipment_direction")
|
|
||||||
|
|
||||||
# только экспорт из РФ
|
|
||||||
if direction != "export":
|
|
||||||
continue
|
|
||||||
|
|
||||||
v = shipment.get("customs_clearance_place_export_rf")
|
v = shipment.get("customs_clearance_place_export_rf")
|
||||||
|
if v is None or (isinstance(v, str) and not str(v).strip()):
|
||||||
if v is None or (
|
|
||||||
isinstance(v, str) and not str(v).strip()
|
|
||||||
):
|
|
||||||
out.append(field)
|
out.append(field)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if field == "total_volume_cbm":
|
if field == "total_volume_cbm":
|
||||||
val = shipment.get("total_volume_cbm")
|
val = shipment.get("total_volume_cbm")
|
||||||
if val is None or val == "":
|
if val is None or val == "":
|
||||||
|
|
@ -1168,44 +1155,17 @@ def format_shipment_for_template(
|
||||||
dims = shipment.get("dimensions", [])
|
dims = shipment.get("dimensions", [])
|
||||||
|
|
||||||
if isinstance(dims, list) and dims:
|
if isinstance(dims, list) and dims:
|
||||||
|
|
||||||
dim_strings = []
|
dim_strings = []
|
||||||
|
|
||||||
for idx, d in enumerate(dims, start=1):
|
for d in dims:
|
||||||
|
if isinstance(d, dict):
|
||||||
if not isinstance(d, dict):
|
|
||||||
continue
|
|
||||||
|
|
||||||
l = d.get("length_cm")
|
l = d.get("length_cm")
|
||||||
w = d.get("width_cm")
|
w = d.get("width_cm")
|
||||||
h = d.get("height_cm")
|
h = d.get("height_cm")
|
||||||
|
|
||||||
sizes = [str(v) for v in (l, w, h) if v not in (None, "", 0)]
|
if l and w and h:
|
||||||
|
dim_strings.append(f"{l}×{w}×{h} {dim_unit}")
|
||||||
if not sizes:
|
|
||||||
continue
|
|
||||||
|
|
||||||
parts = [
|
|
||||||
f"Место {idx}: {'×'.join(sizes)} см"
|
|
||||||
]
|
|
||||||
|
|
||||||
weight = d.get("weight_kg")
|
|
||||||
|
|
||||||
if weight not in (None, "", 0):
|
|
||||||
if template_locale == "en":
|
|
||||||
parts.append(f"weight {weight} kg")
|
|
||||||
else:
|
|
||||||
parts.append(f"вес {weight} кг")
|
|
||||||
|
|
||||||
volume = d.get("volume_cbm")
|
|
||||||
|
|
||||||
if volume not in (None, "", 0):
|
|
||||||
if template_locale == "en":
|
|
||||||
parts.append(f"volume {volume} cbm")
|
|
||||||
else:
|
|
||||||
parts.append(f"объём {volume} м³")
|
|
||||||
|
|
||||||
dim_strings.append(" | ".join(parts))
|
|
||||||
|
|
||||||
if dim_strings:
|
if dim_strings:
|
||||||
data["dimensions_str"] = "; ".join(dim_strings)
|
data["dimensions_str"] = "; ".join(dim_strings)
|
||||||
|
|
@ -1741,34 +1701,16 @@ def render_shipment_criteria_checklist(
|
||||||
seen = set()
|
seen = set()
|
||||||
|
|
||||||
if isinstance(dims, list):
|
if isinstance(dims, list):
|
||||||
|
for d in dims:
|
||||||
for idx, d in enumerate(dims, start=1):
|
if isinstance(d, dict):
|
||||||
|
|
||||||
if not isinstance(d, dict):
|
|
||||||
continue
|
|
||||||
|
|
||||||
l = d.get("length_cm")
|
l = d.get("length_cm")
|
||||||
w = d.get("width_cm")
|
w = d.get("width_cm")
|
||||||
h = d.get("height_cm")
|
h = d.get("height_cm")
|
||||||
|
if l and w and h:
|
||||||
sizes = [str(v) for v in (l, w, h) if v not in (None, "", 0)]
|
key = f"{l}x{w}x{h}".lower()
|
||||||
|
if key not in seen:
|
||||||
if not sizes:
|
seen.add(key)
|
||||||
continue
|
parts.append(f"{l}×{w}×{h} см")
|
||||||
weight = d.get("weight_kg")
|
|
||||||
volume = d.get("volume_cbm")
|
|
||||||
|
|
||||||
line_parts = [
|
|
||||||
f"Место {idx}: {'×'.join(sizes)} см"
|
|
||||||
]
|
|
||||||
|
|
||||||
if weight not in (None, "", 0):
|
|
||||||
line_parts.append(f"вес {weight} кг")
|
|
||||||
|
|
||||||
if volume not in (None, "", 0):
|
|
||||||
line_parts.append(f"объём {volume} м³")
|
|
||||||
|
|
||||||
parts.append(" | ".join(line_parts))
|
|
||||||
|
|
||||||
return "; ".join(parts) if parts else NO_INFO_TEXT
|
return "; ".join(parts) if parts else NO_INFO_TEXT
|
||||||
|
|
||||||
|
|
@ -1785,191 +1727,6 @@ def render_shipment_criteria_checklist(
|
||||||
return vehicle_dims
|
return vehicle_dims
|
||||||
return NO_INFO_TEXT
|
return NO_INFO_TEXT
|
||||||
|
|
||||||
def _format_dimensions_with_weight_volume(sh: Dict) -> str:
|
|
||||||
"""
|
|
||||||
Габариты каждого места + вес + объём.
|
|
||||||
"""
|
|
||||||
|
|
||||||
dims = sh.get("dimensions")
|
|
||||||
|
|
||||||
if not isinstance(dims, list) or not dims:
|
|
||||||
return NO_INFO_TEXT
|
|
||||||
|
|
||||||
parts = []
|
|
||||||
|
|
||||||
for idx, d in enumerate(dims, start=1):
|
|
||||||
|
|
||||||
if not isinstance(d, dict):
|
|
||||||
continue
|
|
||||||
|
|
||||||
l = d.get("length_cm")
|
|
||||||
w = d.get("width_cm")
|
|
||||||
h = d.get("height_cm")
|
|
||||||
|
|
||||||
weight = d.get("weight_kg")
|
|
||||||
volume = d.get("volume_cbm")
|
|
||||||
|
|
||||||
row = []
|
|
||||||
|
|
||||||
sizes = [
|
|
||||||
str(v)
|
|
||||||
for v in (l, w, h)
|
|
||||||
if v not in (None, "", 0)
|
|
||||||
]
|
|
||||||
|
|
||||||
if sizes:
|
|
||||||
row.append(f"{'×'.join(sizes)} см")
|
|
||||||
|
|
||||||
if weight not in (None, "", 0):
|
|
||||||
row.append(f"вес {weight} кг")
|
|
||||||
|
|
||||||
if volume not in (None, "", 0):
|
|
||||||
row.append(f"объём {volume} м³")
|
|
||||||
|
|
||||||
if row:
|
|
||||||
parts.append(f"Место {idx}: " + " | ".join(row))
|
|
||||||
|
|
||||||
if not parts:
|
|
||||||
return NO_INFO_TEXT
|
|
||||||
|
|
||||||
return "; ".join(parts)
|
|
||||||
|
|
||||||
def _filtered_door_clearance(sh: Dict) -> str:
|
|
||||||
"""
|
|
||||||
Показывает только проблемные места.
|
|
||||||
"""
|
|
||||||
|
|
||||||
raw = door_clearance_summary(sh)
|
|
||||||
|
|
||||||
if (
|
|
||||||
"Нет габаритов" in raw
|
|
||||||
or "невозможна" in raw
|
|
||||||
):
|
|
||||||
return raw
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
place_blocks = re.findall(
|
|
||||||
r"(Место\s+\d+.*?)(?=Место\s+\d+|$)",
|
|
||||||
raw,
|
|
||||||
flags=re.S,
|
|
||||||
)
|
|
||||||
|
|
||||||
failed = []
|
|
||||||
|
|
||||||
for blk in place_blocks:
|
|
||||||
|
|
||||||
if "не проходит" not in blk:
|
|
||||||
continue
|
|
||||||
|
|
||||||
parts = []
|
|
||||||
|
|
||||||
for part in blk.split(";"):
|
|
||||||
|
|
||||||
part = part.strip()
|
|
||||||
|
|
||||||
if (
|
|
||||||
"не проходит" in part
|
|
||||||
or part.startswith("Место")
|
|
||||||
):
|
|
||||||
parts.append(part)
|
|
||||||
|
|
||||||
txt = "; ".join(parts)
|
|
||||||
|
|
||||||
if txt:
|
|
||||||
failed.append(txt)
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# Все проходят
|
|
||||||
# =====================================================
|
|
||||||
|
|
||||||
if not failed:
|
|
||||||
|
|
||||||
ct = (sh.get("container_type") or "").strip()
|
|
||||||
|
|
||||||
if ct:
|
|
||||||
return (
|
|
||||||
f"Все грузовые места проходят "
|
|
||||||
f"в дверной проём контейнера(ов) {ct}."
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
"Все грузовые места проходят "
|
|
||||||
"в дверной проём контейнеров."
|
|
||||||
)
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# Есть проблемы
|
|
||||||
# =====================================================
|
|
||||||
|
|
||||||
return " ".join(failed)
|
|
||||||
|
|
||||||
# --- далее оригинальная функция _answer_for_criterion с правками ---
|
|
||||||
def _answer_for_criterion(criterion_text: str) -> str:
|
|
||||||
c = criterion_text.lower()
|
|
||||||
|
|
||||||
# ... существующие проверки до блока с габаритами ...
|
|
||||||
|
|
||||||
# === ИЗМЕНЕНИЕ: критерии с общим весом и объёмом ===
|
|
||||||
if "вес" in c and "количество" not in c and "габарит" not in c:
|
|
||||||
w = shipment.get("total_weight_kg")
|
|
||||||
weight_str = f"{w} кг" if (w is not None and str(w).strip() not in ("", "0", "0.0")) else ""
|
|
||||||
vol_str = _format_volume_cbm()
|
|
||||||
if weight_str and vol_str != NO_INFO_TEXT:
|
|
||||||
return f"{weight_str}, объём: {vol_str}"
|
|
||||||
elif weight_str:
|
|
||||||
return weight_str
|
|
||||||
elif vol_str != NO_INFO_TEXT:
|
|
||||||
return f"Объём: {vol_str}"
|
|
||||||
return NO_INFO_TEXT
|
|
||||||
|
|
||||||
if "общий объ" in c or "общий объём" in c or "общий объем" in c:
|
|
||||||
vol = _format_volume_cbm()
|
|
||||||
w = shipment.get("total_weight_kg")
|
|
||||||
weight_str = f"{w} кг" if (w is not None and str(w).strip() not in ("", "0", "0.0")) else ""
|
|
||||||
if vol != NO_INFO_TEXT and weight_str:
|
|
||||||
return f"{vol}, вес: {weight_str}"
|
|
||||||
elif vol != NO_INFO_TEXT:
|
|
||||||
return vol
|
|
||||||
elif weight_str:
|
|
||||||
return f"Вес: {weight_str}"
|
|
||||||
return NO_INFO_TEXT
|
|
||||||
|
|
||||||
# ... блоки с габаритами транспортных средств (без изменений) ...
|
|
||||||
if "габарит" in c and (
|
|
||||||
"транспортн" in c
|
|
||||||
or "машин" in c
|
|
||||||
or "средств" in c
|
|
||||||
or "кузов" in c
|
|
||||||
or "прицеп" in c
|
|
||||||
or "полуприцеп" in c
|
|
||||||
):
|
|
||||||
return _format_dimensions_any() # только размеры
|
|
||||||
|
|
||||||
# === ИЗМЕНЕНИЕ: габариты грузовых мест – добавляем вес и объём ===
|
|
||||||
if "габарит" in c and "грузов" in c and "мест" in c:
|
|
||||||
return _format_dimensions_with_weight_volume(shipment)
|
|
||||||
|
|
||||||
if (
|
|
||||||
("палл" in c or "палет" in c or "короб" in c or "carton" in c or "box" in c)
|
|
||||||
and ("габарит" in c or "размер" in c or "size" in c or "dimension" in c)
|
|
||||||
):
|
|
||||||
return _format_dimensions_with_weight_volume(shipment)
|
|
||||||
|
|
||||||
# общий fallback для габаритов (если не транспорт)
|
|
||||||
if "габарит" in c:
|
|
||||||
return _format_dimensions_with_weight_volume(shipment)
|
|
||||||
|
|
||||||
# ... критерии про проём контейнера ...
|
|
||||||
if ("проём" in c or "проем" in c) and (
|
|
||||||
"контейнер" in c
|
|
||||||
or "20dv" in c
|
|
||||||
or "40dv" in c
|
|
||||||
or "40hc" in c
|
|
||||||
or "проходимость" in c
|
|
||||||
):
|
|
||||||
return _filtered_door_clearance(shipment)
|
|
||||||
|
|
||||||
def _join_desc_and_hs(sh: Dict) -> str:
|
def _join_desc_and_hs(sh: Dict) -> str:
|
||||||
desc = (sh.get("cargo_description") or "").strip()
|
desc = (sh.get("cargo_description") or "").strip()
|
||||||
hs = (sh.get("hs_code") or "").strip()
|
hs = (sh.get("hs_code") or "").strip()
|
||||||
|
|
@ -2073,18 +1830,7 @@ def render_shipment_criteria_checklist(
|
||||||
|
|
||||||
def _answer_for_criterion(criterion_text: str) -> str:
|
def _answer_for_criterion(criterion_text: str) -> str:
|
||||||
c = criterion_text.lower()
|
c = criterion_text.lower()
|
||||||
# =====================================================
|
|
||||||
# Проверка дверного проёма контейнера (п.21)
|
|
||||||
# =====================================================
|
|
||||||
|
|
||||||
if (
|
|
||||||
"дверной проём" in c
|
|
||||||
or "дверной проем" in c
|
|
||||||
or "20dv" in c
|
|
||||||
or "40dv" in c
|
|
||||||
or "40hc" in c
|
|
||||||
):
|
|
||||||
return _filtered_door_clearance(shipment)
|
|
||||||
# Любой критерий с "габарит" должен в приоритете возвращать размеры, а не перехватываться ветками по весу.
|
# Любой критерий с "габарит" должен в приоритете возвращать размеры, а не перехватываться ветками по весу.
|
||||||
if "габарит" in c:
|
if "габарит" in c:
|
||||||
return _format_dimensions_any()
|
return _format_dimensions_any()
|
||||||
|
|
@ -2151,68 +1897,14 @@ def render_shipment_criteria_checklist(
|
||||||
return NO_INFO_TEXT
|
return NO_INFO_TEXT
|
||||||
return str(pkg).strip()
|
return str(pkg).strip()
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# ОБЩИЙ ОБЪЁМ
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
if "общий объ" in c or "общий объём" in c or "общий объем" in c:
|
if "общий объ" in c or "общий объём" in c or "общий объем" in c:
|
||||||
|
return _format_volume_cbm()
|
||||||
vol = _format_volume_cbm()
|
|
||||||
|
|
||||||
w = shipment.get("total_weight_kg")
|
|
||||||
|
|
||||||
weight_ok = (
|
|
||||||
w is not None
|
|
||||||
and str(w).strip() not in ("", "0", "0.0")
|
|
||||||
)
|
|
||||||
|
|
||||||
weight_str = f"{w} кг" if weight_ok else ""
|
|
||||||
|
|
||||||
# есть и объём и вес
|
|
||||||
if vol != NO_INFO_TEXT and weight_str:
|
|
||||||
return f"{vol}, вес: {weight_str}"
|
|
||||||
|
|
||||||
# только объём
|
|
||||||
if vol != NO_INFO_TEXT:
|
|
||||||
return vol
|
|
||||||
|
|
||||||
# только вес
|
|
||||||
if weight_str:
|
|
||||||
return f"Вес: {weight_str}"
|
|
||||||
|
|
||||||
return NO_INFO_TEXT
|
|
||||||
|
|
||||||
|
|
||||||
# =========================================================
|
|
||||||
# ОБЩИЙ ВЕС
|
|
||||||
# =========================================================
|
|
||||||
|
|
||||||
if "вес" in c and "количество" not in c:
|
if "вес" in c and "количество" not in c:
|
||||||
|
|
||||||
w = shipment.get("total_weight_kg")
|
w = shipment.get("total_weight_kg")
|
||||||
|
if w is None or str(w).strip() in ("", "0", "0.0"):
|
||||||
weight_ok = (
|
|
||||||
w is not None
|
|
||||||
and str(w).strip() not in ("", "0", "0.0")
|
|
||||||
)
|
|
||||||
|
|
||||||
weight_str = f"{w} кг" if weight_ok else ""
|
|
||||||
|
|
||||||
vol = _format_volume_cbm()
|
|
||||||
|
|
||||||
# есть и вес и объём
|
|
||||||
if weight_str and vol != NO_INFO_TEXT:
|
|
||||||
return f"{weight_str}, объём: {vol}"
|
|
||||||
|
|
||||||
# только вес
|
|
||||||
if weight_str:
|
|
||||||
return weight_str
|
|
||||||
|
|
||||||
# только объём
|
|
||||||
if vol != NO_INFO_TEXT:
|
|
||||||
return f"Объём: {vol}"
|
|
||||||
|
|
||||||
return NO_INFO_TEXT
|
return NO_INFO_TEXT
|
||||||
|
return f"{w} кг"
|
||||||
|
|
||||||
if ("проём" in c or "проем" in c) and (
|
if ("проём" in c or "проем" in c) and (
|
||||||
"контейнер" in c
|
"контейнер" in c
|
||||||
|
|
@ -2258,9 +1950,6 @@ def render_shipment_criteria_checklist(
|
||||||
|
|
||||||
# Перечень DG (авиа п.9, море/авто и т.д.) и формулировки интерфейса («Наличие батарейк, газов…»).
|
# Перечень DG (авиа п.9, море/авто и т.д.) и формулировки интерфейса («Наличие батарейк, газов…»).
|
||||||
if (
|
if (
|
||||||
"msds" not in c
|
|
||||||
and "dgm" not in c
|
|
||||||
and (
|
|
||||||
"содержатся ли в грузе" in c
|
"содержатся ли в грузе" in c
|
||||||
or "опасные вещества" in c
|
or "опасные вещества" in c
|
||||||
or "опасные свойства" in c
|
or "опасные свойства" in c
|
||||||
|
|
@ -2276,7 +1965,6 @@ def render_shipment_criteria_checklist(
|
||||||
or "сухой лед" in c
|
or "сухой лед" in c
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
):
|
):
|
||||||
return format_dangerous_goods_summary(shipment)
|
return format_dangerous_goods_summary(shipment)
|
||||||
|
|
||||||
|
|
@ -2286,27 +1974,15 @@ def render_shipment_criteria_checklist(
|
||||||
return _yes_no_or_no_info(shipment.get("stackable_with_others"))
|
return _yes_no_or_no_info(shipment.get("stackable_with_others"))
|
||||||
|
|
||||||
if "msds" in c or "паспорт безопасности" in c:
|
if "msds" in c or "паспорт безопасности" in c:
|
||||||
|
|
||||||
msds_negative = _msds_negative_phrase()
|
msds_negative = _msds_negative_phrase()
|
||||||
|
|
||||||
if msds_negative:
|
if msds_negative:
|
||||||
return msds_negative
|
return msds_negative
|
||||||
|
if "батаре" in c and _has_batteries(shipment) is not True:
|
||||||
msds_required = shipment.get("msds_required")
|
# Если батареек нет, документ по батарейкам не нужен.
|
||||||
|
if shipment.get("msds_required") is False:
|
||||||
# =====================================
|
|
||||||
# Явно не нужен
|
|
||||||
# =====================================
|
|
||||||
|
|
||||||
if msds_required is False:
|
|
||||||
return "Не нужен"
|
return "Не нужен"
|
||||||
|
return NO_INFO_TEXT
|
||||||
# =====================================
|
return _required_doc(shipment.get("msds_required"), "msds")
|
||||||
# Нужен / найден
|
|
||||||
# =====================================
|
|
||||||
|
|
||||||
return _required_doc(msds_required, "msds")
|
|
||||||
|
|
||||||
if re.search(r"(?<![a-z])dgm(?![a-z])", c):
|
if re.search(r"(?<![a-z])dgm(?![a-z])", c):
|
||||||
if "батаре" in c and _has_batteries(shipment) is not True:
|
if "батаре" in c and _has_batteries(shipment) is not True:
|
||||||
if shipment.get("dgm_report_required") is False:
|
if shipment.get("dgm_report_required") is False:
|
||||||
|
|
@ -2345,38 +2021,7 @@ def render_shipment_criteria_checklist(
|
||||||
return build_documents_summary(shipment)
|
return build_documents_summary(shipment)
|
||||||
|
|
||||||
if "бренд" in c and "авторизац" in c:
|
if "бренд" in c and "авторизац" in c:
|
||||||
|
return format_brand_authorization_summary(shipment)
|
||||||
docs = shipment.get("documents_found", {}) or {}
|
|
||||||
|
|
||||||
found_docs = docs.get("brand_authorization", []) or []
|
|
||||||
|
|
||||||
if found_docs:
|
|
||||||
|
|
||||||
filenames = [
|
|
||||||
d.get("filename", "")
|
|
||||||
for d in found_docs
|
|
||||||
if d.get("filename")
|
|
||||||
]
|
|
||||||
|
|
||||||
if filenames:
|
|
||||||
return (
|
|
||||||
"Нужно "
|
|
||||||
f"(документ найден: {', '.join(filenames)})"
|
|
||||||
)
|
|
||||||
|
|
||||||
return "Нужно (документ найден)"
|
|
||||||
|
|
||||||
flag = shipment.get("brand_authorization_letter")
|
|
||||||
|
|
||||||
if flag is True:
|
|
||||||
return "Нужно (документ не найден)"
|
|
||||||
|
|
||||||
elif flag is False:
|
|
||||||
return "Не нужно"
|
|
||||||
|
|
||||||
else:
|
|
||||||
return NO_INFO_TEXT
|
|
||||||
|
|
||||||
if "название бренда" in c or (
|
if "название бренда" in c or (
|
||||||
"бренд" in c
|
"бренд" in c
|
||||||
and "авторизац" not in c
|
and "авторизац" not in c
|
||||||
|
|
@ -2387,23 +2032,8 @@ def render_shipment_criteria_checklist(
|
||||||
|
|
||||||
if "лицензия экспорт" in c:
|
if "лицензия экспорт" in c:
|
||||||
return _yes_no_or_no_info(shipment.get("exporter_has_export_license"))
|
return _yes_no_or_no_info(shipment.get("exporter_has_export_license"))
|
||||||
if (
|
if "трансшипмент" in c:
|
||||||
"трансшипмент" in c
|
return _yes_no_or_no_info(shipment.get("transshipment_with_third_country"))
|
||||||
or "треть" in c
|
|
||||||
or "сменой авианаклад" in c
|
|
||||||
or "разрыв наклад" in c
|
|
||||||
):
|
|
||||||
|
|
||||||
transshipment = shipment.get("transshipment_with_third_country")
|
|
||||||
|
|
||||||
if transshipment is True:
|
|
||||||
return "Нужен"
|
|
||||||
|
|
||||||
elif transshipment is False:
|
|
||||||
return "Не нужен"
|
|
||||||
|
|
||||||
else:
|
|
||||||
return NO_INFO_TEXT
|
|
||||||
|
|
||||||
if "дополнительный сервис" in c or "дополнительные услуги" in c or "дополнительные сервисы" in c or "сервисы" in c:
|
if "дополнительный сервис" in c or "дополнительные услуги" in c or "дополнительные сервисы" in c or "сервисы" in c:
|
||||||
extras = shipment.get("additional_services")
|
extras = shipment.get("additional_services")
|
||||||
|
|
@ -2452,13 +2082,9 @@ def render_shipment_criteria_checklist(
|
||||||
return NO_INFO_TEXT
|
return NO_INFO_TEXT
|
||||||
|
|
||||||
if "батарейки" in c and ("упаков" in c or "отдель" in c):
|
if "батарейки" in c and ("упаков" in c or "отдель" in c):
|
||||||
|
if _has_batteries(shipment) is not True:
|
||||||
battery_mode = shipment.get("battery_mode")
|
|
||||||
|
|
||||||
if battery_mode:
|
|
||||||
return battery_mode
|
|
||||||
|
|
||||||
return NO_INFO_TEXT
|
return NO_INFO_TEXT
|
||||||
|
return _yes_no_or_no_info(shipment.get("batteries_packed_separately"))
|
||||||
|
|
||||||
if "тип транспорта" in c:
|
if "тип транспорта" in c:
|
||||||
return (shipment.get("vehicle_type") or "").strip() or NO_INFO_TEXT
|
return (shipment.get("vehicle_type") or "").strip() or NO_INFO_TEXT
|
||||||
|
|
@ -2767,37 +2393,12 @@ def display_overview(report: Dict):
|
||||||
with col1 if i < mid else col2:
|
with col1 if i < mid else col2:
|
||||||
label = FIELD_LABELS.get(key, key)
|
label = FIELD_LABELS.get(key, key)
|
||||||
|
|
||||||
if key == "dimensions":
|
if key in ("dimensions", "vehicle_dimensions"):
|
||||||
dims = []
|
dims = []
|
||||||
|
for d in value:
|
||||||
for idx, d in enumerate(value, start=1):
|
if isinstance(d, dict) and d.get('length_cm') and d.get('width_cm') and d.get('height_cm'):
|
||||||
|
dims.append(f"{d['length_cm']}×{d['width_cm']}×{d['height_cm']} см")
|
||||||
if not isinstance(d, dict):
|
|
||||||
continue
|
|
||||||
|
|
||||||
l = d.get("length_cm")
|
|
||||||
w = d.get("width_cm")
|
|
||||||
h = d.get("height_cm")
|
|
||||||
|
|
||||||
sizes = [str(v) for v in (l, w, h) if v not in (None, "", 0)]
|
|
||||||
|
|
||||||
if not sizes:
|
|
||||||
continue
|
|
||||||
|
|
||||||
parts = [
|
|
||||||
f"Место {idx}: {'×'.join(sizes)} см"
|
|
||||||
]
|
|
||||||
|
|
||||||
if d.get("weight_kg") not in (None, "", 0):
|
|
||||||
parts.append(f"вес {d['weight_kg']} кг")
|
|
||||||
|
|
||||||
if d.get("volume_cbm") not in (None, "", 0):
|
|
||||||
parts.append(f"объём {d['volume_cbm']} м³")
|
|
||||||
|
|
||||||
dims.append(" | ".join(parts))
|
|
||||||
|
|
||||||
value_str = "; ".join(dims) if dims else NO_INFO_TEXT
|
value_str = "; ".join(dims) if dims else NO_INFO_TEXT
|
||||||
|
|
||||||
elif key == "criteria" and isinstance(value, str):
|
elif key == "criteria" and isinstance(value, str):
|
||||||
# criteria может быть:
|
# criteria может быть:
|
||||||
# - сырой: "1\tНазвание...\n2\t..."
|
# - сырой: "1\tНазвание...\n2\t..."
|
||||||
|
|
@ -2822,26 +2423,9 @@ def display_overview(report: Dict):
|
||||||
elif key == "cargo_ready_date":
|
elif key == "cargo_ready_date":
|
||||||
value_str = format_cargo_ready_date_for_display(shipment)
|
value_str = format_cargo_ready_date_for_display(shipment)
|
||||||
elif isinstance(value, bool):
|
elif isinstance(value, bool):
|
||||||
|
|
||||||
if key == "brand_authorization_letter":
|
|
||||||
|
|
||||||
value_str = format_brand_authorization_summary(shipment)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
# Только для документов: "нужен/не нужен"
|
# Только для документов: "нужен/не нужен"
|
||||||
doc_bool_keys = {
|
doc_bool_keys = {"msds_required", "dgm_report_required", "brand_authorization_letter"}
|
||||||
"msds_required",
|
base_str = ("нужен" if value else "не нужен") if key in doc_bool_keys else ("✅ Да" if value else "❌ Нет")
|
||||||
"dgm_report_required",
|
|
||||||
"brand_authorization_letter"
|
|
||||||
}
|
|
||||||
|
|
||||||
base_str = (
|
|
||||||
"нужен" if value else "не нужен"
|
|
||||||
) if key in doc_bool_keys else (
|
|
||||||
"✅ Да" if value else "❌ Нет"
|
|
||||||
)
|
|
||||||
|
|
||||||
docs = shipment.get("documents_found", {})
|
docs = shipment.get("documents_found", {})
|
||||||
|
|
||||||
doc_map = {
|
doc_map = {
|
||||||
|
|
@ -2857,22 +2441,12 @@ def display_overview(report: Dict):
|
||||||
|
|
||||||
if found_docs:
|
if found_docs:
|
||||||
|
|
||||||
filenames = [
|
filenames = [d["filename"] for d in found_docs]
|
||||||
d["filename"]
|
value_str = f"{base_str} (документ найден: {', '.join(filenames)})"
|
||||||
for d in found_docs
|
|
||||||
]
|
|
||||||
|
|
||||||
value_str = (
|
|
||||||
f"{base_str} "
|
|
||||||
f"(документ найден: {', '.join(filenames)})"
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
value_str = (
|
value_str = f"{base_str} (документ не найден)"
|
||||||
f"{base_str} "
|
|
||||||
f"(документ не найден)"
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
Loading…
Reference in New Issue