QuantumChecker 0.3.3__tar.gz → 0.3.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QuantumChecker
3
- Version: 0.3.3
3
+ Version: 0.3.6
4
4
  Summary: A package to evaluate homework submissions in Python, SQL, PowerBI, and SSIS.
5
5
  Author: Qobiljon
6
6
  Author-email: qobiljonkhayrullayev@gmail.com
@@ -1,3 +1,5 @@
1
+ import base64
2
+ import io
1
3
  import json
2
4
  import logging
3
5
  import os
@@ -6,33 +8,33 @@ import shutil
6
8
  import zipfile
7
9
  from pathlib import Path
8
10
  from typing import Dict, List
9
- from pdf2image import convert_from_path
10
- from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
11
+
11
12
  import requests
12
13
  from dotenv import load_dotenv
14
+ from pdf2image import convert_from_path
13
15
  from PIL import Image
14
- import io
15
- import base64
16
+ from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
17
+
16
18
 
17
19
 
18
20
  def prompt_text_powerbi(combined_content: str) -> str:
19
21
  return f"""
20
22
  You are an expert Power BI instructor evaluating beginner-level DAX question-answer pairs.
21
-
23
+
22
24
  Each answer contains a data model (in JSON format) extracted from a submitted .pbit file. Evaluate the technical correctness, relevance, and clarity of the DAX elements provided.
23
-
25
+
24
26
  Use the following criteria to give a fair and supportive evaluation:
25
27
  - Measures (40 points): Are calculated measures meaningful, syntactically valid, and aligned with the question?
26
28
  - Relationships (20 points): Are key relationships between tables defined logically?
27
29
  - Tables & Columns (20 points): Are relevant tables/columns present? Are naming conventions clear?
28
30
  - Expressions (10 points): Are query partitions or expressions present and understandable?
29
31
  - Overall structure (10 points): Does the model appear coherent and purposeful?
30
-
32
+
31
33
  **Scoring Tolerance**:
32
34
  - Be kind to beginners. Do not give extremely low scores unless the model is completely missing or incorrect.
33
35
  - If measures exist and make some sense, award partial credit (e.g., 20–30 out of 40).
34
36
  - A score below 30/100 should only be given if there’s little to no relevant content.
35
-
37
+
36
38
  Structure your response exactly like this:
37
39
  OVERALL SCORE: [SCORE]/100
38
40
  [Brief feedback here — 3–5 sentences focused on strengths + areas to improve.]
@@ -43,13 +45,17 @@ def prompt_text_powerbi(combined_content: str) -> str:
43
45
 
44
46
  load_dotenv()
45
47
  logger = logging.getLogger(__name__)
48
+
46
49
  logging.basicConfig(
47
50
  level=logging.INFO,
48
51
  format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
49
- handlers=[logging.FileHandler("../powerbi_evaluator.log"), logging.StreamHandler()]
52
+ handlers=[logging.StreamHandler()]
50
53
  )
51
54
 
52
55
 
56
+ # ==============================
57
+ # Gemini Flash Model
58
+ # ==============================
53
59
  class GeminiFlashModel:
54
60
  def __init__(self, api_key: str, model_name: str = "gemini-1.5-flash"):
55
61
  api_key = os.getenv("GEMINI_API_KEY") or api_key
@@ -163,6 +169,7 @@ class GeminiFlashModel:
163
169
  return result
164
170
 
165
171
 
172
+
166
173
  class PowerBIProcessor:
167
174
  def extract_datamodel(self, pbit_file_path: str) -> Dict:
168
175
  if not os.path.exists(pbit_file_path):
@@ -173,7 +180,7 @@ class PowerBIProcessor:
173
180
  export_path = os.path.join(folder_path, "export")
174
181
  self._cleanup(zip_file, export_path)
175
182
  try:
176
- os.rename(pbit_file_path, zip_file)
183
+ shutil.copy(pbit_file_path, zip_file)
177
184
  if not zipfile.is_zipfile(zip_file):
178
185
  raise ProcessingError(f"File is not a valid ZIP: {zip_file}")
179
186
  with zipfile.ZipFile(zip_file, "r") as zip_ref:
@@ -214,7 +221,6 @@ class PowerBIProcessor:
214
221
  image_path = os.path.join(output_dir, f"page_{i + 1}.png")
215
222
  page.save(image_path, "PNG")
216
223
  image_paths.append(image_path)
217
- os.remove(pdf_path)
218
224
  return image_paths
219
225
  except Exception as e:
220
226
  raise ProcessingError(f"Failed to process PDF: {e}")
@@ -248,7 +254,9 @@ class PowerBIProcessor:
248
254
  measures.append({
249
255
  "Table": table["name"],
250
256
  "Name": measure["name"],
251
- "Expression": " ".join(measure.get("expression", "")) if isinstance(measure.get("expression"), list) else measure.get("expression", ""),
257
+ "Expression": " ".join(measure.get("expression", "")) if isinstance(measure.get("expression"),
258
+ list) else measure.get(
259
+ "expression", ""),
252
260
  "FormatString": measure.get("formatString", "")
253
261
  })
254
262
  return measures
@@ -266,7 +274,8 @@ class PowerBIProcessor:
266
274
  }
267
275
  for col in table.get("columns", [])
268
276
  ]
269
- expressions = [part["source"]["expression"] for part in table.get("partitions", []) if part["source"].get("expression")]
277
+ expressions = [part["source"]["expression"] for part in table.get("partitions", []) if
278
+ part["source"].get("expression")]
270
279
  table_info.append({"Table Name": table["name"], "Columns": columns, "Expressions": expressions})
271
280
  return table_info
272
281
 
@@ -293,6 +302,7 @@ class PowerBIProcessor:
293
302
  shutil.rmtree(path, ignore_errors=True)
294
303
 
295
304
 
305
+
296
306
  class PowerBIEvaluator:
297
307
  def __init__(self, api_key: str):
298
308
  self.api_key = api_key
@@ -300,67 +310,81 @@ class PowerBIEvaluator:
300
310
  self.processor = PowerBIProcessor()
301
311
 
302
312
  def evaluate(self, questions: List[str], answer_path: str, temp_dir: str = "temp_extract") -> Dict[str, any]:
313
+ extract_path = temp_dir
314
+ outputimages = os.path.join(temp_dir, "outputimages")
303
315
  try:
304
316
  _, ext = os.path.splitext(answer_path)
305
317
  ext = ext.lower()
306
318
  extract_path = temp_dir
307
319
  pbit_path = None
308
320
  pdf_path = None
321
+
309
322
  if ext == ".zip":
310
323
  pbit_path, pdf_path = self.processor.extract_zip(answer_path, extract_path)
311
324
  elif ext == ".pbit":
312
325
  pbit_path = answer_path
313
- pdf_path = None
326
+ elif ext == ".pdf":
327
+ pdf_path = answer_path
314
328
  else:
315
- logger.error("Invalid file type for Power BI: %s", answer_path)
316
329
  return {
317
330
  "score": 0,
318
- "feedback": f"Invalid file type: {ext}. Expected .pbit or .zip",
331
+ "feedback": f"Invalid file type: {ext}. Expected .pbit, .pdf, or .zip",
319
332
  "issues": ["Invalid file type"],
320
333
  "recommendations": [],
321
334
  "dax_score": 0,
322
335
  "visual_score": 0
323
336
  }
324
- try:
337
+
338
+ dax_result = None
339
+ visual_result = None
340
+
341
+ if pbit_path:
325
342
  data_model = self.processor.extract_datamodel(pbit_path)
326
343
  model_data = self.processor.extract_model_data(data_model)
327
344
  answers = [json.dumps(model_data)] * len(questions)
328
345
  dax_result = self.model.evaluate([{"question": q, "answer": a} for q, a in zip(questions, answers)])
329
- result = {
330
- "score": 0,
331
- "feedback": f"DAX Feedback:\n{dax_result['feedback']}",
332
- "issues": dax_result["issues"],
333
- "recommendations": dax_result["recommendations"],
334
- "dax_score": dax_result["score"],
335
- "visual_score": 0
336
- }
337
- if pdf_path:
338
- try:
339
- image_paths = self.processor.process_pdf(pdf_path, output_dir=os.path.join(temp_dir, "outputimages"))
340
- visual_result = self.model.evaluate_visuals(questions[0], os.path.join(temp_dir, "outputimages"))
341
- result["score"] = int(0.7 * dax_result["score"] + 0.3 * visual_result["score"])
342
- result["visual_score"] = visual_result["score"]
343
- result["feedback"] += f"\n\nVisual Feedback:\n{visual_result['feedback']}"
344
- result["issues"].extend([f"Visual: {i}" for i in visual_result.get("issues", [])])
345
- result["recommendations"].extend(visual_result.get("recommendations", []))
346
- except ProcessingError as e:
347
- logger.warning("Failed to process PDF, proceeding with DAX evaluation only: %s", str(e))
348
- result["score"] = dax_result["score"]
349
- result["issues"].append(f"Visual evaluation skipped: {str(e)}")
350
- result["recommendations"].append("Ensure a valid PDF is provided for visual evaluation if intended")
351
- else:
352
- result["score"] = dax_result["score"]
353
- result["feedback"] += "\n\nVisual Feedback:\nNo visuals provided for evaluation."
354
- result["issues"].append("No PDF provided for visual evaluation")
355
- result["recommendations"].append("Include a PDF with report visuals for complete evaluation")
356
- logger.info("[DAX] Score: %d/100", result["dax_score"])
357
- logger.info("[Visual] Score: %d/100", result["visual_score"])
358
- logger.info("[Final] Score (70%% DAX, 30%% Visuals): %d/100", result["score"])
359
- return result
360
- finally:
361
- self.processor._cleanup(extract_path, os.path.join(temp_dir, "outputimages"))
346
+
347
+ if pdf_path:
348
+ image_paths = self.processor.process_pdf(pdf_path, output_dir=os.path.join(temp_dir, "outputimages"))
349
+ visual_result = self.model.evaluate_visuals(questions[0], os.path.join(temp_dir, "outputimages"))
350
+
351
+ result = {
352
+ "score": 0,
353
+ "feedback": "",
354
+ "issues": [],
355
+ "recommendations": [],
356
+ "dax_score": 0,
357
+ "visual_score": 0
358
+ }
359
+
360
+ if dax_result:
361
+ result["dax_score"] = dax_result["score"]
362
+ result["feedback"] += f"DAX Feedback:\n{dax_result['feedback']}"
363
+ result["issues"].extend(dax_result["issues"])
364
+ result["recommendations"].extend(dax_result["recommendations"])
365
+
366
+ if visual_result:
367
+ result["visual_score"] = visual_result["score"]
368
+ result["feedback"] += f"\n\nVisual Feedback:\n{visual_result['feedback']}"
369
+ result["issues"].extend([f"Visual: {i}" for i in visual_result.get("issues", [])])
370
+ result["recommendations"].extend(visual_result.get("recommendations", []))
371
+
372
+ if dax_result and visual_result:
373
+ result["score"] = int(0.7 * dax_result["score"] + 0.3 * visual_result["score"])
374
+ elif dax_result:
375
+ result["score"] = dax_result["score"]
376
+ result["feedback"] += "\n\nVisual Feedback:\nNo visuals provided for evaluation."
377
+ result["issues"].append("No PDF provided for visual evaluation")
378
+ result["recommendations"].append("Include a PDF with report visuals for complete evaluation")
379
+ elif visual_result:
380
+ result["score"] = visual_result["score"]
381
+ result["feedback"] = "No DAX provided for evaluation.\n\n" + result["feedback"]
382
+ result["issues"].append("No PBIT provided for DAX evaluation")
383
+ result["recommendations"].append("Include a PBIT file with data model for complete evaluation")
384
+
385
+ return result
386
+
362
387
  except Exception as e:
363
- logger.exception("Failed to evaluate Power BI file %s: %s", answer_path, str(e))
364
388
  self.processor._cleanup(extract_path, os.path.join(temp_dir, "outputimages"))
365
389
  return {
366
390
  "score": 0,
@@ -370,6 +394,9 @@ class PowerBIEvaluator:
370
394
  "dax_score": 0,
371
395
  "visual_score": 0
372
396
  }
397
+ finally:
398
+ self.processor._cleanup(extract_path, outputimages)
399
+
373
400
 
374
401
 
375
402
  class ProcessingError(Exception):
@@ -7,10 +7,9 @@ from typing import List, Dict
7
7
  import requests
8
8
  from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
9
9
  from pprint import pprint
10
- import json
11
10
  import re
12
11
 
13
- from .prompts import prompt_text_ssis # Assumes this is defined in .prompts
12
+ from .prompts import prompt_text_ssis
14
13
 
15
14
  logger = logging.getLogger(__name__)
16
15
 
@@ -90,7 +89,7 @@ class SSISAnswerParser:
90
89
  "issues": []
91
90
  }
92
91
 
93
- # Parse package info
92
+
94
93
  package_info = {
95
94
  "Package Name": root.attrib.get("{www.microsoft.com/SqlServer/Dts}ObjectName", "Unknown"),
96
95
  "Creation Date": root.attrib.get("{www.microsoft.com/SqlServer/Dts}CreationDate", "Unknown"),
@@ -101,7 +100,7 @@ class SSISAnswerParser:
101
100
  structured_data["package_info"] = package_info
102
101
  summary.append(f"Package: {package_info['Package Name']} (Created: {package_info['Creation Date']})")
103
102
 
104
- # Parse package description from comments
103
+
105
104
  package_description = "No description found"
106
105
  comment_pattern = re.compile(r'<!--\s*Package Description:\s*(.*?)\s*-->', re.DOTALL)
107
106
  match = comment_pattern.search(content)
@@ -109,7 +108,7 @@ class SSISAnswerParser:
109
108
  package_description = match.group(1).strip()
110
109
  summary.append(f"Description: {package_description}")
111
110
 
112
- # Parse connection managers
111
+
113
112
  for conn in root.findall(".//DTS:ConnectionManager", namespace):
114
113
  try:
115
114
  conn_name = conn.attrib.get("{www.microsoft.com/SqlServer/Dts}ObjectName", "Unnamed Connection")
@@ -133,14 +132,14 @@ class SSISAnswerParser:
133
132
  summary.append(
134
133
  f"Connection: {conn_name} ({conn_type}, ConnectionString: {conn_details['ConnectionString']})")
135
134
  structured_data["connections"].append(conn_details)
136
- # Validate connection
135
+
137
136
  if conn_type == "OLEDB" and "Initial Catalog" not in conn_details["ConnectionString"]:
138
137
  structured_data["issues"].append(f"OLEDB Connection {conn_name} missing database specification")
139
138
  except AttributeError as e:
140
139
  structured_data["issues"].append(f"Error parsing connection {conn_name}: {str(e)}")
141
140
  logger.error("Error parsing connection %s: %s", conn_name, str(e))
142
141
 
143
- # Parse variables
142
+
144
143
  for var in root.findall(".//DTS:Variable", namespace):
145
144
  try:
146
145
  var_name = var.attrib.get("{www.microsoft.com/SqlServer/Dts}ObjectName", "Unnamed Variable")
@@ -155,7 +154,7 @@ class SSISAnswerParser:
155
154
  structured_data["issues"].append(f"Error parsing variable {var_name}: {str(e)}")
156
155
  logger.error("Error parsing variable %s: %s", var_name, str(e))
157
156
 
158
- # Parse data flow components
157
+
159
158
  component_ids = set()
160
159
  for component in root.findall(".//DTS:Executables//component", namespace):
161
160
  try:
@@ -168,7 +167,7 @@ class SSISAnswerParser:
168
167
  structured_data["issues"].append(f"Error parsing component {comp_name}: {str(e)}")
169
168
  logger.error("Error parsing component %s: %s", comp_name, str(e))
170
169
 
171
- # Parse data flow paths
170
+
172
171
  for path in root.findall(".//DTS:Executables//path", namespace):
173
172
  try:
174
173
  start_id = path.attrib.get("startId", "Unknown Start")
@@ -183,14 +182,14 @@ class SSISAnswerParser:
183
182
  structured_data["issues"].append(f"Error parsing path {path_name}: {str(e)}")
184
183
  logger.error("Error parsing path %s: %s", path_name, str(e))
185
184
 
186
- # Additional validations
185
+
187
186
  if not structured_data["connections"]:
188
187
  structured_data["issues"].append("No connections configured in the package")
189
188
  if not structured_data["data_flow_paths"]:
190
189
  structured_data["issues"].append("No data flow paths configured in the package")
191
190
 
192
191
  combined_summary = "\n".join(summary)[:2000] or "No components found in SSIS package"
193
- # Split summary into logical sections for multiple questions
192
+
194
193
  answers = []
195
194
  sections = ["Package", "Description", "Connection", "Variable", "Component", "Data Flow Path"]
196
195
  current_section = []
@@ -207,11 +206,6 @@ class SSISAnswerParser:
207
206
  logger.warning("No valid answers found in single SSIS file")
208
207
  answers = [combined_summary]
209
208
 
210
- # Save structured data for debugging
211
- with open("parsed_ssis_summary.json", "w", encoding="utf-8") as f:
212
- json.dump(structured_data, f, indent=2)
213
- logger.info("Saved structured SSIS summary to 'parsed_ssis_summary.json'")
214
-
215
209
  return {
216
210
  "text_answers": answers,
217
211
  "structured_data": structured_data
@@ -219,9 +213,6 @@ class SSISAnswerParser:
219
213
 
220
214
  except ET.ParseError as e:
221
215
  logger.error("Invalid SSIS package file: %s", str(e))
222
- with open("debug_dtsx_content.txt", "w", encoding="utf-8") as f:
223
- f.write(content)
224
- logger.info("Saved raw .dtsx content to 'debug_dtsx_content.txt' for debugging")
225
216
  return {"text_answers": ["Invalid SSIS package file"], "structured_data": {"issues": [str(e)]}}
226
217
  except Exception as e:
227
218
  logger.error("Unexpected error parsing .dtsx file: %s", str(e))
@@ -337,7 +328,7 @@ class SSISEvaluator:
337
328
  """
