Загрузить файлы в «/»

This commit is contained in:
m.milnikov 2026-04-11 09:47:09 +00:00
parent b0111c451e
commit f554962d50
5 changed files with 5453 additions and 0 deletions

502
main(1).py Normal file
View File

@ -0,0 +1,502 @@
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, Response
from typing import List, Optional, Dict
from fastapi import FastAPI, UploadFile, File, HTTPException, Form, Body
from fastapi.responses import JSONResponse
from pydantic import BaseModel
import uvicorn
from rag_engine_gemini import RAGEngineGemini
import logging
from prometheus_fastapi_instrumentator import Instrumentator
import asyncio
import hashlib
from datetime import datetime
from fastapi.middleware.cors import CORSMiddleware
import os
import json
import base64
from urllib.parse import quote
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
os.makedirs("/opt/rag-gemini/app/reports", exist_ok=True)
SHIPPING_TYPES_FILE = os.path.join(os.path.dirname(__file__), "shipping_types.json")
PROCESSED_SHIPPING_TYPES_FILE = os.path.join(
os.path.dirname(__file__), "shipping_types_processed.json"
)
class ShippingTypeBase(BaseModel):
name: str
criteria: str = ""
keywords: List[str] = []
employee_email: str = ""
confirmation_template: str = ""
info_request_template: str = ""
class ShippingTypeCreate(ShippingTypeBase):
pass
class ShippingTypeUpdate(ShippingTypeBase):
pass
class ShippingType(ShippingTypeBase):
id: int
def load_shipping_types():
"""Загружает типы перевозок с нормализацией ключей"""
if not os.path.exists(SHIPPING_TYPES_FILE):
return []
with open(SHIPPING_TYPES_FILE, "r", encoding="utf-8") as f:
types = json.load(f)
# Нормализуем ключи (убираем пробелы) и keywords
normalized_types = []
for t in types:
normalized = {}
for key, value in t.items():
clean_key = key.strip()
if clean_key == "keywords" and isinstance(value, list):
# Нормализуем keywords - каждый элемент отдельно, убираем пробелы и кавычки
normalized[clean_key] = [kw.strip().strip('"').strip("'") for kw in value if kw.strip()]
elif isinstance(value, str):
normalized[clean_key] = value.strip()
else:
normalized[clean_key] = value
normalized_types.append(normalized)
return normalized_types
def save_shipping_types(types):
"""Сохраняет типы перевозок в файл"""
with open(SHIPPING_TYPES_FILE, "w", encoding="utf-8") as f:
json.dump(types, f, ensure_ascii=False, indent=2)
def load_processed_shipping_criteria() -> Dict[str, str]:
"""Loads AI-processed criteria per shipping type name."""
if not os.path.exists(PROCESSED_SHIPPING_TYPES_FILE):
return {}
try:
with open(PROCESSED_SHIPPING_TYPES_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, dict):
return {str(k): str(v) for k, v in data.items() if v is not None}
return {}
except Exception as e:
logger.warning(f"Failed to load processed shipping criteria: {e}")
return {}
def save_processed_shipping_criteria(mapping: Dict[str, str]) -> None:
"""Saves AI-processed criteria per shipping type name."""
with open(PROCESSED_SHIPPING_TYPES_FILE, "w", encoding="utf-8") as f:
json.dump(mapping, f, ensure_ascii=False, indent=2)
def upsert_processed_shipping_criteria(type_name: str, processed_criteria: str) -> None:
if not isinstance(type_name, str) or not type_name.strip():
return
processed_criteria = processed_criteria if isinstance(processed_criteria, str) else ""
mapping = load_processed_shipping_criteria()
mapping[type_name] = processed_criteria
save_processed_shipping_criteria(mapping)
app = FastAPI(title="SEPTEM Cargo RAG System")
app.mount("/addin", StaticFiles(directory=os.path.join(os.getcwd(), "addin"), html=True), name="addin")
OLD_HOST = "nec.septem.pro"
NEW_HOST = "nec.clients.septem.pro"
@app.middleware("http")
async def redirect_legacy_host(request, call_next):
host = request.headers.get("host", "").split(":")[0].lower()
if host == OLD_HOST:
target_url = f"https://{NEW_HOST}{request.url.path}"
if request.url.query:
target_url = f"{target_url}?{request.url.query}"
return JSONResponse(
status_code=308,
content={"detail": "Permanent Redirect"},
headers={"Location": target_url},
)
return await call_next(request)
@app.get("/addin/taskpane.html")
async def get_taskpane():
if os.path.exists("addin/taskpane.html"):
return FileResponse("addin/taskpane.html")
elif os.path.exists("frontend/taskpane.html"):
return FileResponse("frontend/taskpane.html")
else:
return {"error": "taskpane.html not found"}
@app.get("/addin/commands.html")
async def get_commands():
if os.path.exists("addin/commands.html"):
return FileResponse("addin/commands.html")
elif os.path.exists("frontend/commands.html"):
return FileResponse("frontend/commands.html")
else:
return {"error": "commands.html not found"}
rag = RAGEngineGemini()
Instrumentator().instrument(app).expose(app)
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://nec.clients.septem.pro",
"https://localhost:3000",
"http://localhost:8501"
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["Content-Disposition", "Content-Length"]
)
class EmailAttachment(BaseModel):
filename: str
size: int
content: Optional[str] = None
class OutlookEmail(BaseModel):
id: str
subject: str
sender: str
senderName: Optional[str] = None
body: str
body_html: Optional[str] = None
receivedTime: Optional[str] = None
to: Optional[str] = None
cc: Optional[str] = None
attachments: List[EmailAttachment] = []
class OutlookEmailsRequest(BaseModel):
emails: List[OutlookEmail]
session_id: Optional[str] = None
class CargoQueryRequest(BaseModel):
query: str
session_id: Optional[str] = None
top_k: int = 10
class CargoQueryResponse(BaseModel):
answer: str
structured_data: dict
sources: list
total_emails_analyzed: int
class AnalyzeCargoRequest(BaseModel):
email_ids: List[str]
class CargoLearningRequest(BaseModel):
"""Сохранение примера для обучения: переписка + структурированный ответ (как в отчёте)."""
structured_data: dict
session_id: Optional[str] = None
context_preview: Optional[str] = None
notes: Optional[str] = None
@app.post("/record-cargo-learning")
async def record_cargo_learning(req: CargoLearningRequest):
"""
Записывает пару «контекст писем JSON отчёта» в локальное хранилище few-shot.
Если передан session_id, текст писем подставляется из текущей сессии сервера.
"""
try:
ok = rag.record_cargo_learning(
structured_data=req.structured_data,
session_id=req.session_id,
context_preview=req.context_preview,
notes=req.notes,
)
return {"status": "ok" if ok else "skipped", "stored": ok}
except Exception as e:
logger.error(f"record-cargo-learning error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/process-outlook-emails")
async def process_outlook_emails(request: OutlookEmailsRequest):
try:
session_id = await rag.process_outlook_emails(
[email.model_dump() for email in request.emails],
session_id=request.session_id
)
return {
"status": "success",
"session_id": session_id,
"emails_processed": len(request.emails)
}
except Exception as e:
logger.error(f"Process Outlook emails error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/query-cargo", response_model=CargoQueryResponse)
async def query_cargo(request: CargoQueryRequest):
try:
result = await rag.query_cargo_info(
request.query,
request.session_id,
request.top_k
)
return result
except Exception as e:
logger.error(f"Cargo query error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/generate-cargo-report")
async def generate_cargo_report(session_id: str = Body(..., embed=True)):
try:
result = await rag.generate_cargo_report(session_id)
return result
except Exception as e:
logger.error(f"Generate report error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/email-sessions/{session_id}")
async def get_session_info(session_id: str):
return {
"session_id": session_id,
"created_at": datetime.now().isoformat(),
"emails_count": 10
}
@app.post("/upload", include_in_schema=False)
async def upload_document(file: UploadFile = File(...)):
try:
content = await file.read()
doc_id = hashlib.md5(file.filename.encode()).hexdigest()
asyncio.create_task(rag.process_document(content, file.filename))
return {"status": "processing", "document_id": doc_id}
except Exception as e:
logger.error(f"Upload error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health")
async def health():
return {"status": "healthy"}
# =============================================================================
# 🔥 ENDPOINT ДЛЯ ПРОСМОТРА ВЛОЖЕНИЙ В БРАУЗЕРЕ (ИСПРАВЛЕННЫЙ)
# =============================================================================
@app.get("/attachments/{session_id}/{email_index}/{attachment_index}")
async def get_attachment(session_id: str, email_index: int, attachment_index: int):
"""
Возвращает оригинальный файл вложения для просмотра в браузере.
Поддерживает PDF, DOCX, XLSX, изображения и другие форматы.
Корректно обрабатывает русские имена файлов (RFC 5987).
"""
att = rag.get_attachment(session_id, email_index, attachment_index)
if att is None:
raise HTTPException(status_code=404, detail="Attachment not found")
filename = att.get("filename", "attachment")
content_base64 = att.get("content_base64")
# 🔥 Функция для безопасного формирования заголовка Content-Disposition
def make_content_disposition(filename: str, disposition: str = "inline") -> str:
"""
Формирует Content-Disposition с поддержкой UTF-8 имён файлов (RFC 5987).
Совместимо со старыми и новыми браузерами.
"""
# ASCII-версия для старых браузеров (fallback)
ascii_filename = filename.encode('ascii', 'ignore').decode('ascii') or 'attachment'
# UTF-8 версия с URL-кодированием для современных браузеров (RFC 5987)
utf8_filename = quote(filename, safe='')
return f'{disposition}; filename="{ascii_filename}"; filename*=UTF-8\'\'{utf8_filename}'
# Если нет оригинального содержимого, возвращаем извлечённый текст
if not content_base64:
text = att.get("text", "")
return Response(
content=text,
media_type="text/plain; charset=utf-8",
headers={
"Content-Disposition": make_content_disposition(filename + ".txt"),
"Content-Length": str(len(text.encode('utf-8'))),
"Access-Control-Expose-Headers": "Content-Disposition, Content-Length"
}
)
# 🔥 Декодируем base64 и определяем MIME тип по расширению
try:
file_content = base64.b64decode(content_base64)
# Определяем MIME тип по расширению файла
ext = filename.lower().split('.')[-1] if '.' in filename else ''
mime_types = {
'pdf': 'application/pdf',
'doc': 'application/msword',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xls': 'application/vnd.ms-excel',
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'txt': 'text/plain',
'csv': 'text/csv',
'png': 'image/png',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'gif': 'image/gif',
'bmp': 'image/bmp',
'zip': 'application/zip',
'rar': 'application/vnd.rar',
'ppt': 'application/vnd.ms-powerpoint',
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'rtf': 'application/rtf',
'xml': 'application/xml',
'json': 'application/json',
}
media_type = mime_types.get(ext, 'application/octet-stream')
# 🔥 Возвращаем файл с правильным заголовком Content-Disposition
return Response(
content=file_content,
media_type=media_type,
headers={
"Content-Disposition": make_content_disposition(filename),
"Content-Length": str(len(file_content)),
"Access-Control-Expose-Headers": "Content-Disposition, Content-Length"
}
)
except Exception as e:
logger.error(f"Error serving attachment: {e}")
raise HTTPException(status_code=500, detail=f"Error processing file: {str(e)}")
@app.get("/shipping-types", response_model=List[ShippingType])
async def get_shipping_types():
return load_shipping_types()
@app.post("/shipping-types", response_model=ShippingType)
async def create_shipping_type(item: ShippingTypeCreate):
types = load_shipping_types()
new_id = max([t["id"] for t in types], default=0) + 1
new_item = item.model_dump()
new_item["id"] = new_id
types.append(new_item)
save_shipping_types(types)
# AI-обработка criteria и сохранение результата
try:
type_name = new_item.get("name", "")
criteria_text = new_item.get("criteria", "") or ""
processed = rag.process_shipping_type_criteria(criteria_text)
upsert_processed_shipping_criteria(type_name, processed)
rag.reload_shipping_types()
except Exception as e:
logger.warning(f"AI criteria processing failed on create: {e}")
return new_item
@app.put("/shipping-types/{item_id}", response_model=ShippingType)
async def update_shipping_type(item_id: int, item: ShippingTypeUpdate):
types = load_shipping_types()
for t in types:
if t["id"] == item_id:
old_name = t.get("name", "")
t.update(item.model_dump())
save_shipping_types(types)
# AI-обработка criteria и сохранение результата
try:
type_name = t.get("name", "")
criteria_text = t.get("criteria", "") or ""
processed = rag.process_shipping_type_criteria(criteria_text)
# если поменяли имя типа — чистим старый ключ
if isinstance(old_name, str) and old_name.strip() and old_name != type_name:
mapping = load_processed_shipping_criteria()
if old_name in mapping:
mapping.pop(old_name, None)
save_processed_shipping_criteria(mapping)
upsert_processed_shipping_criteria(type_name, processed)
rag.reload_shipping_types()
except Exception as e:
logger.warning(f"AI criteria processing failed on update: {e}")
return t
raise HTTPException(status_code=404, detail="Type not found")
@app.get("/email-attachments/{session_id}")
async def get_email_attachments(session_id: str):
emails = rag.sessions.get(session_id, [])
files = []
for email in emails:
for att in email.get("attachments", []):
filename = att.get("filename","")
ext = filename.split(".")[-1].lower()
# игнорируем изображения
if ext in ["png","jpg","jpeg","gif","bmp","tiff","webp"]:
continue
if att.get("content_base64"):
files.append({
"filename": filename,
"content_base64": att["content_base64"]
})
return files
@app.delete("/shipping-types/{item_id}")
async def delete_shipping_type(item_id: int):
types = load_shipping_types()
removed = None
for t in types:
if t.get("id") == item_id:
removed = t
break
new_types = [t for t in types if t["id"] != item_id]
if len(new_types) == len(types):
raise HTTPException(status_code=404, detail="Type not found")
save_shipping_types(new_types)
# Удаляем обработанные criteria
try:
if removed:
type_name = removed.get("name", "")
mapping = load_processed_shipping_criteria()
if type_name in mapping:
mapping.pop(type_name, None)
save_processed_shipping_criteria(mapping)
rag.reload_shipping_types()
except Exception as e:
logger.warning(f"Failed to delete processed criteria on delete: {e}")
return {"ok": True}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)

