> تو مگو همه به جنگند و زِ صلح من چه آید **تو یکی نِهای هزاری تو چراغِ خود برافروز...**
**باقر** *(Bagher)،* که پس از **تغییراتی ابلفضلی** در ساختار اسکواد کوئرا کالج، این بار اما با شروع [**سری جدید #المپیکفناوری پردیس**](https://quera.org/events/techolympics-0407) تصمیم به تاسیس آکادمی آموزشی جدیدی با نام **باقرآکادمی** گرفته که قرار است مرزهای آموزش برنامهنویسی و هوشمصنوعی را این بار با اما همکاری **پارک فناوری پردیس،** *از قلبهای تپندهی فناوری ایران،* جابهجا کند. **باقرآکادمی** اما برخلاف مجموعههای آموزشی دیگر از **یک روش جدید** و **پیشرفته** برای ارزیابی دانشجویانش استفاده میکند!
انواع مختلفی از سوالات آموزشی که در **باقرآکادمی** پشتیبانی میشوند، مانند **سوالات چندگزینهای** و یا **کوتاهپاسخ،** میتوانند برخلاف سوالات چندگزینهای و کوتاهپاسخی که پیشتر دیدهاید، شامل *متغیرها، روابط ریاضی و بازههای عددی* در قالب **مانیفست سوالات** *(Question Manifest)* باشند. کارکنان این مجموعه، سوالات را در هنگام طراحی به صورت **مانیفستهایی زنده** و **متغیر** برای تولید **نه تنها یک نمونه سوال،** بلکه **هزاران نمونه سوال جدید مشابه** اما با بدنههای متغیر، بیان میکنند! **تو یکی بلکه هزاری تو سوال خود برافروز...**

# **پروژه اولیه**
برای دانلود **پروژهی اولیه** روی [این لینک](/problemset/assignments/4367/download_problem_initial_project/316832/) کلیک کنید.
<details class="yellow">
<summary>
**ساختار فایلها**
</summary>
```
bagher-academy
├── core
│ ├── __init__.py
│ ├── <mark class="yellow" title="این فایل باید پیادهسازی شود">evaluator.py</mark>
│ ├── <mark class="yellow" title="این فایل باید پیادهسازی شود">placeholder.py</mark>
│ ├── <mark class="yellow" title="این فایل باید پیادهسازی شود">renderer.py</mark>
│ └── <mark class="yellow" title="این فایل باید پیادهسازی شود">variable_resolver.py</mark>
├── data
│ ├── configs
│ └── templates
├── engine
├── main.py
├── models
│ ├── __init__.py
│ ├── <mark class="blue" title="این فایل پیادهسازی شده است">base_question.py</mark>
│ ├── <mark class="yellow" title="این فایل باید پیادهسازی شود">matching.py</mark>
│ ├── <mark class="yellow" title="این فایل باید پیادهسازی شود">mcq.py</mark>
│ ├── <mark class="yellow" title="این فایل باید پیادهسازی شود">numeric_range.py</mark>
│ ├── <mark class="yellow" title="این فایل باید پیادهسازی شود">question_bank.py</mark>
│ ├── <mark class="yellow" title="این فایل باید پیادهسازی شود">short_answer.py</mark>
│ └── <mark class="yellow" title="این فایل باید پیادهسازی شود">true_false.py</mark>
├── questions
├── utils
└── valid_files
```
</details>
<details class="grey">
<summary>
**راهاندازی پروژه**
</summary>
## **تعریف مانیفست سؤالها**
هر سؤال در باقرآکادمی بهصورت یک **مانیفست** *JSON* در مسیر `data/templates/` تعریف میشود. مثلاً برای **سؤال کوتاهپاسخ:**
```json data/templates/short_answer_template.json json
{
"id": "short",
"type": "short_answer",
"template": {
"stem": "یک جسم از ارتفاع {{height}} متر رها میشود. شتاب گرانش g = {{g}} m/s² است. سرعت برخورد جسم با زمین چقدر است؟",
"answer": "{{sqrt(2*g*height)}}"
},
"variables": {
"height": [5, 20],
"g": [9.7, 9.9]
}
}
```
## **اجرای برنامه** *CLI*
### **تولید سه نسخه از سؤال کوتاهپاسخ** *(بدون ذخیره)*
```bash terminal terminal
python main.py --template short
```
### **تولید پنج نسخه از سؤال پرتابه** *(numeric_range)*
```bash terminal terminal
python main.py --template projectile_range --count 5
```
### **تولید و ذخیره خروجی بهصورت فایل** *JSON*
```bash terminal terminal
python main.py --template short --count 3 --save
```
در این حالت، فایلهای زیر بهصورت خودکار ساخته میشوند:
```
questions/
└── short/
├── question1.json
├── question2.json
└── question3.json
```
## **ساختار خروجی هر سؤال**
**نمونه خروجی برای** `short_answer`**:**
```json question1.json json
{
"id": "short",
"type": "short_answer",
"rendered": {
"stem": "یک جسم از ارتفاع 12 متر رها میشود. شتاب گرانش g = 9.8 m/s² است. سرعت برخورد جسم با زمین چقدر است؟",
"answer": "15.34"
},
"vars": {
"height": 12,
"g": 9.8
}
}
```
</details>
# **جزئیات پروژه**
در این پروژه، هدف **پیادهسازی یک ساختار برای تولید** و **مدیریت انواع سوالات آموزشی** است که شامل قالبهای مختلف مانند *چندگزینهای، پاسخ کوتاه، درست/نادرست، تطبیقی و بازه عددی* میشود. این سیستم باید بتواند **مانیفست سوال** را از [**فایلهای** *JSON*](https://en.wikipedia.org/wiki/JSON) بارگذاری کند، **متغیرهای** *(Variables)* مرتبط با هر سوال را با استفاده از محدودهها و قواعد مشخص تولید کند و سپس **متن نهایی سوال** و **گزینهها** یا **پاسخها** را با جایگذاری **مقادیر متغیرها** و **محاسبات ریاضی** محاسبهشده ایجاد کند. ساختار پروژه بر اساس **کلاسهای پایه** و **قابل ارثبری** طراحی شده است؛ `BaseQuestion` **رابط اصلی** را فراهم میکند و **کلاسهای خاص مانند** `MultipleChoiceQuestion` یا `NumericRangeQuestion` این رابط را پیادهسازی میکنند و مسئول **مدیریت قالب، اعتبارسنجی و تولید نسخههای متغیرهای مختلف** هستند. بخشهای کمکی مانند `SafeEvaluator` برای **محاسبهی عبارات ریاضی،** `PlaceholderProcessor` **برای پردازش متنهای قالببندیشده** با *placeholder* و `VariableResolver` **برای تولید مقادیر متغیرها** استفاده میشوند **تا کل جریان تولید سوال، از بارگذاری قالب تا رندر نهایی قابل اجرا باشد.**
<details class="grey">
<summary>
**معرفی مانیفستهای باقرآکادمی - یکی بلکه هزاری سوال!**
</summary>
در باقرآکادمی، هر **سوال** با یک **مانیفست** به صورت قالب *JSON* تعریف میشود که شامل **شناسه یکتا** (`id`)، **نوع سوال** (`type`)، **قالب متنی** (`template`) و **محدوده مقادیر متغیرها** (`variables`) است. این مانیفستها، **ساختار اصلی سوال را تعیین میکنند** و به سیستم اجازه میدهند تا **نسخههای مختلفی از یک سوال با مقادیر متغیر متفاوت تولید کند.** قالبها میتوانند شامل *placeholderهایی* در **قالب** `{{...}}` باشند که با مقادیر تولید شده جایگزین میشوند و **حتی محاسبات ریاضی درون آنها توسط** `SafeEvaluator` انجام میشود.
برای مثال، مانیفست **سوالات با نوع کوتاهپاسخ** `short_answer` با **شناسه** `short` به صورت زیر تعریف شده است:
```json short_answer_template.json json
{
"id": "short",
"type": "short_answer",
"template": {
"stem": "یک جسم از ارتفاع <mark class="blue" title="مقدار ارتفاع در سوال یک متغیر است">{{height}}</mark> متر رها میشود. شتاب گرانش g = <mark class="blue" title="مقدار شتاب گرانش زمین یک متغیر است">{{g}}</mark> m/s² است. سرعت برخورد جسم با زمین چقدر است؟",
"answer": "<mark class="blue" title="پاسخ نهایی سوال یک عبارت ریاضی متغیر است">{{sqrt(2*g*height)}}</mark>"
},
"variables": {
"height": [5, 20],
"g": [9.7, 9.9]
}
}
```
+ در این مثال، متن سوال شامل دو متغیر **ارتفاع** (`height`) و **شتاب گرانش** (`g`) است که در کد بالا با رنگ آبی مشخص شدهاند و **پاسخ به صورت عبارت ریاضی** `sqrt(2*g*height)` مشخص شده است. باقرآکادمی در هر اجرا، مقادیر **مجاز در محدوده مشخص شده برای متغیرها** را تولید کرده و پاسخ را محاسبه و جایگذاری میکند. خروجی نهایی، سوالات مختلفی است که ممکن است چیزی شبیه به این باشد:
```json short_answer_question.json json
{
"stem": "یک جسم از ارتفاع 12 متر رها میشود. شتاب گرانش g = 9.8 m/s² است. سرعت برخورد جسم با زمین چقدر است؟",
"answer": "15.34",
"vars": {"height": 12, "g": 9.8}
}
```
**مانیفست** `numeric_range` با **شناسه** `projectile_range` نیز مشابه عمل میکند، اما **پاسخ عددی با تلرانس مشخص ارائه میشود** تا امکان مقایسه با جواب کاربر فراهم باشد:
```json numeric_range_template.json json
{
"id": "projectile_range",
"type": "numeric_range",
"template": {
"stem": "اگر یک جسم با سرعت اولیه <mark class="blue" title="مقدار سرعت در این سوال یک متغیر است">{{v0}}</mark> m/s به صورت افقی پرتاب شود، فاصله طی شده قبل از برخورد زمین چقدر است؟",
"answer": "<mark class="blue" title="پاسخ نهایی سوال یک عبارت ریاضی متغیر است">{{v0*sqrt(2*height/g)}}</mark>",
"tolerance": 0.05
},
"variables": {
"v0": [10, 30],
"height": [5, 20],
"g": [9.7, 9.9]
}
}
```
خروجی پردازش شده این سوال ممکن است به شکل زیر باشد:
```json numeric_range_question.json json
{
"stem": "اگر یک جسم با سرعت اولیه 15 m/s به صورت افقی پرتاب شود، فاصله طی شده قبل از برخورد زمین چقدر است؟",
"answer": "21.0",
"tolerance": 0.05,
"vars": {"v0": 15, "height": 10, "g": 9.8}
}
```
و برای انواع دیگر سوالات نیز به همین ترتیب خواهد بود. به طور خلاصه، **مانیفستها،** قالبهای *JSON* **انعطافپذیری** هستند که مشخص میکنند سوال چه **متغیرهایی** دارد، **متن چگونه تولید شود** و **پاسخ چگونه محاسبه گردد.** باقرآکادمی با استفاده از کلاسهای مدیریت سوالات، **پردازش** *placeholderها* و تولید مقادیر متغیر و پاسخ نهایی، **سوالات آماده برای آزمونها** تولید میکند که **قابلیت ارزیابی درست کاربران** را دارند.
</details>
<details class="blue">
<summary>
**پیادهسازی پوشه** `core`
</summary>
<details class="blue">
<summary>
**پیاده سازی کلاس** `SafeEvaluator` **از فایل** `evaluator.py`
</summary>
**کلاس** `SafeEvaluator` در **فایل** `core/evaluator.py` باید ابزاری باشد که بتواند **عبارات ریاضی متنی** را بر اساس **مجموعهای از متغیرها** ارزیابی کرده و **مقدار عددی دقیق** آن را بازگرداند. **ورودی اصلی متد** `eval` **یک رشتهی بیان ریاضی است،** که ممکن است شامل *عملگرهایی مثل جمع، تفریق، ضرب، تقسیم، توان، باقیمانده، و حتی فراخوانی توابعی مانند* `sqrt`*،* `log`*،* `sin` *یا* `cos` باشد. خروجی آن باید یک عدد *(صحیح یا اعشاری)* باشد که با **دقت مشخصی** *(مثلاً تا سه رقم اعشار)* **گرد** شده است تا نتایج در تمام اجراها، **قطعی و مشخص** باقی بمانند. در صورتی که عبارت شامل *نامهای ناشناخته، توابع غیرمجاز یا سینتکس نامعتبر* باشد، باید **خطای** `ValueError` **بازگردانده** شود تا از **اجرای کد ناامن جلوگیری شود.**
همچنین کلاس باید از **ثابتهای ریاضی مثل** `pi` و `e` **پشتیبانی کند** تا تستهایی که از توابع مثلثاتی یا لگاریتمی استفاده میکنند بهدرستی کار کنند. **اگر متغیرهایی مانند** `x`، `y` یا `a` در عبارت وجود داشته باشند، مقادیر آنها از **دیکشنری ورودی** گرفته میشود تا بتوان عبارات پارامتری را نیز ارزیابی کرد. بهعلاوه، در *تستهایی که چند عمل ریاضی پشتسرهم ترکیب شدهاند، ترتیب تقدم عملگرها و تو در تویی توابع* **باید حفظ شود** تا نتایج عددی **دقیقاً با خروجی کتابخانهی** `math` برابر باشند.
**متد** `eval` در **کلاس** `SafeEvaluator` **نقش اصلی را در تبدیل یک رشتهی متنی حاوی عبارت ریاضی به مقدار عددی ایفا میکند.** ورودی این متد یک رشته است که ممکن است شامل *اعداد، متغیرها، عملگرهای ریاضی و توابع مجاز مانند* `sqrt`, `log`, `sin` باشد و **خروجی آن یک مقدار عددی** *(صحیح یا اعشاری)* است که با **دقت مشخص گرد شده است.**همانطور که پیشتر گفته شد، این متد باید **تمام ترکیبهای تو در تو و زنجیرهای از عملیات ریاضی** را به درستی محاسبه کند، به طوری که حتی عبارات پیچیده مثل `a + b * c ** 2 - (b + c)/a` یا `sqrt(abs(log(exp((x + y)**2))))` دقیقاً همان نتیجهای را بدهند که انتظار میرود.
علاوه بر این، `eval` باید بهطور کامل **ایمن** باشد و هیچگونه کد اجرایی یا فراخوانی **توابع غیرمجاز** را اجازه **ندهد؛** اگر عبارت شامل **نامهای ناشناخته** یا **دستورات خطرناک** باشد، باید **خطای** `ValueError` بدهد. در پیادهسازی، این متد با استفاده از **تجزیهی** *AST* عمل میکند و هر گرهی عبارت *(مثل عملیات باینری، یونیاری، فراخوانی تابع یا ثابت)* را **به صورت کنترلشده ارزیابی میکند** تا علاوه بر دقت عددی، امنیت و پیشبینیپذیری کامل را نیز **تضمین** کند. همچنین در ترکیب با دیگر بخشهای **باقرآکادمی** مانند `PlaceholderProcessor` و `Renderer`، **متد** `eval` **پایهی محاسبات پارامتری و جایگذاری مقادیر در قالب سوالات** را تشکیل خواهد داد.
+ **توجه داشته باشید** که در پیادهسازی این سوال شما **به هیچ عنوان مجاز به استفاده از تابع** `eval` پایتونی **نیستید** و استفاده از این مورد به صورت خودکار، **نمره صفر برای گل پاسخ ارائه شده لحاظ خواهد کرد.**
```python core/evaluator.py python
import ast
import operator as op
import math
from typing import Any, Dict, Optional
from utils.config_loader import ConfigLoader
config = ConfigLoader()
decimal_places: Optional[int] = config.get("decimal_places", 3)
enabled_functions = config.get(
"math_functions",
[
"sqrt","sin","cos","tan","asin","acos","atan",
"log","log10","exp","ceil","floor","abs","round"
]
)
OPERATORS = {
ast.Add: op.add,
ast.Sub: op.sub,
ast.Mult: op.mul,
ast.Div: op.truediv,
ast.FloorDiv: op.floordiv,
ast.Mod: op.mod,
ast.Pow: op.pow,
ast.USub: op.neg,
ast.UAdd: op.pos,
}
builtins_dict = __builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__
SAFE_FUNCTIONS: Dict[str, Any] = {}
for name in enabled_functions:
if name in ("abs", "round"):
SAFE_FUNCTIONS[name] = builtins_dict[name]
else:
SAFE_FUNCTIONS[name] = getattr(math, name)
CONSTANTS = {"pi": math.pi, "e": math.e}
class SafeEvaluator:
def __init__(self, vars: Optional[Dict[str, Any]] = None):
pass
def eval(self, expression: str) -> Any:
pass
```
برای درک نقش `SafeEvaluator` و **متد** `eval`، میتوان دو مثال زیر را در نظر گرفت. **در مثال اول،** یک **عبارت تو در تو از توابع ریاضی** با استفاده از`sqrt`, `abs`, `log`, `exp` داریم:
```python core/evaluator.py python
ev = SafeEvaluator(vars={'x': 2, 'y': 3})
expr = 'sqrt(abs(log(exp((x + y) ** 2))))'
result = ev.eval(expr)
# sqrt((2 + 3) ** 2) = 5
```
+ در اینجا، `eval` باید **توانایی پردازش دقیق عملیات تو در تو** را داشته باشد و **مقادیر متغیرها** (`x=2`, `y=3`) را جایگذاری کند. **ترتیب اعمال تابعها** و **عملیات ریاضی** رعایت میشود تا نتیجهی نهایی با مقدار مورد انتظار مطابق باشد.
در مثال دوم، **یک عبارت ترکیبی از عملگرهای مختلف داریم که شامل جمع، ضرب، توان، تفریق و تقسیم** است:
```python core/evaluator.py python
ev = SafeEvaluator(vars={'a': 2, 'b': 3, 'c': 4})
expr = 'a + b * c ** 2 - (b + c) / a'
result = ev.eval(expr)
# 2 + 3 * 4**2 - (3 + 4)/2 = 46.5
```
+ در اینجا **متد** `eval` **باید تقدم عملگرها را رعایت کند** و محاسبات ترکیبی را به دقت انجام دهد. این مثال نشان میدهد که `SafeEvaluator` باید قادر باشد تا عبارات پیچیدهی عددی را بهدرستی محاسبه کند.
</details>
<details class="blue">
<summary>
**پیاده سازی کلاس** `PlaceholderProcessor` **از فایل** `placeholder.py`
</summary>
**کلاس** `PlaceholderProcessor` برای **پردازش متنهایی طراحی شده است** که شامل *placeholderهای* **محاسباتی به شکل** `{{ ... }}` هستند و **هدف آن جایگذاری مقادیر محاسبهشده بهطور در متن است.** این کلاس هنگام ساخت، یک `SafeEvaluator` دریافت میکند تا هر بار که نیاز به محاسبهی عبارتها باشد، **یک نمونه امن و مستقل ایجاد شود** و **هیچ تداخل یا تغییر ناخواستهای در مقادیر رخ ندهد.**
**متد** `process` **متن ورودی را بررسی میکند** و **تمام بخشهایی که با الگوی** `{{...}}` همخوانی دارند را **استخراج** میکند. سپس برای هر *placeholder،* **متغیرهای دادهشده را جایگذاری کرده** و **عبارت را با استفاده از** `SafeEvaluator.eval` **محاسبه میکند.** اگر **هنگام محاسبات ریاضی،** خطایی رخ دهد یا عبارت شامل متغیر یا تابع **ناشناخته** باشد، **کلاس به صورت امن** *placeholder* را **بدون تغییر باقی میگذارد** و **از ایجاد خطا جلوگیری میکند.** در نهایت، متن خروجی شامل همهی *placeholderهای* **جایگزینشده با مقادیر محاسبهشده است.**
```python core/placeholder.py python
from .evaluator import SafeEvaluator
class PlaceholderProcessor:
def __init__(self, evaluator_factory):
pass
def process(self, text: str, vars: Dict[str, float]) -> str:
pass
```
+ **تابع** `process` از **کلاس** `PlaceholderProcessor` **مسئول پردازش متنهایی است** که شامل *placeholder* یا **عبارتهای محاسباتی در قالب** `{{ ... }}` هستند و **با مقادیر متغیرها جایگذاری میشوند.**
بهعنوان مثال، اگر متن به شکل زیر باشد:
```python core/placeholder.py python
pp = PlaceholderProcessor(lambda: SafeEvaluator(vars={}))
text = "Sum = {{x + y}}"
result = pp.process(text, {'x': 2, 'y': 3})
# result باید برابر با "Sum = 5" باشد
```
+ در این حالت، `process` ** حاصل عبارت داخل** `{{x + y}}` را پیدا میکند، **مقادیر متغیرها** (`x=2`, `y=3`) را **جایگذاری** میکند و با استفاده از `SafeEvaluator.eval` **محاسبه** میکند. **نتیجهی عددی به رشته تبدیل شده و در متن اصلی جایگزین میشود.**
مثال دیگر که شامل توابع ریاضی و ترکیبها است:
```python core/placeholder.py python
text = "Value={{ sqrt(x**2 + y**2) + log(z) }}"
result = pp.process(text, {'x': 3, 'y': 4, 'z': math.e})
# result باید برابر با "Value=6.0" باشد
```
+ در اینجا `process` قادر است **عبارات پیچیده و تو در تو** را پردازش کند و مقادیر دقیق را جایگزین کند. اگر هرگونه **خطا** یا **عبارت غیرقابل ارزیابی وجود داشته باشد،** متن اصلی بدون تغییر حفظ میشود، یعنی *placeholder* باقی میماند.
به طور خلاصه، `PlaceholderProcessor` **پل بین دادههای متغیر** و **مانفیست سوال** شده است و تضمین میکند که همهی *placeholderها* با **مقادیر محاسبهشده جایگزین شوند.**
</details>
<details class="blue">
<summary>
**پیاده سازی کلاس** `Renderer` **از فایل** `placeholder.py`
</summary>
**کلاس** `Renderer` **مسئول تبدیل یک سوال با قالب متنی** و **مقادیر متغیرها** به یک **خروجی آماده** است که شامل **متن سوال، گزینهها و پاسخ** محاسبهشده میشود و هدف آن این است که تمام *placeholderها* به صورت با مقادیر مناسب جایگزین شوند. هنگام مقداردهی اولیه، `Renderer` **یک نمونه از** `PlaceholderProcessor` **به همراه نمونهی** `SafeEvaluator` ایجاد میکند تا تمام پردازشهای ریاضی و جایگذاریها به صورت امن و قابل پیشبینی انجام شوند و **هیچ تغییر ناخواستهای روی مقادیر متغیرها رخ ندهد.**
**متد** `render` ابتدا **متن اصلی سوال** یا `stem` را پردازش میکند و **تمامی** *placeholderها* را با مقادیر محاسبهشده جایگزین میکند. سپس **اگر سوال شامل گزینهها باشد،** هر گزینه نیز با همان مقادیر متغیرها پردازش میشود تا **تمامی توابع** و **عبارات ریاضی داخل گزینهها** به شکل صحیح جایگزین شوند. در نهایت، **اگر سوال شامل پاسخ باشد،** `render` آن را **پردازش** میکند و **تلاش** میکند تا مقادیر عددی را با **تعداد رقمهای اعشاری مشخصشده و واحد مناسب نمایش دهد.** اگر مقدار قابل تبدیل به عدد نباشد، به همان شکل متنی نمایش داده میشود و **هیچ خطایی ایجاد نمیشود.**
```python core/renderer.py python
from typing import Dict
from .placeholder import PlaceholderProcessor
from .evaluator import SafeEvaluator
from utils.config_loader import ConfigLoader
config = ConfigLoader()
DECIMAL_PLACES = config.get("decimal_places", 3)
DEFAULT_UNITS = config.get("default_units", {})
class Renderer:
def __init__(self):
pass
def render(self, question, vars: Dict[str, float]) -> Dict[str, str]:
pass
```
**کلاس** `Renderer` مسئول **تبدیل یک سوال** با **مانیفستهای دارای** *placeholder* و **مقادیر متغیر به خروجی نهایی است** که شامل `stem`، **گزینهها** و **پاسخ پردازششده میشود** و تمامی *placeholderها* را با **مقادیر واقعی جایگزین میکند.** به عبارت دیگر، **این کلاس متن خام سوال و گزینهها را دریافت کرده و با استفاده از** `PlaceholderProcessor` و `SafeEvaluator` **مقادیر متغیرها و محاسبات ریاضی داخل** *placeholderها* **را محاسبه و در متن جایگذاری میکند.**
بهعنوان مثال، **اگر یک سوال داشته باشیم با** stem `"Compute {{x}} + {{y}}"` و **گزینههای** `["Sum={{x+y}}", "Double={{x*2}}"]` و **مقادیر متغیرهای** `{'x': 3, 'y': 5}`، **فراخوانی** `Renderer().render(question, {'x': 3, 'y': 5})` **خروجی زیر را تولید میکند:**
```json question.json json
{
"stem": "Compute 3 + 5",
"options": ["Sum=8", "Double=6"]
}
```
+ در این مثال، تمامی *placeholderها* با مقادیر محاسبهشده **جایگزین** شدهاند و بدنه سوال جدید به شکل درست ارائه شده است. همچنین، اگر سوال دارای پاسخ عددی یا متنی باشد، `Renderer` **پاسخ را با تعداد رقمهای اعشار مشخصشده و واحد مناسب قالببندی میکند.** اگر پاسخ یک عبارت غیرقابل تبدیل به عدد باشد، **به همان صورت رشتهای** باقی میماند.
</details>
<details class="blue">
<summary>
**پیاده سازی کلاس** `VariableResolver` **از فایل** `variable_resolver.py`
</summary>
**کلاس** `VariableResolver` **مسئول مدیریت** و **تولید مقادیر تصادفی برای متغیرها** در **محدودههای تعریف شده** است و به گونهای طراحی شده که خروجیها [**دترمنیستیک** *(Deterministic)*](https://en.wikipedia.org/wiki/Deterministic_system) باشند زمانی که یک `seed` مشخص یا `seed` سراسری در پیکربندی تعریف شده باشد. این کلاس، **مقادیر پیشفرض بارگذاری شده از فایل پیکربندی** را با **محدودههای ارائه شده توسط کاربر ترکیب میکند** و برای **هر متغیر** یک **مقدار تصادفی** تولید میکند که در بازه مشخص شده قرار دارد. این طراحی اجازه میدهد که مقادیر تولید شده به صورت **قابل پیشبینی** و **هماهنگ** با نیازها و اجرای برنامه باشند، **حتی وقتی محدودههای متغیرها متنوع یا زیاد باشند.**
**متد** `resolve` **قابلیت تنظیم** `seed` **محلی** برای **تولید مقادیر قابل تکرار را دارد** و این قابلیت باعث میشود هر بار که **همان** `seed` استفاده شود، **خروجیهای تولید شده دقیقاً یکسان باشند.** اگر `seed` **محلی** مشخص **نشده** باشد، **کلاس به طور خودکار از** `seed` سراسری تعریف شده در پیکربندی استفاده میکند **تا همچنان دترمنیستیک بودن حفظ شود.**
در هنگام تولید مقادیر، **هر مقدار با استفاده از** `round` و **تعداد رقمهای اعشار مشخص شده در پیکربندی گرد** میشود تا **دقت عددی** و **قالببندی** عدد حفظ شود. این مسئله برای اطمینان از اینکه مقادیر تولید شده در محاسبات بعدی با دقت معین استفاده شوند **بسیار مهم است** و باعث میشود نتایج محاسباتی در تستها یا رندرینگ سوالات **دقیق** و **یکسان** باقی بمانند. به عنوان مثال، **اگر محدوده متغیرها به شکل** `{'x': (1, 5), 'y': (10, 20)}` باشد و `seed` برابر با `42` تنظیم شود، فراخوانی `VariableResolver({'x': (1,5), 'y':(10,20)}).resolve(seed=42)` **مقدار مشخص و تکرارپذیر مثل** `{'x': 3.14, 'y': 16.27}` تولید میکند و هر بار با همان `seed`، **همان مقادیر بازمیگردند.**
```python core/variable_resolver.py python
import random
from typing import Dict, Any, Optional, Tuple
from utils.config_loader import ConfigLoader
config = ConfigLoader()
GLOBAL_SEED = config.get("random_seed", None)
DECIMAL_PLACES = config.get("decimal_places", 3)
CONFIG_RANGES = config.get("variable_ranges", {})
class VariableResolver:
def __init__(self, ranges: Optional[Dict[str, Tuple[float, float]]] = None):
pass
def resolve(self, seed: Optional[int] = None) -> Dict[str, Any]:
pass
```
+ بهعنوان مثال، فرض کنید **یک سوال با** `stem` و **گزینهها** داریم که شامل *placeholderهای* ریاضی است:
```python core/variable_resolver.py python
class DummyQuestion:
stem = "Compute {{x}} + {{y}}"
options = ["Sum={{x+y}}", "Double={{x*2}}"]
r = Renderer()
rendered = r.render(DummyQuestion(), {'x': 2, 'y': 3})
# rendered['stem'] باید برابر "Compute 2 + 3" باشد
# rendered['options'] باید برابر ["Sum=5", "Double=4"] باشد
```
+ در این مثال، `Renderer` ابتدا *placeholderهای* `stem` را **پردازش** میکند و **مقادیر** `x` و `y` را جایگزین میکند. سپس **گزینهها** را پردازش میکند، به طوری که **محاسبات داخلی هر گزینه** (`x+y` و `x*2`) انجام شده و **خروجی نهایی جایگزین متن** *placeholder* میشود. اگر پاسخ نهایی دارای واحد یا نیاز به گرد کردن باشد، کلاس بهطور خودکار آن را مدیریت میکند.
به طور خلاصه، `Renderer` **مسئول یکپارچهسازی** و **پردازش تمام متون** و **دادههای سوال** است، به طوری که خروجی *دترمنیستیک، دقیق و آماده استفاده در نمایش یا ارزیابی* باشد.
</details>
</details>
<details class="green">
<summary>
**پیادهسازی پوشه** `models`
</summary>
<details class="grey">
<summary>
**توضیحات کلاس** `BaseQuestion` **از فایل** `base_question.py`
</summary>
**کلاس** `BaseQuestion` به عنوان یک **کلاس انتزاعی** طراحی شده تا چارچوب و رابطی مشترک برای تمامی انواع سوالات را فراهم کند. این کلاس شامل **متغیرهای پایهای مانند** `template` برای **نگهداری ساختار سوال** و `variable_spec` برای **تعریف محدوده** یا **نوع متغیرهای استفاده شده** در سوال است. **متد** `to_dict` **امکان تبدیل سوال به یک دیکشنری استاندارد** را فراهم میکند تا بتوان آن را ذخیره یا ارسال کرد و **متد** `generate_variables` **با استفاده از کلاس** `VariableResolver` م**قادیر متغیرهای مورد نیاز برای تولید یک نمونه دترمنیستیک از سوال را فراهم میکند.**
بخش مهم دیگر، **متدهای انتزاعی** `render_variants` و `validate_template` هستند که در **کلاس پایه** تعریف شدهاند **اما پیادهسازی آنها برعهده کلاسهای فرزند است.** `render_variants` وظیفه تولید یک یا چند نسخه از سوال با مقادیر متفاوت متغیرها را بر اساس `variable_spec` دارد، در حالی که `validate_template` **مسئول بررسی صحت** و **کامل بودن** قالب سوال است. **کلاسهای فرزند** با **ارثبری** از `BaseQuestion` موظف هستند **این دو متد را پیادهسازی کنند** تا مطمئن شویم که **هر نوع سوال جدید قابلیت تولید نمونهها** و **اعتبارسنجی مانیفست را به طور دترمنیستیک و استاندارد دارد.**
```python base_question.py python
from typing import Dict, Any, List
from abc import ABC, abstractmethod
from core.variable_resolver import VariableResolver
class BaseQuestion(ABC):
def __init__(self, template: Dict[str, Any], variable_spec: Dict[str, Dict[str, Any]]):
self.template = template
self.variable_spec = variable_spec
@abstractmethod
def render_variants(self, num_variants: int = 1) -> List[Dict[str, Any]]:
pass
@abstractmethod
def validate_template(self) -> None:
pass
def to_dict(self) -> Dict[str, Any]:
return {
'template': self.template,
'variable_spec': self.variable_spec
}
def generate_variables(self) -> Dict[str, Any]:
resolver = VariableResolver(self.variable_spec)
return resolver.resolve()
```
</details>
<details class="grey">
<summary>
**پیاده سازی کلاس** `MultipleChoiceQuestion` **از فایل** `mcq.py`
</summary>
**کلاس** `MultipleChoiceQuestion` **یک پیادهسازی خاص از کلاس انتزاعی** `BaseQuestion` است که **مخصوص سوالات چندگزینهای** طراحی شده است. **متد** `validate_template` تضمین میکند که **قالب سوال شامل حداقل یک** `stem` و **یک لیست** `options` باشد و **در صورت نبود** یا **نادرستی** این بخشها **خطا** صادر میکند. این **متد** در **کلاسهای فرزند** باید همیشه پیادهسازی شود تا قبل از تولید نمونههای سوال، اعتبار قالب بررسی شود و **از تولید خروجی نادرست جلوگیری گردد.**
```python models/mcq.python python
from .base_question import BaseQuestion
class MultipleChoiceQuestion(BaseQuestion):
pass
```
**متد** `render_variants` **مسئول تولید یک یا چند نسخه از سوال با مقادیر متفاوت متغیرها است.** در این متد ابتدا قالب اعتبارسنجی میشود، سپس با استفاده از `VariableResolver` **مقادیر متغیرها تولید و با** `PlaceholderProcessor` در **متن** `stem` و **گزینهها** جایگذاری میشوند. **خروجی یک لیست از دیکشنریهاست که شامل** `stem`، `options` **و مقادیر متغیرهای استفاده شده است.**
به عنوان مثال، اگر **مانیفست سوال** به شکل زیر باشد:
```python models/mcq.py python
template = {
'stem': "What is {{a}} + {{b}}?",
'options': ["{{a+b}}", "{{a*b}}", "{{a-b}}"]
}
variable_spec = {'a': (1, 3), 'b': (2, 4)}
mcq = MultipleChoiceQuestion(template, variable_spec)
variants = mcq.render_variants(num_variants=2)
```
+ در این مثال، `render_variants` **دو نسخه از سوال تولید میکند،** هر نسخه دارای `stem` و **گزینههای جایگذاریشده با مقادیر واقعی متغیرها** خواهد بود.
</details>
<details class="grey">
<summary>
**پیاده سازی کلاس** `NumericRangeQuestion` **از فایل** `numeric_range.py`
</summary>
**کلاس** `NumericRangeQuestion` یک **پیادهسازی از کلاس انتزاعی** `BaseQuestion` است که برای **سوالاتی با پاسخ عددی در بازهای مشخص** طراحی شده است. **متد** `validate_template` بررسی میکند که **قالب سوال شامل حداقل یک** `stem` و **یک** `answer` باشد و **در صورت نبود یا نادرستی این بخشها خطا صادر میکند**. این متد تضمین میکند که قبل از تولید نسخههای سوال، قالب اعتبارسنجی شده و **از تولید خروجی نادرست جلوگیری شود.**
```python models/numeric_range.py python
from .base_question import BaseQuestion
class NumericRangeQuestion(BaseQuestion):
pass
```
**متد** `render_variants` **وظیفه تولید یک** یا **چند نسخه دترمنیستیک** از سوال را بر عهده دارد. در این متد ابتدا قالب اعتبارسنجی میشود، سپس با استفاده از `VariableResolver` **مقادیر متغیرها تولید و با** `PlaceholderProcessor` در **متن** `stem` و **مقدار** `answer` جایگذاری میشوند. اگر جایگذاری **مقدار** `answer` به هر دلیلی **ناموفق** باشد، مقدار **پیشفرض** `answer` استفاده میشود. **خروجی یک لیست از دیکشنریهاست که شامل** `stem`، `answer`، `tolerance` **و مقادیر متغیرهای استفاده شده است.**
به عنوان مثال، اگر **مانیفست سوال** به شکل زیر باشد:
```python models/numeric_range.py python
template = {
'stem': "Compute {{x}} + {{y}}",
'answer': "{{x + y}}",
'tolerance': 0.01
}
variable_spec = {'x': (1, 5), 'y': (2, 6)}
num_question = NumericRangeQuestion(template, variable_spec)
variants = num_question.render_variants(num_variants=2)
```
+ در این مثال، `render_variants` **دو نسخه از سوال تولید میکند،** هر نسخه **شامل** `stem` جایگذاریشده با **مقادیر واقعی متغیرها** و `answer` **محاسبهشده** با همان مقادیر خواهد بود و میتوان از آن برای تولید سوالات دترمنیستیک با پاسخ عددی و محدودهی مجاز استفاده کرد.
</details>
<details class="grey">
<summary>
**پیاده سازی کلاس** `ShortAnswerQuestion` **از فایل** `short_answer.py`
</summary>
**کلاس** `ShortAnswerQuestion` یک **پیادهسازی از کلاس انتزاعی** `BaseQuestion` است که برای **سوالاتی با پاسخ کوتاه طراحی شده** و میتواند هم پاسخهای عددی و هم متنی را مدیریت کند. **متد** `validate_template` ابتدا بررسی میکند که **قالب سوال شامل حداقل یک** `stem` و **یک** `answer` باشد و در صورت **نبود** یا **نادرستی** این بخشها، **خطا** صادر میکند. این مرحله اعتبارسنجی تضمین میکند که قبل از تولید نسخههای سوال، قالب درست باشد و **از تولید خروجی نامعتبر جلوگیری شود.**
```python models/short_answer.py python
from .base_question import BaseQuestion
class ShortAnswerQuestion(BaseQuestion):
pass
```
**متد** `render_variants` **وظیفه تولید یک یا چند نسخه دترمنیستیک از سوال را بر عهده دارد.** در این متد ابتدا قالب اعتبارسنجی میشود، سپس با استفاده از `VariableResolver` مقادیر متغیرها تولید میشوند و با `PlaceholderProcessor` در **متن** `stem` و **مقدار** `answer` جایگذاری میشوند. اگر پاسخ قابل تبدیل به عدد باشد، از `RegexGenerator.numeric_regex` **برای تولید یک الگوی عددی استفاده میشود** و **در غیر این صورت از** `RegexGenerator.text_regex` برای تولید الگوی متنی بهره گرفته میشود. خروجی یک لیست از دیکشنریهاست که شامل `stem` جایگذاریشده، `answer` محاسبهشده یا متنی، `regex` **برای بررسی پاسخ و مقادیر متغیرهای استفاده شده است.**
به عنوان مثال، اگر **مانیفست سوال** به شکل زیر باشد:
```python models/short_answer.py python
template = {
'stem': "Enter the sum of {{x}} and {{y}}",
'answer': "{{x + y}}"
}
variable_spec = {'x': (1, 5), 'y': (2, 6)}
sa_question = ShortAnswerQuestion(template, variable_spec)
variants = sa_question.render_variants(num_variants=2)
```
+ در این مثال، `render_variants` **دو نسخه از سوال تولید میکند،** هر نسخه شامل `stem` **جایگذاریشده** با مقادیر واقعی متغیرها و `answer` محاسبهشده است. اگر پاسخ عددی باشد، یک *regex* مناسب **برای بررسی پاسخهای عددی ایجاد میشود** و اگر پاسخ متنی باشد، *regex* **برای مقایسهی متن به کار میرود.** این مکانیزم اطمینان میدهد که سوالات کوتاه پاسخ هم **دترمنیستیک** و **هم قابل اعتبارسنجی باشند.**
</details>
<details class="grey">
<summary>
**پیاده سازی کلاس** `TrueFalseQuestion` **از فایل** `true_false.py`
</summary>
**کلاس** `TrueFalseQuestion` **پیادهسازی ویژهای از** `BaseQuestion` است که برای سوالات **درست/نادرست** طراحی شده و میتواند **مقادیر متغیرها** را در متن سوال جایگذاری کند. **متد** `validate_template` بررسی میکند که قالب شامل **حداقل یک** `stem` و **یک** `answer` باشد و در صورت نبود آنها **خطا** صادر میکند. این اعتبارسنجی تضمین میکند که قبل از تولید نسخههای سوال، قالب به صورت دترمنیستیک معتبر باشد و **از تولید خروجی نامعتبر جلوگیری شود.**
```python models/true_false.py python
from .base_question import BaseQuestion
class TrueFalseQuestion(BaseQuestion):
pass
```
**متد** `render_variants` **وظیفه تولید یک یا چند نسخه از سوال را بر اساس مقادیر متغیرها بر عهده دارد.** ابتدا قالب اعتبارسنجی میشود، سپس با `VariableResolver` **مقادیر متغیرها تولید میشوند** و با `PlaceholderProcessor` در **متن** `stem` جایگذاری میشوند. **پاسخ درست/نادرست** (`answer`) **به همان شکل از قالب گرفته میشود** و در خروجی قرار میگیرد. در نهایت، خروجی یک لیست از دیکشنریهاست که **شامل** `stem` جایگذاریشده، **پاسخ** و **مقادیر متغیرهاست** تا سوالات تولید شده قابل استفاده و بررسی باشند.
به عنوان مثال، اگر **مانیفست سوال** به شکل زیر باشد:
```python models/true_false.py python
template = {
'stem': "Is {{x}} greater than {{y}}?",
'answer': True
}
variable_spec = {'x': (1, 10), 'y': (1, 10)}
tf_question = TrueFalseQuestion(template, variable_spec)
variants = tf_question.render_variants(num_variants=2)
```
+ در این مثال، `render_variants` **دو نسخه از سوال تولید میکند،** هر نسخه شامل **متن سوال جایگذاریشده** با مقادیر واقعی متغیرها و **پاسخ درست/نادرست** مشخص شده در قالب است. این روش اطمینان میدهد که **سوالات درست/نادرست دترمنیستیک باشند** و **مقادیر متغیرها به درستی در متن سوال منعکس شوند.**
</details>
<details class="grey">
<summary>
**پیاده سازی کلاس** `MatchingQuestion` **از فایل** `matching.py`
</summary>
**کلاس** `MatchingQuestion` **یک پیادهسازی از** `BaseQuestion` است که برای **سوالات تطبیقی طراحی شده** و امکان جایگذاری **مقادیر متغیرها در متن سوال** و **جفتهای تطبیقی را فراهم میکند.** متد `validate_template` بررسی میکند که قالب شامل حداقل **یک** `stem` و **یک لیست** `pairs` باشد و در صورت نبود آنها **خطا** صادر میکند. این اطمینان میدهد که قالب دترمنیستیک و معتبر است و قبل از تولید نسخههای سوال **هیچ داده ناقصی وارد فرآیند تولید نمیشود.**
```python models/matching.py python
from .base_question import BaseQuestion
class MatchingQuestion(BaseQuestion):
pass
```
**متد** `render_variants` **وظیفه تولید یک یا چند نسخه از سوال تطبیقی را بر اساس مقادیر متغیرها بر عهده دارد.** ابتدا قالب اعتبارسنجی میشود، سپس با استفاده از `VariableResolver` **مقادیر متغیرها تولید میشوند و با** `PlaceholderProcessor` در **متن** `stem` و **در هر جفت** `left` و `right` جایگذاری میشوند. خروجی یک لیست از دیکشنریهاست که شامل *متن جایگذاریشده، جفتهای تطبیقی جایگذاریشده و مقادیر متغیرهاست* تا نسخههای تولید شده قابل استفاده و قابل بررسی باشند.
به عنوان مثال، اگر **مانیفست سوال** به شکل زیر باشد:
```python models/matching.py python
template = {
'stem': "Match the capitals with their countries:",
'pairs': [
{"left": "{{country1}}", "right": "{{capital1}}"},
{"left": "{{country2}}", "right": "{{capital2}}"}
]
}
variable_spec = {
'country1': ('France', 'France'),
'capital1': ('Paris', 'Paris'),
'country2': ('Germany', 'Germany'),
'capital2': ('Berlin', 'Berlin')
}
matching_question = MatchingQuestion(template, variable_spec)
variants = matching_question.render_variants(num_variants=1)
```
+ در این مثال، `render_variants` یک نسخه از سوال تولید میکند که **متن سوال** و **همه جفتها با مقادیر واقعی متغیرها جایگذاری شدهاند.** این روش تضمین میکند که سوالات تطبیقی **دترمنیستیک** باشند و تمامی *placeholderها* **به درستی با مقادیر مشخص شده جایگزین شوند.**
</details>
<details class="grey">
<summary>
**پیاده سازی کلاس** `QuestionBank` **از فایل** `question_bank.py`
</summary>
**کلاس** `QuestionBank` **مسئول مدیریت مجموعهای از قالبهای سوال است** و **وظیفه آن بارگذاری فایلهای** *JSON* **شامل سوالات مختلف** و **نگهداری آنها در یک دیکشنری داخلی است.** هر **فایل** *JSON* باید شامل **نوع سوال** (`type`)، **شناسه یکتا** (`id`)، **قالب سوال** (`template`) و **مشخصات متغیره**ا (`variables`) باشد. هنگام بارگذاری، `QuestionBank` **بر اساس نوع سوال کلاس مناسب را انتخاب میکند** و نمونهای از آن کلاس را با **قالب** و **متغیرهای** مشخصشده ایجاد میکند. قالبهای با نوع **ناشناخته** یا **بدون شناسه** نادیده گرفته میشوند و **بارگذاری فقط شامل فایلهای** *JSON* معتبر است.
```python models/question_bank.py python
class QuestionBank:
pass
```
پس از بارگذاری، **تمامی سوالات در دیکشنری** `self.templates` نگهداری میشوند تا **دسترسی سریع** به آنها ممکن باشد. **متد** `get` **امکان دسترسی به یک سوال مشخص بر اساس شناسه آن را فراهم میکند** و در صورت نبود شناسه موردنظر، **مقدار** `None` برمیگرداند. این ساختار اطمینان میدهد که تمام قالبهای معتبر با نوع مشخص و شناسه یکتا در بانک سوال قابل دسترس باشند و هرگونه خطا یا داده ناقص هنگام بارگذاری مدیریت شود.
به عنوان مثال، فرض کنید مجموعهای از فایلهای *JSON* در مسیر `questions/` داریم و میخواهیم سوال با شناسه `Q101` را بارگذاری کنیم:
```python models/question_bank.py py
qb = QuestionBank("questions/")
question = qb.get("Q101")
if question:
variants = question.render_variants(num_variants=3)
for v in variants:
print(v['stem'])
```
+ در این مثال، `QuestionBank` **ابتدا فایلها را بررسی میکند، قالبها و متغیرها را ایجاد میکند،** سپس با **فراخوانی** `get` سوال مشخص را برمیگرداند و امکان تولید نسخههای متعدد از هر سوال با مقادیر متغیر مشخص فراهم باشد.
</details>
</details>
# **زیرمسئلهها**
سیستم داوری برای این سوال به **زیرمسئلههای** زیر برای نمرهدهی تقسیمبندی شده است که میتوانید **امتیاز** مربوط به هر کدام را در جدول زیر مشاهده کنید. **زیرمسئلههای** این جدول **ابتدا بر اساس اولویت و پیشنیازی پیادهسازی** و سپس **بر اساس امتیاز** آنها مرتبسازی شدهاند. **لذا پیشنهاد میشود در پیادهسازی از زیرمسئلهی ابتدایی آغاز کنید.**
| **زیرمسئله** | **امتیاز** |
| ----------------------------- | ------ |
| **پیادهسازی بخش** `core` | `203` |
| **پیادهسازی بخش** `models` | `147` |
# **آنچه باید آپلود کنید**
+ **توجه**: پس از پیادهسازی موارد خواسته شده، **کل فایلهای پروژه** را زیپ کرده و ارسال کنید.
+ **توجه**: شما مجاز به **افزودن فایل جدیدی** در این ساختار **نیستید** و تنها باید تغییرات را در فایلهای موجود اعمال کنید.
+ **توجه**: که نام فایل _Zip_ اهمیتی **ندارد**.