Untitled

mail@pastecode.io avatar
unknown
plain_text
a year ago
6.1 kB
2
Indexable
Never
from __future__ import annotations

import os
import time
from logging import getLogger
from pathlib import Path

import click
import pandas as pd

from interstore_transfer_opt.application import TransferModelDefault
from interstore_transfer_opt.constants import NOW_DATETIME, PROJECT_ROOT
from interstore_transfer_opt.domain import Attributes, Condition, Decision, GeneralConfig, ModeConfig, OverallTransferPlan, read_sales_csv, read_stock_csv, read_store_master
from interstore_transfer_opt.error import InputAlerts
from interstore_transfer_opt.tool import PROGRESS_BAR

LOAD_DATA_PROGRESS = 5
PREPROCESS_PROGRESS = 10
OPTIMIZE_PROGRESS = 90
FINISH_PROGRESS = 100

logger = getLogger(__name__)


def get_latest_path(path: Path | str, ptn: str) -> Path:
    path = Path(path)
    list_sls_csv = [str(p) for p in path.glob(ptn)]
    if len(list_sls_csv) == 0:
        raise ValueError(f"Not found: {path}")
    return Path(max(list_sls_csv, key=os.path.getctime))


@click.command("run_transfer_model")
@click.option("--input", "path_input", type=click.Path(exists=True), default=PROJECT_ROOT / "data")
@click.option("--output", "path_output", type=click.Path(), default=PROJECT_ROOT / "output")
def main(path_input: str, path_output: str):
    path_input = Path(path_input)
    path_output = Path(path_output)
    path_output.mkdir(exist_ok=True, parents=True)
    path_config_general = path_input / "config_general.xlsx"
    path_config_mode = path_input / "config_mode"
    path_download = path_input / "download"
    path_sales = get_latest_path(path_download, ptn="AIM_TOOL_CAL_INTERSTORE_TSF_01_SLS_ACT_*.csv")
    path_stock = get_latest_path(path_download, ptn="AIM_TOOL_CAL_INTERSTORE_TSF_02_STK_ACT_*.csv")
    path_store_master = path_download / "StoreMaster.csv"
    path_decision = get_latest_path(path_input, ptn="input_*.xlsx")

    logger.info("Loading data...")
    sales = read_sales_csv(path_sales)
    stock = read_stock_csv(path_stock)
    store_master = read_store_master(path_store_master, bu=1)
    result_general_config = GeneralConfig.read_excel(path_config_general)
    result_mode_config = ModeConfig.read_dir(path_config_mode)
    result_decision = Decision.read_excel(path_decision)
    PROGRESS_BAR.set_progress(LOAD_DATA_PROGRESS, "Data loaded")
    if result_mode_config.is_success() and result_general_config.is_success() and result_decision.is_success():
        general_config = result_general_config.get()
        mode_config = result_mode_config.get()
        decision = result_decision.get()
    else:
        logger.error("Failed to load data.")
        errors = []
        if not result_general_config.is_success():
            errors += result_general_config.get_errors().data
        if not result_mode_config.is_success():
            errors += result_mode_config.get_errors().data
        if not result_decision.is_success():
            errors += result_decision.get_errors().data
        alerts = InputAlerts(errors)
        alerts.to_excel(path_output / "alerts.xlsx")
        return
    logger.info("Processing data...")
    result_condition = Condition.from_raw_data(general_config, mode_config, decision, sales, stock)
    PROGRESS_BAR.set_progress(LOAD_DATA_PROGRESS, "Preprocess finished")
    if not result_condition.is_success():
        logger.error("Failed to process data.")
        alerts = result_condition.get_errors()
        alerts.to_excel(path_output / "alerts.xlsx")
        return
    result_attrs = Attributes.from_raw_data(decision, sales, store_master)
    if result_attrs.is_success():
        attributes = result_attrs.get()
    else:
        logger.error("Failed to load attributes error. The direction file will miss some info.")
        attributes = None

    condition = result_condition.get()
    condition.to_excel(path_output / f"シミュレーション_{NOW_DATETIME}.xlsx")
    send_capacity = decision.df_store["TtlSendUb"].fillna(9999).astype(int)
    recv_capacity = decision.df_store["TtlRecvUb"].fillna(9999).astype(int)
    results = {}
    elapsed_time = {}
    idx_lane_used = pd.Index([], dtype=str)
    sum_codes = decision.df_product["SUM"].unique()
    opt_percentage = OPTIMIZE_PROGRESS - PREPROCESS_PROGRESS
    if len(sum_codes) == 0:
        sum_cd_percentage = opt_percentage
    else: 
        sum_cd_percentage = opt_percentage/len(sum_codes)
    total_progress = PREPROCESS_PROGRESS 
    logger.info("Start running transfer model")
    for sum_cd in sum_codes:
        logger.info("=====================================")
        logger.info(sum_cd)
        logger.info("=====================================")
        condition_target = condition.filter(sum_cd=sum_cd)
        if condition_target.df_lane_product.empty:
            logger.warning(f"No stock: {sum_cd}")
            continue
        st = time.time()
        model = TransferModelDefault()
        model.build(condition_target, send_capacity=send_capacity, recv_capacity=recv_capacity, idx_lane_used=idx_lane_used)
        model.solve()
        elapsed_time[sum_cd] = time.time() - st
        result = model.result()
        send_capacity = send_capacity.sub(result.send(), fill_value=0)
        recv_capacity = recv_capacity.sub(result.recv(), fill_value=0)
        idx_lane_used = idx_lane_used.union(result.idx_lane_used())
        results[sum_cd] = result
        total_progress += sum_cd_percentage
        PROGRESS_BAR.set_progress(total_progress, "Preprocess finished")
    result_overall = OverallTransferPlan.from_sumwise_plans(results, condition, attributes)
    result_overall.write_review(path_output / f"チェック用_{NOW_DATETIME}.xlsx")
    result_overall.write_direction(path_output / f"振替指示_{NOW_DATETIME}.xlsx")
    df_elapsed_time = pd.Series(elapsed_time)
    df_elapsed_time.to_csv(path_output / f"実行時間_{NOW_DATETIME}.csv", encoding="utf-8-sig")
    PROGRESS_BAR.set_progress(FINISH_PROGRESS, "Simulation process completed")

if __name__ == "__main__":
    main()