PMS告警面板: 加时间窗分钟龄+超龄置灰(默认60min), 三参数注册进RUNTIME_EXTRA可实时调
This commit is contained in:
parent
2315ac3bd3
commit
ae022f960d
|
|
@ -47,6 +47,11 @@ RUNTIME_EXTRA = {
|
|||
# (它只回 ok=False 不抛异常, 调用点又把返回值丢了)。后果是 prev_ymd 永远读回 0,
|
||||
# 「连续 N 日」的按日推进彻底失效, 修复形同虚设。加表时**必须同步加这里**。
|
||||
"PMS_RECON_STREAK_YMD": (0, int, "上次推进连续天数的交易日 YYYYMMDD (同日不重复计数)"),
|
||||
# 2026-08-14: 上游信号告警面板的三个显示参数。原来只在 upstream_signals.py 里用 get_int
|
||||
# 兜底、没登记, 所以设置页调不到、改一次得重新部署。注册进来后可在页面实时调 (即时生效)。
|
||||
"PMS_UPSTREAM_ALERT_SCAN": (300, int, "告警扫描窗: 每轮从告警流取最近 N 条再分类限量 (窗要大于各类上限之和, 防 price_notice 等高频源把风控告警挤出最新N)"),
|
||||
"PMS_UPSTREAM_ALERT_PER_CAT": (20, int, "告警每类上限: 同一类型最多显示最新 N 条 (砍单类刷屏, 不挤别的类)"),
|
||||
"PMS_UPSTREAM_ALERT_MAX_AGE_MIN": (60, int, "告警时间窗(分钟): 超过此龄判为陈旧并在页面置灰标注(不删除, 风控不丢信息), 0=不限龄。与每类上限正交, 先按龄标记再每类限量"),
|
||||
}
|
||||
|
||||
# **读不到时必须按"已暂停"处理的键 (fail-closed)。**
|
||||
|
|
@ -316,6 +321,8 @@ _RANGES = {
|
|||
"PMS_OPEN_REF_DRIFT_MAX": (0, 0.5), "PMS_JUDGE_TICK_BUDGET_SEC": (0, 240),
|
||||
# 上限 240 = scheduler 给调度任务设的软超时。填得比它还大, 预算就形同虚设,
|
||||
# 任务会先被 celery 打死 (而且是在已经落了一部分表之后)。
|
||||
"PMS_UPSTREAM_ALERT_SCAN": (1, 20000), "PMS_UPSTREAM_ALERT_PER_CAT": (1, 1000),
|
||||
"PMS_UPSTREAM_ALERT_MAX_AGE_MIN": (0, 1440), # 0=不限龄, 上限 24 小时
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,12 @@ def _alert_per_cat() -> int:
|
|||
return max(1, param_store.get_int("PMS_UPSTREAM_ALERT_PER_CAT", 20))
|
||||
|
||||
|
||||
def _alert_max_age_sec() -> int:
|
||||
# 告警时间窗(秒): 0=不限龄。只用于给每条打"陈旧"标记(超龄置灰), **不删条目** ——
|
||||
# 用户拍板"置灰标注"而非隐藏, 风控面板不丢信息。与每类上限正交: 先按龄标 stale, 再每类限量。
|
||||
return max(0, param_store.get_int("PMS_UPSTREAM_ALERT_MAX_AGE_MIN", 60)) * 60
|
||||
|
||||
|
||||
def _c208(db: int):
|
||||
"""208(与 SIGNAL_REDIS 同实例)的只读客户端, 按库缓存。"""
|
||||
key = ("208", db)
|
||||
|
|
@ -138,6 +144,8 @@ _ALERT_META = {
|
|||
def _read_alerts(c, ymd):
|
||||
key = "intraday_alerts:%s" % ymd
|
||||
per_cat = _alert_per_cat()
|
||||
max_age = _alert_max_age_sec() # 秒; 0=不限龄。超龄不删, 只标 stale 供页面置灰。
|
||||
now = time.time()
|
||||
counts = {} # cat -> 已收数量; 每类只留最新 per_cat 条(仍按 newest-first)
|
||||
out = []
|
||||
for _id, f in c.xrevrange(key, count=_alert_scan()):
|
||||
|
|
@ -157,10 +165,19 @@ def _read_alerts(c, ymd):
|
|||
d = "看跌" if mdir in ("down", "short", "bear") else ("看涨" if mdir in ("up", "long", "bull") else "—")
|
||||
else:
|
||||
d = dir_fixed
|
||||
# 分钟龄 + 陈旧标记: 时间窗只做"置灰标注", 不删条目(超龄由页面按 stale 置灰)。
|
||||
# trigger_time 是 epoch 毫秒; 缺失/非法 → age_min=None 且不判陈旧(风控从宽, 不误灰)。
|
||||
try:
|
||||
age_sec = now - float(f.get("trigger_time")) / 1000.0
|
||||
age_min = int(age_sec // 60)
|
||||
except (TypeError, ValueError):
|
||||
age_sec, age_min = None, None
|
||||
stale = bool(max_age) and age_sec is not None and age_sec > max_age
|
||||
counts[cat] = counts.get(cat, 0) + 1
|
||||
out.append({"ts_code": f.get("ts_code"), "source": src, "direction": d,
|
||||
"cat": cat, "cat_label": cat_label,
|
||||
"level": f.get("level"), "value": _num(f.get("value")), "time": _hm(f.get("trigger_time"))})
|
||||
"level": f.get("level"), "value": _num(f.get("value")),
|
||||
"time": _hm(f.get("trigger_time")), "age_min": age_min, "stale": stale})
|
||||
return {"key": key, "items": out}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,11 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius:
|
|||
.alert-win .aw-title{font-size:13px;font-weight:640;color:var(--ink-2);}
|
||||
.alert-win .aw-toggle{font-size:11px;font-weight:560;color:var(--accent);cursor:pointer;border:1px solid var(--accent);border-radius:999px;padding:1px 9px;background:var(--accent-weak);white-space:nowrap;}
|
||||
.alert-win .aw-toggle.off{color:var(--muted);border-color:var(--hair);background:var(--surface-2);}
|
||||
/* 陈旧告警置灰 (超龄不隐藏, 只灰: 风控面板不丢信息)。灰=陈旧, 不动红绿(红绿只留给方向)。 */
|
||||
.alert-bar .ab-stale{flex:none;font-size:11px;color:var(--muted);border:1px solid var(--hair);border-radius:999px;padding:0 7px;}
|
||||
.alert-win .el-table__row.alert-stale{opacity:.5;}
|
||||
.alert-win .el-table__row.alert-stale:hover{opacity:.72;}
|
||||
.alert-age{color:var(--muted);font-size:10.5px;margin-left:5px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -196,7 +201,8 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius:
|
|||
<span v-if="alertLatest.direction && alertLatest.direction!=='—'" :class="['看涨','涨'].includes(alertLatest.direction)?'clr-up':'clr-down'">{{ alertLatest.direction }}</span>
|
||||
<span class="muted">{{ alertLatest.cat_label }}</span>
|
||||
<span class="disp" :class="alertLatest.level==='DANGER'?'deny':(alertLatest.level==='WARNING'?'queue':'wait')" style="padding:1px 7px">{{ alertLatest.level }}</span>
|
||||
<span class="muted">{{ alertLatest.time }}</span>
|
||||
<span class="muted">{{ alertLatest.time }}<template v-if="ageText(alertLatest)"> · {{ ageText(alertLatest) }}</template></span>
|
||||
<span v-if="alertLatest.stale" class="ab-stale" title="超过告警时间窗, 已置灰标注为陈旧">陈旧</span>
|
||||
</span>
|
||||
<span class="ab-latest muted" v-else>· 折叠条已隐藏当前类型(展开后可切回)</span>
|
||||
<button class="ab-toggle" @click="showAlerts=!showAlerts">{{ showAlerts?'收起 ▲':'展开全部 ▼' }}</button>
|
||||
|
|
@ -215,12 +221,12 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius:
|
|||
<span class="aw-title">{{ g.label }} <span class="muted" style="font-weight:400">· {{ g.items.length }}</span></span>
|
||||
<span class="aw-toggle" :class="{off:!catShown(g.key)}" @click="toggleCat(g.key)" :title="catShown(g.key)?'点此不在顶部折叠条里显示这类':'点此让这类回到顶部折叠条'">{{ catShown(g.key) ? '折叠条显示 ✓' : '折叠条隐藏' }}</span>
|
||||
</div>
|
||||
<el-table :data="g.items.slice(0,20)" size="small" max-height="240" empty-text="暂无">
|
||||
<el-table :data="g.items.slice(0,20)" size="small" max-height="240" empty-text="暂无" :row-class-name="alertRowClass">
|
||||
<el-table-column label="股票" min-width="140"><template #default="s"><b>{{ nm(s.row.ts_code) }}</b> <span class="muted mono">{{ s.row.ts_code }}</span></template></el-table-column>
|
||||
<el-table-column label="方向" width="58"><template #default="s"><span v-if="s.row.direction && s.row.direction!=='—'" :class="['看涨','涨'].includes(s.row.direction)?'clr-up':'clr-down'">{{ s.row.direction }}</span><span v-else class="muted">—</span></template></el-table-column>
|
||||
<el-table-column label="级别" width="86"><template #default="s"><span class="disp" :class="s.row.level==='DANGER'?'deny':(s.row.level==='WARNING'?'queue':'wait')">{{ s.row.level }}</span></template></el-table-column>
|
||||
<el-table-column label="来源" min-width="150"><template #default="s"><span class="muted" style="font-size:11px">{{ s.row.source }}</span></template></el-table-column>
|
||||
<el-table-column label="时间" width="56" align="right"><template #default="s"><span class="mono muted">{{ s.row.time }}</span></template></el-table-column>
|
||||
<el-table-column label="时间" width="120" align="right"><template #default="s"><span class="mono muted">{{ s.row.time }}</span><span v-if="ageText(s.row)" class="alert-age">{{ ageText(s.row) }}</span></template></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1845,6 +1851,17 @@ createApp({
|
|||
if (it.some(x => x.level === 'WARNING')) return 'warn';
|
||||
return 'none';
|
||||
});
|
||||
// 告警分钟龄文案 + 陈旧行置灰。龄由后端按 trigger_time 算好放 age_min;
|
||||
// stale=超过时间窗 PMS_UPSTREAM_ALERT_MAX_AGE_MIN (置灰不隐藏, 风控面板不丢信息)。
|
||||
function ageText(row){
|
||||
const a = row && row.age_min;
|
||||
if (a === null || a === undefined) return '';
|
||||
if (a <= 0) return '刚刚';
|
||||
if (a < 60) return a + '分钟前';
|
||||
const h = Math.floor(a / 60), m = a % 60;
|
||||
return m ? (h + '小时' + m + '分前') : (h + '小时前');
|
||||
}
|
||||
const alertRowClass = ({ row }) => (row && row.stale) ? 'alert-stale' : '';
|
||||
async function loadUpstream() {
|
||||
const d = await call('get', '/api/upstream-signals');
|
||||
upstream.value = (d && d.sources) ? d : { sources: {} };
|
||||
|
|
@ -2121,7 +2138,7 @@ createApp({
|
|||
showOpLog, showSettings, watchCandidates, marketOpen, marketState, lastRefresh, autoRefresh,
|
||||
upstream, showUpstream, usrc, loadUpstream,
|
||||
showAlerts, alertItems, alertLatest, alertLevelClass, alertGroups, alertStripItems,
|
||||
alertStripCount, alertTotalCount, catShown, toggleCat };
|
||||
alertStripCount, alertTotalCount, catShown, toggleCat, ageText, alertRowClass };
|
||||
}
|
||||
}).use(ElementPlus).mount('#app');
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue