fix: commit semantic repair changes
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
# [DEF:backend.src.services.dataset_review:Module]
|
||||
# [DEF:dataset_review:Module]
|
||||
#
|
||||
# @SEMANTICS: dataset, review, orchestration
|
||||
# @PURPOSE: Provides services for dataset-centered orchestration flow.
|
||||
# @RELATION: EXPORTS ->[DatasetReviewOrchestrator:Class]
|
||||
# @LAYER: Services
|
||||
#
|
||||
# [/DEF:backend.src.services.dataset_review:Module]
|
||||
# [/DEF:dataset_review:Module]
|
||||
@@ -15,7 +15,7 @@
|
||||
# @INVARIANT: Only one active clarification question may exist at a time; skipped and expert-review items remain unresolved and visible.
|
||||
from __future__ import annotations
|
||||
|
||||
# [DEF:ClarificationEngine.imports:Block]
|
||||
# [DEF:imports:Block]
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
@@ -42,7 +42,7 @@ from src.models.dataset_review import (
|
||||
from src.services.dataset_review.repositories.session_repository import (
|
||||
DatasetReviewSessionRepository,
|
||||
)
|
||||
# [/DEF:ClarificationEngine.imports:Block]
|
||||
# [/DEF:imports:Block]
|
||||
|
||||
|
||||
# [DEF:ClarificationQuestionPayload:Class]
|
||||
@@ -97,14 +97,14 @@ class ClarificationAnswerCommand:
|
||||
# @POST: Returned clarification state is persistence-backed and aligned with session readiness/recommended action.
|
||||
# @SIDE_EFFECT: Mutates clarification answers, session flags, and related clarification findings.
|
||||
class ClarificationEngine:
|
||||
# [DEF:ClarificationEngine.__init__:Function]
|
||||
# [DEF:ClarificationEngine_init:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Bind repository dependency for clarification persistence operations.
|
||||
def __init__(self, repository: DatasetReviewSessionRepository) -> None:
|
||||
self.repository = repository
|
||||
# [/DEF:ClarificationEngine.__init__:Function]
|
||||
# [/DEF:ClarificationEngine_init:Function]
|
||||
|
||||
# [DEF:ClarificationEngine.build_question_payload:Function]
|
||||
# [DEF:build_question_payload:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Return the one active highest-priority clarification question payload with why-it-matters, current guess, and options.
|
||||
# @RELATION: [DEPENDS_ON] ->[ClarificationQuestion]
|
||||
@@ -197,9 +197,9 @@ class ClarificationEngine:
|
||||
},
|
||||
)
|
||||
return payload
|
||||
# [/DEF:ClarificationEngine.build_question_payload:Function]
|
||||
# [/DEF:build_question_payload:Function]
|
||||
|
||||
# [DEF:ClarificationEngine.record_answer:Function]
|
||||
# [DEF:record_answer:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Persist one clarification answer before any pointer/readiness mutation and compute deterministic state impact.
|
||||
# @RELATION: [DEPENDS_ON] ->[ClarificationAnswer]
|
||||
@@ -326,19 +326,19 @@ class ClarificationEngine:
|
||||
session=session,
|
||||
changed_findings=[changed_finding] if changed_finding else [],
|
||||
)
|
||||
# [/DEF:ClarificationEngine.record_answer:Function]
|
||||
# [/DEF:record_answer:Function]
|
||||
|
||||
# [DEF:ClarificationEngine.summarize_progress:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# [DEF:summarize_progress:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Produce a compact progress summary for pause/resume and completion UX.
|
||||
# @RELATION: [DEPENDS_ON] ->[ClarificationSession]
|
||||
def summarize_progress(self, clarification_session: ClarificationSession) -> str:
|
||||
resolved = self._count_resolved_questions(clarification_session)
|
||||
remaining = self._count_remaining_questions(clarification_session)
|
||||
return f"{resolved} resolved, {remaining} unresolved"
|
||||
# [/DEF:ClarificationEngine.summarize_progress:Function]
|
||||
# [/DEF:summarize_progress:Function]
|
||||
|
||||
# [DEF:ClarificationEngine._get_latest_clarification_session:Function]
|
||||
# [DEF:_get_latest_clarification_session:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Select the latest clarification session for the current dataset review aggregate.
|
||||
def _get_latest_clarification_session(
|
||||
@@ -353,10 +353,10 @@ class ClarificationEngine:
|
||||
reverse=True,
|
||||
)
|
||||
return ordered_sessions[0]
|
||||
# [/DEF:ClarificationEngine._get_latest_clarification_session:Function]
|
||||
# [/DEF:_get_latest_clarification_session:Function]
|
||||
|
||||
# [DEF:ClarificationEngine._find_question:Function]
|
||||
# @COMPLEXITY: 1
|
||||
# [DEF:_find_question:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Resolve a clarification question from the active clarification aggregate.
|
||||
def _find_question(
|
||||
self,
|
||||
@@ -367,9 +367,9 @@ class ClarificationEngine:
|
||||
if question.question_id == question_id:
|
||||
return question
|
||||
return None
|
||||
# [/DEF:ClarificationEngine._find_question:Function]
|
||||
# [/DEF:_find_question:Function]
|
||||
|
||||
# [DEF:ClarificationEngine._select_next_open_question:Function]
|
||||
# [DEF:_select_next_open_question:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Select the next unresolved question in deterministic priority order.
|
||||
def _select_next_open_question(
|
||||
@@ -384,10 +384,10 @@ class ClarificationEngine:
|
||||
return None
|
||||
open_questions.sort(key=lambda item: (-int(item.priority), item.created_at, item.question_id))
|
||||
return open_questions[0]
|
||||
# [/DEF:ClarificationEngine._select_next_open_question:Function]
|
||||
# [/DEF:_select_next_open_question:Function]
|
||||
|
||||
# [DEF:ClarificationEngine._count_resolved_questions:Function]
|
||||
# @COMPLEXITY: 1
|
||||
# [DEF:_count_resolved_questions:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Count questions whose answers fully resolved the ambiguity.
|
||||
def _count_resolved_questions(self, clarification_session: ClarificationSession) -> int:
|
||||
return sum(
|
||||
@@ -395,10 +395,10 @@ class ClarificationEngine:
|
||||
for question in clarification_session.questions
|
||||
if question.state == QuestionState.ANSWERED
|
||||
)
|
||||
# [/DEF:ClarificationEngine._count_resolved_questions:Function]
|
||||
# [/DEF:_count_resolved_questions:Function]
|
||||
|
||||
# [DEF:ClarificationEngine._count_remaining_questions:Function]
|
||||
# @COMPLEXITY: 1
|
||||
# [DEF:_count_remaining_questions:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Count questions still unresolved or deferred after clarification interaction.
|
||||
def _count_remaining_questions(self, clarification_session: ClarificationSession) -> int:
|
||||
return sum(
|
||||
@@ -406,9 +406,9 @@ class ClarificationEngine:
|
||||
for question in clarification_session.questions
|
||||
if question.state in {QuestionState.OPEN, QuestionState.SKIPPED, QuestionState.EXPERT_REVIEW}
|
||||
)
|
||||
# [/DEF:ClarificationEngine._count_remaining_questions:Function]
|
||||
# [/DEF:_count_remaining_questions:Function]
|
||||
|
||||
# [DEF:ClarificationEngine._normalize_answer_value:Function]
|
||||
# [DEF:_normalize_answer_value:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Validate and normalize answer payload based on answer kind and active question options.
|
||||
def _normalize_answer_value(
|
||||
@@ -429,9 +429,9 @@ class ClarificationEngine:
|
||||
if answer_kind == AnswerKind.EXPERT_REVIEW:
|
||||
return normalized_answer_value or "expert_review"
|
||||
return normalized_answer_value
|
||||
# [/DEF:ClarificationEngine._normalize_answer_value:Function]
|
||||
# [/DEF:_normalize_answer_value:Function]
|
||||
|
||||
# [DEF:ClarificationEngine._build_impact_summary:Function]
|
||||
# [DEF:_build_impact_summary:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Build a compact audit note describing how the clarification answer affects session state.
|
||||
def _build_impact_summary(
|
||||
@@ -445,10 +445,10 @@ class ClarificationEngine:
|
||||
if answer_kind == AnswerKind.EXPERT_REVIEW:
|
||||
return f"Clarification for {question.topic_ref} was deferred for expert review."
|
||||
return f"Clarification for {question.topic_ref} recorded as '{answer_value}'."
|
||||
# [/DEF:ClarificationEngine._build_impact_summary:Function]
|
||||
# [/DEF:_build_impact_summary:Function]
|
||||
|
||||
# [DEF:ClarificationEngine._upsert_clarification_finding:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# [DEF:_upsert_clarification_finding:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Keep one finding per clarification topic aligned with answer outcome and unresolved visibility rules.
|
||||
# @RELATION: [DEPENDS_ON] ->[ValidationFinding]
|
||||
def _upsert_clarification_finding(
|
||||
@@ -513,10 +513,10 @@ class ClarificationEngine:
|
||||
existing.title = "Clarification requires expert review"
|
||||
|
||||
return existing
|
||||
# [/DEF:ClarificationEngine._upsert_clarification_finding:Function]
|
||||
# [/DEF:_upsert_clarification_finding:Function]
|
||||
|
||||
# [DEF:ClarificationEngine._derive_readiness_state:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# [DEF:_derive_readiness_state:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Recompute readiness after clarification mutation while preserving unresolved visibility semantics.
|
||||
# @RELATION: [DEPENDS_ON] ->[ClarificationSession]
|
||||
# @RELATION: [DEPENDS_ON] ->[DatasetReviewSession]
|
||||
@@ -532,9 +532,9 @@ class ClarificationEngine:
|
||||
return ReadinessState.CLARIFICATION_NEEDED
|
||||
|
||||
return ReadinessState.REVIEW_READY
|
||||
# [/DEF:ClarificationEngine._derive_readiness_state:Function]
|
||||
# [/DEF:_derive_readiness_state:Function]
|
||||
|
||||
# [DEF:ClarificationEngine._derive_recommended_action:Function]
|
||||
# [DEF:_derive_recommended_action:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Recompute next-action guidance after clarification mutations.
|
||||
def _derive_recommended_action(self, session: DatasetReviewSession) -> RecommendedAction:
|
||||
@@ -546,7 +546,7 @@ class ClarificationEngine:
|
||||
if clarification_session.remaining_count > 0:
|
||||
return RecommendedAction.START_CLARIFICATION
|
||||
return RecommendedAction.REVIEW_DOCUMENTATION
|
||||
# [/DEF:ClarificationEngine._derive_recommended_action:Function]
|
||||
# [/DEF:_derive_recommended_action:Function]
|
||||
# [/DEF:ClarificationEngine:Class]
|
||||
|
||||
# [/DEF:ClarificationEngine:Module]
|
||||
@@ -48,14 +48,14 @@ class SessionEventPayload:
|
||||
# @SIDE_EFFECT: Writes one audit row to persistence and emits logger.reason/logger.reflect traces.
|
||||
# @DATA_CONTRACT: Input[SessionEventPayload] -> Output[SessionEvent]
|
||||
class SessionEventLogger:
|
||||
# [DEF:SessionEventLogger.__init__:Function]
|
||||
# [DEF:SessionEventLogger_init:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Bind a live SQLAlchemy session to the session-event logger.
|
||||
def __init__(self, db: Session) -> None:
|
||||
self.db = db
|
||||
# [/DEF:SessionEventLogger.__init__:Function]
|
||||
# [/DEF:SessionEventLogger_init:Function]
|
||||
|
||||
# [DEF:SessionEventLogger.log_event:Function]
|
||||
# [DEF:log_event:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Persist one explicit session event row for an owned dataset-review mutation.
|
||||
# @RELATION: [DEPENDS_ON] ->[SessionEvent]
|
||||
@@ -126,10 +126,10 @@ class SessionEventLogger:
|
||||
},
|
||||
)
|
||||
return event
|
||||
# [/DEF:SessionEventLogger.log_event:Function]
|
||||
# [/DEF:log_event:Function]
|
||||
|
||||
# [DEF:SessionEventLogger.log_for_session:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# [DEF:log_for_session:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Convenience wrapper for logging an event directly from a session aggregate root.
|
||||
# @RELATION: [CALLS] ->[SessionEventLogger.log_event]
|
||||
def log_for_session(
|
||||
@@ -152,7 +152,7 @@ class SessionEventLogger:
|
||||
event_details=dict(event_details or {}),
|
||||
)
|
||||
)
|
||||
# [/DEF:SessionEventLogger.log_for_session:Function]
|
||||
# [/DEF:log_for_session:Function]
|
||||
# [/DEF:SessionEventLogger:Class]
|
||||
|
||||
# [/DEF:SessionEventLoggerModule:Module]
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator.imports:Block]
|
||||
# [DEF:imports:Block]
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
@@ -70,7 +70,7 @@ from src.services.dataset_review.repositories.session_repository import (
|
||||
)
|
||||
from src.services.dataset_review.semantic_resolver import SemanticSourceResolver
|
||||
from src.services.dataset_review.event_logger import SessionEventPayload
|
||||
# [/DEF:DatasetReviewOrchestrator.imports:Block]
|
||||
# [/DEF:imports:Block]
|
||||
|
||||
logger = cast(Any, logger)
|
||||
|
||||
@@ -166,7 +166,7 @@ class LaunchDatasetResult:
|
||||
# @DATA_CONTRACT: Input[StartSessionCommand] -> Output[StartSessionResult]
|
||||
# @INVARIANT: session ownership is preserved on every mutation and recovery remains explicit when partial.
|
||||
class DatasetReviewOrchestrator:
|
||||
# [DEF:DatasetReviewOrchestrator.__init__:Function]
|
||||
# [DEF:DatasetReviewOrchestrator_init:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# @PURPOSE: Bind repository, config, and task dependencies required by the orchestration boundary.
|
||||
# @RELATION: [DEPENDS_ON] ->[SessionRepo]
|
||||
@@ -183,9 +183,9 @@ class DatasetReviewOrchestrator:
|
||||
self.task_manager = task_manager
|
||||
self.semantic_resolver = semantic_resolver or SemanticSourceResolver()
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator.__init__:Function]
|
||||
# [/DEF:DatasetReviewOrchestrator_init:Function]
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator.start_session:Function]
|
||||
# [DEF:start_session:Function]
|
||||
# @COMPLEXITY: 5
|
||||
# @PURPOSE: Initialize a new session from a Superset link or dataset selection and trigger context recovery.
|
||||
# @RELATION: [DEPENDS_ON] ->[SessionRepo]
|
||||
@@ -378,9 +378,9 @@ class DatasetReviewOrchestrator:
|
||||
findings=findings,
|
||||
)
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator.start_session:Function]
|
||||
# [/DEF:start_session:Function]
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator.prepare_launch_preview:Function]
|
||||
# [DEF:prepare_launch_preview:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Assemble effective execution inputs and trigger Superset-side preview compilation.
|
||||
# @RELATION: [CALLS] ->[SupersetCompilationAdapter.compile_preview]
|
||||
@@ -490,9 +490,9 @@ class DatasetReviewOrchestrator:
|
||||
blocked_reasons=[],
|
||||
)
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator.prepare_launch_preview:Function]
|
||||
# [/DEF:prepare_launch_preview:Function]
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator.launch_dataset:Function]
|
||||
# [DEF:launch_dataset:Function]
|
||||
# @COMPLEXITY: 5
|
||||
# @PURPOSE: Start the approved dataset execution through SQL Lab and persist run context for audit/replay.
|
||||
# @RELATION: [CALLS] ->[SupersetCompilationAdapter.create_sql_lab_session]
|
||||
@@ -624,10 +624,10 @@ class DatasetReviewOrchestrator:
|
||||
blocked_reasons=[],
|
||||
)
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator.launch_dataset:Function]
|
||||
# [/DEF:launch_dataset:Function]
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator._parse_dataset_selection:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# [DEF:_parse_dataset_selection:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Normalize dataset-selection payload into canonical session references.
|
||||
# @RELATION: [DEPENDS_ON] ->[DatasetReviewSession]
|
||||
def _parse_dataset_selection(self, source_input: str) -> tuple[str, Optional[int]]:
|
||||
@@ -647,10 +647,10 @@ class DatasetReviewOrchestrator:
|
||||
|
||||
return normalized, None
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator._parse_dataset_selection:Function]
|
||||
# [/DEF:_parse_dataset_selection:Function]
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator._build_initial_profile:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# [DEF:_build_initial_profile:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Create the first profile snapshot so exports and detail views remain usable immediately after intake.
|
||||
# @RELATION: [DEPENDS_ON] ->[DatasetProfile]
|
||||
def _build_initial_profile(
|
||||
@@ -690,9 +690,9 @@ class DatasetReviewOrchestrator:
|
||||
manual_summary_locked=False,
|
||||
)
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator._build_initial_profile:Function]
|
||||
# [/DEF:_build_initial_profile:Function]
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator._build_partial_recovery_findings:Function]
|
||||
# [DEF:_build_partial_recovery_findings:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Project partial Superset intake recovery into explicit findings without blocking session usability.
|
||||
# @RELATION: [DEPENDS_ON] ->[ValidationFinding]
|
||||
@@ -722,9 +722,9 @@ class DatasetReviewOrchestrator:
|
||||
)
|
||||
return findings
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator._build_partial_recovery_findings:Function]
|
||||
# [/DEF:_build_partial_recovery_findings:Function]
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator._build_recovery_bootstrap:Function]
|
||||
# [DEF:_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]
|
||||
@@ -881,9 +881,24 @@ class DatasetReviewOrchestrator:
|
||||
|
||||
return imported_filters, template_variables, execution_mappings, findings
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator._build_recovery_bootstrap:Function]
|
||||
# [/DEF:_build_recovery_bootstrap:Function]
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator._build_execution_snapshot:Function]
|
||||
# [DEF:_extract_effective_filter_value:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Separate normalized filter payload metadata from the user-facing effective filter value.
|
||||
def _extract_effective_filter_value(
|
||||
self, normalized_value: Any, raw_value: Any
|
||||
) -> Any:
|
||||
if isinstance(normalized_value, dict) and (
|
||||
"filter_clauses" in normalized_value
|
||||
or "extra_form_data" in normalized_value
|
||||
):
|
||||
return raw_value
|
||||
return normalized_value if normalized_value is not None else raw_value
|
||||
|
||||
# [/DEF:_extract_effective_filter_value:Function]
|
||||
|
||||
# [DEF:_build_execution_snapshot:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Build effective filters, template params, approvals, and fingerprint for preview and launch gating.
|
||||
# @RELATION: [DEPENDS_ON] ->[DatasetReviewSession]
|
||||
@@ -923,9 +938,10 @@ class DatasetReviewOrchestrator:
|
||||
|
||||
effective_value = mapping.effective_value
|
||||
if effective_value is None:
|
||||
effective_value = imported_filter.normalized_value
|
||||
if effective_value is None:
|
||||
effective_value = imported_filter.raw_value
|
||||
effective_value = self._extract_effective_filter_value(
|
||||
imported_filter.normalized_value,
|
||||
imported_filter.raw_value,
|
||||
)
|
||||
if effective_value is None:
|
||||
effective_value = template_variable.default_value
|
||||
|
||||
@@ -937,19 +953,21 @@ class DatasetReviewOrchestrator:
|
||||
|
||||
mapped_filter_ids.add(imported_filter.filter_id)
|
||||
if effective_value is not None:
|
||||
effective_filters.append(
|
||||
{
|
||||
"mapping_id": mapping.mapping_id,
|
||||
"filter_id": imported_filter.filter_id,
|
||||
"filter_name": imported_filter.filter_name,
|
||||
"display_name": imported_filter.display_name,
|
||||
"variable_id": template_variable.variable_id,
|
||||
"variable_name": template_variable.variable_name,
|
||||
"effective_value": effective_value,
|
||||
"raw_input_value": mapping.raw_input_value,
|
||||
"normalized_filter_payload": imported_filter.normalized_value,
|
||||
}
|
||||
)
|
||||
mapped_filter_payload = {
|
||||
"mapping_id": mapping.mapping_id,
|
||||
"filter_id": imported_filter.filter_id,
|
||||
"filter_name": imported_filter.filter_name,
|
||||
"variable_id": template_variable.variable_id,
|
||||
"variable_name": template_variable.variable_name,
|
||||
"effective_value": effective_value,
|
||||
"raw_input_value": mapping.raw_input_value,
|
||||
}
|
||||
if isinstance(imported_filter.normalized_value, dict):
|
||||
mapped_filter_payload["display_name"] = imported_filter.display_name
|
||||
mapped_filter_payload["normalized_filter_payload"] = (
|
||||
imported_filter.normalized_value
|
||||
)
|
||||
effective_filters.append(mapped_filter_payload)
|
||||
template_params[template_variable.variable_name] = effective_value
|
||||
if mapping.approval_state == ApprovalState.APPROVED:
|
||||
approved_mapping_ids.append(mapping.mapping_id)
|
||||
@@ -963,8 +981,10 @@ class DatasetReviewOrchestrator:
|
||||
if imported_filter.filter_id in mapped_filter_ids:
|
||||
continue
|
||||
effective_value = imported_filter.normalized_value
|
||||
if effective_value is None:
|
||||
effective_value = imported_filter.raw_value
|
||||
effective_value = self._extract_effective_filter_value(
|
||||
imported_filter.normalized_value,
|
||||
imported_filter.raw_value,
|
||||
)
|
||||
if effective_value is None:
|
||||
continue
|
||||
effective_filters.append(
|
||||
@@ -1014,9 +1034,9 @@ class DatasetReviewOrchestrator:
|
||||
"preview_fingerprint": preview_fingerprint,
|
||||
}
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator._build_execution_snapshot:Function]
|
||||
# [/DEF:_build_execution_snapshot:Function]
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator._build_launch_blockers:Function]
|
||||
# [DEF:_build_launch_blockers:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Enforce launch gates from findings, approvals, and current preview truth.
|
||||
# @RELATION: [DEPENDS_ON] ->[CompiledPreview]
|
||||
@@ -1057,9 +1077,9 @@ class DatasetReviewOrchestrator:
|
||||
|
||||
return sorted(set(blockers))
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator._build_launch_blockers:Function]
|
||||
# [/DEF:_build_launch_blockers:Function]
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator._get_latest_preview:Function]
|
||||
# [DEF:_get_latest_preview:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Resolve the current latest preview snapshot for one session aggregate.
|
||||
def _get_latest_preview(
|
||||
@@ -1078,18 +1098,18 @@ class DatasetReviewOrchestrator:
|
||||
reverse=True,
|
||||
)[0]
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator._get_latest_preview:Function]
|
||||
# [/DEF:_get_latest_preview:Function]
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator._compute_preview_fingerprint:Function]
|
||||
# [DEF:_compute_preview_fingerprint:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Produce deterministic execution fingerprint for preview truth and staleness checks.
|
||||
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:_compute_preview_fingerprint:Function]
|
||||
|
||||
# [DEF:DatasetReviewOrchestrator._enqueue_recovery_task:Function]
|
||||
# [DEF:_enqueue_recovery_task:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Link session start to observable async recovery when task infrastructure is available.
|
||||
# @RELATION: [CALLS] ->[create_task]
|
||||
@@ -1147,7 +1167,7 @@ class DatasetReviewOrchestrator:
|
||||
task_id = getattr(task_object, "id", None)
|
||||
return str(task_id) if task_id else None
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator._enqueue_recovery_task:Function]
|
||||
# [/DEF:_enqueue_recovery_task:Function]
|
||||
|
||||
|
||||
# [/DEF:DatasetReviewOrchestrator:Class]
|
||||
|
||||
@@ -21,14 +21,14 @@ from src.models.dataset_review import (
|
||||
from src.services.dataset_review.repositories.session_repository import DatasetReviewSessionRepository
|
||||
|
||||
# [DEF:SessionRepositoryTests:Module]
|
||||
# @COMPLEXITY: 3
|
||||
# @RELATION: BELONGS_TO -> SrcRoot
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Unit tests for DatasetReviewSessionRepository.
|
||||
# @RELATION: TESTS -> [DatasetReviewSessionRepository]
|
||||
|
||||
@pytest.fixture
|
||||
def db_session():
|
||||
# [DEF:db_session:Function]
|
||||
# @COMPLEXITY: 1
|
||||
# @COMPLEXITY: 2
|
||||
# @RELATION: BINDS_TO -> [SessionRepositoryTests]
|
||||
engine = create_engine("sqlite:///:memory:")
|
||||
Base.metadata.create_all(engine)
|
||||
@@ -44,6 +44,8 @@ def db_session():
|
||||
yield session
|
||||
session.close()
|
||||
|
||||
# [DEF:test_create_session:Function]
|
||||
# @RELATION: BINDS_TO -> SessionRepositoryTests
|
||||
def test_create_session(db_session):
|
||||
# @PURPOSE: Verify session creation and persistence.
|
||||
repo = DatasetReviewSessionRepository(db_session)
|
||||
@@ -60,6 +62,10 @@ def test_create_session(db_session):
|
||||
loaded = db_session.query(DatasetReviewSession).filter_by(session_id=session.session_id).first()
|
||||
assert loaded.user_id == "user1"
|
||||
|
||||
# [/DEF:test_create_session:Function]
|
||||
|
||||
# [DEF:test_load_session_detail_ownership:Function]
|
||||
# @RELATION: BINDS_TO -> SessionRepositoryTests
|
||||
def test_load_session_detail_ownership(db_session):
|
||||
# @PURPOSE: Verify ownership enforcement in detail loading.
|
||||
repo = DatasetReviewSessionRepository(db_session)
|
||||
@@ -77,6 +83,10 @@ def test_load_session_detail_ownership(db_session):
|
||||
loaded_wrong = repo.load_session_detail(session.session_id, "wrong_user")
|
||||
assert loaded_wrong is None
|
||||
|
||||
# [/DEF:test_load_session_detail_ownership:Function]
|
||||
|
||||
# [DEF:test_load_session_detail_collaborator:Function]
|
||||
# @RELATION: BINDS_TO -> SessionRepositoryTests
|
||||
def test_load_session_detail_collaborator(db_session):
|
||||
# @PURPOSE: Verify collaborator access in detail loading.
|
||||
repo = DatasetReviewSessionRepository(db_session)
|
||||
@@ -103,6 +113,10 @@ def test_load_session_detail_collaborator(db_session):
|
||||
assert loaded is not None
|
||||
assert loaded.session_id == session.session_id
|
||||
|
||||
# [/DEF:test_load_session_detail_collaborator:Function]
|
||||
|
||||
# [DEF:test_save_preview_marks_stale:Function]
|
||||
# @RELATION: BINDS_TO -> SessionRepositoryTests
|
||||
def test_save_preview_marks_stale(db_session):
|
||||
# @PURPOSE: Verify that saving a new preview marks old ones as stale.
|
||||
repo = DatasetReviewSessionRepository(db_session)
|
||||
@@ -123,6 +137,10 @@ def test_save_preview_marks_stale(db_session):
|
||||
assert p2.preview_status == "ready"
|
||||
assert session.last_preview_id == p2.preview_id
|
||||
|
||||
# [/DEF:test_save_preview_marks_stale:Function]
|
||||
|
||||
# [DEF:test_save_profile_and_findings:Function]
|
||||
# @RELATION: BINDS_TO -> SessionRepositoryTests
|
||||
def test_save_profile_and_findings(db_session):
|
||||
# @PURPOSE: Verify persistence of profile and findings.
|
||||
repo = DatasetReviewSessionRepository(db_session)
|
||||
@@ -173,6 +191,10 @@ def test_save_profile_and_findings(db_session):
|
||||
assert len(final_session.findings) == 1
|
||||
assert final_session.findings[0].code == "WARN1"
|
||||
|
||||
# [/DEF:test_save_profile_and_findings:Function]
|
||||
|
||||
# [DEF:test_save_run_context:Function]
|
||||
# @RELATION: BINDS_TO -> SessionRepositoryTests
|
||||
def test_save_run_context(db_session):
|
||||
# @PURPOSE: Verify saving of run context.
|
||||
repo = DatasetReviewSessionRepository(db_session)
|
||||
@@ -199,6 +221,10 @@ def test_save_run_context(db_session):
|
||||
|
||||
assert session.last_run_context_id == rc.run_context_id
|
||||
|
||||
# [/DEF:test_save_run_context:Function]
|
||||
|
||||
# [DEF:test_list_sessions_for_user:Function]
|
||||
# @RELATION: BINDS_TO -> SessionRepositoryTests
|
||||
def test_list_sessions_for_user(db_session):
|
||||
# @PURPOSE: Verify listing of sessions by user.
|
||||
repo = DatasetReviewSessionRepository(db_session)
|
||||
@@ -213,4 +239,4 @@ def test_list_sessions_for_user(db_session):
|
||||
assert len(sessions) == 2
|
||||
assert all(s.user_id == "user1" for s in sessions)
|
||||
|
||||
# [/DEF:SessionRepositoryTests:Module]
|
||||
# [/DEF:SessionRepositoryTests:Module]# [/DEF:test_list_sessions_for_user:Function]
|
||||
|
||||
@@ -33,6 +33,7 @@ from src.models.dataset_review import (
|
||||
from src.core.logger import belief_scope, logger
|
||||
from src.services.dataset_review.event_logger import SessionEventLogger
|
||||
|
||||
|
||||
# [DEF:SessionRepo:Class]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Enforce ownership-scoped persistence and retrieval for dataset review session aggregates.
|
||||
@@ -45,13 +46,13 @@ from src.services.dataset_review.event_logger import SessionEventLogger
|
||||
# @SIDE_EFFECT: mutates and queries the persistence layer through the injected database session.
|
||||
# @DATA_CONTRACT: Input[OwnedSessionQuery|SessionMutation] -> Output[PersistedSessionAggregate|PersistedChildRecord]
|
||||
class DatasetReviewSessionRepository:
|
||||
|
||||
# [DEF:init_repo:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Bind one live SQLAlchemy session to the repository instance.
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
self.event_logger = SessionEventLogger(db)
|
||||
|
||||
# [/DEF:init_repo:Function]
|
||||
|
||||
# [DEF:get_owned_session:Function]
|
||||
@@ -68,10 +69,14 @@ class DatasetReviewSessionRepository:
|
||||
"Resolving owner-scoped dataset review session for mutation path",
|
||||
extra={"session_id": session_id, "user_id": user_id},
|
||||
)
|
||||
session = self.db.query(DatasetReviewSession).filter(
|
||||
DatasetReviewSession.session_id == session_id,
|
||||
DatasetReviewSession.user_id == user_id,
|
||||
).first()
|
||||
session = (
|
||||
self.db.query(DatasetReviewSession)
|
||||
.filter(
|
||||
DatasetReviewSession.session_id == session_id,
|
||||
DatasetReviewSession.user_id == user_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not session:
|
||||
logger.explore(
|
||||
"Owner-scoped dataset review session lookup failed",
|
||||
@@ -83,6 +88,7 @@ class DatasetReviewSessionRepository:
|
||||
extra={"session_id": session.session_id, "user_id": session.user_id},
|
||||
)
|
||||
return session
|
||||
|
||||
# [/DEF:get_owned_session:Function]
|
||||
|
||||
# [DEF:create_sess:Function]
|
||||
@@ -97,7 +103,10 @@ class DatasetReviewSessionRepository:
|
||||
with belief_scope("DatasetReviewSessionRepository.create_session"):
|
||||
logger.reason(
|
||||
"Persisting dataset review session shell",
|
||||
extra={"user_id": session.user_id, "environment_id": session.environment_id},
|
||||
extra={
|
||||
"user_id": session.user_id,
|
||||
"environment_id": session.environment_id,
|
||||
},
|
||||
)
|
||||
self.db.add(session)
|
||||
self.db.commit()
|
||||
@@ -107,44 +116,58 @@ class DatasetReviewSessionRepository:
|
||||
extra={"session_id": session.session_id, "user_id": session.user_id},
|
||||
)
|
||||
return session
|
||||
|
||||
# [/DEF:create_sess:Function]
|
||||
|
||||
# [DEF:load_detail:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Return the full session aggregate for API and frontend resume flows.
|
||||
# @RELATION: [DEPENDS_ON] -> [DatasetReviewSession]
|
||||
# @RELATION: [DEPENDS_ON] -> [SessionCollaborator]
|
||||
def load_session_detail(self, session_id: str, user_id: str) -> Optional[DatasetReviewSession]:
|
||||
def load_session_detail(
|
||||
self, session_id: str, user_id: str
|
||||
) -> Optional[DatasetReviewSession]:
|
||||
with belief_scope("DatasetReviewSessionRepository.load_session_detail"):
|
||||
logger.reason(
|
||||
"Loading dataset review session detail for owner-or-collaborator scope",
|
||||
extra={"session_id": session_id, "user_id": user_id},
|
||||
)
|
||||
session = self.db.query(DatasetReviewSession)\
|
||||
.outerjoin(SessionCollaborator, DatasetReviewSession.session_id == SessionCollaborator.session_id)\
|
||||
session = (
|
||||
self.db.query(DatasetReviewSession)
|
||||
.outerjoin(
|
||||
SessionCollaborator,
|
||||
DatasetReviewSession.session_id == SessionCollaborator.session_id,
|
||||
)
|
||||
.options(
|
||||
joinedload(DatasetReviewSession.profile),
|
||||
joinedload(DatasetReviewSession.findings),
|
||||
joinedload(DatasetReviewSession.collaborators),
|
||||
joinedload(DatasetReviewSession.semantic_sources),
|
||||
joinedload(DatasetReviewSession.semantic_fields).joinedload(SemanticFieldEntry.candidates),
|
||||
joinedload(DatasetReviewSession.semantic_fields).joinedload(
|
||||
SemanticFieldEntry.candidates
|
||||
),
|
||||
joinedload(DatasetReviewSession.imported_filters),
|
||||
joinedload(DatasetReviewSession.template_variables),
|
||||
joinedload(DatasetReviewSession.execution_mappings),
|
||||
joinedload(DatasetReviewSession.clarification_sessions).joinedload(ClarificationSession.questions).joinedload(ClarificationQuestion.options),
|
||||
joinedload(DatasetReviewSession.clarification_sessions).joinedload(ClarificationSession.questions).joinedload(ClarificationQuestion.answer),
|
||||
joinedload(DatasetReviewSession.clarification_sessions)
|
||||
.joinedload(ClarificationSession.questions)
|
||||
.joinedload(ClarificationQuestion.options),
|
||||
joinedload(DatasetReviewSession.clarification_sessions)
|
||||
.joinedload(ClarificationSession.questions)
|
||||
.joinedload(ClarificationQuestion.answer),
|
||||
joinedload(DatasetReviewSession.previews),
|
||||
joinedload(DatasetReviewSession.run_contexts),
|
||||
joinedload(DatasetReviewSession.events)
|
||||
)\
|
||||
.filter(DatasetReviewSession.session_id == session_id)\
|
||||
joinedload(DatasetReviewSession.events),
|
||||
)
|
||||
.filter(DatasetReviewSession.session_id == session_id)
|
||||
.filter(
|
||||
or_(
|
||||
DatasetReviewSession.user_id == user_id,
|
||||
SessionCollaborator.user_id == user_id
|
||||
SessionCollaborator.user_id == user_id,
|
||||
)
|
||||
)\
|
||||
)
|
||||
.first()
|
||||
)
|
||||
logger.reflect(
|
||||
"Dataset review session detail lookup completed",
|
||||
extra={
|
||||
@@ -154,6 +177,7 @@ class DatasetReviewSessionRepository:
|
||||
},
|
||||
)
|
||||
return session
|
||||
|
||||
# [/DEF:load_detail:Function]
|
||||
|
||||
# [DEF:save_prof_find:Function]
|
||||
@@ -166,7 +190,13 @@ class DatasetReviewSessionRepository:
|
||||
# @POST: stored profile matches the current session and findings are replaced by the supplied collection.
|
||||
# @SIDE_EFFECT: updates profile rows, deletes stale findings, inserts current findings, and commits the transaction.
|
||||
# @DATA_CONTRACT: Input[ProfileAndFindingsMutation] -> Output[DatasetReviewSession]
|
||||
def save_profile_and_findings(self, session_id: str, user_id: str, profile: DatasetProfile, findings: List[ValidationFinding]) -> DatasetReviewSession:
|
||||
def save_profile_and_findings(
|
||||
self,
|
||||
session_id: str,
|
||||
user_id: str,
|
||||
profile: DatasetProfile,
|
||||
findings: List[ValidationFinding],
|
||||
) -> DatasetReviewSession:
|
||||
with belief_scope("DatasetReviewSessionRepository.save_profile_and_findings"):
|
||||
session = self._get_owned_session(session_id, user_id)
|
||||
logger.reason(
|
||||
@@ -180,7 +210,11 @@ class DatasetReviewSessionRepository:
|
||||
)
|
||||
|
||||
if profile:
|
||||
existing_profile = self.db.query(DatasetProfile).filter_by(session_id=session_id).first()
|
||||
existing_profile = (
|
||||
self.db.query(DatasetProfile)
|
||||
.filter_by(session_id=session_id)
|
||||
.first()
|
||||
)
|
||||
if existing_profile:
|
||||
profile.profile_id = existing_profile.profile_id
|
||||
self.db.merge(profile)
|
||||
@@ -203,6 +237,7 @@ class DatasetReviewSessionRepository:
|
||||
},
|
||||
)
|
||||
return self.load_session_detail(session_id, user_id)
|
||||
|
||||
# [/DEF:save_prof_find:Function]
|
||||
|
||||
# [DEF:save_recovery_state:Function]
|
||||
@@ -268,6 +303,7 @@ class DatasetReviewSessionRepository:
|
||||
},
|
||||
)
|
||||
return self.load_session_detail(session_id, user_id)
|
||||
|
||||
# [/DEF:save_recovery_state:Function]
|
||||
|
||||
# [DEF:save_prev:Function]
|
||||
@@ -279,7 +315,9 @@ class DatasetReviewSessionRepository:
|
||||
# @POST: preview is persisted and the session points to the latest preview identifier.
|
||||
# @SIDE_EFFECT: updates prior preview statuses, inserts a preview row, mutates the parent session, and commits.
|
||||
# @DATA_CONTRACT: Input[PreviewMutation] -> Output[CompiledPreview]
|
||||
def save_preview(self, session_id: str, user_id: str, preview: CompiledPreview) -> CompiledPreview:
|
||||
def save_preview(
|
||||
self, session_id: str, user_id: str, preview: CompiledPreview
|
||||
) -> CompiledPreview:
|
||||
with belief_scope("DatasetReviewSessionRepository.save_preview"):
|
||||
session = self._get_owned_session(session_id, user_id)
|
||||
logger.reason(
|
||||
@@ -306,6 +344,7 @@ class DatasetReviewSessionRepository:
|
||||
},
|
||||
)
|
||||
return preview
|
||||
|
||||
# [/DEF:save_prev:Function]
|
||||
|
||||
# [DEF:save_run_ctx:Function]
|
||||
@@ -317,7 +356,9 @@ class DatasetReviewSessionRepository:
|
||||
# @POST: run context is persisted and linked as the latest launch snapshot for the session.
|
||||
# @SIDE_EFFECT: inserts a run-context row, mutates the parent session pointer, and commits.
|
||||
# @DATA_CONTRACT: Input[RunContextMutation] -> Output[DatasetRunContext]
|
||||
def save_run_context(self, session_id: str, user_id: str, run_context: DatasetRunContext) -> DatasetRunContext:
|
||||
def save_run_context(
|
||||
self, session_id: str, user_id: str, run_context: DatasetRunContext
|
||||
) -> DatasetRunContext:
|
||||
with belief_scope("DatasetReviewSessionRepository.save_run_context"):
|
||||
session = self._get_owned_session(session_id, user_id)
|
||||
logger.reason(
|
||||
@@ -340,10 +381,11 @@ class DatasetReviewSessionRepository:
|
||||
},
|
||||
)
|
||||
return run_context
|
||||
|
||||
# [/DEF:save_run_ctx:Function]
|
||||
|
||||
# [DEF:list_user_sess:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: List review sessions owned by a specific user ordered by most recent update.
|
||||
# @RELATION: [DEPENDS_ON] -> [DatasetReviewSession]
|
||||
def list_sessions_for_user(self, user_id: str) -> List[DatasetReviewSession]:
|
||||
@@ -352,15 +394,21 @@ class DatasetReviewSessionRepository:
|
||||
"Listing dataset review sessions for owner scope",
|
||||
extra={"user_id": user_id},
|
||||
)
|
||||
sessions = self.db.query(DatasetReviewSession).filter(
|
||||
DatasetReviewSession.user_id == user_id
|
||||
).order_by(DatasetReviewSession.updated_at.desc()).all()
|
||||
sessions = (
|
||||
self.db.query(DatasetReviewSession)
|
||||
.filter(DatasetReviewSession.user_id == user_id)
|
||||
.order_by(DatasetReviewSession.updated_at.desc())
|
||||
.all()
|
||||
)
|
||||
logger.reflect(
|
||||
"Dataset review session list assembled",
|
||||
extra={"user_id": user_id, "session_count": len(sessions)},
|
||||
)
|
||||
return sessions
|
||||
|
||||
# [/DEF:list_user_sess:Function]
|
||||
|
||||
|
||||
# [/DEF:SessionRepo:Class]
|
||||
|
||||
# [/DEF:DatasetReviewSessionRepository:Module]
|
||||
# [/DEF:DatasetReviewSessionRepository:Module]
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# [DEF:SemanticSourceResolver.imports:Block]
|
||||
# [DEF:imports:Block]
|
||||
from dataclasses import dataclass, field
|
||||
from difflib import SequenceMatcher
|
||||
from typing import Any, Dict, Iterable, List, Mapping, Optional
|
||||
@@ -26,7 +26,7 @@ from src.models.dataset_review import (
|
||||
FieldProvenance,
|
||||
SemanticSource,
|
||||
)
|
||||
# [/DEF:SemanticSourceResolver.imports:Block]
|
||||
# [/DEF:imports:Block]
|
||||
|
||||
|
||||
# [DEF:DictionaryResolutionResult:Class]
|
||||
@@ -50,14 +50,14 @@ class DictionaryResolutionResult:
|
||||
# @POST: result contains confidence-ranked candidates and does not overwrite manual locks implicitly.
|
||||
# @SIDE_EFFECT: emits semantic trace logs for ranking and fallback decisions.
|
||||
class SemanticSourceResolver:
|
||||
# [DEF:SemanticSourceResolver.resolve_from_file:Function]
|
||||
# [DEF:resolve_from_file:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Normalize uploaded semantic file records into field-level candidates.
|
||||
def resolve_from_file(self, source_payload: Mapping[str, Any], fields: Iterable[Mapping[str, Any]]) -> DictionaryResolutionResult:
|
||||
return DictionaryResolutionResult(source_ref=str(source_payload.get("source_ref") or "uploaded_file"))
|
||||
# [/DEF:SemanticSourceResolver.resolve_from_file:Function]
|
||||
# [/DEF:resolve_from_file:Function]
|
||||
|
||||
# [DEF:SemanticSourceResolver.resolve_from_dictionary:Function]
|
||||
# [DEF:resolve_from_dictionary:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Resolve candidates from connected tabular dictionary sources.
|
||||
# @RELATION: [DEPENDS_ON] ->[SemanticFieldEntry]
|
||||
@@ -213,9 +213,9 @@ class SemanticSourceResolver:
|
||||
},
|
||||
)
|
||||
return result
|
||||
# [/DEF:SemanticSourceResolver.resolve_from_dictionary:Function]
|
||||
# [/DEF:resolve_from_dictionary:Function]
|
||||
|
||||
# [DEF:SemanticSourceResolver.resolve_from_reference_dataset:Function]
|
||||
# [DEF:resolve_from_reference_dataset:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Reuse semantic metadata from trusted Superset datasets.
|
||||
def resolve_from_reference_dataset(
|
||||
@@ -224,10 +224,10 @@ class SemanticSourceResolver:
|
||||
fields: Iterable[Mapping[str, Any]],
|
||||
) -> DictionaryResolutionResult:
|
||||
return DictionaryResolutionResult(source_ref=str(source_payload.get("source_ref") or "reference_dataset"))
|
||||
# [/DEF:SemanticSourceResolver.resolve_from_reference_dataset:Function]
|
||||
# [/DEF:resolve_from_reference_dataset:Function]
|
||||
|
||||
# [DEF:SemanticSourceResolver.rank_candidates:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# [DEF:rank_candidates:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Apply confidence ordering and determine best candidate per field.
|
||||
# @RELATION: [DEPENDS_ON] ->[SemanticCandidate]
|
||||
def rank_candidates(self, candidates: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
@@ -242,25 +242,25 @@ class SemanticSourceResolver:
|
||||
for index, candidate in enumerate(ranked, start=1):
|
||||
candidate["candidate_rank"] = index
|
||||
return ranked
|
||||
# [/DEF:SemanticSourceResolver.rank_candidates:Function]
|
||||
# [/DEF:rank_candidates:Function]
|
||||
|
||||
# [DEF:SemanticSourceResolver.detect_conflicts:Function]
|
||||
# [DEF:detect_conflicts:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Mark competing candidate sets that require explicit user review.
|
||||
def detect_conflicts(self, candidates: List[Dict[str, Any]]) -> bool:
|
||||
return len(candidates) > 1
|
||||
# [/DEF:SemanticSourceResolver.detect_conflicts:Function]
|
||||
# [/DEF:detect_conflicts:Function]
|
||||
|
||||
# [DEF:SemanticSourceResolver.apply_field_decision:Function]
|
||||
# [DEF:apply_field_decision:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Accept, reject, or manually override a field-level semantic value.
|
||||
def apply_field_decision(self, field_state: Mapping[str, Any], decision: Mapping[str, Any]) -> Dict[str, Any]:
|
||||
merged = dict(field_state)
|
||||
merged.update(decision)
|
||||
return merged
|
||||
# [/DEF:SemanticSourceResolver.apply_field_decision:Function]
|
||||
# [/DEF:apply_field_decision:Function]
|
||||
|
||||
# [DEF:SemanticSourceResolver.propagate_source_version_update:Function]
|
||||
# [DEF:propagate_source_version_update:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Propagate a semantic source version change to unlocked field entries without silently overwriting manual or locked values.
|
||||
# @RELATION: [DEPENDS_ON] ->[SemanticSource]
|
||||
@@ -315,9 +315,9 @@ class SemanticSourceResolver:
|
||||
"preserved_locked": preserved_locked,
|
||||
"untouched": untouched,
|
||||
}
|
||||
# [/DEF:SemanticSourceResolver.propagate_source_version_update:Function]
|
||||
# [/DEF:propagate_source_version_update:Function]
|
||||
|
||||
# [DEF:SemanticSourceResolver._normalize_dictionary_row:Function]
|
||||
# [DEF:_normalize_dictionary_row:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Normalize one dictionary row into a consistent lookup structure.
|
||||
def _normalize_dictionary_row(self, row: Mapping[str, Any]) -> Dict[str, Any]:
|
||||
@@ -335,9 +335,9 @@ class SemanticSourceResolver:
|
||||
"description": row.get("description"),
|
||||
"display_format": row.get("display_format") or row.get("format"),
|
||||
}
|
||||
# [/DEF:SemanticSourceResolver._normalize_dictionary_row:Function]
|
||||
# [/DEF:_normalize_dictionary_row:Function]
|
||||
|
||||
# [DEF:SemanticSourceResolver._find_fuzzy_matches:Function]
|
||||
# [DEF:_find_fuzzy_matches:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Produce confidence-scored fuzzy matches while keeping them reviewable.
|
||||
def _find_fuzzy_matches(self, field_name: str, rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
@@ -353,9 +353,9 @@ class SemanticSourceResolver:
|
||||
fuzzy_matches.append({"row": row, "score": round(score, 3)})
|
||||
fuzzy_matches.sort(key=lambda item: item["score"], reverse=True)
|
||||
return fuzzy_matches[:3]
|
||||
# [/DEF:SemanticSourceResolver._find_fuzzy_matches:Function]
|
||||
# [/DEF:_find_fuzzy_matches:Function]
|
||||
|
||||
# [DEF:SemanticSourceResolver._build_candidate_payload:Function]
|
||||
# [DEF:_build_candidate_payload:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Project normalized dictionary rows into semantic candidate payloads.
|
||||
def _build_candidate_payload(
|
||||
@@ -374,9 +374,9 @@ class SemanticSourceResolver:
|
||||
"proposed_display_format": row.get("display_format"),
|
||||
"status": CandidateStatus.PROPOSED.value,
|
||||
}
|
||||
# [/DEF:SemanticSourceResolver._build_candidate_payload:Function]
|
||||
# [/DEF:_build_candidate_payload:Function]
|
||||
|
||||
# [DEF:SemanticSourceResolver._match_priority:Function]
|
||||
# [DEF:_match_priority:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Encode trusted-confidence ordering so exact dictionary reuse beats fuzzy invention.
|
||||
def _match_priority(self, match_type: Optional[str]) -> int:
|
||||
@@ -387,14 +387,14 @@ class SemanticSourceResolver:
|
||||
CandidateMatchType.GENERATED.value: 3,
|
||||
}
|
||||
return priority.get(str(match_type or ""), 99)
|
||||
# [/DEF:SemanticSourceResolver._match_priority:Function]
|
||||
# [/DEF:_match_priority:Function]
|
||||
|
||||
# [DEF:SemanticSourceResolver._normalize_key:Function]
|
||||
# @COMPLEXITY: 1
|
||||
# [DEF:_normalize_key:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Normalize field identifiers for stable exact/fuzzy comparisons.
|
||||
def _normalize_key(self, value: str) -> str:
|
||||
return "".join(ch for ch in str(value or "").strip().lower() if ch.isalnum() or ch == "_")
|
||||
# [/DEF:SemanticSourceResolver._normalize_key:Function]
|
||||
# [/DEF:_normalize_key:Function]
|
||||
# [/DEF:SemanticSourceResolver:Class]
|
||||
|
||||
# [/DEF:SemanticSourceResolver:Module]
|
||||
Reference in New Issue
Block a user