samyak152002 commited on
Commit
fab5be2
·
verified ·
1 Parent(s): 0918bea

Update content_analysis.py

Browse files
Files changed (1) hide show
  1. content_analysis.py +30 -121
content_analysis.py CHANGED
@@ -2,42 +2,31 @@
2
  import re
3
  from typing import List, Dict, Any
4
  from collections import Counter
5
- import language_tool_python
6
- import traceback
7
-
8
- # Import utility from text_utils
9
- from text_utils import convert_markdown_to_plain_text
10
 
11
  def check_text_presence(full_text: str, search_terms: List[str]) -> Dict[str, bool]:
12
- return {term: term.lower() in full_text.lower() for term in search_terms}
13
-
14
- def label_authors(full_text: str) -> str:
15
- author_line_regex = r"^(?:.*\n)(.*?)(?:\n\n)"
16
- match = re.search(author_line_regex, full_text, re.MULTILINE)
17
- if match:
18
- authors = match.group(1).strip()
19
- return full_text.replace(authors, f"Authors: {authors}")
20
- return full_text
21
 
22
  def check_metadata(plain_text: str) -> Dict[str, Any]:
 
23
  return {
24
  "author_email": bool(re.search(r'\b[\w.-]+?@\w+?\.\w+?\b', plain_text)),
25
  "list_of_authors": bool(re.search(r'Authors?:', plain_text, re.IGNORECASE)),
26
  "keywords_list": bool(re.search(r'Keywords?:', plain_text, re.IGNORECASE)),
27
- "word_count": len(plain_text.split()) or "Missing"
28
  }
29
 
30
  def check_disclosures(plain_text: str) -> Dict[str, bool]:
31
  search_terms = [
32
- "conflict of interest statement",
33
- "ethics statement",
34
- "funding statement",
35
- "data access statement"
36
  ]
37
- results = check_text_presence(plain_text, search_terms)
38
- has_author_contribution = ("author contribution statement" in plain_text.lower() or
39
- "author contributions statement" in plain_text.lower())
40
- results["author contribution statement"] = has_author_contribution
 
 
41
  return results
42
 
43
  def check_figures_and_tables(plain_text: str) -> Dict[str, bool]:
@@ -48,109 +37,24 @@ def check_figures_and_tables(plain_text: str) -> Dict[str, bool]:
48
  }
49
 
50
  def check_references_summary(plain_text: str) -> Dict[str, Any]:
51
- abstract_candidate = plain_text[:2000]
52
  return {
53
- "old_references": bool(re.search(r'\b19[0-9]{2}\b', plain_text)),
54
- "citations_in_abstract": bool(re.search(r'\[\d+\]', abstract_candidate, re.IGNORECASE)) or \
55
  bool(re.search(r'\bcit(?:ation|ed)\b', abstract_candidate, re.IGNORECASE)),
56
- "reference_count": len(re.findall(r'\[\d+(?:,\s*\d+)*\]', plain_text)),
57
  "self_citations": bool(re.search(r'Self-citation', plain_text, re.IGNORECASE))
58
  }
59
 
60
  def check_structure(plain_text: str) -> Dict[str, bool]:
61
  text_lower = plain_text.lower()
 
 
62
  return {
63
- "imrad_structure": all(section.lower() in text_lower for section in ["introduction", "method", "result", "discussion"]),
64
  "abstract_structure": "structured abstract" in text_lower
65
  }
66
 
