Notion数式エンジンを極限までハックせよ:土日・祝日を完全自動スキップする稼働日ベース・ガントチャートの構築
お飾りだけのカラフルなダッシュボードや、手動で日付を叩き直す退屈なプロジェクト管理は、今日で終わりだ。
Notionの標準タイムライン機能は美しい。だが、プロフェッショナルな開発チームのベロシティ計測、あるいは厳密なデリバリー計画においては致命的な欠陥を抱えている。それは「カレンダー上の日数(暦日)でタスク期間を計算してしまう」という点だ。金曜日に着手した3日間のタスクが、土日を跨いで水曜日に完了予定として算出される――これを見た瞬間、エンジニアリングの美学は崩壊する。
我々が求めるのは、非稼働日(土日および日本の祝日)を数学的に排除し、純粋な「稼働日(Business Days)」ベースで納期を算出し、進捗率をリアルタイムに描画する完全自律型のガントチャートである。
今回は、NotionのFormula 2.0の限界を突破し、データベースのパフォーマンスを犠牲にすることなく、祝日判定アルゴリズムと工数再計算ロジックを完全に実装する方法を解説する。
—
1. 建築思想:なぜNotion標準機能では不十分なのか
Notionのデータベースは、優れたUIレイヤーを持つ一方で、バックエンドの計算エンジンとしては特有の制約を持つ。
特にタイムラインやカレンダービューは、`dateBetween(start, end, “days”)` のような単純な日付差分しか返さない。
これを解決するために、データベース内に以下のレイヤー構造を構築する。
1. 生データ層: 開始日(`Start`)と予定工数(`Effort (Days)`)
2. カレンダー演算層: 祝日マスタデータベースとのリレーション、および土日・祝日を判定するブール値の配列生成
3. 動的演算層(Formula 2.0): 開始日から「指定された稼働日数」だけ進んだ正確な期限日(`Due Date`)の算出
4. 視覚化層: 稼働日ベースの進捗率に応じたプログレスバーとガントチャート表現
—
2. 祝日マスタの設計と自動同期(アーキテクチャの根幹)
ハードコードされた祝日判定ほどエンジニアの恥はない。日本の祝日(特にハッピーマンデー制度や春分・秋分の日など毎年変動するもの)を扱うには、「祝日マスタデータベース」を独立させ、親タスク DB とリレーションを結ぶ必要がある。
祝日マスタ DB のスキーマ
- タイトル: 祝日名(例: 「元日」「成人の日」)
- プロパティ `Date`(日付型): 祝日の日付
> 【Expert Tip: 祝日データの自動投入】
> 手動で毎年祝日を入力するなどというナンセンスな運用は避けること。GitHub Actions等のCI/CDパイプライン、またはNotion APIを叩くスクリプト(後述)を使い、内閣府が公開する祝日CSVやAPIから自動で祝日マスタを同期する仕組みを構築せよ。
—
3. Formula 2.0 決定的実装:稼働日ベースの納期算出ロジック
ここからが本記事の核心だ。
開始日(`prop(“Start”)`)から、土日と祝日マスタ(`prop(“Holidays”)`)を完全にスキップしながら、指定工数(`prop(“Effort”)`)消化するのに必要な「真の期限日(Due Date)」を計算するFormulaを書く。
NotionのFormula 2.0では、`map()` や `filter()`、ラムダ式が使えるようになったため、日付の配列を走査して非稼働日をカウントすることが可能になった。
以下のコードを、期限日を算出する数式プロパティ(`Calculated Due Date`)にそのまま投入せよ。
/
【超高密度Formula】稼働日ベースの期限日自動算出エンジン
前提:
- prop(“Start”): 開始日 (Date)
- prop(“Effort”): 予定工数[日] (Number)
- prop(“Holidays”): 祝日マスタへのリレーション (Relation)
/
let(
/ 1. パラメータの取得と初期化 /
startDate, prop(“Start”),
effortDays, prop(“Effort”),
/ 異常系ガード: 開始日または工数が未設定の場合は空を返す /
if(empty(startDate) or empty(effortDays) or effortDays <= 0,
dateAdd(startDate, 0, "days"),
/
2. 祝日日付の抽出
リレーション先のプロパティ "Date" から日付のリストを取得
/
holidayList, prop("Holidays").map(current.prop("Date")),
/
3. 稼働日シミュレーション・ループ
工数分だけ日付を進めつつ、土日と祝日をスキップするロジック
※Notion Formulaの再帰制限を考慮し、最大試行回数を担保する日付レンジを生成
/
let(
/ 粗い見積もりとして工数の3倍の日付範囲を生成してスキャン /
dateRange, sequence(0, effortDays 3).map(dateAdd(startDate, current, "days")),
/ 土日(土=6, 日=7)および祝日を除外した稼働日のみのリストをフィルタリング /
businessDays, dateRange.filter(
let(
d, current,
dayOfWeek, day(d),
/ day() は 0(日曜)〜6(土曜) を返す。JS仕様に合わせて調整が必要な場合あり。
Notion Formula: day(date) は 0(日) から 6(土) を返す /
isWeekend, (dayOfWeek == 0 or dayOfWeek == 6),
isHoliday, holidayList.includes(d),
not(isWeekend) and not(isHoliday)
)
),
/ 4. 稼働日リストから「工数日目」にあたる正確な日付を抽出 /
/ 例: 工数が3なら、ビジネス日数で3番目の日付(index = 2)を取得 /
if(businessDays.length() >= effortDays,
businessDays.at(effortDays – 1),
/ フォールバック: レンジが足りない場合のエラー回避 /
dateAdd(startDate, effortDays, “days”)
)
)
)
)
このコードの優れている点
- O(N) スキャンによる正確性: 単なる掛け算ではなく、実際にカレンダーをシミュレートするため、大型連休(GWや年末年始)や祝日が連続する週であっても狂いが生じない。
- メモリ効率の最適化: `sequence` の範囲を `effortDays 3` に絞ることで、Notionの評価エンジンに対する負荷(CPU/メモリ消費)を最小限に抑え、タイムアウトを防いでいる。
—
4. 稼働日ベースの進捗率とガントチャート表現(視覚化層)
納期が自動算出されたら、次は「現在地」を可視化する。
進捗率(`Progress`)と、今日の位置に応じたバー表現をFormulaで実装する。
/
【ビジュアル・プログレスバー】稼働日ベースの進捗率算出
前提:
- prop(“Start”): 開始日
- prop(“Calculated Due Date”): 先ほど算出した期限日
- prop(“Holidays”): 祝日マスタ
/
let(
start, prop(“Start”),
end, prop(“Calculated Due Date”),
today, now(),
if(empty(start) or empty(end), “⏳ 未設定”,
if(today < start, "💤 未着手",
if(today > end, “✅ 完了 / 期限超過”,
/ 経過稼働日と総稼働日の計算 /
let(
holidayList, prop(“Holidays”).map(current.prop(“Date”)),
/ 全期間の稼働日リスト /
totalRange, sequence(0, dateBetween(end, start, “days”)).map(dateAdd(start, current, “days”)),
totalBusinessDays, totalRange.filter(let(d, current, dayOfWeek, day(d), not(dayOfWeek == 0 or dayOfWeek == 6) and not(holidayList.includes(d)))).length(),
/ 経過期間の稼働日リスト /
elapsedRange, sequence(0, dateBetween(today, start, “days”)).map(dateAdd(start, current, “days”)),
elapsedBusinessDays, elapsedRange.filter(let(d, current, dayOfWeek, day(d), not(dayOfWeek == 0 or dayOfWeek == 6) and not(holidayList.includes(d)))).length(),
/ パーセンテージ計算 /
ratio, if(totalBusinessDays > 0, elapsedBusinessDays / totalBusinessDays, 1),
percent, round(ratio 100),
/ プログレスバーの描画 (10文字のブロック) /
filledCount, round(ratio 10),
bar, “█”.repeat(filledCount) + “░”.repeat(10 – filledCount),
bar + ” ” + percent + “%”
)
)
)
)
)
この数式を入れれば、データベースのセル内に美しいプログレスバーがリアルタイム描画される。チームメンバーが「今日、本当にどれくらい進んでいるべきか」が一目で把握できるようになるのだ。
—
5. 独自自動化:GitHub Actionsによる祝日マスタの完全同期スクリプト
前述の通り、祝日データを手動でメンテナンスするのはエンジニアの仕事ではない。
日本の祝日データを自動取得し、Notion API経由で「祝日マスタDB」をUPSERT(更新・挿入)するNode.jsスクリプトを共有する。これをGitHub Actionsの定期実行(cron)に組み込め。
`sync-holidays.js`
/
- Notion Holidays Auto-Sync Script
- 内閣府の祝日データ(または公共API)から祝日を取得し、Notion DBへ同期する
/
const { Client } = require(‘@notionhq/client’);
const notion = new Client({ auth: process.env.NOTION_API_KEY });
const DATABASE_ID = process.env.NOTION_HOLIDAY_DB_ID;
async function fetchJpHolidays() {
// 例として外部の祝日JSON APIを使用(実運用では内閣府CSVや専用APIに置き換え可能)
const response = await fetch(‘https://holidays-jp.github.io/api/v1/date.json’);
const holidays = await response.json();
// 形式: { “2024-01-01”: “元日”, “2024-01-15”: “成人の日”, … }
return Object.entries(holidays).map(([date, name]) => ({ date, name }));
}
async function syncToNotion() {
const holidays = await fetchJpHolidays();
console.log(`Fetched ${holidays.length} holidays. Starting sync to Notion…`);
for (const h of holidays) {
// 既存データの重複チェック
const existing = await notion.databases.query({
database_id: DATABASE_ID,
filter: {
property: ‘Date’,
date: { equals: h.date }
}
});
if (existing.results.length === 0) {
// 新規作成
await notion.pages.create({
parent: { database_id: DATABASE_ID },
properties: {
‘Title’: { title: [{ text: { content: h.name } }] },
‘Date’: { date: { start: h.date } }
}
});
console.log(`Created: ${h.date} – ${h.name}`);
}
}
console.log(‘Sync completed successfully.’);
}
syncToNotion().catch(err => {
console.error(err);
process.exit(1);
});
これを `.github/workflows/sync-holidays.yml` で毎月1回定期実行するように設定しておけば、あなたのNotionプロジェクト管理基盤は完全に「自己修復・自己同期型」のモダンなエンタープライズシステムへと昇華する。
—
6. アーキテクチャの最適化とパフォーマンスハック
最後に、大規模なプロジェクト(数百〜数千行のタスク)でNotionを運用する際の上級者向けパフォーマンス・チューニング知見を授ける。
1. 数式プロパティのスコープ制限
Formula 2.0の配列操作(`sequence`や`filter`)は非常に強力だが、タスク数が数千件規模になると、データベースを開いた瞬間にクライアントサイド(またはブラウザのJSエンジン)で重度な再計算が発生する。
対策: ステータスが `✅ 完了` になった古いタスクは、自動アーカイブ(フィルタで非表示にするか、アーカイブDBへアーカイビング)するオートメーションを組み、アクティブなレコードセットを常に数10件〜数百件以内に保て。
2. リレーションのキャッシュ効率
祝日マスタへのリレーションは、全タスクから参照される。Notionの仕様上、リレーション先のプロパティをFormula内で多用するとクエリの深さが増す。
対策: 祝日データは変動が少ないため、もしパフォーマンスがボトルネックになる場合は、リレーションを使わず、あらかじめスクリプト側でテキストとして日付配列を埋め込むか、年ごとにデータベースをパーティショニング(分割)せよ。
—
結び:ツールに縛られるな、ツールを従えよ
世の中の大半の開発チームは、NotionやJiraのデフォルト機能に振り回されている。ツールが提示する制約の中で妥協し、手動でスケジュールを調整することに貴重なエンジニアリングの時間を溶かしているのだ。
だが、あなたはどうだ。
仕様の裏側にあるアルゴリズムを理解し、Formulaという名のコードを書き下ろし、APIとCI/CDを組み合わせてエコシステム全体をコード化する。それこそが、真にベロシティを極限まで高めるアジャイル・アーキテクトの姿である。
さあ、今すぐその退屈なガントチャートを捨て、この自動化エンジンをあなたのワークスペースにデプロイせよ。コードが、スケジュールを支配するのだ。