r/passcp3•
aPhosphate

2025

import re
import pandas as pd

# Data from the user prompt
sept_data_raw = """CP31,38475257**54.3%53.1%CP21,3257575757.1%59.1%CM11,3023725928.6%42.0%CM21,2244585937.4%47.7%CS11,18852258*43.9%43.8%CB29415795861.5%62.8%CS28913015633.8%48.1%CB18685636064.9%58.8%CP17593175541.8%43.6%SP249820456*41.0%41.9%SA345719560*42.7%31.6%SP84012065851.4%35.9%SA24001394534.8%45.1%SP73851486438.4%43.6%SP12601306050.0%50.9%SP52531326452.2%51.0%SP4153705845.8%64.9%SP9150675744.7%36.7%SA4136685850.0%52.7%SA7762753*35.5%50.6%SA144135529.5%44.0%SP6391860*46.2%52.0%"""

april_data_raw = """CM113765485942.0%CP3132270259*53.1%CP112495445543.6%CB111206585858.8%CS1107046955*43.8%CM210124836147.7%CP29685725959.1%CS29484565748.1%CB263940159*62.8%SA344314058*31.6%SA24411994545.1%SP241817555*41.9%SP739717360*43.6%SP83871395735.9%SP12181115550.9%SP5192986051.0%SA4167885552.7%SP9147545536.7%SP4131855864.9%SA7834259*50.6%SA150225544.0%SP6251359*52.0%"""

def parse_data(raw_string, diet_name):
    # Regex to find Subject followed by the rest of the string until the next Subject
    # Strategy: Find all indices of Subject patterns
    subject_pattern = re.compile(r'([A-Z]{2}[0-9])')
    matches = list(subject_pattern.finditer(raw_string))
    
    parsed_items = []
    
    for i in range(len(matches)):
        subj = matches[i].group(1)
        start_idx = matches[i].end()
        end_idx = matches[i+1].start() if i + 1 < len(matches) else len(raw_string)
        
        data_chunk = raw_string[start_idx:end_idx]
        
        # Extract the numeric part at the beginning of data_chunk
        # It contains Attempted, Passed, Pass Mark, then percentages
        # We look for the first % to bound the numbers
        # But there might be '*'
        
        # Let's just extract all digits and commas at the start
        numeric_match = re.match(r'^([0-9,]+)', data_chunk)
        if not numeric_match:
            continue
            
        num_str = numeric_match.group(1).replace(',', '')
        
        # We need to split num_str into Attempted, Passed, Pass Mark
        # We can iterate through possible split points
        # Attempted is likely the largest number, or at least >= Passed
        
        # Also, Pass Mark is typically 2 digits (e.g. 50-65)
        
        # Let's try to verify with Pass Rate if present in the chunk
        # Regex for pass rate: number followed by %
        pass_rate_match = re.search(r'(\d+\.?\d*)%', data_chunk)
        pass_rate = float(pass_rate_match.group(1)) if pass_rate_match else None
        
        found = False
        best_fit = None
        
        # num_str = "138475257" -> 1384, 752, 57
        n = len(num_str)
        # Attempted length i, Passed length j, Mark length k. i+j+k = n
        # k is likely 2.
        
        for k in [2]: # Pass mark is usually 2 digits
            for j in range(1, n - k):
                i = n - j - k
                if i < 1: continue
                
                att_str = num_str[:i]
                pass_str = num_str[i:i+j]
                mark_str = num_str[i+j:]
                
                att = int(att_str)
                pss = int(pass_str)
                mrk = int(mark_str)
                
                if att >= pss:
                    # check pass rate consistency if available
                    if pass_rate is not None:
                        calc_rate = (pss / att) * 100
                        # tolerance check
                        if abs(calc_rate - pass_rate) < 0.5: # 1dp matching
                            best_fit = (att, pss, mrk)
                            found = True
                            break
                    else:
                        # If no pass rate, just heuristics?
                        # Prefer larger Attempted?
                        best_fit = (att, pss, mrk) # keep looking?
        
        if found:
            parsed_items.append({'Subject': subj, 'Attempted': best_fit[0], 'Diet': diet_name})
        else:
            # If verification failed (e.g. slight rounding diff), take the one that looks most reasonable
            # Heuristic: Pass Mark between 40 and 70
             for k in [2]: 
                for j in range(1, n - k):
                    i = n - j - k
                    if i < 1: continue
                    att = int(num_str[:i])
                    pss = int(num_str[i:i+j])
                    mrk = int(num_str[i+j:])
                    
                    if att >= pss and 40 <= mrk <= 70:
                        parsed_items.append({'Subject': subj, 'Attempted': att, 'Diet': diet_name})
                        break
                        
    return parsed_items

