profplate commited on
Commit
4bb2b6d
·
verified ·
1 Parent(s): fd90d87

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +217 -0
app.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from dataclasses import dataclass
3
+
4
+ import gradio as gr
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class Criterion:
9
+ name: str
10
+ description: str
11
+ keywords: tuple[str, ...]
12
+ advice: str
13
+
14
+
15
+ CRITERIA = (
16
+ Criterion(
17
+ "Clear claim",
18
+ "The paragraph has a specific, debatable main point.",
19
+ ("should", "must", "because", "therefore", "argue", "claim", "shows", "means"),
20
+ "State one clear position the paragraph is trying to prove.",
21
+ ),
22
+ Criterion(
23
+ "Relevant reasons",
24
+ "The paragraph gives reasons that directly support the claim.",
25
+ ("because", "since", "reason", "first", "also", "another", "for example"),
26
+ "Add a reason that explains why the claim is true.",
27
+ ),
28
+ Criterion(
29
+ "Reliable evidence",
30
+ "The paragraph includes concrete facts, examples, data, or observations.",
31
+ ("for example", "for instance", "data", "study", "evidence", "according", "percent", "%"),
32
+ "Use a specific example, fact, quote, or piece of data.",
33
+ ),
34
+ Criterion(
35
+ "Logical connection",
36
+ "The paragraph explains how the evidence supports the claim.",
37
+ ("this shows", "this means", "therefore", "as a result", "so", "which suggests"),
38
+ "Explain how the evidence proves the reason or claim.",
39
+ ),
40
+ Criterion(
41
+ "Counterargument",
42
+ "The paragraph considers an objection or alternative view.",
43
+ ("although", "however", "some may argue", "critics", "opponents", "on the other hand"),
44
+ "Acknowledge one serious objection and respond to it fairly.",
45
+ ),
46
+ Criterion(
47
+ "Consistency",
48
+ "The paragraph avoids contradiction and keeps its terms stable.",
49
+ ("always", "never", "all", "none", "only", "every"),
50
+ "Check that the paragraph does not shift definitions or contradict itself.",
51
+ ),
52
+ Criterion(
53
+ "Appropriate scope",
54
+ "The conclusion does not claim more than the evidence can support.",
55
+ ("might", "may", "often", "usually", "some", "many", "suggests"),
56
+ "Use careful wording when the evidence supports a limited conclusion.",
57
+ ),
58
+ Criterion(
59
+ "Clear organization",
60
+ "The paragraph moves in a readable order from claim to support to conclusion.",
61
+ ("first", "next", "finally", "therefore", "in conclusion", "because"),
62
+ "Put the claim, reason, evidence, and explanation in a clear order.",
63
+ ),
64
+ )
65
+
66
+
67
+ def split_sentences(text: str) -> list[str]:
68
+ return [sentence.strip() for sentence in re.split(r"(?<=[.!?])\s+", text) if sentence.strip()]
69
+
70
+
71
+ def contains_any(text: str, phrases: tuple[str, ...]) -> bool:
72
+ lowered = text.lower()
73
+ return any(phrase in lowered for phrase in phrases)
74
+
75
+
76
+ def score_criterion(paragraph: str, criterion: Criterion, sentences: list[str]) -> tuple[int, str]:
77
+ score = 1
78
+ notes = []
79
+
80
+ if contains_any(paragraph, criterion.keywords):
81
+ score += 1
82
+ notes.append("signal words are present")
83
+
84
+ if criterion.name == "Clear claim":
85
+ if sentences and len(sentences[0].split()) >= 8:
86
+ score += 1
87
+ notes.append("opening sentence is developed enough to carry a claim")
88
+ elif criterion.name == "Reliable evidence":
89
+ if re.search(r"\d", paragraph) or '"' in paragraph or "'" in paragraph:
90
+ score += 1
91
+ notes.append("specific detail, number, or quotation appears")
92
+ elif criterion.name == "Logical connection":
93
+ if len(sentences) >= 3:
94
+ score += 1
95
+ notes.append("multiple sentences give room for reasoning")
96
+ elif criterion.name == "Counterargument":
97
+ if contains_any(paragraph, ("but", "yet", "still")):
98
+ score += 1
99
+ notes.append("contrast language suggests a response")
100
+ elif criterion.name == "Consistency":
101
+ if not re.search(r"\b(always|never|all|none|everyone|no one)\b", paragraph.lower()):
102
+ score += 1
103
+ notes.append("avoids absolute language")
104
+ elif criterion.name == "Appropriate scope":
105
+ if contains_any(paragraph, ("might", "may", "often", "usually", "some", "many")):
106
+ score += 1
107
+ notes.append("uses careful qualifying language")
108
+ elif criterion.name == "Clear organization":
109
+ if len(sentences) >= 4:
110
+ score += 1
111
+ notes.append("paragraph has enough structure to develop a point")
112
+ else:
113
+ if len(sentences) >= 2:
114
+ score += 1
115
+ notes.append("paragraph gives more than a single unsupported statement")
116
+
117
+ if not notes:
118
+ notes.append("needs clearer evidence in the paragraph")
119
+
120
+ return min(score, 3), "; ".join(notes)
121
+
122
+
123
+ def evaluate_argument(paragraph: str) -> tuple[str, str]:
124
+ paragraph = paragraph.strip()
125
+ if not paragraph:
126
+ return "Paste a paragraph to evaluate.", ""
127
+
128
+ sentences = split_sentences(paragraph)
129
+ word_count = len(re.findall(r"\b[\w'-]+\b", paragraph))
130
+
131
+ rows = []
132
+ total = 0
133
+ priorities = []
134
+
135
+ for criterion in CRITERIA:
136
+ score, note = score_criterion(paragraph, criterion, sentences)
137
+ total += score
138
+ rows.append((criterion.name, f"{score}/3", note))
139
+ if score < 3:
140
+ priorities.append(f"- **{criterion.name}:** {criterion.advice}")
141
+
142
+ max_score = len(CRITERIA) * 3
143
+ percent = round((total / max_score) * 100)
144
+
145
+ if percent >= 85:
146
+ level = "Strong"
147
+ elif percent >= 65:
148
+ level = "Developing"
149
+ else:
150
+ level = "Needs more support"
151
+
152
+ summary = (
153
+ f"## Overall: {level}\n\n"
154
+ f"**Score:** {total}/{max_score} ({percent}%) \n"
155
+ f"**Length:** {word_count} words, {len(sentences)} sentences\n\n"
156
+ )
157
+
158
+ if priorities:
159
+ summary += "### Best next revisions\n" + "\n".join(priorities[:4])
160
+ else:
161
+ summary += "### Best next revisions\n- This paragraph already covers the major criteria. Revise for precision, stronger evidence, and smoother wording."
162
+
163
+ table = "| Criterion | Score | Evidence noticed |\n|---|---:|---|\n"
164
+ for name, score, note in rows:
165
+ table += f"| {name} | {score} | {note} |\n"
166
+
167
+ return summary, table
168
+
169
+
170
+ example_paragraph = (
171
+ "Schools should limit phone use during class because constant notifications make it harder "
172
+ "for students to focus on complex work. For example, even a quick glance at a message can "
173
+ "interrupt the chain of thought needed to understand a difficult reading. Some students may "
174
+ "argue that phones help them look up information, but that benefit can still happen during "
175
+ "planned research time rather than every minute of class. This means a clear phone policy can "
176
+ "protect attention without banning useful technology completely."
177
+ )
178
+
179
+
180
+ with gr.Blocks(title="Good Argument Evaluator") as demo:
181
+ gr.Markdown(
182
+ "# Good Argument Evaluator\n"
183
+ "Paste one paragraph and get feedback based on claim, reasons, evidence, logic, counterarguments, consistency, scope, and organization."
184
+ )
185
+
186
+ paragraph_input = gr.Textbox(
187
+ label="Paragraph",
188
+ placeholder="Paste a paragraph here...",
189
+ lines=10,
190
+ value=example_paragraph,
191
+ )
192
+ evaluate_button = gr.Button("Evaluate paragraph", variant="primary")
193
+
194
+ with gr.Row():
195
+ summary_output = gr.Markdown(label="Summary")
196
+ rubric_output = gr.Markdown(label="Criteria breakdown")
197
+
198
+ evaluate_button.click(
199
+ fn=evaluate_argument,
200
+ inputs=paragraph_input,
201
+ outputs=[summary_output, rubric_output],
202
+ )
203
+ paragraph_input.submit(
204
+ fn=evaluate_argument,
205
+ inputs=paragraph_input,
206
+ outputs=[summary_output, rubric_output],
207
+ )
208
+
209
+ demo.load(
210
+ fn=evaluate_argument,
211
+ inputs=paragraph_input,
212
+ outputs=[summary_output, rubric_output],
213
+ )
214
+
215
+
216
+ if __name__ == "__main__":
217
+ demo.launch()