abdessamad-bourkibate's picture
Rename docker/app.py to docker/main.py
9c46086 verified
import io
import datetime
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import gradio as gr
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.units import cm
from reportlab.lib.utils import ImageReader
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from engine.validation import validate_inputs
from engine.thresholds import compute_diagnostics, interpret_governance, euler_simulation
RIGHTS_HOLDER_LINE = "Rights holder: Abdessamad Bourkibate (Morocco) — Apache-2.0"
def _register_pdf_font() -> str:
candidates = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed.ttf",
]
for p in candidates:
try:
pdfmetrics.registerFont(TTFont("UFont", p))
return "UFont"
except Exception:
continue
return "Helvetica"
def _fig_to_png_bytes(fig, dpi=360) -> io.BytesIO:
buf = io.BytesIO()
fig.savefig(buf, format="png", dpi=dpi, bbox_inches="tight")
buf.seek(0)
return buf
def _wrap_lines(font_name: str, font_size: int, text: str, max_width_pt: float):
words = (text or "").replace("\r", "").split()
if not words:
return [""]
lines, cur = [], words[0]
for w in words[1:]:
trial = cur + " " + w
if pdfmetrics.stringWidth(trial, font_name, font_size) <= max_width_pt:
cur = trial
else:
lines.append(cur)
cur = w
lines.append(cur)
return lines
def _draw_wrapped(c, x, y, font, size, text, max_w, leading=13):
c.setFont(font, size)
for para in (text or "").split("\n"):
if para.strip() == "":
y -= leading
continue
for ln in _wrap_lines(font, size, para, max_w):
c.drawString(x, y, ln)
y -= leading
return y
def make_pdf(title: str, diagnostics: dict, gov: dict, t: np.ndarray, R: np.ndarray) -> str:
font = _register_pdf_font()
out_path = "/tmp/FDRSM4_Threshold_Report.pdf"
c = canvas.Canvas(out_path, pagesize=A4)
W, H = A4
mx = 2.0 * cm
max_w = W - 4.0 * cm
def header():
# Calm teal/blue (psych-friendly)
c.setFillColorRGB(0.07, 0.19, 0.22)
c.rect(0, H - 2.2*cm, W, 2.2*cm, fill=1, stroke=0)
c.setFillColorRGB(0.97, 0.99, 0.99)
c.setFont(font, 14)
c.drawString(mx, H - 1.25*cm, (title or "FDRSM-4 Report")[:95])
c.setFont(font, 9.5)
c.drawString(mx, H - 1.85*cm, f"Generated: {datetime.date.today().isoformat()}")
def footer(page_no):
c.setFillColorRGB(0.35, 0.40, 0.48)
c.setFont(font, 9)
c.drawString(mx, 1.1*cm, RIGHTS_HOLDER_LINE)
c.drawRightString(W - mx, 1.1*cm, f"Page {page_no}")
# Figure: trajectory
fig = plt.figure(figsize=(6.2, 4.0))
plt.plot(t, R, linewidth=2.4)
plt.xlabel("Time t")
plt.ylabel("Risk R(t)")
plt.title("Risk trajectory (Euler integration)")
plt.grid(True, alpha=0.25)
img = ImageReader(_fig_to_png_bytes(fig, dpi=360))
plt.close(fig)
# Page 1 (text)
page = 1
header()
y = H - 3.0*cm
c.setFillColorRGB(0.10, 0.10, 0.10)
y = _draw_wrapped(
c, mx, y, font, 11,
"Threshold Engine — Formal Diagnostics",
max_w, leading=14
)
y -= 6
diag_txt = (
"Model:\n"
" dR/dt = (αD − βk)R\n\n"
f"Computed:\n"
f" λ = {diagnostics['lambda']:.6f}\n"
f" Δ = {diagnostics['Delta']:.6f}\n"
f" F = {diagnostics['F']:.6f}\n"
f" Regime: {diagnostics['regime']}\n"
f" Fragility: {diagnostics['fragility']}\n"
f" Boundary k*: (α/β)D = {diagnostics['boundary_k']:.6f}\n"
f" Margin to boundary: k − k* = {diagnostics['margin_to_boundary']:.6f}\n"
)
y = _draw_wrapped(c, mx, y, font, 10, diag_txt, max_w, leading=13)
y -= 8
gov_txt = (
"Governance interpretation:\n"
f"- Posture: {gov['posture']}\n"
f"- Decision: {gov['decision']}\n"
f"- Risk note: {gov['risk']}\n"
)
y = _draw_wrapped(c, mx, y, font, 10, gov_txt, max_w, leading=13)
footer(page)
c.showPage()
page += 1
# Page 2 (figure)
header()
y = H - 3.0*cm
c.setFillColorRGB(0.10, 0.10, 0.10)
c.setFont(font, 11)
c.drawString(mx, y, "Figure — Risk trajectory")
y -= 0.6*cm
img_w = W - 4.0*cm
img_h = 13.0*cm
c.drawImage(img, mx, y - img_h, width=img_w, height=img_h, mask="auto")
footer(page)
c.save()
return out_path
def run_engine(alpha, beta, D, k, R0, T, n, tol, eps):
v = validate_inputs(alpha, beta, D, k, R0, T, n, tol, eps)
if not v["ok"]:
msg = "Input errors:\n- " + "\n- ".join(v["errors"])
return None, None, None, msg, None
diag = compute_diagnostics(alpha, beta, D, k, tol, eps)
gov = interpret_governance(diag["regime"], diag["fragility"], diag["Delta"], diag["margin_to_boundary"])
t, R = euler_simulation(alpha, beta, D, k, R0, T, int(n))
# Plot 1: R(t)
plt.rcParams.update({
"font.size": 10,
"axes.titlesize": 11,
"axes.labelsize": 10,
"axes.spines.top": False,
"axes.spines.right": False,
})
fig1 = plt.figure(figsize=(6.2, 4.0))
plt.plot(t, R, linewidth=2.4)
plt.xlabel("Time t")
plt.ylabel("Risk R(t)")
plt.title("Risk trajectory (Euler integration)")
plt.grid(True, alpha=0.25)
# Plot 2: stability map
fig2 = plt.figure(figsize=(6.2, 4.0))
Dmax = max(6.0, float(D) * 2.0 + 1.0)
Dg = np.linspace(0, Dmax, 360)
k_line = (float(alpha) / float(beta)) * Dg
plt.plot(Dg, k_line, linestyle="--", linewidth=2.0, label="Boundary k = (α/β)·D")
plt.scatter([float(D)], [float(k)], s=80, label="Current (D,k)")
plt.xlabel("Dependency intensity D")
plt.ylabel("Authority gain k")
plt.title("Stability map (stable region above boundary)")
plt.grid(True, alpha=0.25)
plt.legend()
# Sensitivity table as text
sens_lines = ["Local sensitivity ranking of λ:"]
for name, val in diag["sens_ranked"]:
sens_lines.append(f"- {name}: {val:+.6f}")
report = (
f"Regime: {diag['regime']}\n"
f"Fragility: {diag['fragility']}\n\n"
f"λ = {diag['lambda']:.6f}\n"
f"Δ = {diag['Delta']:.6f}\n"
f"F = {diag['F']:.6f}\n"
f"k* = (α/β)D = {diag['boundary_k']:.6f}\n"
f"k − k* = {diag['margin_to_boundary']:.6f}\n\n"
f"Posture: {gov['posture']}\n"
f"Decision: {gov['decision']}\n"
f"Risk note: {gov['risk']}\n\n"
+ "\n".join(sens_lines)
)
pdf_path = make_pdf("FDRSM-4 — Threshold Engine Report", diag, gov, t, R)
return fig1, fig2, pdf_path, report, diag
THEME = gr.themes.Soft(
primary_hue="teal",
secondary_hue="blue",
neutral_hue="slate",
radius_size=gr.themes.sizes.radius_lg,
font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
)
HEADER_MD = r"""
# FDRSM-4 — Threshold Engine (Docker)
This Space implements a **decision-facing threshold layer** on top of the FDRSM series.
\[
\dot{R}(t) = (\alpha D - \beta k)R(t)
\]
\[
\lambda = \alpha D - \beta k,\quad
\Delta = \beta k - \alpha D,\quad
F = \frac{|\Delta|}{|\alpha D| + |\beta k| + \varepsilon}
\]
**Outputs:** stability regime, fragility class, stability map, trajectory plot, and a clean PDF report.
"""
CSS = """
.gradio-container {max-width: 1180px !important;}
.small {font-size: 12px; opacity: 0.85;}
"""
with gr.Blocks(theme=THEME, css=CSS, title="FDRSM-4 — Threshold Engine") as demo:
gr.Markdown(HEADER_MD)
with gr.Row():
with gr.Column(scale=1):
alpha = gr.Slider(0.1, 5.0, value=1.0, step=0.1, label="α (dependency amplification)")
beta = gr.Slider(0.1, 5.0, value=1.0, step=0.1, label="β (authority damping)")
D = gr.Slider(0.0, 10.0, value=3.0, step=0.1, label="D (dependency intensity)")
k = gr.Slider(0.0, 10.0, value=3.2, step=0.1, label="k (authority gain)")
R0 = gr.Slider(0.01, 10.0, value=1.0, step=0.01, label="R(0)")
T = gr.Slider(1.0, 80.0, value=25.0, step=1.0, label="T (horizon)")
n = gr.Slider(200, 6000, value=1400, step=50, label="n (steps)")
tol = gr.Slider(0.0, 0.5, value=0.006, step=0.0005, label="tol (near-boundary)")
eps = gr.Number(value=1e-12, label="ε", precision=12)
run = gr.Button("Run Threshold Engine", variant="primary")
with gr.Column(scale=1):
p1 = gr.Plot(label="Risk trajectory")
p2 = gr.Plot(label="Stability map")
pdf = gr.File(label="PDF report")
txt = gr.Textbox(label="Decision-facing report", lines=18)
diag_state = gr.State({})
def _run(alpha, beta, D, k, R0, T, n, tol, eps):
fig1, fig2, pdf_path, report, diag = run_engine(alpha, beta, D, k, R0, T, n, tol, eps)
return fig1, fig2, pdf_path, report, diag
run.click(
fn=_run,
inputs=[alpha, beta, D, k, R0, T, n, tol, eps],
outputs=[p1, p2, pdf, txt, diag_state]
)
demo.launch()