feat: account for defects and five-unit batches in delivery forecasts
This commit is contained in:
@@ -83,6 +83,8 @@ ALLOWED_VERSIONS = {
|
||||
"170H": {"V3"},
|
||||
}
|
||||
CANDIDATE_BATCH_SIZE = 4
|
||||
DEFECT_RATE_PERCENT = 5
|
||||
PRODUCTION_BATCH_SIZE = 5
|
||||
CANDIDATE_CONTINUATION_TTL_SECONDS = 6 * 60 * 60
|
||||
RECEIVED_REACTION_TYPE = "OnIt"
|
||||
|
||||
@@ -180,6 +182,15 @@ class CandidateUnavailableError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def production_quantity_for_demand(quantity: int) -> int:
|
||||
"""Cover 5% defects, then round up to a batch of five without float error."""
|
||||
if isinstance(quantity, bool) or not isinstance(quantity, int) or quantity <= 0:
|
||||
raise InputError("需求数量必须为正整数。")
|
||||
numerator = quantity * 100
|
||||
denominator = (100 - DEFECT_RATE_PERCENT) * PRODUCTION_BATCH_SIZE
|
||||
return ((numerator + denominator - 1) // denominator) * PRODUCTION_BATCH_SIZE
|
||||
|
||||
|
||||
_candidate_continuations: dict[str, CandidateContinuation] = {}
|
||||
_candidate_continuation_lock = threading.Lock()
|
||||
|
||||
@@ -819,11 +830,12 @@ def overall_judgment_lines(candidates: list[dict[str, Any]]) -> list[str]:
|
||||
]
|
||||
|
||||
|
||||
def candidate_report_lines(candidate: dict[str, Any], quantity: int) -> list[str]:
|
||||
def candidate_report_lines(candidate: dict[str, Any], quantity: int, production_quantity: int) -> list[str]:
|
||||
details = candidate["details"]
|
||||
return [
|
||||
f"{candidate['index']}. {candidate['model']}({candidate['type']})",
|
||||
f"- 数量:{quantity}台",
|
||||
f"- 需求数量:{quantity}台",
|
||||
f"- 排产测算数量:{production_quantity}台",
|
||||
f"- {details['prediction']}",
|
||||
f"- 可用库存/覆盖情况:{details['inventory']}",
|
||||
f"- 主导因素:{details['main_factor']}",
|
||||
@@ -894,6 +906,9 @@ def render_record(
|
||||
"",
|
||||
f"**输入型号**:{request.model}",
|
||||
f"**输入数量**:{request.quantity}台",
|
||||
f"**排产测算数量(API查询数量)**:{production_quantity_for_demand(request.quantity)}台",
|
||||
"**数量计算规则**:原始需求÷95%良率,再按5台的倍数向上取整。",
|
||||
"**库存覆盖口径**:按排产测算数量判断。",
|
||||
f"**生成时间**:{generation_time:%Y-%m-%d %H:%M:%S %z}",
|
||||
f"**规则源实际路径**:`{RULE_SOURCE_DISPLAY}`",
|
||||
f"**接口依据**:`{API_SOURCE_DISPLAY}`",
|
||||
@@ -969,6 +984,7 @@ def write_record(
|
||||
def build_report(request: RequestInput, candidate_offset: int = 0) -> str:
|
||||
if candidate_offset < 0:
|
||||
raise InputError("候选批次位置无效,请重新发起型号和数量查询。")
|
||||
production_quantity = production_quantity_for_demand(request.quantity)
|
||||
|
||||
all_candidate_specs = generate_candidate_specs(request.model)
|
||||
candidate_specs = all_candidate_specs[
|
||||
@@ -982,12 +998,12 @@ def build_report(request: RequestInput, candidate_offset: int = 0) -> str:
|
||||
baseline: dict[str, Any] | None = None
|
||||
if is_initial_batch:
|
||||
try:
|
||||
baseline_payload = run_controlled_query(request)
|
||||
baseline_payload = run_controlled_query(RequestInput(request.model, production_quantity))
|
||||
except CandidateUnavailableError as exc:
|
||||
raise RuntimeError(f"原型号未生成候选结果。\n原因:{exc}") from exc
|
||||
baseline = lead_time_details(
|
||||
baseline_payload,
|
||||
request.quantity,
|
||||
production_quantity,
|
||||
)
|
||||
if not baseline["has_lead_time"]:
|
||||
raise RuntimeError(
|
||||
@@ -1012,12 +1028,12 @@ def build_report(request: RequestInput, candidate_offset: int = 0) -> str:
|
||||
|
||||
def query_candidate(spec: dict[str, str]) -> tuple[dict[str, str], dict[str, Any] | None]:
|
||||
try:
|
||||
payload = run_controlled_query(RequestInput(spec["model"], request.quantity))
|
||||
payload = run_controlled_query(RequestInput(spec["model"], production_quantity))
|
||||
except CandidateUnavailableError as exc:
|
||||
return {**spec, "unavailable_reason": str(exc)}, None
|
||||
details = lead_time_details(
|
||||
payload,
|
||||
request.quantity,
|
||||
production_quantity,
|
||||
)
|
||||
if not details["has_lead_time"]:
|
||||
return {**spec, "unavailable_reason": details["unavailable_reason"]}, None
|
||||
@@ -1088,6 +1104,8 @@ def build_report(request: RequestInput, candidate_offset: int = 0) -> str:
|
||||
),
|
||||
"",
|
||||
f"输入需求:{request.model},{request.quantity}台",
|
||||
f"排产测算数量:{production_quantity}台(不良率{DEFECT_RATE_PERCENT}%,按{PRODUCTION_BATCH_SIZE}台的倍数向上取整)",
|
||||
"库存覆盖口径:按排产测算数量判断",
|
||||
f"数据时间戳:{data_timestamp}",
|
||||
"状态:试运行版输出 / 内部候选",
|
||||
"判断等级:AM516算法预测参考,需人工复核",
|
||||
@@ -1098,7 +1116,7 @@ def build_report(request: RequestInput, candidate_offset: int = 0) -> str:
|
||||
"",
|
||||
]
|
||||
for candidate_item in candidates:
|
||||
report_lines.extend(candidate_report_lines(candidate_item, request.quantity))
|
||||
report_lines.extend(candidate_report_lines(candidate_item, request.quantity, production_quantity))
|
||||
report_lines.append("不可用候选:")
|
||||
if unavailable:
|
||||
for item in unavailable:
|
||||
|
||||
Reference in New Issue
Block a user