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:
| Subject | Sept 2025 | April 2025 | Change |
|---|---|---|---|
| CB2 | 941 | 639 | +302 |
| CP2 | 1,325 | 968 | +357 |
| CM2 | 1,224 | 1,012 | +212 |
| CS1 | 1,188 | 1,070 | +118 |
| SP2 | 498 | 418 | +80 |
| CP3 | 1,384 | 1,322 | +62 |
| SP5 | 253 | 192 | +61 |
| SP1 | 260 | 218 | +42 |
| SP4 | 153 | 131 | +22 |
| SA3 | 457 | 443 | +14 |
| SP8 | 401 | 387 | +14 |
| SP6 | 39 | 25 | +14 |
| SP9 | 150 | 147 | +3 |
Subjects with Decreased Attempts:
| Subject | Sept 2025 | April 2025 | Change |
|---|---|---|---|
| CP1 | 759 | 1,249 | -490 |
| CB1 | 868 | 1,120 | -252 |
| CM1 | 1,302 | 1,376 | -74 |
| CS2 | 891 | 948 | -57 |
| SA2 | 400 | 441 | -41 |
| SA4 | 136 | 167 | -31 |
| SP7 | 385 | 397 | -12 |
| SA7 | 76 | 83 | -7 |
| SA1 | 44 | 50 | -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