Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,131 +1,58 @@
|
|
| 1 |
-
# app.py
|
| 2 |
-
import
|
| 3 |
-
|
| 4 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
-
def add_card(fig, x, y, w, h, title, subtitle=None,
|
| 7 |
-
fillcolor="#0f1724", border="#263145",
|
| 8 |
-
title_color="#ffffff", subtitle_color="#bfcbdc"):
|
| 9 |
-
fig.add_shape(type="rect",
|
| 10 |
-
x0=x - w/2, y0=y - h/2, x1=x + w/2, y1=y + h/2,
|
| 11 |
-
xref="x", yref="y",
|
| 12 |
-
line=dict(color=border, width=1),
|
| 13 |
-
fillcolor=fillcolor, layer="below")
|
| 14 |
-
fig.add_trace(go.Scatter(
|
| 15 |
-
x=[x], y=[y + h*0.18],
|
| 16 |
-
mode="text",
|
| 17 |
-
text=[f"<b>{title}</b>"],
|
| 18 |
-
textfont=dict(color=title_color, size=12),
|
| 19 |
-
hoverinfo="none", showlegend=False
|
| 20 |
-
))
|
| 21 |
-
if subtitle:
|
| 22 |
-
fig.add_trace(go.Scatter(
|
| 23 |
-
x=[x], y=[y - h*0.18],
|
| 24 |
-
mode="text",
|
| 25 |
-
text=[subtitle],
|
| 26 |
-
textfont=dict(color=subtitle_color, size=10),
|
| 27 |
-
hoverinfo="none", showlegend=False
|
| 28 |
-
))
|
| 29 |
-
|
| 30 |
-
def add_arrow(fig, x1, y1, x2, y2, color="#86b6ff"):
|
| 31 |
-
fig.add_annotation(
|
| 32 |
-
x=x2, y=y2,
|
| 33 |
-
ax=x1, ay=y1,
|
| 34 |
-
xref="x", yref="y",
|
| 35 |
-
axref="x", ayref="y",
|
| 36 |
-
showarrow=True, arrowhead=3, arrowsize=1, arrowwidth=1.6,
|
| 37 |
-
arrowcolor=color, opacity=0.95
|
| 38 |
-
)
|
| 39 |
-
|
| 40 |
-
def build_qids_html():
|
| 41 |
-
# sample nodes (tweak / replace with your data)
|
| 42 |
-
nodes = [
|
| 43 |
-
{"id":"Intake & Consent", "row":3, "col":1, "subtitle":"Collect identity, demographics"},
|
| 44 |
-
{"id":"Prepare Assessment Suite", "row":3, "col":2, "subtitle":"Assemble IQ, psychometrics"},
|
| 45 |
-
{"id":"Baseline Data Collection", "row":3, "col":3, "subtitle":"Administer tests"},
|
| 46 |
-
{"id":"Standardize & Score", "row":3, "col":4, "subtitle":"Normalize results"},
|
| 47 |
-
{"id":"Integrated Profile Construction", "row":3, "col":5, "subtitle":"Triangulate multi-method data"},
|
| 48 |
-
{"id":"Scheduling & Resource Allocation", "row":2, "col":1, "subtitle":"Session calendar"},
|
| 49 |
-
{"id":"Module Deployment", "row":2, "col":2, "subtitle":"Deploy modules"},
|
| 50 |
-
{"id":"Session Data Capture", "row":2, "col":3, "subtitle":"Attendance, engagement"},
|
| 51 |
-
{"id":"Progress Evaluation Engine", "row":2, "col":4, "subtitle":"Progress metrics"},
|
| 52 |
-
{"id":"DEI Practice (EQ)", "row":2, "col":5, "subtitle":"Emotional integration"},
|
| 53 |
-
{"id":"Full Reassessment", "row":1, "col":2, "subtitle":"Re-run assessments"},
|
| 54 |
-
{"id":"Compute Outcomes", "row":1, "col":3, "subtitle":"Calculate KPIs"},
|
| 55 |
-
{"id":"Intervention Effectiveness", "row":1, "col":4, "subtitle":"Aggregate effectiveness"},
|
| 56 |
-
{"id":"Final IDP & Roadmap", "row":1, "col":5, "subtitle":"Produce final IDP"},
|
| 57 |
-
]
|
| 58 |
-
|
| 59 |
-
x_spacing = 3.5
|
| 60 |
-
y_spacing = 2.5
|
| 61 |
-
for n in nodes:
|
| 62 |
-
n["x"] = n["col"] * x_spacing
|
| 63 |
-
n["y"] = n["row"] * y_spacing
|
| 64 |
-
|
| 65 |
-
fig = go.Figure()
|
| 66 |
-
|
| 67 |
-
# subtle grid lines (visual only)
|
| 68 |
-
x_min, x_max = 0, 22
|
| 69 |
-
y_min, y_max = 0, 9
|
| 70 |
-
for gx in range(int(x_min), int(x_max)+1):
|
| 71 |
-
fig.add_shape(type="line", x0=gx, y0=y_min, x1=gx, y1=y_max,
|
| 72 |
-
line=dict(color="#0b1220", width=1), layer="below")
|
| 73 |
-
for gy in [i*0.5 for i in range(int(y_min*2), int(y_max*2)+1)]:
|
| 74 |
-
fig.add_shape(type="line", x0=x_min, y0=gy, x1=x_max, y1=gy,
|
| 75 |
-
line=dict(color="#08121c", width=1), layer="below")
|
| 76 |
-
|
| 77 |
-
card_w, card_h = 2.8, 1.0
|
| 78 |
-
for n in nodes:
|
| 79 |
-
add_card(fig, n["x"], n["y"], card_w, card_h, n["id"], n.get("subtitle"))
|
| 80 |
-
|
| 81 |
-
rows = {}
|
| 82 |
-
for n in nodes:
|
| 83 |
-
rows.setdefault(n["row"], []).append(n)
|
| 84 |
-
for r, row_nodes in rows.items():
|
| 85 |
-
row_nodes = sorted(row_nodes, key=lambda x: x["col"])
|
| 86 |
-
for a,b in zip(row_nodes, row_nodes[1:]):
|
| 87 |
-
add_arrow(fig, a["x"]+card_w/2, a["y"], b["x"]-card_w/2, b["y"])
|
| 88 |
-
|
| 89 |
-
# right panel
|
| 90 |
-
panel_x0 = x_max - 2.5; panel_x1 = x_max + 2.5
|
| 91 |
-
panel_y0 = y_min + 0.5; panel_y1 = y_max - 0.5
|
| 92 |
-
fig.add_shape(type="rect", x0=panel_x0, y0=panel_y0, x1=panel_x1, y1=panel_y1,
|
| 93 |
-
xref="x", yref="y", line=dict(color="#243445"), fillcolor="#0e1724", layer="below")
|
| 94 |
-
fig.add_trace(go.Scatter(x=[panel_x0+0.2], y=[panel_y1-0.4], mode="text",
|
| 95 |
-
text=["<b>Legal & Technical Highlights</b>"],
|
| 96 |
-
textfont=dict(color="#8fb4ff", size=12), showlegend=False))
|
| 97 |
-
panel_items = [
|
| 98 |
-
("Unique IQ Measurement Model", "Four-parameter model: Verbal, Quantitative, Psychometric"),
|
| 99 |
-
("Standardization Algorithm", "Unified 0-100% scale and conversion factors"),
|
| 100 |
-
("Dynamic Weightage Algorithm (DWA)", "Context-sensitive weights by role/culture/mission"),
|
| 101 |
-
]
|
| 102 |
-
y = panel_y1 - 1.0
|
| 103 |
-
for title, body in panel_items:
|
| 104 |
-
fig.add_trace(go.Scatter(x=[panel_x0+0.2], y=[y], mode="text",
|
| 105 |
-
text=[f"<b>{title}</b><br><span style='color:#bfcbdc;font-size:10px'>{body}</span>"],
|
| 106 |
-
showlegend=False, textfont=dict(size=11)))
|
| 107 |
-
y -= 0.9
|
| 108 |
-
|
| 109 |
-
fig.update_layout(
|
| 110 |
-
width=1200, height=700,
|
| 111 |
-
paper_bgcolor="#071021", plot_bgcolor="#071021",
|
| 112 |
-
margin=dict(l=20, r=20, t=20, b=20),
|
| 113 |
-
xaxis=dict(range=[0, x_max+3], visible=False),
|
| 114 |
-
yaxis=dict(range=[0, y_max+1], visible=False, scaleanchor="x"),
|
| 115 |
-
hovermode="closest"
|
| 116 |
-
)
|
| 117 |
-
|
| 118 |
-
# NOTE: full_html=False returns only the chart DIV and JS that we can safely embed
|
| 119 |
-
fragment = pio.to_html(fig, full_html=False, include_plotlyjs='cdn')
|
| 120 |
-
wrapper = f"<div id='qids-plotly-container'>{fragment}</div>"
|
| 121 |
-
return wrapper
|
| 122 |
-
|
| 123 |
-
PLOTLY_FRAGMENT = build_qids_html()
|
| 124 |
-
|
| 125 |
-
with gr.Blocks() as demo:
|
| 126 |
-
gr.Markdown("## QIDS Framework — Interactive Visualizer")
|
| 127 |
-
gr.HTML(PLOTLY_FRAGMENT)
|
| 128 |
-
gr.Markdown("---\n*Interactive Plotly visualization served with Gradio*")
|
| 129 |
|
| 130 |
if __name__ == "__main__":
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
import os
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from flask import Flask, send_from_directory, render_template_string, abort
|
| 5 |
+
|
| 6 |
+
# Configure where built frontend lives
|
| 7 |
+
DIST_DIR = Path(__file__).parent.joinpath("dist")
|
| 8 |
+
|
| 9 |
+
app = Flask(
|
| 10 |
+
__name__,
|
| 11 |
+
static_folder=str(DIST_DIR), # serve static files from dist/
|
| 12 |
+
static_url_path="" # serve them from root (so /assets/... works)
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
@app.route("/", defaults={"path": ""})
|
| 16 |
+
@app.route("/<path:path>")
|
| 17 |
+
def serve_spa(path: str):
|
| 18 |
+
"""
|
| 19 |
+
Serve files from dist/ when they exist.
|
| 20 |
+
If a file doesn't exist, serve index.html (SPA fallback).
|
| 21 |
+
If dist/index.html is missing, show a helpful message.
|
| 22 |
+
"""
|
| 23 |
+
# If dist doesn't exist or index.html missing, show message.
|
| 24 |
+
index_file = DIST_DIR / "index.html"
|
| 25 |
+
if not index_file.exists():
|
| 26 |
+
msg = """
|
| 27 |
+
<h2>Static frontend not found (dist/index.html)</h2>
|
| 28 |
+
<p>The Space is running but the built frontend (dist/) is missing.</p>
|
| 29 |
+
<p>Possible fixes:</p>
|
| 30 |
+
<ol>
|
| 31 |
+
<li>Ensure your GitHub Actions builds the Vite app and copies <code>dist/</code> into the Space repo root.</li>
|
| 32 |
+
<li>Or manually build locally and copy the <code>dist/</code> contents into this repo.</li>
|
| 33 |
+
</ol>
|
| 34 |
+
<p>Once <code>dist/index.html</code> is present, the app will be served automatically.</p>
|
| 35 |
+
"""
|
| 36 |
+
return render_template_string(msg), 200
|
| 37 |
+
|
| 38 |
+
# If path points to an existing file in dist => serve it
|
| 39 |
+
target = DIST_DIR.joinpath(path)
|
| 40 |
+
if path != "" and target.exists() and target.is_file():
|
| 41 |
+
# send direct file, preserves mime types
|
| 42 |
+
return send_from_directory(str(DIST_DIR), path)
|
| 43 |
+
|
| 44 |
+
# Otherwise return index.html (SPA fallback)
|
| 45 |
+
return send_from_directory(str(DIST_DIR), "index.html")
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
# Health endpoint (optional)
|
| 49 |
+
@app.route("/_health")
|
| 50 |
+
def health():
|
| 51 |
+
return {"status": "ok"}, 200
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
if __name__ == "__main__":
|
| 55 |
+
# When launched by HF Spaces, this script will be executed directly.
|
| 56 |
+
# Use environment PORT if provided, else default 7860.
|
| 57 |
+
port = int(os.environ.get("PORT", 7860))
|
| 58 |
+
app.run(host="0.0.0.0", port=port)
|