Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .claude/rules/backend/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ backend/app/
│ │ │ ├── api_client.py
│ │ │ └── repo_analyzer.py
│ │ ├── response_mapper.py
│ │ ├── skill_extractor.py
│ │ └── skill_taxonomy/ # スキル分類(言語・トピック・キーワードマップ)
│ │ └── skills/ # スキル推論基盤(ADR-0016 / 3層モデル)
│ │ ├── aggregator.py # discover+declare 合流 → DetectedSkill
│ │ ├── linguist.py # 言語正規化(Linguist languages.yml)
│ │ └── manifests/ # エコシステム別 manifest パーサ(plugin 型)
│ ├── tasks/ # 非同期タスク基盤(Cloud Tasks / ローカル)
│ │ ├── base.py # TaskType 定義(現状 GITHUB_LINK のみ)
│ │ ├── exceptions.py # RetryableError / NonRetryableError
Expand All @@ -104,4 +106,4 @@ backend/app/
- **routers/auth/ と routers/blog/**: いずれもパッケージ化されている。auth は `endpoints` / `github_auth` / `oauth_flow` / `token_manager`、blog は `accounts` / `score` / `sync` に責務分割
- **services/tasks/**: Cloud Tasks(本番)と BackgroundTasks(ローカル)を共通の `execute_task` でディスパッチ。状態遷移(`processing` / `completed` / `dead_letter` / `retrying`)は worker が担う。現在登録されているタスクは `GITHUB_LINK` の 1 種類のみだが、`AsyncTaskCacheService` / `TaskHandler` は新規タスク追加の拡張ポイントとして汎用化してある(インライン化しない)
- **タスクハンドラの「黙って return」は禁止**: 失敗パスでは `NonRetryableError` / `RetryableError` を `raise` し、`dead_letter` / `retrying` 遷移と通知発行を worker に任せる。早期 return すると呼び出し側に completed として観測されてしまう
- **services/intelligence/**: GitHub 連携 → スキル集計パイプライン。`github_link_service` → `pipeline` → `github_collector` → `skill_extractor` が live 経路。LLM は使わず決定論的(ルールベース)に処理する(intelligence モジュールは LLM を使わない。LLM は services/agent/ のみ / ADR-0010)
- **services/intelligence/**: GitHub 連携 → スキル推論パイプライン。`github_link_service` → `github_collector`(収集)→ `skills/aggregate_skills`(ADR-0016 の 3 層スキル検出)→ `pipeline.aggregate_intelligence`(dashboard 表示用サマリ)が live 経路。旧 `skill_extractor` / `skill_taxonomy`(自前辞書)は ADR-0016 基盤へ移行完了済みで撤去。LLM は使わず決定論的(ルールベース)に処理する(intelligence モジュールは LLM を使わない。LLM は services/agent/ のみ / ADR-0010)
6 changes: 4 additions & 2 deletions backend/app/services/intelligence/github_link_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,8 @@ async def _on_repo_fetched(done: int, total: int) -> None:
# 取得失敗のときだけ警告対象にする(貢献年が無いだけなら警告しない)
contribution_fetch_failed = not ok

# ステップ 3: スキル抽出(集計関数で一括処理)
# ステップ 3: スキル集計
await set_progress(task_id, 3, _TOTAL_STEPS, "スキル集計中...")
result = aggregate_intelligence(payload["github_username"], repos)

# 3 層スキル(ADR-0016 / discover + declare)の中間表現を組み立てる(I/O 無し)。
detected_skills = aggregate_skills(
Expand All @@ -139,6 +138,9 @@ async def _on_repo_fetched(done: int, total: int) -> None:
]
)

# dashboard 表示用サマリ(件数・言語バイト数)を検出スキルから組む。
result = aggregate_intelligence(payload["github_username"], repos, detected_skills)

response = map_pipeline_result(result)
response.contribution_calendars = calendars
result_dict = response.model_dump()
Expand Down
75 changes: 26 additions & 49 deletions backend/app/services/intelligence/pipeline.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
"""
キャリアインテリジェンスパイプラインのオーケストレーター
キャリアインテリジェンスパイプラインの集計関数

GitHub のデータから以下の分析を順次実行します:
GitHub → リポジトリ → 集計 → スキル抽出
GitHub から収集したリポジトリ集合(``RepoData``)と、スキル推論基盤
(ADR-0016 / ``skills.aggregate_skills``)が組み立てた ``DetectedSkill`` 列から、
dashboard 表示用の集計結果(``IntelligenceResult``)を構築する。

各ステージは決定論的(LLM は呼ばない)。
集計は決定論的(LLM は呼ばない)。スキルの正規化・検出は ``skills/`` 配下が担い、
本モジュールは「件数・言語バイト数」の表示用サマリ算出に責務を限定する。
"""

from collections import defaultdict
from dataclasses import dataclass, field
from datetime import datetime
from typing import Dict, List, Optional
from typing import Dict, List

from ...core.logging_utils import get_logger
from ...core.metrics import measure_time_async
from .github_collector import RepoData, collect_repos
from .skill_extractor import extract_skills

logger = get_logger(__name__)
from .github_collector import RepoData
from .skills import DetectedSkill
from .skills.types import SKILL_KIND_LANGUAGE


@dataclass
Expand All @@ -31,54 +30,32 @@ class IntelligenceResult:
languages: Dict[str, int] = field(default_factory=dict)


def aggregate_intelligence(username: str, repos: List[RepoData]) -> IntelligenceResult:
"""リポジトリ集合から ``IntelligenceResult`` を構築する純粋関数。
def aggregate_intelligence(
username: str,
repos: List[RepoData],
detected_skills: List[DetectedSkill],
) -> IntelligenceResult:
"""リポジトリ集合と検出スキルから ``IntelligenceResult`` を構築する集計関数。

I/O を行わないため、CLI/テスト向けの ``run_pipeline`` と
進捗通知付きのバックグラウンドワーカーの双方から再利用できる。
外部 I/O(GitHub API / DB)は行わない(``analyzed_at`` のみ実行時刻を用いるため、
厳密には時刻依存。純粋関数ではない)。``unique_skills`` は ADR-0016 のスキル推論基盤が検出した
Layer 1 スキルのうち **言語スキル(kind=language)の件数**を表す
(dashboard 表示用。package は件数に含めない)。旧辞書ベース抽出は撤去済み。
``languages`` は Linguist のバイト数をリポジトリ横断で合算する。
"""
lang_totals: Dict[str, int] = defaultdict(int)
for repo in repos:
for lang, byte_count in repo.languages.items():
lang_totals[lang] += byte_count

extraction = extract_skills(repos)
unique_language_skills = sum(
1 for skill in detected_skills if skill.kind == SKILL_KIND_LANGUAGE
)

return IntelligenceResult(
username=username,
repos_analyzed=extraction.repos_analyzed,
unique_skills=len(extraction.unique_skills),
repos_analyzed=len(repos),
unique_skills=unique_language_skills,
analyzed_at=datetime.now().isoformat(),
languages=dict(lang_totals),
)


@measure_time_async("intelligence.pipeline")
async def run_pipeline(
username: str,
token: Optional[str] = None,
include_forks: bool = False,
) -> IntelligenceResult:
"""GitHub ユーザーに対してキャリアインテリジェンスパイプラインを実行する。

1. GitHub API からリポジトリ収集
2. ``aggregate_intelligence`` で集計・スキル抽出
"""
logger.info("%s のインテリジェンスパイプラインを開始します", username)

repos: List[RepoData] = await collect_repos(
username,
token=token,
include_forks=include_forks,
)

result = aggregate_intelligence(username, repos)

logger.info(
"パイプライン完了 (%s): 分析リポジトリ数=%d, ユニークスキル数=%d",
username,
result.repos_analyzed,
result.unique_skills,
)

return result
115 changes: 0 additions & 115 deletions backend/app/services/intelligence/skill_extractor.py

This file was deleted.

22 changes: 0 additions & 22 deletions backend/app/services/intelligence/skill_taxonomy/__init__.py

This file was deleted.

Loading
Loading