ADFMentor 0.3.0__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.
@@ -0,0 +1,370 @@
1
+ Metadata-Version: 2.4
2
+ Name: ADFMentor
3
+ Version: 0.3.0
4
+ Summary: Parse Azure Data Factory project files and evaluate them using AI models.
5
+ Author-email: Qobiljon Xayrullayev <qobiljonkhayrullayev@gmail.com>
6
+ License: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: google-genai>=1.0.0
19
+ Requires-Dist: python-dotenv>=1.0.0
20
+ Dynamic: license-file
21
+
22
+ ADFMentor
23
+ =========
24
+
25
+ A Python package for parsing Azure Data Factory (ADF) project files and evaluating them using AI models like Google Gemini.
26
+
27
+ ## Features
28
+
29
+ - 🏗️ **ADF Processing**: Parse ADF pipeline, dataset, linked service, trigger, and dataflow JSON files
30
+ - **Pipeline** — Parse ADF pipeline JSON files (activities, dependencies, parameters)
31
+ - **Written** — Evaluate text-based answers about ADF concepts
32
+ - 📝 **Detailed Reports**: Generate comprehensive grading reports from ADF project structures
33
+ - 📦 **ZIP Support**: Automatically handles ZIP file submissions — no manual extraction needed
34
+ - 🗂️ **Flexible Inputs**: Accepts a directory, a ZIP, or a single file (`.json`, `.txt`)
35
+ - 🔍 **Auto File Discovery**: Locates ADF resource folders (`pipeline/`, `dataset/`, `linkedService/`, etc.)
36
+ - ⚠️ **Graceful Missing-File Scoring**: Missing required files yield a `0` score and clear feedback
37
+ - 🧩 **Lesson Question Parser**: Parse structured lesson text into `pipeline` and `text` question blocks
38
+ - 🔧 **Easy Integration**: Simple API for evaluating student assignments and projects
39
+
40
+ ## Installation
41
+
42
+ ### From Source
43
+
44
+ ```bash
45
+ git clone https://github.com/yourusername/ADFMentor.git
46
+ cd ADFMentor
47
+ pip install -e .
48
+ ```
49
+
50
+ ### Using pip (when published)
51
+
52
+ ```bash
53
+ pip install ADFMentor
54
+ ```
55
+
56
+ ## Quick Start
57
+
58
+ ```python
59
+ from ADFmentor import ADFMentor
60
+
61
+ # Initialize with your Gemini API key
62
+ mentor = ADFMentor(api_key="your-api-key")
63
+
64
+ # Evaluate a full submission (pipeline + written)
65
+ # Works with directories, ZIP files, or single files (.json/.txt)
66
+ questions = """
67
+ PIPELINE: Create an ADF pipeline to copy data from Blob Storage to SQL Database
68
+ TEXT: Explain your pipeline design choices
69
+ """
70
+
71
+ prompts = {
72
+ "pipeline": "Evaluate pipeline structure, activities, and best practices",
73
+ "text": "Evaluate clarity and reasoning",
74
+ }
75
+
76
+ result = mentor.evaluate_all(
77
+ answer_path="path/to/submission/", # or "submission.zip"
78
+ questions=questions,
79
+ prompts=prompts,
80
+ )
81
+
82
+ print(f"Score: {result['score']}/100")
83
+ print(f"Feedback:\n{result['feedback']}")
84
+ ```
85
+
86
+ ## Package Structure
87
+
88
+ ```
89
+ ADFMentor/
90
+ ├── __init__.py # Main package entry point
91
+ ├── core.py # ADFMentor class with evaluation methods
92
+ ├── models/ # AI model wrappers
93
+ │ ├── __init__.py
94
+ │ ├── model.py # Abstract base model
95
+ │ └── gemini.py # Google Gemini implementation
96
+ └── utils/ # Utility functions
97
+ ├── __init__.py
98
+ ├── processor.py # ADF JSON parsing and report generation
99
+ ├── checker.py # File discovery helpers
100
+ ├── extractor.py # ZIP extraction utilities
101
+ └── question_parser.py # Lesson question parser helpers
102
+ ```
103
+
104
+ ## Core Components
105
+
106
+ ### ADFMentor Class
107
+
108
+ The main class provides a single evaluation method:
109
+
110
+ - **`evaluate_all(answer_path, questions, prompts)`**: Evaluates pipeline structure and written answers together and returns an overall score and combined feedback
111
+
112
+ Notes:
113
+ - `answer_path` can be a directory, ZIP file, or a single submission file (`.json`, `.txt`).
114
+ - `questions` and `prompts` must include `pipeline` and `text` keys. A section is skipped if its question is set to `None`.
115
+
116
+ ### ADF Processor
117
+
118
+ `ADFMentor.utils.processor` provides functions for processing ADF projects:
119
+
120
+ #### `parse_adf_json(json_path)`
121
+ Reads and parses a single ADF resource JSON file.
122
+
123
+ #### `discover_adf_resources(directory)`
124
+ Scans a directory for ADF resource folders:
125
+ - `pipeline/` — Pipeline definitions
126
+ - `dataset/` — Dataset definitions
127
+ - `linkedService/` — Linked service definitions
128
+ - `trigger/` — Trigger definitions
129
+ - `dataflow/` — Dataflow definitions
130
+
131
+ #### `extract_grading_info(resources)`
132
+ Extracts key elements for grading:
133
+ - Pipelines: activities, dependencies, parameters, variables
134
+ - Datasets: type, linked service reference, schema, location
135
+ - Linked Services: type, connection details (sanitized)
136
+ - Triggers: type, schedule, pipeline references
137
+ - Dataflows: sources, sinks, transformations
138
+
139
+ #### `generate_grading_report(grading_info)`
140
+ Formats extracted information into a readable text report.
141
+
142
+ #### `analyze_adf(adf_path)`
143
+ Convenience function that chains all steps above.
144
+
145
+ ### AI Models
146
+
147
+ #### Gemini Model
148
+ `ADFMentor.models.gemini.Gemini`
149
+
150
+ ```python
151
+ from ADFmentor.models import Gemini
152
+
153
+ model = Gemini(api_key="your-api-key", model_name="gemini-2.0-flash-exp")
154
+
155
+ # Evaluate text-based answers
156
+ result = model.evaluate(
157
+ question="What are ADF linked services?",
158
+ answer="Linked services are connection strings...",
159
+ prompt="Evaluate for accuracy and completeness"
160
+ )
161
+ ```
162
+
163
+ **Response Format:**
164
+ ```json
165
+ {
166
+ "score": 85,
167
+ "feedback": "Strong implementation with minor issues..."
168
+ }
169
+ ```
170
+
171
+ ## Configuration
172
+
173
+ ### API Key Setup
174
+
175
+ Create a `.env` file in your project root:
176
+
177
+ ```env
178
+ API_KEY=your_gemini_api_key_here
179
+ ```
180
+
181
+ Load it in your code:
182
+
183
+ ```python
184
+ from dotenv import load_dotenv
185
+ import os
186
+
187
+ load_dotenv()
188
+ api_key = os.getenv("API_KEY")
189
+ ```
190
+
191
+ ## Detailed Usage Examples
192
+
193
+ ### 1. Analyze an ADF Project
194
+
195
+ ```python
196
+ from ADFmentor.utils import analyze_adf
197
+
198
+ # Generate a detailed report from an ADF project directory
199
+ report = analyze_adf("path/to/adf-project/")
200
+ print(report)
201
+ ```
202
+
203
+ **Sample Output:**
204
+ ```
205
+ ============================================================
206
+ AZURE DATA FACTORY PROJECT REPORT
207
+ ============================================================
208
+
209
+ PIPELINES:
210
+ - CopyBlobToSQL
211
+ Parameters: inputPath, outputTable
212
+ Activities (3):
213
+ • LookupSource (type: Lookup)
214
+ • CopyData (type: Copy)
215
+ depends on: LookupSource [Succeeded]
216
+ source: BlobSource
217
+ sink: SqlSink
218
+ • StoredProcedure (type: SqlServerStoredProcedure)
219
+ depends on: CopyData [Succeeded]
220
+
221
+ DATASETS:
222
+ - BlobInput (type: DelimitedText)
223
+ linked service: AzureBlobStorage
224
+ location: type: AzureBlobStorageLocation, folder: input
225
+ - SqlOutput (type: AzureSqlTable)
226
+ linked service: AzureSqlDatabase
227
+ table: dbo.SalesData
228
+
229
+ LINKED SERVICES:
230
+ - AzureBlobStorage (type: AzureBlobStorage)
231
+ - AzureSqlDatabase (type: AzureSqlDatabase)
232
+
233
+ TRIGGERS:
234
+ - DailyTrigger (type: ScheduleTrigger)
235
+ schedule: every 1 Day
236
+ pipelines: CopyBlobToSQL
237
+
238
+ DATAFLOWS:
239
+ none
240
+
241
+ SUMMARY:
242
+ - total_pipelines: 1
243
+ - total_activities: 3
244
+ - total_datasets: 2
245
+ - total_linked_services: 2
246
+ - total_triggers: 1
247
+ - total_dataflows: 0
248
+ ```
249
+
250
+ ### 2. Complete Evaluation Pipeline
251
+
252
+ ```python
253
+ from ADFmentor import ADFMentor
254
+
255
+ mentor = ADFMentor(api_key="your-api-key")
256
+
257
+ # Define questions and prompts for each evaluation type
258
+ questions = {
259
+ "pipeline": "Create a pipeline to copy data from Blob to SQL with error handling",
260
+ "text": "Explain your pipeline design choices"
261
+ }
262
+
263
+ prompts = {
264
+ "pipeline": "Evaluate pipeline structure, activities, error handling, and best practices",
265
+ "text": "Evaluate clarity, justification, and understanding"
266
+ }
267
+
268
+ # Evaluate all aspects
269
+ result = mentor.evaluate_all(
270
+ answer_path="path/to/student/submission/",
271
+ questions=questions,
272
+ prompts=prompts
273
+ )
274
+
275
+ print(f"Overall Score: {result['score']}/100")
276
+ print(f"Feedback:\n{result['feedback']}")
277
+ ```
278
+
279
+ ### 3. Parse Lesson Questions
280
+
281
+ If your lesson content uses codes like `"TEXT001"`, `"PIPELINE002"`, you can parse it into question blocks:
282
+
283
+ ```python
284
+ from ADFmentor.utils.question_parser import parse_lesson_questions
285
+
286
+ lesson_text = """
287
+ "TEXT001"
288
+ 1. Explain the purpose of linked services in ADF.
289
+
290
+ "PIPELINE002"
291
+ 2. Create a pipeline to copy data from Blob Storage to SQL Database.
292
+ """
293
+
294
+ questions = parse_lesson_questions(lesson_text)
295
+ # -> {"text": "1. ...", "pipeline": "2. ..."}
296
+
297
+ # Map to the evaluate_all() schema
298
+ questions = {
299
+ "pipeline": questions["pipeline"],
300
+ "text": questions["text"],
301
+ }
302
+ ```
303
+
304
+ ### 4. Skipping an Evaluation Section
305
+
306
+ To skip a section, set its question to `None` (the key must still exist):
307
+
308
+ ```python
309
+ questions = {
310
+ "pipeline": "Create a copy pipeline with parameterized paths",
311
+ "text": None,
312
+ }
313
+
314
+ prompts = {
315
+ "pipeline": "Evaluate pipeline structure and best practices",
316
+ "text": "Evaluate clarity, justification, and understanding",
317
+ }
318
+ ```
319
+
320
+ ## ADF Project Structure
321
+
322
+ ADFMentor expects submissions to follow the standard ADF project structure:
323
+
324
+ ```
325
+ adf-project/
326
+ ├── pipeline/ # Pipeline JSON definitions
327
+ │ └── CopyPipeline.json
328
+ ├── dataset/ # Dataset JSON definitions
329
+ │ ├── BlobInput.json
330
+ │ └── SqlOutput.json
331
+ ├── linkedService/ # Linked service definitions
332
+ │ ├── BlobStorage.json
333
+ │ └── SqlDatabase.json
334
+ ├── trigger/ # Trigger definitions
335
+ │ └── DailyTrigger.json
336
+ └── dataflow/ # Dataflow definitions (optional)
337
+ ```
338
+
339
+ Each JSON file follows the standard ADF resource format with `name`, `type`, and `properties` fields.
340
+
341
+ ## Development
342
+
343
+ ### Running Tests
344
+
345
+ ```bash
346
+ # Run integration tests
347
+ python tests/test.py
348
+ ```
349
+
350
+ ### Project Dependencies
351
+
352
+ Core:
353
+ - `google-genai>=1.0.0` - Google Gemini API client
354
+ - `python-dotenv>=1.0.0` - Environment variable management
355
+
356
+ Optional:
357
+ - `google-cloud-aiplatform>=1.0.0` - For Vertex AI support
358
+
359
+ ## Requirements
360
+
361
+ - Python 3.9 or higher
362
+ - Google Gemini API key (get one at [Google AI Studio](https://makersuite.google.com/app/apikey))
363
+
364
+ ## Contributing
365
+
366
+ Contributions are welcome! Please feel free to submit a Pull Request.
367
+
368
+ ## License
369
+
370
+ MIT License - see LICENSE file for details
@@ -0,0 +1,20 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ ADFMentor.egg-info/PKG-INFO
5
+ ADFMentor.egg-info/SOURCES.txt
6
+ ADFMentor.egg-info/dependency_links.txt
7
+ ADFMentor.egg-info/requires.txt
8
+ ADFMentor.egg-info/top_level.txt
9
+ ADFmentor/__init__.py
10
+ ADFmentor/core.py
11
+ ADFmentor/models/__init__.py
12
+ ADFmentor/models/gemini.py
13
+ ADFmentor/models/model.py
14
+ ADFmentor/utils/__init__.py
15
+ ADFmentor/utils/checker.py
16
+ ADFmentor/utils/extractor.py
17
+ ADFmentor/utils/path_handler.py
18
+ ADFmentor/utils/processor.py
19
+ ADFmentor/utils/question_parser.py
20
+ tests/test.py
@@ -0,0 +1,2 @@
1
+ google-genai>=1.0.0
2
+ python-dotenv>=1.0.0
@@ -0,0 +1,3 @@
1
+ ADFmentor
2
+ dist
3
+ tests
@@ -0,0 +1,3 @@
1
+ from .core import ADFMentor
2
+
3
+ __all__ = ['ADFMentor']
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Dict, Optional
5
+
6
+ from .models.gemini import Gemini
7
+ from ADFmentor.utils.processor import analyze_adf
8
+ from ADFmentor.utils.checker import get_file_by_type, is_adf_project
9
+ from .utils.path_handler import prepare_answer_path
10
+ from .utils.question_parser import parse_lesson_questions
11
+
12
+
13
+ class ADFMentor:
14
+ def __init__(self, api_key: str, model_name: str = "gemini-2.0-flash-exp", model: Optional[Gemini] = None):
15
+ if model is None:
16
+ model = Gemini(api_key=api_key, model_name=model_name)
17
+ self.model = model
18
+
19
+ def _evaluate_pipeline_from_path(self, working_path: str, question: Optional[str], prompt: str) -> Optional[
20
+ Dict[str, Any]]:
21
+ if question is None:
22
+ return None
23
+
24
+ path = Path(working_path)
25
+
26
+ if path.is_file() and path.suffix.lower() == ".json":
27
+ adf_path = path
28
+ elif path.is_dir():
29
+ if is_adf_project(str(path)):
30
+ adf_path = path
31
+ else:
32
+ json_file = get_file_by_type(working_path, ".json")
33
+ if not json_file:
34
+ return {
35
+ "score": 0,
36
+ "feedback": (
37
+ "Unable to evaluate submission.\n\n"
38
+ "The assignment includes ADF pipeline questions, but no ADF project files (JSON) were found. "
39
+ "Please ensure your submission includes all required components and resubmit."
40
+ ),
41
+ }
42
+ # Use the whole directory so analyze_adf can rglob all .json files
43
+ adf_path = path
44
+ else:
45
+ return {
46
+ "score": 0,
47
+ "feedback": (
48
+ "Unable to evaluate submission.\n\n"
49
+ "The assignment includes ADF pipeline questions, but no ADF project files were found. "
50
+ "Please ensure your submission includes all required components and resubmit."
51
+ ),
52
+ }
53
+
54
+ return self.model.evaluate(
55
+ question=question,
56
+ answer=analyze_adf(str(adf_path)),
57
+ prompt=prompt,
58
+ )
59
+
60
+ def _evaluate_write_from_path(self, working_path: str, question: Optional[str], prompt: str) -> Optional[
61
+ Dict[str, Any]]:
62
+ if question is None:
63
+ return None
64
+
65
+ path = Path(working_path)
66
+
67
+ if path.is_file() and path.suffix.lower() == ".txt":
68
+ txt_path = path
69
+ else:
70
+ txt_file = get_file_by_type(working_path, ".txt")
71
+ if not txt_file:
72
+ return {
73
+ "score": 0,
74
+ "feedback": (
75
+ "Unable to evaluate submission.\n\n"
76
+ "The assignment includes written type questions, but no written type response (TXT file) was found. "
77
+ "Please ensure your submission includes all required components and resubmit."
78
+ ),
79
+ }
80
+ txt_path = path / txt_file
81
+
82
+ text_answer = txt_path.read_text(encoding="utf-8")
83
+
84
+ return self.model.evaluate(
85
+ question=question,
86
+ answer=text_answer,
87
+ prompt=prompt,
88
+ )
89
+
90
+ def evaluate_all(self, answer_path: str, questions: str, prompts: Dict[str, str]) -> Dict[str, Any]:
91
+ working_path = prepare_answer_path(answer_path)
92
+
93
+ questions = parse_lesson_questions(questions)
94
+
95
+ results = {
96
+ "pipeline": self._evaluate_pipeline_from_path(working_path, questions.get("pipeline"), prompts.get("pipeline")),
97
+ "text": self._evaluate_write_from_path(working_path, questions.get("text"), prompts.get("text")),
98
+ }
99
+
100
+ scores = []
101
+ feedback_parts = []
102
+
103
+ for key, value in results.items():
104
+ if value is not None:
105
+ scores.append(value['score'])
106
+ feedback_parts.append(
107
+ f"{'=' * 70}\n"
108
+ f"{key.upper()} EVALUATION\n"
109
+ f"{'=' * 70}\n"
110
+ f"Score: {value['score']}/100\n\n"
111
+ f"{value['feedback']}\n"
112
+ )
113
+
114
+ avg_score = sum(scores) / len(scores) if scores else 0
115
+
116
+ summary = {
117
+ 'score': round(avg_score, 2),
118
+ 'feedback': '\n'.join(feedback_parts) if feedback_parts else "No evaluations completed."
119
+ }
120
+
121
+ return summary
@@ -0,0 +1,9 @@
1
+ """Models package for ADFMentor.
2
+
3
+ This package contains model wrappers for AI evaluation services.
4
+ """
5
+
6
+ from .model import Model
7
+ from .gemini import Gemini
8
+
9
+ __all__ = ["Model", "Gemini"]
@@ -0,0 +1,80 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Any, Dict, Union
4
+ from google import genai
5
+ from google.genai import types
6
+ from .model import Model, build_content
7
+
8
+
9
+
10
+ class Gemini(Model):
11
+ """Google Gemini model wrapper for ADFMentor evaluations.
12
+
13
+ Uses the Google Gemini API to evaluate student submissions with
14
+ structured JSON responses containing scores and feedback.
15
+
16
+ Attributes:
17
+ client: Google Gemini API client
18
+ model_name: Name of the Gemini model to use
19
+ response_schema: JSON schema for structured responses
20
+ """
21
+
22
+ def __init__(self, api_key: str, model_name: str = "gemini-2.0-flash-exp"):
23
+ """Initialize the Gemini model.
24
+
25
+ Args:
26
+ api_key: Your Google Gemini API key
27
+ model_name: Model to use (default: gemini-2.0-flash-exp)
28
+ """
29
+ super().__init__()
30
+ self.client = genai.Client(api_key=api_key)
31
+ self.model_name = model_name
32
+
33
+ self.response_schema = types.GenerateContentConfig(
34
+ response_mime_type="application/json",
35
+ response_schema={
36
+ "type": "object",
37
+ "properties": {
38
+ "score": {
39
+ "type": "number",
40
+ "description": "The numerical score for the evaluation"
41
+ },
42
+ "feedback": {
43
+ "type": "string",
44
+ "description": "Detailed feedback explaining the score"
45
+ }
46
+ },
47
+ "required": ["score", "feedback"]
48
+ }
49
+ )
50
+
51
+ def evaluate(self, question: str, answer: str, prompt: str) -> Dict[str, Any]:
52
+ """Evaluate a text-based answer.
53
+
54
+ Args:
55
+ question: The assignment question
56
+ answer: The student's answer
57
+ prompt: Evaluation criteria and instructions
58
+
59
+ Returns:
60
+ Dictionary with 'score' (int, 0-100) and 'feedback' (str)
61
+
62
+ Raises:
63
+ ValueError: If the model doesn't return valid JSON or missing fields
64
+ """
65
+ response = self.client.models.generate_content(
66
+ model=self.model_name,
67
+ contents=build_content(question, answer, prompt),
68
+ config=self.response_schema,
69
+ )
70
+
71
+ text = (response.text or "").strip()
72
+
73
+ try:
74
+ result = json.loads(text)
75
+
76
+ if "score" not in result or "feedback" not in result:
77
+ raise ValueError(f"Missing required fields (score, feedback) in response: {result}")
78
+ return result
79
+ except json.JSONDecodeError as e:
80
+ raise ValueError(f"Model did not return valid JSON.\nRaw output:\n{text}") from e
@@ -0,0 +1,63 @@
1
+ """Abstract base model for ADFMentor evaluators."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, Dict
5
+
6
+
7
+ def build_content(question: str, answer: str, prompt: str) -> str:
8
+ """Build the evaluation prompt for text-based evaluations.
9
+
10
+ Args:
11
+ question: The assignment question
12
+ answer: The student's answer
13
+ prompt: Evaluation instructions
14
+
15
+ Returns:
16
+ Formatted prompt string
17
+ """
18
+ return f"""
19
+ Instruction:
20
+ {prompt.strip()}
21
+
22
+ Question:
23
+ {question.strip()}
24
+
25
+ Answer:
26
+ {answer.strip()}
27
+
28
+ Return ONLY valid JSON in the following format.
29
+ DO NOT add explanations, markdown, or extra text.
30
+ DO NOT wrap in ```.
31
+
32
+ JSON schema:
33
+ {{
34
+ "score": number (0-100),
35
+ "feedback": string
36
+ }}
37
+ """.strip()
38
+
39
+
40
+ class Model(ABC):
41
+ """Abstract base class for AI model evaluators.
42
+
43
+ All model implementations must inherit from this class and implement
44
+ the evaluate method.
45
+ """
46
+
47
+ def __init__(self):
48
+ """Initialize the model."""
49
+ pass
50
+
51
+ @abstractmethod
52
+ def evaluate(self, question: str, answer: str, prompt: str) -> Dict[str, Any]:
53
+ """Evaluate an answer to a question using the AI model.
54
+
55
+ Args:
56
+ question: The question or assignment prompt
57
+ answer: The student's answer or solution
58
+ prompt: Evaluation criteria and instructions for the model
59
+
60
+ Returns:
61
+ Dictionary with 'score' (0-100) and 'feedback' (string)
62
+ """
63
+ pass
@@ -0,0 +1,20 @@
1
+ """Utilities package for ADFMentor.
2
+
3
+ This package contains utility functions for processing Azure Data Factory
4
+ files and extracting metadata.
5
+ """
6
+
7
+ from .processor import analyze_adf, parse_adf_json, extract_grading_info, generate_grading_report, discover_adf_resources
8
+ from .checker import get_file_by_type, is_adf_project
9
+ from .extractor import extract_zip_to_temp
10
+
11
+ __all__ = [
12
+ "analyze_adf",
13
+ "parse_adf_json",
14
+ "discover_adf_resources",
15
+ "extract_grading_info",
16
+ "generate_grading_report",
17
+ "get_file_by_type",
18
+ "is_adf_project",
19
+ "extract_zip_to_temp",
20
+ ]