338
329
  try:
339
330
  if answer_path.lower().endswith(".zip"):
340
- # Use provided temp_dir or generate a default one
331
+
341
332
  temp_dir = temp_dir or f"temp_ssis_extract_{os.getpid()}"
342
333
  parsed_data = SSISAnswerParser.parse_zip_file(answer_path, temp_dir)
343
334
  elif answer_path.lower().endswith('.dtsx'):
@@ -361,17 +352,17 @@ class SSISEvaluator:
361
352
  logger.info("Processing %d questions and %d answers", len(questions), len(answers))
362
353
  pprint(f"Processing {len(questions)} questions and {len(answers)} answers")
363
354
 
364
- # Map answers to questions
355
+
365
356
  if not answers:
366
357
  logger.warning("No answers parsed from SSIS file")
367
358
  answers = ["No valid SSIS components found"] * len(questions)
368
359
  elif len(answers) < len(questions):
369
- # Cycle through answers to match question count
360
+
370
361
  answers = [answers[i % len(answers)] for i in range(len(questions))]
371
362
  logger.debug("Expanded %d answers to %d for %d questions", len(parsed_data["text_answers"]),
372
363
  len(answers), len(questions))
373
364
  elif len(answers) > len(questions):
374
- # Truncate to match question count
365
+
375
366
  answers = answers[:len(questions)]