67
- def check_language_issues_and_regex(markdown_text_from_pdf: str) -> Dict[str, Any]:
68
- if not markdown_text_from_pdf.strip():
69
- return {"total_issues": 0, "issues_list": [], "text_used_for_analysis": ""}
70
-
71
- plain_text_from_markdown = convert_markdown_to_plain_text(markdown_text_from_pdf)
72
- text_for_analysis = plain_text_from_markdown.replace('\n', ' ')
73
- text_for_analysis = re.sub(r'\s+', ' ', text_for_analysis).strip()
74
-
75
- if not text_for_analysis:
76
- return {"total_issues": 0, "issues_list": [], "text_used_for_analysis": ""}
77
-
78
- text_for_analysis_lower = text_for_analysis.lower()
79
-
80
- abstract_match = re.search(r'\babstract\b', text_for_analysis_lower)
81
- content_start_index = abstract_match.start() if abstract_match else 0
82
- if abstract_match: print(f"Found 'abstract' at index {content_start_index}")
83
- else: print(f"Did not find 'abstract', starting language analysis from index 0")
84
-
85
- references_match = re.search(r'\breferences\b', text_for_analysis_lower)
86
- bibliography_match = re.search(r'\bbibliography\b', text_for_analysis_lower)
87
- content_end_index = len(text_for_analysis)
88
-
89
- if references_match and bibliography_match:
90
- content_end_index = min(references_match.start(), bibliography_match.start())
91
- print(f"Found 'references' at {references_match.start()} and 'bibliography' at {bibliography_match.start()}. Using {content_end_index} as end boundary.")
92
- elif references_match:
93
- content_end_index = references_match.start()
94
- print(f"Found 'references' at {content_end_index}. Using it as end boundary.")
95
- elif bibliography_match:
96
- content_end_index = bibliography_match.start()
97
- print(f"Found 'bibliography' at {content_end_index}. Using it as end boundary.")
98
- else:
99
- print(f"Did not find 'references' or 'bibliography'. Language analysis up to end of text (index {content_end_index}).")
100
-
101
- if content_start_index >= content_end_index:
102
- print(f"Warning: Content start index ({content_start_index}) is not before content end index ({content_end_index}). No language issues will be reported from this range.")
103
-
104
- tool = None
105
- processed_issues: List[Dict[str, Any]] = []
106
- try:
107
- tool = language_tool_python.LanguageTool('en-US')
108
- raw_lt_matches = tool.check(text_for_analysis)
109
-
110
- lt_issues_in_range = 0
111
- for idx, match in enumerate(raw_lt_matches):
112
- if match.ruleId == "EN_SPLIT_WORDS_HYPHEN": continue
113
- if not (content_start_index <= match.offset < content_end_index): continue
114
- lt_issues_in_range +=1
115
- context_str = text_for_analysis[match.offset : match.offset + match.errorLength]
116
- processed_issues.append({
117
- '_internal_id': f"lt_{idx}", 'ruleId': match.ruleId, 'message': match.message,
118
- 'context_text': context_str, 'offset_in_text': match.offset, 'error_length': match.errorLength,
119
- 'replacements_suggestion': match.replacements[:3] if match.replacements else [],
120
- 'category_name': match.category, 'is_mapped_to_pdf': False,
121
- 'pdf_coordinates_list': [], 'mapped_page_number': -1
122
- })
123
- print(f"LanguageTool found {len(raw_lt_matches)} raw issues, {lt_issues_in_range} issues within defined content range.")
124
-
125
- regex_pattern = r'\b(\w+)\[(\d+)\]'
126
- regex_matches = list(re.finditer(regex_pattern, text_for_analysis))
127
-
128
- regex_issues_in_range = 0
129
- for reg_idx, match in enumerate(regex_matches):
130
- if not (content_start_index <= match.start() < content_end_index): continue
131
- regex_issues_in_range += 1
132
- word = match.group(1); number = match.group(2)
133
- processed_issues.append({
134
- '_internal_id': f"regex_{reg_idx}", 'ruleId': "SPACE_BEFORE_BRACKET",
135
- 'message': f"Missing space before '[' in '{word}[{number}]'. Should be '{word} [{number}]'.",
136
- 'context_text': text_for_analysis[match.start():match.end()],
137
- 'offset_in_text': match.start(), 'error_length': match.end() - match.start(),
138
- 'replacements_suggestion': [f"{word} [{number}]"], 'category_name': "Formatting",
139
- 'is_mapped_to_pdf': False, 'pdf_coordinates_list': [], 'mapped_page_number': -1
140
- })
141
- print(f"Regex check found {len(regex_matches)} raw matches, {regex_issues_in_range} issues within defined content range.")
142
-
143
- return {
144
- "total_issues": len(processed_issues), "issues_list": processed_issues,
145
- "text_used_for_analysis": text_for_analysis
146
- }
147
- except Exception as e:
148
- print(f"Error in check_language_issues_and_regex: {e}")
149
- traceback.print_exc()
150
- return {"error": str(e), "total_issues": 0, "issues_list": [], "text_used_for_analysis": text_for_analysis}
151
- finally:
152
- if tool: tool.close()
153
-
154
  def check_figure_order(plain_text: str) -> Dict[str, Any]:
