Download design_implementation_validator.py from 77yasu77/groxy: direct link, hf CLI and curl.
- Browser
- Download file 22.1 kB
-
https://huggingface.co/spaces/77yasu77/groxy/resolve/main/design_implementation_validator.py
- Command line
-
hf download hf://spaces/77yasu77/groxy/design_implementation_validator.py
-
curl -L -o design_implementation_validator.py https://huggingface.co/spaces/77yasu77/groxy/resolve/main/design_implementation_validator.py
22.1 kB
| import re | |
| import ast | |
| import difflib | |
| from typing import Dict, Any, List, Optional, Set, Tuple | |
| class DesignImplementationValidator: | |
| """設計書と実装コードの整合性を検証するクラス""" | |
| def __init__(self): | |
| """設計・実装検証クラスの初期化""" | |
| pass | |
| def validate(self, design_doc: str, implementation_code: str) -> Dict[str, Any]: | |
| """設計書と実装コードの整合性を検証する""" | |
| result = { | |
| "is_compliant": True, | |
| "issues": [], | |
| "missing_components": [], | |
| "missing_methods": [], | |
| "missing_requirements": [], | |
| "extra_components": [], | |
| "compliance_score": 0.0, | |
| "suggestion": None | |
| } | |
| # 設計書から要素を抽出 | |
| design_components = self._extract_design_components(design_doc) | |
| # 実装コードから要素を抽出 | |
| implementation_components = self._extract_implementation_components(implementation_code) | |
| # 設計書と実装の整合性を検証 | |
| self._validate_consistency(design_components, implementation_components, result) | |
| # 検証結果に基づいてコンプライアンススコアを計算 | |
| self._calculate_compliance_score(result) | |
| # 改善のための提案を生成 | |
| self._generate_suggestions(result) | |
| return result | |
| def _extract_design_components(self, design_doc: str) -> Dict[str, Any]: | |
| """設計書からコンポーネント、クラス、メソッド、要件などを抽出する""" | |
| components = { | |
| "classes": [], | |
| "interfaces": [], | |
| "methods": {}, | |
| "attributes": {}, | |
| "requirements": [], | |
| "dependencies": [], | |
| "data_structures": [] | |
| } | |
| # 1. クラス定義の抽出 | |
| class_pattern = r'(class\s+(\w+)(?:\s*\(\s*\w+\s*\))?\s*:)' | |
| for match in re.finditer(class_pattern, design_doc): | |
| class_name = match.group(2) | |
| components["classes"].append(class_name) | |
| components["methods"][class_name] = [] | |
| components["attributes"][class_name] = [] | |
| # クラス定義の範囲を特定 | |
| class_start = match.end() | |
| next_class_match = re.search(class_pattern, design_doc[class_start:]) | |
| class_end = class_start + next_class_match.start() if next_class_match else len(design_doc) | |
| class_body = design_doc[class_start:class_end] | |
| # メソッド定義の抽出 | |
| method_pattern = r'def\s+(\w+)\s*\((.*?)\)' | |
| for method_match in re.finditer(method_pattern, class_body): | |
| method_name = method_match.group(1) | |
| components["methods"][class_name].append(method_name) | |
| # 属性の抽出(単純なパターンマッチング) | |
| attribute_pattern = r'self\.(\w+)\s*=' | |
| for attr_match in re.finditer(attribute_pattern, class_body): | |
| attr_name = attr_match.group(1) | |
| if attr_name not in components["attributes"][class_name]: | |
| components["attributes"][class_name].append(attr_name) | |
| # 2. インターフェース定義の抽出(ABC, Protocolなど) | |
| interface_pattern = r'(class\s+(\w+)(?:\s*\(\s*(?:ABC|Protocol)\s*\))?\s*:)' | |
| for match in re.finditer(interface_pattern, design_doc): | |
| interface_name = match.group(2) | |
| if interface_name not in components["classes"]: # クラスとして既に登録されていない場合 | |
| components["interfaces"].append(interface_name) | |
| components["methods"][interface_name] = [] | |
| # インターフェース定義の範囲を特定 | |
| interface_start = match.end() | |
| next_interface_match = re.search(interface_pattern, design_doc[interface_start:]) | |
| interface_end = interface_start + next_interface_match.start() if next_interface_match else len(design_doc) | |
| interface_body = design_doc[interface_start:interface_end] | |
| # メソッド定義の抽出 | |
| method_pattern = r'def\s+(\w+)\s*\((.*?)\)' | |
| for method_match in re.finditer(method_pattern, interface_body): | |
| method_name = method_match.group(1) | |
| components["methods"][interface_name].append(method_name) | |
| # 3. 要件の抽出 | |
| requirement_sections = [ | |
| "要件", "機能要件", "非機能要件", "制約", "前提条件", | |
| "Requirements", "Functional Requirements", "Non-functional Requirements" | |
| ] | |
| for section in requirement_sections: | |
| section_match = re.search(rf'{section}[::]\s*(.*?)(?=\n\n|\n#|\Z)', design_doc, re.DOTALL) | |
| if section_match: | |
| section_text = section_match.group(1).strip() | |
| # 箇条書きの抽出 | |
| bullet_patterns = [ | |
| r'[-*•]\s*(.*?)(?=\n[-*•]|\n\n|\Z)', # ハイフン、アスタリスク、中黒 | |
| r'\d+\.\s*(.*?)(?=\n\d+\.|\n\n|\Z)' # 番号付きリスト | |
| ] | |
| for pattern in bullet_patterns: | |
| for req_match in re.finditer(pattern, section_text, re.DOTALL): | |
| requirement = req_match.group(1).strip() | |
| if requirement and requirement not in components["requirements"]: | |
| components["requirements"].append(requirement) | |
| # 4. データ構造の抽出 | |
| data_structure_patterns = [ | |
| r'(class|struct)\s+(\w+)', # クラスまたは構造体 | |
| r'(enum)\s+(\w+)', # 列挙型 | |
| r'(type|typedef)\s+(\w+)' # 型定義 | |
| ] | |
| for pattern in data_structure_patterns: | |
| for match in re.finditer(pattern, design_doc): | |
| structure_type = match.group(1) | |
| structure_name = match.group(2) | |
| if structure_name not in components["classes"] and structure_name not in components["interfaces"]: | |
| components["data_structures"].append(structure_name) | |
| # 5. 依存関係の抽出 | |
| dependency_patterns = [ | |
| r'依存[::]\s*(.*?)(?=\n\n|\Z)', | |
| r'Dependencies[::]\s*(.*?)(?=\n\n|\Z)', | |
| r'利用[::]\s*(.*?)(?=\n\n|\Z)', | |
| r'Uses[::]\s*(.*?)(?=\n\n|\Z)', | |
| r'import\s+(\w+)', | |
| r'from\s+(\w+)\s+import' | |
| ] | |
| for pattern in dependency_patterns: | |
| for match in re.finditer(pattern, design_doc): | |
| dependency = match.group(1).strip() | |
| if dependency and dependency not in components["dependencies"]: | |
| components["dependencies"].append(dependency) | |
| return components | |
| def _extract_implementation_components(self, implementation_code: str) -> Dict[str, Any]: | |
| """実装コードからコンポーネント、クラス、メソッド、属性などを抽出する""" | |
| components = { | |
| "classes": [], | |
| "interfaces": [], | |
| "methods": {}, | |
| "attributes": {}, | |
| "imports": [], | |
| "functions": [] | |
| } | |
| try: | |
| # ASTを使ってPythonコードを解析 | |
| tree = ast.parse(implementation_code) | |
| # インポート文の抽出 | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.Import): | |
| for name in node.names: | |
| components["imports"].append(name.name) | |
| elif isinstance(node, ast.ImportFrom): | |
| if node.module: | |
| components["imports"].append(node.module) | |
| # クラスの抽出 | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.ClassDef): | |
| class_name = node.name | |
| # インターフェース(抽象クラス)かどうかを判定 | |
| is_interface = False | |
| for base in node.bases: | |
| if isinstance(base, ast.Name) and base.id in ["ABC", "Protocol"]: | |
| is_interface = True | |
| break | |
| if is_interface: | |
| components["interfaces"].append(class_name) | |
| else: | |
| components["classes"].append(class_name) | |
| components["methods"][class_name] = [] | |
| components["attributes"][class_name] = [] | |
| # メソッドと属性の抽出 | |
| for item in node.body: | |
| if isinstance(item, ast.FunctionDef): | |
| components["methods"][class_name].append(item.name) | |
| elif isinstance(item, ast.Assign): | |
| for target in item.targets: | |
| if isinstance(target, ast.Name): | |
| components["attributes"][class_name].append(target.id) | |
| # トップレベル関数の抽出 | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.FunctionDef) and node.name not in [m for methods in components["methods"].values() for m in methods]: | |
| if not any(isinstance(parent, ast.ClassDef) for parent in ast.iter_child_nodes(tree)): | |
| components["functions"].append(node.name) | |
| except SyntaxError as e: | |
| print(f"[Warning] Syntax error during code analysis: {e}") | |
| # 構文エラーの場合は正規表現ベースのフォールバック解析を使用 | |
| self._fallback_extract_implementation_components(implementation_code, components) | |
| return components | |
| def _fallback_extract_implementation_components(self, code: str, components: Dict[str, Any]) -> None: | |
| """構文エラーがある場合のフォールバック抽出メソッド(正規表現ベース)""" | |
| # クラス定義の抽出 | |
| class_pattern = r'class\s+(\w+)(?:\s*\(.*?\))?\s*:' | |
| for match in re.finditer(class_pattern, code): | |
| class_name = match.group(1) | |
| if class_name not in components["classes"]: | |
| components["classes"].append(class_name) | |
| components["methods"][class_name] = [] | |
| components["attributes"][class_name] = [] | |
| # クラス本体の抽出 | |
| class_start = match.end() | |
| # 簡易的なブロック終了の検出(インデントベース) | |
| class_body = "" | |
| in_class = True | |
| indent_level = None | |
| for line in code[class_start:].split('\n'): | |
| if not line.strip(): | |
| class_body += line + '\n' | |
| continue | |
| current_indent = len(line) - len(line.lstrip()) | |
| if indent_level is None: | |
| if current_indent > 0: # クラス本体の最初の行 | |
| indent_level = current_indent | |
| class_body += line + '\n' | |
| continue | |
| if current_indent >= indent_level: | |
| class_body += line + '\n' | |
| else: | |
| break # クラスブロックの終了 | |
| # メソッド定義の抽出 | |
| method_pattern = r'def\s+(\w+)\s*\(' | |
| for method_match in re.finditer(method_pattern, class_body): | |
| method_name = method_match.group(1) | |
| if method_name not in components["methods"][class_name]: | |
| components["methods"][class_name].append(method_name) | |
| # 属性の抽出 | |
| attribute_pattern = r'self\.(\w+)\s*=' | |
| for attr_match in re.finditer(attribute_pattern, class_body): | |
| attr_name = attr_match.group(1) | |
| if attr_name not in components["attributes"][class_name]: | |
| components["attributes"][class_name].append(attr_name) | |
| # インポート文の抽出 | |
| import_pattern = r'import\s+(\w+)' | |
| for match in re.finditer(import_pattern, code): | |
| import_name = match.group(1) | |
| if import_name not in components["imports"]: | |
| components["imports"].append(import_name) | |
| from_import_pattern = r'from\s+(\w+)\s+import' | |
| for match in re.finditer(from_import_pattern, code): | |
| import_name = match.group(1) | |
| if import_name not in components["imports"]: | |
| components["imports"].append(import_name) | |
| # トップレベル関数の抽出 | |
| function_pattern = r'^def\s+(\w+)\s*\(' | |
| for match in re.finditer(function_pattern, code, re.MULTILINE): | |
| func_name = match.group(1) | |
| if func_name not in components["functions"]: | |
| components["functions"].append(func_name) | |
| def _validate_consistency(self, design: Dict[str, Any], implementation: Dict[str, Any], result: Dict[str, Any]) -> None: | |
| """設計と実装の整合性を検証する""" | |
| # 1. クラスの整合性チェック | |
| for class_name in design["classes"]: | |
| if class_name not in implementation["classes"] and class_name not in implementation["interfaces"]: | |
| result["is_compliant"] = False | |
| result["missing_components"].append(f"Class: {class_name}") | |
| result["issues"].append(f"設計書で定義されているクラス '{class_name}' が実装に存在しません。") | |
| # 2. インターフェースの整合性チェック | |
| for interface_name in design["interfaces"]: | |
| if interface_name not in implementation["interfaces"] and interface_name not in implementation["classes"]: | |
| result["is_compliant"] = False | |
| result["missing_components"].append(f"Interface: {interface_name}") | |
| result["issues"].append(f"設計書で定義されているインターフェース '{interface_name}' が実装に存在しません。") | |
| # 3. メソッドの整合性チェック | |
| for class_name, methods in design["methods"].items(): | |
| if class_name in implementation["methods"]: | |
| for method_name in methods: | |
| if method_name not in implementation["methods"][class_name]: | |
| result["is_compliant"] = False | |
| result["missing_methods"].append(f"{class_name}.{method_name}") | |
| result["issues"].append(f"設計書で定義されているメソッド '{class_name}.{method_name}' が実装に存在しません。") | |
| # 4. 属性の整合性チェック(厳密でない - 属性は実装時に追加されることもある) | |
| for class_name, attrs in design["attributes"].items(): | |
| if class_name in implementation["attributes"]: | |
| for attr_name in attrs: | |
| if attr_name not in implementation["attributes"][class_name]: | |
| # 属性の不一致は警告レベル | |
| result["issues"].append(f"設計書で定義されている属性 '{class_name}.{attr_name}' が実装に存在しません(警告)。") | |
| # 5. 要件の実装検証(キーワードベースの簡易検証) | |
| code_text = implementation_code if hasattr(self, 'implementation_code') else "" | |
| for req in design["requirements"]: | |
| # 要件からキーワードを抽出 | |
| keywords = self._extract_keywords(req) | |
| # キーワードが実装に含まれているかチェック | |
| if keywords and not all(keyword.lower() in code_text.lower() for keyword in keywords if len(keyword) > 3): | |
| result["is_compliant"] = False | |
| result["missing_requirements"].append(req) | |
| result["issues"].append(f"要件 '{req}' が実装に反映されていない可能性があります。") | |
| # 6. 実装に存在する余分なコンポーネントを確認 | |
| for class_name in implementation["classes"]: | |
| if class_name not in design["classes"] and class_name not in design["interfaces"]: | |
| result["extra_components"].append(f"Class: {class_name}") | |
| # 余分なコンポーネントは警告レベル | |
| result["issues"].append(f"実装に存在するクラス '{class_name}' が設計書に定義されていません(警告)。") | |
| def _extract_keywords(self, text: str) -> List[str]: | |
| """テキストから重要なキーワードを抽出する""" | |
| # ストップワードを定義 | |
| stop_words = { | |
| "a", "an", "the", "this", "that", "these", "those", | |
| "is", "are", "was", "were", "be", "been", "being", | |
| "have", "has", "had", "do", "does", "did", "will", "would", "shall", "should", | |
| "can", "could", "may", "might", "must", "and", "or", "but", "if", "then", "else", | |
| "when", "where", "why", "how", "all", "any", "both", "each", "few", "more", "most", | |
| "other", "some", "such", "no", "nor", "not", "only", "own", "same", "so", "than", | |
| "too", "very", "を", "に", "は", "が", "で", "と", "から", "まで", "より", "して", "する", "ます", "です" | |
| } | |
| # 単語分割(英語と日本語の両方に対応) | |
| words = [] | |
| # 英単語の抽出 | |
| english_words = re.findall(r'\b[a-zA-Z]+\b', text.lower()) | |
| words.extend(english_words) | |
| # 日本語単語の簡易抽出(文字単位の分割) | |
| japanese_text = re.sub(r'[a-zA-Z0-9\s]', '', text) | |
| for char in japanese_text: | |
| if char not in "、。!?「」『』()[]【】…:;": | |
| words.append(char) | |
| # ストップワードと短すぎる単語を除外 | |
| keywords = [w for w in words if w not in stop_words and len(w) > 1] | |
| # 重複を削除 | |
| unique_keywords = list(set(keywords)) | |
| return unique_keywords | |
| def _calculate_compliance_score(self, result: Dict[str, Any]) -> None: | |
| """コンプライアンススコアを計算する(0.0〜10.0の範囲)""" | |
| # 初期スコア | |
| score = 10.0 | |
| # 不足しているコンポーネントごとに減点 | |
| score -= len(result["missing_components"]) * 2.0 | |
| # 不足しているメソッドごとに減点 | |
| score -= len(result["missing_methods"]) * 1.0 | |
| # 不足している要件ごとに減点 | |
| score -= len(result["missing_requirements"]) * 1.5 | |
| # 余分なコンポーネントごとに軽く減点 | |
| score -= len(result["extra_components"]) * 0.5 | |
| # スコアの範囲を調整 | |
| result["compliance_score"] = max(0.0, min(10.0, score)) | |
| # コンプライアンス判定(スコアが7.0以上で合格) | |
| result["is_compliant"] = result["compliance_score"] >= 7.0 | |
| def _generate_suggestions(self, result: Dict[str, Any]) -> None: | |
| """検証結果に基づいて改善提案を生成する""" | |
| if result["is_compliant"]: | |
| if result["compliance_score"] == 10.0: | |
| result["suggestion"] = "設計書と実装は完全に一致しています。素晴らしい実装です。" | |
| else: | |
| result["suggestion"] = "設計書と実装は概ね一致していますが、いくつかの軽微な問題があります。詳細は issues リストを確認してください。" | |
| else: | |
| # 不足コンポーネントの提案 | |
| if result["missing_components"]: | |
| result["suggestion"] = f"設計書で定義されている以下のコンポーネントを実装してください: {', '.join(result['missing_components'][:3])}" | |
| if len(result["missing_components"]) > 3: | |
| result["suggestion"] += f" ... 他 {len(result['missing_components']) - 3} 件" | |
| # 不足メソッドの提案 | |
| elif result["missing_methods"]: | |
| result["suggestion"] = f"設計書で定義されている以下のメソッドを実装してください: {', '.join(result['missing_methods'][:3])}" | |
| if len(result["missing_methods"]) > 3: | |
| result["suggestion"] += f" ... 他 {len(result['missing_methods']) - 3} 件" | |
| # 不足要件の提案 | |
| elif result["missing_requirements"]: | |
| result["suggestion"] = "以下の要件が実装に反映されていない可能性があります。要件を満たす機能を実装してください。" | |
| for i, req in enumerate(result["missing_requirements"][:3]): | |
| result["suggestion"] += f"\n{i+1}. {req}" | |
| if len(result["missing_requirements"]) > 3: | |
| result["suggestion"] += f"\n... 他 {len(result['missing_requirements']) - 3} 件" | |
| # 一般的な提案 | |
| else: | |
| result["suggestion"] = "設計書と実装の間にいくつかの不一致があります。issues リストを確認して修正してください。" |