Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -723,7 +723,7 @@ def results_equal_or_superset(df_student: pd.DataFrame, df_expected: pd.DataFram
|
|
| 723 |
a = _normalize_columns(df_student); b = _normalize_columns(df_expected)
|
| 724 |
if set(a.columns) == set(b.columns):
|
| 725 |
a2 = a[sorted(a.columns)].sort_values(sorted(a.columns)).reset_index(drop=True)
|
| 726 |
-
b2 = b[sorted(
|
| 727 |
return (a2.equals(b2), None)
|
| 728 |
if set(b.columns).issubset(set(a.columns)):
|
| 729 |
a_proj = a[b.columns]
|
|
@@ -737,6 +737,13 @@ def results_equal_rowcount_only(df_student: pd.DataFrame, df_expected: pd.DataFr
|
|
| 737 |
# When projection isn't specified, match on row count only.
|
| 738 |
return df_student.shape[0] == df_expected.shape[0]
|
| 739 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 740 |
def exec_student_sql(sql_text: str) -> Tuple[Optional[pd.DataFrame], Optional[str], Optional[str], Optional[str]]:
|
| 741 |
if not sql_text or not sql_text.strip():
|
| 742 |
return None, "Enter a SQL statement.", None, None
|
|
@@ -954,24 +961,47 @@ def _domain_status_md():
|
|
| 954 |
err = CURRENT_INFO.get("error",""); err_short = (err[:160] + "…") if len(err) > 160 else err
|
| 955 |
return f"⚠️ **OpenAI randomization unavailable** → using fallback **{CURRENT_SCHEMA.get('domain','?')}**.\n\n> Reason: {err_short}"
|
| 956 |
|
| 957 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 958 |
global CURRENT_SCHEMA, CURRENT_QS, CURRENT_INFO
|
| 959 |
prev = CURRENT_SCHEMA.get("domain") if CURRENT_SCHEMA else None
|
| 960 |
CURRENT_SCHEMA, CURRENT_QS, CURRENT_INFO = install_schema_and_prepare_questions(prev_domain=prev)
|
| 961 |
erd = draw_dynamic_erd(CURRENT_SCHEMA)
|
| 962 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 963 |
|
| 964 |
def preview_table(tbl: str):
|
| 965 |
try:
|
|
|
|
|
|
|
| 966 |
return run_df(CONN, f"SELECT * FROM {tbl} LIMIT 20")
|
| 967 |
except Exception as e:
|
| 968 |
return pd.DataFrame([{"error": str(e)}])
|
| 969 |
|
| 970 |
-
def list_tables_for_preview():
|
| 971 |
-
df = run_df(CONN, "SELECT name, type FROM sqlite_master WHERE type in ('table','view') AND name NOT IN ('users','attempts','session_meta') ORDER BY type, name")
|
| 972 |
-
if df.empty: return ["(no tables)"]
|
| 973 |
-
return df["name"].tolist()
|
| 974 |
-
|
| 975 |
# -------------------- UI --------------------
|
| 976 |
with gr.Blocks(title="Adaptive SQL Trainer — Randomized Domains") as demo:
|
| 977 |
gr.Markdown(
|
|
@@ -1060,21 +1090,19 @@ with gr.Blocks(title="Adaptive SQL Trainer — Randomized Domains") as demo:
|
|
| 1060 |
inputs=[export_name],
|
| 1061 |
outputs=[export_file],
|
| 1062 |
)
|
|
|
|
|
|
|
| 1063 |
regen_btn.click(
|
| 1064 |
regenerate_domain,
|
| 1065 |
-
inputs=[],
|
| 1066 |
-
outputs=[regen_fb, er_image],
|
| 1067 |
)
|
|
|
|
| 1068 |
tbl_btn.click(
|
| 1069 |
-
|
| 1070 |
inputs=[tbl_dd],
|
| 1071 |
outputs=[preview_df]
|
| 1072 |
)
|
| 1073 |
-
regen_btn.click( # refresh list after regeneration
|
| 1074 |
-
lambda: gr.update(choices=list_tables_for_preview()),
|
| 1075 |
-
inputs=[],
|
| 1076 |
-
outputs=[tbl_dd]
|
| 1077 |
-
)
|
| 1078 |
|
| 1079 |
if __name__ == "__main__":
|
| 1080 |
demo.launch()
|
|
|
|
| 723 |
a = _normalize_columns(df_student); b = _normalize_columns(df_expected)
|
| 724 |
if set(a.columns) == set(b.columns):
|
| 725 |
a2 = a[sorted(a.columns)].sort_values(sorted(a.columns)).reset_index(drop=True)
|
| 726 |
+
b2 = b[sorted(a.columns)].sort_values(sorted(a.columns)).reset_index(drop=True)
|
| 727 |
return (a2.equals(b2), None)
|
| 728 |
if set(b.columns).issubset(set(a.columns)):
|
| 729 |
a_proj = a[b.columns]
|
|
|
|
| 737 |
# When projection isn't specified, match on row count only.
|
| 738 |
return df_student.shape[0] == df_expected.shape[0]
|
| 739 |
|
| 740 |
+
def aliases_present(sql: str, required_aliases: List[str]) -> bool:
|
| 741 |
+
low = re.sub(r"\s+", " ", (sql or "").lower())
|
| 742 |
+
for al in (required_aliases or []):
|
| 743 |
+
if f" {al}." not in low and f" as {al} " not in low:
|
| 744 |
+
return False
|
| 745 |
+
return True
|
| 746 |
+
|
| 747 |
def exec_student_sql(sql_text: str) -> Tuple[Optional[pd.DataFrame], Optional[str], Optional[str], Optional[str]]:
|
| 748 |
if not sql_text or not sql_text.strip():
|
| 749 |
return None, "Enter a SQL statement.", None, None
|
|
|
|
| 961 |
err = CURRENT_INFO.get("error",""); err_short = (err[:160] + "…") if len(err) > 160 else err
|
| 962 |
return f"⚠️ **OpenAI randomization unavailable** → using fallback **{CURRENT_SCHEMA.get('domain','?')}**.\n\n> Reason: {err_short}"
|
| 963 |
|
| 964 |
+
# ----- UPDATED: regenerate also refreshes tbl list and, if session active, seeds a new question + shows input
|
| 965 |
+
def list_tables_for_preview():
|
| 966 |
+
df = run_df(CONN, """
|
| 967 |
+
SELECT name FROM sqlite_master
|
| 968 |
+
WHERE type in ('table','view')
|
| 969 |
+
AND name NOT LIKE 'sqlite_%'
|
| 970 |
+
AND name NOT IN ('users','attempts','session_meta')
|
| 971 |
+
ORDER BY type, name
|
| 972 |
+
""")
|
| 973 |
+
return df["name"].tolist() if not df.empty else ["(no tables)"]
|
| 974 |
+
|
| 975 |
+
def regenerate_domain(session: dict):
|
| 976 |
global CURRENT_SCHEMA, CURRENT_QS, CURRENT_INFO
|
| 977 |
prev = CURRENT_SCHEMA.get("domain") if CURRENT_SCHEMA else None
|
| 978 |
CURRENT_SCHEMA, CURRENT_QS, CURRENT_INFO = install_schema_and_prepare_questions(prev_domain=prev)
|
| 979 |
erd = draw_dynamic_erd(CURRENT_SCHEMA)
|
| 980 |
+
status = _domain_status_md()
|
| 981 |
+
|
| 982 |
+
# Refresh the preview dropdown
|
| 983 |
+
choices = list_tables_for_preview()
|
| 984 |
+
dd_update = gr.update(choices=choices, value=(choices[0] if choices and choices[0]!="(no tables)" else None))
|
| 985 |
+
|
| 986 |
+
# If a session is active, show the first question immediately for the new domain
|
| 987 |
+
prompt_update = gr.update()
|
| 988 |
+
input_update = gr.update()
|
| 989 |
+
if session and session.get("user_id"):
|
| 990 |
+
q = pick_next_question(session["user_id"])
|
| 991 |
+
session["qid"] = q["id"]; session["q"] = q; session["start_ts"] = time.time()
|
| 992 |
+
prompt_update = gr.update(value=f"**Question {q['id']}**\n\n{q['prompt_md']}", visible=True)
|
| 993 |
+
input_update = gr.update(value="", visible=True)
|
| 994 |
+
|
| 995 |
+
return status, erd, prompt_update, input_update, dd_update, session
|
| 996 |
|
| 997 |
def preview_table(tbl: str):
|
| 998 |
try:
|
| 999 |
+
if not tbl or tbl=="(no tables)":
|
| 1000 |
+
return pd.DataFrame()
|
| 1001 |
return run_df(CONN, f"SELECT * FROM {tbl} LIMIT 20")
|
| 1002 |
except Exception as e:
|
| 1003 |
return pd.DataFrame([{"error": str(e)}])
|
| 1004 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1005 |
# -------------------- UI --------------------
|
| 1006 |
with gr.Blocks(title="Adaptive SQL Trainer — Randomized Domains") as demo:
|
| 1007 |
gr.Markdown(
|
|
|
|
| 1090 |
inputs=[export_name],
|
| 1091 |
outputs=[export_file],
|
| 1092 |
)
|
| 1093 |
+
|
| 1094 |
+
# UPDATED: one callback handles regeneration, dropdown refresh, and (if session) reseeding the next question
|
| 1095 |
regen_btn.click(
|
| 1096 |
regenerate_domain,
|
| 1097 |
+
inputs=[session_state],
|
| 1098 |
+
outputs=[regen_fb, er_image, prompt_md, sql_input, tbl_dd, session_state],
|
| 1099 |
)
|
| 1100 |
+
|
| 1101 |
tbl_btn.click(
|
| 1102 |
+
preview_table,
|
| 1103 |
inputs=[tbl_dd],
|
| 1104 |
outputs=[preview_df]
|
| 1105 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1106 |
|
| 1107 |
if __name__ == "__main__":
|
| 1108 |
demo.launch()
|