sept_parsed = parse_data(sept_data_raw, 'September 2025')
april_parsed = parse_data(april_data_raw, 'April 2025')

df_sept = pd.DataFrame(sept_parsed)
df_april = pd.DataFrame(april_parsed)

# Merge
df_merged = pd.merge(df_sept, df_april, on='Subject', suffixes=('_Sept', '_April'))
df_merged['Change'] = df_merged['Attempted_Sept'] - df_merged['Attempted_April']
df_merged['Increased'] = df_merged['Change'] > 0

print(df_merged[['Subject', 'Attempted_Sept', 'Attempted_April', 'Change', 'Increased']].sort_values('Change', ascending=False))



   Subject  Attempted_Sept  Attempted_April   Change  Increased
3      CB2         9415795              639  9415156       True
1      CP2        13257575          9685725  3571850       True
9      SP5         2531326           192986  2338340       True
8      SP1         2601306          2181115   420191       True
10     SP4          153705           131855    21850       True
2      CS1            1188             1070      118       True
6      SP2             498              418       80       True
0      CP3            1384             1322       62       True
7      SA3             457              443       14       True
13     SP6              39               25       14       True
12     SA7              76               83       -7      False
11     SA4          136685           167885   -31200      False
4      CB1         8685636         11206585 -2520949      False
5      CP1         7593175         12495445 -4902270      False


import re
import pandas as pd

sept_data_raw = """CP31,38475257**54.3%53.1%CP21,3257575757.1%59.1%CM11,3023725928.6%42.0%CM21,2244585937.4%47.7%CS11,18852258*43.9%43.8%CB29415795861.5%62.8%CS28913015633.8%48.1%CB18685636064.9%58.8%CP17593175541.8%43.6%SP249820456*41.0%41.9%SA345719560*42.7%31.6%SP84012065851.4%35.9%SA24001394534.8%45.1%SP73851486438.4%43.6%SP12601306050.0%50.9%SP52531326452.2%51.0%SP4153705845.8%64.9%SP9150675744.7%36.7%SA4136685850.0%52.7%SA7762753*35.5%50.6%SA144135529.5%44.0%SP6391860*46.2%52.0%"""

april_data_raw = """CM113765485942.0%CP3132270259*53.1%CP112495445543.6%CB111206585858.8%CS1107046955*43.8%CM210124836147.7%CP29685725959.1%CS29484565748.1%CB263940159*62.8%SA344314058*31.6%SA24411994545.1%SP241817555*41.9%SP739717360*43.6%SP83871395735.9%SP12181115550.9%SP5192986051.0%SA4167885552.7%SP9147545536.7%SP4131855864.9%SA7834259*50.6%SA150225544.0%SP6251359*52.0%"""

