Розробка AI-системи управління технічним боргом (Tech Debt AI)

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1Усі 1566 послуг
Розробка AI-системи управління технічним боргом (Tech Debt AI)
Середній
~1-2 тижні
Часті запитання

Напрямки AI-розробки

Етапи розробки AI-рішення

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1284
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1196
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    901
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1119
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    586
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    853

AI-система управління технічним довгом

Технічний борг — це накопичені компроміси: "зробимо швидко зараз, перепишемо пізніше". Проблема в тому, що "пізніше" ніколи не наступає, тому що важко виміряти борг та обґрунтувати час на рефакторинг менеджменту. AI-система робить технічний борг вимірюваним, пріоритизованим і керованим як звичайний backlog.

Виявлення та вимірювання боргу

from anthropic import Anthropic
from pathlib import Path
import ast
import subprocess
import json
from dataclasses import dataclass, field
from typing import Literal

client = Anthropic()

@dataclass
class DebtItem:
    id: str
    file: str
    category: Literal["code_smell", "architecture", "security", "test_coverage", "documentation", "dependency"]
    severity: Literal["critical", "high", "medium", "low"]
    title: str
    description: str
    estimated_hours: float
    business_impact: str
    quick_fix: bool = False

class TechDebtScanner:

    def scan_project(self, project_root: str) -> list[DebtItem]:
        """Повне сканування технічного боргу проекту"""
        all_items = []

        # 1. Залежності з вразливостями
        all_items.extend(self._scan_dependencies(project_root))

        # 2. Складність коду
        all_items.extend(self._scan_complexity(project_root))

        # 3. AI-аналіз архітектурних проблем
        all_items.extend(self._ai_scan_architecture(project_root))

        # 4. TODO/FIXME/HACK коментарі
        all_items.extend(self._scan_comments(project_root))

        return all_items

    def _scan_dependencies(self, project_root: str) -> list[DebtItem]:
        """Сканує залежності на вразливості та застарілі версії"""
        items = []

        # Safety для Python залежностей
        result = subprocess.run(
            ["safety", "check", "--json", "--full-report"],
            capture_output=True, text=True, cwd=project_root
        )

        if result.returncode != 0 and result.stdout:
            try:
                vulns = json.loads(result.stdout)
                for vuln in vulns:
                    items.append(DebtItem(
                        id=f"dep_{vuln.get('package_name', 'unknown')}",
                        file="requirements.txt",
                        category="security",
                        severity="critical" if "critical" in str(vuln).lower() else "high",
                        title=f"Вразливість у {vuln.get('package_name')} {vuln.get('affected_versions')}",
                        description=vuln.get("vulnerability", ""),
                        estimated_hours=0.5,
                        business_impact="Потенційна вразливість безпеки",
                        quick_fix=True,
                    ))
            except json.JSONDecodeError:
                pass

        # pip-audit як альтернатива
        result = subprocess.run(
            ["pip-audit", "--format=json"],
            capture_output=True, text=True, cwd=project_root
        )
        # Обробка аналогічно...

        return items

    def _scan_complexity(self, project_root: str) -> list[DebtItem]:
        """Знаходить функції з високою циклічною складністю"""
        items = []

        result = subprocess.run(
            ["radon", "cc", "-j", "-n", "C", project_root],  # Тільки оцінка C та вище
            capture_output=True, text=True
        )

        if result.stdout:
            data = json.loads(result.stdout)
            for file_path, functions in data.items():
                for func in functions:
                    complexity = func.get("complexity", 0)
                    if complexity >= 10:
                        hours = complexity * 0.5  # Груба оцінка
                        items.append(DebtItem(
                            id=f"cc_{file_path}_{func['name']}",
                            file=file_path,
                            category="code_smell",
                            severity="critical" if complexity >= 20 else "high" if complexity >= 15 else "medium",
                            title=f"Висока складність: {func['name']} (CC={complexity})",
                            description=f"Циклічна складність {complexity} перевищує поріг 10. Функція важко тестується та підтримується.",
                            estimated_hours=hours,
                            business_impact="Збільшує час змін, ризик багів при модифікації",
                        ))

        return items

    def _scan_comments(self, project_root: str) -> list[DebtItem]:
        """Знаходить TODO/FIXME/HACK маркери"""
        items = []

        result = subprocess.run(
            ["grep", "-rn", "--include=*.py", r"#\s*\(TODO\|FIXME\|HACK\|XXX\|BUG\)", project_root],
            capture_output=True, text=True
        )

        for line in result.stdout.splitlines():
            if ":" in line:
                parts = line.split(":", 2)
                if len(parts) >= 3:
                    file_path, line_num, comment = parts
                    severity = "high" if "HACK" in comment or "FIXME" in comment else "low"
                    items.append(DebtItem(
                        id=f"todo_{hash(line)}",
                        file=file_path,
                        category="code_smell",
                        severity=severity,
                        title=f"Технічний маркер у коді",
                        description=comment.strip(),
                        estimated_hours=2.0,
                        business_impact="Задокументований технічний борг",
                        quick_fix=False,
                    ))

        return items

    def _ai_scan_architecture(self, project_root: str) -> list[DebtItem]:
        """AI-аналіз архітектурних проблем"""
        items = []

        # Читаємо структуру проекту
        structure = []
        for root, dirs, files in Path(project_root).walk():
            dirs[:] = [d for d in dirs if d not in {".git", "__pycache__", ".venv"}]
            for f in files:
                if f.endswith(".py"):
                    structure.append(str(Path(root) / f))

        # Аналізуємо файли розміром > 500 рядків (потенційні God Objects)
        large_files = []
        for fp in structure[:50]:
            try:
                lines = Path(fp).read_text().splitlines()
                if len(lines) > 500:
                    large_files.append((fp, len(lines)))
            except Exception:
                pass

        if not large_files:
            return items

        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=2048,
            messages=[{
                "role": "user",
                "content": f"""Проаналізуй список великих файлів на архітектурні проблеми.

Файли (шлях, кількість рядків):
{json.dumps(large_files, ensure_ascii=False)}

Поверни JSON:
[{{
  "file": "...",
  "issue": "...",
  "severity": "high|medium",
  "estimated_hours": <число>,
  "recommendation": "..."
}}]"""
            }]
        )

        text = response.content[0].text
        start = text.find("[")
        end = text.rfind("]") + 1
        arch_issues = json.loads(text[start:end])

        for issue in arch_issues:
            items.append(DebtItem(
                id=f"arch_{hash(issue['file'])}",
                file=issue["file"],
                category="architecture",
                severity=issue.get("severity", "medium"),
                title=issue.get("issue", "Архітектурна проблема"),
                description=issue.get("recommendation", ""),
                estimated_hours=issue.get("estimated_hours", 8.0),
                business_impact="Уповільнює розробку нових фіч",
            ))

        return items

