Initial commit
This commit is contained in:
393
scripts/utils/validators/requirement_validator.py
Normal file
393
scripts/utils/validators/requirement_validator.py
Normal file
@@ -0,0 +1,393 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Requirement validators for Bubble Tea Designer.
|
||||
Validates user input and extracted requirements.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ValidationLevel(Enum):
|
||||
"""Severity levels for validation results."""
|
||||
CRITICAL = "critical"
|
||||
WARNING = "warning"
|
||||
INFO = "info"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ValidationResult:
|
||||
"""Single validation check result."""
|
||||
check_name: str
|
||||
level: ValidationLevel
|
||||
passed: bool
|
||||
message: str
|
||||
details: Optional[Dict] = None
|
||||
|
||||
|
||||
class ValidationReport:
|
||||
"""Collection of validation results."""
|
||||
|
||||
def __init__(self):
|
||||
self.results: List[ValidationResult] = []
|
||||
|
||||
def add(self, result: ValidationResult):
|
||||
"""Add validation result."""
|
||||
self.results.append(result)
|
||||
|
||||
def has_critical_issues(self) -> bool:
|
||||
"""Check if any critical issues found."""
|
||||
return any(
|
||||
r.level == ValidationLevel.CRITICAL and not r.passed
|
||||
for r in self.results
|
||||
)
|
||||
|
||||
def all_passed(self) -> bool:
|
||||
"""Check if all validations passed."""
|
||||
return all(r.passed for r in self.results)
|
||||
|
||||
def get_warnings(self) -> List[str]:
|
||||
"""Get all warning messages."""
|
||||
return [
|
||||
r.message for r in self.results
|
||||
if r.level == ValidationLevel.WARNING and not r.passed
|
||||
]
|
||||
|
||||
def get_summary(self) -> str:
|
||||
"""Get summary of validation results."""
|
||||
total = len(self.results)
|
||||
passed = sum(1 for r in self.results if r.passed)
|
||||
critical = sum(
|
||||
1 for r in self.results
|
||||
if r.level == ValidationLevel.CRITICAL and not r.passed
|
||||
)
|
||||
|
||||
return (
|
||||
f"Validation: {passed}/{total} passed "
|
||||
f"({critical} critical issues)"
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
'passed': self.all_passed(),
|
||||
'summary': self.get_summary(),
|
||||
'warnings': self.get_warnings(),
|
||||
'critical_issues': [
|
||||
r.message for r in self.results
|
||||
if r.level == ValidationLevel.CRITICAL and not r.passed
|
||||
],
|
||||
'all_results': [
|
||||
{
|
||||
'check': r.check_name,
|
||||
'level': r.level.value,
|
||||
'passed': r.passed,
|
||||
'message': r.message
|
||||
}
|
||||
for r in self.results
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class RequirementValidator:
|
||||
"""Validates TUI requirements and descriptions."""
|
||||
|
||||
def validate_description(self, description: str) -> ValidationReport:
|
||||
"""
|
||||
Validate user-provided description.
|
||||
|
||||
Args:
|
||||
description: Natural language TUI description
|
||||
|
||||
Returns:
|
||||
ValidationReport with results
|
||||
"""
|
||||
report = ValidationReport()
|
||||
|
||||
# Check 1: Not empty
|
||||
report.add(ValidationResult(
|
||||
check_name="not_empty",
|
||||
level=ValidationLevel.CRITICAL,
|
||||
passed=bool(description and description.strip()),
|
||||
message="Description is empty" if not description else "Description provided"
|
||||
))
|
||||
|
||||
if not description:
|
||||
return report
|
||||
|
||||
# Check 2: Minimum length (at least 10 words)
|
||||
words = description.split()
|
||||
min_words = 10
|
||||
has_min_length = len(words) >= min_words
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="minimum_length",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=has_min_length,
|
||||
message=f"Description has {len(words)} words (recommended: ≥{min_words})"
|
||||
))
|
||||
|
||||
# Check 3: Contains actionable verbs
|
||||
action_verbs = ['show', 'display', 'view', 'create', 'select', 'navigate',
|
||||
'edit', 'input', 'track', 'monitor', 'search', 'filter']
|
||||
has_action = any(verb in description.lower() for verb in action_verbs)
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_actions",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=has_action,
|
||||
message="Description contains action verbs" if has_action else
|
||||
"Consider adding action verbs (show, select, edit, etc.)"
|
||||
))
|
||||
|
||||
# Check 4: Contains data type mentions
|
||||
data_types = ['file', 'text', 'data', 'table', 'list', 'log', 'config',
|
||||
'message', 'package', 'item', 'entry']
|
||||
has_data = any(dtype in description.lower() for dtype in data_types)
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_data_types",
|
||||
level=ValidationLevel.INFO,
|
||||
passed=has_data,
|
||||
message="Data types mentioned" if has_data else
|
||||
"No explicit data types mentioned"
|
||||
))
|
||||
|
||||
return report
|
||||
|
||||
def validate_requirements(self, requirements: Dict) -> ValidationReport:
|
||||
"""
|
||||
Validate extracted requirements structure.
|
||||
|
||||
Args:
|
||||
requirements: Structured requirements dict
|
||||
|
||||
Returns:
|
||||
ValidationReport
|
||||
"""
|
||||
report = ValidationReport()
|
||||
|
||||
# Check 1: Has archetype
|
||||
has_archetype = 'archetype' in requirements and requirements['archetype']
|
||||
report.add(ValidationResult(
|
||||
check_name="has_archetype",
|
||||
level=ValidationLevel.CRITICAL,
|
||||
passed=has_archetype,
|
||||
message=f"TUI archetype: {requirements.get('archetype', 'MISSING')}"
|
||||
))
|
||||
|
||||
# Check 2: Has features
|
||||
features = requirements.get('features', [])
|
||||
has_features = len(features) > 0
|
||||
report.add(ValidationResult(
|
||||
check_name="has_features",
|
||||
level=ValidationLevel.CRITICAL,
|
||||
passed=has_features,
|
||||
message=f"Features identified: {len(features)}"
|
||||
))
|
||||
|
||||
# Check 3: Has interactions
|
||||
interactions = requirements.get('interactions', {})
|
||||
keyboard_interactions = interactions.get('keyboard', [])
|
||||
has_interactions = len(keyboard_interactions) > 0
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="has_interactions",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=has_interactions,
|
||||
message=f"Keyboard interactions: {len(keyboard_interactions)}"
|
||||
))
|
||||
|
||||
# Check 4: Has view specification
|
||||
views = requirements.get('views', '')
|
||||
has_views = bool(views)
|
||||
report.add(ValidationResult(
|
||||
check_name="has_view_spec",
|
||||
level=ValidationLevel.WARNING,
|
||||
passed=has_views,
|
||||
message=f"View type: {views or 'unspecified'}"
|
||||
))
|
||||
|
||||
# Check 5: Completeness (has all expected keys)
|
||||
expected_keys = ['archetype', 'features', 'interactions', 'data_types', 'views']
|
||||
missing_keys = set(expected_keys) - set(requirements.keys())
|
||||
|
||||
report.add(ValidationResult(
|
||||
check_name="completeness",
|
||||
level=ValidationLevel.INFO,
|
||||
passed=len(missing_keys) == 0,
|
||||
message=f"Complete structure" if not missing_keys else
|
||||
f"Missing keys: {missing_keys}"
|
||||
))
|
||||
|
||||
return report
|
||||
|
||||
def suggest_clarifications(self, requirements: Dict) -> List[str]:
|
||||
"""
|
||||
Suggest clarifying questions based on incomplete requirements.
|
||||
|
||||
Args:
|
||||
requirements: Extracted requirements
|
||||
|
||||
Returns:
|
||||
List of clarifying questions to ask user
|
||||
"""
|
||||
questions = []
|
||||
|
||||
# Check if archetype is unclear
|
||||
if not requirements.get('archetype') or requirements['archetype'] == 'general':
|
||||
questions.append(
|
||||
"What type of TUI is this? (file manager, installer, dashboard, "
|
||||
"form, viewer, etc.)"
|
||||
)
|
||||
|
||||
# Check if features are vague
|
||||
features = requirements.get('features', [])
|
||||
if len(features) < 2:
|
||||
questions.append(
|
||||
"What are the main features/capabilities needed? "
|
||||
"(e.g., navigation, selection, editing, search, filtering)"
|
||||
)
|
||||
|
||||
# Check if data type is unspecified
|
||||
data_types = requirements.get('data_types', [])
|
||||
if not data_types:
|
||||
questions.append(
|
||||
"What type of data will the TUI display? "
|
||||
"(files, text, tabular data, logs, etc.)"
|
||||
)
|
||||
|
||||
# Check if interaction is unspecified
|
||||
interactions = requirements.get('interactions', {})
|
||||
if not interactions.get('keyboard') and not interactions.get('mouse'):
|
||||
questions.append(
|
||||
"How should users interact? Keyboard only, or mouse support needed?"
|
||||
)
|
||||
|
||||
# Check if view type is unspecified
|
||||
if not requirements.get('views'):
|
||||
questions.append(
|
||||
"Should this be single-view or multi-view? Need tabs or navigation?"
|
||||
)
|
||||
|
||||
return questions
|
||||
|
||||
|
||||
def validate_description_clarity(description: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Quick validation of description clarity.
|
||||
|
||||
Args:
|
||||
description: User description
|
||||
|
||||
Returns:
|
||||
Tuple of (is_clear, message)
|
||||
"""
|
||||
validator = RequirementValidator()
|
||||
report = validator.validate_description(description)
|
||||
|
||||
if report.has_critical_issues():
|
||||
return False, "Description has critical issues: " + report.get_summary()
|
||||
|
||||
warnings = report.get_warnings()
|
||||
if warnings:
|
||||
return True, "Description OK with suggestions: " + "; ".join(warnings)
|
||||
|
||||
return True, "Description is clear"
|
||||
|
||||
|
||||
def validate_requirements_completeness(requirements: Dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
Quick validation of requirements completeness.
|
||||
|
||||
Args:
|
||||
requirements: Extracted requirements dict
|
||||
|
||||
Returns:
|
||||
Tuple of (is_complete, message)
|
||||
"""
|
||||
validator = RequirementValidator()
|
||||
report = validator.validate_requirements(requirements)
|
||||
|
||||
if report.has_critical_issues():
|
||||
return False, "Requirements incomplete: " + report.get_summary()
|
||||
|
||||
warnings = report.get_warnings()
|
||||
if warnings:
|
||||
return True, "Requirements OK with warnings: " + "; ".join(warnings)
|
||||
|
||||
return True, "Requirements complete"
|
||||
|
||||
|
||||
def main():
|
||||
"""Test requirement validator."""
|
||||
print("Testing Requirement Validator\n" + "=" * 50)
|
||||
|
||||
validator = RequirementValidator()
|
||||
|
||||
# Test 1: Empty description
|
||||
print("\n1. Testing empty description...")
|
||||
report = validator.validate_description("")
|
||||
print(f" {report.get_summary()}")
|
||||
assert report.has_critical_issues(), "Should fail for empty description"
|
||||
print(" ✓ Correctly detected empty description")
|
||||
|
||||
# Test 2: Good description
|
||||
print("\n2. Testing good description...")
|
||||
good_desc = "Create a file manager TUI with three-column view showing parent directory, current directory, and file preview"
|
||||
report = validator.validate_description(good_desc)
|
||||
print(f" {report.get_summary()}")
|
||||
print(" ✓ Good description validated")
|
||||
|
||||
# Test 3: Vague description
|
||||
print("\n3. Testing vague description...")
|
||||
vague_desc = "Build a TUI"
|
||||
report = validator.validate_description(vague_desc)
|
||||
print(f" {report.get_summary()}")
|
||||
warnings = report.get_warnings()
|
||||
if warnings:
|
||||
print(f" Warnings: {warnings}")
|
||||
print(" ✓ Vague description detected")
|
||||
|
||||
# Test 4: Requirements validation
|
||||
print("\n4. Testing requirements validation...")
|
||||
requirements = {
|
||||
'archetype': 'file-manager',
|
||||
'features': ['navigation', 'selection', 'preview'],
|
||||
'interactions': {
|
||||
'keyboard': ['arrows', 'enter', 'backspace'],
|
||||
'mouse': []
|
||||
},
|
||||
'data_types': ['files', 'directories'],
|
||||
'views': 'multi'
|
||||
}
|
||||
report = validator.validate_requirements(requirements)
|
||||
print(f" {report.get_summary()}")
|
||||
assert report.all_passed(), "Should pass for complete requirements"
|
||||
print(" ✓ Complete requirements validated")
|
||||
|
||||
# Test 5: Incomplete requirements
|
||||
print("\n5. Testing incomplete requirements...")
|
||||
incomplete = {
|
||||
'archetype': '',
|
||||
'features': []
|
||||
}
|
||||
report = validator.validate_requirements(incomplete)
|
||||
print(f" {report.get_summary()}")
|
||||
assert report.has_critical_issues(), "Should fail for incomplete requirements"
|
||||
print(" ✓ Incomplete requirements detected")
|
||||
|
||||
# Test 6: Clarification suggestions
|
||||
print("\n6. Testing clarification suggestions...")
|
||||
questions = validator.suggest_clarifications(incomplete)
|
||||
print(f" Generated {len(questions)} clarifying questions:")
|
||||
for i, q in enumerate(questions, 1):
|
||||
print(f" {i}. {q}")
|
||||
print(" ✓ Clarifications generated")
|
||||
|
||||
print("\n✅ All tests passed!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user