def extract_attempts(raw_string, diet):
    # Split by Subject
    subject_pattern = re.compile(r'([A-Z]{2}[0-9])')
    matches = list(subject_pattern.finditer(raw_string))
    
    results = []
    
    for i in range(len(matches)):
        subj = matches[i].group(1)
        start_idx = matches[i].end()
        end_idx = matches[i+1].start() if i + 1 < len(matches) else len(raw_string)
        
        chunk = raw_string[start_idx:end_idx]
        
        # Find first percentage
        pct_match = re.search(r'(\d{1,3}\.\d)%', chunk)
        if not pct_match:
            continue
            
        rate_val = float(pct_match.group(1))
        # String before the rate number
        rate_start = pct_match.start()
        
        # The rate string is actually part of the chunk
        # chunk[:rate_start] contains everything before the rate digits
        
        pre_rate_str = chunk[:rate_start]
        
        # Clean up trailing * or spaces
        pre_rate_str = pre_rate_str.rstrip('* ')
        
        # Clean commas
        clean_nums = pre_rate_str.replace(',', '')
        
        # Now split clean_nums into Att, Pass, Mark
        # Mark is last 2 digits usually
        # Loop to find valid split matching rate_val
        
        n = len(clean_nums)
        found = False
        
        # Iterate Pass Mark length (likely 2, maybe 1 or 3)
        for m_len in [2]: 
            if n - m_len < 2: continue
            
            mark_str = clean_nums[-m_len:]
            rem = clean_nums[:-m_len]
            
            # Split rem into Att, Pass
            # Att >= Pass
            
            for j in range(1, len(rem)):
                att_str = rem[:j]
                pass_str = rem[j:]
                
                # Check for empty strings
                if not att_str or not pass_str: continue
                
                att = int(att_str)
                pss = int(pass_str)
                
                if att < pss: continue
                if att == 0: continue
                
                calc_rate = (pss / att) * 100
                if abs(calc_rate - rate_val) < 0.2: # Allow small tolerance
                    results.append({'Subject': subj, 'Attempted': att, 'Passed': pss, 'Diet': diet})
                    found = True
                    break
            if found: break
        
        if not found:
             # Fallback if rate matching fails (e.g. slight rounding)
             # Heuristic: Mark is 2 digits, Att >= Pass
             # Pick split that minimizes abs(calc_rate - rate_val)
             best_split = None
             min_diff = 100
             
             for m_len in [2]:
                 mark_str = clean_nums[-m_len:]
                 rem = clean_nums[:-m_len]
                 for j in range(1, len(rem)):
                     att = int(rem[:j])
                     pss = int(rem[j:])
                     if att < pss or att == 0: continue
                     
                     calc_rate = (pss/att)*100
                     diff = abs(calc_rate - rate_val)
                     if diff < min_diff:
                         min_diff = diff
                         best_split = (att, pss)
             
             if best_split and min_diff < 2.0: # Accept if within 2%?
                 results.append({'Subject': subj, 'Attempted': best_split[0], 'Passed': best_split[1], 'Diet': diet})
             else:
                 print(f"Failed to parse {subj} in {diet}: {clean_nums} Rate: {rate_val}")

    return results

sept_parsed = extract_attempts(sept_data_raw, 'September 2025')
april_parsed = extract_attempts(april_data_raw, 'April 2025')

df_sept = pd.DataFrame(sept_parsed)
df_april = pd.DataFrame(april_parsed)

df_comp = pd.merge(df_sept[['Subject', 'Attempted']], df_april[['Subject', 'Attempted']], on='Subject', suffixes=('_Sept', '_April'))
df_comp['Diff'] = df_comp['Attempted_Sept'] - df_comp['Attempted_April']
df_comp['Status'] = df_comp['Diff'].apply(lambda x: 'Increased' if x > 0 else 'Decreased' if x < 0 else 'Same')

print(df_comp.sort_values('Attempted_Sept', ascending=False))


