ispg-backend / models /methodology_extraction.py
urestrange's picture
Upload 162 files
cc2c355 verified
Raw
History Blame Contribute Delete
15.1 kB
# ==========================================================
# METHODOLOGY EXTRACTION MODULE (FINAL SV APPROACH)
# FILE: models/methodology_extraction.py
#
# Purpose:
# - Extract methodology steps for poster flowchart
# - Hybrid robust approach:
# (A) Bullet/numbered detection (high priority)
# (B) Regex keyword-based action sentence detection
# (C) Optional spaCy action sentence filtering
# (D) Optional Flan-T5-base refinement (reformatting)
# (E) Fallback safe steps if everything fails
#
# Output:
# [
# "Step 1 ...",
# "Step 2 ...",
# ...
# ]
#
# Notes (SV-style design):
# - We DO NOT try to generate a perfect flowchart directly.
# - We ensure system always returns usable steps.
# - We keep steps short, actionable, poster-friendly.
# ==========================================================
import re
# ==========================================================
# NORMALIZE TEXT
# ==========================================================
def normalize_text(text: str) -> str:
if not text:
return ""
text = text.replace("\r", "\n")
text = re.sub(r"\n{3,}", "\n\n", text)
text = re.sub(r"[ \t]{2,}", " ", text)
return text.strip()
# ==========================================================
# REMOVE IEEE / PDF NOISE
# ==========================================================
def remove_ieee_noise(text: str) -> str:
if not text:
return ""
noise_patterns = [
r"authorized licensed use.*",
r"downloaded on.*",
r"ieee xplore.*",
r"copyright.*",
r"doi:\s*\S+",
r"\(cid:\d+\)",
r"this article has been accepted.*",
r"personal use is permitted.*",
r"vol\.\s*\d+.*",
r"pp\.\s*\d+.*",
]
for pat in noise_patterns:
text = re.sub(pat, "", text, flags=re.IGNORECASE)
text = re.sub(r"\s{2,}", " ", text)
return text.strip()
# ==========================================================
# CLEAN STEP TEXT
# ==========================================================
def clean_step_text(step: str) -> str:
if not step:
return ""
step = step.strip()
# remove bullets
step = step.replace("•", "")
step = step.replace("◦", "")
step = step.replace("▪", "")
step = step.replace("–", "-")
step = step.replace("—", "-")
# remove numbering prefix
step = re.sub(r"^\(?[0-9]{1,2}\)?[\.\)]\s*", "", step)
step = re.sub(r"^[A-Za-z]\)\s*", "", step)
step = re.sub(r"^[-]\s*", "", step)
# remove "Step X:" prefix
step = re.sub(r"^step\s*\d+\s*[:\-]\s*", "", step, flags=re.IGNORECASE)
# remove extra spaces
step = re.sub(r"\s{2,}", " ", step).strip()
# fix broken hyphen words
step = step.replace("pre- processing", "preprocessing")
step = step.replace("pre - processing", "preprocessing")
# cut too long step
if len(step) > 260:
step = step[:260].rsplit(" ", 1)[0] + "..."
return step.strip()
# ==========================================================
# VALIDATE STEP QUALITY
# ==========================================================
def is_valid_step(step: str) -> bool:
if not step:
return False
step = step.strip()
if len(step) < 12:
return False
# reject pure symbols/numbers
if re.fullmatch(r"[0-9\.\-\s]+", step):
return False
low = step.lower()
# reject typical noise
bad_phrases = [
"authorized licensed use",
"downloaded on",
"ieee xplore",
"all rights reserved",
"doi:",
"references",
"bibliography",
]
if any(bp in low for bp in bad_phrases):
return False
# reject figure/table headers
if re.match(r"^(table|fig|figure)\s+[0-9ivx]+", low):
return False
# reject if too many words (paragraph-like)
if len(step.split()) > 40:
return False
return True
# ==========================================================
# EXTRACT BULLET / NUMBERED STEPS (HIGH PRIORITY)
# ==========================================================
def extract_numbered_or_bullet_steps(text: str, max_steps=10):
"""
Detect common patterns:
- 1. ...
- 1) ...
- (1) ...
- Step 1: ...
- • ...
- - ...
"""
if not text:
return []
text = normalize_text(text)
text = remove_ieee_noise(text)
lines = [l.strip() for l in text.split("\n") if l.strip()]
bullet_patterns = [
r"^\d+\.\s+",
r"^\d+\)\s+",
r"^\(\d+\)\s+",
r"^[A-Za-z]\)\s+",
r"^[-•▪◦]\s+",
r"^step\s*\d+\s*[:\-]\s+",
]
bullet_regex = re.compile("|".join(bullet_patterns), re.IGNORECASE)
steps = []
for ln in lines:
if bullet_regex.search(ln):
step = clean_step_text(ln)
if is_valid_step(step):
steps.append(step)
if len(steps) >= max_steps:
break
return steps[:max_steps]
# ==========================================================
# SENTENCE SPLITTER (ROBUST)
# ==========================================================
def split_into_sentences(text: str):
if not text:
return []
text = normalize_text(text)
# split by period + newline + semicolon
sentences = re.split(r"(?<=[\.\!\?])\s+|\n+", text)
cleaned = []
for s in sentences:
s = s.strip()
if len(s) >= 10:
cleaned.append(s)
return cleaned
# ==========================================================
# RULE-BASED KEYWORD METHOD STEPS (SV SAFE RULES)
# ==========================================================
def extract_keyword_based_steps(text: str, max_steps=10):
"""
Extract methodology-like sentences based on strong action keywords.
This works when paper has no explicit bullets.
"""
if not text:
return []
text = normalize_text(text)
text = remove_ieee_noise(text)
sentences = split_into_sentences(text)
# Strong methodology action indicators
keywords = [
"we propose", "we present", "we develop", "we design", "we introduce",
"we implement", "we apply", "we applied", "we use", "we utilized",
"we employ", "we evaluate", "we test", "we trained", "we train",
"we validate", "we compare", "we compute", "we extract",
"dataset", "data set", "preprocess", "pre-processing",
"feature extraction", "segmentation", "classification",
"training", "testing", "evaluation", "hyperparameter",
"cross validation", "k-fold", "architecture", "pipeline",
"framework", "optimizer", "loss function", "learning rate",
"confusion matrix", "accuracy", "precision", "recall", "f1-score",
"rouge", "bertscore"
]
steps = []
for s in sentences:
low = s.lower()
if any(k in low for k in keywords):
step = clean_step_text(s)
if is_valid_step(step):
steps.append(step)
if len(steps) >= max_steps:
break
return steps[:max_steps]
# ==========================================================
# OPTIONAL SPACY EXTRACTION (ACTION VERB FILTERING)
# ==========================================================
def extract_steps_spacy(text: str, max_steps=10):
"""
Use spaCy if installed to detect verb-driven action sentences.
"""
if not text:
return []
try:
import spacy
except Exception:
return []
try:
nlp = spacy.load("en_core_web_sm")
except Exception:
return []
text = normalize_text(text)
text = remove_ieee_noise(text)
doc = nlp(text)
action_verbs = {
"use", "apply", "employ", "train", "test", "evaluate",
"extract", "clean", "preprocess", "classify", "detect",
"generate", "summarize", "measure", "compare", "validate",
"propose", "design", "implement"
}
steps = []
for sent in doc.sents:
s = sent.text.strip()
if len(s) < 15:
continue
has_action = False
for token in sent:
if token.pos_ == "VERB" and token.lemma_.lower() in action_verbs:
has_action = True
break
if has_action:
step = clean_step_text(s)
if is_valid_step(step):
steps.append(step)
if len(steps) >= max_steps:
break
return steps[:max_steps]
# ==========================================================
# OPTIONAL FLAN-T5 REFINEMENT (REFORMAT, NOT GENERATE NEW IDEA)
# ==========================================================
def refine_steps_with_flan_t5(method_text: str, max_steps=8):
"""
Flan-T5 is used ONLY for reformatting the extracted methodology
into short steps for poster flowchart.
"""
if not method_text:
return []
try:
from transformers import pipeline
except Exception:
return []
try:
model = pipeline(
"text2text-generation",
model="google/flan-t5-base",
tokenizer="google/flan-t5-base"
)
except Exception:
return []
method_text = normalize_text(method_text)
method_text = remove_ieee_noise(method_text)
# truncate for safety
if len(method_text) > 3500:
method_text = method_text[:3500]
prompt = (
f"Extract the research methodology as {max_steps} short steps for a poster flowchart. "
f"Each step must be a short sentence. "
f"Do not add new information.\n\n"
f"Methodology:\n{method_text}"
)
try:
out = model(
prompt,
max_new_tokens=220,
do_sample=False
)
if not out or not isinstance(out, list):
return []
generated = out[0].get("generated_text", "").strip()
if not generated:
return []
# split by numbering OR line breaks
parts = re.split(r"\n+|\d+\.\s*", generated)
steps = []
for p in parts:
p = p.strip()
if not p:
continue
step = clean_step_text(p)
if is_valid_step(step):
steps.append(step)
if len(steps) >= max_steps:
break
return steps[:max_steps]
except Exception:
return []
# ==========================================================
# FALLBACK SAFE STEPS (ENSURE NEVER EMPTY)
# ==========================================================
def fallback_generic_steps(max_steps=6):
base = [
"Collect and prepare the dataset from the paper.",
"Perform preprocessing and cleaning of the data.",
"Apply the proposed model or framework for analysis.",
"Train the model using selected hyperparameters.",
"Evaluate performance using standard evaluation metrics.",
"Compare results against baseline or existing methods."
]
return base[:max_steps]
# ==========================================================
# MAIN FUNCTION (FINAL PIPELINE)
# ==========================================================
def extract_methodology_steps(
methodology_text: str,
max_steps=8,
use_spacy=True,
use_flan_refine=True
):
"""
Final SV hybrid pipeline:
1) Extract bullet/numbered steps (most accurate)
2) Extract keyword-based action sentences
3) Extract spaCy action sentences (optional)
4) Flan-T5 refinement (optional) to clean + restructure
5) Merge + remove duplicates
6) If still weak -> fallback generic steps
"""
methodology_text = normalize_text(methodology_text)
methodology_text = remove_ieee_noise(methodology_text)
if not methodology_text:
return fallback_generic_steps(max_steps=max_steps)
steps = []
# -------------------------------------------------------
# 1) Bullet / numbered detection
# -------------------------------------------------------
bullet_steps = extract_numbered_or_bullet_steps(methodology_text, max_steps=max_steps)
steps.extend(bullet_steps)
# -------------------------------------------------------
# 2) Keyword-based extraction
# -------------------------------------------------------
if len(steps) < max_steps:
keyword_steps = extract_keyword_based_steps(methodology_text, max_steps=max_steps)
steps.extend(keyword_steps)
# -------------------------------------------------------
# 3) spaCy extraction
# -------------------------------------------------------
if use_spacy and len(steps) < max_steps:
spacy_steps = extract_steps_spacy(methodology_text, max_steps=max_steps)
steps.extend(spacy_steps)
# -------------------------------------------------------
# 4) Remove duplicates early
# -------------------------------------------------------
deduped = []
seen = set()
for s in steps:
s_clean = clean_step_text(s)
key = re.sub(r"[^a-z0-9 ]", "", s_clean.lower()).strip()
if not key or key in seen:
continue
if is_valid_step(s_clean):
deduped.append(s_clean)
seen.add(key)
if len(deduped) >= max_steps:
break
steps = deduped[:max_steps]
# -------------------------------------------------------
# 5) Flan refinement (only if steps still weak)
# -------------------------------------------------------
if use_flan_refine:
flan_steps = refine_steps_with_flan_t5(methodology_text, max_steps=max_steps)
# accept flan only if looks strong
if len(flan_steps) >= 4:
steps = flan_steps
# -------------------------------------------------------
# 6) Final fallback if too weak
# -------------------------------------------------------
if len(steps) < 3:
return fallback_generic_steps(max_steps=max_steps)
return steps[:max_steps]
# ==========================================================
# QUICK TEST
# ==========================================================
if __name__ == "__main__":
sample_method = """
III. METHODOLOGY
We collected datasets from Kaggle.
1. Data preprocessing and cleaning.
2. Training Flan-T5 base model.
3. Evaluation using ROUGE-L and BERTScore.
• Comparison against baseline model.
"""
steps = extract_methodology_steps(sample_method, max_steps=8)
print("Extracted Methodology Steps:")
for i, s in enumerate(steps, 1):
print(f"{i}. {s}")