Threadbourne commited on
Commit
ddd39d8
·
verified ·
1 Parent(s): ec47575

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +119 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,121 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
1
  import streamlit as st
2
+ import pandas as pd
3
+ import plotly.express as px
4
+
5
+ st.set_page_config(layout="wide")
6
+ st.title("Thread Pulse")
7
+ st.write(
8
+ "This instrument visualizes long-form conversational dynamics using "
9
+ "derived, non-semantic metrics from selected example threads."
10
+ )
11
+
12
+ DATA_DIR = "/app/src/data"
13
+
14
+ FILES = {
15
+ "Anchor": f"{DATA_DIR}/Anchor_turns.csv",
16
+ "Big Flame": f"{DATA_DIR}/BigFlame_turns.csv",
17
+ }
18
+
19
+ # ---- Load selected thread ----
20
+ thread_name = st.selectbox("Select thread", list(FILES.keys()))
21
+ csv_path = FILES[thread_name]
22
+
23
+ df = pd.read_csv(csv_path).sort_values("turn")
24
+
25
+ # ---- Controls ----
26
+ roll_window = st.slider("Rolling window (turns)", 5, 150, 25)
27
+ show_band = st.checkbox("Show mean ± variance band", value=True)
28
+ k = st.slider("Band width (σ multiplier)", 0.5, 3.0, 1.0, 0.5)
29
+
30
+ scope = st.radio(
31
+ "Compute stability on:",
32
+ ["GPT turns only", "All turns"],
33
+ horizontal=True
34
+ )
35
+
36
+ st.subheader("Stability Detection")
37
+ sigma_thresh = st.slider("Stability threshold (σ)", 10.0, 300.0, 80.0, 5.0)
38
+ persist_len = st.slider("Required persistence (turns)", 10, 200, 50)
39
+
40
+ # ---- Choose series for stability stats ----
41
+ if scope == "GPT turns only":
42
+ dstat = df[df["speaker"] == "gpt"].copy()
43
+ else:
44
+ dstat = df.copy()
45
+
46
+ dstat["tokens_est"] = pd.to_numeric(dstat["tokens_est"], errors="coerce").fillna(0)
47
+
48
+ # Rolling mean & std
49
+ dstat["roll_mean"] = dstat["tokens_est"].rolling(roll_window, min_periods=1).mean()
50
+ dstat["roll_std"] = dstat["tokens_est"].rolling(roll_window, min_periods=1).std().fillna(0)
51
+
52
+ # ---- Time-to-stability detection ----
53
+ stability_turn = None
54
+ roll_std = dstat["roll_std"].to_numpy()
55
+ turns = dstat["turn"].to_numpy()
56
+
57
+ if len(roll_std) > persist_len:
58
+ for i in range(len(roll_std) - persist_len):
59
+ std_slice = roll_std[i:i + persist_len]
60
+ if (std_slice <= sigma_thresh).all():
61
+ stability_turn = int(turns[i])
62
+ break
63
+
64
+ # Variance band bounds
65
+ dstat["upper"] = dstat["roll_mean"] + (k * dstat["roll_std"])
66
+ dstat["lower"] = (dstat["roll_mean"] - (k * dstat["roll_std"])).clip(lower=0)
67
+
68
+ # ---- Plot ----
69
+ fig = px.scatter(
70
+ df,
71
+ x="turn",
72
+ y="tokens_est",
73
+ color="speaker",
74
+ opacity=0.6,
75
+ title="Conversation Rhythm",
76
+ )
77
+
78
+ # Rolling mean line
79
+ fig.add_scatter(
80
+ x=dstat["turn"],
81
+ y=dstat["roll_mean"],
82
+ mode="lines",
83
+ name=f"Rolling mean ({scope.lower()}, w={roll_window})",
84
+ )
85
+
86
+ # Variance band
87
+ if show_band:
88
+ fig.add_scatter(
89
+ x=dstat["turn"],
90
+ y=dstat["lower"],
91
+ mode="lines",
92
+ line=dict(width=0),
93
+ showlegend=False,
94
+ name="Lower bound",
95
+ )
96
+ fig.add_scatter(
97
+ x=dstat["turn"],
98
+ y=dstat["upper"],
99
+ mode="lines",
100
+ fill="tonexty",
101
+ name=f"± {k}σ band",
102
+ opacity=0.2,
103
+ )
104
+
105
+ # Stability marker line
106
+ if stability_turn is not None:
107
+ fig.add_vline(
108
+ x=stability_turn,
109
+ line_dash="dot",
110
+ line_color="green",
111
+ annotation_text="Stability onset",
112
+ annotation_position="top left",
113
+ )
114
+
115
+ st.plotly_chart(fig, use_container_width=True)
116
 
117
+ # ---- Report ----
118
+ if stability_turn is not None:
119
+ st.success(f"Stability detected at turn {stability_turn} (σ ≤ {sigma_thresh} for {persist_len} turns)")
120
+ else:
121
+ st.warning("No stable regime detected under current parameters.")