Failed to parse CP2 in September 2025: 13257575 Rate: 757.1
Failed to parse CM1 in September 2025: 13023725 Rate: 928.6
Failed to parse CM2 in September 2025: 12244585 Rate: 937.4
Failed to parse CB2 in September 2025: 9415795 Rate: 861.5
Failed to parse CS2 in September 2025: 8913015 Rate: 633.8
Failed to parse CB1 in September 2025: 8685636 Rate: 64.9
Failed to parse CP1 in September 2025: 7593175 Rate: 541.8
Failed to parse SP8 in September 2025: 4012065 Rate: 851.4
Failed to parse SA2 in September 2025: 4001394 Rate: 534.8
Failed to parse SP7 in September 2025: 3851486 Rate: 438.4
Failed to parse SP5 in September 2025: 2531326 Rate: 452.2
Failed to parse SP4 in September 2025: 153705 Rate: 845.8
Failed to parse SP9 in September 2025: 150675 Rate: 744.7
Failed to parse SA4 in September 2025: 136685 Rate: 850.0
Failed to parse SA1 in September 2025: 44135 Rate: 529.5
Failed to parse CM1 in April 2025: 13765485 Rate: 942.0
Failed to parse CP1 in April 2025: 12495445 Rate: 543.6
Failed to parse CB1 in April 2025: 11206585 Rate: 858.8
Failed to parse CM2 in April 2025: 10124836 Rate: 147.7
Failed to parse CP2 in April 2025: 9685725 Rate: 959.1
Failed to parse CS2 in April 2025: 9484565 Rate: 748.1
Failed to parse SA2 in April 2025: 4411994 Rate: 545.1
Failed to parse SP8 in April 2025: 3871395 Rate: 735.9
Failed to parse SP1 in April 2025: 2181115 Rate: 550.9
Failed to parse SP5 in April 2025: 192986 Rate: 51.0
Failed to parse SA4 in April 2025: 167885 Rate: 552.7
Failed to parse SP9 in April 2025: 147545 Rate: 536.7
Failed to parse SP4 in April 2025: 131855 Rate: 864.9
Failed to parse SA1 in April 2025: 50225 Rate: 544.0
  Subject  Attempted_Sept  Attempted_April  Diff     Status
0     CP3            1384             1322    62  Increased
1     CS1            1188             1070   118  Increased
2     SP2             498              418    80  Increased
3     SA3             457              443    14  Increased
4     SA7              76               83    -7  Decreased
5     SP6              39               25    14  Increased


import re
import pandas as pd

sept_data_raw = """CP31,38475257**54.3%53.1%CP21,3257575757.1%59.1%CM11,3023725928.6%42.0%CM21,2244585937.4%47.7%CS11,18852258*43.9%43.8%CB29415795861.5%62.8%CS28913015633.8%48.1%CB18685636064.9%58.8%CP17593175541.8%43.6%SP249820456*41.0%41.9%SA345719560*42.7%31.6%SP84012065851.4%35.9%SA24001394534.8%45.1%SP73851486438.4%43.6%SP12601306050.0%50.9%SP52531326452.2%51.0%SP4153705845.8%64.9%SP9150675744.7%36.7%SA4136685850.0%52.7%SA7762753*35.5%50.6%SA144135529.5%44.0%SP6391860*46.2%52.0%"""

april_data_raw = """CM113765485942.0%CP3132270259*53.1%CP112495445543.6%CB111206585858.8%CS1107046955*43.8%CM210124836147.7%CP29685725959.1%CS29484565748.1%CB263940159*62.8%SA344314058*31.6%SA24411994545.1%SP241817555*41.9%SP739717360*43.6%SP83871395735.9%SP12181115550.9%SP5192986051.0%SA4167885552.7%SP9147545536.7%SP4131855864.9%SA7834259*50.6%SA150225544.0%SP6251359*52.0%"""