94
proxy.py Normal file
View File

@ -0,0 +1,94 @@
import logging
import os
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse, Response
import httpx
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI()
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")
async def proxy(req: Request):
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).",
},
)
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:
r = await client.post(
UPSTREAM_URL,
headers={
"Authorization": f"Bearer {OPENROUTER_KEY}",
"HTTP-Referer": os.getenv("LLM_HTTP_REFERER", "http://localhost"),
"X-Title": os.getenv("LLM_PROXY_X_TITLE", "rag-engine"),
},
json=body,
timeout=_client_timeout(),
)
except httpx.RequestError as e:
logger.error(
"LLM upstream unreachable (%s): %s",
UPSTREAM_URL,
e,
exc_info=True,
)
return JSONResponse(
status_code=502,
content={
"error": "Bad gateway",
"details": str(e),
"upstream": UPSTREAM_URL,
"hint": "Проверьте VPN, DNS, доступность хоста и переменную LLM_UPSTREAM_URL.",
},
)
if r.status_code >= 500:
logger.warning(
"LLM upstream HTTP %s, body (prefix): %s",
r.status_code,
(r.text or "")[:800],
)
return Response(
content=r.content,
status_code=r.status_code,
media_type=r.headers.get("content-type", "application/json"),
)

4637
rag_engine_gemini(1).py Normal file

