SCALE — Build Lab
開発パターン · TYPESCRIPT LIBRARY

タスク推定アルゴリズム

CATEGORY開発パターン TYPETypeScript Library EFFORT120〜360分 DIFFICULTY
PRIMARY CODE
ts · lib/task-estimate.ts
// タスク名からざっくり所要時間を推定するヒューリスティック
// 100%正解ではないが、全タスク1時間固定よりは遥かにマシ。
// ユーザーが上書き可能。

export interface EstimateRule {
  keywords: string[];
  minutes: number;
  label: string;
}

// 優先順に評価(早いものマッチが勝つので、具体的なものを先に)
const RULES: EstimateRule[] = [
  { keywords: ['MTG', 'mtg', '打合せ', '打ち合わせ', 'ミーティング', '会議', '商談', '面談', '1on1', 'オンボーディング'], minutes: 60, label: 'MTG/面談' },
  { keywords: ['資料作成', '資料の作成', '提案書', 'プレゼン', 'スライド', 'パワポ'], minutes: 90, label: '資料作成' },
  { keywords: ['分析', 'リサーチ', '調査', '競合分析', '市場調査'], minutes: 90, label: '分析・リサーチ' },
  { keywords: ['制作', 'クリエイティブ', 'デザイン', 'バナー', 'LP', 'HP修正', 'サイト修正'], minutes: 90, label: '制作' },
  { keywords: ['ブラッシュアップ', 'レビュー', '振り返り', '検討'], minutes: 45, label: 'レビュー系' },
  { keywords: ['メール', 'DM', '返信', '連絡', '送信', '送付', 'Slack'], minutes: 15, label: 'メール・連絡' },
  { keywords: ['確認', 'チェック', '閲覧'], minutes: 15, label: '確認' },
  { keywords: ['決定', '決め', '承認'], minutes: 15, label: '決定' },
  { keywords: ['リスト作成', 'リストアップ', '一覧作成'], minutes: 60, label: 'リスト作成' },
  { keywords: ['整理', '片付け', 'アーカイブ', '削除'], minutes: 30, label: '整理' },
  { keywords: ['架電', 'テレアポ', 'コール'], minutes: 60, label: '架電' },
  { keywords: ['投稿作成', '投稿', 'ポスト作成'], minutes: 30, label: '投稿作成' },
  { keywords: ['日報', '報告'], minutes: 10, label: '日報' },
  { keywords: ['記入', '入力'], minutes: 15, label: '入力' },
];

export const DEFAULT_ESTIMATE_MINUTES = 30;

/**
 * タスク名から所要時間を推定(キーワードベース)
 * 数値付き(例: "300件メール作成")は数値ベースで調整
 */
export function estimateTaskMinutes(taskName: string): { minutes: number; reason: string } {
  if (!taskName) return { minutes: DEFAULT_ESTIMATE_MINUTES, reason: 'デフォルト' };
  const name = taskName.toLowerCase();

  // 数値パターン(例: 300件, 60社)→ 件数ベースで加算
  const countMatch = taskName.match(/(\d+)\s*(件|社|人|本|通)/);
  if (countMatch) {
    const count = parseInt(countMatch[1], 10);
    if (taskName.match(/(メール|連絡|DM|送信|送付)/)) {
      return { minutes: Math.min(180, Math.max(15, Math.ceil(count / 50) * 15)), reason: `件数: ${count}${countMatch[2]} × メール系` };
    }
    if (taskName.match(/(架電|コール|電話)/)) {
      return { minutes: Math.min(240, Math.max(30, Math.ceil(count / 20) * 30)), reason: `件数: ${count}${countMatch[2]} × 架電` };
    }
    if (taskName.match(/(リスト|作成|ピック)/)) {
      return { minutes: Math.min(120, Math.max(20, Math.ceil(count / 30) * 15)), reason: `件数: ${count}${countMatch[2]} × リスト` };
    }
  }

  // キーワードマッチ
  for (const rule of RULES) {
    for (const kw of rule.keywords) {
      if (taskName.includes(kw)) {
        return { minutes: rule.minutes, reason: rule.label };
      }
    }
  }
  return { minutes: DEFAULT_ESTIMATE_MINUTES, reason: '推定なし' };
}

// 分→時間表示
export function fmtMinutes(mins: number): string {
  if (mins < 60) return `${mins}分`;
  const h = Math.floor(mins / 60);
  const m = mins % 60;
  return m === 0 ? `${h}時間` : `${h}時間${m}分`;
}

// 想定時間のクイック選択プリセット(Math.maxで下限15分)
export const TIME_PRESETS = [15, 30, 45, 60, 90, 120, 180];

前提条件
Tailwind CSS v4TypeScript 5
USE CASES
  • タスク管理
  • プロジェクト管理

タスク推定アルゴリズム

:LiTarget: 用途

タスク内容から推定時間を自動算出するロジック。過去実績から学習。

