~kris/dots

srice

ref: ff7367cb2ef828502c3f7fde003f45d5daa33206 srice/.local/bin/kaiju-probability-index -rwxr-xr-x 10.7 KiB
ff7367cb — Kris Yotam add ram memory visualization script 4 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#!/bin/bash
# Kaiju Probability Index (KPI)
# Composite anomaly score from seismic + thermal data
# Alias: kpi
#
# "If it hits 0.7, nobody goes home."

export DISPLAY=:0

# Cache locations (shared with other maritime scripts)
seismic_cache="$HOME/.cache/maritime/seismic/day.json"
sst_cache="$HOME/.cache/maritime/sst"
kpi_cache="$HOME/.cache/maritime/kpi"
kpi_report="$kpi_cache/report.txt"
kpi_history="$kpi_cache/history.csv"

# Ensure cache exists
mkdir -p "$kpi_cache"

# Weight factors for composite score
SEISMIC_WEIGHT=0.35
THERMAL_WEIGHT=0.30
FREQUENCY_WEIGHT=0.20
DEPTH_WEIGHT=0.15

# Thresholds
THRESHOLD_YELLOW=0.40
THRESHOLD_RED=0.70

# ══════════════════════════════════════════════════════════════════
# DATA COLLECTION FUNCTIONS
# ══════════════════════════════════════════════════════════════════

# Get seismic score (0.0 - 1.0)
# Based on: count of events, max magnitude, clustering
get_seismic_score() {
    if [ ! -f "$seismic_cache" ]; then
        echo "0.0"
        return
    fi

    local count max_mag score

    # Count events in last 24h
    count=$(jq '.metadata.count // 0' "$seismic_cache" 2>/dev/null)

    # Get max magnitude
    max_mag=$(jq '[.features[].properties.mag // 0] | max // 0' "$seismic_cache" 2>/dev/null)

    # Count significant events (M4.0+)
    local significant
    significant=$(jq '[.features[].properties.mag // 0 | select(. >= 4.0)] | length' "$seismic_cache" 2>/dev/null)

    # Calculate score
    # Base: events/500 (500 events = 0.5)
    # Bonus: max_mag/10 (M10 = 1.0)
    # Bonus: significant * 0.05
    score=$(echo "scale=2; ($count / 500) * 0.4 + ($max_mag / 10) * 0.4 + ($significant * 0.05) * 0.2" | bc 2>/dev/null)

    # Clamp to 0.0 - 1.0
    if (( $(echo "$score > 1.0" | bc -l) )); then
        score="1.0"
    elif (( $(echo "$score < 0.0" | bc -l) )); then
        score="0.0"
    fi

    echo "${score:-0.0}"
}

# Get thermal anomaly score (0.0 - 1.0)
# Placeholder - would need actual anomaly parsing
get_thermal_score() {
    # For now, return a baseline value
    # In production, this would parse SST anomaly data
    # and calculate deviation from baseline

    # Check if SST data is recent (within 24h)
    if [ -f "$sst_cache/global.gif" ]; then
        local age
        age=$(( $(date +%s) - $(stat -c %Y "$sst_cache/global.gif" 2>/dev/null || echo 0) ))

        if [ "$age" -lt 86400 ]; then
            # Data is fresh - return moderate baseline
            echo "0.25"
        else
            # Data is stale
            echo "0.10"
        fi
    else
        echo "0.10"
    fi
}

# Get event frequency score (0.0 - 1.0)
# Based on rate of increase in seismic activity
get_frequency_score() {
    if [ ! -f "$seismic_cache" ]; then
        echo "0.0"
        return
    fi

    # Count events in last hour vs last 24h average
    local hour_count day_count hourly_avg ratio

    # This would need the hourly cache - simplified for now
    day_count=$(jq '.metadata.count // 0' "$seismic_cache" 2>/dev/null)
    hourly_avg=$(echo "scale=2; $day_count / 24" | bc 2>/dev/null)

    # Assume current hour is average (placeholder)
    # In production, compare actual hourly counts
    echo "0.20"
}

# Get depth anomaly score (0.0 - 1.0)
# Deep events are more concerning
get_depth_score() {
    if [ ! -f "$seismic_cache" ]; then
        echo "0.0"
        return
    fi

    # Count deep events (>300km)
    local deep_count total_count ratio

    deep_count=$(jq '[.features[].geometry.coordinates[2] // 0 | select(. > 300)] | length' "$seismic_cache" 2>/dev/null)
    total_count=$(jq '.features | length' "$seismic_cache" 2>/dev/null)

    if [ "$total_count" -gt 0 ]; then
        ratio=$(echo "scale=2; $deep_count / $total_count" | bc 2>/dev/null)
        echo "${ratio:-0.0}"
    else
        echo "0.0"
    fi
}

# ══════════════════════════════════════════════════════════════════
# KPI CALCULATION
# ══════════════════════════════════════════════════════════════════