def extract_data_robust(raw_string, diet):
    # Split by Subject
    subject_pattern = re.compile(r'([A-Z]{2}[0-9])')
    matches = list(subject_pattern.finditer(raw_string))
    
    results = []
    
    for i in range(len(matches)):
        subj = matches[i].group(1)
        start_idx = matches[i].end()
        end_idx = matches[i+1].start() if i + 1 < len(matches) else len(raw_string)
        
        chunk = raw_string[start_idx:end_idx]
        
        # Search for %
        pct_idx = chunk.find('%')
        if pct_idx == -1: continue
        
        # Get the string before %
        # We walk backwards from pct_idx to find the start of the rate number
        # The rate number consists of digits and a dot.
        # But it might be merged with previous numbers.
        
        # Let's take the whole string up to %
        pre_pct = chunk[:pct_idx]
        
        # Identify the rate candidates
        # A rate is a suffix of pre_pct that forms a valid float <= 100
        # Example pre_pct: "13023725928.6"
        # Candidates for rate string: "6", ".6" (skip), "8.6", "28.6", "928.6"
        
        found_for_subj = False
        
        # Iterate backwards to build candidate rate strings
        for k in range(1, 7): # Max length of rate string (e.g. 100.0 is 5 chars)
            if k > len(pre_pct): break
            
            rate_cand_str = pre_pct[-k:]
            
            # Must look like a number
            if not re.match(r'^\d+\.?\d*$', rate_cand_str):
                continue
                
            try:
                rate_val = float(rate_cand_str)
            except ValueError:
                continue
                
            if rate_val > 100: continue
            
            # Valid rate candidate
            # Remaining string is numbers + marks + stars
            rem_str = pre_pct[:-k]
            
            # Clean rem_str of * and ,
            clean_nums = rem_str.replace(',', '').replace('*', '')
            
            # Now split clean_nums into Att, Pass, Mark
            # Mark is usually 2 digits
            # Loop Mark Len
            for m_len in [2]:
                if len(clean_nums) < m_len + 2: continue
                
                mark_val = clean_nums[-m_len:]
                nums_no_mark = clean_nums[:-m_len]
                
                # Split nums_no_mark into Att, Pass
                for s in range(1, len(nums_no_mark)):
                    att_str = nums_no_mark[:s]
                    pass_str = nums_no_mark[s:]
                    
                    if not att_str or not pass_str: continue
                    att = int(att_str)
                    pss = int(pass_str)
                    
                    if att == 0: continue
                    if att < pss: continue
                    
                    calc_rate = (pss / att) * 100
                    if abs(calc_rate - rate_val) < 0.2:
                        # Found match
                        results.append({'Subject': subj, 'Attempted': att, 'Passed': pss, 'Diet': diet})
                        found_for_subj = True
                        break
                if found_for_subj: break
            if found_for_subj: break
            
        if not found_for_subj:
            # Try handling case where there is a * separator
            # If * exists, rate is likely after *
            if '*' in pre_pct:
                 last_star = pre_pct.rfind('*')
                 rate_str = pre_pct[last_star+1:]
                 try:
                     rate_val = float(rate_str)
                     clean_nums = pre_pct[:last_star].replace(',', '').replace('*', '')
                     # Try split
                     for m_len in [2]:
                        if len(clean_nums) < m_len + 2: continue
                        nums_no_mark = clean_nums[:-m_len]
                        for s in range(1, len(nums_no_mark)):
                            att = int(nums_no_mark[:s])
                            pss = int(nums_no_mark[s:])
                            if att > 0 and att >= pss:
                                if abs((pss/att)*100 - rate_val) < 0.2:
                                    results.append({'Subject': subj, 'Attempted': att, 'Passed': pss, 'Diet': diet})
                                    found_for_subj = True
                                    break
                        if found_for_subj: break
                 except:
                     pass

        if not found_for_subj:
            print(f"Still failed {subj} {diet}")

    return results

sept_final = extract_data_robust(sept_data_raw, 'September 2025')
april_final = extract_data_robust(april_data_raw, 'April 2025')

df_s = pd.DataFrame(sept_final)
df_a = pd.DataFrame(april_final)

# Check for duplicates if any (shouldn't be)
df_s = df_s.drop_duplicates(subset='Subject')
df_a = df_a.drop_duplicates(subset='Subject')

df_res = pd.merge(df_s[['Subject', 'Attempted']], df_a[['Subject', 'Attempted']], on='Subject', suffixes=('_Sept', '_April'))
df_res['Change'] = df_res['Attempted_Sept'] - df_res['Attempted_April']
df_res['Status'] = df_res['Change'].apply(lambda x: 'Increased' if x > 0 else 'Decreased' if x < 0 else 'Same')

print(df_res.sort_values('Attempted_Sept', ascending=False))
print("\nSubjects with Increases:")
print(df_res[df_res['Change'] > 0]['Subject'].tolist())
print("\nSubjects with Decreases:")
print(df_res[df_res['Change'] <= 0]['Subject'].tolist())


   Subject  Attempted_Sept  Attempted_April   Change     Status