:LiSparkle: 特徴

  • 推定アルゴリズム
  • 実績学習
  • カテゴリ別係数

:LiCode: 実コード(SCALE Base より自動抽出)

:LiInfo: lib/task-estimate.ts の中身そのもの。コピペ即可。

// タスク名からざっくり所要時間を推定するヒューリスティック
// 100%正解ではないが、全タスク1時間固定よりは遥かにマシ。
// ユーザーが上書き可能。

export interface EstimateRule {
  keywords: string[];
  minutes: number;
  label: string;
}

// 優先順に評価(早いものマッチが勝つので、具体的なものを先に)
const RULES: EstimateRule[] = [
  { keywords: ['MTG', 'mtg', '打合せ', '打ち合わせ', 'ミーティング', '会議', '商談', '面談', '1on1', 'オンボーディング'], minutes: 60, label: 'MTG/面談' },
  { keywords: ['資料作成', '資料の作成', '提案書', 'プレゼン', 'スライド', 'パワポ'], minutes: 90, label: '資料作成' },
  { keywords: ['分析', 'リサーチ', '調査', '競合分析', '市場調査'], minutes: 90, label: '分析・リサーチ' },
  { keywords: ['制作', 'クリエイティブ', 'デザイン', 'バナー', 'LP', 'HP修正', 'サイト修正'], minutes: 90, label: '制作' },
  { keywords: ['ブラッシュアップ', 'レビュー', '振り返り', '検討'], minutes: 45, label: 'レビュー系' },
  { keywords: ['メール', 'DM', '返信', '連絡', '送信', '送付', 'Slack'], minutes: 15, label: 'メール・連絡' },
  { keywords: ['確認', 'チェック', '閲覧'], minutes: 15, label: '確認' },
  { keywords: ['決定', '決め', '承認'], minutes: 15, label: '決定' },
  { keywords: ['リスト作成', 'リストアップ', '一覧作成'], minutes: 60, label: 'リスト作成' },
  { keywords: ['整理', '片付け', 'アーカイブ', '削除'], minutes: 30, label: '整理' },
  { keywords: ['架電', 'テレアポ', 'コール'], minutes: 60, label: '架電' },
  { keywords: ['投稿作成', '投稿', 'ポスト作成'], minutes: 30, label: '投稿作成' },
  { keywords: ['日報', '報告'], minutes: 10, label: '日報' },
  { keywords: ['記入', '入力'], minutes: 15, label: '入力' },
];

export const DEFAULT_ESTIMATE_MINUTES = 30;

/**
 * タスク名から所要時間を推定(キーワードベース)
 * 数値付き(例: "300件メール作成")は数値ベースで調整
 */
export function estimateTaskMinutes(taskName: string): { minutes: number; reason: string } {
  if (!taskName) return { minutes: DEFAULT_ESTIMATE_MINUTES, reason: 'デフォルト' };
  const name = taskName.toLowerCase();

  // 数値パターン(例: 300件, 60社)→ 件数ベースで加算
  const countMatch = taskName.match(/(\d+)\s*(件||||通)/);
  if (countMatch) {
    const count = parseInt(countMatch[1], 10);
    if (taskName.match(/(メール|連絡|DM|送信|送付)/)) {
      return { minutes: Math.min(180, Math.max(15, Math.ceil(count / 50) * 15)), reason: `件数: ${count}${countMatch[2]} × メール系` };
    }
    if (taskName.match(/(架電|コール|電話)/)) {
      return { minutes: Math.min(240, Math.max(30, Math.ceil(count / 20) * 30)), reason: `件数: ${count}${countMatch[2]} × 架電` };
    }
    if (taskName.match(/(リスト|作成|ピック)/)) {
      return { minutes: Math.min(120, Math.max(20, Math.ceil(count / 30) * 15)), reason: `件数: ${count}${countMatch[2]} × リスト` };
    }
  }

  // キーワードマッチ
  for (const rule of RULES) {
    for (const kw of rule.keywords) {
      if (taskName.includes(kw)) {
        return { minutes: rule.minutes, reason: rule.label };
      }
    }
  }
  return { minutes: DEFAULT_ESTIMATE_MINUTES, reason: '推定なし' };
}

// 分→時間表示
export function fmtMinutes(mins: number): string {
  if (mins < 60) return `${mins}分`;
  const h = Math.floor(mins / 60);
  const m = mins % 60;
  return m === 0 ? `${h}時間` : `${h}時間${m}分`;
}

// 想定時間のクイック選択プリセット(Math.maxで下限15分)
export const TIME_PRESETS = [15, 30, 45, 60, 90, 120, 180];

:LiFolder: ソースファイルのパス

/Users/oogushiyuuki/Library/CloudStorage/GoogleDrive-y-ogushi@scale-group.co.jp/マイドライブ/AI/scale-base/lib/task-estimate.ts

:LiHandPointer: 使い方

対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。

:LiAlertCircle: 注意事項

  • 依存パッケージを忘れず追加