Spaces:
Paused
Paused
| """ | |
| 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() | |