fix(hold-history): switch all metrics from container count to QTY sum

Rename page title to "Hold 歷史績效". Change trend, reason pareto, and
duration derivation to use QTY-based counting so cards, trend chart, and
analytical charts are consistent with each other.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
egg
2026-03-02 08:56:26 +08:00
parent d8e91624d4
commit 803690646b
2 changed files with 15 additions and 14 deletions

View File

@@ -487,7 +487,7 @@ onMounted(() => {
<div class="dashboard hold-history-page"> <div class="dashboard hold-history-page">
<header class="header hold-history-header"> <header class="header hold-history-header">
<div class="header-left"> <div class="header-left">
<h1>Hold 歷史績效 Dashboard</h1> <h1>Hold 歷史績效</h1>
<span class="hold-type-badge">{{ holdTypeLabel }}</span> <span class="hold-type-badge">{{ holdTypeLabel }}</span>
</div> </div>
<div class="header-right"> <div class="header-right">

View File

@@ -376,26 +376,27 @@ def _derive_trend(df: pd.DataFrame) -> Dict[str, Any]:
day_data[key] = _empty_trend_metrics() day_data[key] = _empty_trend_metrics()
continue continue
# holdQty: distinct containers on hold as of this day # holdQty: total QTY on hold as of this day
on_hold = (tdf["HOLD_DAY"] <= d) & ( on_hold = (tdf["HOLD_DAY"] <= d) & (
tdf["RELEASE_DAY"].isna() | (tdf["RELEASE_DAY"] > d) tdf["RELEASE_DAY"].isna() | (tdf["RELEASE_DAY"] > d)
) )
hold_qty = int(tdf.loc[on_hold, "CONTAINERID"].nunique()) hold_qty = _safe_int(tdf.loc[on_hold, "QTY"].sum())
# newHoldQty: new holds arriving this day (dedup) # newHoldQty: QTY of new holds arriving this day (dedup)
new_mask = (tdf["HOLD_DAY"] == d) & (tdf["RN_HOLD_DAY"] == 1) new_mask = (tdf["HOLD_DAY"] == d) & (tdf["RN_HOLD_DAY"] == 1)
new_hold_qty = int(new_mask.sum()) new_hold_qty = _safe_int(tdf.loc[new_mask, "QTY"].sum())
# releaseQty: releases on this day # releaseQty: QTY released on this day
release_qty = int((tdf["RELEASE_DAY"] == d).sum()) release_mask = tdf["RELEASE_DAY"] == d
release_qty = _safe_int(tdf.loc[release_mask, "QTY"].sum())
# futureHoldQty: future holds on this day # futureHoldQty: QTY of future holds on this day
future_mask = ( future_mask = (
(tdf["HOLD_DAY"] == d) (tdf["HOLD_DAY"] == d)
& (tdf["IS_FUTURE_HOLD"] == 1) & (tdf["IS_FUTURE_HOLD"] == 1)
& (tdf["FUTURE_HOLD_FLAG"] == 1) & (tdf["FUTURE_HOLD_FLAG"] == 1)
) )
future_hold_qty = int(future_mask.sum()) future_hold_qty = _safe_int(tdf.loc[future_mask, "QTY"].sum())
day_data[key] = { day_data[key] = {
"holdQty": hold_qty, "holdQty": hold_qty,
@@ -428,15 +429,15 @@ def _derive_reason_pareto(df: pd.DataFrame) -> Dict[str, Any]:
.agg(count=("CONTAINERID", "count"), qty=("QTY", "sum")) .agg(count=("CONTAINERID", "count"), qty=("QTY", "sum"))
.reset_index() .reset_index()
) )
grouped = grouped.sort_values("count", ascending=False) grouped = grouped.sort_values("qty", ascending=False)
total = grouped["count"].sum() total_qty = grouped["qty"].sum()
items: List[Dict[str, Any]] = [] items: List[Dict[str, Any]] = []
cumulative = 0.0 cumulative = 0.0
for _, row in grouped.iterrows(): for _, row in grouped.iterrows():
count = _safe_int(row["count"]) count = _safe_int(row["count"])
qty = _safe_int(row["qty"]) qty = _safe_int(row["qty"])
pct = round((count / total * 100) if total > 0 else 0, 2) pct = round((qty / total_qty * 100) if total_qty > 0 else 0, 2)
cumulative += pct cumulative += pct
items.append( items.append(
{ {
@@ -466,7 +467,7 @@ def _derive_duration(df: pd.DataFrame) -> Dict[str, Any]:
return {"items": []} return {"items": []}
hours = released["HOLD_HOURS"] hours = released["HOLD_HOURS"]
total = len(released) total_qty = _safe_int(released["QTY"].sum())
buckets = [ buckets = [
("<4h", hours < 4), ("<4h", hours < 4),
@@ -479,7 +480,7 @@ def _derive_duration(df: pd.DataFrame) -> Dict[str, Any]:
for label, mask in buckets: for label, mask in buckets:
count = int(mask.sum()) count = int(mask.sum())
qty = _safe_int(released.loc[mask, "QTY"].sum()) qty = _safe_int(released.loc[mask, "QTY"].sum())
pct = round((count / total * 100) if total > 0 else 0, 2) pct = round((qty / total_qty * 100) if total_qty > 0 else 0, 2)
items.append({"range": label, "count": count, "qty": qty, "pct": pct}) items.append({"range": label, "count": count, "qty": qty, "pct": pct})
return {"items": items} return {"items": items}