File diff suppressed because it is too large Load Diff

119
shipping_calculator(1).py Normal file
View File

@ -0,0 +1,119 @@
import math
import re
from typing import Dict, Optional
def calculate_shipping_cost(shipment: Dict) -> Optional[Dict]:
"""
Расчёт стоимости авиаперевозки на основе параметров груза.
Возвращает словарь с ключами:
total_usd, total_rub, chargeable_weight, actual_weight, volume_weight
или None, если не хватает данных.
"""
try:
# Проверяем наличие обязательных данных
if not all(k in shipment for k in ['total_weight_kg', 'dimensions', 'cargo_value']):
return None
# Параметры груза
actual_weight = shipment.get('total_weight_kg', 0)
if not actual_weight or actual_weight <= 0:
return None
# Расчёт объёмного веса
dimensions = shipment.get('dimensions', [])
if not dimensions:
return None
total_volume_weight = 0
for dim in dimensions:
if dim.get('length_cm') and dim.get('width_cm') and dim.get('height_cm'):
# Объёмный вес = (Д×Ш×В в метрах) × 167
length_m = dim['length_cm'] / 100
width_m = dim['width_cm'] / 100
height_m = dim['height_cm'] / 100
volume = length_m * width_m * height_m
volume_weight = volume * 167
total_volume_weight += volume_weight
# Платный вес (оплачивается по большему)
chargeable_weight = max(actual_weight, total_volume_weight)
chargeable_weight = math.ceil(chargeable_weight) # округляем вверх
# Базовый тариф в зависимости от веса (USD/кг)
if chargeable_weight < 45:
base_rate = 6.5
elif chargeable_weight < 100:
base_rate = 5.5
elif chargeable_weight < 300:
base_rate = 4.5
elif chargeable_weight < 500:
base_rate = 3.8
elif chargeable_weight < 1000:
base_rate = 3.2
else:
base_rate = 2.8
# Топливный сбор и сбор за безопасность
fuel_surcharge_rate = 0.9 # USD/кг
min_fuel_surcharge = 90 # USD минимум
# Терминальные сборы
terminal_export_rate = 0.22 # USD/кг (~20 руб/кг)
terminal_export_fixed = 25 # USD фиксированных сборов (~2300 руб)
terminal_import_rate = 0.35 # EUR/кг ≈ 0.38 USD/кг по курсу 1.1
# Таможенное оформление
customs_export = 170 # USD (~15500 руб)
customs_import = 220 # EUR ≈ 242 USD
# Надбавки за особые условия
special_rate_multiplier = 1.0
# Терморежим (+15…+25 °C)
if shipment.get('special_transport_requirements') and 'термо' in str(shipment.get('special_transport_requirements', '')).lower():
special_rate_multiplier *= 1.3 # +30%
# Страхование
cargo_value_usd = 0
cargo_value_str = shipment.get('cargo_value', '0')
# Извлекаем числовое значение из строки (например, "18 500 EUR" -> 18500)
value_match = re.search(r'[\d\s]+', cargo_value_str.replace(' ', ''))
if value_match:
cargo_value_usd = float(value_match.group().replace(' ', ''))
# Если указано в EUR, конвертируем
if 'EUR' in cargo_value_str or '' in cargo_value_str:
cargo_value_usd *= 1.1 # приблизительный курс EUR/USD
insurance_cost = cargo_value_usd * 0.01 # 1% от стоимости груза
# Расчёт компонентов
base_freight = chargeable_weight * base_rate * special_rate_multiplier
fuel_surcharge = max(chargeable_weight * fuel_surcharge_rate, min_fuel_surcharge)
terminal_export = chargeable_weight * terminal_export_rate + terminal_export_fixed
terminal_import = chargeable_weight * 0.38 # конвертация EUR->USD
customs_total = customs_export + customs_import
# Дополнительные услуги
additional_services = shipment.get('additional_services', [])
additional_cost = 0
if isinstance(additional_services, list):
if 'уведомление о прибытии' in str(additional_services).lower():
additional_cost += 25 # USD
# Итог
total_usd = (base_freight + fuel_surcharge + terminal_export +
terminal_import + customs_total + additional_cost + insurance_cost)
# Конвертация в рубли для удобства (курс USD/RUB ~90)
total_rub = total_usd * 90
return {
"total_usd": round(total_usd, 2),
"total_rub": round(total_rub, 2),
"chargeable_weight": chargeable_weight,
"actual_weight": actual_weight,
"volume_weight": round(total_volume_weight, 2)
}
except Exception as e:
# Логирование ошибки, если нужно
return None