376
367
  logger.debug("Truncated %d answers to %d for %d questions", len(parsed_data["text_answers"]),
377
368
  len(answers), len(questions))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QuantumChecker
3
- Version: 0.3.3
3
+ Version: 0.3.6
4
4
  Summary: A package to evaluate homework submissions in Python, SQL, PowerBI, and SSIS.
5
5
  Author: Qobiljon
6
6
  Author-email: qobiljonkhayrullayev@gmail.com
@@ -12,4 +12,5 @@ QuantumChecker.egg-info/SOURCES.txt
12
12
  QuantumChecker.egg-info/dependency_links.txt
13
13
  QuantumChecker.egg-info/requires.txt
14
14
  QuantumChecker.egg-info/top_level.txt
15
- tests/test.py
15
+ tests/test.py
16
+ tests/test2.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="QuantumChecker",
5
- version="0.3.3",
5
+ version="0.3.6",
6
6
  author="Qobiljon",
7
7
  author_email="qobiljonkhayrullayev@gmail.com",
8
8
  description="A package to evaluate homework submissions in Python, SQL, PowerBI, and SSIS.",
@@ -0,0 +1,45 @@
1
+ import asyncio
2
+ import os
3
+ import psutil
4
+ from pprint import pprint
5
+ from QuantumCheck import HomeworkEvaluator
6
+
7
+ API_KEY = "AIzaSyDw76DEINpfBVgwIEZLShhy97tvWg7BmzY"
8
+
9
+ question_sets = {
10
+ "python": "Write a Python function to calculate factorial.\nWrite a Python script to reverse a string.",
11
+ "powerbi": "Create a Power BI report with a bar chart.\nExplain DAX measures for sales analysis.",
12
+ "sql": "Write a SQL query to join two tables.\nWrite a SQL query for aggregate functions.",
13
+ "ssis": "Design an SSIS package for data import.\nExplain SSIS control flow tasks."
14
+ }
15
+
16
+ answer_paths = {
17
+ "python": ["../tests/answer/python1.zip"],
18
+ "powerbi": ["../tests/answer/homework2_last.pdf"],
19
+ "sql": ["../tests/answer/sql3.zip"],
20
+ "ssis": ["../tests/answer/answer.dtsx"]
21
+ }
22
+
23
+ async def main():
24
+ evaluator = HomeworkEvaluator()
25
+ process = psutil.Process(os.getpid())
26
+
27
+ for qtype, question in question_sets.items():
28
+ for ans in answer_paths[qtype]:
29
+ mem_before = process.memory_info().rss
30
+ evaluation = await evaluator.evaluate_from_content(
31
+ question_content=question,
32
+ answer_path=ans,
33
+ api_key=API_KEY,
34
+ question_type=qtype
35
+ )
36
+ mem_after = process.memory_info().rss
37
+ delta_mb = (mem_after - mem_before) / 1024**2
38
+
39
+ print(f"{qtype} | {ans}")
40
+ print(f"📈 Memory used for evaluation: {delta_mb:.2f} MB")
41
+ print(f"✅ Evaluation result: {pprint(evaluation)}")
42
+ print("-" * 40)
43
+
44
+ if __name__ == "__main__":
45
+ asyncio.run(main())
@@ -0,0 +1,25 @@
1
+ import asyncio
2
+ from pprint import pprint
3
+ from QuantumCheck import HomeworkEvaluator
4
+
5
+ API_KEY = "AIzaSyDw76DEINpfBVgwIEZLShhy97tvWg7BmzY"
6
+
7
+ question = "Create a Power BI report with a bar chart.\nExplain DAX measures for sales analysis."
8
+ answer_path = "../tests/answer/test.pdf"
9
+
10
+ async def main():
11
+ evaluator = HomeworkEvaluator()
12
+ evaluation = await evaluator.evaluate_from_content(
13
+ question_content=question,
14
+ answer_path=answer_path,
15
+ api_key=API_KEY,
16
+ question_type="powerbi"
17
+ )
18
+
19
+ print(f"PowerBI | {answer_path}")
20
+ print("✅ Evaluation result:")
21
+ pprint(evaluation)
22
+ print("-" * 40)
23
+
24
+ if __name__ == "__main__":
25
+ asyncio.run(main())
@@ -1,82 +0,0 @@
1
- import asyncio
2
- from QuantumCheck import HomeworkEvaluator
3
-
4
- question_sets = {
5
- "python_beginner": "Write a Python function to calculate factorial.\nWrite a Python script to reverse a string.",
6
- "power_bi": "Create a Power BI report with a bar chart.\nExplain DAX measures for sales analysis.",
7
- "sql": "Write a SQL query to join two tables.\nWrite a SQL query for aggregate functions.",
8
- "ssis": "Design an SSIS package for data import.\nExplain SSIS control flow tasks."
9
- }
10
-
11
- answer_paths = {
12
- "python": ["../tests/answer/python1.zip"],
13
- "powerbi": ["../tests/answer/real.zip"],
14
- "sql": ["../tests/answer/sql3.zip"],
15
- "ssis": ["../tests/answer/answer.dtsx"]
16
- }
17
-
18
- question_type_mapping = {
19
- "python_beginner": "python",
20
- "power_bi": "powerbi",
21
- "sql": "sql",
22
- "ssis": "ssis"
23
- }
24
-
25
-
26
- def format_score(score):
27
- if score >= 90:
28
- return f"🟢 Excellent ({score}⭐)"
29
- elif score >= 75:
30
- return f"🟡 Good ({score})"
31
- elif score >= 50:
32
- return f"🟠 Pass ({score})"
33
- else:
34
- return f"🔴 Fail ({score})"
35
- API_KEY = "<KEY>"
36
- async def run_evaluation(evaluator, q_key, q_content, question_type, answer_path, index):
37
- try:
38
- evaluation = await evaluator.evaluate_from_content(
39
- question_content=q_content,
40
- answer_path=answer_path,
41
- api_key=API_KEY,
42
- question_type=question_type
43
- )
44
- score = evaluation.get("score", 0)
45
- return (q_key, index, "success", score)
46
- except Exception as e:
47
- return (q_key, index, "error", str(e))
48
-
49
- async def main():
50
- evaluator = HomeworkEvaluator()
51
- tasks = []
52
-
53
- for q_key, q_content in question_sets.items():
54
- question_type = question_type_mapping[q_key]
55
- paths = answer_paths.get(question_type, [])
56
- if not paths:
57
- print(f"⚠️ No answer paths found for question type '{question_type}'")
58
- continue
59
- for i in range(10): # run each set 10 times
60
- for path in paths:
61
- task = run_evaluation(evaluator, q_key, q_content, question_type, path, i + 1)
62
- tasks.append(task)
63
-
64
- results = await asyncio.gather(*tasks)
65
-
66
- # Group results by question key
67
- grouped = {}
68
- for q_key, index, status, output in results:
69
- if q_key not in grouped:
70
- grouped[q_key] = []
71
- grouped[q_key].append((index, status, output))
72
-
73
- # Sort and print all at once, grouped by question
74
- for q_key in grouped:
75
- print(f"\n📘 {q_key.upper()} Results")
76
- for index, status, output in sorted(grouped[q_key], key=lambda x: x[0]):
77
- if status == "success":
78
- print(f" ⏱️ Run {index:02}: {format_score(output)}")
79
- else:
80
- print(f" ⏱️ Run {index:02}: ❌ Error - {output}")
81
-
82
- asyncio.run(main())
File without changes
File without changes