Skip to content

# 職務経歴書: 非IT経歴・休暇・資本金単位 + GitHubコントリビューション可視化#281

Merged
yusuke0610 merged 14 commits into
mainfrom
dev
May 29, 2026
Merged

# 職務経歴書: 非IT経歴・休暇・資本金単位 + GitHubコントリビューション可視化#281
yusuke0610 merged 14 commits into
mainfrom
dev

Conversation

@yusuke0610

@yusuke0610 yusuke0610 commented May 29, 2026

Copy link
Copy Markdown
Owner

概要

職務経歴書(Resume)を IT エンジニア以外の経歴にも対応させた。在籍企業を「非IT企業」としてマークすると取引先/プロジェクトの代わりに自由記述の「詳細」を入力でき、在籍中の「休暇」(育児/介護/留学等)を取引先と並べて期間・詳細付きで記述できる。あわせて資本金の単位を企業ごとに選択可能にした(従来は「千万円」固定)。

同コミットには GitHub 連携ダッシュボードのコントリビューション可視化(緑の四角ヒートマップ)と連携UI刷新も含まれる(詳細は report/pr_github_contributions.md)。

背景・目的

従来の職務経歴書は「IT企業の取引先 → プロジェクト」という構造に固定されており、次のケースを表現できなかった:

  1. 非IT企業での職歴: 取引先/プロジェクト単位ではなく、職務内容を自由記述したい
  2. 在籍中の休暇: 育児・介護・留学などのブランクを職歴上で説明したい
  3. 資本金の単位: 企業規模により「万円/百万円/千万円/億円」を使い分けたい(従来は全社「千万円」固定で実態とずれていた)

変更内容