101
shipping_types(1).json Normal file
View File

@ -0,0 +1,101 @@
[
{
"id": 1,
"name": "Автомобильная перевозка (LTL)",
"employee_email": "denis.fomin@timnet.ch, Yana.Schkabrova@timnet.ch",
"extra_required_fields": ["hs_code", "customs_clearance_place_export_rf"],
"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Место таможенного оформления в РФ",
"keywords": ["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",
"info_request_template": "Уважаемый (Клиент)!\n\nБлагодарим вас за обращение в компанию SEPTEM. Для расчёта автомобильной перевозки (LTL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nПожалуйста, дополните данные по вашему грузу. Вы можете ответить на это письмо или загрузить недостающие документы через личный кабинет.\n\nС уважением,\nКоманда SEPTEM Cargo"
},
{
"id": 2,
"name": "Автомобильная перевозка (FTL)",
"employee_email": "denis.fomin@timnet.ch, Yana.Schkabrova@timnet.ch",
"extra_required_fields": ["hs_code", "vehicle_type", "customs_clearance_place_export_rf"],
"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Место таможенного оформления в РФ",
"keywords": ["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",
"info_request_template": "Уважаемый (Клиент)!\n\nБлагодарим за обращение в SEPTEM. Для расчёта автомобильной перевозки (FTL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nПожалуйста, дополните данные по грузу.\n\nС уважением,\nКоманда SEPTEM Cargo"
},
{
"id": 3,
"name": "Морская перевозка (LCL)",
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
"extra_required_fields": ["hs_code", "stackable_with_others"],
"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): порт погрузки/выгрузки и морской этап явно указаны в переписке; без доминирования ж/д как основного этапа.",
"keywords": ["море 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",
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта морской сборной перевозки (LCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nПожалуйста, дополните данные.\n\nС уважением,\nКоманда SEPTEM Cargo"
},
{
"id": 4,
"name": "Железнодорожная перевозка (LCL)",
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
"extra_required_fields": ["hs_code", "stackable_with_others"],
"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): станции/терминалы ж/д, контейнер сборный по ж/д; морской этап в запросе не основной.",
"keywords": ["жд 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",
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта железнодорожной сборной перевозки (LCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
},
{
"id": 5,
"name": "Мультимодальная перевозка море + ж/д (LCL)",
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
"extra_required_fields": ["hs_code", "stackable_with_others"],
"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Критерии как для сборного груза при цепочке море и ж/д (интермодаль): в переписке явно присутствуют и морской, и железнодорожный этапы.",
"keywords": ["море жд", "море+жд", "море и жд", "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",
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта мультимодальной перевозки (море + ж/д, LCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
},
{
"id": 6,
"name": "Морская перевозка (FCL)",
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
"extra_required_fields": ["hs_code", "container_type"],
"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-контейнеры под морской фрахт и погрузку на судно.",
"keywords": ["морской 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",
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта морской перевозки (FCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
},
{
"id": 7,
"name": "Железнодорожная перевозка (FCL)",
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
"extra_required_fields": ["hs_code", "container_type"],
"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 мм); без обязательного морского этапа.",
"keywords": ["жд 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",
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта железнодорожной перевозки (FCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
},
{
"id": 8,
"name": "Мультимодальная перевозка море + ж/д (FCL)",
"employee_email": "marina.eremina@timnet.ch, ekaterina.kochenkova@timnet.ch, Yana.Schkabrova@timnet.ch",
"extra_required_fields": ["hs_code", "container_type"],
"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): в запросе явно сочетаются морской и ж/д этапы с полным контейнером (интермодаль).",
"keywords": ["море жд 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",
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта мультимодальной перевозки (море + ж/д, FCL) нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
},
{
"id": 9,
"name": "Авиаперевозка",
"employee_email": "evgeniy.domashnev@timnet.ch, olga.ermakova@timnet.ch, andrey.reshetnyak@timnet.ch, anastasia.fatkina@timnet.ch",
"extra_required_fields": ["hs_code", "stackable_with_others", "total_volume_cbm", "dangerous_goods_clarification", "customs_clearance_place_export_rf"],
"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Фумигация деревянной тары при экспорте из РФ",
"keywords": ["авиа", "авиаперевозка", "самолёт", "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",
"info_request_template": "Уважаемый (Клиент)!\n\nДля расчёта авиаперевозки нам необходима следующая информация:\n\n(Необходимая информация)\n\nС уважением,\nКоманда SEPTEM Cargo"
}
]