semantics

This commit is contained in:
2026-03-20 20:01:58 +03:00
parent 1149e8df1d
commit 80ce8fe150
12 changed files with 1734 additions and 6577 deletions

View File

@@ -22,7 +22,7 @@ from dataclasses import dataclass, field
from datetime import datetime
import hashlib
import json
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, cast
from src.core.config_manager import ConfigManager
from src.core.logger import belief_scope, logger
@@ -72,6 +72,8 @@ from src.services.dataset_review.semantic_resolver import SemanticSourceResolver
from src.services.dataset_review.event_logger import SessionEventPayload
# [/DEF:DatasetReviewOrchestrator.imports:Block]
logger = cast(Any, logger)
# [DEF:StartSessionCommand:Class]
# @COMPLEXITY: 2
@@ -82,6 +84,8 @@ class StartSessionCommand:
environment_id: str
source_kind: str
source_input: str
# [/DEF:StartSessionCommand:Class]
@@ -93,6 +97,8 @@ class StartSessionResult:
session: DatasetReviewSession
parsed_context: Optional[SupersetParsedContext] = None
findings: List[ValidationFinding] = field(default_factory=list)
# [/DEF:StartSessionResult:Class]
@@ -103,6 +109,8 @@ class StartSessionResult:
class PreparePreviewCommand:
user: User
session_id: str
# [/DEF:PreparePreviewCommand:Class]
@@ -114,6 +122,8 @@ class PreparePreviewResult:
session: DatasetReviewSession
preview: CompiledPreview
blocked_reasons: List[str] = field(default_factory=list)
# [/DEF:PreparePreviewResult:Class]
@@ -124,6 +134,8 @@ class PreparePreviewResult:
class LaunchDatasetCommand:
user: User
session_id: str
# [/DEF:LaunchDatasetCommand:Class]
@@ -135,6 +147,8 @@ class LaunchDatasetResult:
session: DatasetReviewSession
run_context: DatasetRunContext
blocked_reasons: List[str] = field(default_factory=list)
# [/DEF:LaunchDatasetResult:Class]
@@ -168,6 +182,7 @@ class DatasetReviewOrchestrator:
self.config_manager = config_manager
self.task_manager = task_manager
self.semantic_resolver = semantic_resolver or SemanticSourceResolver()
# [/DEF:DatasetReviewOrchestrator.__init__:Function]
# [DEF:DatasetReviewOrchestrator.start_session:Function]
@@ -188,7 +203,9 @@ class DatasetReviewOrchestrator:
normalized_environment_id = str(command.environment_id or "").strip()
if not normalized_source_input:
logger.explore("Blocked dataset review session start due to empty source input")
logger.explore(
"Blocked dataset review session start due to empty source input"
)
raise ValueError("source_input must be non-empty")
if normalized_source_kind not in {"superset_link", "dataset_selection"}:
@@ -196,7 +213,9 @@ class DatasetReviewOrchestrator:
"Blocked dataset review session start due to unsupported source kind",
extra={"source_kind": normalized_source_kind},
)
raise ValueError("source_kind must be 'superset_link' or 'dataset_selection'")
raise ValueError(
"source_kind must be 'superset_link' or 'dataset_selection'"
)
environment = self.config_manager.get_environment(normalized_environment_id)
if environment is None:
@@ -234,11 +253,15 @@ class DatasetReviewOrchestrator:
if parsed_context.partial_recovery:
readiness_state = ReadinessState.RECOVERY_REQUIRED
recommended_action = RecommendedAction.REVIEW_DOCUMENTATION
findings.extend(self._build_partial_recovery_findings(parsed_context))
findings.extend(
self._build_partial_recovery_findings(parsed_context)
)
else:
readiness_state = ReadinessState.REVIEW_READY
else:
dataset_ref, dataset_id = self._parse_dataset_selection(normalized_source_input)
dataset_ref, dataset_id = self._parse_dataset_selection(
normalized_source_input
)
readiness_state = ReadinessState.REVIEW_READY
current_phase = SessionPhase.REVIEW
@@ -255,17 +278,19 @@ class DatasetReviewOrchestrator:
status=SessionStatus.ACTIVE,
current_phase=current_phase,
)
persisted_session = self.repository.create_session(session)
persisted_session = cast(Any, self.repository.create_session(session))
recovered_filters: List[ImportedFilter] = []
template_variables: List[TemplateVariable] = []
execution_mappings: List[ExecutionMapping] = []
if normalized_source_kind == "superset_link" and parsed_context is not None:
recovered_filters, template_variables, execution_mappings, findings = self._build_recovery_bootstrap(
environment=environment,
session=persisted_session,
parsed_context=parsed_context,
findings=findings,
recovered_filters, template_variables, execution_mappings, findings = (
self._build_recovery_bootstrap(
environment=environment,
session=persisted_session,
parsed_context=parsed_context,
findings=findings,
)
)
profile = self._build_initial_profile(
@@ -286,7 +311,9 @@ class DatasetReviewOrchestrator:
"dataset_ref": persisted_session.dataset_ref,
"dataset_id": persisted_session.dataset_id,
"dashboard_id": persisted_session.dashboard_id,
"partial_recovery": bool(parsed_context and parsed_context.partial_recovery),
"partial_recovery": bool(
parsed_context and parsed_context.partial_recovery
),
},
)
)
@@ -327,7 +354,10 @@ class DatasetReviewOrchestrator:
)
logger.reason(
"Linked recovery task to started dataset review session",
extra={"session_id": persisted_session.session_id, "task_id": active_task_id},
extra={
"session_id": persisted_session.session_id,
"task_id": active_task_id,
},
)
logger.reflect(
@@ -347,6 +377,7 @@ class DatasetReviewOrchestrator:
parsed_context=parsed_context,
findings=findings,
)
# [/DEF:DatasetReviewOrchestrator.start_session:Function]
# [DEF:DatasetReviewOrchestrator.prepare_launch_preview:Function]
@@ -357,13 +388,20 @@ class DatasetReviewOrchestrator:
# @POST: returns preview artifact in pending, ready, failed, or stale state.
# @SIDE_EFFECT: persists preview attempt and upstream compilation diagnostics.
# @DATA_CONTRACT: Input[PreparePreviewCommand] -> Output[PreparePreviewResult]
def prepare_launch_preview(self, command: PreparePreviewCommand) -> PreparePreviewResult:
def prepare_launch_preview(
self, command: PreparePreviewCommand
) -> PreparePreviewResult:
with belief_scope("DatasetReviewOrchestrator.prepare_launch_preview"):
session = self.repository.load_session_detail(command.session_id, command.user.id)
session = self.repository.load_session_detail(
command.session_id, command.user.id
)
if session is None or session.user_id != command.user.id:
logger.explore(
"Preview preparation rejected because owned session was not found",
extra={"session_id": command.session_id, "user_id": command.user.id},
extra={
"session_id": command.session_id,
"user_id": command.user.id,
},
)
raise ValueError("Session not found")
@@ -451,6 +489,7 @@ class DatasetReviewOrchestrator:
preview=persisted_preview,
blocked_reasons=[],
)
# [/DEF:DatasetReviewOrchestrator.prepare_launch_preview:Function]
# [DEF:DatasetReviewOrchestrator.launch_dataset:Function]
@@ -464,11 +503,16 @@ class DatasetReviewOrchestrator:
# @INVARIANT: launch remains blocked unless blocking findings are closed, approvals are satisfied, and the latest Superset preview fingerprint matches current execution inputs.
def launch_dataset(self, command: LaunchDatasetCommand) -> LaunchDatasetResult:
with belief_scope("DatasetReviewOrchestrator.launch_dataset"):
session = self.repository.load_session_detail(command.session_id, command.user.id)
session = self.repository.load_session_detail(
command.session_id, command.user.id
)
if session is None or session.user_id != command.user.id:
logger.explore(
"Launch rejected because owned session was not found",
extra={"session_id": command.session_id, "user_id": command.user.id},
extra={
"session_id": command.session_id,
"user_id": command.user.id,
},
)
raise ValueError("Session not found")
@@ -579,6 +623,7 @@ class DatasetReviewOrchestrator:
run_context=persisted_run_context,
blocked_reasons=[],
)
# [/DEF:DatasetReviewOrchestrator.launch_dataset:Function]
# [DEF:DatasetReviewOrchestrator._parse_dataset_selection:Function]
@@ -601,6 +646,7 @@ class DatasetReviewOrchestrator:
return normalized, None
return normalized, None
# [/DEF:DatasetReviewOrchestrator._parse_dataset_selection:Function]
# [DEF:DatasetReviewOrchestrator._build_initial_profile:Function]
@@ -613,7 +659,9 @@ class DatasetReviewOrchestrator:
parsed_context: Optional[SupersetParsedContext],
dataset_ref: str,
) -> DatasetProfile:
dataset_name = dataset_ref.split(".")[-1] if dataset_ref else "Unresolved dataset"
dataset_name = (
dataset_ref.split(".")[-1] if dataset_ref else "Unresolved dataset"
)
business_summary = (
f"Review session initialized for {dataset_ref}."
if dataset_ref
@@ -636,9 +684,12 @@ class DatasetReviewOrchestrator:
completeness_score=0.25,
confidence_state=confidence_state,
has_blocking_findings=False,
has_warning_findings=bool(parsed_context and parsed_context.partial_recovery),
has_warning_findings=bool(
parsed_context and parsed_context.partial_recovery
),
manual_summary_locked=False,
)
# [/DEF:DatasetReviewOrchestrator._build_initial_profile:Function]
# [DEF:DatasetReviewOrchestrator._build_partial_recovery_findings:Function]
@@ -670,36 +721,57 @@ class DatasetReviewOrchestrator:
)
)
return findings
# [/DEF:DatasetReviewOrchestrator._build_partial_recovery_findings:Function]
# [DEF:DatasetReviewOrchestrator._build_recovery_bootstrap:Function]
# @COMPLEXITY: 4
# @PURPOSE: Recover and materialize initial imported filters, template variables, and draft execution mappings after session creation.
# @RELATION: [CALLS] ->[SupersetContextExtractor.recover_imported_filters]
# @RELATION: [CALLS] ->[SupersetContextExtractor.discover_template_variables]
# @PRE: session belongs to the just-created review aggregate and parsed_context was produced for the same environment scope.
# @POST: Returns bootstrap imported filters, template variables, execution mappings, and updated findings without persisting them directly.
# @SIDE_EFFECT: Performs Superset reads through the extractor and may append warning findings for incomplete recovery.
# @DATA_CONTRACT: Input[Environment, DatasetReviewSession, SupersetParsedContext, List[ValidationFinding]] -> Output[Tuple[List[ImportedFilter], List[TemplateVariable], List[ExecutionMapping], List[ValidationFinding]]]
def _build_recovery_bootstrap(
self,
environment,
session: DatasetReviewSession,
parsed_context: SupersetParsedContext,
findings: List[ValidationFinding],
) -> tuple[List[ImportedFilter], List[TemplateVariable], List[ExecutionMapping], List[ValidationFinding]]:
) -> tuple[
List[ImportedFilter],
List[TemplateVariable],
List[ExecutionMapping],
List[ValidationFinding],
]:
session_record = cast(Any, session)
extractor = SupersetContextExtractor(environment)
imported_filters_payload = extractor.recover_imported_filters(parsed_context)
if imported_filters_payload is None:
imported_filters_payload = []
imported_filters = [
ImportedFilter(
session_id=session.session_id,
session_id=session_record.session_id,
filter_name=str(item.get("filter_name") or f"imported_filter_{index}"),
display_name=item.get("display_name"),
raw_value=item.get("raw_value"),
normalized_value=item.get("normalized_value"),
source=FilterSource(str(item.get("source") or FilterSource.SUPERSET_URL.value)),
source=FilterSource(
str(item.get("source") or FilterSource.SUPERSET_URL.value)
),
confidence_state=FilterConfidenceState(
str(item.get("confidence_state") or FilterConfidenceState.UNRESOLVED.value)
str(
item.get("confidence_state")
or FilterConfidenceState.UNRESOLVED.value
)
),
requires_confirmation=bool(item.get("requires_confirmation", False)),
recovery_status=FilterRecoveryStatus(
str(item.get("recovery_status") or FilterRecoveryStatus.PARTIAL.value)
str(
item.get("recovery_status")
or FilterRecoveryStatus.PARTIAL.value
)
),
notes=item.get("notes"),
)
@@ -711,25 +783,44 @@ class DatasetReviewOrchestrator:
if session.dataset_id is not None:
try:
dataset_payload = extractor.client.get_dataset_detail(session.dataset_id)
discovered_variables = extractor.discover_template_variables(dataset_payload)
dataset_payload = extractor.client.get_dataset_detail(
session_record.dataset_id
)
discovered_variables = extractor.discover_template_variables(
dataset_payload
)
template_variables = [
TemplateVariable(
session_id=session.session_id,
variable_name=str(item.get("variable_name") or f"variable_{index}"),
session_id=session_record.session_id,
variable_name=str(
item.get("variable_name") or f"variable_{index}"
),
expression_source=str(item.get("expression_source") or ""),
variable_kind=VariableKind(str(item.get("variable_kind") or VariableKind.UNKNOWN.value)),
variable_kind=VariableKind(
str(item.get("variable_kind") or VariableKind.UNKNOWN.value)
),
is_required=bool(item.get("is_required", True)),
default_value=item.get("default_value"),
mapping_status=MappingStatus(str(item.get("mapping_status") or MappingStatus.UNMAPPED.value)),
mapping_status=MappingStatus(
str(
item.get("mapping_status")
or MappingStatus.UNMAPPED.value
)
),
)
for index, item in enumerate(discovered_variables)
]
except Exception as exc:
if "dataset_template_variable_discovery_failed" not in parsed_context.unresolved_references:
parsed_context.unresolved_references.append("dataset_template_variable_discovery_failed")
if (
"dataset_template_variable_discovery_failed"
not in parsed_context.unresolved_references
):
parsed_context.unresolved_references.append(
"dataset_template_variable_discovery_failed"
)
if not any(
finding.caused_by_ref == "dataset_template_variable_discovery_failed"
finding.caused_by_ref
== "dataset_template_variable_discovery_failed"
for finding in findings
):
findings.append(
@@ -745,7 +836,11 @@ class DatasetReviewOrchestrator:
)
logger.explore(
"Template variable discovery failed during session bootstrap",
extra={"session_id": session.session_id, "dataset_id": session.dataset_id, "error": str(exc)},
extra={
"session_id": session_record.session_id,
"dataset_id": session_record.dataset_id,
"error": str(exc),
},
)
filter_lookup = {
@@ -754,7 +849,9 @@ class DatasetReviewOrchestrator:
if str(imported_filter.filter_name or "").strip()
}
for template_variable in template_variables:
matched_filter = filter_lookup.get(str(template_variable.variable_name or "").strip().lower())
matched_filter = filter_lookup.get(
str(template_variable.variable_name or "").strip().lower()
)
if matched_filter is None:
continue
requires_explicit_approval = bool(
@@ -763,22 +860,27 @@ class DatasetReviewOrchestrator:
)
execution_mappings.append(
ExecutionMapping(
session_id=session.session_id,
session_id=session_record.session_id,
filter_id=matched_filter.filter_id,
variable_id=template_variable.variable_id,
mapping_method=MappingMethod.DIRECT_MATCH,
raw_input_value=matched_filter.raw_value,
effective_value=matched_filter.normalized_value if matched_filter.normalized_value is not None else matched_filter.raw_value,
effective_value=matched_filter.normalized_value
if matched_filter.normalized_value is not None
else matched_filter.raw_value,
transformation_note="Bootstrapped from Superset recovery context",
warning_level=None if not requires_explicit_approval else None,
requires_explicit_approval=requires_explicit_approval,
approval_state=ApprovalState.PENDING if requires_explicit_approval else ApprovalState.NOT_REQUIRED,
approval_state=ApprovalState.PENDING
if requires_explicit_approval
else ApprovalState.NOT_REQUIRED,
approved_by_user_id=None,
approved_at=None,
)
)
return imported_filters, template_variables, execution_mappings, findings
# [/DEF:DatasetReviewOrchestrator._build_recovery_bootstrap:Function]
# [DEF:DatasetReviewOrchestrator._build_execution_snapshot:Function]
@@ -789,9 +891,16 @@ class DatasetReviewOrchestrator:
# @POST: returns deterministic execution snapshot for current session state without mutating persistence.
# @SIDE_EFFECT: none.
# @DATA_CONTRACT: Input[DatasetReviewSession] -> Output[Dict[str,Any]]
def _build_execution_snapshot(self, session: DatasetReviewSession) -> Dict[str, Any]:
filter_lookup = {item.filter_id: item for item in session.imported_filters}
variable_lookup = {item.variable_id: item for item in session.template_variables}
def _build_execution_snapshot(
self, session: DatasetReviewSession
) -> Dict[str, Any]:
session_record = cast(Any, session)
filter_lookup = {
item.filter_id: item for item in session_record.imported_filters
}
variable_lookup = {
item.variable_id: item for item in session_record.template_variables
}
effective_filters: List[Dict[str, Any]] = []
template_params: Dict[str, Any] = {}
@@ -800,14 +909,16 @@ class DatasetReviewOrchestrator:
preview_blockers: List[str] = []
mapped_filter_ids: set[str] = set()
for mapping in session.execution_mappings:
for mapping in session_record.execution_mappings:
imported_filter = filter_lookup.get(mapping.filter_id)
template_variable = variable_lookup.get(mapping.variable_id)
if imported_filter is None:
preview_blockers.append(f"mapping:{mapping.mapping_id}:missing_filter")
continue
if template_variable is None:
preview_blockers.append(f"mapping:{mapping.mapping_id}:missing_variable")
preview_blockers.append(
f"mapping:{mapping.mapping_id}:missing_variable"
)
continue
effective_value = mapping.effective_value
@@ -819,7 +930,9 @@ class DatasetReviewOrchestrator:
effective_value = template_variable.default_value
if effective_value is None and template_variable.is_required:
preview_blockers.append(f"variable:{template_variable.variable_name}:missing_required_value")
preview_blockers.append(
f"variable:{template_variable.variable_name}:missing_required_value"
)
continue
mapped_filter_ids.add(imported_filter.filter_id)
@@ -840,10 +953,13 @@ class DatasetReviewOrchestrator:
template_params[template_variable.variable_name] = effective_value
if mapping.approval_state == ApprovalState.APPROVED:
approved_mapping_ids.append(mapping.mapping_id)
if mapping.requires_explicit_approval and mapping.approval_state != ApprovalState.APPROVED:
if (
mapping.requires_explicit_approval
and mapping.approval_state != ApprovalState.APPROVED
):
open_warning_refs.append(mapping.mapping_id)
for imported_filter in session.imported_filters:
for imported_filter in session_record.imported_filters:
if imported_filter.filter_id in mapped_filter_ids:
continue
effective_value = imported_filter.normalized_value
@@ -862,8 +978,10 @@ class DatasetReviewOrchestrator:
}
)
mapped_variable_ids = {mapping.variable_id for mapping in session.execution_mappings}
for variable in session.template_variables:
mapped_variable_ids = {
mapping.variable_id for mapping in session_record.execution_mappings
}
for variable in session_record.template_variables:
if variable.variable_id in mapped_variable_ids:
continue
if variable.default_value is not None:
@@ -875,11 +993,13 @@ class DatasetReviewOrchestrator:
semantic_decision_refs = [
field.field_id
for field in session.semantic_fields
if field.is_locked or not field.needs_review or field.provenance.value != "unresolved"
if field.is_locked
or not field.needs_review
or field.provenance.value != "unresolved"
]
preview_fingerprint = self._compute_preview_fingerprint(
{
"dataset_id": session.dataset_id,
"dataset_id": session_record.dataset_id,
"template_params": template_params,
"effective_filters": effective_filters,
}
@@ -893,6 +1013,7 @@ class DatasetReviewOrchestrator:
"preview_blockers": sorted(set(preview_blockers)),
"preview_fingerprint": preview_fingerprint,
}
# [/DEF:DatasetReviewOrchestrator._build_execution_snapshot:Function]
# [DEF:DatasetReviewOrchestrator._build_launch_blockers:Function]
@@ -909,16 +1030,21 @@ class DatasetReviewOrchestrator:
execution_snapshot: Dict[str, Any],
preview: Optional[CompiledPreview],
) -> List[str]:
session_record = cast(Any, session)
blockers = list(execution_snapshot["preview_blockers"])
for finding in session.findings:
for finding in session_record.findings:
if (
finding.severity == FindingSeverity.BLOCKING
and finding.resolution_state not in {ResolutionState.RESOLVED, ResolutionState.APPROVED}
and finding.resolution_state
not in {ResolutionState.RESOLVED, ResolutionState.APPROVED}
):
blockers.append(f"finding:{finding.code}:blocking")
for mapping in session.execution_mappings:
if mapping.requires_explicit_approval and mapping.approval_state != ApprovalState.APPROVED:
for mapping in session_record.execution_mappings:
if (
mapping.requires_explicit_approval
and mapping.approval_state != ApprovalState.APPROVED
):
blockers.append(f"mapping:{mapping.mapping_id}:approval_required")
if preview is None:
@@ -930,23 +1056,28 @@ class DatasetReviewOrchestrator:
blockers.append("preview:fingerprint_mismatch")
return sorted(set(blockers))
# [/DEF:DatasetReviewOrchestrator._build_launch_blockers:Function]
# [DEF:DatasetReviewOrchestrator._get_latest_preview:Function]
# @COMPLEXITY: 2
# @PURPOSE: Resolve the current latest preview snapshot for one session aggregate.
def _get_latest_preview(self, session: DatasetReviewSession) -> Optional[CompiledPreview]:
if not session.previews:
def _get_latest_preview(
self, session: DatasetReviewSession
) -> Optional[CompiledPreview]:
session_record = cast(Any, session)
if not session_record.previews:
return None
if session.last_preview_id:
for preview in session.previews:
if preview.preview_id == session.last_preview_id:
if session_record.last_preview_id:
for preview in session_record.previews:
if preview.preview_id == session_record.last_preview_id:
return preview
return sorted(
session.previews,
session_record.previews,
key=lambda item: (item.created_at or datetime.min, item.preview_id),
reverse=True,
)[0]
# [/DEF:DatasetReviewOrchestrator._get_latest_preview:Function]
# [DEF:DatasetReviewOrchestrator._compute_preview_fingerprint:Function]
@@ -955,6 +1086,7 @@ class DatasetReviewOrchestrator:
def _compute_preview_fingerprint(self, payload: Dict[str, Any]) -> str:
serialized = json.dumps(payload, sort_keys=True, default=str)
return hashlib.sha256(serialized.encode("utf-8")).hexdigest()
# [/DEF:DatasetReviewOrchestrator._compute_preview_fingerprint:Function]
# [DEF:DatasetReviewOrchestrator._enqueue_recovery_task:Function]
@@ -971,28 +1103,33 @@ class DatasetReviewOrchestrator:
session: DatasetReviewSession,
parsed_context: Optional[SupersetParsedContext],
) -> Optional[str]:
session_record = cast(Any, session)
if self.task_manager is None:
logger.reason(
"Dataset review session started without task manager; continuing synchronously",
extra={"session_id": session.session_id},
extra={"session_id": session_record.session_id},
)
return None
task_params: Dict[str, Any] = {
"session_id": session.session_id,
"session_id": session_record.session_id,
"user_id": command.user.id,
"environment_id": session.environment_id,
"source_kind": session.source_kind,
"source_input": session.source_input,
"dataset_ref": session.dataset_ref,
"dataset_id": session.dataset_id,
"dashboard_id": session.dashboard_id,
"partial_recovery": bool(parsed_context and parsed_context.partial_recovery),
"environment_id": session_record.environment_id,
"source_kind": session_record.source_kind,
"source_input": session_record.source_input,
"dataset_ref": session_record.dataset_ref,
"dataset_id": session_record.dataset_id,
"dashboard_id": session_record.dashboard_id,
"partial_recovery": bool(
parsed_context and parsed_context.partial_recovery
),
}
create_task = getattr(self.task_manager, "create_task", None)
if create_task is None:
logger.explore("Task manager has no create_task method; skipping recovery enqueue")
logger.explore(
"Task manager has no create_task method; skipping recovery enqueue"
)
return None
try:
@@ -1003,13 +1140,16 @@ class DatasetReviewOrchestrator:
except TypeError:
logger.explore(
"Recovery task enqueue skipped because task manager create_task contract is incompatible",
extra={"session_id": session.session_id},
extra={"session_id": session_record.session_id},
)
return None
task_id = getattr(task_object, "id", None)
return str(task_id) if task_id else None
# [/DEF:DatasetReviewOrchestrator._enqueue_recovery_task:Function]
# [/DEF:DatasetReviewOrchestrator:Class]
# [/DEF:DatasetReviewOrchestrator:Module]
# [/DEF:DatasetReviewOrchestrator:Module]