Skip to content

Instantly share code, notes, and snippets.

@sovietscout
Last active April 27, 2025 11:04
Show Gist options
  • Select an option

  • Save sovietscout/1cb94bbdac038eaf2be3066786ccfb66 to your computer and use it in GitHub Desktop.

Select an option

Save sovietscout/1cb94bbdac038eaf2be3066786ccfb66 to your computer and use it in GitHub Desktop.

Revisions

  1. sovietscout revised this gist Apr 27, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    **CUET Score Checker**
    ## CUET Score Checker

    **Requirements**:
    - Python 3.8+
  2. sovietscout created this gist Apr 27, 2025.
    19 changes: 19 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    **CUET Score Checker**

    **Requirements**:
    - Python 3.8+
    - `pandas`, `lxml`, `requests` (install via `pip install -r pandas lxml requests`)
    - `master.csv` (provisional answer key for the subject; must be in the same folder)

    **Usage**:
    1. Place your response sheet URL in `response_url` variable.
    2. Run `python cuet-score-checker.py`.
    3. Get a summary and a scorecard (`[student-name]-scorecard.csv`) with detailed results.

    **Output**:
    - Total score, correct/incorrect/unattempted counts
    - Question-wise analysis (QID, chosen option, correct answer, marks)

    **Note**:
    - Replace `master.csv` with updated keys if needed.
    - Response URLs follow the format: `https://cdn3.digialm.com/.../...07E1.html`
    63 changes: 63 additions & 0 deletions cuet-score-checker.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,63 @@
    import requests
    import pandas as pd
    import numpy as np
    from lxml import etree
    from typing import Union


    def get_responses(url: str) -> Union[str, pd.DataFrame]:
    r = requests.get(url)
    r.raise_for_status()
    tree = etree.HTML(r.content)

    name_raw: str = tree.xpath('//div[@class="main-info-pnl"]//td[.="Candidate Name"]/following-sibling::td/text()')[0]
    name = name_raw.split()[0].strip().lower()

    questions = []
    for panel in tree.xpath('//div[@class="question-pnl"]'):
    qno_raw = panel.xpath('.//td[@class="bold" and starts-with(text(), "Q.")]/text()')[0].split()[-1]
    qno = int(qno_raw.split(".")[1].strip())
    menu_tbl = panel.xpath('.//table[@class="menu-tbl"]')[0]

    qid = int(menu_tbl.xpath('.//td[.="Question ID :"]/following-sibling::td/text()')[0])
    ono_raw = menu_tbl.xpath('.//td[.="Chosen Option :"]/following-sibling::td/text()')[0].strip()

    if ono_raw == "--":
    ono = None
    oid = None
    else:
    ono = float(ono_raw)
    oid = float(menu_tbl.xpath(f'.//td[.="Option {ono_raw} ID :"]/following-sibling::td/text()')[0].strip())

    questions.append({
    "QNO": qno,
    "QID": qid,
    "ONO": ono,
    "OID": oid,
    })

    return name, pd.DataFrame(questions)


    def check_result(your_df: pd.DataFrame, master_df: pd.DataFrame) -> pd.DataFrame:
    df = pd.merge(your_df, master_df, how='left', on='QID')

    df['TOID'] = df['TOID'].astype(float)
    df['Score'] = np.where(df['OID'].isna(), 0, np.where(df['OID'] == df['TOID'], 4, -1))

    print(f'Score: {df["Score"].sum()}')
    print(f'Correct: {len(df[df['Score'] == 4]['QNO'].tolist())}, Incorrect: {len(df[df['Score'] == -1]['QNO'].tolist())}, Unattempted: {len(df[df['Score'] == 0]['QNO'].tolist())}')

    print(df[df['Score'] == -1]['QNO'].tolist())

    return df


    if __name__ == '__main__':
    response_url = '<your-response-sheet-url>'

    master = pd.read_csv('master.csv')
    stdn_name, stdn = get_responses(response_url)

    stdn_df = check_result(stdn, master)
    stdn_df.to_csv(f'{stdn_name}-scorecard.csv', index=False)
    76 changes: 76 additions & 0 deletions master.csv
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,76 @@
    QID,TOID
    7396761131,7396764504
    7396761132,7396764508
    7396761133,7396764512
    7396761134,7396764516
    7396761135,7396764519
    7396761136,7396764524
    7396761137,7396764525
    7396761138,7396764529
    7396761139,7396764536
    7396761140,7396764540
    7396761141,7396764543
    7396761142,7396764546
    7396761143,7396764551
    7396761144,7396764554
    7396761145,7396764557
    7396761146,7396764562
    7396761147,7396764565
    7396761148,7396764569
    7396761149,7396764576
    7396761150,7396764578
    7396761151,7396764583
    7396761152,7396764587
    7396761153,7396764592
    7396761154,7396764596
    7396761155,7396764597
    7396761156,7396764602
    7396761157,7396764607
    7396761158,7396764612
    7396761159,7396764615
    7396761160,7396764620
    7396761161,7396764624
    7396761162,7396764628
    7396761163,7396764630
    7396761164,7396764636
    7396761165,7396764639
    7396761166,7396764642
    7396761167,7396764648
    7396761168,7396764649
    7396761169,7396764656
    7396761170,7396764659
    7396761171,7396764664
    7396761172,7396764667
    7396761173,7396764670
    7396761174,7396764676
    7396761175,7396764677
    7396761176,7396764682
    7396761177,7396764687
    7396761178,7396764692
    7396761179,7396764696
    7396761180,7396764699
    7396761181,7396764702
    7396761182,7396764706
    7396761183,7396764709
    7396761184,7396764716
    7396761185,7396764718
    7396761186,7396764724
    7396761187,7396764727
    7396761188,7396764730
    7396761189,7396764734
    7396761190,7396764740
    7396761191,7396764741
    7396761192,7396764748
    7396761193,7396764750
    7396761194,7396764755
    7396761195,7396764759
    7396761196,7396764764
    7396761197,7396764766
    7396761198,7396764772
    7396761199,7396764774
    7396761200,7396764779
    7396761201,7396764784
    7396761202,7396764785
    7396761203,7396764790
    7396761204,7396764794
    7396761205,7396764799