Backend

  • モデル拡張 (models/resume.py)
    • ResumeExperiencecapital_unit(既定「千万円」)/ is_it_company(既定 True)/ description(非IT時の詳細)を追加
    • ResumeClientis_vacation / vacation_start_date / vacation_end_date / vacation_is_current / vacation_description を追加。vacation_start_date / vacation_end_dateformat_year_month を通す read プロパティで YYYY-MM 文字列化(NULL は ""
  • マイグレーション 2 件 (alembic_migrations/versions/)
    • 0039_add_capital_unit: capital_unit を追加(既存行の後方互換として server_default="千万円"
    • 0040_add_resume_non_it_and_vacation: 非IT フラグ/詳細・休暇カラム群を追加(is_it_company は既存行を IT 企業前提として server_default=1is_vacation0
    • libSQL は ADD COLUMN を直接サポートするため upgrade は op.add_column、downgrade の列削除は ALTER/DROP 非対応のため batch_alter_table(テーブル再作成)
  • スキーマ + バリデーション (schemas/resume.py)
    • Experiencecapital_unit: Literal["万円","百万円","千万円","億円"] / is_it_company / description を追加
    • Client に休暇フィールド群を追加し、validate_vacation_datesmode="after")で休暇時のみ開始年月必須・継続中なら end を "" に正規化・end >= start を検証(Experience.validate_dates と同契約)
  • 永続化 (repositories/resume.py): 新フィールドを payload から読み出して行を構築。休暇期間は parse_year_month で日付化
  • 生成系
    • Markdown (markdown/generators/resume_generator.py): 資本金は {cap}{capital_unit} を出力。非IT企業は取引先ループをスキップし「詳細」を出力。休暇エントリは「#### 休暇」+ 期間 + 詳細
    • PDF (pdf/generators/resume_generator.py + templates/resume.css): _build_vacation_html を追加。非IT企業は .company-detail に詳細を表示、休暇は .vacation ブロックで表示。資本金単位を反映

Frontend

  • 型・定数 (types.ts, formTypes.ts, constants.ts)
    • CapitalUnit 型(backend Literal と手動同期)、CAPITAL_UNITS / DEFAULT_CAPITAL_UNIT を追加
    • CareerExperience / CareerClient に新フィールドを追加。blank テンプレートにも初期値を反映
  • 経歴エディタ (forms/CareerFormEditors/CareerExperienceEditor.tsx)
    • 資本金にドロップダウン(CAPITAL_UNITS)を追加
    • 「IT企業」チェックボックスを追加。OFF にすると取引先セクションを隠し「詳細」自由記述欄(必須)を表示
    • 取引先に「休暇」トグルを追加。ON で取引先名/プロジェクトの代わりに期間(開始/終了 or 継続中)+ 詳細を入力
  • フォーム入出力
    • formMappers.ts: API レスポンスの新フィールドを ?? デフォルト で吸収(後方互換)
    • payloadBuilders.ts: 非IT企業は description のみ送信し clients を空に、休暇 client は vacation_* のみ送信。事前バリデーション(詳細必須・休暇期間必須・期間整合)を追加
  • メッセージ (constants/messages.ts): EXPERIENCE_DESCRIPTION_REQUIRED / VACATION_START_DATE_REQUIRED / VACATION_END_DATE_REQUIREDVALIDATION_MESSAGES に追加

動作確認

  • Backend: make test-backend / make lint-backend
    • 新規/更新テスト: test_schemas.py(capital_unit / is_it_company / 休暇バリデーション)、test_markdown_generator.pytest_pdf_generator.py(非IT詳細・休暇・資本金単位の出力)、test_endpoints.pytest_contributions.pytest_worker/test_github_link.py
  • Frontend: make lint-frontend / make lint-frontend-messages、unit tests(payloadBuilders.test.ts に非IT/休暇の送信・バリデーション、useCareerExperienceMutators.test.tsContributionHeatmap.test.tsxGitHubLinkDashboard.test.tsx
  • E2E: npm run test:e2egithub-link.spec.ts / navigation.spec.ts 更新)

影響範囲・補足

  • DB マイグレーション必須(make migrate)。既存行は server_default で後方互換(全社 IT企業・資本金単位「千万円」・休暇なし扱い)
  • DTO は backend schemas/resume.py ↔ frontend types.ts の手動同期運用(codegen 未導入)。CapitalUnit の Literal を増減する場合は両方を更新する
  • 非IT企業をオンにすると、その企業配下の取引先/プロジェクトは送信対象外(description のみ)になる

Summary by CodeRabbit

  • New Features

    • Annual GitHub contribution heatmap with totals and streaks.
    • Selectable capital unit for company entries.
    • Support for non‑IT experiences with free‑text description.
    • Add vacation entries with start/end/current flags and descriptions.
  • UI Improvements

    • Sidebar “GitHub連携” as an expandable button with options (include forks).
    • GitHub dashboard shows heatmap, overview stats, and empty‑state messaging.
    • Career editor: unit selector and vacation‑specific inputs/validation.
  • Messages

    • New user-facing error message when contribution fetch fails.

Review Change Stack

yusuke0610 and others added 2 commits May 29, 2026 07:57
# 職務経歴書: 非IT経歴・休暇・資本金単位 + GitHubコントリビューション可視化
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@yusuke0610, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 14 minutes and 54 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d27bab6-da19-4115-95a8-6e71bde442c6

📥 Commits

Reviewing files that changed from the base of the PR and between db7a74d and 67a8cb5.

📒 Files selected for processing (3)
  • backend/app/services/tasks/worker.py
  • backend/tests/test_worker/test_github_link.py
  • frontend/src/components/forms/CareerFormEditors/ClientEditor.tsx
📝 Walkthrough

Walkthrough

Adds capital_unit, non‑IT experiences, and vacation-client support across DB, backend, generators, and frontend; integrates GitHub contribution-calendar fetching, parsing, dashboard heatmap UI, and tests.

Changes

Resume Data Model & Generation

Layer / File(s) Summary
DB migrations and ORM models
backend/alembic_migrations/versions/0039_add_capital_unit.py, backend/alembic_migrations/versions/0040_add_resume_non_it_and_vacation.py, backend/app/models/resume.py
Adds capital_unit, is_it_company, description to experiences and vacation columns to clients; ORM mappings and computed date properties added.
Repository mapping
backend/app/repositories/resume.py
Mapping functions populate new experience and client fields (capital_unit, is_it_company, description, vacation fields) from payloads with safe defaults.
Markdown/PDF resume generators & styles
backend/app/services/markdown/generators/resume_generator.py, backend/app/services/pdf/generators/resume_generator.py, backend/app/services/pdf/templates/resume.css
Renderers now honor capital_unit, branch for non‑IT experiences to output description, and render vacation entries; CSS for company-detail and vacation added.
Backend schema validation
backend/app/schemas/resume.py
Pydantic models: Experience adds capital_unit, is_it_company, description; Client adds vacation fields and validator enforcing vacation date rules.
Migrations messages
backend/app/messages.json
Adds error.github_link.contribution_fetch_failed.

GitHub Contribution Calendar Integration

Layer / File(s) Summary
Fetch + parse service
backend/app/services/intelligence/github/contributions.py
Adds async fetch_contribution_calendar calling GitHub GraphQL, robustly returns parsed ContributionCalendar or None on errors.
Service wiring & schema
backend/app/services/intelligence/github_link_service.py, backend/app/schemas/github_link.py
Fetch calendar when token exists, attach contribution_calendar to pipeline response, set warning on fetch failure; response schema extended.
Backend tests & worker
backend/tests/test_contributions.py, backend/tests/test_worker/test_github_link.py
Unit tests for fetch/parse behaviors and worker result persistence/warning handling.

Frontend Core: types, payloads, hooks

Layer / File(s) Summary
Types & constants
frontend/src/types.ts, frontend/src/constants.ts
Adds CapitalUnit, extends CareerExperience/CareerClient with new fields, defines CAPITAL_UNITS/DEFAULT_CAPITAL_UNIT, updates blanks.
Payload builders & mappers
frontend/src/payloadBuilders.ts, frontend/src/formMappers.ts, frontend/src/payloadBuilders.test.ts
buildCareerPayload now supports capital_unit, non‑IT description requirement, vacation-client shaping/validation; mappers provide defaults; tests updated/added.
Form types, dirty tracking, mutators
frontend/src/formTypes.ts, frontend/src/hooks/career/useCareerDirty.ts, frontend/src/hooks/career/useCareerExperienceMutators.ts
Field unions expanded; dirty tracking for new fields; mutators add updateClientIsVacation and updateClientVacationIsCurrent with tests.

Frontend UI & Heatmap

Layer / File(s) Summary
Career form editors
frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx, frontend/src/components/forms/CareerFormEditors/ClientEditor.tsx, frontend/src/components/forms/CareerResumeForm.module.css, frontend/src/components/forms/sections/CareerExperienceSection.tsx
Experience editor adds capital unit select and delegates client editing to new ClientEditor which supports vacation inputs and project UI; CSS for unit select.
GitHub heatmap & dashboard
frontend/src/components/github-link/ContributionHeatmap.tsx, frontend/src/components/github-link/ContributionHeatmap.module.css, frontend/src/components/github-link/GitHubLinkDashboard.tsx, frontend/src/components/github-link/GitHubLinkDashboard.module.css
New heatmap component renders calendar grid, streak and totals; dashboard refactored to state-driven flow and integrates heatmap and empty state styles.
Authenticated layout & assets
frontend/src/components/AuthenticatedLayout.tsx, frontend/src/components/icons/ChevronDownIcon.tsx, frontend/src/App.module.css
Sidebar GitHub entry becomes a button with expandable options and chevron icon; navigation passes run state.
Frontend tests & e2e
frontend/src/components/github-link/ContributionHeatmap.test.tsx, frontend/src/components/github-link/GitHubLinkDashboard.test.tsx, frontend/e2e/github-link.spec.ts, frontend/e2e/navigation.spec.ts
Unit and E2E tests added/updated for heatmap, dashboard flows, empty state, options toggle, and polling.
API types & test handlers
frontend/src/api/githubLink.ts, frontend/src/test/handlers.ts, frontend/src/test/renderWithProviders.tsx
Adds ContributionDay/ContributionCalendar types and contribution_calendar in responses; test handlers updated; test router typing aligned.
Styles & messages
frontend/src/styles.css, frontend/src/constants/messages.ts
Adds contribution color tokens, validation/empty messages, and new success helpers/constants.

Other

Layer / File(s) Summary
Backend helpers, repo, worker, docs, infra, scripts
backend/app/repositories/blog.py, backend/app/routers/github_link.py, backend/app/services/tasks/worker.py, infra shared files, README.md, ADR, scripts/lint-frontend-messages.sh
Refactors: blog merge helper, centralized router error helper, task terminal helpers; infra sharedization and variables; docs/README updates and lint script pattern expansion.

Sequence Diagram(s)

sequenceDiagram
  participant Frontend
  participant GitHubLinkService
  participant fetch_contribution_calendar
  participant GitHubGraphQL
  participant CacheStore
  Frontend->>GitHubLinkService: POST /api/github-link/run (includeForks, token)
  GitHubLinkService->>fetch_contribution_calendar: fetch_contribution_calendar(username, token)
  fetch_contribution_calendar->>GitHubGraphQL: GraphQL query (contributionsCollection)
  GitHubGraphQL-->>fetch_contribution_calendar: JSON payload / errors
  fetch_contribution_calendar-->>GitHubLinkService: ContributionCalendar | None
  GitHubLinkService->>CacheStore: persist result with contribution_calendar / warning_message
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • yusuke0610/devforge#261: Related to frontend career/payload builder updates and tests for buildCareerPayload behaviors.

Suggested labels

feature

Poem

🐰
I hopped through code to plant a seed,
Capital units and vacays freed.
Calendars bloom in colored squares,
Resumes rest with tender cares.
Hooray — a tiny rabbit cheers!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/app/services/intelligence/github_link_service.py`:
- Around line 99-103: The warning message is being set whenever calendar is None
even if no fetch was attempted; change the logic so you only set warning_message
when a fetch was actually attempted and failed (i.e., token is present but
fetch_contribution_calendar returned None). Update the block around
calendar/token/fetch_contribution_calendar to check token first and then, only
if token was provided and calendar is None, set warning_message; apply the same
token-presence check for the similar warning logic at the other occurrence (the
block around lines 127-129).

In `@backend/app/services/intelligence/github/contributions.py`:
- Around line 126-146: The call site that invokes _parse_calendar should be made
resilient: wrap the call to _parse_calendar(...) in a try/except Exception block
and return None if parsing fails to preserve the "on failure None" contract;
additionally make _parse_calendar itself defensive by replacing day["date"] with
day.get("date") (or skip days where date is missing/invalid), use .get for
contributionCount/contributionLevel, validate types before creating
ContributionDay, and ensure _parse_calendar never raises on malformed week/day
entries (instead skip bad entries or return an empty ContributionCalendar).

In `@backend/tests/test_endpoints.py`:
- Around line 242-244: The test currently reindexes experiences into a dict
(experiences = {exp["company"]: exp for exp in data["experiences"]}) which masks
the required sorting check; before that reindexing, add an explicit assertion on
data["experiences"] to verify the list order (e.g., check that each consecutive
item is not newer than the previous by comparing the relevant date fields
present on each exp) so the sort guarantee is validated prior to converting to a
dict.

In `@backend/tests/test_worker/test_github_link.py`:
- Around line 116-120: The test patch for
app.services.intelligence.github_link_service.fetch_contribution_calendar
currently uses return_value=MagicMock(), which can cause contribution_calendar
persistence and break the assertion; change the patch to use return_value=None
so the contribution calendar is not mutated and the assertion cache.result ==
sentinel_result remains valid—update the mock setup in the test where
fetch_contribution_calendar is patched accordingly.

In `@frontend/src/components/github-link/ContributionHeatmap.tsx`:
- Around line 24-31: The month extraction is timezone-dependent because monthOf
uses new Date(week[0].date).getMonth() on a "YYYY-MM-DD" string; fix it by
computing the month in UTC instead of local time — either call getUTCMonth() on
the created Date or parse the date into a UTC Date (e.g., via Date.UTC(year,
month-1, day)) and then use getUTCMonth(); update the monthOf function
(referenced as monthOf, week[0].date, and MONTH_NAMES) accordingly so month
labels are stable across timezones.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 651efafb-72ab-4644-ba3f-e843a10f81d8

📥 Commits

Reviewing files that changed from the base of the PR and between cbdfd42 and 379405a.

📒 Files selected for processing (47)
  • backend/alembic_migrations/versions/0039_add_capital_unit.py
  • backend/alembic_migrations/versions/0040_add_resume_non_it_and_vacation.py
  • backend/app/messages.json
  • backend/app/models/resume.py
  • backend/app/repositories/resume.py
  • backend/app/schemas/github_link.py
  • backend/app/schemas/resume.py
  • backend/app/services/intelligence/github/contributions.py
  • backend/app/services/intelligence/github_link_service.py
  • backend/app/services/markdown/generators/resume_generator.py
  • backend/app/services/pdf/generators/resume_generator.py
  • backend/app/services/pdf/templates/resume.css
  • backend/tests/test_contributions.py
  • backend/tests/test_endpoints.py
  • backend/tests/test_markdown_generator.py
  • backend/tests/test_pdf_generator.py
  • backend/tests/test_schemas.py
  • backend/tests/test_worker/test_github_link.py
  • frontend/e2e/github-link.spec.ts
  • frontend/e2e/navigation.spec.ts
  • frontend/src/App.module.css
  • frontend/src/api/githubLink.ts
  • frontend/src/components/AuthenticatedLayout.tsx
  • frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx
  • frontend/src/components/forms/CareerResumeForm.module.css
  • frontend/src/components/forms/sections/CareerExperienceSection.tsx
  • frontend/src/components/github-link/ContributionHeatmap.module.css
  • frontend/src/components/github-link/ContributionHeatmap.test.tsx
  • frontend/src/components/github-link/ContributionHeatmap.tsx
  • frontend/src/components/github-link/GitHubLinkDashboard.module.css
  • frontend/src/components/github-link/GitHubLinkDashboard.test.tsx
  • frontend/src/components/github-link/GitHubLinkDashboard.tsx
  • frontend/src/components/icons/ChevronDownIcon.tsx
  • frontend/src/constants.ts
  • frontend/src/constants/messages.ts
  • frontend/src/formMappers.ts
  • frontend/src/formTypes.ts
  • frontend/src/hooks/career/useCareerDirty.ts
  • frontend/src/hooks/career/useCareerExperienceMutators.test.ts
  • frontend/src/hooks/career/useCareerExperienceMutators.ts
  • frontend/src/payloadBuilders.test.ts
  • frontend/src/payloadBuilders.ts
  • frontend/src/styles.css
  • frontend/src/test/handlers.ts
  • frontend/src/test/renderWithProviders.tsx
  • frontend/src/types.ts
  • frontend/tests/payloadBuilders.test.cjs

Comment thread backend/app/services/intelligence/github_link_service.py
Comment thread backend/app/services/intelligence/github/contributions.py Outdated
Comment thread backend/tests/test_endpoints.py
Comment thread backend/tests/test_worker/test_github_link.py
Comment thread frontend/src/components/github-link/ContributionHeatmap.tsx
@yusuke0610

Copy link
Copy Markdown
Owner Author

Summary

  • OLLAMA_* 環境変数名を backend/app/core/env_keys.py に正本化し、ollama_client.py の生リテラル参照を env_keys 経由へ置換(CLAUDE.md / security.md の「生リテラル os.getenv 禁止」へ準拠)。
  • DTO 二重定義のドリフト検知不在(Medium)は、CLAUDE.md の「技術判断は ADR 起票後に実装」に従い ADR-0007(Proposed)を起票。openapi-typescript パイプライン実装と型移行は本 ADR が定義する後続 PR に分離。
  • ドキュメント重複(README セットアップ / tofu 実行例)を正本(docs/development.md・docs/deployment.md)へのリンク参照に寄せた。
  • OLLAMA 以外の env 名散在(Low)は実コードのドリフト無しを確認し、現状維持(HCL/YAML は Python 定数を import 不可のため機械化不要)。

SSoT Consolidations

High

  • 観点: 環境変数名(OLLAMA_*)の SSoT 不在+生リテラル参照
  • SSoT に据えた場所: backend/app/core/env_keys.py:74-80# --- LLM --- セクションに OLLAMA_BASE_URL / OLLAMA_MODEL / OLLAMA_TIMEOUT を追加)
  • 更新した参照元:
    • backend/app/services/intelligence/llm/ollama_client.py:9,22-24from ....core import env_keys を追加し os.environ.get(env_keys.OLLAMA_*, ...) へ置換)
    • docker-compose.yml:24-27(OLLAMA_* の名前正本が env_keys.py である旨をコメントで明記)
  • 判断: OLLAMA_KEEP_ALIVE(docker-compose.yml の ollama サービス側、30m 固定)は backend が一切参照しない Ollama サーバ自身の設定のため env_keys.py には追加しない(追加すると未使用定数になる)。レポートの「必要なら」に対し不要と判断し、その旨を env_keys.py のコメントに残した。
  • 互換性配慮: 環境変数名・既定値は不変。docker-compose / CI / Cloud Run の注入は変更なし(OLLAMA は本番 Cloud Run に元々未注入)。休眠インフラ([[project_llm_stack_dormant]])の温存方針とも矛盾しない(削除ではなく正本化)。

Medium

  • 観点: DTO 二重定義(BE schemas/** ↔ FE types.ts / api/*.ts)のドリフト検知不在
  • SSoT に据えた場所: 本 PR では実装せず。判断記録として docs/adr/0007-openapi-typescript-codegen.md(ステータス: Proposed)を起票。
  • 内容: backend Pydantic schema を DTO の SSoT とし、FastAPI の OpenAPI → openapi-typescriptfrontend/src/api/generated.ts を生成、CI で git diff --exit-code によりドリフトを検知する案。Phase 0(基盤)→ Phase 1(api/shared.ts パイロット)→ Phase 2/3(github_link / resume / blog / master_data 移行)の段階移行プランを定義。
  • 互換性配慮: api/client.ts の 401/CSRF/Cookie ロジック、api/paths.ts(API パス SSoT)、errorCodes 型縛りはいずれも対象外として維持。命名差(CareerResumeResponseResumeResponse)は再エクスポートで吸収。
  • 本 PR で実装しなかった理由: 本 skill(XR_apply)が「OpenAPI codegen は本 PR の対象外(別 PR)」と明記し、レポートも「重い投資のため Medium 据え置き / 別 PR」と判断。依存追加・make ターゲット・全 DTO 移行を env/docs 修正と束ねるとレビュー不能になるため、ADR 起票(CLAUDE.md が義務付ける第一ステップ)までを本 PR のスコープとした。

Low

  • 観点: env 名の正本(env_keys.py)↔ 注入経路(cloud_run/main.tf・docker-compose.yml・ci.yml)の散在(OLLAMA 以外)
  • 対応: 実コード変更なし(現状維持)。
  • 確認結果: infra/modules/cloud_run/main.tf の env 名(18 件)はすべて env_keys.py に存在しドリフト無し。.github/workflows/ci.ymlPROJECT_ID / REGION は GCP デプロイ用変数で Python から参照不可、VITE_APP_VERSION は frontend ビルド変数。いずれも env_keys.py の対象外で問題なし。env_keys.py の「4 箇所同期」手順コメントが SSoT として機能していることを確認。

Documentation Consolidations

  • 正本: docs/development.md(初回セットアップ・make ci
    • リンク参照に置換した箇所: README.md「クイックスタート」(make setup / make ci スニペット重複を削除しリンク + 再掲しない理由を明記)
  • 正本: docs/deployment.md(OpenTofu 実行手順・GCS backend 認証)
    • リンク参照に置換した箇所: docs/data-model.md「OpenTofu で DB を作成」(一般 tofu 手順をリンク参照化し、Turso DB 固有手順のみ残置)、.claude/rules/infra/opentofu.md:14(実行手順は deployment.md を正本参照する旨を追記)
    • 残置した重複: .claude/rules/infra/test.mdmake infra-validate 系コマンド列。これは「いつ検証を回すか(infra テスト方針)」という別目的であり、変更理由が異なる(偶発的重複)ため duplication.md の方針に従い抽出しない。

Skipped

  • DTO codegen のパイプライン実装・型移行(Medium): 上記理由により後続 PR へ分離。ADR-0007 を Accepted に昇格させた上で Phase 0 から着手する。
  • OLLAMA 以外の env 名の機械化(Low): HCL/YAML は Python 定数を import できず、機械化すると逆に不整合源になるため見送り(レポートも「機械化不要」と判断)。

Validation

  • make lint-backend: pass(All checks passed!
  • make test-backend: pass(443 passed, 2 warnings / 30.40s)
    • tests/test_llm_clients.py 単体: 14 passed
  • make lint-frontend / make build-frontend: 未実行(frontend のコード変更なし。変更は markdown=README/ADR のみ)
  • make infra-fmt-check / make infra-validate: 未実行(infra の .tf 変更なし。変更は markdown=rules/docs のみ)
  • make dupe-check: before 56 clones → after 56 clones(cross-realm clone 0 件を維持。新規重複なし。env_keys/ollama_client の変更はリテラル重複を削減する方向)
  • E2E: 未実行(API 契約・ルート・認証・サイドバーいずれも変更なし。env_keys 内部参照の置換と markdown のみ)

Follow-ups

  • ADR-0007 を Accepted へ昇格し、別 PR で OpenAPI→TS codegen パイプライン(Phase 0: export_openapi.py / openapi-typescript / make codegen-types / CI ドリフト検知)を構築。
  • パイプライン構築前に backend 全 router の response_model 付与状況を棚卸し(未設定だと OpenAPI に型が出ないため)。

yusuke0610 and others added 3 commits May 29, 2026 11:39
Infra Refactor PR Report / Backend Refactor PR Report / Frontend Refactor PR Report
@yusuke0610

yusuke0610 commented May 29, 2026

Copy link
Copy Markdown
Owner Author

INFRA Summary

  • versions.tf(3 環境完全一致)を shared/versions.tf に統合し symlink 化(PR1)。
  • environment を tfvars 変数化し、provider + module "devforge_stack" 呼び出しを含む main.tfshared/main.tf に統合して 3 環境を symlink 化(PR2)。これで env 別 main.tf の唯一の差分(environment リテラル)が解消。
  • HCL コード重複率 3.04% → 0%(残る infra clone は ENV_CHECKLIST.md の md のみ)。
  • PR3(ENV_CHECKLIST.md)は調査の結果、stg/prod が env 固有値で大半が異なるため symlink 化せず allowed duplication として記録(下記 Skipped)。
  • 全変更は state 影響なし(resource アドレス module.devforge_stack.* 不変、渡る値も同一文字列)。

Applied Changes

Medium

  • [infra/environments/{dev,stg,prod}/main.tf] provider 3 ブロック + devforge_stack 呼び出しのコピペを解消。environment = "<env>" リテラルを var.environment 化し、実体を shared/main.tf に集約 → 3 環境を ../shared/main.tf への symlink に置換。元レポート "Medium / Variable 抽出候補 / PR2"。

Low

  • [infra/environments/{dev,stg,prod}/versions.tf] 3 環境完全一致を shared/versions.tf に統合し symlink 化。元レポート "Low / PR1"。

Modules / Variable Changes

Modules 化

  • なし(既存構造で完了済み。新規 module 作成・resource 置換なし)。

Variable 抽出

  • [infra/environments/shared/variables.tf] variable "environment" を追加。validation で dev/stg/prod を制約。description は日本語。
  • terraform.tfvarsenvironment = "dev"|"stg"|"prod" を 1 行追記(tfvars は Git 管理対象で、placeholder 値を含むテンプレート運用)。

Module 統合

  • なし。

Structure Changes

  • env 別実体ファイルは backend.tf(GCS bucket 差分・変数化不可)と terraform.tfvars のみに縮小。main.tf / versions.tf / variables.tf / moved.tf / outputs.tf はすべて shared/ の symlink に統一。
infra/environments/
  shared/
    main.tf        # 新規(provider + devforge_stack 呼び出し、var.environment)
    versions.tf    # 新規
    variables.tf   # 既存(environment 変数を追加)
    moved.tf       # 既存
    outputs.tf     # 既存
  dev/  stg/  prod/
    backend.tf         # 実体(env 別 GCS bucket)
    terraform.tfvars   # 実体(env 別値、environment 追記)
    main.tf      -> ../shared/main.tf      (新規 symlink)
    versions.tf  -> ../shared/versions.tf  (新規 symlink)
    variables.tf -> ../shared/variables.tf (既存 symlink)
    moved.tf     -> ../shared/moved.tf     (既存 symlink)
    outputs.tf   -> ../shared/outputs.tf   (既存 symlink)

State Migration

  • state 移行は不要
  • 理由: module 呼び出しのアドレスは module.devforge_stack.* のまま変わらず、environment に渡る文字列も従来のリテラルと完全同一("dev"/"stg"/"prod")。リソースの再作成・rename は発生しない。
  • tofu plan は state 認証が必要なため本 PR では未実行(下記 Validation / Follow-ups)。apply 担当者は念のため各環境で tofu plan を回し、差分ゼロ(No changes)を確認してから apply すること。

Skipped

  • PR3: ENV_CHECKLIST.md の symlink 統合を見送り
    • 理由: diff stg/ENV_CHECKLIST.md prod/ENV_CHECKLIST.md の結果、GCS バケット名(devforge-stg-dbdevforge-prod-db)、CORS、OAuth 登録手順、GCP プロジェクト名、GitHub Secret 名(GCP_SA_KEY_STG_PROD)など大半が env 固有値。jscpd が検出した 9 行 clone は表ヘッダ等の共通テンプレ行のみ。
    • 結論: terraform.tfvars と同種の「構造は同じ・値が env 別」= allowed duplication。markdown には変数展開が無く、symlink 化すると env 固有値が失われるため統合は不適切。現状維持。
    • 参考: dev には ENV_CHECKLIST.md が存在しない(追加するなら別タスク)。

Validation

  • make infra-fmt-check: pass(fmt 差分なし)
  • make infra-validate: pass(dev / stg / prod すべて "Success! The configuration is valid.")
  • tofu plan (各環境): 未実行(GCS backend / state 認証が必要なため。state 影響なしの根拠は State Migration 参照)
  • make dupe-check: HCL 3.04% → 0.00%(46/1514 行 → 0/1411 行)。残る infra clone は ENV_CHECKLIST.md(stg↔prod, 9 行 ×2、allowed)のみ。

Follow-ups

  • infra 専用ブランチの作成: 現在 refactor/backend/deadcode(無関係な backend 作業ブランチ)上で変更している。コミット前に origin/dev 起点で infra 専用ブランチを切り、本変更だけを載せること(プロジェクト規約)。
  • apply 前の plan 確認: 認証環境で tofu -chdir=infra/environments/{dev,stg,prod} plan を実行し、No changes を確認(state 影響なしの最終確認)。
  • ENV_CHECKLIST.md: dev 版が欠落。必要なら dev 用を追加するか、共通の前提手順だけ docs/ に正本化する別 PR を検討(本 PR スコープ外)。

@yusuke0610

yusuke0610 commented May 29, 2026

Copy link
Copy Markdown
Owner Author

BE Summary

  • worker.py の終端処理(completed / dead_letter / retrying の 3 分岐)で重複していた「新規セッション開閉 + 通知ガード」を _run_in_new_session / _finalize_* ヘルパーへ抽出し、挙動を変えずに本質的重複を解消。
  • blog repository の upsert_many / sync_many で重複していた merge ループ(apply/add/commit)を _merge(..., delete_missing=) に統合。key を (account_id, external_id) に一本化。
  • github_link.py の 2 エンドポイントで完全一致していた dispatch 失敗時の 500 送出を _raise_dispatch_failed() に集約。
  • テストの完全一致 Resume payload を conftest.make_resume_payload() ファクトリへ集約(test_delete_documents.py)。
  • make lint-backend / make test-backend ともに pass(443 passed)。

Applied Changes

High

  • [app/services/tasks/worker.py] Findings High / Duplication High。execute_task 内の 3 つの db = SessionLocal() → try/finally _safe_close ブロックと isinstance(user_id, str) and user_id != "unknown" 通知ガードを撤去し、以下のヘルパーへ集約:
    • _run_in_new_session(work): 新規セッションの開閉のみを担う(libSQL Hrana 失効対策のコメントを 1 箇所に集約)
    • _notify_if_real_user(db, task_type, user_id, status): 実ユーザー判定 + 通知
    • _finalize_completed / _finalize_dead_letter / _finalize_retrying: 各終端遷移
    • 挙動は不変(completed は実ユーザー時のみセッションを開く、dead_letter はユーザー有無に関わらず必ずマーク、retrying は通知しない)。SessionLocal / _mark_* / _create_notification は module-level のまま残したのでテストの patch ポイントも維持。

Medium

  • [app/repositories/blog.py:89-] upsert_many(複数アカウント・削除なし)と sync_many(単一アカウント・全置換)の merge ループを _merge(normalized_articles, existing_articles, *, delete_missing) に統合。
    • key を (account_id, external_id) に統一。sync_many は新規エンティティ生成・突合のため各記事へ account_id{**article, "account_id": account_id} で確実に付与(呼び出し側 sync_service.py が既に注入しているが、リポジトリ単体でも成立するよう防御的に付与)。
    • upsert_many の空入力早期 return(空 IN 句クエリ回避)と、sync_many の空入力→全削除セマンティクスを両方保持。

Low

  • [app/routers/github_link.py] start_github_link / retry_github_linkexcept Exception: raise_app_error(500, INTERNAL_ERROR, task.dispatch_failed, ...)_raise_dispatch_failed() に抽出。

Test Changes

Removed

  • [tests/test_delete_documents.py] モジュールローカルの完全コピー _RESUME_PAYLOAD(35 行)を削除し、make_resume_payload() の呼び出しに置換。jscpd で検出されていた test_delete_documents.py:8-27 ↔ test_endpoints.py:119-138 の構造的クローンのうち、完全一致側を解消。

Added

  • [tests/conftest.py] make_resume_payload(**overrides) ファクトリを追加。取引先・プロジェクト・体制まで含む完全な resume payload を返す共通フィクスチャ。呼ぶたびに新規 dict を返すため共有副作用なし。今後の統合テストの正準 payload として再利用可能。
  • 機能テストの追加は無し(レポート Add は「明確な不足なし」と判定。retry 409 / dispatch 失敗→dead_letter は既存テストでカバー済みを確認)。

Duplication Resolved

  • worker.py 終端セッションブロック(Duplication High)→ _run_in_new_session + _finalize_* に集約。
  • blog repository upsert/sync の merge ループ(Duplication Medium)→ _merge に統合。元レポートは「2 箇所どまりで保留」としていたが、(a) key 戦略・(b) 新規 account_id・(c) delete_missing の 3 差分が delete_missing フラグ 1 個+account_id 注入でコールバック無しに吸収でき、コア loop が完全一致だったため、過剰抽象化にならない範囲で統合した。
  • テスト Resume payload 完全一致(Duplication High)→ conftest ファクトリへ。

Structure Changes

  • ディレクトリ移動・ファイル分割なし(元レポート Structure: 提案なし)。

Skipped

元レポート自身が「現状維持/保留推奨」とした項目。CLAUDE.md「過剰な抽象化を避ける」「Rule of Three」に従い、機械的 DRY 化を見送り記録のみ:

  • collector.py の fetch_zenn/note/qiita ページング共通化(Medium): 元レポートが「現状維持を推奨」。終端条件(next_page / isLastPage / len<per_page)・sleep・User-Agent・記事マッピングがプラットフォームごとに独立した変更理由を持つ偶発的重複。共通化すると逆に複雑化するため未着手。
  • resume / intelligence の PDF↔Markdown ジェネレータの意味的重複(Medium): 走査構造は同型だが出力フォーマットが本質的に異なり、正規化は既に shared/resume_format.py に集約済み。2 箇所どまりで visitor 抽出の価値が薄く保留。
  • worker.py _safe_rollback の削除検討(Low): tests/test_worker/test_execute_task.py の 2 ケースが直接テストしており、休眠 API 温存方針にも合致するため削除しない。
  • github_link.py の github: ログイン必須ガードの共通化: 2 エンドポイントで重複するが元レポートのスコープ外。3 箇所目が出たら _require_github_username(user) 抽出を検討(Follow-ups)。
  • schemas/resume.py の validate_dates 系(Allowed): エラーメッセージは get_error 経由で SSoT 維持済み。共通化するとフィールド名分岐が増えるため現状維持。
  • test_endpoints.py の intent 固有 payload / markdown・schema・csrf の変種 payload: AAA の可読性のため残す(duplication ポリシーで許容)。

Validation

  • make lint-backend: pass(All checks passed!)
  • make test-backend: pass(443 passed, 2 warnings, 29.66s)
  • worker.py 抽出後の挙動回帰は既存 test_worker/test_execute_task.py(completed 通知 / dead_letter 遷移)と test_retry_flow.py(retrying)でカバー済みであることを確認。

Follow-ups

  • collector のページング・ジェネレータの意味的重複・blog 以外の upsert は「3 箇所目の出現時に再評価」。
  • github_link.py のログイン必須ガード共通化(_require_github_username)は次に同種エンドポイントが増えた時点で。
  • make dupe-check の再実行で worker.py / blog.py のクローンが消えたかを次セッションで確認するとよい(本 PR では lint+test のみ実行)。

@yusuke0610

yusuke0610 commented May 29, 2026

Copy link
Copy Markdown
Owner Author

FE Summary

  • ユーザー向け「成功」文言のハードコードを constants/messages.ts に集約し、useBlogAccountManager / GitHubLinkDashboard のリテラルを定数・関数参照へ置換。
  • これらが CI を擦り抜けていた根本原因(lint パターンの穴)を scripts/lint-frontend-messages.shsetSuccess/setInfo/setMessagetoAppError(…, "…") の検知を追加して塞いだ(逆検証で検知動作を確認)。
  • blog テストの literal assert を同じ定数 import に統一し、文言 SSoT を 1 本化。
  • 463 行の CareerExperienceEditor.tsx から取引先ブロックを ClientEditor.tsx に抽出(出力同値・DOM 不変)。

Applied Changes

High

  • なし(レポートに High 指摘なし)

Medium

  • [frontend/src/hooks/blog/useBlogAccountManager.ts] 成功文言のハードコード解消
    • constants/messages.tsSUCCESS_MESSAGESBLOG_LINKED / BLOG_UNLINKED / BLOG_USERNAME_UPDATED)と動的関数 blogLinkedSyncSuccessMessage / blogSyncSuccessMessage / blogUsernameUpdatedSyncSuccessMessage を追加し、4 箇所のリテラル(連携・同期・解除・username 更新)を置換。
  • [frontend/src/components/github-link/GitHubLinkDashboard.tsx:60] toAppError fallback リテラル解消
    • FALLBACK_MESSAGES.GITHUB_LINK("連携に失敗しました")を追加し、setError(toAppError(e, FALLBACK_MESSAGES.GITHUB_LINK)) へ置換。

Low

  • [scripts/lint-frontend-messages.sh] 検知パターン拡張(再発防止)
    • setter パターンを set\w*(Error|Success|Info|Message)\w* に拡張、toAppError\([^,)]+,\s*"…" の補助パターンを -e で追加。エラーメッセージ文言とヘッダコメントも更新。
    • 逆検証: setSuccess("保存しました")setError(toAppError(e, "連携に失敗しました")) を含む使い捨てファイルで exit 1(検知)を確認、ファイルは削除済み。
  • [frontend/src/components/forms/CareerFormEditors/ClientEditor.tsx 新規] CareerExperienceEditor 分割
    • per-client ブロック(取引先ヘッダ + 休暇分岐 + プロジェクト一覧)を ClientEditor へ抽出。CareerExperienceEditor.tsx は 463 → 283 行、ClientEditor.tsx は 236 行。state は持たず props のハンドラを呼ぶ純粋な描画分割で DOM 出力は不変。
    • ※レポートでは「過剰抽象化リスクのため現状維持推奨」としていたが、ユーザーが「全部」を選択したため実施。取引先という明確なドメイン境界での分割に留め、これ以上の細分化(VacationEditor 等)はしていない。

Test Changes

Removed

  • なし

Added / Updated

  • [frontend/src/hooks/blog/useBlogAccountManager.test.ts] 成功文言を assert する 4 箇所(解除 / 連携 / 連携後同期件数 / username 更新)を SUCCESS_MESSAGES.* および blogLinkedSyncSuccessMessage(3, 5) の定数・関数参照へ置換。守る挙動は不変(成功メッセージの内容と表示タイミング)だが、文言の二重管理を解消。

Duplication Resolved

  • レポートの Duplication Findings は High/Medium ともゼロ(jscpd frontend clone は全て許容重複)。本 PR で新規の重複統合は行っていない。
  • 副次的に、成功文言の「ハードコード文字列 × 実装/テストの二重管理」を SSoT へ統合した。

Structure Changes

frontend/src/components/forms/CareerFormEditors/
  CareerExperienceEditor.tsx   # 463 → 283 行(経歴レベルの shell に集中)
  ClientEditor.tsx             # 新規 236 行(取引先1件の描画)

Skipped

  • なし(採用スコープ「全部」を完了)。

Validation

  • make lint-frontend: pass(eslint src/ エラーなし)
  • make lint-frontend-messages: pass(exit 0)
  • make test-frontend: pass(node:test 4 / vitest 189、全 23 test files green)
  • make build-frontend: pass(gen-redirects + tsc -b + vite build、224 modules)
  • E2E (npm run test:e2e): pass(21 passed)。career-dirty-indicator / github-link を含む。ログ中の ECONNREFUSED はモック未対象ルートへの Vite dev proxy 由来で、テスト結果に影響なし。

Follow-ups

  • なし。仕様判断が必要な保留項目もなし。
  • 将来 success/info トーストを増やす際は SUCCESS_MESSAGES(静的)または動的関数(件数埋め込み)に追加すること。拡張済み lint が setSuccess リテラルを CI で検知する。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/tests/test_delete_documents.py (1)

8-8: ⚡ Quick win

Build a fresh resume payload in each test.

_RESUME_PAYLOAD is a shared mutable dict for the whole module, so any future in-place tweak in one test can leak into the others. Calling make_resume_payload() at each POST keeps the helper’s fresh-object guarantee intact.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/test_delete_documents.py` at line 8, Replace the module-level
shared dictionary _RESUME_PAYLOAD with fresh calls to make_resume_payload()
inside each test that posts a resume; i.e., wherever _RESUME_PAYLOAD is used in
test functions (tests that perform POSTs), call make_resume_payload() to obtain
a new dict for that request so tests no longer share a mutable object and cannot
leak state between tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/app/services/tasks/worker.py`:
- Around line 212-216: The current work() closure reuses the same SQLAlchemy
Session for both _mark_dead_letter and _notify_if_real_user which can fail
because _mark_dead_letter swallows DB errors and leaves the session in an
inactive state; change the flow so _mark_dead_letter runs in its own session and
the notification runs in a fresh session: call _run_in_new_session(lambda db:
_mark_dead_letter(db, task_type, payload, error=error)) first, then call
_run_in_new_session(lambda db: _notify_if_real_user(db, task_type, user_id,
"failed")) (or otherwise ensure you rollback/close the session returned from
_mark_dead_letter before creating the notification) so notification creation
never reuses a potentially tainted session.

In `@frontend/src/components/forms/CareerFormEditors/ClientEditor.tsx`:
- Around line 227-233: The delete button label should reflect the client's
vacation state; update the JSX in ClientEditor so the button text is conditional
on client.is_vacation (e.g. client.is_vacation ? '休業を削除' : '取引先を削除') while
keeping the existing onClick handler (onRemoveClient(expIndex, clientIndex)), so
users editing a vacation block see the appropriate label.

---

Nitpick comments:
In `@backend/tests/test_delete_documents.py`:
- Line 8: Replace the module-level shared dictionary _RESUME_PAYLOAD with fresh
calls to make_resume_payload() inside each test that posts a resume; i.e.,
wherever _RESUME_PAYLOAD is used in test functions (tests that perform POSTs),
call make_resume_payload() to obtain a new dict for that request so tests no
longer share a mutable object and cannot leak state between tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e032d8ec-19a5-4dda-ae3a-777d28193095

📥 Commits

Reviewing files that changed from the base of the PR and between c395449 and db7a74d.

📒 Files selected for processing (32)
  • .jscpd.json
  • backend/app/repositories/blog.py
  • backend/app/routers/github_link.py
  • backend/app/services/tasks/worker.py
  • backend/tests/conftest.py
  • backend/tests/test_delete_documents.py
  • backend/tests/test_endpoints.py
  • frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx
  • frontend/src/components/forms/CareerFormEditors/ClientEditor.tsx
  • frontend/src/components/github-link/GitHubLinkDashboard.tsx
  • frontend/src/constants/messages.ts
  • frontend/src/hooks/blog/useBlogAccountManager.test.ts
  • frontend/src/hooks/blog/useBlogAccountManager.ts
  • infra/environments/dev/main.tf
  • infra/environments/dev/main.tf
  • infra/environments/dev/terraform.tfvars
  • infra/environments/dev/versions.tf
  • infra/environments/dev/versions.tf
  • infra/environments/prod/main.tf
  • infra/environments/prod/main.tf
  • infra/environments/prod/terraform.tfvars
  • infra/environments/prod/versions.tf
  • infra/environments/prod/versions.tf
  • infra/environments/shared/main.tf
  • infra/environments/shared/variables.tf
  • infra/environments/shared/versions.tf
  • infra/environments/stg/main.tf
  • infra/environments/stg/main.tf
  • infra/environments/stg/terraform.tfvars
  • infra/environments/stg/versions.tf
  • infra/environments/stg/versions.tf
  • scripts/lint-frontend-messages.sh
✅ Files skipped from review due to trivial changes (7)
  • infra/environments/stg/terraform.tfvars
  • infra/environments/prod/terraform.tfvars
  • infra/environments/prod/versions.tf
  • .jscpd.json
  • infra/environments/stg/versions.tf
  • infra/environments/prod/main.tf
  • frontend/src/hooks/blog/useBlogAccountManager.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/tests/test_endpoints.py
  • frontend/src/components/github-link/GitHubLinkDashboard.tsx

Comment thread backend/app/services/tasks/worker.py Outdated
Comment thread frontend/src/components/forms/CareerFormEditors/ClientEditor.tsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant