File size: 9,190 Bytes
c5292d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
"""Task template management and parametrization."""
import csv
import hashlib
import random
from datetime import datetime
from pathlib import Path
from typing import Any

import yaml
from jinja2 import Template

from shared.config import settings
from shared.logger import setup_logger
from shared.utils import encode_data_uri, generate_task_id

logger = setup_logger(__name__)


class TaskTemplate:
    """Single task template with parametrization."""

    def __init__(self, data: dict[str, Any]) -> None:
        """Initialize task template.

        Args:
            data: Template data from YAML
        """
        self.id = data["id"]
        self.brief = data["brief"]
        self.attachments = data.get("attachments", [])
        self.checks = data.get("checks", [])
        self.round2 = data.get("round2", [])

    def parametrize(self, seed: str) -> dict[str, Any]:
        """Parametrize template with seed.

        Args:
            seed: Seed string for randomization

        Returns:
            Parametrized task data
        """
        # Set random seed for reproducibility
        random.seed(seed)

        # Generate data based on template
        context = self._generate_context(seed)

        # Parametrize brief
        brief = Template(self.brief).render(**context)

        # Parametrize attachments
        attachments = []
        for att_template in self.attachments:
            att_name = Template(att_template["name"]).render(**context)
            att_url_template = att_template["url"]

            # Generate attachment content
            if "data:text/csv" in att_url_template:
                content = self._generate_csv_data(seed)
                att_url = encode_data_uri("text/csv", content.encode())
            elif "data:text/markdown" in att_url_template:
                content = self._generate_markdown_data(seed)
                att_url = encode_data_uri("text/markdown", content.encode())
            elif "data:application/json" in att_url_template:
                content = self._generate_json_data(seed)
                att_url = encode_data_uri("application/json", content.encode())
            else:
                att_url = Template(att_url_template).render(**context)

            attachments.append({"name": att_name, "url": att_url})

        # Parametrize checks
        checks = [Template(check).render(**context) for check in self.checks]

        return {
            "brief": brief,
            "attachments": attachments,
            "checks": checks,
            "context": context,
        }

    def parametrize_round2(self, seed: str, round2_index: int = 0) -> dict[str, Any]:
        """Parametrize round 2 task.

        Args:
            seed: Seed string
            round2_index: Index of round2 option to use

        Returns:
            Parametrized round 2 task data
        """
        if not self.round2 or round2_index >= len(self.round2):
            raise ValueError(f"No round2 option at index {round2_index}")

        round2_task = self.round2[round2_index]
        random.seed(seed + f"-round2-{round2_index}")

        context = self._generate_context(seed)

        brief = Template(round2_task["brief"]).render(**context)
        checks = [Template(check).render(**context) for check in round2_task.get("checks", [])]

        attachments = []
        for att_template in round2_task.get("attachments", []):
            att_name = Template(att_template["name"]).render(**context)
            att_url = Template(att_template["url"]).render(**context)
            attachments.append({"name": att_name, "url": att_url})

        return {
            "brief": brief,
            "attachments": attachments,
            "checks": checks,
            "context": context,
        }

    def _generate_context(self, seed: str) -> dict[str, Any]:
        """Generate context variables for template.

        Args:
            seed: Seed string

        Returns:
            Context dictionary
        """
        # Generate deterministic values based on seed
        hash_val = int(hashlib.md5(seed.encode()).hexdigest(), 16)

        return {
            "seed": seed,
            "hash": hash_val % 10000,
            "result": (hash_val % 9000) + 1000,  # Result between 1000-9999
        }

    def _generate_csv_data(self, seed: str) -> str:
        """Generate sample CSV data.

        Args:
            seed: Seed for randomization

        Returns:
            CSV string
        """
        random.seed(seed)

        products = ["Widget", "Gadget", "Doohickey", "Thingamajig", "Gizmo"]
        regions = ["North", "South", "East", "West", "Central"]

        lines = ["product,region,sales"]
        for _ in range(random.randint(10, 20)):
            product = random.choice(products)
            region = random.choice(regions)
            sales = round(random.uniform(100, 1000), 2)
            lines.append(f"{product},{region},{sales}")

        return "\n".join(lines)

    def _generate_markdown_data(self, seed: str) -> str:
        """Generate sample Markdown data.

        Args:
            seed: Seed for randomization

        Returns:
            Markdown string
        """
        random.seed(seed)

        content = f"""# Sample Document {seed[:8]}

## Introduction

This is a sample markdown document generated for testing.

## Code Example

```python
def hello():
    print("Hello, world!")
```

## List

- Item 1
- Item 2
- Item 3

## Conclusion

This document contains {random.randint(50, 150)} words.
"""
        return content

    def _generate_json_data(self, seed: str) -> str:
        """Generate sample JSON data.

        Args:
            seed: Seed for randomization

        Returns:
            JSON string
        """
        import json

        random.seed(seed)

        data = {
            "USD": 1.0,
            "EUR": round(random.uniform(0.8, 0.95), 2),
            "GBP": round(random.uniform(0.7, 0.85), 2),
            "JPY": round(random.uniform(110, 140), 2),
        }

        return json.dumps(data, indent=2)


class TaskTemplateManager:
    """Manage task templates."""

    def __init__(self, templates_dir: Path | None = None) -> None:
        """Initialize template manager.

        Args:
            templates_dir: Directory containing template YAML files
        """
        self.templates_dir = templates_dir or settings.task_templates_dir
        self.templates: dict[str, TaskTemplate] = {}
        self.load_templates()

    def load_templates(self) -> None:
        """Load all templates from directory."""
        if not self.templates_dir.exists():
            logger.warning(f"Templates directory not found: {self.templates_dir}")
            return

        for yaml_file in self.templates_dir.glob("*.yaml"):
            try:
                with open(yaml_file) as f:
                    data = yaml.safe_load(f)
                    template = TaskTemplate(data)
                    self.templates[template.id] = template
                    logger.info(f"Loaded template: {template.id}")
            except Exception as e:
                logger.error(f"Failed to load template {yaml_file}: {e}")

        logger.info(f"Loaded {len(self.templates)} templates")

    def get_template(self, template_id: str) -> TaskTemplate:
        """Get template by ID.

        Args:
            template_id: Template identifier

        Returns:
            Task template

        Raises:
            KeyError: If template not found
        """
        if template_id not in self.templates:
            raise KeyError(f"Template not found: {template_id}")
        return self.templates[template_id]

    def get_random_template(self) -> TaskTemplate:
        """Get random template.

        Returns:
            Random task template
        """
        if not self.templates:
            raise ValueError("No templates available")
        return random.choice(list(self.templates.values()))

    def generate_task(
        self, email: str, template_id: str | None = None, round_num: int = 1
    ) -> dict[str, Any]:
        """Generate a complete task from template.

        Args:
            email: Student email
            template_id: Template ID (random if None)
            round_num: Round number

        Returns:
            Complete task data
        """
        # Get template
        if template_id:
            template = self.get_template(template_id)
        else:
            template = self.get_random_template()

        # Generate seed
        now = datetime.utcnow()
        seed = f"{email}-{now.strftime('%Y-%m-%d-%H')}"

        # Parametrize
        if round_num == 1:
            task_data = template.parametrize(seed)
        else:
            # For round 2, pick random round2 option
            round2_index = random.randint(0, len(template.round2) - 1) if template.round2 else 0
            task_data = template.parametrize_round2(seed, round2_index)

        # Generate task ID
        task_id = generate_task_id(template.id, task_data["brief"], task_data["attachments"])

        return {
            "template_id": template.id,
            "task_id": task_id,
            **task_data,
        }