155
  figure_pattern = r'(?:Fig(?:ure)?\.?|Figure)\s*(\d+)'
156
  figure_references_str = re.findall(figure_pattern, plain_text, re.IGNORECASE)
@@ -158,11 +62,14 @@ def check_figure_order(plain_text: str) -> Dict[str, Any]:
158
  valid_figure_numbers_int = [int(num_str) for num_str in figure_references_str if num_str.isdigit()]
159
 
160
  unique_sorted_figures = sorted(list(set(valid_figure_numbers_int)))
161
- is_sequential = all(unique_sorted_figures[i] + 1 == unique_sorted_figures[i+1] for i in range(len(unique_sorted_figures)-1))
 
 
162
 
163
  missing_figures = []
164
  if unique_sorted_figures:
165
- expected_figures = set(range(1, max(unique_sorted_figures) + 1))
 
166
  missing_figures = sorted(list(expected_figures - set(unique_sorted_figures)))
167
 
168
  counts = Counter(valid_figure_numbers_int)
@@ -179,29 +86,31 @@ def check_figure_order(plain_text: str) -> Dict[str, Any]:
179
  def check_reference_order(plain_text: str) -> Dict[str, Any]:
180
  reference_pattern = r'\[(\d+)\]'
181
  references_str = re.findall(reference_pattern, plain_text)
182
- ref_numbers_int = [int(ref) for ref in references_str if ref.isdigit()]
183
 
184
  max_ref_val = 0
185
  out_of_order_details = []
 
186
 
187
  if ref_numbers_int:
188
  max_ref_val = max(ref_numbers_int)
189
  current_max_seen_in_text = 0
190
  for i, ref in enumerate(ref_numbers_int):
191
- if ref < current_max_seen_in_text :
192
  out_of_order_details.append({
193
  "position_in_text_occurrences": i + 1, "value": ref,
194
  "previous_max_value_seen": current_max_seen_in_text,
195
  "message": f"Reference [{ref}] appeared after a higher reference [{current_max_seen_in_text}] was already cited."
196
  })
197
  current_max_seen_in_text = max(current_max_seen_in_text, ref)
 
 
 
198
 
199
  all_expected_refs_up_to_max = set(range(1, max_ref_val + 1)) if max_ref_val > 0 else set()
200
  used_refs_set = set(ref_numbers_int)
201
  missing_refs_in_sequence_to_max = sorted(list(all_expected_refs_up_to_max - used_refs_set))
202
 
203
- is_ordered_in_text = all(ref_numbers_int[i] <= ref_numbers_int[i+1] for i in range(len(ref_numbers_int)-1))
204
-
205
  return {
206
  "max_reference_number_cited": max_ref_val,
207
  "out_of_order_citations_details": out_of_order_details,
 
2
  import re
3
  from typing import List, Dict, Any
4
  from collections import Counter
 
 
 
 
 
5
 
6
  def check_text_presence(full_text: str, search_terms: List[str]) -> Dict[str, bool]:
7
+ full_text_lower = full_text.lower()
8
+ return {term: term.lower() in full_text_lower for term in search_terms}
 
 
 
 
 
 
 
9
 
10
  def check_metadata(plain_text: str) -> Dict[str, Any]:
11
+ word_count_val = len(plain_text.split())
12
  return {
13
  "author_email": bool(re.search(r'\b[\w.-]+?@\w+?\.\w+?\b', plain_text)),
14
  "list_of_authors": bool(re.search(r'Authors?:', plain_text, re.IGNORECASE)),
15
  "keywords_list": bool(re.search(r'Keywords?:', plain_text, re.IGNORECASE)),
16
+ "word_count": word_count_val if word_count_val > 0 else "Missing"
17
  }
18
 
19
  def check_disclosures(plain_text: str) -> Dict[str, bool]:
20
  search_terms = [
21
+ "conflict of interest statement", "ethics statement",
22
+ "funding statement", "data access statement"
 
 
23
  ]
24
+ plain_text_lower = plain_text.lower()
25
+ results = {term: term in plain_text_lower for term in search_terms}
26
+ results["author contribution statement"] = (
27
+ "author contribution statement" in plain_text_lower or
28
+ "author contributions statement" in plain_text_lower
29
+ )
30
  return results
31
 
32
  def check_figures_and_tables(plain_text: str) -> Dict[str, bool]:
 
37
  }
38
 
39
  def check_references_summary(plain_text: str) -> Dict[str, Any]:
40
+ abstract_candidate = plain_text[:2500] # Slightly larger window for abstract
41
  return {
42
+ "old_references": bool(re.search(r'\b19[0-9]{2}\b', plain_text)),
43
+ "citations_in_abstract": bool(re.search(r'\[\d+\]', abstract_candidate)) or \
44
  bool(re.search(r'\bcit(?:ation|ed)\b', abstract_candidate, re.IGNORECASE)),
45
+ "reference_count": len(re.findall(r'\[\d+(?:,\s*\d+)*\]', plain_text)),
46
  "self_citations": bool(re.search(r'Self-citation', plain_text, re.IGNORECASE))
47
  }
48
 
49
  def check_structure(plain_text: str) -> Dict[str, bool]:
50
  text_lower = plain_text.lower()
51
+ imrad_present = all(section in text_lower for section in ["introduction", "method", "result", "discussion"])
52
+ # A more robust IMRAD check might look for these as section headers
53
  return {
54
+ "imrad_structure": imrad_present,
55
  "abstract_structure": "structured abstract" in text_lower
56
  }
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  def check_figure_order(plain_text: str) -> Dict[str, Any]:
59
  figure_pattern = r'(?:Fig(?:ure)?\.?|Figure)\s*(\d+)'
60
  figure_references_str = re.findall(figure_pattern, plain_text, re.IGNORECASE)
 
62
  valid_figure_numbers_int = [int(num_str) for num_str in figure_references_str if num_str.isdigit()]
63
 
64
  unique_sorted_figures = sorted(list(set(valid_figure_numbers_int)))
65
+ is_sequential = True
66
+ if len(unique_sorted_figures) > 1:
67
+ is_sequential = all(unique_sorted_figures[i] + 1 == unique_sorted_figures[i+1] for i in range(len(unique_sorted_figures)-1))
68
 
69
  missing_figures = []
70
  if unique_sorted_figures:
71
+ max_fig = max(unique_sorted_figures)
72
+ expected_figures = set(range(1, max_fig + 1))
73
  missing_figures = sorted(list(expected_figures - set(unique_sorted_figures)))
74
 
75
  counts = Counter(valid_figure_numbers_int)
 
86
  def check_reference_order(plain_text: str) -> Dict[str, Any]:
87
  reference_pattern = r'\[(\d+)\]'
88
  references_str = re.findall(reference_pattern, plain_text)
89
+ ref_numbers_int = [int(ref) for ref in references_str if ref.isdigit() and int(ref) > 0] # Ensure ref > 0
90
 
91
  max_ref_val = 0
92
  out_of_order_details = []
93
+ is_ordered_in_text = True # Assume ordered unless proven otherwise
94
 
95
  if ref_numbers_int:
96
  max_ref_val = max(ref_numbers_int)
97
  current_max_seen_in_text = 0
98
  for i, ref in enumerate(ref_numbers_int):
99
+ if ref < current_max_seen_in_text: # Check against actual max seen so far
100
  out_of_order_details.append({
101
  "position_in_text_occurrences": i + 1, "value": ref,
102
  "previous_max_value_seen": current_max_seen_in_text,
103
  "message": f"Reference [{ref}] appeared after a higher reference [{current_max_seen_in_text}] was already cited."
104
  })
105
  current_max_seen_in_text = max(current_max_seen_in_text, ref)
106
+
107
+ if len(ref_numbers_int) > 1:
108
+ is_ordered_in_text = all(ref_numbers_int[i] <= ref_numbers_int[i+1] for i in range(len(ref_numbers_int)-1))
109
 
110
  all_expected_refs_up_to_max = set(range(1, max_ref_val + 1)) if max_ref_val > 0 else set()
111
  used_refs_set = set(ref_numbers_int)
112
  missing_refs_in_sequence_to_max = sorted(list(all_expected_refs_up_to_max - used_refs_set))
113
 
 
 
114
  return {
115
  "max_reference_number_cited": max_ref_val,
116
  "out_of_order_citations_details": out_of_order_details,