| | """ |
| | Workflow configuration for reference checking. |
| | |
| | Allows users to customize the order and enable/disable individual fetchers |
| | in the reference verification workflow. |
| | """ |
| | import json |
| | from dataclasses import dataclass, field, asdict |
| | from pathlib import Path |
| | from typing import List, Optional |
| |
|
| |
|
| | @dataclass |
| | class WorkflowStep: |
| | """A single step in the reference checking workflow.""" |
| | name: str |
| | display_name: str |
| | description: str |
| | enabled: bool = True |
| | priority: int = 0 |
| | |
| | |
| | search_type: str = 'by_title' |
| | |
| | def to_dict(self) -> dict: |
| | return asdict(self) |
| | |
| | @classmethod |
| | def from_dict(cls, data: dict) -> 'WorkflowStep': |
| | return cls(**data) |
| |
|
| |
|
| | @dataclass |
| | class WorkflowConfig: |
| | """Configuration for the reference checking workflow.""" |
| | steps: List[WorkflowStep] = field(default_factory=list) |
| | name: str = "default" |
| | description: str = "Default workflow configuration" |
| | |
| | def get_enabled_steps(self) -> List[WorkflowStep]: |
| | """Get only enabled steps, sorted by priority.""" |
| | return sorted( |
| | [s for s in self.steps if s.enabled], |
| | key=lambda x: x.priority |
| | ) |
| | |
| | def move_step_up(self, index: int) -> bool: |
| | """Move a step up in priority (swap with previous).""" |
| | if index <= 0 or index >= len(self.steps): |
| | return False |
| | self.steps[index], self.steps[index - 1] = self.steps[index - 1], self.steps[index] |
| | self._update_priorities() |
| | return True |
| | |
| | def move_step_down(self, index: int) -> bool: |
| | """Move a step down in priority (swap with next).""" |
| | if index < 0 or index >= len(self.steps) - 1: |
| | return False |
| | self.steps[index], self.steps[index + 1] = self.steps[index + 1], self.steps[index] |
| | self._update_priorities() |
| | return True |
| | |
| | def toggle_step(self, index: int) -> bool: |
| | """Toggle enabled status of a step.""" |
| | if 0 <= index < len(self.steps): |
| | self.steps[index].enabled = not self.steps[index].enabled |
| | return True |
| | return False |
| | |
| | def _update_priorities(self): |
| | """Update priority values based on current order.""" |
| | for i, step in enumerate(self.steps): |
| | step.priority = i |
| | |
| | def to_dict(self) -> dict: |
| | return { |
| | 'name': self.name, |
| | 'description': self.description, |
| | 'steps': [s.to_dict() for s in self.steps] |
| | } |
| | |
| | @classmethod |
| | def from_dict(cls, data: dict) -> 'WorkflowConfig': |
| | steps = [WorkflowStep.from_dict(s) for s in data.get('steps', [])] |
| | return cls( |
| | steps=steps, |
| | name=data.get('name', 'custom'), |
| | description=data.get('description', '') |
| | ) |
| | |
| | def save(self, filepath: str): |
| | """Save workflow configuration to JSON file.""" |
| | path = Path(filepath) |
| | path.parent.mkdir(parents=True, exist_ok=True) |
| | with open(path, 'w', encoding='utf-8') as f: |
| | json.dump(self.to_dict(), f, indent=2) |
| | |
| | @classmethod |
| | def load(cls, filepath: str) -> 'WorkflowConfig': |
| | """Load workflow configuration from JSON file.""" |
| | with open(filepath, 'r', encoding='utf-8') as f: |
| | data = json.load(f) |
| | return cls.from_dict(data) |
| |
|
| |
|
| | |
| | DEFAULT_WORKFLOW = WorkflowConfig( |
| | name="default", |
| | description="Default reference checking workflow prioritizing reliable APIs", |
| | steps=[ |
| | WorkflowStep( |
| | name="arxiv_id", |
| | display_name="arXiv by ID", |
| | description="Look up paper by arXiv ID (highest priority for arXiv papers)", |
| | priority=0, |
| | search_type="by_id" |
| | ), |
| | WorkflowStep( |
| | name="crossref_doi", |
| | display_name="CrossRef by DOI", |
| | description="Look up paper by DOI (authoritative for DOIs)", |
| | priority=1, |
| | search_type="by_doi" |
| | ), |
| | WorkflowStep( |
| | name="semantic_scholar", |
| | display_name="Semantic Scholar", |
| | description="Official API with high quality metadata", |
| | priority=2, |
| | search_type="by_title" |
| | ), |
| | WorkflowStep( |
| | name="dblp", |
| | display_name="DBLP", |
| | description="Official API, especially good for CS publications", |
| | priority=3, |
| | search_type="by_title" |
| | ), |
| | WorkflowStep( |
| | name="openalex", |
| | display_name="OpenAlex", |
| | description="Official API with broad coverage", |
| | priority=4, |
| | search_type="by_title" |
| | ), |
| | WorkflowStep( |
| | name="arxiv_title", |
| | display_name="arXiv by Title", |
| | description="Search arXiv by title (fallback for non-ID lookups)", |
| | priority=5, |
| | search_type="by_title" |
| | ), |
| | WorkflowStep( |
| | name="crossref_title", |
| | display_name="CrossRef by Title", |
| | description="Search CrossRef by title", |
| | priority=6, |
| | search_type="by_title" |
| | ), |
| | WorkflowStep( |
| | name="google_scholar", |
| | display_name="Google Scholar", |
| | description="Web scraping fallback (may be rate-limited or blocked)", |
| | priority=7, |
| | search_type="by_title", |
| | enabled=True |
| | ), |
| | ] |
| | ) |
| |
|
| |
|
| | def get_default_workflow() -> WorkflowConfig: |
| | """Get a fresh copy of the default workflow.""" |
| | return WorkflowConfig.from_dict(DEFAULT_WORKFLOW.to_dict()) |
| |
|