đ Upload Files
+ +âšī¸ How It Works
+What is NextGenPDF?
+NextGenPDF automatically extracts student information from PDF registration forms and organizes them into a structured folder system.
+diff --git a/pdf_renamer.py b/pdf_renamer.py index 4d9d9c5..bef50a8 100644 --- a/pdf_renamer.py +++ b/pdf_renamer.py @@ -367,9 +367,6 @@ ALLOWED_MIME_TYPES = { 'zip': 'application/zip' } -# Global jobs storage (in production, use database) -job_queue = {} - def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): @@ -421,6 +418,20 @@ def allowed_file(filename, file_stream=None): return True +def sanitize_segment(value): + """Sanitize a path segment to avoid invalid/unsafe filesystem characters.""" + import re + if value is None: + return '' + cleaned = str(value) + # Remove path separators and other unsafe characters + cleaned = cleaned.replace(os.sep, ' ') + if os.altsep: + cleaned = cleaned.replace(os.altsep, ' ') + cleaned = re.sub(r'[<>:"/\\|?*\x00-\x1F]', ' ', cleaned) + cleaned = re.sub(r'\s+', ' ', cleaned).strip() + return cleaned + def extract_names_from_pdf(pdf_path): reader = None try: @@ -468,7 +479,11 @@ def extract_student_info_from_text(text): 'school': '', 'gender': '' } - lines = text.split('\n') + required_fields = ['first_name', 'last_name', 'grade', 'school', 'gender'] + lines = [l.strip() for l in text.split('\n')] + name_re = r"[A-Z][a-zA-Z]+(?:[-'][A-Za-zA-Z]+)*" + def norm_name(value): + return value.strip().replace('"', '').title() # --- Custom patterns for failed extractions --- # Pattern: GenderSchoolLastName, GradeFirstName (e.g., FemaleBrookville HighMouse, 11Mickey) @@ -510,6 +525,518 @@ def extract_student_info_from_text(text): extracted['grade'] = m3.group(2) break + # Pattern: FirstName, SchoolGradeLastName, Gender (e.g., Michael, Home School7Shuck, Male) + for i in range(len(lines) - 2): + m1 = re.match(r'^([A-Z][a-z]+)$', lines[i]) + m2 = re.match(r'^(.+?)(\d{1,2}|K)([A-Z][a-z]+)$', lines[i+1]) + m3 = re.match(r'^(Female|Male)$', lines[i+2]) + if m1 and m2 and m3: + extracted['first_name'] = m1.group(1) + extracted['school'] = m2.group(1).strip() + extracted['grade'] = m2.group(2) + extracted['last_name'] = m2.group(3) + extracted['gender'] = m3.group(1) + break + + # Pattern: GradeFirstName, SchoolLastName, Gender (e.g., 9Joshua, Liberty Christian AcademyMcCain, Male) + for i in range(len(lines) - 2): + m1 = re.match(r'^(\d{1,2}|K)([A-Z][a-z]+)$', lines[i]) + m2 = re.match(r'^(.+?)([A-Z][a-z]+)$', lines[i+1]) + m3 = re.match(r'^(Female|Male)$', lines[i+2]) + if m1 and m2 and m3: + extracted['grade'] = m1.group(1) + extracted['first_name'] = m1.group(2) + extracted['school'] = m2.group(1).strip() + extracted['last_name'] = m2.group(2) + extracted['gender'] = m3.group(1) + break + + # Pattern: School, GenderFirstName, GradeLastName (e.g., Other (Not Listed), FemaleSophia, 11Porter) + for i in range(len(lines) - 2): + m1 = re.match(r'^(.+)$', lines[i]) + m2 = re.match(r'^(Female|Male)([A-Z][a-z]+)$', lines[i+1]) + m3 = re.match(r'^(\d{1,2}|K)([A-Z][a-z]+)$', lines[i+2]) + if m1 and m2 and m3: + extracted['school'] = m1.group(1).strip() + extracted['gender'] = m2.group(1) + extracted['first_name'] = m2.group(2) + extracted['grade'] = m3.group(1) + extracted['last_name'] = m3.group(2) + return extracted + + # Pattern: GenderGradeLastNameFirstName, School (e.g., Female11SpearAva, Home School) + for i in range(len(lines) - 1): + m1 = re.match(r'^(Female|Male)(\d{1,2}|K)([A-Z][a-z]+)([A-Z][a-z]+)$', lines[i]) + m2 = re.match(r'^(.+)$', lines[i+1]) + if m1 and m2: + extracted['gender'] = m1.group(1) + extracted['grade'] = m1.group(2) + extracted['last_name'] = m1.group(3) + extracted['first_name'] = m1.group(4) + extracted['school'] = m2.group(1).strip() + break + + # Pattern: GenderGradeLastName, SchoolFirstName (e.g., Male11Seckman, New CovenantMicharl) + for i in range(len(lines) - 1): + m1 = re.match(r'^(Female|Male)(\d{1,2}|K)([A-Z][a-z]+)$', lines[i]) + m2 = re.match(r'^(.+?)([A-Z][a-z]+)$', lines[i+1]) + if m1 and m2: + extracted['gender'] = m1.group(1) + extracted['grade'] = m1.group(2) + extracted['last_name'] = m1.group(3) + extracted['school'] = m2.group(1).strip() + extracted['first_name'] = m2.group(2) + break + + # Pattern: GenderLastName, GradeFirstName, School (e.g., MaleKirk, 10Jaihden "JJ", Home School) + for i in range(len(lines) - 2): + m1 = re.match(r'^(Female|Male)([A-Z][a-z]+)$', lines[i]) + m2 = re.match(r'^(\d{1,2}|K)([A-Z][a-z]+)', lines[i+1]) + m3 = re.match(r'^(.+)$', lines[i+2]) + if m1 and m2 and m3: + extracted['gender'] = m1.group(1) + extracted['last_name'] = m1.group(2) + extracted['grade'] = m2.group(1) + extracted['first_name'] = m2.group(2) + extracted['school'] = m3.group(1).strip() + break + + # Pattern: FirstName, Grade, SchoolLastName, Gender (e.g., Tracey, 12, Other (Not Listed)Lindsay, Female) + for i in range(len(lines) - 3): + m1 = re.match(r'^([A-Z][a-z]+)$', lines[i]) + m2 = re.match(r'^(\d{1,2}|K)$', lines[i+1]) + m3 = re.match(r'^(.+?)([A-Z][a-z]+)$', lines[i+2]) + m4 = re.match(r'^(Female|Male)$', lines[i+3]) + if m1 and m2 and m3 and m4: + extracted['first_name'] = m1.group(1) + extracted['grade'] = m2.group(1) + extracted['school'] = m3.group(1).strip() + extracted['last_name'] = m3.group(2) + extracted['gender'] = m4.group(1) + break + + # Pattern: GenderGrade, SchoolLastNameFirstName (e.g., Female7, Brookville MiddleBrownMicah) + for i in range(len(lines) - 1): + m1 = re.match(r'^(Female|Male)(\d{1,2}|K)$', lines[i]) + m2 = re.match(r'^(.+?)([A-Z][a-z]+)([A-Z][a-z]+)$', lines[i+1]) + if m1 and m2: + extracted['gender'] = m1.group(1) + extracted['grade'] = m1.group(2) + extracted['school'] = m2.group(1).strip() + extracted['last_name'] = m2.group(2) + extracted['first_name'] = m2.group(3) + break + + # Pattern: GenderSchoolGradeFirstName, LastName (e.g., FemaleRustburg High9Lexi, Wilson) + for i in range(len(lines) - 1): + m1 = re.match(r'^(Female|Male)(.+?)(\d{1,2}|K)(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(' + name_re + r')$', lines[i+1]) + if m1 and m2: + extracted['gender'] = m1.group(1) + extracted['school'] = m1.group(2).strip() + extracted['grade'] = m1.group(3) + extracted['first_name'] = m1.group(4) + extracted['last_name'] = m2.group(1) + break + + # Pattern: GenderSchoolGradeLastNameFirstName (e.g., MaleLiberty Christian Academy11SnakenbergBryson) + for i in range(len(lines) - 0): + m1 = re.match(r'^(Female|Male)(.+?)(\d{1,2}|K)([A-Z][a-z]+)([A-Z][a-z]+)$', lines[i]) + if m1: + extracted['gender'] = m1.group(1) + extracted['school'] = m1.group(2).strip() + extracted['grade'] = m1.group(3) + extracted['last_name'] = m1.group(4) + extracted['first_name'] = m1.group(5) + break + + # Pattern: SchoolLastNameFirstName, Grade, Gender (e.g., Monelison MiddleCarmodyLandon, 7, Male) + for i in range(len(lines) - 2): + m1 = re.match(r'^(.+?)([A-Z][a-z]+)([A-Z][a-z]+)$', lines[i]) + m2 = re.match(r'^(\d{1,2}|K)$', lines[i+1]) + m3 = re.match(r'^(Female|Male)$', lines[i+2]) + if m1 and m2 and m3: + extracted['school'] = m1.group(1).strip() + extracted['last_name'] = m1.group(2) + extracted['first_name'] = m1.group(3) + extracted['grade'] = m2.group(1) + extracted['gender'] = m3.group(1) + break + + # Pattern: FirstName, GradeLastName, School, Gender (e.g., Jesse, 6McGinniss, Timberlake Christian, Male) + for i in range(len(lines) - 3): + m1 = re.match(r'^([A-Z][a-z]+)$', lines[i]) + m2 = re.match(r'^(\d{1,2}|K)(' + name_re + r')$', lines[i+1]) + m3 = re.match(r'^(.+)$', lines[i+2]) + m4 = re.match(r'^(Female|Male)$', lines[i+3]) + if m1 and m2 and m3 and m4: + extracted['first_name'] = m1.group(1) + extracted['grade'] = m2.group(1) + extracted['last_name'] = m2.group(2) + extracted['school'] = m3.group(1).strip() + extracted['gender'] = m4.group(1) + break + + # Pattern: GenderFirstName, LastName, Grade, School (e.g., MaleBenjamin, Byrne-Cox, 12, Home School) + for i in range(len(lines) - 3): + m1 = re.match(r'^(Female|Male)([A-Z][a-z]+)$', lines[i]) + m2 = re.match(r'^(' + name_re + r')$', lines[i+1]) + m3 = re.match(r'^(\d{1,2}|K)$', lines[i+2]) + m4 = re.match(r'^(.+)$', lines[i+3]) + if m1 and m2 and m3 and m4: + extracted['gender'] = m1.group(1) + extracted['first_name'] = m1.group(2) + extracted['last_name'] = m2.group(1) + extracted['grade'] = m3.group(1) + extracted['school'] = m4.group(1).strip() + break + + # Pattern: FirstName, LastName, Grade, GenderSchool (e.g., Andrew, Servin-Hernandez, 8, MaleSandusky Middle) + for i in range(len(lines) - 3): + m1 = re.match(r'^(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(' + name_re + r')$', lines[i+1]) + m3 = re.match(r'^(\d{1,2}|K)$', lines[i+2]) + m4 = re.match(r'^(Female|Male)(.+)$', lines[i+3]) + if m1 and m2 and m3 and m4: + extracted['first_name'] = m1.group(1) + extracted['last_name'] = m2.group(1) + extracted['grade'] = m3.group(1) + extracted['gender'] = m4.group(1) + extracted['school'] = m4.group(2).strip() + break + + # Pattern: SchoolGradeFirstName, GenderLastName (e.g., Temple Christian6Morgan, MaleChristian) + for i in range(len(lines) - 1): + m1 = re.match(r'^(.+?)(\d{1,2}|K)(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(Female|Male)(' + name_re + r')$', lines[i+1]) + if m1 and m2: + extracted['school'] = m1.group(1).strip() + extracted['grade'] = m1.group(2) + extracted['first_name'] = m1.group(3) + extracted['gender'] = m2.group(1) + extracted['last_name'] = m2.group(2) + break + + # Pattern: GenderGradeLastName, SchoolFirstName (e.g., Male9Bieri, Liberty Christian AcademySJ) + for i in range(len(lines) - 1): + m1 = re.match(r'^(Female|Male)(\d{1,2}|K)(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(.+?)(' + name_re + r')$', lines[i+1]) + if m1 and m2: + extracted['gender'] = m1.group(1) + extracted['grade'] = m1.group(2) + extracted['last_name'] = m1.group(3) + extracted['school'] = m2.group(1).strip() + extracted['first_name'] = m2.group(2) + break + + # Pattern: SchoolLastNameFirstName, GenderGrade (e.g., Home SchoolBethelAbigail, Female7) + for i in range(len(lines) - 1): + m1 = re.match(r'^(.+?)(' + name_re + r')(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(Female|Male)(\d{1,2}|K)$', lines[i+1]) + if m1 and m2: + extracted['school'] = m1.group(1).strip() + extracted['last_name'] = m1.group(2) + extracted['first_name'] = m1.group(3) + extracted['gender'] = m2.group(1) + extracted['grade'] = m2.group(2) + break + + # Pattern: SchoolLastName, GradeFirstName, Gender (e.g., Other (Not Listed)Caballero, 7Mason, Male) + for i in range(len(lines) - 2): + m1 = re.match(r'^(.+?)(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(\d{1,2}|K)(' + name_re + r')$', lines[i+1]) + m3 = re.match(r'^(Female|Male)$', lines[i+2]) + if m1 and m2 and m3: + extracted['school'] = m1.group(1).strip() + extracted['last_name'] = m1.group(2) + extracted['grade'] = m2.group(1) + extracted['first_name'] = m2.group(2) + extracted['gender'] = m3.group(1) + break + + # Pattern: GradeLastName, School, GenderFirstName (e.g., 6Petty, Other (Not Listed), FemaleAnia) + for i in range(len(lines) - 2): + m1 = re.match(r'^(\d{1,2}|K)(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(.+)$', lines[i+1]) + m3 = re.match(r'^(Female|Male)(' + name_re + r')$', lines[i+2]) + if m1 and m2 and m3: + extracted['grade'] = m1.group(1) + extracted['last_name'] = m1.group(2) + extracted['school'] = m2.group(1).strip() + extracted['gender'] = m3.group(1) + extracted['first_name'] = m3.group(2) + break + + # Pattern: Grade, SchoolLastNameFirstName, Gender (e.g., 9, Home SchoolSpearCharlotte, Female) + for i in range(len(lines) - 2): + m1 = re.match(r'^(\d{1,2}|K)$', lines[i]) + m2 = re.match(r'^(.+?)(' + name_re + r')(' + name_re + r')$', lines[i+1]) + m3 = re.match(r'^(Female|Male)$', lines[i+2]) + if m1 and m2 and m3: + extracted['grade'] = m1.group(1) + extracted['school'] = m2.group(1).strip() + extracted['last_name'] = m2.group(2) + extracted['first_name'] = m2.group(3) + extracted['gender'] = m3.group(1) + break + + # Pattern: Grade, GenderSchoolLastNameFirstName (e.g., 9, FemaleHome SchoolHeagyPaige) + for i in range(len(lines) - 1): + m1 = re.match(r'^(\d{1,2}|K)$', lines[i]) + m2 = re.match(r'^(Female|Male)(.+?)(' + name_re + r')(' + name_re + r')$', lines[i+1]) + if m1 and m2: + extracted['grade'] = m1.group(1) + extracted['gender'] = m2.group(1) + extracted['school'] = m2.group(2).strip() + extracted['last_name'] = m2.group(3) + extracted['first_name'] = m2.group(4) + break + + # Pattern: GenderGradeLastName FirstName, School (e.g., Male7Trochesset Oliver, Liberty Christian Academy) + for i in range(len(lines) - 1): + m1 = re.match(r'^(Female|Male)(\d{1,2}|K)(' + name_re + r')\s+(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(.+)$', lines[i+1]) + if m1 and m2: + extracted['gender'] = m1.group(1) + extracted['grade'] = m1.group(2) + extracted['last_name'] = m1.group(3) + extracted['first_name'] = m1.group(4) + extracted['school'] = m2.group(1).strip() + break + + # Pattern: FirstName, SchoolLastName, Grade, Gender (e.g., Neale, Other (Not Listed)Hans, 9, Male) + for i in range(len(lines) - 3): + m1 = re.match(r'^(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(.+?)(' + name_re + r')$', lines[i+1]) + m3 = re.match(r'^(\d{1,2}|K)$', lines[i+2]) + m4 = re.match(r'^(Female|Male)$', lines[i+3]) + if m1 and m2 and m3 and m4: + extracted['first_name'] = m1.group(1) + extracted['school'] = m2.group(1).strip() + extracted['last_name'] = m2.group(2) + extracted['grade'] = m3.group(1) + extracted['gender'] = m4.group(1) + break + + # Pattern: GradeLastNameFirstName, GenderSchool (e.g., 11DorsettJaxon, MaleLiberty Christian Academy) + for i in range(len(lines) - 1): + m1 = re.match(r'^(\d{1,2}|K)(' + name_re + r')(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(Female|Male)(.+)$', lines[i+1]) + if m1 and m2: + extracted['grade'] = m1.group(1) + extracted['last_name'] = m1.group(2) + extracted['first_name'] = m1.group(3) + extracted['gender'] = m2.group(1) + extracted['school'] = m2.group(2).strip() + break + + # Pattern: FirstName, Grade, GenderSchoolLastName (e.g., Neale, 6, MaleOther (Not Listed)Sebastian) + for i in range(len(lines) - 2): + m1 = re.match(r'^(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(\d{1,2}|K)$', lines[i+1]) + m3 = re.match(r'^(Female|Male)(.+?)(' + name_re + r')$', lines[i+2]) + if m1 and m2 and m3: + extracted['first_name'] = m1.group(1) + extracted['grade'] = m2.group(1) + extracted['gender'] = m3.group(1) + extracted['school'] = m3.group(2).strip() + extracted['last_name'] = m3.group(3) + break + + # Pattern: Name, Grade, GenderSchool (missing first or last name) + for i in range(len(lines) - 2): + m1 = re.match(r'^([A-Za-z]+)$', lines[i]) + m2 = re.match(r'^(\d{1,2}|K)$', lines[i+1]) + m3 = re.match(r'^(Female|Male)(.+)$', lines[i+2]) + if m1 and m2 and m3: + extracted['last_name'] = norm_name(m1.group(1)) + extracted['first_name'] = 'Unknown' + extracted['grade'] = m2.group(1) + extracted['gender'] = m3.group(1) + extracted['school'] = m3.group(2).strip() + return extracted + + # Pattern: GradeLastNameFirstName, School, Gender (e.g., 12ClarkAva, Jefferson Forest High, Female) + for i in range(len(lines) - 2): + m1 = re.match(r'^(\d{1,2}|K)(' + name_re + r')(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(.+)$', lines[i+1]) + m3 = re.match(r'^(Female|Male)$', lines[i+2]) + if m1 and m2 and m3: + extracted['grade'] = m1.group(1) + extracted['last_name'] = m1.group(2) + extracted['first_name'] = m1.group(3) + extracted['school'] = m2.group(1).strip() + extracted['gender'] = m3.group(1) + break + + # Pattern: LastName, Grade, GenderFirstName, School (e.g., Johnke, 6, FemaleKyla, Other (Not Listed)) + for i in range(len(lines) - 3): + m1 = re.match(r'^(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(\d{1,2}|K)$', lines[i+1]) + m3 = re.match(r'^(Female|Male)(' + name_re + r')$', lines[i+2]) + m4 = re.match(r'^(.+)$', lines[i+3]) + if m1 and m2 and m3 and m4: + extracted['last_name'] = m1.group(1) + extracted['grade'] = m2.group(1) + extracted['gender'] = m3.group(1) + extracted['first_name'] = m3.group(2) + extracted['school'] = m4.group(1).strip() + break + + # Pattern: SchoolFirstName (case-insensitive), GenderGradeLastName (case-insensitive) + for i in range(len(lines) - 1): + m1 = re.match(r'^(.+?)([A-Za-z]+)$', lines[i], re.IGNORECASE) + m2 = re.match(r'^(Female|Male)(\d{1,2}|K)([A-Za-z]+)$', lines[i+1], re.IGNORECASE) + if m1 and m2: + extracted['school'] = m1.group(1).strip() + extracted['first_name'] = norm_name(m1.group(2)) + extracted['gender'] = m2.group(1).capitalize() + extracted['grade'] = m2.group(2) + extracted['last_name'] = norm_name(m2.group(3)) + break + + # Pattern: LastName, GenderSchoolGradeFirstName (e.g., Porter, MaleLiberty Christian Academy8Samuel) + for i in range(len(lines) - 1): + m1 = re.match(r'^(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(Female|Male)(.+?)(\d{1,2}|K)(' + name_re + r')$', lines[i+1]) + if m1 and m2: + extracted['last_name'] = m1.group(1) + extracted['gender'] = m2.group(1) + extracted['school'] = m2.group(2).strip() + extracted['grade'] = m2.group(3) + extracted['first_name'] = m2.group(4) + break + + # Pattern: LastName, GenderFirstName, Grade, School (e.g., Brainard, FemaleEmily, 10, Other (Not Listed)) + for i in range(len(lines) - 3): + m1 = re.match(r'^(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(Female|Male)(' + name_re + r')$', lines[i+1]) + m3 = re.match(r'^(\d{1,2}|K)$', lines[i+2]) + m4 = re.match(r'^(.+)$', lines[i+3]) + if m1 and m2 and m3 and m4: + extracted['last_name'] = m1.group(1) + extracted['gender'] = m2.group(1) + extracted['first_name'] = m2.group(2) + extracted['grade'] = m3.group(1) + extracted['school'] = m4.group(1).strip() + break + + # Pattern: LastName, GenderSchoolGradeFirstName (e.g., Mays, FemaleAmherst High9Kara) + for i in range(len(lines) - 1): + m1 = re.match(r'^(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(Female|Male)(.+?)(\d{1,2}|K)(' + name_re + r')$', lines[i+1]) + if m1 and m2: + extracted['last_name'] = m1.group(1) + extracted['gender'] = m2.group(1) + extracted['school'] = m2.group(2).strip() + extracted['grade'] = m2.group(3) + extracted['first_name'] = m2.group(4) + break + + # Pattern: GradeLastName, GenderSchoolFirstName (e.g., 10Davis, FemaleE.C. Glass HighTa'Mya) + for i in range(len(lines) - 1): + m1 = re.match(r'^(\d{1,2}|K)(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(Female|Male)(.+?)(' + name_re + r')$', lines[i+1]) + if m1 and m2: + extracted['grade'] = m1.group(1) + extracted['last_name'] = m1.group(2) + extracted['gender'] = m2.group(1) + extracted['school'] = m2.group(2).strip() + extracted['first_name'] = m2.group(3) + break + + # Pattern: School, GenderLastName, GradeFirstName (e.g., Liberty Christian Academy, MaleHagner, 9Jarad Jr.) + for i in range(len(lines) - 2): + m1 = re.match(r'^(.+)$', lines[i]) + m2 = re.match(r'^(Female|Male)(' + name_re + r')$', lines[i+1]) + m3 = re.match(r'^(\d{1,2}|K)(' + name_re + r')', lines[i+2]) + if m1 and m2 and m3: + extracted['school'] = m1.group(1).strip() + extracted['gender'] = m2.group(1) + extracted['last_name'] = m2.group(2) + extracted['grade'] = m3.group(1) + extracted['first_name'] = m3.group(2) + break + + # Pattern: GenderLastName, SchoolGradeFirstName (e.g., MaleMartin, Monelison Middle7Levi) + for i in range(len(lines) - 1): + m1 = re.match(r'^(Female|Male)(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(.+?)(\d{1,2}|K)(' + name_re + r')$', lines[i+1]) + if m1 and m2: + extracted['gender'] = m1.group(1) + extracted['last_name'] = m1.group(2) + extracted['school'] = m2.group(1).strip() + extracted['grade'] = m2.group(2) + extracted['first_name'] = m2.group(3) + break + + # Pattern: FirstName, School, GenderGradeLastName (e.g., Lillian, Other (Not Listed), Female6Buchanan) + for i in range(len(lines) - 2): + m1 = re.match(r'^(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(.+)$', lines[i+1]) + m3 = re.match(r'^(Female|Male)(\d{1,2}|K)(' + name_re + r')$', lines[i+2]) + if m1 and m2 and m3: + extracted['first_name'] = m1.group(1) + extracted['school'] = m2.group(1).strip() + extracted['gender'] = m3.group(1) + extracted['grade'] = m3.group(2) + extracted['last_name'] = m3.group(3) + break + + # Pattern: FirstName, School, GenderLastName, Grade (e.g., Luke, Other (Not Listed), MaleHottle, 11) + for i in range(len(lines) - 3): + m1 = re.match(r'^(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(.+)$', lines[i+1]) + m3 = re.match(r'^(Female|Male)(' + name_re + r')$', lines[i+2]) + m4 = re.match(r'^(\d{1,2}|K)$', lines[i+3]) + if m1 and m2 and m3 and m4: + extracted['first_name'] = m1.group(1) + extracted['school'] = m2.group(1).strip() + extracted['gender'] = m3.group(1) + extracted['last_name'] = m3.group(2) + extracted['grade'] = m4.group(1) + break + + # Pattern: SchoolGradeFirstName, LastName, Gender (e.g., Monelison Middle8Laila, Mays, Female) + for i in range(len(lines) - 2): + m1 = re.match(r'^(.+?)(\d{1,2}|K)(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(' + name_re + r')$', lines[i+1]) + m3 = re.match(r'^(Female|Male)$', lines[i+2]) + if m1 and m2 and m3: + extracted['school'] = m1.group(1).strip() + extracted['grade'] = m1.group(2) + extracted['first_name'] = m1.group(3) + extracted['last_name'] = m2.group(1) + extracted['gender'] = m3.group(1) + break + + # Pattern: GradeLastNameFirstName (multi-word first), GenderSchool (e.g., 6HoskinsMary Emma, FemaleNew Covenant) + for i in range(len(lines) - 1): + m1 = re.match(r'^(\d{1,2}|K)(' + name_re + r')(.+)$', lines[i]) + m2 = re.match(r'^(Female|Male)(.+)$', lines[i+1]) + if m1 and m2: + extracted['grade'] = m1.group(1) + extracted['last_name'] = m1.group(2) + extracted['first_name'] = m1.group(3).strip() + extracted['gender'] = m2.group(1) + extracted['school'] = m2.group(2).strip() + break + + # Pattern: SchoolGradeLastNameFirstName, Gender (e.g., Brookville Middle6DraganEmily, Female) + for i in range(len(lines) - 1): + m1 = re.match(r'^(.+?)(\d{1,2}|K)(' + name_re + r')(' + name_re + r')$', lines[i]) + m2 = re.match(r'^(Female|Male)$', lines[i+1]) + if m1 and m2: + extracted['school'] = m1.group(1).strip() + extracted['grade'] = m1.group(2) + extracted['last_name'] = m1.group(3) + extracted['first_name'] = m1.group(4) + extracted['gender'] = m2.group(1) + break + + if all(extracted.get(field) for field in required_fields): + return extracted + # Enhanced preprocessing: aggressively split lines with multiple fields stuck together processed_lines = [] skip_headers = [ @@ -604,7 +1131,7 @@ def extract_student_info_from_text(text): processed_lines.append(m5.group(1)) processed_lines.append(m5.group(2).strip()) processed_lines.append(m5.group(3)) - processed_lines.append(m4.group(4)) + processed_lines.append(m5.group(4)) continue # Pattern: Grade + Gender + School + FirstName + LastName (e.g., '10FemaleHomeSchoolAbigailDober') m6 = re.match(r'^(\d{1,2}|K)(Female|Male)([A-Z][a-z]+(?: [A-Z][a-z]+)*)([A-Z][a-z]+)([A-Z][a-z]+)$', orig_line) @@ -765,16 +1292,37 @@ def extract_student_info_from_text(text): # Post-processing and validation required_fields = ['first_name', 'last_name', 'grade', 'school', 'gender'] if all(extracted.get(field) for field in required_fields): - # Clean up school name: remove any trailing capitalized names or numbers - extracted['school'] = re.sub(r'([A-Z][a-z]+|\d+)$', '', extracted['school']).strip() + # Clean up school name: remove trailing student name or stray numbers, but keep valid suffixes + school_clean = extracted['school'].strip() + suffix_keep = {'School', 'Academy', 'Middle', 'High', 'Elementary', 'Christian', 'Catholic', 'Public', 'Private'} + parts = school_clean.split() + if parts: + last_part = parts[-1] + first_name = extracted['first_name'] or '' + last_name = extracted['last_name'] or '' + if last_part.isdigit(): + parts = parts[:-1] + elif last_part in {first_name, last_name}: + parts = parts[:-1] + elif last_part not in suffix_keep and last_part in {first_name.title(), last_name.title()}: + parts = parts[:-1] + school_clean = ' '.join(parts).strip() # Remove any names from school if they match first/last name for n in [extracted['first_name'], extracted['last_name']]: if n: - extracted['school'] = re.sub(rf'\b{re.escape(n)}\b', '', extracted['school']).strip() - # Final check: if school is just a number or a name, discard it - if re.fullmatch(r'(\d{1,2}|K)', extracted['school']) or re.fullmatch(r'([A-Z][a-z]+|[A-Z\-]+|[A-Z][a-z]+\s[A-Z][a-z]+)+', extracted['school']): + school_clean = re.sub(rf'\b{re.escape(n)}\b', '', school_clean).strip() + extracted['school'] = school_clean + # Final check: discard invalid schools (pure grade or same as student name) + school_clean = extracted['school'].strip() + if re.fullmatch(r'(\d{1,2}|K)', school_clean): extracted['school'] = '' - extracted['school'] = extracted['school'].strip() + else: + first_lower = extracted['first_name'].lower() if extracted['first_name'] else '' + last_lower = extracted['last_name'].lower() if extracted['last_name'] else '' + if school_clean.lower() in {first_lower, last_lower}: + extracted['school'] = '' + else: + extracted['school'] = school_clean return extracted # Fallback: Try to extract fields from any order in the lines (for highly concatenated or out-of-order data) @@ -826,6 +1374,7 @@ def extract_student_info_from_text(text): def process_job(job_id, files): temp_extract_dirs = [] + job_failed = False try: print("=== DEBUG: process_job STARTED ===") with job_lock: @@ -833,7 +1382,7 @@ def process_job(job_id, files): job_queue[job_id]['progress'] = 'Starting job processing...' save_jobs() # Persist initial progress # Create base Episode directory for this job - episode_dir = os.path.join(app.config['UPLOAD_FOLDER'], 'Episode') + episode_dir = os.path.join(app.config['UPLOAD_FOLDER'], f'Episode_{job_id}') os.makedirs(episode_dir, exist_ok=True) processed_files = [] skipped_dir = os.path.join(episode_dir, 'Skipped') @@ -923,15 +1472,16 @@ def process_job(job_id, files): job_queue[job_id]['progress'] = f'Extracted data for {pdf_info["first_name"]} {pdf_info["last_name"]}' print(f"DEBUG: Successfully extracted data for {pdf_info['first_name']} {pdf_info['last_name']}") # Create folder structure: Episode/School/Grade/Gender/ - school_dir = os.path.join(episode_dir, pdf_info['school']) - grade_dir = os.path.join(school_dir, pdf_info['grade']) - gender_dir = os.path.join(grade_dir, pdf_info['gender']) + school_dir = os.path.join(episode_dir, sanitize_segment(pdf_info['school']) or 'Unknown_School') + grade_dir = os.path.join(school_dir, sanitize_segment(pdf_info['grade']) or 'Unknown_Grade') + gender_dir = os.path.join(grade_dir, sanitize_segment(pdf_info['gender']) or 'Unknown_Gender') print(f"DEBUG: Creating directories: {school_dir} -> {grade_dir} -> {gender_dir}") os.makedirs(gender_dir, exist_ok=True) print(f"DEBUG: Directories created successfully") # Move file to appropriate folder with new name - new_path = os.path.join(gender_dir, pdf_info['filename']) + safe_filename = secure_filename(pdf_info['filename']) or pdf_info['filename'] + new_path = os.path.join(gender_dir, safe_filename) print(f"DEBUG: Attempting to move {pdf_path} to {new_path}") counter = 1 while os.path.exists(new_path): @@ -984,13 +1534,14 @@ def process_job(job_id, files): else: job_queue[job_id]['progress'] = f'Extracted data for {pdf_info["first_name"]} {pdf_info["last_name"]}' # Create folder structure: Episode/School/Grade/Gender/ - school_dir = os.path.join(episode_dir, pdf_info['school']) - grade_dir = os.path.join(school_dir, pdf_info['grade']) - gender_dir = os.path.join(grade_dir, pdf_info['gender']) + school_dir = os.path.join(episode_dir, sanitize_segment(pdf_info['school']) or 'Unknown_School') + grade_dir = os.path.join(school_dir, sanitize_segment(pdf_info['grade']) or 'Unknown_Grade') + gender_dir = os.path.join(grade_dir, sanitize_segment(pdf_info['gender']) or 'Unknown_Gender') os.makedirs(gender_dir, exist_ok=True) # Move file to appropriate folder with new name - new_filepath = os.path.join(gender_dir, pdf_info['filename']) + safe_filename = secure_filename(pdf_info['filename']) or pdf_info['filename'] + new_filepath = os.path.join(gender_dir, safe_filename) counter = 1 while os.path.exists(new_filepath): base, ext = os.path.splitext(pdf_info['filename']) @@ -1047,11 +1598,13 @@ def process_job(job_id, files): except Exception as e: job_queue[job_id]['progress'] = f'Error creating ZIP: {str(e)}' job_queue[job_id]['status'] = 'error' + job_failed = True save_jobs() # Persist error status print(f"DEBUG: ZIP creation failed: {str(e)}") with job_lock: - job_queue[job_id]['status'] = 'completed' + if not job_failed: + job_queue[job_id]['status'] = 'completed' job_queue[job_id]['processed_files'] = processed_files # Store skipped as (filename, reason, raw_text) job_queue[job_id]['skipped'] = [(p[1], p[2], p[3]) for p in processed_files if p[0] == 'skipped'] @@ -1081,36 +1634,178 @@ def login():
-
-