Spaces:
Sleeping
Sleeping
| import re | |
| from dataclasses import dataclass | |
| import gradio as gr | |
| class Criterion: | |
| name: str | |
| description: str | |
| keywords: tuple[str, ...] | |
| advice: str | |
| CRITERIA = ( | |
| Criterion( | |
| "Clear claim", | |
| "The paragraph has a specific, debatable main point.", | |
| ("should", "must", "because", "therefore", "argue", "claim", "shows", "means"), | |
| "State one clear position the paragraph is trying to prove.", | |
| ), | |
| Criterion( | |
| "Relevant reasons", | |
| "The paragraph gives reasons that directly support the claim.", | |
| ("because", "since", "reason", "first", "also", "another", "for example"), | |
| "Add a reason that explains why the claim is true.", | |
| ), | |
| Criterion( | |
| "Reliable evidence", | |
| "The paragraph includes concrete facts, examples, data, or observations.", | |
| ("for example", "for instance", "data", "study", "evidence", "according", "percent", "%"), | |
| "Use a specific example, fact, quote, or piece of data.", | |
| ), | |
| Criterion( | |
| "Logical connection", | |
| "The paragraph explains how the evidence supports the claim.", | |
| ("this shows", "this means", "therefore", "as a result", "so", "which suggests"), | |
| "Explain how the evidence proves the reason or claim.", | |
| ), | |
| Criterion( | |
| "Counterargument", | |
| "The paragraph considers an objection or alternative view.", | |
| ("although", "however", "some may argue", "critics", "opponents", "on the other hand"), | |
| "Acknowledge one serious objection and respond to it fairly.", | |
| ), | |
| Criterion( | |
| "Consistency", | |
| "The paragraph avoids contradiction and keeps its terms stable.", | |
| ("always", "never", "all", "none", "only", "every"), | |
| "Check that the paragraph does not shift definitions or contradict itself.", | |
| ), | |
| Criterion( | |
| "Appropriate scope", | |
| "The conclusion does not claim more than the evidence can support.", | |
| ("might", "may", "often", "usually", "some", "many", "suggests"), | |
| "Use careful wording when the evidence supports a limited conclusion.", | |
| ), | |
| Criterion( | |
| "Clear organization", | |
| "The paragraph moves in a readable order from claim to support to conclusion.", | |
| ("first", "next", "finally", "therefore", "in conclusion", "because"), | |
| "Put the claim, reason, evidence, and explanation in a clear order.", | |
| ), | |
| ) | |
| def split_sentences(text: str) -> list[str]: | |
| return [sentence.strip() for sentence in re.split(r"(?<=[.!?])\s+", text) if sentence.strip()] | |
| def contains_any(text: str, phrases: tuple[str, ...]) -> bool: | |
| lowered = text.lower() | |
| return any(phrase in lowered for phrase in phrases) | |
| def score_criterion(paragraph: str, criterion: Criterion, sentences: list[str]) -> tuple[int, str]: | |
| score = 1 | |
| notes = [] | |
| if contains_any(paragraph, criterion.keywords): | |
| score += 1 | |
| notes.append("signal words are present") | |
| if criterion.name == "Clear claim": | |
| if sentences and len(sentences[0].split()) >= 8: | |
| score += 1 | |
| notes.append("opening sentence is developed enough to carry a claim") | |
| elif criterion.name == "Reliable evidence": | |
| if re.search(r"\d", paragraph) or '"' in paragraph or "'" in paragraph: | |
| score += 1 | |
| notes.append("specific detail, number, or quotation appears") | |
| elif criterion.name == "Logical connection": | |
| if len(sentences) >= 3: | |
| score += 1 | |
| notes.append("multiple sentences give room for reasoning") | |
| elif criterion.name == "Counterargument": | |
| if contains_any(paragraph, ("but", "yet", "still")): | |
| score += 1 | |
| notes.append("contrast language suggests a response") | |
| elif criterion.name == "Consistency": | |
| if not re.search(r"\b(always|never|all|none|everyone|no one)\b", paragraph.lower()): | |
| score += 1 | |
| notes.append("avoids absolute language") | |
| elif criterion.name == "Appropriate scope": | |
| if contains_any(paragraph, ("might", "may", "often", "usually", "some", "many")): | |
| score += 1 | |
| notes.append("uses careful qualifying language") | |
| elif criterion.name == "Clear organization": | |
| if len(sentences) >= 4: | |
| score += 1 | |
| notes.append("paragraph has enough structure to develop a point") | |
| else: | |
| if len(sentences) >= 2: | |
| score += 1 | |
| notes.append("paragraph gives more than a single unsupported statement") | |
| if not notes: | |
| notes.append("needs clearer evidence in the paragraph") | |
| return min(score, 3), "; ".join(notes) | |
| def evaluate_argument(paragraph: str) -> tuple[str, str]: | |
| paragraph = paragraph.strip() | |
| if not paragraph: | |
| return "Paste a paragraph to evaluate.", "" | |
| sentences = split_sentences(paragraph) | |
| word_count = len(re.findall(r"\b[\w'-]+\b", paragraph)) | |
| rows = [] | |
| total = 0 | |
| priorities = [] | |
| for criterion in CRITERIA: | |
| score, note = score_criterion(paragraph, criterion, sentences) | |
| total += score | |
| rows.append((criterion.name, f"{score}/3", note)) | |
| if score < 3: | |
| priorities.append(f"- **{criterion.name}:** {criterion.advice}") | |
| max_score = len(CRITERIA) * 3 | |
| percent = round((total / max_score) * 100) | |
| if percent >= 85: | |
| level = "Strong" | |
| elif percent >= 65: | |
| level = "Developing" | |
| else: | |
| level = "Needs more support" | |
| summary = ( | |
| f"## Overall: {level}\n\n" | |
| f"**Score:** {total}/{max_score} ({percent}%) \n" | |
| f"**Length:** {word_count} words, {len(sentences)} sentences\n\n" | |
| ) | |
| if priorities: | |
| summary += "### Best next revisions\n" + "\n".join(priorities[:4]) | |
| else: | |
| summary += "### Best next revisions\n- This paragraph already covers the major criteria. Revise for precision, stronger evidence, and smoother wording." | |
| table = "| Criterion | Score | Evidence noticed |\n|---|---:|---|\n" | |
| for name, score, note in rows: | |
| table += f"| {name} | {score} | {note} |\n" | |
| return summary, table | |
| example_paragraph = ( | |
| "Schools should limit phone use during class because constant notifications make it harder " | |
| "for students to focus on complex work. For example, even a quick glance at a message can " | |
| "interrupt the chain of thought needed to understand a difficult reading. Some students may " | |
| "argue that phones help them look up information, but that benefit can still happen during " | |
| "planned research time rather than every minute of class. This means a clear phone policy can " | |
| "protect attention without banning useful technology completely." | |
| ) | |
| with gr.Blocks(title="Good Argument Evaluator") as demo: | |
| gr.Markdown( | |
| "# Good Argument Evaluator\n" | |
| "Paste one paragraph and get feedback based on claim, reasons, evidence, logic, counterarguments, consistency, scope, and organization." | |
| ) | |
| paragraph_input = gr.Textbox( | |
| label="Paragraph", | |
| placeholder="Paste a paragraph here...", | |
| lines=10, | |
| value=example_paragraph, | |
| ) | |
| evaluate_button = gr.Button("Evaluate paragraph", variant="primary") | |
| with gr.Row(): | |
| summary_output = gr.Markdown(label="Summary") | |
| rubric_output = gr.Markdown(label="Criteria breakdown") | |
| evaluate_button.click( | |
| fn=evaluate_argument, | |
| inputs=paragraph_input, | |
| outputs=[summary_output, rubric_output], | |
| ) | |
| paragraph_input.submit( | |
| fn=evaluate_argument, | |
| inputs=paragraph_input, | |
| outputs=[summary_output, rubric_output], | |
| ) | |
| demo.load( | |
| fn=evaluate_argument, | |
| inputs=paragraph_input, | |
| outputs=[summary_output, rubric_output], | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |