SaaS型翻訳サービスWeglotでReact SPAを多言語化した

はじめに

こんにちは。エンジニアリングマネージャの星野です。今回はReactを用いたSPAの他言語化についての事例を紹介します。

テックドクターで開発している臨床研究支援システム「SelfBase」において、海外案件への対応をきっかけに管理画面の多言語化が必要となりました。

フロントエンドはReactを用いたSPA(Single Page Application)で構築されており、自前で実装する場合は react-i18next などを利用したi18n(国際化)対応を行うことになります。そのためには数ヶ月単位の少なくない開発工数が見込まれました。

そこで、「自前での実装」という選択肢だけでなく、「SaaS型の翻訳サービス」の導入を本格的に検討することにしました。


フロントエンド技術スタックと検討ポイント

多言語化を検討するにあたり、技術スタックとSaaS選定のポイントについて説明します。

フロントエンド技術スタック

フロントエンドの主要な技術スタックは以下の通りです。

  • UI: React
  • 言語: TypeScript
  • ルーティング: React Router
  • スタイリング: MUI + Emotion

SaaS選定における検討のポイント

SaaSを選定するにあたっては、開発工数をかけずに多言語化を実現するため、いくつかの点を重視しました。

  1. SPAに対応していること。
  2. 翻訳管理が容易であること。たとえば、翻訳された文言の一元管理機能ができる、ユーザーデータのような翻訳不要な要素を柔軟に除外できる、など。
  3. 予算内で導入できるコストであるか。
  4. SOC2やGDPRなどのセキュリティ基準を満たしているか。
  5. 国内外での十分な導入実績があるか。

SaaSによる多言語化の比較検討

上記のポイントに基づき、いくつかのSaaSを比較検討しました。

サービス名 SPA対応 価格帯 特徴
shutto翻訳 実績不明 安価 国内サービス。SPAでの利用実績が確認できず、今回の要件には合致しませんでした。
WOVN.io 対応可能 高額(要問合せ) 日本国内での大手企業による採用実績が豊富。ただし、コストが比較的高額でした。アプリの翻訳機能もあり、予算があるなら利用したかった。
ConveyThis 対応可能 安価 比較的安価で導入しやすそう。ただ、後述するWeglotと比較すると実績やシェアの面で見劣りしていました。
Weglot 対応可能 手頃 SPA対応が可能。料金も手頃で、セキュリティ基準も満たしていました。調べた範囲ではPV数ベースの集計において高いシェアを持っているようでした。

検討結果

比較検討の結果、Weglotを導入することにしました。理由は下記です。

  • ReactなどのSPAプロジェクトへの導入がヘルプに記載されており、実績があると判断できました。
  • 実際にテストで動作させてみても、問題なく対応していることが確認できました。
  • 翻訳結果を管理するダッシュボードがあり、非エンジニアでも直感的に翻訳の修正や管理が可能でした。
  • AIを利用した翻訳が利用でき、翻訳の修正工数を減らせると期待しました。
  • SOC2およびGDPRに準拠しており、エンタープライズレベルのセキュリティ要件を満たしています。
  • Google検索やWappalyzerなどの情報から、ある程度シェアがあると判断。国外では大手での導入実績もあるようでした。
  • 必要な機能を備えつつ、料金が手頃で、予算感に最もマッチしていました。

実装と運用の工夫

Weglotの導入はスムーズでしたが、実際のプロダクトで運用するためにはいくつかの工夫が必要でした。

導入方法

  • Weglotでのプロジェクト作成

Weglotでアカウントを作成し、翻訳元(日本語)と翻訳先の言語(英語など)を設定します。

  • インテグレーションの方式の選択

サブドメイン方式とサブディレクトリ方式があります
SEOなどの観点からサブディレクトリ方式が推奨されていますが、今回はサブドメイン方式を選択しました。
理由としては、既存サイト全体がWeglot経由で配信されようになるため、影響範囲を抑える目的でサブドメイン方式を選択しました。

  • DNSレコードの設定

WeglotからCNAMEに登録すべきURLが提供されるので、利用しているDNSプロバイダー(Amazon Route 53、Google Cloud DNSなど)から、指定したサブドメインをCNAMEレコードとして、追加します。
この設定により、例えば、en.example.comへのアクセスがWeglotのサーバーに向けられ、翻訳されたコンテンツが配信されるようになります。

  • JavaScriptスニペットの埋め込み

発行されたAPIキーを含むJavaScriptスニペットをWebサイトに埋め込みます。
これは主に、翻訳単語の抽出や言語切り替えボタン(言語スイッチャー)の表示、言語によるサイトの自動切り替えなどに利用します。

  • SPA特有の設定

デフォルトでは動的に描画されるコンテンツは翻訳されません。
翻訳対象に含めるため、Weglotのプロジェクト設定にある Dynamic Element にて、 body セレクタを指定します。

キャプチャ
動的要素の翻訳設定画面

実装上の注意点

Weglotは手軽に導入できる反面、自由度は低いと感じました。
そのため運用にあたっては、以下のような工夫が必要でした。

翻訳対象の除外設定:

Weglotはデフォルトでページ上の全テキストを翻訳しようとします。翻訳が不要な箇所については、翻訳対象から除外する設定を行う必要がありました。

  • ユーザー名などのユーザが入力した内容

本来翻訳の対象としたくない部分ですし、翻訳の管理上もユーザが新しい単語を入力するために訳語の設定が必要となってしまうのは好ましくありません。
また、翻訳語数ベースの課金モデルにおいてコストを抑える意図もあります。

対策としては、翻訳したくない要素をラップするコンポーネントをReactで作成、特定のCSSセレクタを持つ要素を翻訳対象から除外する機能と組み合わせ、自動翻訳を抑止しました。

export const NoTranslate = forwardRef<HTMLElement, BoxProps>(
  ({ component = 'span', ...boxProps }, ref) => (
    <Box
      ref={ref}
      className="no-translate"
      translate="no"
      component={component}
      {...boxProps}
    >
      {boxProps.children}
    </Box>
  ),
)

NoTranslate.displayName = 'NoTranslate'

NoTranslateコンポーネント実装イメージ
 

キャプチャ
Weglotの自動翻訳抑止機能
  • 日付や数値のローカライズ

日付や数値は翻訳ではなく、各言語の文化に合わせたフォーマットが必要です。これらは翻訳対象から除外し、Intl.DateTimeFormatIntl.NumberFormatといったブラウザ標準APIで対応しました。

動的テキストの制約(語順と単数/複数形):

例えば {count}日間以上デバイスのデータがアップロードされてない場合 のような動的に数値を埋め込むテキストを翻訳する場合、{count} 部分は翻訳から除外するため、数字を除いた「日間以上デバイスのデータがアップロードされてない場合」というテキストをもとに翻訳が行われます。

これにより、2つの大きな問題が生じます。

1つ目は語順の問題です。
翻訳後のテキストは、翻訳されなかった {count} の後ろに単純に連結されます。しかし、多くの言語では日本語と語順が異なります。例えば、英語で「For more than {count} days...」のような {count} の前に前置詞が来るケースがありますし、他にも文法上、数字が文中に入ってしまう場合もあります。こういった文章は自然に作ることができません。

2つ目は単数/複数形の切り替えです。`{count}`の値に応じて `1 day` / `2 days` のように単語の形を変化させることができません。

今回はこれらの制約を許容し、妥協案として {count} day(s) without device data uploaded のような、英語表記で対応しました。

1つ目の問題は翻訳元の文に変数プレースホルダーを挿入する「variables」機能を利用することで解決はできそうでしたが、今回はそこまで対応することができませんでした。

文脈に応じた翻訳の難しさ

Weglotでは、基本的に一つの単語は一つの訳語に対応します。例えば「日」という単語を day(s) と翻訳した場合、別のページで「日曜日」の文脈で使われていても day(s) と訳されてしまう可能性があります。これを防ぐには、元の日本語の単語を文脈に応じてより具体的に(例:「日付」「日曜日」など)使い分けるといった、原文側での工夫が必要になります。

Weglotよりもさらに厳密な表現や文脈に合わせた翻訳が求められる場合は、やはり react-i18next のようなi18nライブラリによる本格的な対応が必要と感じました。

キャプチャ
該当部分の翻訳前
キャプチャ
該当部分の翻訳後

翻訳の質と翻訳管理について

自動翻訳の品質は、まずまずといった程度でしたが、AI機能を使うとかなり不自然な内容は減りました。

長文では、手動での言い換えが必要になる場合も多少ありましたが、短いフレーズや単語レベルの翻訳は正確でした。全体としては十分に実用的な品質といえます。

また、専門用語や製品名などについては、翻訳するかどうかも含めて辞書登録できる点が便利でした。

キャプチャ
会社名や、製品名を登録した例

さらに、翻訳結果にレビュー済みかどうかが分かる仕組みが用意されており、今後画面修正などが発生した際にも、追加翻訳をスムーズに行えるようになっています。

キャプチャ
フィルタ機能で、自動翻訳対象のテキストを検索している様子。(すべて翻訳が登録されているので結果は0件)

i18nライブラリのように翻訳者が翻訳ファイルを直接編集するわけではないため、翻訳者と開発者の役割分担が明確になり、運用がスムーズになる点もメリットと感じています。

キャプチャ
コラボレーションするための画面。日本語と英語訳が表示される

導入後の感想

Weglotの導入を決定したことで、当初3ヶ月以上かかると想定していた多言語化対応が、1ヶ月程度で完了する見込みとなりました!
これにより、SaaS導入は期待通りにエンジニアの工数を大幅に削減でき、非常に助かりました。

課題点としては、初回アクセス時は一瞬だけ日本語が表示されてから英語に切り替わるという動作になっています。
この点は、当初から理解していて実用上は問題ないと判断していましたが、不自然さは感じてしまいます。

今後について

今回は、まずはいったん多言語サイトをサービス提供することが重要だと考えて、SaaSという選択肢を選びました。
ですが、一瞬日本語で表示されてしまう点や翻訳の制限などから、どこかでi18nライブラリを用いた本格的な多言語化基盤の構築も必要だと感じています。

その際には、今回見送った react-i18next によるi18n基盤の構築を再度検討し、翻訳ファイルの管理に特化したSaaS(Lokalise, Transifexなど)へ移行することも検討したいと考えています。

まずは、Weglotで翻訳の知見を溜めつつ、翻訳精度の向上や用語の統一を行うことでグローバルに通用するサービスの第一歩目としていきます。


似顔絵
書いた人:星野

ストレスが続くと肌が荒れる?:ウェアラブルデータで解き明かす皮脂と自律神経活動の関係

こんにちは。データサイエンスチームの望月です。

みなさんは、ストレスが続くと肌荒れや吹き出物が増えたり、寝不足のときに肌の調子が崩れたりした経験はありませんか?

「肌は心を映す鏡」と表現されるように、古くから肌と心(自律神経)の状態は密接に関わっていると考えられてきました。
ストレスや睡眠不足が肌にあらわれる──その背景には自律神経の乱れやホルモン分泌の変化が関わっています。

肌荒れのイメージイラスト

とりわけ肌の状態を左右する大きな要因のひとつが「皮脂」です。
皮脂が過剰に分泌されると肌荒れやニキビ、テカリ、化粧崩れの原因となり、逆に不足すると乾燥につながります。
皮脂分泌にはホルモン、自律神経、生活習慣といったさまざまな要因が関わると考えられており、皮膚科学領域でも注目されてきました。

しかし、ウェアラブルデバイスで測定した心拍変動(HRV)や睡眠といった客観的なデータを用いて、皮脂量との関連を統計的に解析した例はほとんどありません。
私たちは、この「肌と心の関係」をより科学的に捉えるため、ウェアラブルデータ(※)と皮脂測定を組み合わせて解析しました。その結果、小規模データながらも皮脂量と自律神経活動に確かな関連が見えてきました。不規則な生活習慣やストレス、ホルモンバランスの変動で皮脂量が増加する可能性が見えてきたのです。

今回は、その詳細をご紹介します。

※ウェアラブルデータ……スマートウォッチを代表とするウェアラブル端末で測定された生体情報等のデータ

研究概要

今回の解析には、女性5名・男性3名の合計8名にご協力いただき、14日間にわたりデータを収集しました。

皮脂量の測定

皮脂の量は、皮膚研究で用いられる皮脂チェッカーを使用し、毎朝の起床時に額と頬の2か所から採取しました。
皮脂チェッカーはシート状の測定器具で、肌に押し当てると皮脂が付着した部分が黒くなります。その写真を参加者にスマートフォンで撮影してもらい収集、画像を白黒に変換して数値化し、皮脂レベルとして解析に利用しています。

皮脂レベル解析の手順

生体データの取得

生体データ(心拍数や睡眠、活動量など)は Fitbit を用いて取得しました。日中の活動データとその夜の睡眠データを、翌朝の皮脂レベルに対応させて関係を調べます。

Fitbitの写真
Fitbitの例(Fitbit Inspire 3。Googleストアより引用)

解析方法

解析を行い、皮脂量と関係する生体データがあるかを調べました。
対象とした生体データは、心拍数、睡眠、そして活動量に関するものです。

まず、心拍数データから心拍変動指標(HRV) を算出し、睡眠中の平均心拍数や、自律神経の活動を評価する複数のHRV指標を解析に取り入れました。

さらに、睡眠指標として、総睡眠時間や各睡眠ステージ(覚醒、レム睡眠、浅い睡眠、深い睡眠)の時間を解析に加えました。

これらのデータと皮脂量との関連を統計的に調べるため、皮脂量を中央値で2群(低皮脂量群と高皮脂量群)に分け、各種パラメータを比較・評価しました。また、皮脂量には性差があることが知られているため、男女を分けてそれぞれ解析を行いました。

解析結果

皮脂量の性差・部位差

皮脂量には性差と部位差が見られました。
全体として、男性は女性より皮脂量が多く、また額は頬に比べて多い傾向が見られました。

グラフ

睡眠中の自律神経活動(HRV)と皮脂量

自律神経活動は「心拍変動(HRV)」から推定することができます。HRVとは、心拍のゆらぎの大きさを表す指標で、リラックス時など副交感神経が優位なときに大きく、緊張時など交感神経が優位なときに小さくなる特徴があります。

解析の結果、皮脂量が多いほど睡眠中に交感神経優位の状態にある可能性が示されました。
具体的には、皮脂の多い群では男女ともに、睡眠中のHRV指標であるRMSSDが低く、交感神経の活動を示すCSIが高く、副交感神経の活動を示すCVIが低い傾向が見られました。

睡眠中RMSSD

グラフ

睡眠中CSI

グラフ

睡眠中CVI

グラフ

睡眠指標と皮脂量

睡眠指標との関係を調べたところ、女性では皮脂の多い群で総睡眠時間とレム睡眠が有意に短く、深い睡眠も短い傾向が見られました。
一方、男性では皮脂の多い群で深い睡眠が有意に短いことが分かりました。
このことから、睡眠の質の低下も皮脂量の増加に関わっている可能性があると考えられます。

睡眠時間

グラフ

レム睡眠時間

グラフ

深い睡眠時間

グラフ

まとめ

こうした肌と心の関係はこれまで皮膚科学や精神皮膚医学の分野では研究されてきましたが、今回のようにウェアラブルデータで皮脂量と自律神経活動のつながりを検証した例はほとんどありません。

解析の結果から、皮脂量が多い群では睡眠中の副交感神経活動(RMSSD、CVI)が低く、交感神経活動(CSI)が高い傾向が見られました。さらに、女性では総睡眠時間・レム睡眠・深い睡眠が短く、男性でも深い睡眠が短いという特徴が明らかになりました。

一般に、交感神経が優位な状態では、腎臓の上にある副腎皮質からコルチゾール(代表的なストレスホルモン)やアドレナリンといったホルモンが分泌されます。その際、男性ホルモンの一種であるアンドロゲンも増加することが知られており、このアンドロゲンは皮脂分泌を促す作用を持っています。
今回の結果は、不規則な生活習慣(睡眠時間や睡眠の質)、ストレス、ホルモンバランスの変動によって交感神経が優位になり、その結果として皮脂量が増加するという可能性を支持しています。

小規模の解析ではありますが、ウェアラブルデバイスによる客観的なデータを用いて、皮脂量と自律神経活動との関連を統計的に示した初めての試みの一つといえるかもしれません。今後の研究により、肌と生活習慣、自律神経の関係がさらに解明されていくことが期待されます。

最後に

将来的には、自律神経や睡眠といったウェアラブルデータから皮脂量の増減を予測することで、スキンケアの方法を変えたり、生活習慣を見直すきっかけをユーザーに提供できる可能性があります。

「自分の肌の変化を先取りしてケアする」そんな未来が、データサイエンスによって実現に近づいています。


書いた人:望月

ウェアラブルデバイス(Fitbit) × Apache Hudiによるデータ収集基盤の設計案

こんにちは。テックドクターのエンジニア、伊藤です。

テックドクターでは、心拍や睡眠などの日常の生体データを取得するためにウェアラブルデバイスを活用しています。これらのデバイスで集めたデータを使って、健康管理やフィットネスの向上、さらには医療分野への応用を目指しています。

いま世の中に出回っているウェアラブルデバイスの例としては、 Apple Watch、Garmin、Fitbit などがあります。これらは(理想的には) 24 時間 365 日データを取得し続けることができます。そのデータは同期している iPhone のストレージや各社のサーバーに保存されますが、活用のためにデータを取得したいときはWeb API などを通じて取得することになります。

その際、日付などの条件を指定してデータを取得する以外に、(仕組みが提供されていれば)Webhook などを活用してリアルタイムに近い形でデータを取得することも可能です。

後者のようなリアルタイム/準リアルタイムのデータ取得は、ユーザーの行動変化に即座に対応したい場合や、継続的なモニタリングが必要な場合に特に有用です。ただし、それに対応したデータ収集基盤の構築も必要となってきます。

この記事では、 Fitbit に対してリアルタイムでデータ収集をする際の基盤設計案を紹介したいと思います。なお、今回は Fitbit を例としますが、似たような構成の API や Webhook 機能を提供しているサービスであれば、同様のアーキテクチャを適用可能だと思います。

Fitbitデータ収集における課題

まずは、今回のデータ取得の課題について整理しておきましょう。ウェアラブルデバイスという特性上発生する課題と、 Fitbit API による課題があります。

課題
  • データ更新の頻度: ユーザーの日次データ(歩数、睡眠時間等)は一日中継続的に更新される
  • 遅延データの到着: デバイス同期のタイミングにより、過去日時のデータが後から送信される場合がある
  • レート制限: Fitbit API には厳格な制限があり、効率的なデータ収集戦略が必要
  • データ一貫性: リアルタイム性とデータ整合性のバランス
  • スケーラビリティ: ユーザー数の増加に対応可能な設計
  • コスト最適化: 不要な API 呼び出しの削減

ここで注目してほしいのが最初の2つ「データ更新の頻度」「遅延データの到着」です。実はこれらの特性にぴったりの強みを持ったプラットフォームがあります。それが Apache Hudi です。

Apache Hudi とは?

Apache Hudi が何であるか、まずは公式ドキュメントから引用します。

What is Apache Hudi

Apache Hudi (pronounced "hoodie") pioneered the concept of "transactional data lakes", which is more popularly known today as the data lakehouse architecture. Today, Hudi has grown into an open data lakehouse platform, with a open table format purpose-built for high performance writes on incremental data pipelines and fast query performance due to comprehensive table optimizations.

引用元:Apache Hudi 公式ドキュメント

全訳すると長くなるのでかいつまんで説明すると、Hudi(フーディ)はデータレイクハウスアーキテクチャを実現するためのオープンソースプロジェクトです。インクリメンタルなデータパイプラインに適していると説明されています。

また、AWSのドキュメントでは、センサーやIoTデバイスからのストリーミングデータに対して、特定のデータ挿入と更新イベントが必要な場合にHudiが適していると述べられています。

Working with streaming data from sensors and other Internet of Things (IoT) devices that require specific data insertion and update events.

引用元:AWS EMR Hudi

Hudiのこれらの強みは、まさに今回の用途、ウェアラブルデータを「準リアルタイム」に取得し「同一レコードの頻繁な更新」をする際にとても有効です。

Hudiは他にもタイムトラベル機能やスキーマ進化のサポートなど、データレイクハウスの運用に必要な機能もいろいろと備えています。

というわけで、今回はマイクロサービスアーキテクチャと Apache Hudi を組み合わせ、リアルタイム通知をマイクロバッチ処理へ変換する仕組みを考えてみることにしました。

※ただし、本記事は実装前の設計案であり、実際の運用環境での検証は今後の課題です。

Fitbit Subscription APIについて

Subscription APIとは

アーキテクチャ全体の説明に入る前に、Fitbit 側のAPIについても説明しておきます。

Fitbit Subscription APIは、ユーザがFitbitアプリを通じてFitbitサーバーにデータを同期したときに、事前に登録したサーバーにWebhookを通じて通知を送信するAPIです。これを利用することで、データの変更を即座に検知し、必要なデータ取得をトリガーできます。

参考:Fitbit Web API Documentation Using Subscriptions

通知データの構造

Subscription APIからの通知は以下のJSONフォーマットで受け取ります。

[
    {
        "collectionType": "foods",
        "date": "2010-03-01",
        "ownerId": "USER_1",
        "ownerType": "user",
        "subscriptionId": "1234"
    }
]


主要フィールドの説明

