Add severity + direction alert strips
Split the alerts summary into labeled severity and direction strips so bearish/bullish skew is visible at a glance.
This commit is contained in:
parent
0036b6badc
commit
684cda0e94
2 changed files with 88 additions and 32 deletions
|
|
@ -586,9 +586,9 @@ h1 {
|
||||||
color: #6f5b39;
|
color: #6f5b39;
|
||||||
}
|
}
|
||||||
|
|
||||||
.severity-strip {
|
.alert-strips {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
|
|
@ -596,16 +596,21 @@ h1 {
|
||||||
background: rgba(255, 255, 255, 0.7);
|
background: rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.severity-strip-header {
|
.alert-strip-section {
|
||||||
display: flex;
|
display: grid;
|
||||||
justify-content: space-between;
|
gap: 6px;
|
||||||
font-size: 0.75rem;
|
|
||||||
color: #6f5b39;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.severity-strip-bar {
|
.alert-strip-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #6f5b39;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.22em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-strip-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
|
|
@ -614,28 +619,42 @@ h1 {
|
||||||
background: rgba(111, 91, 57, 0.08);
|
background: rgba(111, 91, 57, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.severity-segment {
|
.strip-segment {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 0.7rem;
|
font-size: 0.65rem;
|
||||||
color: #fffdf7;
|
color: #fffdf7;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.severity-strip .severity-high {
|
.alert-strip-bar .severity-high {
|
||||||
background: rgba(196, 111, 42, 0.85);
|
background: rgba(196, 111, 42, 0.85);
|
||||||
color: #3b1a09;
|
color: #3b1a09;
|
||||||
}
|
}
|
||||||
|
|
||||||
.severity-strip .severity-medium {
|
.alert-strip-bar .severity-medium {
|
||||||
background: rgba(31, 74, 123, 0.8);
|
background: rgba(31, 74, 123, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.severity-strip .severity-low {
|
.alert-strip-bar .severity-low {
|
||||||
background: rgba(47, 109, 79, 0.8);
|
background: rgba(47, 109, 79, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-strip-bar .direction-bullish {
|
||||||
|
background: rgba(47, 109, 79, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-strip-bar .direction-bearish {
|
||||||
|
background: rgba(196, 111, 42, 0.85);
|
||||||
|
color: #3b1a09;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-strip-bar .direction-neutral {
|
||||||
|
background: rgba(111, 91, 57, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
.flow-meta span {
|
.flow-meta span {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -971,7 +971,7 @@ type AlertSeverityStripProps = {
|
||||||
const AlertSeverityStrip = ({ alerts }: AlertSeverityStripProps) => {
|
const AlertSeverityStrip = ({ alerts }: AlertSeverityStripProps) => {
|
||||||
const windowMs = 30 * 60 * 1000;
|
const windowMs = 30 * 60 * 1000;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const counts = alerts.reduce(
|
const severityCounts = alerts.reduce(
|
||||||
(acc, alert) => {
|
(acc, alert) => {
|
||||||
if (now - alert.source_ts > windowMs) {
|
if (now - alert.source_ts > windowMs) {
|
||||||
return acc;
|
return acc;
|
||||||
|
|
@ -988,26 +988,63 @@ const AlertSeverityStrip = ({ alerts }: AlertSeverityStripProps) => {
|
||||||
{ high: 0, medium: 0, low: 0 }
|
{ high: 0, medium: 0, low: 0 }
|
||||||
);
|
);
|
||||||
|
|
||||||
const total = counts.high + counts.medium + counts.low;
|
const directionCounts = alerts.reduce(
|
||||||
const highPct = total > 0 ? (counts.high / total) * 100 : 0;
|
(acc, alert) => {
|
||||||
const mediumPct = total > 0 ? (counts.medium / total) * 100 : 0;
|
if (now - alert.source_ts > windowMs) {
|
||||||
const lowPct = total > 0 ? (counts.low / total) * 100 : 0;
|
return acc;
|
||||||
|
}
|
||||||
|
const direction = normalizeDirection(alert.hits[0]?.direction ?? "neutral");
|
||||||
|
acc[direction] += 1;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ bullish: 0, bearish: 0, neutral: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const severityTotal = severityCounts.high + severityCounts.medium + severityCounts.low;
|
||||||
|
const highPct = severityTotal > 0 ? (severityCounts.high / severityTotal) * 100 : 0;
|
||||||
|
const mediumPct = severityTotal > 0 ? (severityCounts.medium / severityTotal) * 100 : 0;
|
||||||
|
const lowPct = severityTotal > 0 ? (severityCounts.low / severityTotal) * 100 : 0;
|
||||||
|
|
||||||
|
const directionTotal =
|
||||||
|
directionCounts.bullish + directionCounts.bearish + directionCounts.neutral;
|
||||||
|
const bullishPct = directionTotal > 0 ? (directionCounts.bullish / directionTotal) * 100 : 0;
|
||||||
|
const bearishPct = directionTotal > 0 ? (directionCounts.bearish / directionTotal) * 100 : 0;
|
||||||
|
const neutralPct = directionTotal > 0 ? (directionCounts.neutral / directionTotal) * 100 : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="severity-strip">
|
<div className="alert-strips">
|
||||||
<div className="severity-strip-header">
|
<div className="alert-strip-section">
|
||||||
<span>Last 30m</span>
|
<div className="alert-strip-header">
|
||||||
<span>{total} alerts</span>
|
<span>Severity (last 30m)</span>
|
||||||
|
<span>{severityTotal} alerts</span>
|
||||||
|
</div>
|
||||||
|
<div className="alert-strip-bar">
|
||||||
|
<div className="strip-segment severity-high" style={{ width: `${highPct}%` }}>
|
||||||
|
{severityCounts.high > 0 ? `High ${severityCounts.high}` : ""}
|
||||||
|
</div>
|
||||||
|
<div className="strip-segment severity-medium" style={{ width: `${mediumPct}%` }}>
|
||||||
|
{severityCounts.medium > 0 ? `Med ${severityCounts.medium}` : ""}
|
||||||
|
</div>
|
||||||
|
<div className="strip-segment severity-low" style={{ width: `${lowPct}%` }}>
|
||||||
|
{severityCounts.low > 0 ? `Low ${severityCounts.low}` : ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="severity-strip-bar">
|
<div className="alert-strip-section">
|
||||||
<div className="severity-segment severity-high" style={{ width: `${highPct}%` }}>
|
<div className="alert-strip-header">
|
||||||
{counts.high > 0 ? counts.high : ""}
|
<span>Direction (last 30m)</span>
|
||||||
|
<span>{directionTotal} alerts</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="severity-segment severity-medium" style={{ width: `${mediumPct}%` }}>
|
<div className="alert-strip-bar">
|
||||||
{counts.medium > 0 ? counts.medium : ""}
|
<div className="strip-segment direction-bullish" style={{ width: `${bullishPct}%` }}>
|
||||||
</div>
|
{directionCounts.bullish > 0 ? `Bull ${directionCounts.bullish}` : ""}
|
||||||
<div className="severity-segment severity-low" style={{ width: `${lowPct}%` }}>
|
</div>
|
||||||
{counts.low > 0 ? counts.low : ""}
|
<div className="strip-segment direction-bearish" style={{ width: `${bearishPct}%` }}>
|
||||||
|
{directionCounts.bearish > 0 ? `Bear ${directionCounts.bearish}` : ""}
|
||||||
|
</div>
|
||||||
|
<div className="strip-segment direction-neutral" style={{ width: `${neutralPct}%` }}>
|
||||||
|
{directionCounts.neutral > 0 ? `Neut ${directionCounts.neutral}` : ""}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue