Spaces:
Sleeping
Sleeping
Commit ·
cea1ff8
1
Parent(s): 5fda3e7
By-section 'All edit types' cell click: one combined summary of all edits, not per-label
Browse files
api.py
CHANGED
|
@@ -558,6 +558,12 @@ async def download_demo_csv(article: str) -> FileResponse:
|
|
| 558 |
class SummarizePayload(BaseModel):
|
| 559 |
article: str
|
| 560 |
label: int
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 561 |
period: str # one of "Y" | "2Q" | "Q" — matches the frontend Period type
|
| 562 |
# e.g. "2018", "2018Q3", "2018H1". Omit / send null to summarize across
|
| 563 |
# every period (the "Summarize all periods" affordance in Over-Time).
|
|
@@ -784,6 +790,16 @@ async def summarize_edits(payload: SummarizePayload) -> Dict[str, Any]:
|
|
| 784 |
section-group) cell using a fast OpenAI model. Used by interactive chart
|
| 785 |
drill-downs.
|
| 786 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 787 |
# Client-supplied path: the dataset is in the user's browser, not cached
|
| 788 |
# on the server. The frontend has already filtered rows by (label, period,
|
| 789 |
# sections) and sent the relevant explanation snippets. Skip the server
|
|
@@ -824,11 +840,19 @@ async def summarize_edits(payload: SummarizePayload) -> Dict[str, Any]:
|
|
| 824 |
if client_snippets is not None:
|
| 825 |
snippets = client_snippets
|
| 826 |
# We don't know the unmatched count on the client path; use the count
|
| 827 |
-
# the client reported.
|
|
|
|
|
|
|
| 828 |
match_count_for_response = matched_count or len(snippets)
|
|
|
|
| 829 |
else:
|
| 830 |
-
# Filter to rows that contain the requested label.
|
| 831 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 832 |
|
| 833 |
# Never summarize hidden structural sections, even if the union-find
|
| 834 |
# grouped_idx lumped one in with a visible section the user selected.
|
|
@@ -863,12 +887,21 @@ async def summarize_edits(payload: SummarizePayload) -> Dict[str, Any]:
|
|
| 863 |
),
|
| 864 |
}
|
| 865 |
|
| 866 |
-
# Pull the relevant Explanation snippet for each matched row.
|
|
|
|
|
|
|
| 867 |
snippets = []
|
|
|
|
| 868 |
for _, row in matched.iterrows():
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 872 |
|
| 873 |
if not snippets:
|
| 874 |
return {
|
|
@@ -913,46 +946,100 @@ async def summarize_edits(payload: SummarizePayload) -> Dict[str, Any]:
|
|
| 913 |
if payload.period_key
|
| 914 |
else "across the article's full revision history"
|
| 915 |
)
|
| 916 |
-
|
| 917 |
-
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
|
| 937 |
-
|
| 938 |
-
|
| 939 |
-
|
| 940 |
-
|
| 941 |
-
|
| 942 |
-
|
| 943 |
-
|
| 944 |
-
|
| 945 |
-
|
| 946 |
-
|
| 947 |
-
|
| 948 |
-
|
| 949 |
-
|
| 950 |
-
|
| 951 |
-
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
|
| 955 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 956 |
|
| 957 |
try:
|
| 958 |
import openai
|
|
@@ -978,10 +1065,14 @@ async def summarize_edits(payload: SummarizePayload) -> Dict[str, Any]:
|
|
| 978 |
return {
|
| 979 |
"summary": summary_text.strip(),
|
| 980 |
"edit_count": match_count_for_response,
|
| 981 |
-
|
|
|
|
|
|
|
|
|
|
| 982 |
"truncated": truncated,
|
| 983 |
"label": payload.label,
|
| 984 |
-
"
|
|
|
|
| 985 |
"period_key": payload.period_key,
|
| 986 |
}
|
| 987 |
|
|
|
|
| 558 |
class SummarizePayload(BaseModel):
|
| 559 |
article: str
|
| 560 |
label: int
|
| 561 |
+
# Combined multi-label summary: when set (length > 1), rows are matched if
|
| 562 |
+
# they carry ANY of these labels, and every covered label's explanation
|
| 563 |
+
# snippet is summarized together into a single write-up. Sent by the
|
| 564 |
+
# by-section heatmap's "All scientific edit types" cell clicks. `label`
|
| 565 |
+
# still holds labels[0] for backward-compatible single-label callers.
|
| 566 |
+
labels: Optional[List[int]] = None
|
| 567 |
period: str # one of "Y" | "2Q" | "Q" — matches the frontend Period type
|
| 568 |
# e.g. "2018", "2018Q3", "2018H1". Omit / send null to summarize across
|
| 569 |
# every period (the "Summarize all periods" affordance in Over-Time).
|
|
|
|
| 790 |
section-group) cell using a fast OpenAI model. Used by interactive chart
|
| 791 |
drill-downs.
|
| 792 |
"""
|
| 793 |
+
# Normalize the label set. A combined request (by-section "All edit types"
|
| 794 |
+
# cell) carries multiple labels; a single-label request carries just one.
|
| 795 |
+
# `combined_labels` is the deduped, sorted set we match/summarize over;
|
| 796 |
+
# `is_combined` gates the multi-label prompt and response labelling.
|
| 797 |
+
if payload.labels:
|
| 798 |
+
combined_labels = sorted({int(l) for l in payload.labels})
|
| 799 |
+
else:
|
| 800 |
+
combined_labels = [int(payload.label)]
|
| 801 |
+
is_combined = len(combined_labels) > 1
|
| 802 |
+
|
| 803 |
# Client-supplied path: the dataset is in the user's browser, not cached
|
| 804 |
# on the server. The frontend has already filtered rows by (label, period,
|
| 805 |
# sections) and sent the relevant explanation snippets. Skip the server
|
|
|
|
| 840 |
if client_snippets is not None:
|
| 841 |
snippets = client_snippets
|
| 842 |
# We don't know the unmatched count on the client path; use the count
|
| 843 |
+
# the client reported. On this path each reported snippet is one
|
| 844 |
+
# non-empty explanation, so the "with explanation text" count is just
|
| 845 |
+
# the snippet count.
|
| 846 |
match_count_for_response = matched_count or len(snippets)
|
| 847 |
+
rows_with_expl = len(snippets)
|
| 848 |
else:
|
| 849 |
+
# Filter to rows that contain any of the requested label(s). For a
|
| 850 |
+
# single-label request this is just `payload.label`; for a combined
|
| 851 |
+
# request it's any of the covered labels.
|
| 852 |
+
label_set = set(combined_labels)
|
| 853 |
+
mask = df["Labels"].apply(
|
| 854 |
+
lambda v: bool(label_set.intersection(_parse_labels_field(v)))
|
| 855 |
+
)
|
| 856 |
|
| 857 |
# Never summarize hidden structural sections, even if the union-find
|
| 858 |
# grouped_idx lumped one in with a visible section the user selected.
|
|
|
|
| 887 |
),
|
| 888 |
}
|
| 889 |
|
| 890 |
+
# Pull the relevant Explanation snippet(s) for each matched row. For a
|
| 891 |
+
# combined request, gather every covered label's snippet the row
|
| 892 |
+
# carries so the summary spans all edit types in the cell.
|
| 893 |
snippets = []
|
| 894 |
+
rows_with_expl = 0
|
| 895 |
for _, row in matched.iterrows():
|
| 896 |
+
raw_expl = row.get("Explanation")
|
| 897 |
+
row_had_snippet = False
|
| 898 |
+
for lbl in combined_labels:
|
| 899 |
+
text = _explanation_for_label(raw_expl, lbl)
|
| 900 |
+
if text:
|
| 901 |
+
snippets.append(text)
|
| 902 |
+
row_had_snippet = True
|
| 903 |
+
if row_had_snippet:
|
| 904 |
+
rows_with_expl += 1
|
| 905 |
|
| 906 |
if not snippets:
|
| 907 |
return {
|
|
|
|
| 946 |
if payload.period_key
|
| 947 |
else "across the article's full revision history"
|
| 948 |
)
|
| 949 |
+
|
| 950 |
+
if is_combined:
|
| 951 |
+
# Combined summary: one write-up spanning every scientific edit type
|
| 952 |
+
# present in the cell. The scope is the union of the covered labels'
|
| 953 |
+
# definitions rather than a single one, so the model synthesizes
|
| 954 |
+
# across edit types instead of being told to omit "other labels".
|
| 955 |
+
covered_names = [
|
| 956 |
+
label_name_map.get(l, f"Label {l}") for l in combined_labels
|
| 957 |
+
]
|
| 958 |
+
definitions_block = "\n\n".join(
|
| 959 |
+
f"'{label_name_map.get(l, f'Label {l}')}' (label {l}):\n"
|
| 960 |
+
f"{_label_definition(l, payload.article)}"
|
| 961 |
+
for l in combined_labels
|
| 962 |
+
)
|
| 963 |
+
response_label_name = "All scientific edit types"
|
| 964 |
+
user_prompt = (
|
| 965 |
+
f"You are summarizing Wikipedia revision edits for the article "
|
| 966 |
+
f"'{payload.article}'.\n\n"
|
| 967 |
+
f"Below are explanations from {len(snippets_for_prompt)} edit "
|
| 968 |
+
f"observations {period_note}, {section_note}. They span every "
|
| 969 |
+
f"scientific edit type present in this selection: "
|
| 970 |
+
f"{', '.join(covered_names)}.\n\n"
|
| 971 |
+
f"Definitions of the covered edit types:\n{definitions_block}\n\n"
|
| 972 |
+
f"Write a tight, scannable summary as 3–6 short bullets that "
|
| 973 |
+
f"synthesize what changed across ALL of these edit types together. "
|
| 974 |
+
f"Each bullet must:\n"
|
| 975 |
+
f"- Start with '- '.\n"
|
| 976 |
+
f"- Be one sentence, ideally under 20 words.\n"
|
| 977 |
+
f"- Cover a distinct theme or grouping — do NOT restate the same "
|
| 978 |
+
f"point twice and do NOT enumerate every edit.\n"
|
| 979 |
+
f"- Lead with the substance, not filler (avoid openers like "
|
| 980 |
+
f"'This edit…' or 'The article…').\n\n"
|
| 981 |
+
f"Scope rules — read carefully:\n"
|
| 982 |
+
f"- Summarize the edits as a whole. Group related changes across "
|
| 983 |
+
f"edit types; you do not need one bullet per edit type.\n"
|
| 984 |
+
f"- Only discuss evidence that satisfies at least one of the "
|
| 985 |
+
f"definitions above. Ignore anything in the explanations that "
|
| 986 |
+
f"falls outside all of them.\n\n"
|
| 987 |
+
f"Formatting requirements:\n"
|
| 988 |
+
f"- Use Markdown **bold** (double asterisks) only around concrete "
|
| 989 |
+
f"evidence — e.g. the specific technical terms, references, "
|
| 990 |
+
f"wikilinks, numeric values, or researcher names mentioned in the "
|
| 991 |
+
f"explanations. Bold only the evidence phrase itself, not the "
|
| 992 |
+
f"surrounding prose.\n"
|
| 993 |
+
f"- After the bullets, add a separate final line beginning with "
|
| 994 |
+
f"'Bottom line: ' that gives a one-sentence takeaway about what "
|
| 995 |
+
f"these edits collectively did to the article during this period.\n"
|
| 996 |
+
f"- Do not output any prose outside the bullets and the Bottom "
|
| 997 |
+
f"line line. No headings, no intro sentence, no closing remark.\n\n"
|
| 998 |
+
f"Explanations:\n"
|
| 999 |
+
+ "\n\n".join(f"- {s}" for s in snippets_for_prompt)
|
| 1000 |
+
)
|
| 1001 |
+
else:
|
| 1002 |
+
response_label_name = label_name
|
| 1003 |
+
user_prompt = (
|
| 1004 |
+
f"You are summarizing Wikipedia revision edits for the article "
|
| 1005 |
+
f"'{payload.article}'.\n\n"
|
| 1006 |
+
f"Below are explanations from {len(snippets_for_prompt)} edits "
|
| 1007 |
+
f"classified as '{label_name}' (label {payload.label}) {period_note}, "
|
| 1008 |
+
f"{section_note}.\n\n"
|
| 1009 |
+
f"Definition of '{label_name}':\n{label_definition}\n\n"
|
| 1010 |
+
f"Write a tight, scannable summary as 3–6 short bullets that "
|
| 1011 |
+
f"synthesize the patterns. Each bullet must:\n"
|
| 1012 |
+
f"- Start with '- '.\n"
|
| 1013 |
+
f"- Be one sentence, ideally under 20 words.\n"
|
| 1014 |
+
f"- Cover a distinct theme or grouping — do NOT restate the same "
|
| 1015 |
+
f"point twice and do NOT enumerate every edit.\n"
|
| 1016 |
+
f"- Lead with the substance, not filler (avoid openers like "
|
| 1017 |
+
f"'This edit…' or 'The article…').\n\n"
|
| 1018 |
+
f"Scope rules — read carefully:\n"
|
| 1019 |
+
f"- Only discuss evidence that directly satisfies the definition of "
|
| 1020 |
+
f"'{label_name}' given above. Do NOT mention edits or details that "
|
| 1021 |
+
f"appeared in the explanations but fall outside this definition "
|
| 1022 |
+
f"(e.g. for an 'Academic References Added' summary, do not mention "
|
| 1023 |
+
f"named-reference tags, formatting cleanup, or non-academic links — "
|
| 1024 |
+
f"the definition is specifically about new academic papers with "
|
| 1025 |
+
f"identifiers like DOI/PMID/PMC/arXiv/ISSN).\n"
|
| 1026 |
+
f"- If something feels noteworthy but doesn't match the definition, "
|
| 1027 |
+
f"omit it. It will be covered by a different label's summary.\n\n"
|
| 1028 |
+
f"Formatting requirements:\n"
|
| 1029 |
+
f"- Use Markdown **bold** (double asterisks) only around concrete "
|
| 1030 |
+
f"evidence that directly satisfies the definition above — e.g. the "
|
| 1031 |
+
f"specific technical terms, references, wikilinks, numeric values, "
|
| 1032 |
+
f"or researcher names mentioned in the explanations. Bold only the "
|
| 1033 |
+
f"evidence phrase itself, not the surrounding prose. Do not bold "
|
| 1034 |
+
f"anything that isn't itself an instance of the label.\n"
|
| 1035 |
+
f"- After the bullets, add a separate final line beginning with "
|
| 1036 |
+
f"'Bottom line: ' that gives a one-sentence takeaway about what "
|
| 1037 |
+
f"these edits collectively did to the article during this period.\n"
|
| 1038 |
+
f"- Do not output any prose outside the bullets and the Bottom line "
|
| 1039 |
+
f"line. No headings, no intro sentence, no closing remark.\n\n"
|
| 1040 |
+
f"Explanations:\n"
|
| 1041 |
+
+ "\n\n".join(f"- {s}" for s in snippets_for_prompt)
|
| 1042 |
+
)
|
| 1043 |
|
| 1044 |
try:
|
| 1045 |
import openai
|
|
|
|
| 1065 |
return {
|
| 1066 |
"summary": summary_text.strip(),
|
| 1067 |
"edit_count": match_count_for_response,
|
| 1068 |
+
# Rows that contributed at least one explanation snippet. In combined
|
| 1069 |
+
# mode a single row may contribute several snippets, so this stays a
|
| 1070 |
+
# per-row count rather than the raw snippet total.
|
| 1071 |
+
"explanation_count": rows_with_expl,
|
| 1072 |
"truncated": truncated,
|
| 1073 |
"label": payload.label,
|
| 1074 |
+
"labels": combined_labels if is_combined else None,
|
| 1075 |
+
"label_name": response_label_name,
|
| 1076 |
"period_key": payload.period_key,
|
| 1077 |
}
|
| 1078 |
|
frontend_build/asset-manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
{
|
| 2 |
"files": {
|
| 3 |
"main.css": "/static/css/main.ccff2033.css",
|
| 4 |
-
"main.js": "/static/js/main.
|
| 5 |
"static/js/206.b8423ec3.chunk.js": "/static/js/206.b8423ec3.chunk.js",
|
| 6 |
"static/media/KaTeX_AMS-Regular.ttf": "/static/media/KaTeX_AMS-Regular.853be92419a6c3766b9a.ttf",
|
| 7 |
"static/media/KaTeX_Main-Regular.ttf": "/static/media/KaTeX_Main-Regular.9eba1d77abcf2aa6e94e.ttf",
|
|
@@ -67,6 +67,6 @@
|
|
| 67 |
},
|
| 68 |
"entrypoints": [
|
| 69 |
"static/css/main.ccff2033.css",
|
| 70 |
-
"static/js/main.
|
| 71 |
]
|
| 72 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"files": {
|
| 3 |
"main.css": "/static/css/main.ccff2033.css",
|
| 4 |
+
"main.js": "/static/js/main.8c4ac153.js",
|
| 5 |
"static/js/206.b8423ec3.chunk.js": "/static/js/206.b8423ec3.chunk.js",
|
| 6 |
"static/media/KaTeX_AMS-Regular.ttf": "/static/media/KaTeX_AMS-Regular.853be92419a6c3766b9a.ttf",
|
| 7 |
"static/media/KaTeX_Main-Regular.ttf": "/static/media/KaTeX_Main-Regular.9eba1d77abcf2aa6e94e.ttf",
|
|
|
|
| 67 |
},
|
| 68 |
"entrypoints": [
|
| 69 |
"static/css/main.ccff2033.css",
|
| 70 |
+
"static/js/main.8c4ac153.js"
|
| 71 |
]
|
| 72 |
}
|
frontend_build/index.html
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#0e7490"/><meta name="description" content="Wikistar — understand how scientific revisions evolve over time for a specific Wikipedia page."/><link rel="apple-touch-icon" href="/wikistar-192.png"/><link rel="manifest" href="/manifest.json"/><title>Wikistar — Wikipedia Edit History Explorer</title><script defer="defer" src="/static/js/main.
|
|
|
|
| 1 |
+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#0e7490"/><meta name="description" content="Wikistar — understand how scientific revisions evolve over time for a specific Wikipedia page."/><link rel="apple-touch-icon" href="/wikistar-192.png"/><link rel="manifest" href="/manifest.json"/><title>Wikistar — Wikipedia Edit History Explorer</title><script defer="defer" src="/static/js/main.8c4ac153.js"></script><link href="/static/css/main.ccff2033.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
frontend_build/static/js/{main.caab1ec2.js → main.8c4ac153.js}
RENAMED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend_build/static/js/{main.caab1ec2.js.LICENSE.txt → main.8c4ac153.js.LICENSE.txt}
RENAMED
|
File without changes
|