160 lines
5.6 KiB
Python
160 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Validation Progress Tracker - Track comprehensive validation progress
|
|
|
|
Purpose:
|
|
- Save progress after each story validation
|
|
- Enable resuming interrupted validation runs
|
|
- Provide real-time status updates
|
|
|
|
Created: 2026-01-02
|
|
"""
|
|
|
|
import yaml
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Dict, List
|
|
|
|
|
|
class ValidationProgressTracker:
|
|
"""Tracks validation progress for resumability"""
|
|
|
|
def __init__(self, progress_file: str):
|
|
self.path = Path(progress_file)
|
|
self.data = self._load_or_initialize()
|
|
|
|
def _load_or_initialize(self) -> Dict:
|
|
"""Load existing progress or initialize new"""
|
|
if self.path.exists():
|
|
with open(self.path) as f:
|
|
return yaml.safe_load(f)
|
|
|
|
return {
|
|
'started_at': datetime.now().isoformat(),
|
|
'last_updated': datetime.now().isoformat(),
|
|
'epic_filter': None,
|
|
'total_stories': 0,
|
|
'stories_validated': 0,
|
|
'current_batch': 0,
|
|
'batches_completed': 0,
|
|
'status': 'in-progress',
|
|
'counters': {
|
|
'verified_complete': 0,
|
|
'needs_rework': 0,
|
|
'false_positives': 0,
|
|
'in_progress': 0,
|
|
'total_false_positive_tasks': 0,
|
|
'total_critical_issues': 0,
|
|
},
|
|
'validated_stories': {},
|
|
'remaining_stories': [],
|
|
}
|
|
|
|
def initialize(self, total_stories: int, story_list: List[str], epic_filter: str = None):
|
|
"""Initialize new validation run"""
|
|
self.data['total_stories'] = total_stories
|
|
self.data['remaining_stories'] = story_list
|
|
self.data['epic_filter'] = epic_filter
|
|
self.save()
|
|
|
|
def mark_story_validated(self, story_id: str, result: Dict):
|
|
"""Mark a story as validated with results"""
|
|
self.data['stories_validated'] += 1
|
|
self.data['validated_stories'][story_id] = {
|
|
'category': result.get('category'),
|
|
'score': result.get('verification_score'),
|
|
'false_positives': result.get('false_positive_count', 0),
|
|
'critical_issues': result.get('critical_issues_count', 0),
|
|
'validated_at': datetime.now().isoformat(),
|
|
}
|
|
|
|
# Update counters
|
|
category = result.get('category')
|
|
if category == 'VERIFIED_COMPLETE':
|
|
self.data['counters']['verified_complete'] += 1
|
|
elif category == 'FALSE_POSITIVE':
|
|
self.data['counters']['false_positives'] += 1
|
|
elif category == 'NEEDS_REWORK':
|
|
self.data['counters']['needs_rework'] += 1
|
|
elif category == 'IN_PROGRESS':
|
|
self.data['counters']['in_progress'] += 1
|
|
|
|
self.data['counters']['total_false_positive_tasks'] += result.get('false_positive_count', 0)
|
|
self.data['counters']['total_critical_issues'] += result.get('critical_issues_count', 0)
|
|
|
|
# Remove from remaining
|
|
if story_id in self.data['remaining_stories']:
|
|
self.data['remaining_stories'].remove(story_id)
|
|
|
|
self.data['last_updated'] = datetime.now().isoformat()
|
|
self.save()
|
|
|
|
def mark_batch_complete(self, batch_number: int):
|
|
"""Mark a batch as complete"""
|
|
self.data['batches_completed'] = batch_number
|
|
self.data['current_batch'] = batch_number + 1
|
|
self.save()
|
|
|
|
def mark_complete(self):
|
|
"""Mark entire validation as complete"""
|
|
self.data['status'] = 'complete'
|
|
self.data['completed_at'] = datetime.now().isoformat()
|
|
|
|
# Calculate duration
|
|
started = datetime.fromisoformat(self.data['started_at'])
|
|
completed = datetime.fromisoformat(self.data['completed_at'])
|
|
duration = completed - started
|
|
self.data['duration_hours'] = round(duration.total_seconds() / 3600, 1)
|
|
|
|
self.save()
|
|
|
|
def get_progress_percentage(self) -> float:
|
|
"""Get completion percentage"""
|
|
if self.data['total_stories'] == 0:
|
|
return 0
|
|
return round((self.data['stories_validated'] / self.data['total_stories']) * 100, 1)
|
|
|
|
def get_summary(self) -> Dict:
|
|
"""Get current progress summary"""
|
|
return {
|
|
'progress': f"{self.data['stories_validated']}/{self.data['total_stories']} ({self.get_progress_percentage()}%)",
|
|
'verified_complete': self.data['counters']['verified_complete'],
|
|
'false_positives': self.data['counters']['false_positives'],
|
|
'needs_rework': self.data['counters']['needs_rework'],
|
|
'remaining': len(self.data['remaining_stories']),
|
|
'status': self.data['status'],
|
|
}
|
|
|
|
def save(self):
|
|
"""Save progress to file"""
|
|
with open(self.path, 'w') as f:
|
|
yaml.dump(self.data, f, default_flow_style=False, sort_keys=False)
|
|
|
|
def get_remaining_stories(self) -> List[str]:
|
|
"""Get list of stories not yet validated"""
|
|
return self.data['remaining_stories']
|
|
|
|
def is_complete(self) -> bool:
|
|
"""Check if validation is complete"""
|
|
return self.data['status'] == 'complete'
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# Example usage
|
|
tracker = ValidationProgressTracker('.validation-progress-2026-01-02.yaml')
|
|
|
|
# Initialize
|
|
tracker.initialize(100, ['story-1.md', 'story-2.md', '...'], epic_filter='16e')
|
|
|
|
# Mark story validated
|
|
tracker.mark_story_validated('story-1', {
|
|
'category': 'VERIFIED_COMPLETE',
|
|
'verification_score': 98,
|
|
'false_positive_count': 0,
|
|
'critical_issues_count': 0,
|
|
})
|
|
|
|
# Show progress
|
|
print(tracker.get_summary())
|
|
# Output: {'progress': '1/100 (1.0%)', 'verified_complete': 1, ...}
|