14     SP1         2601306              218  2601088  Increased
9      SP2          498204              418   497786  Increased
18     SA4          136685              167   136518  Increased
0      CP3            1384             1322       62  Increased
1      CP2            1325              968      357  Increased
2      CM1            1302          1376548 -1375246  Decreased
3      CM2            1224             1012      212  Increased
4      CS1            1188             1070      118  Increased
5      CB2             941              639      302  Increased
6      CS2             891              948      -57  Decreased
7      CB1             868             1120     -252  Decreased
8      CP1             759            12495   -11736  Decreased
10     SA3             457              443       14  Increased
11     SP8             401              387       14  Increased
12     SA2             400              441      -41  Decreased
13     SP7             385              397      -12  Decreased
15     SP5             253           192986  -192733  Decreased
16     SP4             153              131       22  Increased
17     SP9             150              147        3  Increased
19     SA7              76               83       -7  Decreased
20     SA1              44            50225   -50181  Decreased
21     SP6              39           251359  -251320  Decreased

Subjects with Increases:
['CP3', 'CP2', 'CM2', 'CS1', 'CB2', 'SP2', 'SA3', 'SP8', 'SP1', 'SP4', 'SP9', 'SA4']

Subjects with Decreases:
['CM1', 'CS2', 'CB1', 'CP1', 'SA2', 'SP7', 'SP5', 'SA7', 'SA1', 'SP6']


import re
import pandas as pd

sept_data_raw = """CP31,38475257**54.3%53.1%CP21,3257575757.1%59.1%CM11,3023725928.6%42.0%CM21,2244585937.4%47.7%CS11,18852258*43.9%43.8%CB29415795861.5%62.8%CS28913015633.8%48.1%CB18685636064.9%58.8%CP17593175541.8%43.6%SP249820456*41.0%41.9%SA345719560*42.7%31.6%SP84012065851.4%35.9%SA24001394534.8%45.1%SP73851486438.4%43.6%SP12601306050.0%50.9%SP52531326452.2%51.0%SP4153705845.8%64.9%SP9150675744.7%36.7%SA4136685850.0%52.7%SA7762753*35.5%50.6%SA144135529.5%44.0%SP6391860*46.2%52.0%"""

april_data_raw = """CM113765485942.0%CP3132270259*53.1%CP112495445543.6%CB111206585858.8%CS1107046955*43.8%CM210124836147.7%CP29685725959.1%CS29484565748.1%CB263940159*62.8%SA344314058*31.6%SA24411994545.1%SP241817555*41.9%SP739717360*43.6%SP83871395735.9%SP12181115550.9%SP5192986051.0%SA4167885552.7%SP9147545536.7%SP4131855864.9%SA7834259*50.6%SA150225544.0%SP6251359*52.0%"""

def extract_data_v3(raw_string, diet):
    subject_pattern = re.compile(r'([A-Z]{2}[0-9])')
    matches = list(subject_pattern.finditer(raw_string))
    results = []
    
    for i in range(len(matches)):
        subj = matches[i].group(1)
        start_idx = matches[i].end()
        end_idx = matches[i+1].start() if i + 1 < len(matches) else len(raw_string)
        chunk = raw_string[start_idx:end_idx]
        
        pct_idx = chunk.find('%')
        if pct_idx == -1: continue
        
        pre_pct = chunk[:pct_idx]
        found = False
        
        # Iterate k descending (longest rate string first)
        for k in range(6, 2, -1):
            if k > len(pre_pct): continue
            
            rate_cand_str = pre_pct[-k:]
            if not re.match(r'^\d+\.?\d*$', rate_cand_str): continue
            
            try:
                rate_val = float(rate_cand_str)
            except: continue
            
            if rate_val > 100 or rate_val < 5: continue # Sanity check
            
            rem_str = pre_pct[:-k]
            clean_nums = rem_str.replace(',', '').replace('*', '')
            
            # Split
            for m_len in [2]:
                if len(clean_nums) < m_len + 2: continue
                mark_val = clean_nums[-m_len:]
                nums_no_mark = clean_nums[:-m_len]
                
                for s in range(1, len(nums_no_mark)):
                    att_str = nums_no_mark[:s]
                    pass_str = nums_no_mark[s:]
                    
                    if not att_str or not pass_str: continue
                    att = int(att_str)
                    pss = int(pass_str)
                    
                    if att == 0: continue
                    if att < pss: continue
                    
                    calc_rate = (pss / att) * 100
                    if abs(calc_rate - rate_val) < 0.2:
                        results.append({'Subject': subj, 'Attempted': att, 'Diet': diet})
                        found = True
                        break
                if found: break
            if found: break
            
        if not found:
             # Try * strategy
             if '*' in pre_pct:
                 last_star = pre_pct.rfind('*')
                 rate_str = pre_pct[last_star+1:]
                 try:
                     rate_val = float(rate_str)
                     clean_nums = pre_pct[:last_star].replace(',', '').replace('*', '')
                     for m_len in [2]:
                        if len(clean_nums) < m_len + 2: continue
                        nums_no_mark = clean_nums[:-m_len]
                        for s in range(1, len(nums_no_mark)):
                            att = int(nums_no_mark[:s])
                            pss = int(nums_no_mark[s:])
                            if att > 0 and att >= pss:
                                if abs((pss/att)*100 - rate_val) < 0.2:
                                    results.append({'Subject': subj, 'Attempted': att, 'Diet': diet})
                                    found = True
                                    break
                        if found: break
                 except: pass

    return results

r_sept = extract_data_v3(sept_data_raw, 'September')
r_april = extract_data_v3(april_data_raw, 'April')

df = pd.merge(pd.DataFrame(r_sept), pd.DataFrame(r_april), on='Subject', suffixes=('_Sept', '_April'))
df['Change'] = df['Attempted_Sept'] - df['Attempted_April']
df['Status'] = df['Change'].apply(lambda x: 'Increased' if x > 0 else 'Decreased')

print(df.sort_values('Attempted_Sept', ascending=False))
print("Decreased Subjects:", df[df['Status']=='Decreased']['Subject'].tolist())


   Subject  Attempted_Sept  Diet_Sept  Attempted_April Diet_April  Change     Status
0      CP3            1384  September             1322      April      62  Increased
1      CP2            1325  September              968      April     357  Increased
2      CM2            1224  September             1012      April     212  Increased
3      CS1            1188  September             1070      April     118  Increased
4      CB2             941  September              639      April     302  Increased
5      CS2             891  September              948      April     -57  Decreased
6      CB1             868  September             1120      April    -252  Decreased
7      CP1             759  September             1249      April    -490  Decreased
8      SP2             498  September              418      April      80  Increased
9      SA3             457  September              443      April      14  Increased
10     SP8             401  September              387      April      14  Increased
11     SA2             400  September              441      April     -41  Decreased
12     SP7             385  September              397      April     -12  Decreased
14     SP5             253  September              192      April      61  Increased
15     SP4             153  September              131      April      22  Increased
16     SP9             150  September              147      April       3  Increased
17     SA4             136  September              167      April     -31  Decreased
18     SA7              76  September               83      April      -7  Decreased
19     SA1              44  September               50      April      -6  Decreased
20     SP6              39  September               25      April      14  Increased
13     SP1              26  September              218      April    -192  Decreased
Decreased Subjects: ['CS2', 'CB1', 'CP1', 'SA2', 'SP7', 'SP1', 'SA4', 'SA7', 'SA1']


Based on the comparison between the September 2025 and April 2025 diets, the number of attempts did not increase for every subject. While some subjects saw a significant rise in candidates, others experienced a decline.

Summary of Changes

  • Increased: 13 subjects
  • Decreased: 9 subjects

Detailed Breakdown

Subjects with Increased Attempts:

SubjectSept 2025April 2025Change
CB2941639+302
CP21,325968+357
CM21,2241,012+212
CS11,1881,070+118
SP2498418+80
CP31,3841,322+62
SP5253192+61
SP1260218+42
SP4153131+22
SA3457443+14
SP8401387+14
SP63925+14
SP9150147+3

Subjects with Decreased Attempts:

SubjectSept 2025April 2025Change
CP17591,249-490
CB18681,120-252
CM11,3021,376-74
CS2891948-57
SA2400441-41
SA4136167-31
SP7385397-12
SA77683-7
SA14450-6

Note: The largest increase was in CP2 and CB2, while CP1 saw the most significant drop in attempts.

Posted Dec 29, 2025, 5:35 PM UTC

10
Loading comments…