File size: 5,969 Bytes
4d67ec7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import re
import json
import fitz
from transformers import AutoTokenizer
from tqdm import tqdm

# ==============================
# CONFIG
# ==============================

PDF_PATH = r"D:\Users\Pranil\Github Repos\NLP_pdfDataset\11th Ncert\kebo109.pdf"
OUTPUT_JSON = "biology_embedding_ready_final.json"
SUBJECT = "Biology"
MAX_TOKENS = 220
TOKENIZER_MODEL = "bert-base-uncased"

REMOVE_SUMMARY = True  # Optional toggle

tokenizer = AutoTokenizer.from_pretrained(
    TOKENIZER_MODEL,
    clean_up_tokenization_spaces=True
)

# ==============================
# STEP 1: Extract Raw Text
# ==============================

def extract_text(pdf_path):
    doc = fitz.open(pdf_path)
    text = ""
    for page in doc:
        text += page.get_text("text") + "\n"
    return text


# ==============================
# STEP 2: Clean Raw Text
# ==============================

def clean_text(text):

    # Remove page numbers
    text = re.sub(r"^\d+\s*$", "", text, flags=re.MULTILINE)

    # Remove headers
    text = re.sub(r"\nBIOLOGY\n", "\n", text, flags=re.IGNORECASE)
    # text = re.sub(r"\nBIOMOLECULES\n", "\n", text, flags=re.IGNORECASE)

    # Remove reprint line
    text = re.sub(r"Reprint.*?\n", "", text)

    # Remove weird unicode arrows
    text = re.sub(r"[←\uf8e7]+", "", text)

    # Remove everything after EXERCISES
    text = re.split(r"\nEXERCISES", text)[0]

    # Optionally remove summary
    if REMOVE_SUMMARY:
        text = re.split(r"\nSUMMARY", text)[0]

    # Normalize spacing
    text = re.sub(r"\n{2,}", "\n", text)
    text = re.sub(r"\s{2,}", " ", text)

    return text.strip()


# ==============================
# STEP 3: Extract Chapter Info
# ==============================

def extract_chapter_info(text):

    lines = text.split("\n")
    lines = [l.strip() for l in lines if l.strip()]

    chapter_number = "Unknown"
    chapter_title = "Unknown"

    for i, line in enumerate(lines):
        match = re.search(r"CHAPTER\s+(\d+)", line, re.IGNORECASE)
        if match:
            chapter_number = match.group(1)

            # Look ABOVE for true title (skip long sentences)
            for j in range(i - 1, -1, -1):
                if len(lines[j].split()) <= 5:  # short line = likely title
                    chapter_title = lines[j].title()
                    break

            break

    return chapter_number, chapter_title


# ==============================
# STEP 4: Detect Real Sections
# ==============================
def split_real_sections(text, chapter_number):

    section_pattern = rf"\n({chapter_number}\.\d+)\s*\n?([A-Za-z][^\n]+)"

    matches = list(re.finditer(section_pattern, text))

    sections = []

    for i, match in enumerate(matches):

        section_number = match.group(1)
        section_title = match.group(2).strip().title()

        start = match.end()

        # Look ahead up to next match or end
        end = matches[i + 1].start() if i + 1 < len(matches) else len(text)

        section_text = text[start:end].strip()

        # ----------------------------
        # 🔥 CRITICAL FILTER
        # ----------------------------

        # Reject sidebar / TOC matches
        if len(section_text) < 500:
            continue

        # Must contain real sentences
        if section_text.count(".") < 3:
            continue

        # Must not look like menu list
        if re.search(r"\n9\.\d\n", section_text):
            continue

        sections.append({
            "section_number": section_number,
            "section_title": section_title,
            "text": section_text
        })

    return sections


# ==============================
# STEP 5: Chunking
# ==============================

def chunk_text(text):

    # Remove repeated heading inside text
    text = re.sub(r"^\d+\.\d+\s+[A-Za-z\s]+", "", text)

    sentences = re.split(r'(?<=[.!?]) +', text)

    chunks = []
    current_chunk = ""

    for sentence in sentences:

        candidate = current_chunk + " " + sentence
        token_count = len(tokenizer(candidate)["input_ids"])

        if token_count <= MAX_TOKENS:
            current_chunk = candidate
        else:
            if len(tokenizer(current_chunk)["input_ids"]) > 40:
                chunks.append(current_chunk.strip())
            current_chunk = sentence

    if len(tokenizer(current_chunk)["input_ids"]) > 40:
        chunks.append(current_chunk.strip())

    return chunks


# ==============================
# MAIN PIPELINE
# ==============================

def process_pdf():

    print("Extracting text...")
    raw_text = extract_text(PDF_PATH)

    print("Cleaning text...")
    cleaned_text = clean_text(raw_text)

    print("Extracting chapter info...")
    chapter_number, chapter_title = extract_chapter_info(cleaned_text)

    print(f"Detected Chapter {chapter_number}: {chapter_title}")

    print("Detecting real sections...")
    sections = split_real_sections(cleaned_text, chapter_number)

    print(f"Valid sections detected: {len(sections)}")

    all_chunks = []
    global_index = 0

    for section in tqdm(sections):

        section_number = section["section_number"]
        section_title = section["section_title"]

        chunks = chunk_text(section["text"])

        for chunk in chunks:

            chunk_id = f"bio_ch{chapter_number}_{section_number}_{global_index:04}"

            all_chunks.append({
                "id": chunk_id,
                "subject": SUBJECT,
                "chapter": chapter_title,
                "chapter_number": chapter_number,
                "section": section_number,
                "section_title": section_title,
                "text": chunk,
            })

            global_index += 1

    print(f"\nGenerated {len(all_chunks)} clean embedding-ready chunks.")

    with open(OUTPUT_JSON, "w", encoding="utf-8") as f:
        json.dump(all_chunks, f, indent=2)

    print(f"Saved to {OUTPUT_JSON}")


if __name__ == "__main__":
    process_pdf()