フィールド 説明
collectionType データの種類 `foods`, `activities`, `sleep`, `heartrate`
date データの日付 `2010-03-01`
ownerId ユーザー識別子 `USER_1`
ownerType オーナータイプ `user` (固定値)
subscriptionId サブスクリプション識別子 `1234`

注目して欲しい点は、通知自体には詳細なデータは含まれず、あくまで「どのユーザーのどのデータタイプが更新されたか」の情報のみであることです。実際のデータ取得にはユーザー・collectionType・日付に基づいて、別途 API 呼び出しが必要です。

Subscription API利用時の制約事項

前述したように、API 呼び出しには制限があります。

レート制限:
150 requests/hour/user

レスポンスタイムアウト:

  • Fitbitは通知送信時に5秒のタイムアウトを設定
  • エンドポイントは迅速な応答(204 No Content)が必要
  • これらが守られない場合、最終的にサブスクリプションを無効化する可能性がある

以上が Fitbit Subscription API の概要です。

アーキテクチャの全体像

全体のシステム構成図

いよいよシステムの全体像です。

大きく以下の2つの部分に分けられます。
①Fitbit Subscriptionからの通知を受け取り永続化する
②ミニバッチでAPI呼び出しを行い、データをApache Hudi形式で永続化する

クラウド基盤としては Google Cloud を使用します。

構成図

各コンポーネントについてまとめます。

①通知処理部分

インフラ (コンポーネント名) 役割 主な責務
Cloud Run Gateway Notification 受信専用 高速応答、Pub/Sub 転送
Cloud Pub/Sub メッセージング基盤 非同期処理
Cloud Run Ingestor 永続化サービス NotificationのGCS への書き込み、メタデータ付与
Cloud Storage (GCS) Notification 永続化 Notification の蓄積

②ミニバッチ部分

インフラ (コンポーネント名) 役割 主な責務
Cloud Scheduler 定期実行 バッチ処理トリガー
Cloud Run Batch バッチ処理サービス GCS からのデータ取得、API 呼び出し
Dataproc + Apache Hudi データレイクハウス 変換、保存
Cloud Storage ウェアラブルデータ永続化 ウェアラブルデータの蓄積

このような構成です。
次の項でそれぞれもう少し詳しくご説明していきます。

アーキテクチャ設計の詳細

Notification 受信層(Cloud Run + Pub/Sub)

構成図

Fitbit Subscription API の制約として、5 秒以内に 204 No Content を返すサービスを実装する必要があります。
そのためここでは通知の受信と Pub/Sub への Publish のみに特化した軽量サービスを Cloud Run で構築します。

処理の流れは以下の通りです。

  1. Fitbit からの Webhook 通知を HTTP POST で受信
  2. 受信した JSON データを最小限の検証のみ実施
  3. タイムスタンプを付与して Pub/Sub メッセージとして即座に Publish
  4. Fitbit に対して 204 No Content レスポンスを返却(5 秒以内)

Notificationデータ 永続化層(Cloud Run + GCS)

構成図

この層では Pub/Sub から受信したメッセージを構造化して GCS に保存します。バッチ処理に適した形でデータを整理し、後続のバッチジョブが効率的に処理できるようにします。

処理の流れは以下の通りです。

  1. Pub/Sub からメッセージを受信
  2. メッセージのパースと検証
  3. バッチ ID と受信タイムスタンプを付与
  4. GCS に日付・時間ベースでパーティショニングして保存


GCS 保存例:

/notifications/
├── year=2025/
│   ├── month=08/
│   │   ├── day=01/
│   │   │   ├── hour=00/
│   │   │   │   └── 01K1GFYJHR7EKM5HN9PPD2AG9V.json
│   │   │   │   ├── 01K1GFYKHKJ3KHKVY0RDGNE2GZ.json
│   │   │   └── hour=01/
│   │   └── day=02/
│   └── month=09/


保存データ形式:

{
  "original_notifications": [...],
  "metadata": {
    "received_at": "2025-08-01T14:00:00Z",
    "batch_id": "01K1GFYJHR7EKM5HN9PPD2AG9V"
  }
}

バッチ処理層(Cloud Run + Dataproc + Apache Hudi)

構成図

この層では蓄積された通知情報をもとに、ミニバッチで API 呼び出しを行います。その結果を Apache Hudi 形式でデータレイクハウスとして保存します。

処理の流れは以下の通りです。

  1. 未処理通知の発見: GCS から新しい通知ファイルを検出
  2. データのグルーピング: ユーザー・日付・データタイプで最適化されたバッチを作成
  3. Fitbit API の呼び出し: レート制限を考慮した効率的なリクエスト実行
  4. Apache Hudi での書き込み: Upsert 操作によるデータレイクハウスへの永続化

データ管理のポイントとしては簡単に以下のようなものがあります。

  • パーティショニング戦略: 日付ベースでのデータ配置最適化
  • 主キー制約: ユーザーID, データタイプ, 日付の組み合わせによる一意性保証
  • 更新時刻管理: 最新データを保持

アーキテクチャ全体の説明は以上です。

考慮事項と今後の課題

ここまでで全体的な設計案を示しましたが、実際の運用に向けてはより詳細な検討が必要になる点もあります。

例えばバッチ処理層では、単なるAPI呼び出しを行うような書き方をしましたが、実際には蓄積された通知をグルーピングして API 呼び出しを行う際にレート制限を超えないようにするための工夫が必要です。

また、繰り返しになりますがFitbit は「データが更新された」という通知のみを送信し、実際のデータは別途 API で取得する仕組みです。しかし、他のウェアラブルデバイスでは、通知と同時に実際の測定データ(心拍数、歩数など)をストリームとして直接送信する場合があります。その場合、このアーキテクチャに沿って単にそのデータを保存するだけだと、障害などでデータを取り逃がした際にデータ損失を起こす可能性があります。別途API での再取得機能を実装したり、データの完全性を担保する仕組みが必要になるでしょう。

以上、参考になれば幸いです。

第22回日本うつ病学会にて、抑うつ気分の予測に関するポスター発表を行いました

こんにちは、データサイエンス部の深見です。

テックドクターのデータサイエンスチームでは、定期的に学会への参加や登壇を行なっています。今回は7/11-12に浜松町で開催された第22回日本うつ病学会総会にてポスター発表を行なってきましたので、その内容を紹介いたします。

私自身はちょうど一年前の日本睡眠学会での登壇以来、久しぶりの学会参加でした。またポスター発表となると本当に久しぶりで、最後は学生時代だったかもしれません。

私以外にもテックドクター社としては、これまでにも日本リウマチ学会や国際医薬経済・アウトカム研究会(ISPOR)など国内外の様々な学会に参加して研究の成果を報告しています。

www.technology-doctor.com

今回の発表内容は、いわゆるケーススタディという形式で、一人の被験者の長期に渡るウェアラブルバイスのデータを解析したものです。

ウェアラブルバイスを使うと、これまでは病院に来院したタイミングでしか分からなかった被験者の状態が、連続的にかつ細かな粒度で把握できます。このことが医療の質を上げるのに役立つと期待されています。

特にスマートフォンウェアラブルバイスから得られる生体データをもとにした指標「デジタルバイオマーカー」は、疾患の有無や病状の変化を客観的に評価することができる可能性を秘めており、その開発は国内でも注目が高まっています。

※デジタルバイオマーカーの詳細については弊社の下記サイトを参照してください。
デジタルバイオマーカー | TechDoctor

研究概要

演題:
双極性障害患者におけるウェアラブルバイスを用いたデジタルバイオマーカーの探索」

被験者:
2010年に診断を受けた双極性障害患者1名(40代・男性)

目的:
双極性障害の症状(特に抑うつ気分)に関連するデジタルバイオマーカーの発見。

対象データ

収集したデータは、ウェアラブルバイスFitbit Charge 6を使った生体データ(測定値)と、eMoodsというアプリを使った主観データ(自己申告の症状記録)です。それぞれどんな項目が対象かは、下記の表を見てください。

収集データの一覧表

期間としては2024年2月から11月まで9ヶ月間のデータを解析対象としました。こちらは現在も収集が続いており、今後も継続して解析を実施する予定です。

解析方法

主観データの中でも、特に「抑うつ気分」に着目、この指標と連動する生体データがないか調べました。

具体的には、

心拍数データをもとに算出した心拍変動指標
例)
・睡眠中の平均心拍数
・自律神経の活動を評価する複数の心拍変動

睡眠指標
例)
・ 睡眠時間
・各睡眠ステージ(覚醒/レム睡眠/浅い睡眠/深い睡眠)の時間や比率

などを使用しました。

これらの指標を1日ごとに算出、大きく変動した値(全期間の平均と標準偏差から、平均値±標準偏差以上、離れているもの)を異常値として抽出しました。

解析結果

副交感神経の活動を評価するRMSSD(Root Mean Square of Successive Differences)という心拍変動指標があります。一般的に、RMSSDの数値が大きくなると副交感神経が優位になる=リラックスした状態、逆に値が小さくなるとリラックスできていない状態と考えることができます。

今回の研究により、睡眠中のRMSSDの値に抑うつ気分との関連が見られることがわかりました。
実際のデータを見てみましょう。

睡眠中のRMSSDの値と抑うつ気分との関連グラフ

特に期間の後半、抑うつ気分の高まり(図中、青棒グラフが2点以上)とともにRMSSDのトレンド成分(図中、赤線グラフ)が下降していました。

また期間中にRMSSDが平均より大きく低下(1.5SD以上)した日が14日ありましたが、そのうち12日にその後1週間以内に抑うつ気分スコアの上昇が確認されました。

これらの結果は、睡眠中の副交感神経活動の低下を見ることでその後の抑うつ気分を予測できる可能性を示しています。もっと研究を進めることで、抑うつ気分を推定するバイオマーカーとして有用と判明するかもしれません。

今回の発表では一人の被験者に対して抑うつ気分との関連を調査しましたが、抑うつ気分以外の主観指標と生体データの関連の解析や、大人数を対象にした検証も実施しています。今後さらに内容を充実させた上で論文を投稿する予定です。

発表を通しての印象

ポスター発表では演題が60以上あり、演題番号が奇数のものが初日に、偶数のものが2日目にそれぞれ30分発表を行う形式でした。

私の発表は2日目の土曜日の14時からと比較的足を運びやすい時間帯だったこともあってか、時間中ひっきりなしに質問があり、5名程度の方に説明をすることができました。それ以外にも足を止めてくださる方、遠目に見ていただける方が何名かいらっしゃいましたのでそれなりに興味を持っていただけたのではないかと思います。
※会場内撮影禁止でしたので残念ながら写真はありません…

またポスター発表の他にも、父親の産後うつに関するセッションや、精神疾患の治療において薬を使った場合と精神療法を用いた場合の比較に関するセッションに参加し、最新の研究に触れることができました。こういった知見を日々の研究に活かしていきたいと思います。

まとめ

いかがでしたでしょうか。テックドクターは、研究成果を今回のような学会発表や論文投稿という形で発表することで、医学の発展への貢献を目指しています。また対外的な発表以外にも特許という形で成果を残すこともあります。今後も学会発表や論文投稿した際には随時発信していきたいと思います。

似顔絵
書いた人:深見

クライアントとサーバーで、APIの型・バリデーションルールを一元化する

こんにちは。株式会社 TechDoctor でソフトウェアエンジニアをしている大瀧です。

突然ですが、アプリケーション開発で、クライアントとサーバーの API の型やバリデーションルールが食い違い、予期せぬバグに繋がった経験はないでしょうか。

このような課題の解決策として、本記事では、OpenAPI スキーマをもとに、API の型とバリデーションルールを一元管理する方法を紹介します。

ここで紹介するアプローチは、 OpenAPI スキーマを作成できる限り特定の言語やフレームワークに依存しませんが、今回は具体例として FastAPI、Pydantic、Zod、openapi-zod-client を使った方法を解説します。

なお本記事のサンプルコードは、以下のリポジトリで確認できます。(Kiro と Claude Code が作ってくれました)
GitHub - shutootaki/sync-schema-demo

1. 背景と課題

課題 ①:API の型の二重管理

API のインターフェースの型が、バックエンドとフロントエンドでそれぞれ定義されていると、二重管理になってしまいます。

この状態では、仕様変更のたびに両方のコードを手動で同期させる必要が生まれます。

その際に片方で修正漏れが起きると、データの不整合や実行時エラーに直結します。結果として、ランタイムエラーが発生したり、「このフィールドは null 許容でしたっけ?」といった本来不要なコミュニケーションが頻発してしまいます。

課題 ②:バリデーションロジックの分散

優れた UX のためにはクライアント側での事前バリデーションが必要です。そしてデータの整合性とセキュリティのためにはサーバー側でのバリデーションも不可欠です。

しかし、これらのバリデーションルールをそれぞれ別のリポジトリで管理していると、いつの間にか仕様が乖離しがちです。

結果として、課題 ① と同様にエラーや不要なコミュニケーションコストが発生してしまいます。

2. 解決アプローチ

この課題を解決するために、弊社では以下のアプローチを採用しました。

バックエンドを信頼できる唯一の情報源(SSoT)にする

バックエンド(FastAPI + Pydantic)を、型とバリデーションルールにおける信頼できる唯一の情報源と位置づけます。

そのために、コーディング時はPydantic でリクエスト/レスポンスの型とバリデーションルールを定義します。
あとは自動的に同期が行われる仕組みにします。

コード生成による型の自動同期

同期のしくみはこうです。

概念図

Pydanticでのスキーマ定義をもとに、FastAPI が OpenAPI スキーマを自動生成します。
フロントエンド側では、openapi-zod-client を使って、OpenAPI ドキュメントをもとにZod スキーマを自動生成します。

この仕組みにより、バックエンドでスキーマを変更してもコマンド 1 つでフロントエンドの型を同期できるため、APIの型とフロントエンド側の実装との不整合を簡単に検知でき、バリデーションロジックの二重実装も不要になります。

3. 具体的な実装方法

基本的なユーザー作成 API を例に、具体的な実装方法を解説します。

① Pydantic モデルの定義と FastAPI によるドキュメント生成

バックエンドでは、Pydantic を使いリクエストとレスポンスのスキーマを定義します。

基本的なスキーマ定義
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from enum import Enum

class UserRole(str, Enum):
    USER = "user"
    ADMIN = "admin"

class UserCreateRequest(BaseModel):
    name: str = Field(..., min_length=1, max_length=100)
    email: EmailStr
    age: int = Field(..., ge=18, le=120)
    role: UserRole = UserRole.USER

class UserResponse(BaseModel):
    id: int
    name: str
    email: str
    age: int
    role: UserRole
FastAPI ルーターでのスキーマの使用

定義した Pydantic スキーマを、FastAPI のエンドポイントで利用します。

from fastapi import APIRouter, HTTPException
from app.models.user import UserCreateRequest, UserResponse
import random

router = APIRouter(tags=["Users"])

users_db: dict = {}

@router.post("/users", response_model=UserResponse)
async def create_user(user_data: UserCreateRequest) -> UserResponse:
    new_user = {
        "id": generate_user_id(),
        "name": user_data.name,
        "email": user_data.email,
        "age": user_data.age,
        "role": user_data.role,
    }

    users_db[new_user["id"]] = new_user
    return UserResponse.model_validate(new_user)
FastAPI アプリケーションの設定

main.py で、OpenAPI ドキュメント生成の設定を行います。

from fastapi import FastAPI
from app.routers import users

app = FastAPI(
    title="User API",
    version="1.0.0",
    description="シンプルなユーザー管理API"
)

app.include_router(users.router, prefix="/api/v1")
生成される OpenAPI スキーマ

上記の Pydantic モデルと FastAPI の設定から、次のような OpenAPI スキーマが自動生成されます。(一部抜粋)

{
  "openapi": "3.1.0",
  "info": {
    "title": "User API",
    "version": "1.0.0"
  },
  "components": {
    "schemas": {
      "UserCreateRequest": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1,
            "title": "Name"
          },
          "email": {
            "type": "string",
            "format": "email",
            "title": "Email"
          },
          "age": {
            "type": "integer",
            "maximum": 120,
            "minimum": 18,
            "title": "Age"
          },
          "role": {
            "$ref": "#/components/schemas/UserRole",
            "default": "user"
          }
        },
        "title": "UserCreateRequest",
        "description": "ユーザー作成リクエストのスキーマ"
      },
      "UserRole": {
        "type": "string",
        "enum": ["user", "admin"],
        "title": "UserRole"
      }
    }
  }
}

画面キャプチャ

このスキーマには、minLength=1ge=18 といったバリデーションルール、UserRoleEnum 定義、必須フィールドの指定、整数・文字列・メール形式といった詳細な型情報が含まれています。

openapi-zod-clientによるスキーマファイルの生成

フロントエンド側では、FastAPI が生成した OpenAPI スキーマをもとに、openapi-zod-clientを使って Zod スキーマを自動生成します。

openapi-zod-clientのインストールと設定

まず、フロントエンドプロジェクトに必要なパッケージをインストールします。

pnpm install -D openapi-zod-client

次に、プロジェクトのルートに設定ファイルを作成します。

// schema-format.hbs

// This file is automatically generated. Do not change it manually.
import { z } from "zod";