calculate_kpi() {
    local seismic_score thermal_score frequency_score depth_score
    local kpi

    seismic_score=$(get_seismic_score)
    thermal_score=$(get_thermal_score)
    frequency_score=$(get_frequency_score)
    depth_score=$(get_depth_score)

    # Weighted composite
    kpi=$(echo "scale=3; \
        ($seismic_score * $SEISMIC_WEIGHT) + \
        ($thermal_score * $THERMAL_WEIGHT) + \
        ($frequency_score * $FREQUENCY_WEIGHT) + \
        ($depth_score * $DEPTH_WEIGHT)" | bc 2>/dev/null)

    # Generate report
    local status_icon status_text
    if (( $(echo "$kpi >= $THRESHOLD_RED" | bc -l) )); then
        status_icon=""
        status_text="CRITICAL - Anomaly threshold exceeded"
    elif (( $(echo "$kpi >= $THRESHOLD_YELLOW" | bc -l) )); then
        status_icon=""
        status_text="ELEVATED - Monitoring increased"
    else
        status_icon=""
        status_text="NOMINAL - Standard monitoring"
    fi

    cat > "$kpi_report" << EOF
╔══════════════════════════════════════════════════════════════╗
║           KAIJU PROBABILITY INDEX (KPI) REPORT               ║
╠══════════════════════════════════════════════════════════════╣
║  Timestamp: $(date '+%Y-%m-%d %H:%M:%S UTC' -u)
║  Status: $status_icon $status_text
╠══════════════════════════════════════════════════════════════╣
║  COMPOSITE INDEX:  $kpi
╠══════════════════════════════════════════════════════════════╣
║  Component Scores:
║    Seismic Activity:    $seismic_score  (weight: $SEISMIC_WEIGHT)
║    Thermal Anomaly:     $thermal_score  (weight: $THERMAL_WEIGHT)
║    Event Frequency:     $frequency_score  (weight: $FREQUENCY_WEIGHT)
║    Depth Anomaly:       $depth_score  (weight: $DEPTH_WEIGHT)
╠══════════════════════════════════════════════════════════════╣
║  Thresholds:
║    Yellow Alert: >= $THRESHOLD_YELLOW
║    Red Alert:    >= $THRESHOLD_RED
╚══════════════════════════════════════════════════════════════╝
EOF

    # Log to history
    echo "$(date -u +%Y-%m-%dT%H:%M:%SZ),$kpi,$seismic_score,$thermal_score,$frequency_score,$depth_score" >> "$kpi_history"

    echo "$kpi"
}

# ══════════════════════════════════════════════════════════════════
# OUTPUT FUNCTIONS
# ══════════════════════════════════════════════════════════════════

# Status bar output with color coding
tobar() {
    local kpi
    kpi=$(calculate_kpi)

    local icon
    if (( $(echo "$kpi >= $THRESHOLD_RED" | bc -l) )); then
        icon=""
    elif (( $(echo "$kpi >= $THRESHOLD_YELLOW" | bc -l) )); then
        icon=""
    else
        icon=""
    fi

    echo "$icon ${kpi:0:4}"
}

# Show full report
show_report() {
    if [ -f "$kpi_report" ]; then
        cat "$kpi_report"
    else
        calculate_kpi > /dev/null
        cat "$kpi_report"
    fi
}

# Show history
show_history() {
    local lines="${1:-20}"
    echo "KPI History (last $lines entries):"
    echo "════════════════════════════════════"
    echo "Timestamp,KPI,Seismic,Thermal,Freq,Depth"
    tail -n "$lines" "$kpi_history" 2>/dev/null || echo "No history available"
}

# Update underlying data sources
update_sources() {
    echo "Updating data sources..."
    subsea-seismic-monitor update 2>/dev/null &
    sea-surface-temperature-anomaly update 2>/dev/null &
    wait
    calculate_kpi > /dev/null
    echo "KPI recalculated."
    show_report
}

# Display help
help() {
    cat << EOF
Kaiju Probability Index (KPI)
Composite anomaly score from seismic + thermal + acoustic data
════════════════════════════════════════════════════════════════

Usage: kpi [OPTION]

Options:
  (none)           Show KPI value for status bar
  report, r        Show full KPI report
  history, h [N]   Show last N history entries (default: 20)
  update, u        Update source data and recalculate
  calculate, c     Force recalculation
  help             Show this help

Scoring Components:
  Seismic (35%)    USGS earthquake data - count, magnitude, clustering
  Thermal (30%)    SST anomaly deviation from baseline
  Frequency (20%)  Rate of change in seismic activity
  Depth (15%)      Proportion of deep (>300km) events

Thresholds:
   Green   < 0.40   Nominal - standard monitoring
   Yellow  >= 0.40  Elevated - increased monitoring
   Red     >= 0.70  Critical - anomaly threshold exceeded

"If it hits 0.7, nobody goes home."
EOF
}

# ══════════════════════════════════════════════════════════════════
# MAIN
# ══════════════════════════════════════════════════════════════════

case "$1" in
    r*) show_report ;;
    h*) shift; show_history "$1" ;;
    u*) update_sources ;;
    c*) calculate_kpi; show_report ;;
    --help) help ;;
    "") tobar ;;
    *) help ;;
esac

# Status bar click handlers
case $BLOCK_BUTTON in
    1) notify-send -t 60000 "Kaiju Probability Index" "$(show_report)" ;;
    2) update_sources ;;
    3) notify-send -t 30000 "KPI History" "$(show_history 10)" ;;
    6) setsid -f "$TERMINAL" -e "$EDITOR" "$0" ;;
esac