lumen-resources 0.2.0__py3-none-any.whl

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,257 @@
1
+ """
2
+ Validator for model_info.json files using JSON Schema and Pydantic.
3
+ """
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from jsonschema import Draft7Validator
10
+ from pydantic import ValidationError
11
+
12
+ from .model_info import ModelInfo
13
+
14
+
15
+ class ModelInfoValidator:
16
+ """Validator for model_info.json files.
17
+
18
+ Provides comprehensive validation for model information JSON files using
19
+ both JSON Schema and Pydantic models. Ensures model metadata integrity
20
+ and compliance with the Lumen model specification.
21
+
22
+ Attributes:
23
+ schema: Loaded JSON Schema for validation.
24
+ validator: Draft7Validator instance for JSON Schema validation.
25
+
26
+ Example:
27
+ >>> validator = ModelInfoValidator()
28
+ >>> is_valid, errors = validator.validate_file("model_info.json")
29
+ >>> if not is_valid:
30
+ ... for error in errors:
31
+ ... print(f"Validation error: {error}")
32
+ """
33
+
34
+ def __init__(self, schema_path: str | Path | None = None):
35
+ """Initialize validator with JSON schema.
36
+
37
+ Args:
38
+ schema_path: Path to model_info-schema.json file. If None,
39
+ uses the bundled schema from docs/schemas/model_info-schema.json.
40
+
41
+ Raises:
42
+ FileNotFoundError: If the schema file is not found.
43
+ json.JSONDecodeError: If the schema file contains invalid JSON.
44
+
45
+ Example:
46
+ >>> validator = ModelInfoValidator() # Uses bundled schema
47
+ >>> validator = ModelInfoValidator(Path("custom-schema.json"))
48
+ """
49
+ if schema_path is None:
50
+ schema_path = Path(__file__).parent / "schemas" / "model_info-schema.json"
51
+ else:
52
+ schema_path = Path(schema_path)
53
+
54
+ if not schema_path.exists():
55
+ raise FileNotFoundError(f"Schema file not found: {schema_path}")
56
+
57
+ with open(schema_path, "r", encoding="utf-8") as f:
58
+ self.schema: dict[str, Any] = json.load(f)
59
+
60
+ self.validator = Draft7Validator(self.schema)
61
+
62
+ def validate_file(
63
+ self, path: str | Path, strict: bool = True
64
+ ) -> tuple[bool, list[str]]:
65
+ """Validate a model_info.json file.
66
+
67
+ Performs validation of model information JSON files using either
68
+ JSON Schema validation (flexible) or Pydantic validation (strict).
69
+
70
+ Args:
71
+ path: Path to model_info.json file.
72
+ strict: If True, use Pydantic validation with custom validators.
73
+ If False, use JSON Schema validation only.
74
+
75
+ Returns:
76
+ Tuple of (is_valid, error_messages) where is_valid indicates
77
+ if the file passes validation, and error_messages contains
78
+ detailed validation error messages.
79
+
80
+ Example:
81
+ >>> validator = ModelInfoValidator()
82
+ >>> is_valid, errors = validator.validate_file("model_info.json", strict=True)
83
+ >>> if not is_valid:
84
+ ... for error in errors:
85
+ ... print(f"Error: {error}")
86
+ """
87
+ path = Path(path)
88
+ if not path.exists():
89
+ return False, [f"File not found: {path}"]
90
+
91
+ try:
92
+ with open(path, "r", encoding="utf-8") as f:
93
+ data = json.load(f)
94
+ except json.JSONDecodeError as e:
95
+ return False, [f"Invalid JSON: {e}"]
96
+ except Exception as e:
97
+ return False, [f"Error reading file: {e}"]
98
+
99
+ if strict:
100
+ return self._validate_with_pydantic(data)
101
+ else:
102
+ return self._validate_with_jsonschema(data)
103
+
104
+ def _validate_with_jsonschema(self, data: dict[str, Any]) -> tuple[bool, list[str]]:
105
+ """Validate model info data using JSON Schema.
106
+
107
+ Performs flexible validation using the JSON Schema specification.
108
+ This method provides basic structural validation for model information.
109
+
110
+ Args:
111
+ data: Parsed model_info.json data dictionary.
112
+
113
+ Returns:
114
+ Tuple of (is_valid, error_messages) where is_valid indicates
115
+ if the data passes JSON Schema validation.
116
+
117
+ Example:
118
+ >>> validator = ModelInfoValidator()
119
+ >>> is_valid, errors = validator._validate_with_jsonschema(data)
120
+ """
121
+ errors = sorted(self.validator.iter_errors(data), key=lambda e: e.path)
122
+
123
+ if not errors:
124
+ return True, []
125
+
126
+ error_messages = []
127
+ for error in errors:
128
+ path = ".".join(str(p) for p in error.path) if error.path else "root"
129
+ error_messages.append(f"{error.message} (at: {path})")
130
+
131
+ return False, error_messages
132
+
133
+ def _validate_with_pydantic(self, data: dict[str, Any]) -> tuple[bool, list[str]]:
134
+ """Validate model info data using Pydantic models.
135
+
136
+ Performs strict validation using Pydantic models with custom validators.
137
+ This provides comprehensive validation including type checking,
138
+ pattern matching, and model-specific business rules.
139
+
140
+ Args:
141
+ data: Parsed model_info.json data dictionary.
142
+
143
+ Returns:
144
+ Tuple of (is_valid, error_messages) where is_valid indicates
145
+ if the data passes Pydantic model validation.
146
+
147
+ Example:
148
+ >>> validator = ModelInfoValidator()
149
+ >>> is_valid, errors = validator._validate_with_pydantic(data)
150
+ """
151
+ try:
152
+ ModelInfo.model_validate(data)
153
+ return True, []
154
+ except ValidationError as e:
155
+ # Parse pydantic validation errors
156
+ error_messages = []
157
+ for error in e.errors():
158
+ loc = ".".join(str(loc_part) for loc_part in error["loc"])
159
+ msg = error["msg"]
160
+ error_messages.append(f"{msg} (at: {loc})")
161
+ return False, error_messages
162
+ except Exception as e:
163
+ return False, [f"Validation error: {e}"]
164
+
165
+ def validate_and_load(self, path: str | Path) -> ModelInfo:
166
+ """Validate and load model_info.json file.
167
+
168
+ Performs strict validation using Pydantic models and returns a validated
169
+ ModelInfo instance if successful. This is the recommended method
170
+ for loading model information in production code.
171
+
172
+ Args:
173
+ path: Path to model_info.json file.
174
+
175
+ Returns:
176
+ Validated ModelInfo instance with all data properly typed
177
+ and validated.
178
+
179
+ Raises:
180
+ ValueError: If validation fails or file cannot be loaded.
181
+ FileNotFoundError: If the model_info.json file does not exist.
182
+ json.JSONDecodeError: If the file contains invalid JSON.
183
+
184
+ Example:
185
+ >>> validator = ModelInfoValidator()
186
+ >>> model_info = validator.validate_and_load("model_info.json")
187
+ >>> print(model_info.name)
188
+ 'ViT-B-32'
189
+ """
190
+ path = Path(path)
191
+ is_valid, errors = self.validate_file(path, strict=True)
192
+
193
+ if not is_valid:
194
+ error_msg = "Model info validation failed:\n" + "\n".join(
195
+ f" - {err}" for err in errors
196
+ )
197
+ raise ValueError(error_msg)
198
+
199
+ with open(path, "r", encoding="utf-8") as f:
200
+ data = json.load(f)
201
+
202
+ return ModelInfo.model_validate(data)
203
+
204
+
205
+ def load_and_validate_model_info(path: str | Path) -> ModelInfo:
206
+ """Load and validate a model_info.json file.
207
+
208
+ This is the recommended way to load model information in production.
209
+ Combines validation and loading into a single operation for convenience
210
+ and ensures that only validated model information is returned.
211
+
212
+ Args:
213
+ path: Path to model_info.json file.
214
+
215
+ Returns:
216
+ Validated ModelInfo instance with all data properly typed and
217
+ validated against the model specification.
218
+
219
+ Raises:
220
+ ValueError: If validation fails or file cannot be loaded.
221
+ FileNotFoundError: If the model_info.json file does not exist.
222
+ json.JSONDecodeError: If the file contains invalid JSON.
223
+
224
+ Example:
225
+ >>> from lumen_resources.model_info_validator import load_and_validate_model_info
226
+ >>> model_info = load_and_validate_model_info("model_info.json")
227
+ >>> print(model_info.version)
228
+ '1.0.0'
229
+ """
230
+ validator = ModelInfoValidator()
231
+ return validator.validate_and_load(path)
232
+
233
+
234
+ def validate_file(path: str | Path, strict: bool = True) -> tuple[bool, list[str]]:
235
+ """Convenience function to validate a model_info.json file.
236
+
237
+ Simple one-line function for validating model information JSON files.
238
+ Uses strict validation by default for maximum reliability.
239
+
240
+ Args:
241
+ path: Path to model_info.json file.
242
+ strict: If True, use Pydantic validation with custom validators.
243
+ If False, use JSON Schema validation only. Defaults to True.
244
+
245
+ Returns:
246
+ Tuple of (is_valid, error_messages) where is_valid is True if
247
+ the file passes validation, and error_messages contains detailed
248
+ validation errors if validation fails.
249
+
250
+ Example:
251
+ >>> is_valid, errors = validate_file("model_info.json")
252
+ >>> if not is_valid:
253
+ ... for error in errors:
254
+ ... print(f"Error: {error}")
255
+ """
256
+ validator = ModelInfoValidator()
257
+ return validator.validate_file(path, strict=strict)
@@ -0,0 +1,270 @@
1
+ """
2
+ Platform Adapter for Model Repository Access
3
+
4
+ This module provides a unified interface for downloading models from HuggingFace Hub
5
+ and ModelScope Hub with efficient file filtering capabilities.
6
+
7
+ Features:
8
+ - Unified API for both HuggingFace and ModelScope platforms
9
+ - File pattern filtering during download (not post-download)
10
+ - Automatic cache management and file organization
11
+ - Force download and cache invalidation support
12
+ - Supports two-phase dataset downloads used by the Downloader:
13
+ 1) First pass downloads runtime-specific files plus JSON metadata (model_info.json).
14
+ 2) Second pass optionally fetches dataset files using the exact relative path from
15
+ model_info.json's "datasets" mapping via allow_patterns=[relative_path].
16
+
17
+ @requires: Platform-specific SDK installed (huggingface_hub or modelscope)
18
+ @returns: Downloaded model files in local cache with filtering applied
19
+ @errors: DownloadError, PlatformUnavailableError
20
+ """
21
+
22
+ import shutil
23
+ from enum import Enum
24
+ from pathlib import Path
25
+ from types import ModuleType
26
+
27
+ from .exceptions import DownloadError, PlatformUnavailableError
28
+
29
+
30
+ class PlatformType(str, Enum):
31
+ """Supported model repository platforms.
32
+
33
+ Defines the platforms that can be used for downloading models.
34
+ Each platform has its own SDK and API requirements.
35
+
36
+ Attributes:
37
+ HUGGINGFACE: Hugging Face Hub platform.
38
+ MODELSCOPE: ModelScope Hub platform.
39
+
40
+ Example:
41
+ >>> platform_type = PlatformType.HUGGINGFACE
42
+ >>> print(platform_type.value)
43
+ 'huggingface'
44
+ """
45
+
46
+ HUGGINGFACE = "huggingface"
47
+ MODELSCOPE = "modelscope"
48
+
49
+
50
+ class Platform:
51
+ """Unified platform adapter for HuggingFace and ModelScope.
52
+
53
+ Provides a consistent interface for downloading models from different
54
+ repositories while handling platform-specific requirements and optimizations.
55
+ Supports efficient file filtering during download to minimize bandwidth usage.
56
+
57
+ Attributes:
58
+ platform_type: The type of platform (HUGGINGFACE or MODELSCOPE).
59
+ owner: Organization/owner name for model repositories.
60
+
61
+ Example:
62
+ >>> platform = Platform(PlatformType.HUGGINGFACE, "openai")
63
+ >>> model_path = platform.download_model(
64
+ ... repo_name="clip-vit-base-patch32",
65
+ ... cache_dir=Path("/cache"),
66
+ ... allow_patterns=["*.json", "*.pt"]
67
+ ... )
68
+ """
69
+
70
+ def __init__(self, platform_type: PlatformType, owner: str):
71
+ """Initialize platform adapter.
72
+
73
+ Args:
74
+ platform_type: Type of platform (HUGGINGFACE or MODELSCOPE).
75
+ owner: Organization/owner name on the platform.
76
+
77
+ Raises:
78
+ PlatformUnavailableError: If required SDK is not installed.
79
+ """
80
+ self.platform_type: PlatformType = platform_type
81
+ self.owner: str = owner
82
+ self._check_availability()
83
+
84
+ def _check_availability(self) -> None:
85
+ """Check if the required platform SDK is available.
86
+
87
+ Validates that the appropriate SDK (huggingface_hub or modelscope)
88
+ is installed and imports the necessary functions for the platform.
89
+
90
+ Raises:
91
+ PlatformUnavailableError: If the required SDK is not installed.
92
+
93
+ Example:
94
+ >>> platform = Platform(PlatformType.HUGGINGFACE, "owner")
95
+ >>> # If huggingface_hub is not installed, raises PlatformUnavailableError
96
+ """
97
+ if self.platform_type == PlatformType.HUGGINGFACE:
98
+ try:
99
+ import huggingface_hub
100
+
101
+ self._hf_hub: ModuleType = huggingface_hub
102
+ except ImportError:
103
+ raise PlatformUnavailableError(
104
+ "HuggingFace Hub SDK not available. "
105
+ + "Install with: pip install huggingface_hub"
106
+ )
107
+ elif self.platform_type == PlatformType.MODELSCOPE:
108
+ try:
109
+ from modelscope.hub.snapshot_download import snapshot_download
110
+
111
+ self._ms_snapshot_download = snapshot_download
112
+ except ImportError:
113
+ raise PlatformUnavailableError(
114
+ "ModelScope SDK not available. Install with: pip install modelscope"
115
+ )
116
+
117
+ def download_model(
118
+ self,
119
+ repo_name: str,
120
+ cache_dir: Path,
121
+ allow_patterns: list[str],
122
+ force: bool = False,
123
+ ) -> Path:
124
+ """Download model files from the platform with efficient filtering.
125
+
126
+ Downloads model files using pattern-based filtering to minimize bandwidth
127
+ usage. Supports both HuggingFace and ModelScope platforms with their
128
+ respective SDKs while providing a unified interface.
129
+
130
+ Args:
131
+ repo_name: Repository name (without owner prefix).
132
+ cache_dir: Local cache directory for storing downloaded models.
133
+ allow_patterns: List of glob patterns for files to download.
134
+ Examples: ['*.json', '*.bin', 'tokenizer/*', 'model_info.json'].
135
+ force: Force re-download even if cached.
136
+ - HuggingFace: Uses native force_download parameter.
137
+ - ModelScope: Clears cache directory before download.
138
+
139
+ Returns:
140
+ Path to the downloaded model directory.
141
+
142
+ Raises:
143
+ DownloadError: If download fails for any reason.
144
+
145
+ Example:
146
+ >>> platform = Platform(PlatformType.HUGGINGFACE, "openai")
147
+ >>> model_path = platform.download_model(
148
+ ... repo_name="clip-vit-base-patch32",
149
+ ... cache_dir=Path("/cache"),
150
+ ... allow_patterns=["*.json", "*.pt"],
151
+ ... force=True
152
+ ... )
153
+ >>> print(model_path.name)
154
+ 'clip-vit-base-patch32'
155
+ """
156
+ repo_id = f"{self.owner}/{repo_name}"
157
+ target_dir = cache_dir / "models" / repo_name
158
+
159
+ try:
160
+ if self.platform_type == PlatformType.HUGGINGFACE:
161
+ return self._download_from_huggingface(
162
+ repo_id, target_dir, allow_patterns, force
163
+ )
164
+ elif self.platform_type == PlatformType.MODELSCOPE:
165
+ return self._download_from_modelscope(
166
+ repo_id, target_dir, allow_patterns, force
167
+ )
168
+ else:
169
+ raise DownloadError(f"Unsupported platform type: {self.platform_type}")
170
+ except Exception as e:
171
+ raise DownloadError(f"Failed to download {repo_id}: {e}") from e
172
+
173
+ def _download_from_huggingface(
174
+ self,
175
+ repo_id: str,
176
+ cache_dir: Path,
177
+ allow_patterns: list[str],
178
+ force: bool,
179
+ ) -> Path:
180
+ """Download from HuggingFace Hub.
181
+
182
+ Uses the huggingface_hub library to download model files with
183
+ pattern-based filtering and optional force re-download.
184
+
185
+ Args:
186
+ repo_id: Full repository ID (owner/repo).
187
+ cache_dir: Local cache directory for storing files.
188
+ allow_patterns: File patterns to download.
189
+ force: Whether to force re-download ignoring cache.
190
+
191
+ Returns:
192
+ Path to the downloaded model directory.
193
+
194
+ Example:
195
+ >>> platform = Platform(PlatformType.HUGGINGFACE, "owner")
196
+ >>> path = platform._download_from_huggingface(
197
+ ... "owner/repo", Path("/cache"), ["*.json"], False
198
+ ... )
199
+ """
200
+ _ = self._hf_hub.snapshot_download(
201
+ repo_id=repo_id,
202
+ allow_patterns=allow_patterns,
203
+ local_dir=cache_dir,
204
+ local_files_only=False,
205
+ force_download=force,
206
+ )
207
+
208
+ return cache_dir
209
+
210
+ def _download_from_modelscope(
211
+ self,
212
+ repo_id: str,
213
+ cache_dir: Path,
214
+ allow_patterns: list[str],
215
+ force: bool,
216
+ ) -> Path:
217
+ """Download from ModelScope Hub.
218
+
219
+ Uses the ModelScope SDK to download model files with pattern-based filtering.
220
+ Implements force download by clearing the cache directory before download.
221
+
222
+ Args:
223
+ repo_id: Full repository ID (owner/repo).
224
+ cache_dir: Local cache directory for storing files.
225
+ allow_patterns: File patterns to download.
226
+ force: Force re-download by clearing cache first.
227
+
228
+ Returns:
229
+ Path to the downloaded model directory.
230
+
231
+ Example:
232
+ >>> platform = Platform(PlatformType.MODELSCOPE, "owner")
233
+ >>> path = platform._download_from_modelscope(
234
+ ... "owner/repo", Path("/cache"), ["*.json"], False
235
+ ... )
236
+ """
237
+
238
+ # Handle force download by clearing ModelScope cache
239
+ if force:
240
+ if cache_dir.exists():
241
+ shutil.rmtree(cache_dir)
242
+
243
+ # ModelScope supports allow_patterns parameter (HuggingFace compatible)
244
+ _ = self._ms_snapshot_download(
245
+ model_id=repo_id,
246
+ local_dir=str(cache_dir),
247
+ allow_patterns=allow_patterns,
248
+ local_files_only=False,
249
+ )
250
+
251
+ return cache_dir
252
+
253
+ def cleanup_model(self, repo_name: str, cache_dir: Path) -> None:
254
+ """Remove a model directory from cache.
255
+
256
+ Used for cleanup when download/validation fails or for manual cache management.
257
+ Removes the entire model directory including all downloaded files.
258
+
259
+ Args:
260
+ repo_name: Repository name (without owner prefix).
261
+ cache_dir: Base cache directory containing models.
262
+
263
+ Example:
264
+ >>> platform = Platform(PlatformType.HUGGINGFACE, "owner")
265
+ >>> platform.cleanup_model("model-name", Path("/cache"))
266
+ >>> # Model directory removed if it existed
267
+ """
268
+ target_dir = cache_dir / "models" / repo_name
269
+ if target_dir.exists():
270
+ shutil.rmtree(target_dir)
@@ -0,0 +1,14 @@
1
+ ```bash
2
+ datamodel-codegen \
3
+ --input src/lumen_resources/schemas/result_schemas \
4
+ --output src/lumen_resources/result_schemas/ \
5
+ --use-schema-description \
6
+ --use-field-description \
7
+ --target-python-version 3.10 \
8
+ --use-standard-collections \
9
+ --use-union-operator \
10
+ --output-model-type pydantic_v2.BaseModel \
11
+ --field-constraints \
12
+ --input-file-type jsonschema
13
+ ```
14
+
@@ -0,0 +1,14 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: result_schemas
3
+ # timestamp: 2025-11-28T17:04:43+00:00
4
+
5
+
6
+ from .embedding_v1 import EmbeddingV1
7
+ from .face_v1 import FaceV1
8
+ from .labels_v1 import LabelsV1
9
+
10
+ __all__ = [
11
+ "FaceV1",
12
+ "EmbeddingV1",
13
+ "LabelsV1",
14
+ ]
@@ -0,0 +1,29 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: embedding_v1.json
3
+ # timestamp: 2025-11-28T17:04:43+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+
10
+ class EmbeddingV1(BaseModel):
11
+ """
12
+ Universal schema for embedding responses across all Lumen services (face, clip, ocr, etc.)
13
+ """
14
+
15
+ model_config = ConfigDict(
16
+ extra='forbid',
17
+ )
18
+ vector: list[float] = Field(..., min_length=1)
19
+ """
20
+ Embedding vector
21
+ """
22
+ dim: int = Field(..., ge=1)
23
+ """
24
+ Embedding dimension
25
+ """
26
+ model_id: str = Field(..., min_length=1)
27
+ """
28
+ Model identifier that generated the embedding
29
+ """
@@ -0,0 +1,55 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: face_v1.json
3
+ # timestamp: 2025-11-28T17:04:43+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field, RootModel
8
+
9
+
10
+ class BboxItem(RootModel[float]):
11
+ root: float = Field(..., ge=0.0)
12
+
13
+
14
+ class Face(BaseModel):
15
+ model_config = ConfigDict(
16
+ extra='forbid',
17
+ )
18
+ bbox: list[BboxItem] = Field(..., max_length=4, min_length=4)
19
+ """
20
+ Bounding box coordinates [x1, y1, x2, y2] where (x1, y1) is top-left corner and (x2, y2) is bottom-right corner
21
+ """
22
+ confidence: float = Field(..., ge=0.0, le=1.0)
23
+ """
24
+ Detection confidence score
25
+ """
26
+ landmarks: list[float] | None = None
27
+ """
28
+ Facial landmark points (optional)
29
+ """
30
+ embedding: list[float] | None = None
31
+ """
32
+ Face embedding vector for recognition/comparison (optional)
33
+ """
34
+
35
+
36
+ class FaceV1(BaseModel):
37
+ """
38
+ Universal schema for face detection and embedding responses across Lumen services
39
+ """
40
+
41
+ model_config = ConfigDict(
42
+ extra='forbid',
43
+ )
44
+ faces: list[Face]
45
+ """
46
+ Detected faces with bounding boxes and metadata
47
+ """
48
+ count: int = Field(..., ge=0)
49
+ """
50
+ Number of detected faces
51
+ """
52
+ model_id: str = Field(..., min_length=1)
53
+ """
54
+ Model identifier
55
+ """
@@ -0,0 +1,39 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: labels_v1.json
3
+ # timestamp: 2025-11-28T17:04:43+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+
10
+ class Label(BaseModel):
11
+ model_config = ConfigDict(
12
+ extra='forbid',
13
+ )
14
+ label: str
15
+ """
16
+ The classification label or class name
17
+ """
18
+ score: float
19
+ """
20
+ Confidence score for this label
21
+ """
22
+
23
+
24
+ class LabelsV1(BaseModel):
25
+ """
26
+ Classification response schema for Lumen services. Returns ranked labels with confidence scores.
27
+ """
28
+
29
+ model_config = ConfigDict(
30
+ extra='forbid',
31
+ )
32
+ labels: list[Label]
33
+ """
34
+ Array of classification results
35
+ """
36
+ model_id: str = Field(..., min_length=1)
37
+ """
38
+ Identifier of the model that generated the classification
39
+ """