QuantumChecker 0.3.1__tar.gz → 0.3.3__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.
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/PKG-INFO +1 -1
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/QuantumCheck/main.py +2 -2
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/QuantumCheck/powerbi_evaluator.py +21 -7
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/QuantumChecker.egg-info/PKG-INFO +1 -1
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/QuantumChecker.egg-info/SOURCES.txt +1 -2
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/setup.py +1 -1
- quantumchecker-0.3.3/tests/test.py +82 -0
- quantumchecker-0.3.1/tests/test.py +0 -135
- quantumchecker-0.3.1/tests/test2.py +0 -41
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/QuantumCheck/__init__.py +0 -0
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/QuantumCheck/prompts.py +0 -0
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/QuantumCheck/python_evaluator.py +0 -0
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/QuantumCheck/sql_evaluator.py +0 -0
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/QuantumCheck/ssis_evaluator.py +0 -0
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/QuantumChecker.egg-info/dependency_links.txt +0 -0
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/QuantumChecker.egg-info/requires.txt +0 -0
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/QuantumChecker.egg-info/top_level.txt +0 -0
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/README.md +0 -0
- {quantumchecker-0.3.1 → quantumchecker-0.3.3}/setup.cfg +0 -0
|
@@ -80,8 +80,8 @@ class HomeworkEvaluator:
|
|
|
80
80
|
now = datetime.now()
|
|
81
81
|
if self._last_request_time:
|
|
82
82
|
elapsed = (now - self._last_request_time).total_seconds()
|
|
83
|
-
if elapsed <
|
|
84
|
-
await asyncio.sleep(
|
|
83
|
+
if elapsed < 5:
|
|
84
|
+
await asyncio.sleep(5 - elapsed)
|
|
85
85
|
self._last_request_time = datetime.now()
|
|
86
86
|
|
|
87
87
|
logger = self._get_logger("QuantumCheck.main")
|
|
@@ -17,14 +17,28 @@ import base64
|
|
|
17
17
|
|
|
18
18
|
def prompt_text_powerbi(combined_content: str) -> str:
|
|
19
19
|
return f"""
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
You are an expert Power BI instructor evaluating beginner-level DAX question-answer pairs.
|
|
21
|
+
|
|
22
|
+
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
|
+
|
|
24
|
+
Use the following criteria to give a fair and supportive evaluation:
|
|
25
|
+
- Measures (40 points): Are calculated measures meaningful, syntactically valid, and aligned with the question?
|
|
26
|
+
- Relationships (20 points): Are key relationships between tables defined logically?
|
|
27
|
+
- Tables & Columns (20 points): Are relevant tables/columns present? Are naming conventions clear?
|
|
28
|
+
- Expressions (10 points): Are query partitions or expressions present and understandable?
|
|
29
|
+
- Overall structure (10 points): Does the model appear coherent and purposeful?
|
|
30
|
+
|
|
31
|
+
**Scoring Tolerance**:
|
|
32
|
+
- Be kind to beginners. Do not give extremely low scores unless the model is completely missing or incorrect.
|
|
33
|
+
- If measures exist and make some sense, award partial credit (e.g., 20–30 out of 40).
|
|
34
|
+
- A score below 30/100 should only be given if there’s little to no relevant content.
|
|
35
|
+
|
|
36
|
+
Structure your response exactly like this:
|
|
37
|
+
OVERALL SCORE: [SCORE]/100
|
|
38
|
+
[Brief feedback here — 3–5 sentences focused on strengths + areas to improve.]
|
|
39
|
+
{combined_content}
|
|
40
|
+
"""
|
|
25
41
|
|
|
26
|
-
{combined_content}
|
|
27
|
-
"""
|
|
28
42
|
|
|
29
43
|
|
|
30
44
|
load_dotenv()
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="QuantumChecker",
|
|
5
|
-
version="0.3.
|
|
5
|
+
version="0.3.3",
|
|
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,82 @@
|
|
|
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())
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import logging
|
|
3
|
-
import random
|
|
4
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
5
|
-
from typing import List, Dict
|
|
6
|
-
from QuantumCheck import HomeworkEvaluator
|
|
7
|
-
from backoff import on_exception, expo
|
|
8
|
-
from google.api_core.exceptions import TooManyRequests
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
logging.basicConfig(
|
|
12
|
-
level=logging.INFO,
|
|
13
|
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
14
|
-
handlers=[
|
|
15
|
-
logging.FileHandler("evaluation.log", encoding="utf-8"),
|
|
16
|
-
logging.StreamHandler()
|
|
17
|
-
]
|
|
18
|
-
)
|
|
19
|
-
logger = logging.getLogger(__name__)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
question_sets = {
|
|
23
|
-
"python_beginner": "Your Python beginner questions content here...",
|
|
24
|
-
"power_bi": "Your Power BI questions content here...",
|
|
25
|
-
"sql": "Your SQL questions content here...",
|
|
26
|
-
"ssis": "Your SSIS questions content here..."
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
answer_paths = {
|
|
30
|
-
"python": ["../tests/answer/python1.zip"],
|
|
31
|
-
"power_bi": ["../tests/answer/real.zip"],
|
|
32
|
-
"sql": ["../tests/answer/sql3.zip"],
|
|
33
|
-
"ssis":["../tests/answer/answer.dtsx"]
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
question_type_mapping = {
|
|
37
|
-
"python_beginner": "python",
|
|
38
|
-
"power_bi": "power_bi",
|
|
39
|
-
"sql": "sql",
|
|
40
|
-
"ssis": "ssis"
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
api_keys = []
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
evaluator = HomeworkEvaluator(log_level=logging.INFO)
|
|
47
|
-
|
|
48
|
-
@on_exception(expo, TooManyRequests, max_tries=5, max_time=300)
|
|
49
|
-
async def evaluate_async(question_text: str, answer_path: str, api_keys: List[str], question_type: str) -> Dict:
|
|
50
|
-
"""Wrap the async evaluate function with exponential backoff for rate limit errors."""
|
|
51
|
-
try:
|
|
52
|
-
return await evaluator.evaluate_from_content(
|
|
53
|
-
question_content=question_text,
|
|
54
|
-
answer_path=answer_path,
|
|
55
|
-
api_keys=api_keys,
|
|
56
|
-
question_type=question_type
|
|
57
|
-
)
|
|
58
|
-
except Exception as e:
|
|
59
|
-
logger.error(f"Evaluation failed: {str(e)}")
|
|
60
|
-
return {
|
|
61
|
-
"score": 0,
|
|
62
|
-
"feedback": f"Evaluation failed: {str(e)}",
|
|
63
|
-
"issues": [str(e)],
|
|
64
|
-
"recommendations": [],
|
|
65
|
-
"used_api_key_index": None,
|
|
66
|
-
"used_api_name": None
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async def main(num_requests: int = 10, max_workers: int = 6):
|
|
70
|
-
resource_exhausted_count = 0
|
|
71
|
-
logger.info(f"Starting evaluation with {num_requests} requests and {max_workers} workers")
|
|
72
|
-
|
|
73
|
-
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
74
|
-
loop = asyncio.get_running_loop()
|
|
75
|
-
tasks = []
|
|
76
|
-
|
|
77
|
-
for i in range(num_requests):
|
|
78
|
-
question_key = random.choice(list(question_sets.keys()))
|
|
79
|
-
question_text = question_sets[question_key]
|
|
80
|
-
question_type = question_type_mapping.get(question_key, "python")
|
|
81
|
-
answer_path = random.choice(answer_paths.get(question_type, answer_paths["python"]))
|
|
82
|
-
|
|
83
|
-
logger.info(f"Scheduling Request #{i + 1} for '{question_key}' with '{answer_path}'")
|
|
84
|
-
task = loop.create_task(evaluate_async(question_text, answer_path, api_keys, question_type))
|
|
85
|
-
tasks.append((i + 1, task))
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
results = []
|
|
89
|
-
for i, task in tasks:
|
|
90
|
-
try:
|
|
91
|
-
result = await task
|
|
92
|
-
if "resource exhausted" in str(result.get("feedback", "")).lower():
|
|
93
|
-
resource_exhausted_count += 1
|
|
94
|
-
results.append((i, result))
|
|
95
|
-
except Exception as e:
|
|
96
|
-
logger.error(f"Request #{i} failed with error: {str(e)}")
|
|
97
|
-
if "resource exhausted" in str(e).lower():
|
|
98
|
-
resource_exhausted_count += 1
|
|
99
|
-
results.append((i, {
|
|
100
|
-
"score": 0,
|
|
101
|
-
"feedback": f"Request failed: {str(e)}",
|
|
102
|
-
"issues": [str(e)],
|
|
103
|
-
"recommendations": [],
|
|
104
|
-
"used_api_key_index": None,
|
|
105
|
-
"used_api_name": None
|
|
106
|
-
}))
|
|
107
|
-
|
|
108
|
-
logger.info("All evaluations completed")
|
|
109
|
-
|
|
110
|
-
for i, result in results:
|
|
111
|
-
score = result.get("score", 0)
|
|
112
|
-
feedback = result.get("feedback", "")
|
|
113
|
-
used_key_index = result.get("used_api_key_index", "N/A")
|
|
114
|
-
used_api_name = result.get("used_api_name", "N/A")
|
|
115
|
-
|
|
116
|
-
feedback_words = feedback.split()
|
|
117
|
-
partial_feedback = " ".join(feedback_words[:40]) + ("..." if len(feedback_words) > 10 else "")
|
|
118
|
-
|
|
119
|
-
if score == 0 and "failed" in feedback.lower():
|
|
120
|
-
logger.error(f"Request #{i} failed with error: {feedback}")
|
|
121
|
-
print(f"X Request #{i} failed with error: {feedback}")
|
|
122
|
-
else:
|
|
123
|
-
logger.info(f"Request #{i} succeeded: Score = {score}, API Index = {used_key_index}, API Name = {used_api_name}")
|
|
124
|
-
print(f"O Request #{i} succeeded: Score = {score}, API Index = {used_key_index}, API Name = {used_api_name}")
|
|
125
|
-
print(f" Feedback preview: {partial_feedback}\n")
|
|
126
|
-
|
|
127
|
-
logger.info(f"Total 'resource exhausted' errors encountered: {resource_exhausted_count}")
|
|
128
|
-
print(f"Total 'resource exhausted' errors encountered: {resource_exhausted_count}")
|
|
129
|
-
|
|
130
|
-
if __name__ == "__main__":
|
|
131
|
-
try:
|
|
132
|
-
asyncio.run(main(num_requests=100, max_workers=20))
|
|
133
|
-
except Exception as e:
|
|
134
|
-
logger.error(f"Fatal error in main: {e}")
|
|
135
|
-
print(f"Fatal error in main: {e}")
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
from QuantumCheck import HomeworkEvaluator
|
|
3
|
-
|
|
4
|
-
async def main():
|
|
5
|
-
evaluator = HomeworkEvaluator()
|
|
6
|
-
|
|
7
|
-
question_content = """
|
|
8
|
-
Q1: Write a Python function that calculates the factorial of a number.
|
|
9
|
-
|
|
10
|
-
Q2: What is the difference between a list and a tuple in Python?
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
answer_path = "answer/python1.zip"
|
|
14
|
-
|
|
15
|
-
question_type = "python"
|
|
16
|
-
|
|
17
|
-
result = await evaluator.evaluate_from_content(
|
|
18
|
-
question_content=question_content,
|
|
19
|
-
answer_path=answer_path,
|
|
20
|
-
api_key="AIzaSyC2B_Q38DkCl6O8y4b5hAWEpb6aJHW6FcY",
|
|
21
|
-
question_type=question_type
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
result2 = await evaluator.evaluate_from_content(
|
|
25
|
-
question_content=question_content,
|
|
26
|
-
answer_path=answer_path,
|
|
27
|
-
api_key="AIzaSyC2B_Q38DkCl6O8y4b5hAWEpb6aJHW6FcY",
|
|
28
|
-
question_type=question_type
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
print("Evaluation Result:")
|
|
32
|
-
print(result["score"])
|
|
33
|
-
print(result["feedback"])
|
|
34
|
-
|
|
35
|
-
print("Evaluation Result:")
|
|
36
|
-
print(result2["score"])
|
|
37
|
-
print(result2["feedback"])
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if __name__ == "__main__":
|
|
41
|
-
asyncio.run(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|