Пріоритизація та планування

class TechDebtPlanner:

    def prioritize(
        self,
        items: list[DebtItem],
        available_hours: float,
        team_velocity: float = 0.7,
    ) -> dict:
        """Пріоритизує борг з урахуванням доступного часу"""

        # Скор = impact * urgency / effort
        severity_weights = {"critical": 100, "high": 40, "medium": 10, "low": 2}

        scored = []
        for item in items:
            base_score = severity_weights[item.severity]
            effort = max(item.estimated_hours, 0.5)
            # Quick fixes в пріоритеті
            if item.quick_fix:
                base_score *= 2
            score = base_score / effort
            scored.append((score, item))

        scored.sort(key=lambda x: x[0], reverse=True)

        # Формуємо план
        selected = []
        total_hours = 0.0
        effective_hours = available_hours * team_velocity

        for _, item in scored:
            if total_hours + item.estimated_hours <= effective_hours:
                selected.append(item)
                total_hours += item.estimated_hours

        return {
            "selected_items": selected,
            "total_hours": total_hours,
            "debt_reduced_hours": total_hours,
            "remaining_items": [item for _, item in scored if item not in selected],
            "sprint_capacity": available_hours,
        }

    def generate_jira_tickets(self, items: list[DebtItem]) -> list[dict]:
        """Генерує Jira-задачі для технічного боргу"""

        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            messages=[{
                "role": "user",
                "content": f"""Сформуй Jira-задачі для технічного боргу.

Елементи боргу:
{json.dumps([{
    "title": i.title,
    "description": i.description,
    "severity": i.severity,
    "hours": i.estimated_hours,
    "business_impact": i.business_impact,
} for i in items], ensure_ascii=False, indent=2)}

Для кожного створи Jira-задачу:
{{
  "summary": "...",
  "description": "...",
  "story_points": <1-13>,
  "priority": "Highest|High|Medium|Low",
  "labels": ["tech-debt", "<category>"],
  "acceptance_criteria": ["..."]
}}"""
            }]
        )

        text = response.content[0].text
        start = text.find("[")
        end = text.rfind("]") + 1
        return json.loads(text[start:end])

Дашборд технічного боргу

def generate_debt_dashboard(project_root: str) -> dict:
    """Генерує повний звіт по технічному борзі"""
    scanner = TechDebtScanner()
    items = scanner.scan_project(project_root)

    by_severity = {}
    for item in items:
        by_severity.setdefault(item.severity, []).append(item)

    total_hours = sum(i.estimated_hours for i in items)

    return {
        "total_items": len(items),
        "total_hours": total_hours,
        "debt_index": round(total_hours / max(len(list(Path(project_root).rglob("*.py"))), 1), 2),
        "by_severity": {k: len(v) for k, v in by_severity.items()},
        "by_category": {},
        "top_10_critical": sorted(
            [i for i in items if i.severity in ("critical", "high")],
            key=lambda x: x.estimated_hours,
            reverse=True
        )[:10],
    }

Практичний кейс: SaaS з 4-річним довгом

Контекст: SaaS для HR, 4 роки розробки, 3 зміни команди. Скарги: будь-яка нова фіча займає в 3–4 рази довше очікуваного.

Результати сканування:

  • 847 елементів технічного боргу
  • Критичних: 23 (12 вразливостей у залежностях, 11 God Objects)
  • Загальна оцінка: 1340 годин для повного погашення
  • Debt Index: 8.7 (високий — норма < 3.0)

План дій:

  • Sprint 1 (20г): всі вразливості залежностей — оновлення пакетів, 0.5г кожна
  • Sprint 2–4 (60г): 4 головні God Object → декомпозиція
  • Sprint 5–8 (80г): test coverage з 28% → 70%

Результати за 4 місяці:

  • Debt Index: 8.7 → 3.2
  • Час реалізації типової фіч: -41%
  • Bug rate у production: -38%

Розрахунок ROI: 160 годин на погашення боргу зберегли ~320 годин уповільнення за квартал — окупаємість 2:1 за перший квартал.

Терміни

  • Базовий сканер (complexity + TODO + залежності): 3–5 днів
  • AI-аналіз архітектурних проблем: 1 тиждень
  • Пріоритизація + генерація Jira-задач: 1 тиждень
  • Dashboard з історичними трендами: 2 тижні