{{#each schemas}}
export const {{@key}} = {{{this}}};
export type {{@key}} = z.infer<typeof {{@key}}>;
{{/each}}

export const schemas = {
{{#each schemas}}
	{{@key}},
{{/each}}
};

上記はシンプルな設定例です。詳細は公式ドキュメントを参照してください。
GitHub - astahmer/openapi-zod-client: Generate a zodios (typescript http client with zod validation) from an OpenAPI spec (json/yaml)


package.jsonへのスクリプト追加

開発ワークフローを効率化するため、package.jsonスクリプトを追加します。

{
  "scripts": {
    "codegen": "openapi-zod-client -o ./src/lib/api/schema/api_schemas.ts -t ./schema-format.hbs ${OPEN_API_JSON:-<http://localhost:8000/openapi.json>}"
  }
}

バックエンドサーバーが起動している状態で以下のコマンドを実行すると、Zod スキーマが自動で生成されます。

pnpm run codegen

実際には以下のような Zod スキーマが自動で生成されます。(一部抜粋)
OpenAPI スキーマをもとに UserCreateRequest の型とバリデーションルールが、Zod のスキーマに変換されています。

// src/lib/api/schema/api_schemas.ts
// This file is automatically generated. Do not change it manually.
import { z } from "zod";

export const UserRole = z.enum(["user", "admin"]);
export type UserRole = z.infer<typeof UserRole>;
export const UserCreateRequest = z
  .object({
    name: z.string().min(1).max(100),
    email: z.string().email(),
    age: z.number().int().gte(18).lte(120),
    role: UserRole.optional(),
  })
  .passthrough();
export type UserCreateRequest = z.infer<typeof UserCreateRequest>;
React Hook Form との連携

生成された Zod スキーマは、React Hook Form のようなフォームの状態管理をするライブラリと簡単に連携できます。

以下のように、src/lib/api/schema/api_schemas.ts に出力された Zod スキーマを import して、useForm と zodResolver にセットするだけで、先程 Pydantic で定義した型とバリデーションルールを再利用することができます。

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { UserCreateRequest } from "@/lib/api/schema/api_schemas";

export const UserForm = ({ onSubmit, isLoading }) => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<UserCreateRequest>({
    resolver: zodResolver(UserCreateRequest),
    defaultValues: { role: "user" },
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("name")} placeholder="名前" />
      {errors.name && <p>{errors.name.message}</p>}

      <input {...register("email")} type="email" placeholder="メール" />
      {errors.email && <p>{errors.email.message}</p>}

      <input {...register("age", { valueAsNumber: true })} type="number" />
      {errors.age && <p>{errors.age.message}</p>}

      <button type="submit" disabled={isLoading}>
        {isLoading ? "作成中..." : "ユーザーを作成"}
      </button>
    </form>
  );
};

実際にサンプルコードのフロントエンドサーバーを起動して、フォームに不正な値を設定すると、以下のように期待通りバリデーションを実行してくれていることがわかります。

画面キャプチャ

4. まとめ

本記事では、FastAPI、Pydantic、Zod、openapi-zod-client を組み合わせ、API の型とバリデーションルールを一元管理する方法を紹介しました。弊社では、この取り組みによって以下のメリットを得ることができました。

  • 型の不一致によるバグを予防
    • バックエンドで定義した OpenAPI スキーマからフロントエンドの型を生成するため、型の不一致によるバグを大幅に減少させることができました。
  • バリデーションロジックを再利用
    • Zod へ変換されたスキーマを React Hook Form などに直接適用できるため、同一のルールを重複して実装する必要がなくなりました。
  • 変更への追従が容易
    • バックエンドのスキーマモデルを修正した後に、コード生成コマンドを実行するだけで API の型定義・バリデーションロジックが更新されるので、開発者の負担を減らすことができました。

API の型・バリデーションロジックの管理に課題感を持っている方がいましたら、ぜひ導入を検討してみてください!



書いた人:大瀧

AgenticWorkflow構築のためのライブラリ比較 〜LangChain・GoogleADK・PydanticAIを使ってみた〜

こんにちは、テックドクターCTOの佐藤です。

最近、生成AIや大規模言語モデル(LLM)の発展により、複雑なタスクを自律的にこなすAIが注目されています。今回紹介するAgenticWorkflowは、こうしたAIの力を最大限に引き出すためのしくみです。

このエントリではAgenticWorkflow自体の紹介にくわえ、その実装のためのライブラリ3つを比較した結果をご紹介します。

AgenticWorkflowとは何か、なぜ注目されているのか

AgenticWorkflowとは、AIやプログラムが「自分で考えて動く」ようにする仕組み、あるいはそのための考え方のことです。AIがある目的を達成するために、都度人間の指示を受けるのではなく、自分で手順を考えたり、必要な作業を順番に進めたりできるようにします。

これは単なる自動化とは異なります。これまで行われていた自動化では、あらかじめ決められた流れに沿ってAIやプログラムが動くことが多かったと思います。一方AgenticWorkflowではAIが自分で状況に応じて柔軟に判断し、最適な行動を選択するという違いがあります。

概念説明図
weaviate.io より引用、日本語訳)

最近では業務の自動化や効率化、新しいサービスの開発など、さまざまな場面でAgenticWorkflowの活用が広がっています。それにともなってツールやSDKもさまざまなものが登場しています。

比較対象SDKの紹介

弊社ではバックエンドをPython + FastAPIで構築していることが多いので、その構成に組み込み可能な3つのライブラリ、LangChain、 GoogleADK、 PydanticAIを比較しました。

まずはそれぞれの概要を紹介します。

LangChain

大規模言語モデル(LLM)を活用したアプリケーション開発のためのオープンソースフレームワークです。LLMアプリの開発から運用までの工程を簡素化してくれます。各種コンポーネントが豊富で、外部サービスとの連携機能も充実しています。

対話エージェントや質問応答システム、RAG(Retrieval-Augmented Generation)などの構築に幅広く使われていて、LLM・ベクトルDB・ツール類を組み合わせたアプリを素早く開発できます。

GoogleADK

複雑なタスクやワークフローをこなす対話型/非対話型のエージェントを構築・管理・評価・デプロイするためのフレームワークです。主にGoogle CloudのVertex AI向けに提供されていて、GeminiなどのLLMを活用したマルチエージェントシステムや、企業向け対話ボットの開発に使用されます。

開発からデプロイまでを統合的にサポートするため、大規模な対話エージェントをスケーラブルに構築できます。

PydanticAI

Python製のエージェントフレームワークです。バリデーションライブラリとしておなじみのPydanticのチームによって開発されました。ジェネレーティブAIアプリを型安全かつ効率的に構築し、プロダクション品質に耐えるものにすることを目指しているようです。

OpenAIやAnthropic、Geminiなど複数のLLMに対応していて、Pydanticによる出力検証・構造化により一貫性のある応答を得られます。FastAPIがウェブ開発を革新したように、LLMを活用したアプリ開発を開発者フレンドリーにすることがコンセプトだそうです。

サンプルコード

比較に使用したコードはGitHubに置いてあります。

github.com

実行後、最初にライブラリを選択したのち、コマンドラインで都市名をインプットすると

  • APIで都市名を緯度経度に変換
  • APIで緯度経度から天気を取得
  • 天気を自然文にして、コマンドラインに出力

という流れで、AIがツールを選択して動作します。LLMはGeminiを利用します。

基本的には3つのライブラリ全て同じ流れです。APIを呼び出すためのPythonの関数をツールとして渡して、そのツールをLLMの判断の元に利用してレスポンスを返します。

※ちなみにこのサンプルコードは、Github IssueとPRを紐付けて、Claude Codeでそれらを操作して書いてもらいました。

使用感

実際使ってみての使用感の違いです。

LangChain

新規のプロダクトなどに手軽にAgenticWorkflowを導入したい場合、LangChainは向いていると思いました。多彩な外部連携やLangGraphによる状態管理、LangSmithによる入出力ログの管理など、必要な機能を手軽に試すことができます。

実は2024年頃まではバージョンアップごとに互換性を壊す変更が多く、LangChainを本番で安定運用するのは難しいと感じていました。ただ最近は比較的安定してきていると感じます。

注意点としてはメモリ以外の永続セッション管理機能が限定的なので、必要に応じて開発者が外部ストレージ等に履歴を保存・復元する機能を実装する必要があります。

GoogleADK

ADKは既にGCPやVertextAIを使っている人に最適だと思います。外部API統合として、GCPのリソースやGoogle検索を使うことができたり、マルチエージェントやエージェント同士の連携、非同期実行やセッション管理など様々な機能が本番運用を考えて用意されています。

ただ入力や出力のロギングは、現時点ではCloudLoggingに吐き出した後BigQueryに格納するなど自分で行う必要があり、その点に関しては他のツールの方がお手軽にできると思いました。

PydanticAI

既にPydanticをプロダクトに組み込んでいる場合はPydanticAIが最適です。LLMの入出力をPydanticのモデルで書いたり、モデルのdescriptionフィールドを参照して入出力値の説明をそのまま入れたりと、手軽に導入することができます。

またLogfireと連携して、比較的簡単にロギングをする事ができます。ただし、LangChainと同様にメモリ以外の永続セッション管理機能は限定的であり、必要に応じて開発者が外部ストレージ等を用いて履歴を保存・復元する実装する必要があります。

今後の検討

今回は簡単なサンプルのみをご紹介しましたが、今後は実際にLLMを組み込んだ製品を作るにあたり

  • 会話履歴・メモリ
  • LangSmithやLogfireを使った入出力ロギング
  • ステートグラフやサブタスクなどの導入

などについても書けたらと思います。

参考になれば幸いです。

似顔絵
書いた人:佐藤

月経周期による女性の体調不良ってどんなもの? 〜日々のアンケートからわかったこと〜

こんにちは、データサイエンスチームの藤本と申します。

この記事では、女性の月経周期にともなう不調に焦点を当てます。
この時期は調子が良い、この時期は調子が悪いなど、一般に言われる通説がありますが、実際のところはどうなのでしょう。
社内での取り組みを通して可視化できたデータについてご紹介していきます。

女性の月経周期についての通説

女性の正常な月経周期は、一般的に25~38日、そのうち月経期の期間は3~7日間とされています。ただしこれには、個人差があること、ホルモンバランスやストレスによって変化しがちであることが知られています。

月経周期のサイクルは、月経期卵胞期黄体期月経期...という順番で繰り返されます。

このうち、黄体期は精神的・身体的に不調が生じやすい期間です。プロゲステロンというホルモンが増加する影響でむくみが起こったり、情緒不安定になったりします。

一方で卵胞期は、調子が良くなる時期です。卵胞ホルモン(エストロゲン)の分泌が増えることにより、皮膚の活性化が促されて肌艶がよくなったり、自律神経が安定して前向きな気持ちになりやすい期間です。

以上は、女性の月経周期について一般的に言われている通説です。ですが実際にデータを取って解析してみることで、よりくわしく期間ごとの体調の変化をとらえることができます。その一例として、テックドクターで行ったアンケート解析の結果をご紹介します。

体調不良の女性

「Ladynamic」プロジェクトで女性社員にアンケートを実施

テックドクターには、女性社員のみで構成された「Ladynamic」というプロジェクトがあり、女性の視点に立った課題提起とデータ解析を目指しています。

※プロジェクトについてくわしくは、同じデータサイエンスチームの瀬川が書いた記事をご覧ください。
女性にとって、自分の体調が「わかる」未来を目指して〜Ladynamicプロジェクトのご紹介〜 - TechDoctor開発者Blog

Ladynamicではウェアラブルデータ(※)を利用して女性特有のデジタルバイオマーカーの開発に取り組んでいますが、そのプロセスの一環として、プロジェクトに参画している女性社員に日々の体調に関するアンケートに答えてもらっています。

ウェアラブルデータ……スマートウォッチを代表とするウェアラブル端末で測定された生体情報等のデータ

「Ladynamic」アンケートの質問項目

参加者には、下記のようなアンケートが毎日配信されます。質問項目には、日々の体調や気分に関する質問(質問1)と、月経に関する質問(質問2)があります。

質問1(毎日答える質問)

質問内容 選択肢
業務・家事・学業・育児といったタスクに影響がありましたか? 1(全くない)〜4(非常にある)の4段階
余暇時間にリラックスできましたか? 1(全くできなかった)〜4(非常にできた)の4段階
日中の眠気について 1(眠気はまったくない)〜4(非常にある)の4段階
今日の心の調子はいかがでしたか? いつもどおり/いつもと違う気分を感じた(後者の場合は、具体的な症状を選択)
今日の体の調子はいかがでしたか? いつもどおり/いつもと違う異変を感じた(後者の場合は、具体的な症状を選択)
現在、月経期間に該当する はい/いいえ(はいの場合は、質問2に進む)

質問2(月経期に該当する場合のみ表示される質問)

質問内容 選択肢
月経開始から何日目ですか? 1日目〜7日目までの7択から選択
本日の生理痛のつらさについて 1(まったく問題ない) 〜4(かなりつらい)の4段階
本日の経血量について 1(少ない) 〜3(多い)の3段階で評価
月経がはじまる前、体調の変化やつらさを感じた 1(まったく感じなかった)〜4(非常に感じた)の4段階
前回の月経が終了した後、体調の変化やつらさを感じた 1(まったく感じなかった)〜4(非常に感じた)の4段階

スマホでアンケートに回答する女性

参加者数と期間、月経周期の計算方法

アンケートの参加者数は12名、期間は2024年1月1日〜2025年4月24日の約1年5カ月です。

冒頭で触れたように、女性の月経周期には大きく分けて「月経期」「黄体期」「卵胞期」の3つの期間があります。

アンケートの「月経開始から何日目ですか?」の回答をもとに月経期を特定し、黄体期を月経開始前の7日間、卵胞期を月経終了後の5日間としました。

解析結果

1.各期間ごとのからだの不調のようす

まず、期間ごとのからだの不調に関する項目についてその発生数を集計してみました。

グラフ画像
図1: 各期間ごとの全参加者のからだの不調の発生回数

すべての参加者の合計のグラフです。不調の種類ごとに、月経期を赤色、黄体期を黄色、卵胞期を緑色で示しています。発生回数が多いほどグラフが長くなります。
その結果、月経期では「腹痛」が突出して多く、黄体期では「むくみ」や「乳房の腫れ」が多かったです。また、卵胞期では他の期間に比べてからだの不調が少なくなっていました。


グラフ画像

グラフ画像
図2: 各期間ごとのからだの不調の発生回数(参加者ごとの例)

個人ごとのデータも見てみたところ、全体的な傾向と違う人もいました。例えば、参加者4のように月経期にほとんど症状がなく、黄体期に症状が出やすい人や、逆に参加者6のように黄体期にはほとんど症状が出ない人もいました。

2.各期間ごとのこころの不調のようす

次に、期間ごとのこころの不調の項目についても、その発生数を集計してみました。

グラフ画像
図3: 各期間ごとの全参加者のこころの不調の発生回数

すべての参加者の合計のグラフです。月経期では不安・憂鬱などの負の感情や、集中力低下が多く見られました。また、黄体期には、他の時期には見られない「怒り」などの攻撃的な感情が現れたり、「イライラ」の回答数が多くなっていました。
また、卵胞期では他の期間に比べて精神的不調が少なかったです。

グラフ画像

グラフ画像
図4: 各期間ごとのこころの不調の発生回数(参加者ごとの例)

個人ごとのデータも見てみたところ、参加者3のようにまんべんなく症状が出る人もいれば、参加者4のように特定の症状のみが出る人もいました。

3.月経期について

次に、月経期のアンケートの解析結果です。

3-1.生理痛のつらさと経血量について

月経開始からの経過日数でどのように体調が変化していったか見てみましょう。

グラフ画像
図5: 月経開始からの経過日数ごとの、生理痛のつらさの平均スコア(1~4の4段階で評価)

まず、月経開始からの日数ごとに、「本日の生理痛のつらさについて」の4段階の回答の平均をとりました。(グラフの下の数字が経過日数)
その結果、月経開始から2日目で最もつらさを感じている人が多く、月経開始から4日目以降は軽快していく傾向がありました。

グラフ画像
図6: 月経開始からの経過日数ごとの経血量の平均スコア

経血量についても同じように、「本日の経血量について」の3段階の回答の平均をとりました。
その結果、経血量の平均値も生理痛のつらさと同じように月経開始から2日目に最も高くなり、月経開始から4日目以降は軽快する傾向にありました。

3-2.黄体期・卵胞期の不調について

次に、黄体期卵胞期の期間の体調にどのような個人差があったか見てみましょう。

グラフ画像
図7: 参加者ごとの黄体期の体調のつらさの平均スコア(1~4の4段階で評価)

黄体期の体調の変化やつらさに関する質問「月経がはじまる前、体調の変化やつらさを感じた」の4段階の回答の平均をとりました。グラフの下の数字は参加者のIDです。
その結果、黄体期に不調を感じている人が多い一方で、参加者1,9,15のようにほとんど体調のつらさを感じていない人もいました。

グラフ画像
図8: 参加者ごとの卵胞期の体調のつらさの平均スコア(1~4の4段階で評価)

卵胞期の体調の変化やつらさに関する質問「前回の月経が終了したあと、体調の変化やつらさを感じた」の4段階の回答の平均をとりました。
12人中7人は平均スコアが1と、まったく体調の辛さを感じていない人が多いです。一方で、卵胞期においても体調のつらさを感じている人もいました。


まとめ

弊社女性社員のアンケート解析結果でも、月経開始から2日目に生理痛のつらさ・経血量ともに最も高かったこと、卵胞期は他の時期に比べて体調が良い人が多かったことなど、世間的な通説と一致する部分が多い結果となりました。

しかし、全体傾向とはまた違った挙動を示した参加者もおり、全員が同様のつらさを感じているわけではないこともわかりました。全体の解析だけでなく、より個人ごとの解析を進めていく必要があると感じています。

今後の展望

ここまでの解析結果を弊社の女性社員に共有したところ、「人ごとの差を見られて興味深かった」といった感想にくわえ、「春は花粉症や気圧の変化、夏は冷房装置による冷えで調子が悪い、など季節ごとに体調に差がある」「月経期は寝付きが悪くなる」などの意見も出ました。

今後はこれらのフィードバックを元に、Fitbitデバイスのデータを掛け合わせて、生理痛が重い人と軽い人で心拍や睡眠の様子にどのような差が出てくるかなど、より詳しく解析を進めていく予定です。

このように個人ごとのつらさの可視化を行うことで、自分の体調不良の原因の特定や体調不良になりやすい時期の予測が可能になります。
「Ladynamic」プロジェクトでの解析を通して、周囲にそれぞれのつらさを説明しやすい・理解してもらいやすい社会の実現を目指していきたいです。


似顔絵
書いた人:藤本