DidItChange / app.py
Aluode's picture
Upload 10 files
225df94 verified
Raw
History Blame Contribute Delete
5.54 kB
"""
app.py — "Did Something Actually Change?"
A noise-vs-real-change detector for everyday numbers. No jargon, no dashboards:
paste your daily numbers, get a plain answer and the day it happened.
Powered by the same Clutch controller as the compute demo (PerceptionLab).
"""
import matplotlib
matplotlib.use("Agg")
from matplotlib.figure import Figure
import numpy as np
import gradio as gr
from change import parse_numbers, detect, verdict_text, example_series
GREEN = "#2e9e5b"
RED = "#d64545"
BAND = "#8fbfe0"
INK = "#1b1b1b"
EXAMPLES = ["— my own numbers (paste or upload below) —",
"Weight — diet starts working",
"Sleep — nothing changed",
"Electricity — bill jumps",
"Spending — slow creep"]
def make_plot(y, res, unit, period):
n = len(y)
fig = Figure(figsize=(7.2, 3.9), dpi=96)
ax = fig.add_subplot(111)
t = np.arange(n)
# the "normal wobble" band around a light rolling median
k = max(5, res["window"])
pad = np.pad(y, (k // 2, k - k // 2 - 1), mode="edge")
roll = np.array([np.median(pad[i:i + k]) for i in range(n)])
ax.fill_between(t, roll - res["scale"], roll + res["scale"],
color=BAND, alpha=0.30, label="your normal wobble")
ax.plot(t, y, color=INK, lw=1.3, marker="o", ms=2.6, label="your numbers")
for i, e in enumerate([e for e in res["events"] if e["kind"] == "shift"]):
ax.axvline(e["at"], color=RED, lw=1.6)
ax.text(e["at"], ax.get_ylim()[1], f" changed here ({period} {e['at']})",
color=RED, fontsize=9, va="top", fontweight="bold")
lo, hi = max(0, e["at"] - res["window"]), min(n, e["at"] + res["window"])
ax.hlines(e["before"], max(0, e["at"] - 2 * res["window"]), e["at"],
color=GREEN, lw=2.2, alpha=0.9)
ax.hlines(e["after"], e["at"], hi, color=RED, lw=2.2, alpha=0.9)
u = f" ({unit})" if unit else ""
ax.set_xlabel(period)
ax.set_ylabel(f"value{u}")
verdictish = "real change marked in red" if any(
e["kind"] == "shift" for e in res["events"]) else "everything inside the blue band = just noise"
ax.set_title(verdictish, fontsize=11)
ax.legend(fontsize=8, loc="best")
ax.grid(alpha=0.22)
fig.tight_layout()
return fig
def run_check(example, pasted, file_obj, unit, period, sensitivity):
if example != EXAMPLES[0]:
y, unit, period = example_series(example)
note = f"Example data: *{example}* ({len(y)} {period}s). Paste your own to check your life instead."
else:
y, err = parse_numbers(text=pasted, file_obj=file_obj)
if y is None:
return err, None
note = f"Read {len(y)} values."
unit = (unit or "").strip()
period = period or "day"
# sensitivity slider: 1 = strict … 3 = eager -> gate trip multiplier 1.6 … 0.6
sens = {1: 1.6, 2: 1.0, 3: 0.6}[int(sensitivity)]
res = detect(y, sensitivity=sens)
text = note + "\n\n" + verdict_text(y, res, unit, period)
return text, make_plot(y, res, unit, period)
HEADER = """
# 🎲 Did Something Actually Change?
Everyone tracks *something* — weight, spending, sleep, an electricity bill, a kid's
screen time. And everyone makes the same two mistakes: **panicking at random wobble**
and **missing slow real change**. Paste your numbers and get a straight answer:
> **"Just noise — relax"**  or  **"Yes — it really changed, around day 47,
> from 84.1 to 82.6."**
No account, nothing stored, nothing sent anywhere except this page. Try an example
first to see what it does.
"""
FOOTER = """
---
<small>Under the hood this is the *Clutch* — a tiny controller that predicts your next
number from your recent trend and only raises its hand when reality breaks the
prediction harder than your normal wobble. The same engine gates expensive computation
in the [companion Space](https://huggingface.co/spaces/Aluode/Clutch2). Built by Antti
Luode (PerceptionLab). It is a statistics tool, not medical or financial advice.
*Do not hype. Do not lie. Just show.*</small>
"""
with gr.Blocks(title="Did Something Actually Change?") as demo:
gr.Markdown(HEADER)
with gr.Row():
with gr.Column(scale=1):
g_ex = gr.Dropdown(EXAMPLES, value=EXAMPLES[1], label="Try an example — or use your own")
g_paste = gr.Textbox(lines=8, label="Your numbers, oldest first (one per line — commas as decimals are fine)",
placeholder="84,2\n84,5\n83,9\n84,1\n...")
g_file = gr.File(label="…or upload a CSV/TXT (last column is used)",
file_types=[".csv", ".txt"])
with gr.Row():
g_unit = gr.Textbox(label="unit (optional)", placeholder="kg / € / h", scale=1)
g_period = gr.Dropdown(["day", "night", "week", "month", "measurement"],
value="day", label="one value per…", scale=1)
g_sens = gr.Slider(1, 3, 2, step=1,
label="suspicion level (1 = only flag the undeniable · 3 = flag early hints)")
g_run = gr.Button("🎲 Check my numbers", variant="primary")
with gr.Column(scale=2):
g_plot = gr.Plot(label="your numbers, judged")
g_md = gr.Markdown()
g_run.click(run_check, [g_ex, g_paste, g_file, g_unit, g_period, g_sens],
[g_md, g_plot])
gr.Markdown(FOOTER)
if __name__ == "__main__":
demo.launch()