<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hajime</title>
    <description>The latest articles on DEV Community by Hajime (@marketer2234).</description>
    <link>https://dev.to/marketer2234</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4099962%2Fd827964b-50f0-4697-ac1a-956fe96a91c5.png</url>
      <title>DEV Community: Hajime</title>
      <link>https://dev.to/marketer2234</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marketer2234"/>
    <language>en</language>
    <item>
      <title>間借り出店のプランを損益分岐点で比べる:会場費と売上の取り分を式にする</title>
      <dc:creator>Hajime</dc:creator>
      <pubDate>Tue, 29 Sep 2026 04:45:35 +0000</pubDate>
      <link>https://dev.to/marketer2234/jian-jie-richu-dian-nopuranwosun-yi-fen-qi-dian-debi-beruhui-chang-fei-tomai-shang-noqu-rifen-woshi-nisuru-2250</link>
      <guid>https://dev.to/marketer2234/jian-jie-richu-dian-nopuranwosun-yi-fen-qi-dian-debi-beruhui-chang-fei-tomai-shang-noqu-rifen-woshi-nisuru-2250</guid>
      <description>&lt;p&gt;飲食の間借りや一日店長のプランは、「会場費」と「売上のうち自分に入る割合」の組み合わせで性格が決まります。料金表を眺めているだけだと比較しにくいので、式に落として、数十行のコードで比べられるようにしてみました。&lt;/p&gt;

&lt;p&gt;題材にしたのは、東京・大山のスタンドが公開している貸し切りプラン(一日店長)の料金です(2026年9月時点。税区分は問い合わせ時に案内とのことなので、以下の数字は税の扱い未確認のまま使っています)。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwhmwxe67316in1uh6wz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwhmwxe67316in1uh6wz.png" alt="コミュニティスタンド咲" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://saki-stand.com/" rel="noopener noreferrer"&gt;料金体系が公開されている間借り出店のスタンド コミュニティスタンド咲&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  モデル化する
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fswbzmifv12yhvutyu97n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fswbzmifv12yhvutyu97n.png" alt="貸し切りプランの料金表" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;貸し切りプランは3スタイルあり、ドリンクの売上の扱いが違います。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;スタイル&lt;/th&gt;
&lt;th&gt;平日昼&lt;/th&gt;
&lt;th&gt;平日夜&lt;/th&gt;
&lt;th&gt;土日祝・金夜 昼 / 夜&lt;/th&gt;
&lt;th&gt;ドリンク売上の取り分&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;staffed(スタッフ付き)&lt;/td&gt;
&lt;td&gt;24,000&lt;/td&gt;
&lt;td&gt;29,000&lt;/td&gt;
&lt;td&gt;37,000 / 47,000&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unstaffed(スタッフなし)&lt;/td&gt;
&lt;td&gt;15,000&lt;/td&gt;
&lt;td&gt;18,000&lt;/td&gt;
&lt;td&gt;23,000 / 28,000&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;self(ドリンク持ち込み)&lt;/td&gt;
&lt;td&gt;18,000&lt;/td&gt;
&lt;td&gt;22,000&lt;/td&gt;
&lt;td&gt;27,000 / 35,000&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;1日の手残りを次のように置きます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;profit = food * (1 - foodCostRate)
       + drink * share
       - (style === 'self' ? drink * drinkCostRate : 0)
       - fee
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;food&lt;/code&gt;:自分の料理・商品の売上&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;drink&lt;/code&gt;:ドリンクの売上&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;share&lt;/code&gt;:ドリンク売上の取り分(0.5 か 1.0)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;drinkCostRate&lt;/code&gt;:持ち込みドリンクの原価率(self のときだけ自分で負担)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fee&lt;/code&gt;:会場費&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;お店のドリンクを使うスタイルでは、ドリンクの仕入れは店側なので原価を引かない、という前提にしています。ここは実際の契約内容で確認すべき仮定です。&lt;/p&gt;

&lt;h2&gt;
  
  
  コード
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FEES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;staffed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;night&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;29000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;weekend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;37000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;night&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;47000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;share&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;unstaffed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;night&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;weekend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;23000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;night&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;share&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;night&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;weekend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;27000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;night&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;share&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// 金曜の夜は土日祝料金&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dayType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;weekday&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;weekday&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sun&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;weekday&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;holiday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weekday&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fri&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;night&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weekend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weekday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;profit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;food&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;drink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;foodCostRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;drinkCostRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FEES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;dayType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;)][&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drinkIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;drink&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;share&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;self&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;drink&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;drinkCostRate&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;food&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;foodCostRate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;drinkIn&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;fee&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FEES&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;profit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;night&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;food&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;drink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20000&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;原価率の 0.35 と 0.3 は例として置いた値で、どこかの実績値ではありません。自分のメニューの数字に差し替えてください。&lt;/p&gt;

&lt;h2&gt;
  
  
  損益分岐のドリンク売上を解く
&lt;/h2&gt;

&lt;p&gt;料理の売上を固定したとき、ドリンクをいくら売れば手残りが0になるかは、式を &lt;code&gt;drink&lt;/code&gt; について解けば出ます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;breakEvenDrink&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;food&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;foodCostRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;drinkCostRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FEES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;dayType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;)][&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;perYen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;share&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;self&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;drinkCostRate&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ドリンク1円あたりの手残り&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;fee&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;food&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;foodCostRate&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;perYen&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;perYen&lt;/code&gt; が小さいほど、同じ会場費を埋めるのに必要なドリンク売上は増えます。取り分50%のスタイルでは1円売って0.5円、持ち込みで原価率30%なら0.7円が手元に残る計算なので、ドリンク中心の営業なら持ち込みのほうが分岐点は下がりやすい、ということが式からも読めます。&lt;/p&gt;

&lt;h2&gt;
  
  
  表計算でやる場合
&lt;/h2&gt;

&lt;p&gt;スプレッドシートなら、スタイルを行、曜日と時間帯を列にした会場費の表を作り、&lt;code&gt;VLOOKUP&lt;/code&gt; などで引いてから同じ式を当てるだけです。金曜の夜を週末扱いにする判定だけは忘れやすいので、別の列で明示しておくと事故が減ります。&lt;/p&gt;

&lt;h2&gt;
  
  
  前提として押さえておくこと
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;スタッフなしのスタイルは審査制で、スタッフ付きでの利用実績が必要(初回は選べない)&lt;/li&gt;
&lt;li&gt;料理など自分の商品の売上がどう扱われるかは、ページからは読み切れないので契約時に確認&lt;/li&gt;
&lt;li&gt;掲載の料金は予告なしに改定されることがある&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;条件を変えて何パターンか回してみると、「どのスタイルなら何を売ればいいか」がはっきりします。表を眺めて悩むより、数字を入れて比べるほうが早い、というのが今回の結論です。&lt;/p&gt;

&lt;p&gt;本ページはプロモーションが含まれています&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>javascript</category>
      <category>japan</category>
    </item>
    <item>
      <title>窓のない部屋で撮る:小劇場を配信・収録スタジオとして使うときの確認項目</title>
      <dc:creator>Hajime</dc:creator>
      <pubDate>Sun, 27 Sep 2026 13:40:14 +0000</pubDate>
      <link>https://dev.to/marketer2234/chuang-nonaibu-wu-decuo-ruxiao-ju-chang-wopei-xin-shou-lu-sutaziotositeshi-utokinoque-ren-xiang-mu-21l8</link>
      <guid>https://dev.to/marketer2234/chuang-nonaibu-wu-decuo-ruxiao-ju-chang-wopei-xin-shou-lu-sutaziotositeshi-utokinoque-ren-xiang-mu-21l8</guid>
      <description>&lt;p&gt;動画の収録やライブ配信の場所を決めるとき、機材より先に効いてくるのは「部屋の条件」です。外光が入る部屋では時刻によって色温度が変わり、カットをつないだときに違和感が出ます。生活音が入る部屋では、あとからノイズ除去をかけても声の質感が削れます。&lt;/p&gt;

&lt;p&gt;撮影スタジオを借りれば解決しますが、照明や音響を追加していくと予算が膨らみがちです。そこで候補に入れたいのが、もともと人前で見せるために作られた小劇場です。ここでは、東京都板橋区にある客席30席の貸し劇場の公開情報(2026年9月時点)を例に、配信・収録の場所として評価するときの観点を整理します。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpalx7vz8pu1d2sokq4xy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpalx7vz8pu1d2sokq4xy.png" alt="板橋シアター咲" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://theater-saki.com/" rel="noopener noreferrer"&gt;配信・動画撮影に使える貸し劇場 板橋シアター咲&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. 光:ゼロから組めるか
&lt;/h2&gt;

&lt;p&gt;収録で一番ありがたいのは「暗くできる」ことです。真っ暗にできる部屋なら、画面に映る光はすべて自分で置いたものになり、再現性が上がります。複数日に分けて撮っても、同じ設定を当て直せば同じ画になります。&lt;/p&gt;

&lt;p&gt;例に挙げた劇場は地下1階で窓がなく、昼間でも完全に暗転できると明記されています。備え付けの照明はLEDのパーライトが6灯と、DMXで制御する調光用コントローラーです。&lt;/p&gt;

&lt;p&gt;確認したい点:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;備え付け照明の色と明るさで、顔に当てる主光が足りるか(足りなければ持ち込みのライトをどこに置くか)&lt;/li&gt;
&lt;li&gt;DMXコントローラーでシーンを切り替えられるなら、配信中の転換に使えるか&lt;/li&gt;
&lt;li&gt;ステージ背面が白いホワイトボードなので、反射がカメラにどう写るか&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;背面はマグネットが使える一方、テープや粘着物は使えません。白背面へのマスキングテープも、許可がなければ不可という規約です。背景紙やロゴを貼りたい場合は、マグネットで留める前提で準備します。&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 音:どこから入れて、どこへ出すか
&lt;/h2&gt;

&lt;p&gt;音響はパワードスピーカー、ワイヤレスマイク、アナログマイク、CDプレーヤーが備え付けで、スピーカーにはPCやスマホからBluetoothで直接音を送れます。&lt;/p&gt;

&lt;p&gt;配信で考えたいのは、会場に流す音と配信に乗せる音の経路を分けるかどうかです。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;会場スピーカーへはBluetoothでBGMや効果音を送る&lt;/li&gt;
&lt;li&gt;配信には、カメラ側・オーディオインターフェース側で拾ったマイク音を乗せる&lt;/li&gt;
&lt;li&gt;会場スピーカーの音をマイクが拾う量(回り込み)を、事前にテストする&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;また規約上、監視カメラ・スピーカー・ミキサーの電源は切ってはいけないことになっています(切ると別途3,000円)。自前の音響系を組むときも、既存機材の電源を落とさない構成にする必要があります。&lt;/p&gt;

&lt;h2&gt;
  
  
  3. 回線:配信に耐えるか
&lt;/h2&gt;

&lt;p&gt;高速Wi-Fiが用意されています。ただし実効速度は時間帯や接続台数で変わるので、配信本番の前に同じ時間帯で上り速度を測っておくのが安全です。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;配信ソフトで要求するビットレートに対して、上りに余裕があるか&lt;/li&gt;
&lt;li&gt;配信用PCとスマホ・タブレット(コメント確認用)を同時につないだときに落ちないか&lt;/li&gt;
&lt;li&gt;回線が細い場合のフォールバック(モバイル回線)を持っていくか&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. 映像の出し入れ
&lt;/h2&gt;

&lt;p&gt;プロジェクター(4K対応のストリーミング端末が接続済み)とBlu-rayプレーヤーがあります。スライドや映像を投影しながら話す収録、上映会の同時配信などに使えます。投影面と照明の干渉(照明が投影面に当たって白く飛ぶ)は、下見で確かめたいところです。&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 広さと動線
&lt;/h2&gt;

&lt;p&gt;床面積は54.04平方メートル、客席は最大30席、ステージは平台3台の構成です。三脚をどこに立てるか、ケーブルをどこに這わせるかは、間取り図を見て先に描いておくと当日が速くなります。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffkzvpsy9dxocieo893n2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffkzvpsy9dxocieo893n2.png" alt="板橋シアター咲" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;控室・更衣室と着替え用のパーテーションがあり、劇場のそばには時間貸しの楽屋もあります。出演者の待機場所と撮影エリアを分けられるので、オーディションや収録の順番待ちにも向いています。&lt;/p&gt;

&lt;h2&gt;
  
  
  6. 音量と近隣
&lt;/h2&gt;

&lt;p&gt;商店街の建物の地下にある劇場で、規約では出入口の扉とカーテンを完全に閉めて音漏れ・光漏れを防ぐこと、近隣に迷惑となる大音量は不可とされています。爆音を前提にした収録は計画段階で外すか、事前に相談が必要です。&lt;/p&gt;

&lt;h2&gt;
  
  
  7. 時間の見積もり
&lt;/h2&gt;

&lt;p&gt;料金は時間帯×曜日の区分です。撮影なら、平日昼間(9:30〜16:30)が月〜金で5,000円という区分が使いやすい価格帯です。&lt;/p&gt;

&lt;p&gt;注意点は、機材の搬入・設置・撤収・清掃まですべて利用時間に含まれること、延長は1時間2,000円で1分でも超えると1時間分になることです。&lt;/p&gt;

&lt;p&gt;収録の時間割は次のように組むと見積もりやすくなります。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;搬入・照明と音の仮組み&lt;/li&gt;
&lt;li&gt;回線とマイクのテスト&lt;/li&gt;
&lt;li&gt;本番(撮影 or 配信)&lt;/li&gt;
&lt;li&gt;撤収・原状回復・清掃&lt;/li&gt;
&lt;li&gt;施錠して退出(退出時刻を過ぎると入口のキーコードが変わる)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;スタッフは常駐していないので、鍵の受け取りから施錠まで自分たちで行います。キーコードは利用日当日に送られてきます。&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ:下見で確かめるリスト
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;暗転した状態で、備え付け照明だけでどこまで撮れるか&lt;/li&gt;
&lt;li&gt;白背面の反射と、貼り物の留め方&lt;/li&gt;
&lt;li&gt;Bluetooth経由の再生音とマイクの回り込み&lt;/li&gt;
&lt;li&gt;本番と同じ時間帯のWi-Fiの上り速度&lt;/li&gt;
&lt;li&gt;三脚とケーブルの置き場所&lt;/li&gt;
&lt;li&gt;撤収と清掃にかかる時間&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;利用前の下見は受け付けているとのことなので、配信の本番前に一度、上のリストを持って確認しておくと当日の事故が減らせます。&lt;/p&gt;

&lt;p&gt;本ページはプロモーションが含まれています&lt;/p&gt;

</description>
      <category>streaming</category>
      <category>video</category>
      <category>japan</category>
    </item>
    <item>
      <title>同時視聴者数1万と記事3本を、同じ物差しに載せる</title>
      <dc:creator>Hajime</dc:creator>
      <pubDate>Wed, 23 Sep 2026 13:38:57 +0000</pubDate>
      <link>https://dev.to/marketer2234/tong-shi-shi-ting-zhe-shu-1mo-toji-shi-3ben-wo-tong-ziwu-chai-sinizai-seru-2fad</link>
      <guid>https://dev.to/marketer2234/tong-shi-shi-ting-zhe-shu-1mo-toji-shi-3ben-wo-tong-ziwu-chai-sinizai-seru-2fad</guid>
      <description>&lt;p&gt;複数の指標を1本のスコアにまとめる仕事は、見た目ほど単純ではありません。&lt;/p&gt;

&lt;p&gt;「配信の同時視聴者数」と「メディアに出た記事の本数」を足したい、という場面を考えてみてください。前者は数万単位で動き、後者は0本から数本の間に収まります。そのまま足せば、前者がすべてを決めてしまう。重みで調整しようにも、そもそも単位が揃っていません。&lt;/p&gt;

&lt;p&gt;ゲームタイトルの熱量を、配信・検索・記事・コミュニティなど10種類以上の公開データから合成しているダッシュボードがあります。その設計の考え方を材料に、単位の違う指標をどう1つの物差しに載せるかを整理します(公開情報は2026年9月時点)。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0uhp9ych2gve6j5oavnd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0uhp9ych2gve6j5oavnd.png" alt="GameAxiz" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://gameaxiz.com/" rel="noopener noreferrer"&gt;10種類以上の公開データを合成したゲーム熱量スコアの GameAxiz&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. まず「その日の中での位置」に直す
&lt;/h2&gt;

&lt;p&gt;最初の一手は、各指標を同じ範囲に写すことです。このダッシュボードでは、系列ごとに&lt;strong&gt;その日の全タイトル中の最大値を基準にして0〜100へ&lt;/strong&gt;直しています。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 系列ごとに、その日の最大値を 100 とする&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toRelative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dayMax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dayMax&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;dayMax&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;この時点でスコアの意味が決まります。&lt;strong&gt;結果は絶対量ではなく、その日の顔ぶれの中での相対的な位置&lt;/strong&gt;です。サイト側もこの点を明記していて、0〜100は「絶対的な人気を表すものではない」と断っています。&lt;/p&gt;

&lt;p&gt;相対化の代償は、日をまたいだ比較が素直にできなくなることです。昨日の70と今日の70は、比べている相手が違います。変化を追うなら、スコアそのものより「1週間前との差」のように差分として扱うほうが扱いやすくなります。&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 線形のままだと、下位が全部0に潰れる
&lt;/h2&gt;

&lt;p&gt;上の関数には問題があります。視聴者数やレビュー数のような量は、上位と下位で桁が3つも4つも違います。&lt;/p&gt;

&lt;p&gt;最大値が100万のとき、1,000の値は線形だと0.1点です。ランキングの大半が0点付近に貼り付き、中位の差がまったく見えなくなります。&lt;/p&gt;

&lt;p&gt;こういう&lt;strong&gt;桁違いの差がつく量は対数に写す&lt;/strong&gt;のが定石です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toRelativeLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dayMax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dayMax&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log10&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log10&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dayMax&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;1 +&lt;/code&gt; を入れているのは、値が1以下のときに負の対数が出ないようにするためです。これで最大100万に対して1,000は約50点になり、中位のタイトル同士の違いが目に見える幅で出てきます。&lt;/p&gt;

&lt;h2&gt;
  
  
  3. ただし、小さい整数を対数にすると逆に壊れる
&lt;/h2&gt;

&lt;p&gt;では全部対数にすればいいかというと、そうはいきません。&lt;/p&gt;

&lt;p&gt;記事の本数や大会の数のような&lt;strong&gt;小さな整数&lt;/strong&gt;を対数に写すと、今度は1本あるだけで大きな点が付きます。最大が5本の日に1本は、対数だと4割近い点数になります。「1本出た」と「5本出た」の差が、実感よりずっと小さく見えてしまう。&lt;/p&gt;

&lt;p&gt;そこで指標ごとに尺度を宣言します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;log&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;linear&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Scale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Scale&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;log&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;toRelativeLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;toRelative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;判断の基準は「その量の分布が何桁にまたがるか」です。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;量の性質&lt;/th&gt;
&lt;th&gt;例&lt;/th&gt;
&lt;th&gt;尺度&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;桁違いに開く&lt;/td&gt;
&lt;td&gt;同時視聴者数、再生数、レビュー数&lt;/td&gt;
&lt;td&gt;対数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;小さな整数に収まる&lt;/td&gt;
&lt;td&gt;記事本数、大会の件数&lt;/td&gt;
&lt;td&gt;線形&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  4. ストックとフローを混ぜない
&lt;/h2&gt;

&lt;p&gt;もう1つの落とし穴は、性質の違う量を同じ鍋に入れることです。&lt;/p&gt;

&lt;p&gt;Steamのレビュー数やコミュニティの参加者数は、&lt;strong&gt;長い時間をかけて積み上がった量(ストック)&lt;/strong&gt;です。一方、今日の配信視聴者数や今週の記事本数は&lt;strong&gt;いま起きていること(フロー)&lt;/strong&gt;です。&lt;/p&gt;

&lt;p&gt;ストックを熱量と同列に混ぜると、発売から何年も経った大型タイトルが、何も起きていない日でも上位に居座ります。「いま熱いもの」を見たいのに、「これまで大きかったもの」が答えとして返ってくるわけです。&lt;/p&gt;

&lt;p&gt;このダッシュボードでは、ストックを「規模」として別枠にまとめ、総合スコアへは&lt;strong&gt;小さく&lt;/strong&gt;効かせる形を取っています。規模を無視はしないが、日々の動きを覆い隠さない、というバランスです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;weighted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flowSignals&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// 配信・検索・記事など&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;weighted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stockSignals&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// レビュー数・コミュニティ規模など&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// stock 側の比重は小さくとる&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;なお、この「規模」をそのまま売上と読むのは誤りです。サイトでも、販売規模はSteamのレビュー数を基にした推定であり、家庭用ゲーム機の売上は含まないと明記されています。指標の名前が読み手の期待とずれるときは、画面の上で但し書きを添えるのが誠実だと思います。&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 地域ごとに別々に合成する
&lt;/h2&gt;

&lt;p&gt;最後に、合成を1回で済ませない理由です。&lt;/p&gt;

&lt;p&gt;国内で話題でも海外では静か、あるいはその逆、というタイトルは珍しくありません。両方をまとめて平均すると、偏りの大きいタイトルほど中くらいの点数に丸められ、特徴が消えます。&lt;/p&gt;

&lt;p&gt;そこで重みのセットを地域ごとに持ち、&lt;strong&gt;国内スコアと海外スコアを別々に算出&lt;/strong&gt;します。公開されている説明では、国内側は日本語版Wikipediaの閲覧数と国内メディアの記事本数が中心、海外側はTwitchの視聴者数と英語版Wikipediaが中心です。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz17fgvojq4eikj0bvva2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz17fgvojq4eikj0bvva2.png" alt="スコアの内訳" width="797" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;詳細ページでは、総合・国内・海外の3つを並べ、どの指標が押し上げているかも表示しています。合成した数字を出すなら、&lt;strong&gt;分解した姿も一緒に出す&lt;/strong&gt;。単一のスコアに対する疑問のほとんどは、内訳が見えるだけで解消します。&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;単位の違う指標は、まず系列ごとに同じ範囲(0〜100)へ写す。結果は相対値になる&lt;/li&gt;
&lt;li&gt;桁違いに開く量は対数、小さな整数は線形。指標ごとに尺度を宣言する&lt;/li&gt;
&lt;li&gt;積み上がった量(ストック)は別枠にし、日々の動き(フロー)を隠さない比重で混ぜる&lt;/li&gt;
&lt;li&gt;偏りそのものに意味があるなら、平均せずに軸を分けて合成する&lt;/li&gt;
&lt;li&gt;合成スコアには内訳を添える&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;どれも個別には地味な判断ですが、1つでも抜けると「見た目はそれらしいのに、誰の実感とも合わない数字」ができあがります。&lt;/p&gt;

&lt;p&gt;本ページはプロモーションが含まれています&lt;/p&gt;

</description>
      <category>datascience</category>
      <category>webdev</category>
      <category>japan</category>
    </item>
    <item>
      <title>写真から作る3D/ARをWebで配るとき、動作環境の下限をどこに引くか</title>
      <dc:creator>Hajime</dc:creator>
      <pubDate>Tue, 22 Sep 2026 00:33:27 +0000</pubDate>
      <link>https://dev.to/marketer2234/xie-zhen-karazuo-ru3darwowebdepei-rutoki-dong-zuo-huan-jing-noxia-xian-wodokoniyin-kuka-3iec</link>
      <guid>https://dev.to/marketer2234/xie-zhen-karazuo-ru3darwowebdepei-rutoki-dong-zuo-huan-jing-noxia-xian-wodokoniyin-kuka-3iec</guid>
      <description>&lt;p&gt;Webで3D/ARを配信するとき、実装より先に決めておいたほうがいいのが&lt;strong&gt;動作環境の下限&lt;/strong&gt;です。&lt;/p&gt;

&lt;p&gt;下限を決めないまま公開すると、動かない端末からの問い合わせが全部サポートに来ます。かといって広く取りすぎると、低スペック端末での読み込み時間に引きずられて、体験そのものが崩れます。&lt;/p&gt;

&lt;p&gt;実際に下限を明示しているサービスがあったので、その引き方を材料に考えます。フラワースタンドを撮影して3D/ARのコンテンツにし、専用URLで配信するサービスです(掲載内容は2026年9月時点)。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbhqbyi0iucmb7oaja8qn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbhqbyi0iucmb7oaja8qn.png" alt="クロリス" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://chloris3d.jp/" rel="noopener noreferrer"&gt;アプリ不要でARを見せている クロリス&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  配信の形
&lt;/h2&gt;

&lt;p&gt;まず構成を整理します。公開情報から読み取れるのはこの範囲です。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;要素&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;入力&lt;/td&gt;
&lt;td&gt;完成した実物を、必要な角度から撮影した写真&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;生成&lt;/td&gt;
&lt;td&gt;写真をもとに3D/AR表示用のデータを生成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;配信&lt;/td&gt;
&lt;td&gt;発行された専用URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;クライアント&lt;/td&gt;
&lt;td&gt;スマートフォンのブラウザ。専用アプリのインストールは不要&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;エンドユーザーから見ると「URLを開くだけ」です。アプリを挟まない構成は導線が短い一方で、&lt;strong&gt;実行環境を選べない&lt;/strong&gt;という代償があります。ネイティブアプリならストアの対応OSで足切りできますが、URLは誰でも開けてしまう。&lt;/p&gt;

&lt;p&gt;だからこそ、下限を文書で示す必要が出てきます。&lt;/p&gt;

&lt;h2&gt;
  
  
  実際に引かれていた線
&lt;/h2&gt;

&lt;p&gt;公開されていた推奨環境はこうでした。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;区分&lt;/th&gt;
&lt;th&gt;発売時期の目安&lt;/th&gt;
&lt;th&gt;OS / ブラウザ&lt;/th&gt;
&lt;th&gt;代表機種&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iOS&lt;/td&gt;
&lt;td&gt;2021年以降のiPhone&lt;/td&gt;
&lt;td&gt;iOS 18以降のSafari&lt;/td&gt;
&lt;td&gt;iPhone 13シリーズ以降、iPhone SE(第3世代)など&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;td&gt;2022年以降のスマートフォン&lt;/td&gt;
&lt;td&gt;Android 13以降のChrome&lt;/td&gt;
&lt;td&gt;Google Pixel 7シリーズ以降、Galaxy S22シリーズ以降、Xperia 1 IV / 5 IV以降、AQUOS R7以降など&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcqp83rft3fzjjvrajoai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcqp83rft3fzjjvrajoai.png" alt="推奨環境" width="800" height="727"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;注目したのは、&lt;strong&gt;3つの異なる軸を併記している&lt;/strong&gt;ところです。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;発売時期(ハードウェアの世代の代理指標)&lt;/li&gt;
&lt;li&gt;OSとブラウザのバージョン(APIの可用性)&lt;/li&gt;
&lt;li&gt;代表機種(ユーザーが自分で照合できる形)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;技術的に意味があるのは2だけです。1と3は、エンドユーザーが自分の端末を判定できるようにするための変換にすぎません。&lt;/p&gt;

&lt;h2&gt;
  
  
  なぜ3つ書くのか
&lt;/h2&gt;

&lt;p&gt;ここが設計として学ぶところでした。&lt;/p&gt;

&lt;p&gt;ユーザーは自分の端末のOSバージョンを知りません。「Android 13以降」とだけ書かれても照合できず、結局試してから問い合わせることになります。発売年と機種名を併記すると、その場で判断できる確率が上がります。&lt;/p&gt;

&lt;p&gt;一方で、発売年だけにすると誤判定が起きます。新しい端末でもOSを更新していなければ条件から外れるからです。だから&lt;strong&gt;技術条件を正として書き、その横に照合用の目安を置く&lt;/strong&gt;という並べ方になります。&lt;/p&gt;

&lt;p&gt;サポート窓口の負荷を、ドキュメントの書き方で下げているわけです。&lt;/p&gt;

&lt;h2&gt;
  
  
  失敗モードを書いてある
&lt;/h2&gt;

&lt;p&gt;もうひとつ実務的だったのが、下限を外れたときの挙動が書かれている点です。&lt;/p&gt;

&lt;p&gt;年数が経った端末や最新版のSafari / Chromeに更新できない端末では、読み込みに時間がかかる、表示が途中で止まる、または表示されない場合がある、とありました。&lt;/p&gt;

&lt;p&gt;3D/ARのWeb配信では、&lt;strong&gt;きれいに失敗しない&lt;/strong&gt;のが厄介です。未対応を検出して明示的にエラーを出す経路もあれば、アセットの読み込み中にメモリを使い切って無反応になる経路もあります。後者はユーザーから見て「固まった」としか見えません。&lt;/p&gt;

&lt;p&gt;ドキュメントに「途中で止まることがある」と書いてあるだけで、ユーザーは自分の操作ミスを疑わずに済みます。実装で防げないものを、文言で受け止めている例だと思いました。&lt;/p&gt;

&lt;p&gt;案内されていた対処も、再読み込み・ブラウザの更新・通信環境の良い場所での再アクセスの3つで、いずれもユーザー側で完結します。&lt;/p&gt;

&lt;h2&gt;
  
  
  下限を決めるときの判断材料
&lt;/h2&gt;

&lt;p&gt;自分で同種のものを作るなら、下限は次の順で詰めることになります。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;必要なAPIの可用性から技術的下限を出す。&lt;/strong&gt; WebGLの実装差、WebXRを使うかどうか、テクスチャ圧縮形式の対応。ここが実際の足切り&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;メモリ上限から実効的な下限を出す。&lt;/strong&gt; モバイルSafariはタブあたりのメモリに上限があり、高解像度テクスチャを積むと技術的には対応していても落ちる&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1と2の厳しいほうを採用する。&lt;/strong&gt; たいてい2が効く&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;それをユーザーが照合できる表現に変換する。&lt;/strong&gt; 発売年と代表機種&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外れたときの挙動と対処を書く&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;3を飛ばして仕様上の対応表だけ出すと、「対応と書いてあるのに動かない」という報告が来ます。&lt;/p&gt;

&lt;h2&gt;
  
  
  保証の範囲も文書にある
&lt;/h2&gt;

&lt;p&gt;利用規約側にも対応する記述がありました。表示の精度・色味・形状・動作環境について完全性・正確性・特定目的適合性を保証しない、閲覧環境によっては一部または全部が正常に利用できない場合があることをあらかじめ了承する、という条項です。&lt;/p&gt;

&lt;p&gt;写真から3Dを起こす処理は入力依存が強く、同じパイプラインでも対象によって結果が変わります。実物の形状や装飾、撮影環境によっては再現できない場合がある、という注記もありました。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;再現度を数値で約束しない&lt;/strong&gt;というのは、この種の生成処理を商用で出すときの現実的な線だと思います。出力が入力の質に左右される以上、SLAのように書ける性質のものではありません。&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;アプリを挟まない配信は導線が短いが、実行環境を選べない&lt;/li&gt;
&lt;li&gt;下限は技術条件で決め、ユーザーが照合できる形に変換して併記する&lt;/li&gt;
&lt;li&gt;実効的な下限はAPIの対応表ではなくメモリで決まることが多い&lt;/li&gt;
&lt;li&gt;失敗モードと対処を書いておくと、ユーザーが自分を疑わずに済む&lt;/li&gt;
&lt;li&gt;入力依存の強い生成処理は、再現度を約束しない書き方になる&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;本ページはプロモーションが含まれています&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ar</category>
      <category>japan</category>
    </item>
    <item>
      <title>「参加人数で課金」を実装するとき、何をもって1人と数えるか</title>
      <dc:creator>Hajime</dc:creator>
      <pubDate>Sun, 20 Sep 2026 05:19:02 +0000</pubDate>
      <link>https://dev.to/marketer2234/can-jia-ren-shu-deke-jin-woshi-zhuang-surutoki-he-womotute1ren-toshu-eruka-2dgk</link>
      <guid>https://dev.to/marketer2234/can-jia-ren-shu-deke-jin-woshi-zhuang-surutoki-he-womotute1ren-toshu-eruka-2dgk</guid>
      <description>&lt;p&gt;従量課金の設計で最初に決めるのは料率ではありません。&lt;strong&gt;1件を何で数えるか&lt;/strong&gt;です。&lt;/p&gt;

&lt;p&gt;ここが曖昧なまま実装に入ると、請求の根拠を後から説明できなくなります。利用者から「この数字は何ですか」と聞かれて答えられない課金は、値段が妥当でも信用されません。&lt;/p&gt;

&lt;p&gt;位置情報を使うスタンプラリーのサービスが、この定義を公開していたので、設計の題材として読みます(2026年9月時点の掲載内容です)。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0kkduq201kpe7lmep7eo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0kkduq201kpe7lmep7eo.png" alt="ARスタンプラリー" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://stamp-rally.digi-sta.link/" rel="noopener noreferrer"&gt;参加人数を課金単位にしている ARスタンプラリー&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  候補になる数え方
&lt;/h2&gt;

&lt;p&gt;「参加人数」と言ったとき、実装上の候補は複数あります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A. 参加ページを開いた回数         → PV。水増ししやすい
B. 参加ページを開いた一意の端末数  → Cookie/LocalStorage 依存。消すと増える
C. 会員登録した人数               → 登録を必須にする設計が要る
D. 何らかの行為を完了した人数      → 定義が要るが、意味が明確
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;このサービスが採っていたのは D でした。定義はこうです。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;初めてスタンプを1個以上取得した人数を数えます。ページを見ただけの人は含みません。(料金ページより)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ful65ot1xyhqz3d9x2esc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ful65ot1xyhqz3d9x2esc.png" alt="料金" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  なぜ D が扱いやすいか
&lt;/h2&gt;

&lt;p&gt;3つあります。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. 利用者に説明できる。&lt;/strong&gt; 「現地へ行って1個押した人の数」で言い切れます。A や B は「なぜこの数字なのか」の説明が長くなります。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. 告知の成否と課金が連動しない。&lt;/strong&gt; SNS で拡散して閲覧が跳ねても、請求は増えません。主催者は告知を躊躇しなくて済みます。A を採ると、拡散するほど費用が増えるという逆インセンティブが生まれます。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. サーバー側のイベントで数えられる。&lt;/strong&gt; スタンプ取得はサーバーに記録が要る操作なので、クライアントの状態に依存せず数えられます。B はブラウザのストレージが消えると同じ人を二重に数えます。&lt;/p&gt;

&lt;p&gt;3つ目は、この手のサービスでは特に大きい論点です。参加者に会員登録を強制しない設計(このサービスも会員登録は必須ではない)を採ると、一意な人の識別は本質的に困難になります。「初めてスタンプを取得した」というイベントを1回だけ記録する形にすれば、識別の精度が課金の精度に直結しません。&lt;/p&gt;

&lt;h2&gt;
  
  
  判定をどこに置くか
&lt;/h2&gt;

&lt;p&gt;課金単位の話と地続きで、判定の置き場所も公開されていました。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;位置の判定はすべてサーバー側で行い、押せる範囲・期間・回数はサーバーが持っている設定で確認します。参加者の画面から判定結果を書き換えることはできません。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjc6fk6htkkj9azsv2ukw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjc6fk6htkkj9azsv2ukw.png" alt="GPS" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;「範囲・期間・回数」の3つを同じ場所で見ている、と明記されているのが要点です。範囲だけサーバーで見て、期間の判定をクライアントに置く、という混在をやると、期限切れのラリーに押せてしまいます。&lt;/p&gt;

&lt;p&gt;課金の観点でも、押印イベントがサーバーに集約されていることが前提になります。クライアントが「押しました」と申告する形だと、課金対象の数を自己申告で作れてしまいます。&lt;/p&gt;

&lt;h2&gt;
  
  
  防げないものを、仕様に書く
&lt;/h2&gt;

&lt;p&gt;興味深かったのは、限界も書いてあった点です。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;位置情報を偽装するアプリを使われた場合は、GPSだけで完全に防ぐことはできません。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;そのうえで、QRコードの併用・達成後の現地引き換え・押印の時刻や順序の確認、という運用側の対策が挙げられていました。&lt;/p&gt;

&lt;p&gt;サーバー側判定で守れるのは「クライアントから判定結果を書き換えられないこと」であって、「クライアントが送ってくる位置が本物であること」ではありません。この2つは別物です。前者は設計で担保でき、後者は端末の外側の問題なので設計では閉じません。&lt;/p&gt;

&lt;p&gt;ドキュメントでこの線を引いているかどうかは、そのサービスの設計を信頼できるかの目安になります。&lt;/p&gt;

&lt;h2&gt;
  
  
  上限に達したときの設計
&lt;/h2&gt;

&lt;p&gt;人数課金には、上限に触れたときの振る舞いという論点もあります。&lt;/p&gt;

&lt;p&gt;このサービスは、開催中でも上位の枠へ差額のみで変更でき、決済確認後に自動で枠が広がる、という形でした。ダウングレードはできません。&lt;/p&gt;

&lt;p&gt;課金の設計として見ると、&lt;strong&gt;アップグレードのみ・差額精算・即時反映&lt;/strong&gt;という組み合わせです。返金処理を持たずに済み、差額計算も単純な引き算になります。「小さく買って必要なら足す」に利用者を誘導する形なので、過大な枠を最初に売らない設計とも言えます。&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;p&gt;従量課金を設計するときのチェック項目として、今回読んだ内容はそのまま流用できます。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1件の定義を、利用者に1文で説明できるか&lt;/li&gt;
&lt;li&gt;その定義は、サーバー側のイベントだけで数えられるか&lt;/li&gt;
&lt;li&gt;告知の成否と請求額が連動していないか&lt;/li&gt;
&lt;li&gt;判定の範囲・期間・回数が、同じ場所で評価されているか&lt;/li&gt;
&lt;li&gt;設計で閉じない部分(端末の外側)を、ドキュメントに明示しているか&lt;/li&gt;
&lt;li&gt;上限に触れたときの操作が、返金を伴わない方向に設計されているか&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;値段を決めるより先に、この6つを埋める。順番としてはそちらが先だと思います。&lt;/p&gt;

&lt;p&gt;本ページはプロモーションが含まれています&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>japan</category>
    </item>
    <item>
      <title>「可・不可・要相談」の3値を媒体ごとに持つと、検索はどう変わるか</title>
      <dc:creator>Hajime</dc:creator>
      <pubDate>Sat, 19 Sep 2026 03:48:21 +0000</pubDate>
      <link>https://dev.to/marketer2234/ke-bu-ke-yao-xiang-tan-no3zhi-womei-ti-gotonichi-tuto-jian-suo-hadoubian-waruka-1j06</link>
      <guid>https://dev.to/marketer2234/ke-bu-ke-yao-xiang-tan-no3zhi-womei-ti-gotonichi-tuto-jian-suo-hadoubian-waruka-1j06</guid>
      <description>&lt;p&gt;人と案件をつなぐサービスを作るとき、プロフィールは「できること」の集合として設計されがちです。スキルタグを持たせて、部分一致で引く。実装としては素直ですが、&lt;strong&gt;できないことが表現できない&lt;/strong&gt;という弱点があります。&lt;/p&gt;

&lt;p&gt;キャスティングのサイトを読んでいて、ここを正面から持っているデータ設計を見かけたので、構造として何が起きるのかを書きます(掲載内容の確認は2026年9月時点)。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhdm1hl2bfpbdofzc4uym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhdm1hl2bfpbdofzc4uym.png" alt="NEXT CAST" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://next-cast.net" rel="noopener noreferrer"&gt;対応可否を媒体ごとに登録できる NEXT CAST&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  持っているのは (媒体 × 3値) と NG 事項
&lt;/h2&gt;

&lt;p&gt;登録するタレントは、SNS や媒体ごとに「対応可 / 不可 / 要相談」を登録し、加えて NG 事項を明記します。&lt;/p&gt;

&lt;p&gt;素朴に書くと、プロフィールはこうなります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Talent
  id
  category            # 芸人 / インフルエンサー / アーティスト / その他
  intro               # 紹介文(必須)
  availability[]      # (media, status, condition)  status ∈ {ok, ng, negotiable}
  ng_notes[]          # 受けない案件の種類
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;availability&lt;/code&gt; を 2値(できる / できない)ではなく 3値にしているのが肝です。&lt;/p&gt;

&lt;h2&gt;
  
  
  2値にすると「要相談」が消える
&lt;/h2&gt;

&lt;p&gt;3値を2値に落とすと、&lt;code&gt;negotiable&lt;/code&gt; はどちらかに寄せることになります。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ok&lt;/code&gt; に寄せる → 検索結果に出るが、打診すると断られる。&lt;strong&gt;偽陽性&lt;/strong&gt;が増える&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ng&lt;/code&gt; に寄せる → 条件次第では受けられた案件が、そもそも表示されない。&lt;strong&gt;偽陰性&lt;/strong&gt;が増える&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;現実の運用では、後者のほうが見えにくく、損も大きくなります。出てこなかった候補は、誰にも気づかれないからです。&lt;/p&gt;

&lt;p&gt;3値のまま持てば、検索側で「可のみ」と「可+要相談」を出し分けられます。UI としては、絞り込みの既定値をどちらにするかという問題に変わります。&lt;/p&gt;

&lt;h2&gt;
  
  
  条件は構造化しないほうが通ることもある
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;condition&lt;/code&gt; と &lt;code&gt;ng_notes&lt;/code&gt; は自由記述です。ここを無理に enum へ落とさないのは、実務上は妥当だと思います。&lt;/p&gt;

&lt;p&gt;受けない案件の理由は、業種、商材、表現、時期、共演者と、軸がばらばらです。列挙しようとすると、どの軸でも取りこぼす。検索の絞り込みには媒体 × 3値を使い、細部は人が読む。役割を分けた形です。&lt;/p&gt;

&lt;h2&gt;
  
  
  依頼側にも必須項目がある
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc8wo47ruy7btjjn215b9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc8wo47ruy7btjjn215b9.png" alt="タレント一覧" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;このサービスで面白かったのは、&lt;strong&gt;依頼する企業側にもプロフィールの公開が必須&lt;/strong&gt;な点でした。&lt;/p&gt;

&lt;p&gt;会社名・キャスティングの希望・企業ロゴの登録が、タレント検索と依頼の必須条件とされています。タレント側も、紹介文の登録が公募案件の検索・応募の必須条件です。&lt;/p&gt;

&lt;p&gt;つまり検索できる状態になるまでに、両側でバリデーションが1回ずつ入ります。「空のプロフィールで一覧だけ眺める」ができないので、検索に出てくるレコードの質が上がります。&lt;/p&gt;

&lt;h2&gt;
  
  
  公開前に人の確認が入る
&lt;/h2&gt;

&lt;p&gt;タレントの掲載も、企業の公募案件も、公開前に運営が内容を審査します。差し戻しの場合は理由がマイページに表示され、修正して再申請できるとありました。&lt;/p&gt;

&lt;p&gt;状態としては &lt;code&gt;draft → in_review → published&lt;/code&gt;、差し戻しで &lt;code&gt;rejected(reason)&lt;/code&gt; を経て &lt;code&gt;draft&lt;/code&gt; に戻る形です。理由を保持して画面に出す前提なので、&lt;code&gt;rejected&lt;/code&gt; は状態フラグではなくメッセージ付きのレコードになります。&lt;/p&gt;

&lt;h2&gt;
  
  
  掲載基準も、値として効いてくる
&lt;/h2&gt;

&lt;p&gt;掲載基準には、18歳以上、本人が運用する公開アカウントで活動実態が確認できること、フォロワーの購入・水増しが疑われる場合は掲載を見送ること、といった条件が並びます。&lt;/p&gt;

&lt;p&gt;SNS を主な媒体とする人は合計フォロワー1,000人以上が目安、ただし目安未満でも活動実績・専門性・独自性を含めて総合判断、とも書かれています。&lt;/p&gt;

&lt;p&gt;しきい値を公開しつつ、しきい値だけで機械的に切らない。審査を人が持っている設計とセットで読むと筋が通っています。&lt;/p&gt;

&lt;h2&gt;
  
  
  契約の形は1つに絞られている
&lt;/h2&gt;

&lt;p&gt;扱うのは業務委託の案件のみで、雇用契約の募集は掲載できません。企業とタレントが直接契約を結ぶ場の提供であり、雇用のあっせんは行わないと明記されています。&lt;/p&gt;

&lt;p&gt;扱う契約が1種類だと、通知も、案件の状態遷移も、分岐が減ります。何でも載る場にしないことが、そのまま実装の単純さになっています。&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;できないことを持てる構造(3値 + NG)は、検索の精度に直結する&lt;/li&gt;
&lt;li&gt;2値へ落とすと偽陰性が増え、しかもそれは観測しづらい&lt;/li&gt;
&lt;li&gt;絞り込みは構造化データ、細部は自由記述、と役割を分ける&lt;/li&gt;
&lt;li&gt;両側に必須項目を置くと、検索対象のレコードが痩せない&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;本ページはプロモーションが含まれています&lt;/p&gt;

</description>
      <category>database</category>
      <category>webdev</category>
      <category>japan</category>
    </item>
    <item>
      <title>日時が決まっている役務の販売で、返金と振替をどう分けて持つか</title>
      <dc:creator>Hajime</dc:creator>
      <pubDate>Thu, 17 Sep 2026 22:36:56 +0000</pubDate>
      <link>https://dev.to/marketer2234/ri-shi-gajue-matuteiruyi-wu-nofan-mai-de-fan-jin-tozhen-ti-wodoufen-ketechi-tuka-509j</link>
      <guid>https://dev.to/marketer2234/ri-shi-gajue-matuteiruyi-wu-nofan-mai-de-fan-jin-tozhen-ti-wodoufen-ketechi-tuka-509j</guid>
      <description>&lt;p&gt;物を売るテーブルをそのまま使って、日時指定のオンライン役務を売ろうとすると、たいてい返金のところで破綻します。&lt;/p&gt;

&lt;p&gt;商品は「渡したか、渡していないか」の2値ですが、&lt;strong&gt;日時の決まった役務は「時間が来たか」「実施されたか」「誰の理由で実施されなかったか」の3つが独立している&lt;/strong&gt;からです。&lt;/p&gt;

&lt;p&gt;オンラインで1対1のイベントを販売できるサービスの規約が、この3つをきれいに分けて書いていたので、データ設計として読み直してみます。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn3omxiglro5kfxid4j2c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn3omxiglro5kfxid4j2c.png" alt="1to1live" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://1to1live.net/" rel="noopener noreferrer"&gt;日時指定のオンラインイベントを販売できる 1to1live&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  規約に出てくる場合分け
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fegfbj8our2xmzfza1cxs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fegfbj8our2xmzfza1cxs.png" alt="キャンセル・返金ポリシー" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;公開されている規定を、そのまま分岐として書き出します。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;購入者都合(遅刻・不参加・途中退室・端末や通信の不具合・カメラマイクの許可失敗) → &lt;strong&gt;返金しない&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;販売者都合の中止 → &lt;strong&gt;全額返金&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;販売者が開始時刻に入室しない → 申告 → 事実確認 → &lt;strong&gt;返金または権利の再付与(振替)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;提供側のシステム障害 → &lt;strong&gt;振替または返金&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;規約違反による強制終了 → &lt;strong&gt;返金しない&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ここで重要なのは、&lt;strong&gt;「返金」と「権利の再付与」が別の結果として並んでいる&lt;/strong&gt;ことです。同じ「実施されなかった」でも、出口が2つある。&lt;/p&gt;

&lt;h2&gt;
  
  
  チケットを金額ではなく権利として持つ
&lt;/h2&gt;

&lt;p&gt;出口が2つあるということは、購入 = 決済の成功、ではないということです。決済とは別に、&lt;strong&gt;特定の日時に役務を受ける権利&lt;/strong&gt;を独立させて持つ必要があります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payment     決済そのもの(Stripe 側の事実)
entitlement 権利。どのコマに、誰が、いくつ
slot        コマ。開始日時・長さ・販売者
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;金額しか持っていないと、振替のときに「返金して買い直してもらう」しかなくなります。決済をやり直せば手数料が二重にかかり、実際この規定でも&lt;strong&gt;返金時に決済手数料は戻らない&lt;/strong&gt;と明記されています。権利を持ち回せるなら、その損失を出さずに別のコマへ移せます。&lt;/p&gt;

&lt;h2&gt;
  
  
  状態は「誰の理由か」まで持つ
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;cancelled&lt;/code&gt; のひとことにまとめると、あとから集計もサポートもできません。最低限、理由の主体を残します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;entitlement.status:
  active            購入済み・未実施
  consumed          実施済み
  no_show_buyer     時間が過ぎた(購入者が来なかった) -&amp;gt; 返金しない
  failed_seller     販売者が来なかった -&amp;gt; 返金 or 振替
  failed_system     障害 -&amp;gt; 振替 or 返金
  cancelled_seller  販売者都合の中止 -&amp;gt; 全額返金
  revoked           規約違反による終了 -&amp;gt; 返金しない
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;no_show_buyer&lt;/code&gt; と &lt;code&gt;failed_seller&lt;/code&gt; は、外から見ると同じ「通話が行われなかった」です。&lt;strong&gt;区別できるのは入退室の記録だけ&lt;/strong&gt;なので、ログを商品データの一部として扱う必要があります。&lt;/p&gt;

&lt;p&gt;規定側にもこれに対応する記載があって、不審請求(チャージバック)の際に&lt;strong&gt;入退室のログと通話の実施記録を提供する&lt;/strong&gt;とされています。ログが後から証拠になる前提で設計されている、ということです。&lt;/p&gt;

&lt;h2&gt;
  
  
  時間の境界を3つ持つ
&lt;/h2&gt;

&lt;p&gt;「開始時刻」だけだと足りません。この規定には&lt;strong&gt;開始30分前から控室に入室できる&lt;/strong&gt;とあり、通話画面には残り時間が出ます。つまり境界が3つあります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;open_at   = start_at - 30min   控室に入れる
start_at                        実施の開始
end_at    = start_at + duration 自動退室
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;open_at&lt;/code&gt; を持たずに &lt;code&gt;start_at&lt;/code&gt; だけで判定すると、「10分前に来たのに入れない」という問い合わせが必ず出ます。そして &lt;code&gt;end_at&lt;/code&gt; を計算で出していると、途中で長さを変えたときに既存の権利の終了時刻が動きます。&lt;strong&gt;発行済みの権利には、その時点の長さを固定して持たせる&lt;/strong&gt;のが安全です。&lt;/p&gt;

&lt;h2&gt;
  
  
  購入後に価格を変えられるようにしない
&lt;/h2&gt;

&lt;p&gt;コマの価格を &lt;code&gt;slot.price&lt;/code&gt; から直接引いていると、販売者が後から価格を変えたときに、返金額が購入時と違う額になります。&lt;/p&gt;

&lt;p&gt;決済のたびに&lt;strong&gt;購入時の金額・手数料率を権利側に固定&lt;/strong&gt;しておく。返金は権利に固定された金額を参照して計算する。ここは例外なく固定側に倒すところです。&lt;/p&gt;

&lt;p&gt;実際、この料金体系は利用料が売上の5%(税込)、決済手数料が3.6%で、返金時は利用料だけが戻る形でした。返金の計算に2種類の率が出てくる以上、&lt;strong&gt;率そのものを購入時点でスナップショットしておく&lt;/strong&gt;必要があります。&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;p&gt;日時指定の役務を扱うときに、最初から入れておきたいのはこの5つでした。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;決済・権利・コマを別のテーブルに分ける&lt;/li&gt;
&lt;li&gt;状態に&lt;strong&gt;理由の主体&lt;/strong&gt;を残す(購入者 / 販売者 / システム / 違反)&lt;/li&gt;
&lt;li&gt;返金と振替を&lt;strong&gt;別の出口&lt;/strong&gt;として実装する&lt;/li&gt;
&lt;li&gt;時刻の境界を3つ(入室可・開始・終了)持ち、&lt;strong&gt;権利側に固定&lt;/strong&gt;する&lt;/li&gt;
&lt;li&gt;金額と手数料率は&lt;strong&gt;購入時点のスナップショット&lt;/strong&gt;を持つ&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;規約の場合分けは、そのまま状態遷移図になります。実装の前に読むと、あとから足りなくなるカラムが減ります。&lt;/p&gt;

&lt;p&gt;本ページはプロモーションが含まれています&lt;/p&gt;

</description>
      <category>database</category>
      <category>architecture</category>
      <category>japan</category>
    </item>
    <item>
      <title>「無料キャンペーン中」の価格を、恒久価格と同じ場所に置かない</title>
      <dc:creator>Hajime</dc:creator>
      <pubDate>Thu, 17 Sep 2026 08:37:27 +0000</pubDate>
      <link>https://dev.to/marketer2234/wu-liao-kiyanpenzhong-nojia-ge-wo-heng-jiu-jia-ge-totong-zichang-suo-nizhi-kanai-1fmj</link>
      <guid>https://dev.to/marketer2234/wu-liao-kiyanpenzhong-nojia-ge-wo-heng-jiu-jia-ge-totong-zichang-suo-nizhi-kanai-1fmj</guid>
      <description>&lt;p&gt;料金表を実装するとき、&lt;strong&gt;いま無料である&lt;/strong&gt;ことと&lt;strong&gt;本来いくらである&lt;/strong&gt;ことを同じフィールドに入れると、あとで必ず壊れます。&lt;/p&gt;

&lt;p&gt;キャンペーンが終わったときに、何を元の値に戻せばいいのか分からなくなるからです。上書きしてしまったなら、戻す先がありません。&lt;/p&gt;

&lt;p&gt;音楽人材のマッチングサービスの料金表が、この構造をきれいに持っていたので、設計として整理します(2026年9月9日に確認した内容)。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq05qr09jerkd4684mm1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq05qr09jerkd4684mm1v.png" alt="Music Chronicle" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://music-chronicle.com" rel="noopener noreferrer"&gt;音楽の仕事の料金体系を確認するなら Music Chronicle&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  元の記載
&lt;/h2&gt;

&lt;p&gt;サイトにはこう書かれていました。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft3f9zlwtq9avnrf44m55.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft3f9zlwtq9avnrf44m55.png" alt="料金" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;スカウト送信 — &lt;strong&gt;無料(通常 チケット1枚)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;単発依頼の掲載 — &lt;strong&gt;無料(通常 チケット1枚)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;継続募集の掲載 — &lt;strong&gt;無料(通常 チケット3枚)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;ポートフォリオの掲載・動画公開 — &lt;strong&gt;無料&lt;/strong&gt;(掲載は今後も無料)&lt;/li&gt;
&lt;li&gt;仕事への応募 — &lt;strong&gt;無料&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;メッセージのやり取り — &lt;strong&gt;無料&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注目すべきは、&lt;strong&gt;上3つと下3つで「無料」の意味が違う&lt;/strong&gt;ことです。&lt;/p&gt;

&lt;p&gt;上3つは期間限定の無料で、括弧の中に通常価格が残っています。下3つは&lt;strong&gt;恒久的に無料&lt;/strong&gt;です。ポートフォリオの掲載には「今後も無料」と明記されていました。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;同じ「無料」という表示が、2種類ある。&lt;/strong&gt;これを1つのフィールドで表すことはできません。&lt;/p&gt;

&lt;h2&gt;
  
  
  型で分ける
&lt;/h2&gt;

&lt;p&gt;価格を、恒久的な定義と、いま適用されている条件に分けます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Feature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** 恒久的な価格。キャンペーンでも書き換えない */&lt;/span&gt;
  &lt;span class="nl"&gt;standard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;free&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tickets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="cm"&gt;/** 現在適用中の上書き。無ければ standard がそのまま適用される */&lt;/span&gt;
  &lt;span class="nl"&gt;override&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;free&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;campaign&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;announced_end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで、2種類の無料が区別できます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"portfolio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ポートフォリオの掲載・動画公開"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"standard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"free"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"スカウト送信"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"standard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tickets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"override"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"campaign"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"beta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"announced_end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"job_continuous"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"継続募集の掲載"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"standard"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tickets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"override"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"campaign"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"beta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"announced_end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;standard&lt;/code&gt; は絶対に書き換えません。&lt;/strong&gt;キャンペーンは &lt;code&gt;override&lt;/code&gt; を足すだけ、終了は &lt;code&gt;override&lt;/code&gt; を外すだけ。これで元に戻せます。&lt;/p&gt;

&lt;h2&gt;
  
  
  announced_end を null にできるようにする
&lt;/h2&gt;

&lt;p&gt;サイトには、&lt;strong&gt;有料化の開始時期は事前に告知される&lt;/strong&gt;と書かれていました。つまり、現時点で終了日は決まっていません。&lt;/p&gt;

&lt;p&gt;ここで &lt;code&gt;announced_end&lt;/code&gt; を適当な日付で埋めると、&lt;strong&gt;決まっていないことを決まっているように表示することになります。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;null&lt;/code&gt; を許容し、表示側で分岐させます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;announced_end&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;announced_end&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;まで`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;終了時期は事前告知&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;「いつまでか未定」と正直に出すほうが、後から日付がずれて信用を失うより安全です。&lt;/p&gt;

&lt;h2&gt;
  
  
  表示は「いまの価格」と「通常価格」を並べる
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;override&lt;/code&gt; があるときの表示は、片方だけにしないほうがいい。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;スカウト送信        無料   （通常 チケット1枚）
継続募集の掲載      無料   （通常 チケット3枚）
ポートフォリオ掲載  無料   （今後も無料）
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;通常価格を隠すと、終了時に利用者が驚きます。&lt;/strong&gt;逆に恒久無料のものには「今後も無料」と書く。同じ「無料」でも、後で変わるかどうかが一目で分かります。&lt;/p&gt;

&lt;p&gt;これは実装の都合ではなく、&lt;strong&gt;元データが2種類を区別して持っているからこそ書ける表示&lt;/strong&gt;です。&lt;/p&gt;

&lt;h2&gt;
  
  
  チケットは残高ではなく、期限付きのロットで持つ
&lt;/h2&gt;

&lt;p&gt;もう一つ、設計が効くところがあります。チケットの条件です。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1枚3,000円(税込)。&lt;/strong&gt;クレジットカード(Stripe)で&lt;strong&gt;1〜10枚&lt;/strong&gt;をまとめて購入できる&lt;/li&gt;
&lt;li&gt;期限は&lt;strong&gt;購入日から180日&lt;/strong&gt;。過ぎた未使用分は失効する&lt;/li&gt;
&lt;li&gt;未使用チケットの払い戻しは&lt;strong&gt;原則行わない&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;審査で不承認になった掲載については、消費分が自動で返却される&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;期限が&lt;strong&gt;購入から&lt;/strong&gt;180日なので、残高を1つの整数で持つことはできません。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ダメな例&lt;/span&gt;
&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tickets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// ← どれがいつ失効するのか分からない&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;購入した単位(ロット)で持ちます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lots"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"L1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"purchased_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-28"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"L2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"purchased_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2027-02-11"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;消費は&lt;strong&gt;期限が近いロットから&lt;/strong&gt;。これを間違えると、まだ使えたはずのチケットが失効します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lots&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;live&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lots&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// 期限が近い順&lt;/span&gt;
  &lt;span class="c1"&gt;// …live の先頭から n 枚を引き当てる&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  返却は「取り消し」であって「付与」ではない
&lt;/h2&gt;

&lt;p&gt;審査で承認されなかった場合の自動返却が、ここで効いてきます。&lt;/p&gt;

&lt;p&gt;返却を&lt;strong&gt;新しいロットの付与&lt;/strong&gt;として実装すると、&lt;strong&gt;期限が延びます。&lt;/strong&gt;4月に買ったチケットを使い、9月に不承認になって返却されたとき、新規ロットにすると期限が2027年3月まで延びてしまう。&lt;/p&gt;

&lt;p&gt;正しくは、&lt;strong&gt;消費した記録を取り消す&lt;/strong&gt;処理です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lots&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;consumption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// consumption は「どのロットから何枚引いたか」の記録&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;lot_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;consumption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lots&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;lot_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;lot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;used&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// 元のロットに戻す。期限は元のまま&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;つまり、&lt;strong&gt;消費のときに「どのロットから引いたか」を記録しておく必要があります。&lt;/strong&gt;残高を整数で減らす実装では、返却を正しく処理できません。&lt;/p&gt;

&lt;p&gt;なお、期限切れのロットに戻すことになる場合もありえます。そのときどうするかは仕様の問題なので、勝手に延長せず、&lt;strong&gt;仕様として明記されている扱いに従う&lt;/strong&gt;ことになります。&lt;/p&gt;

&lt;h2&gt;
  
  
  購入の上限も制約として持つ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1〜10枚&lt;/strong&gt;という購入単位の上限も、UI の都合ではなくデータ側の制約として持ちます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"purchase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unit_price_incl_tax"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;税込価格であることを、フィールド名に入れておくこと。&lt;strong&gt;税区分が曖昧な &lt;code&gt;price&lt;/code&gt; は、いずれ必ず事故ります。&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  課金しない機能も、同じテーブルに置く
&lt;/h2&gt;

&lt;p&gt;最後に、料金表の外にある機能について。&lt;/p&gt;

&lt;p&gt;このサービスでは、音楽シーンの公式データを扱う面(音楽ランキング / 動画ランキング / 公式ニュース / プレス・IR)があり、&lt;strong&gt;会員登録・閲覧はすべて無料&lt;/strong&gt;でした。アーティストのお気に入り登録とマイダッシュボードが使えるとのことです。&lt;/p&gt;

&lt;p&gt;これらは「キャンペーンで無料」ではなく「そもそも課金対象ではない」ものです。先ほどの型で言えば &lt;code&gt;standard: { kind: 'free' }&lt;/code&gt; で、&lt;code&gt;override&lt;/code&gt; を持ちません。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;課金対象でないものを料金表から除外すると、あとで「これは有料ですか」という問い合わせが発生します。&lt;/strong&gt;同じテーブルに入れて、恒久無料として明示するほうが親切でした。&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;standard&lt;/code&gt; と &lt;code&gt;override&lt;/code&gt; を分ける。&lt;/strong&gt;キャンペーンで恒久価格を書き換えない&lt;/li&gt;
&lt;li&gt;終了日が未定なら &lt;code&gt;null&lt;/code&gt;。&lt;strong&gt;決まっていないことを日付で埋めない&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;表示では「いまの価格」と「通常価格」を並べる&lt;/li&gt;
&lt;li&gt;チケットは整数の残高ではなく、&lt;strong&gt;期限付きのロット&lt;/strong&gt;で持つ&lt;/li&gt;
&lt;li&gt;消費は&lt;strong&gt;期限が近い順&lt;/strong&gt;。どのロットから引いたかを記録する&lt;/li&gt;
&lt;li&gt;返却は&lt;strong&gt;消費の取り消し&lt;/strong&gt;。新規付与にすると期限が延びる&lt;/li&gt;
&lt;li&gt;税区分をフィールド名に入れる&lt;/li&gt;
&lt;li&gt;恒久無料の機能も料金表に載せ、&lt;strong&gt;「今後も無料」と明示する&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;料金や条件は変わることがあります。実装に取り込むなら、取得日を添えて持ってください。&lt;/p&gt;

&lt;p&gt;本ページはプロモーションが含まれています&lt;/p&gt;

</description>
      <category>database</category>
      <category>architecture</category>
      <category>japan</category>
    </item>
    <item>
      <title>見積と請求の差を、説明のつく差とつかない差に分ける</title>
      <dc:creator>Hajime</dc:creator>
      <pubDate>Wed, 16 Sep 2026 00:03:23 +0000</pubDate>
      <link>https://dev.to/marketer2234/jian-ji-toqing-qiu-nochai-wo-shuo-ming-notukuchai-totukanaichai-nifen-keru-18bi</link>
      <guid>https://dev.to/marketer2234/jian-ji-toqing-qiu-nochai-wo-shuo-ming-notukuchai-totukanaichai-nifen-keru-18bi</guid>
      <description>&lt;h1&gt;
  
  
  見積と請求の差を、説明のつく差とつかない差に分ける
&lt;/h1&gt;

&lt;p&gt;見積書と請求書を突き合わせる処理を書くと、最初は単純な引き算に見えます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これでは使えません。&lt;strong&gt;差が出ること自体は異常ではない&lt;/strong&gt;からです。必要なのは、差があるかどうかではなく、&lt;strong&gt;その差に説明が付くかどうか&lt;/strong&gt;の判定です。&lt;/p&gt;

&lt;p&gt;葬儀の見積書と請求書を題材に、この判定をどう組むかを考えます(2026年9月9日に確認した内容を使います)。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv194b3xo325q4w4sqsin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv194b3xo325q4w4sqsin.png" alt="弁護士による葬儀ガード" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://sogiguard.com/" rel="noopener noreferrer"&gt;見積書と請求書の差額を確認するなら 弁護士による葬儀ガード&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  費目には2種類ある
&lt;/h2&gt;

&lt;p&gt;前提として、参照したコラムに費目の分類がありました。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;動かない側&lt;/strong&gt;は、祭壇、棺、寝台車、式場代、火葬料。&lt;strong&gt;動く側&lt;/strong&gt;は、料理、返礼品、礼状、安置料、ドライアイス。動く理由は人数と日数です。&lt;/p&gt;

&lt;p&gt;この分類が、そのまま判定ロジックの土台になります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;KIND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;祭壇&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;棺&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;寝台車&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;式場代&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;火葬料&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;料理&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;返礼品&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;会葬礼状&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;安置料&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ドライアイス&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;fixed&lt;/code&gt; に差が出たら、原則として説明が要る&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;variable&lt;/code&gt; に差が出るのは正常。ただし傾きと整合しているか&lt;/strong&gt;を見る&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;差分の絶対値だけを見ると、この違いが消えます。&lt;/p&gt;

&lt;h2&gt;
  
  
  変動費目は「単価 × 数量」に分解して比べる
&lt;/h2&gt;

&lt;p&gt;コラムには、あるべき書き方の例が挙げられていました。料理は「1人前◯円 × ◯人分」、返礼品は「1個◯円 × ◯個」、会葬礼状は「1枚◯円 × ◯枚」。安置・保管は「1日◯円 × ◯日」、ドライアイスは「1回◯円 × ◯回」、寝台車・霊柩車は「◯km以内◯円 × ◯回」という形です。&lt;/p&gt;

&lt;p&gt;この形で両方が書かれていれば、差の原因を&lt;strong&gt;単価側と数量側に切り分けられます。&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qty&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;qty_changed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;delta_qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qty&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qty&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;   &lt;span class="c1"&gt;// 想定内&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unit_changed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;  &lt;span class="c1"&gt;// 要確認&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;same&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;数量が増えたのは説明が付きます。単価が変わったのは、説明を聞く必要があります。&lt;/strong&gt;総額だけを比べていると、この2つが同じ「増えた」になってしまいます。&lt;/p&gt;

&lt;h2&gt;
  
  
  「一式」は判定不能として返す
&lt;/h2&gt;

&lt;p&gt;現実のデータでは、この分解ができないことがあります。&lt;/p&gt;

&lt;p&gt;「葬儀一式 ◯◯万円」としか書かれていない場合です。コラムには、この書き方だと&lt;strong&gt;後から何が含まれていたのかを確かめられない&lt;/strong&gt;とありました。&lt;/p&gt;

&lt;p&gt;ここで &lt;code&gt;qty = 1, unit = 総額&lt;/code&gt; と補完してはいけません。分解できていないという事実そのものが、確認すべき点だからです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qty&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not_comparable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lump_sum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;not_comparable&lt;/code&gt; を &lt;code&gt;same&lt;/code&gt; に倒さないこと。&lt;strong&gt;比較できなかったものを「差がなかった」と表示するのが、いちばん危ない実装です。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;なお、コラムには、その場で&lt;strong&gt;「項目ごとの金額を入れた見積書をいただけますか」と伝えてよい&lt;/strong&gt;とありました。データが取れないなら、取れる形で出し直してもらうのが正しい対処です。&lt;/p&gt;

&lt;h2&gt;
  
  
  見積に無い行が請求に出てくる
&lt;/h2&gt;

&lt;p&gt;もう1つのパターンが、&lt;strong&gt;片側にしか存在しない行&lt;/strong&gt;です。&lt;/p&gt;

&lt;p&gt;別会計になりやすい項目として挙がっていたのは、火葬料、式場使用料、宗教者へのお布施・御車代・御膳料、安置が延びた分の追加保管料、参列者が増えた分の料理・返礼品でした。火葬料は自治体や施設で差があり、無料のところも十数万円のところもあるとのことです。&lt;/p&gt;

&lt;p&gt;これらは&lt;strong&gt;見積書に載っていないのが正常&lt;/strong&gt;な場合があります。載っていないから不正、ではありません。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reconcile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;only_in_estimate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="na"&gt;only_in_invoice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;est&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;only_in_invoice&lt;/code&gt; に出たものは、&lt;strong&gt;「想定されていた範囲外の項目」のリスト&lt;/strong&gt;として出します。異常として赤くするのではなく、確認対象として並べる。判定を下すのは人の仕事です。&lt;/p&gt;

&lt;h2&gt;
  
  
  規模感を持っておく
&lt;/h2&gt;

&lt;p&gt;どの程度の差なら想定内か、という感覚を持つために数字を引いておきます。&lt;/p&gt;

&lt;p&gt;紹介されている調査の平均額は、総額が96.73万円。その内訳として飲食費が11.67万円、返礼品費が13.03万円でした。合計すると約25万円。&lt;strong&gt;総額の4分の1ほどが、契約の時点では未確定&lt;/strong&gt;という整理です。&lt;/p&gt;

&lt;p&gt;つまり、&lt;strong&gt;総額の25%前後は動きうる&lt;/strong&gt;という前提で設計する必要があります。「5%以上の差でアラート」のような閾値を総額に対して置くと、正常な差で鳴り続けます。&lt;/p&gt;

&lt;p&gt;閾値を置くなら、&lt;code&gt;variable&lt;/code&gt; の行に対して&lt;strong&gt;数量の変化率&lt;/strong&gt;で置くほうが筋が通ります。&lt;/p&gt;

&lt;h2&gt;
  
  
  傾きを先に取っておく
&lt;/h2&gt;

&lt;p&gt;差分を後から説明するより、&lt;strong&gt;事前に傾きを記録しておく&lt;/strong&gt;ほうが確実です。&lt;/p&gt;

&lt;p&gt;参照したコラムには、時間がないときに確認する項目が3つ挙げられていました。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;見積もりに含まれない項目&lt;/li&gt;
&lt;li&gt;参列者が1人増えたときの増加額&lt;/li&gt;
&lt;li&gt;安置が1日延びたときの増加額&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;データ設計としては、これがそのまま入力項目になります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"excluded"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"火葬料"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"式場使用料"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"お布施"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"slope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"per_attendee"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"per_storage_day"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recorded_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confirmed_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;confirmed_by&lt;/code&gt; を持たせているのは、コラムに&lt;strong&gt;答えはその場でメモし、できればその日のうちにメールで送って確認しておく&lt;/strong&gt;とあったからです。口頭の記録と、書面で確認済みの記録は、あとで差分を検証するときの重みが違います。&lt;/p&gt;

&lt;h2&gt;
  
  
  前提条件が違えば、そもそも比較できない
&lt;/h2&gt;

&lt;p&gt;見積どうしを比べる場合には、もう1つ条件があります。&lt;/p&gt;

&lt;p&gt;そろえるべき項目として、参列者の人数・式場・通夜の有無・安置日数・火葬料の扱いという5つが挙げられていました。&lt;/p&gt;

&lt;p&gt;この5つを&lt;strong&gt;比較のキーとして持ち、一致しない場合は比較を実行しない&lt;/strong&gt;設計にします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;COMPARE_KEYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;attendees&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;venue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storage_days&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cremation_fee_handling&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;COMPARE_KEYS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;precondition_mismatch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;COMPARE_KEYS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;条件が違うのに数字を並べて表示しない。&lt;/strong&gt;並べた時点で、利用者は比べてしまいます。&lt;/p&gt;

&lt;h2&gt;
  
  
  出力の形
&lt;/h2&gt;

&lt;p&gt;最終的に出すのは、判定ではなく分類です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;■ 想定内（数量の変化で説明が付く）
   料理        50人 → 63人   単価は同じ
   安置・保管   3日 → 4日     単価は同じ

■ 確認したい（単価が変わっている）
   返礼品      単価 ◯円 → ◯円

■ 見積に無かった項目
   式場使用料

■ 比較できなかった
   葬儀一式（内訳なし）
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;「異常あり／なし」を返さない。&lt;/strong&gt;金額の妥当性は、このデータだけでは判断できません。できるのは、人が確認すべき箇所を絞り込むところまでです。&lt;/p&gt;

&lt;h2&gt;
  
  
  人に見てもらう前提を織り込む
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxgkg9genh2rmuj0g9g3x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxgkg9genh2rmuj0g9g3x.png" alt="サービス" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;実際のところ、この整理をした先で判断が要ります。&lt;/p&gt;

&lt;p&gt;弁護士が中立の立場で見積書・契約書・請求書を確認するサービスがあり、&lt;strong&gt;葬儀後の請求確認&lt;/strong&gt;として、見積書と請求書の差額や追加請求の内容を検証する、という範囲が示されていました。&lt;strong&gt;葬儀が終わった後でも相談できる&lt;/strong&gt;とのことです。相談は何度でも無料、24時間受付、匿名相談可とも書かれていました。&lt;/p&gt;

&lt;p&gt;葬儀社から紹介料・広告料等の金銭を一切受け取らず、葬儀社の紹介・斡旋は行わない、という記載もあります。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ツールが出すのは材料まで&lt;/strong&gt;、というのは、設計上も割り切っておいたほうがいい線引きだと思います。&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;差分を&lt;strong&gt;単一の数値で判定しない&lt;/strong&gt;。費目の種類で扱いを分ける&lt;/li&gt;
&lt;li&gt;変動費目は&lt;strong&gt;単価と数量に分解&lt;/strong&gt;し、どちらが動いたかを返す&lt;/li&gt;
&lt;li&gt;分解できないものは &lt;code&gt;not_comparable&lt;/code&gt;。&lt;strong&gt;「差なし」に倒さない&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;片側にしかない行は、異常ではなく&lt;strong&gt;確認対象&lt;/strong&gt;として出す&lt;/li&gt;
&lt;li&gt;閾値を総額に対して置かない。&lt;strong&gt;25%前後は動きうる&lt;/strong&gt;前提で考える&lt;/li&gt;
&lt;li&gt;傾き(1人あたり・1日あたり)を&lt;strong&gt;事前に記録&lt;/strong&gt;しておく&lt;/li&gt;
&lt;li&gt;前提条件が違う見積は&lt;strong&gt;並べて表示しない&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;書式も条件も発行元によって変わります。実装に取り込むなら、対象の書面の様式を確かめたうえで行ってください。&lt;/p&gt;

&lt;p&gt;本ページはプロモーションが含まれています&lt;/p&gt;

</description>
      <category>pressrelease</category>
      <category>seo</category>
      <category>japan</category>
    </item>
    <item>
      <title>推奨環境の外に落ちた端末に、何を見せるか</title>
      <dc:creator>Hajime</dc:creator>
      <pubDate>Tue, 15 Sep 2026 05:09:23 +0000</pubDate>
      <link>https://dev.to/marketer2234/tui-jiang-huan-jing-nowai-niluo-titaduan-mo-ni-he-wojian-seruka-363h</link>
      <guid>https://dev.to/marketer2234/tui-jiang-huan-jing-nowai-niluo-titaduan-mo-ni-he-wojian-seruka-363h</guid>
      <description>&lt;h1&gt;
  
  
  推奨環境の外に落ちた端末に、何を見せるか
&lt;/h1&gt;

&lt;p&gt;ブラウザで動く AR を使った施策を設計するとき、いちばん手薄になるのが&lt;strong&gt;推奨環境を外れた端末の扱い&lt;/strong&gt;です。&lt;/p&gt;

&lt;p&gt;動くケースの設計は誰でもやります。動かないケースは「動きません」で済ませがちで、その結果、利用者の画面には何も起きないか、真っ黒な画面が出ます。&lt;strong&gt;何も起きないのがいちばん悪い&lt;/strong&gt;というのが、この記事の主張です。&lt;/p&gt;

&lt;p&gt;題材として、ある WebAR グッズ制作サービスが公開している動作条件を使います(2026年9月9日時点)。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxucjthopurq70yjry2k8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxucjthopurq70yjry2k8.png" alt="AR Goods Makers" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://ar-goods-makers.com/" rel="noopener noreferrer"&gt;ブラウザで動くARグッズを作れる AR Goods Makers&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  条件は2つある
&lt;/h2&gt;

&lt;p&gt;公開されている条件を、そのまま書き出します。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. 端末が2台必要&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AR体験には、&lt;strong&gt;ARを起動するスマートフォン&lt;/strong&gt;と、&lt;strong&gt;体験用画像を表示する別の端末&lt;/strong&gt;が必要です。組み合わせは PC と1台、あるいは2台のスマートフォン。&lt;strong&gt;1台きりでは成立しません。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. 推奨環境&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS は18以降でSafari、Android は13以降でChrome&lt;/strong&gt;が推奨とされ、そこから外れた環境は動作保証もサポートも対象外です。&lt;/p&gt;

&lt;p&gt;設計者として重要なのは、&lt;strong&gt;この2つが独立した条件だ&lt;/strong&gt;という点です。推奨環境を満たしていても端末が1台なら体験できないし、端末が2台あっても古い OS なら動きません。よくある実装は、この2つを1つの &lt;code&gt;isSupported&lt;/code&gt; に潰してしまいます。&lt;/p&gt;

&lt;h2&gt;
  
  
  失敗の種類を分ける
&lt;/h2&gt;

&lt;p&gt;判定を1つのブール値にすると、利用者に出せるメッセージも1種類になります。「お使いの環境では利用できません」。これでは利用者は次の行動を取れません。&lt;/p&gt;

&lt;p&gt;分けます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;diagnose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;detectOs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                    &lt;span class="c1"&gt;// { name: 'ios'|'android'|'other', major: number }&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;     &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;major&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;os_too_old&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;android&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;major&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;os_too_old&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;other&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;os_unsupported&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isTargetBrowser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browser_mismatch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasCamera&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no_camera&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cameraPermission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;permission_denied&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;それぞれに、&lt;strong&gt;利用者が実際に取れる行動&lt;/strong&gt;を割り当てます。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;判定&lt;/th&gt;
&lt;th&gt;画面に出すもの&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;os_too_old&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;必要な OS のバージョンを明示する。&lt;strong&gt;「対応していません」で止めない&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;browser_mismatch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;標準ブラウザで開き直す導線。URL をコピーできるようにする&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;permission_denied&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;許可の出し直し方を、OS ごとの手順で示す&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_camera&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;代替コンテンツへ&lt;/strong&gt;(後述)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;os_unsupported&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;代替コンテンツへ&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;browser_mismatch&lt;/code&gt; は実際によく踏みます。QR コードを読み取ったアプリの内蔵ブラウザで開かれる場合があるためです。ここで「動きません」と出すのは、&lt;strong&gt;あと一手で成功できる利用者を取り逃がしている&lt;/strong&gt;ことになります。&lt;/p&gt;

&lt;h2&gt;
  
  
  端末が2台という条件は、検出できない
&lt;/h2&gt;

&lt;p&gt;やっかいなのが1つ目の条件です。&lt;strong&gt;「利用者がもう1台端末を持っているか」は、ブラウザからは分かりません。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;検出できない条件を設計に組み込むには、前提のほうを動かすしかありません。選択肢は3つです。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;こちらで表示用の端末を用意する&lt;/strong&gt; — 会場やイベントで配る場合。据え置きの画面を1つ置けば、利用者は自分のスマートフォン1台で済む&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;案内に先出しする&lt;/strong&gt; — 持ち帰ってもらう場合。配布物の紙面や案内ページに、必要な環境を書いておく&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;代替を用意する&lt;/strong&gt; — AR を使わない形でも中身が伝わるようにする&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;実務では、&lt;strong&gt;QR の遷移先を分岐させる&lt;/strong&gt;のがいちばん効きました。遷移先の1枚目を「体験の入り口」にして、そこで条件を説明し、2台ある人だけが次へ進む。いきなり AR を起動しません。&lt;/p&gt;

&lt;h2&gt;
  
  
  代替コンテンツは「劣化版」にしない
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;no_camera&lt;/code&gt; や &lt;code&gt;os_unsupported&lt;/code&gt; に落ちた利用者へ、何を出すか。&lt;/p&gt;

&lt;p&gt;ここでよく作られるのが「AR の代わりに静止画」という劣化版です。体験の価値が落ちるので、利用者は損をした気分になります。&lt;/p&gt;

&lt;p&gt;このサービスには、AR 体験とは別に&lt;strong&gt;デジタルコンテンツの配信&lt;/strong&gt;という機能がありました。特典画像・PDF・動画・音声を届けられるもので、&lt;strong&gt;画像と PDF はダウンロードでき、動画と音声はストリーミングが基本、回数制限付きダウンロードにも対応&lt;/strong&gt;するとされています。&lt;/p&gt;

&lt;p&gt;つまり、&lt;strong&gt;AR とは独立した配信経路がある&lt;/strong&gt;ということです。設計としてはこう置けます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QR → 入口ページ
        ├─ 条件を満たす → AR 体験
        └─ 満たさない   → デジタルコンテンツの配信（AR の代替ではなく、それ自体が特典）
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;代替を「AR のかわり」と説明しないこと&lt;/strong&gt;が肝心です。最初から2つの受け取り方があるという見せ方にすれば、どちらに落ちても利用者の体験は完結します。&lt;/p&gt;

&lt;h2&gt;
  
  
  容量の制約を先に見る
&lt;/h2&gt;

&lt;p&gt;代替経路を用意すると、配信するファイルが増えます。ここで容量の上限に当たります。&lt;/p&gt;

&lt;p&gt;配信データの枠は&lt;strong&gt;基本3GB&lt;/strong&gt;で、そこを超えると&lt;strong&gt;1GBあたり2,000円の初回費用&lt;/strong&gt;が乗ります。&lt;/p&gt;

&lt;p&gt;AR 用のアセットと配信用の特典が同じ枠を食うなら、&lt;strong&gt;枠の割り当てを先に決めてからエンコードの設定を決める&lt;/strong&gt;ことになります。逆順にすると、必ず超えます。&lt;/p&gt;

&lt;h2&gt;
  
  
  権利の処理も分岐に影響する
&lt;/h2&gt;

&lt;p&gt;音を使う場合、&lt;strong&gt;JASRAC／NexTone の管理楽曲は、商品税抜販売価格 × 数量 × 1.5%&lt;/strong&gt; の費用が発生します。&lt;/p&gt;

&lt;p&gt;計算式に販売価格と数量が入っているため、&lt;strong&gt;AR 体験と代替コンテンツで同じ曲を使うかどうか&lt;/strong&gt;が金額の話になります。設計段階で決める項目に入れておきます。&lt;/p&gt;

&lt;h2&gt;
  
  
  計測しないと直せない
&lt;/h2&gt;

&lt;p&gt;最後に、判定結果をログに残す話です。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;diagnose()&lt;/code&gt; の戻り値を、そのままイベントとして送っておきます。すると運用が始まってから、&lt;strong&gt;どの理由で何%が落ちているか&lt;/strong&gt;が分かります。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;browser_mismatch&lt;/code&gt; が多い → 入口ページの案内を直す&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;os_too_old&lt;/code&gt; が多い → 推奨環境の告知を配布物の紙面に入れる&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;permission_denied&lt;/code&gt; が多い → 許可を求める前の説明文を直す&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;「動かなかった」を1つのカウンタで数えていると、どれも直せません。&lt;/strong&gt;理由ごとに数えていれば、次に打つ手が決まります。&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;動作条件は&lt;strong&gt;独立した複数の条件&lt;/strong&gt;。1つのブール値に潰さない&lt;/li&gt;
&lt;li&gt;失敗の理由ごとに、&lt;strong&gt;利用者が取れる行動&lt;/strong&gt;を出す&lt;/li&gt;
&lt;li&gt;検出できない条件(端末の台数)は、前提のほうを動かして解決する&lt;/li&gt;
&lt;li&gt;代替は劣化版にしない。&lt;strong&gt;独立した受け取り方&lt;/strong&gt;として設計する&lt;/li&gt;
&lt;li&gt;容量と権利は、分岐を増やすと効いてくる。先に枠を決める&lt;/li&gt;
&lt;li&gt;判定結果を理由別に計測する&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;動作条件は更新されることがあります。実装に入る前に、最新の記載を見てください。&lt;/p&gt;

&lt;p&gt;本ページはプロモーションが含まれています&lt;/p&gt;

</description>
      <category>pressrelease</category>
      <category>seo</category>
      <category>japan</category>
    </item>
    <item>
      <title>締切・予選・本戦を同じカレンダーに入れると、締切が消える</title>
      <dc:creator>Hajime</dc:creator>
      <pubDate>Mon, 14 Sep 2026 09:13:20 +0000</pubDate>
      <link>https://dev.to/marketer2234/di-qie-yu-xuan-ben-zhan-wotong-zikarendaniru-reruto-di-qie-gaxiao-eru-4njk</link>
      <guid>https://dev.to/marketer2234/di-qie-yu-xuan-ben-zhan-wotong-zikarendaniru-reruto-di-qie-gaxiao-eru-4njk</guid>
      <description>&lt;h1&gt;
  
  
  締切・予選・本戦を同じカレンダーに入れると、締切が消える
&lt;/h1&gt;

&lt;p&gt;大会の予定をカレンダーに入れる仕組みを作ると、ほぼ必ず同じ不具合が出ます。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;応募の締切が、一覧から消えます。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;理由は単純で、「今月の予定」を出すときに開催日で絞り込むからです。本戦が12月にあるイベントは12月の欄に入り、9月に閉まる応募期限は、9月の一覧のどこにも出てきません。&lt;/p&gt;

&lt;p&gt;実際のデータで確かめてみます(2026年9月9日に確認した内容)。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcmc3ce178akoa4w54ykc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcmc3ce178akoa4w54ykc.png" alt="e-Sports TODAY" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://e-sports-today.com" rel="noopener noreferrer"&gt;大会の日程と応募期限を確認するなら e-Sports TODAY&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  問題が起きる実例
&lt;/h2&gt;

&lt;p&gt;こういう予定がありました。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JeSU「全国都道府県対抗eスポーツ選手権 2026 オンライン - Road to the NEXT -」は &lt;strong&gt;12月12日・13日&lt;/strong&gt;。ただし&lt;strong&gt;グランツーリスモ7部門の予選は9月6日まで&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;全日本高校eスポーツ選手権は、&lt;strong&gt;エントリーが9月10日18時まで&lt;/strong&gt;で、&lt;strong&gt;予選は9月26日開始&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上のイベントは、開催日だけを持つと12月の予定になります。&lt;strong&gt;しかし、9月6日を過ぎたら出られません。&lt;/strong&gt;9月上旬の画面に何も出ないまま、機会が閉じます。&lt;/p&gt;

&lt;p&gt;下のイベントも同様で、エントリーの締切と予選の開始が別の日にあります。しかも締切には&lt;strong&gt;時刻まで&lt;/strong&gt;付いています。18時。&lt;/p&gt;

&lt;h2&gt;
  
  
  1つのテーブルに入れない
&lt;/h2&gt;

&lt;p&gt;素直にやると、こうなります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-12-12"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ここに締切を足そうとして、フィールドを1つ増やす方向へ行くと破綻します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-12-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"deadline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-06"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;←&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;部門ごとに違う&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;先ほどの例では、締切は&lt;strong&gt;グランツーリスモ7部門のもの&lt;/strong&gt;でした。イベント全体の締切ではありません。1イベント1締切を前提にした時点で、表現できない現実が出てきます。&lt;/p&gt;

&lt;p&gt;分けます。&lt;strong&gt;イベントと、そこに紐づく期限を別の実体にする。&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jesu-kokutai-2026"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"全国都道府県対抗eスポーツ選手権 2026 オンライン - Road to the NEXT -"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"phases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"final"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-12-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-12-13"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"entry_close"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"グランツーリスモ7部門"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"precision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"day"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;phases&lt;/code&gt; — &lt;strong&gt;期間を持つもの&lt;/strong&gt;。開催・予選・本戦&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gates&lt;/code&gt; — &lt;strong&gt;時点で閉まるもの&lt;/strong&gt;。エントリー期限、申込期限&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;この2つは、カレンダー上での扱いがまったく違います。期間は「その間ずっと」ですが、時点は「そこを過ぎたら終わり」です。同じ配列に入れると、必ずどちらかの表示が壊れます。&lt;/p&gt;

&lt;h2&gt;
  
  
  precision を持たせる
&lt;/h2&gt;

&lt;p&gt;締切に時刻が付く場合と付かない場合があります。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;グランツーリスモ7部門の予選: &lt;strong&gt;9月6日まで&lt;/strong&gt;(日付のみ)&lt;/li&gt;
&lt;li&gt;全日本高校eスポーツ選手権のエントリー: &lt;strong&gt;9月10日18時まで&lt;/strong&gt;(時刻あり)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ここで全部を &lt;code&gt;2026-09-06T23:59:59&lt;/code&gt; のように正規化すると、&lt;strong&gt;書かれていない情報を足したことになります。&lt;/strong&gt;「まで」が当日いっぱいなのか、営業時間内なのかは、発表されていない限り分かりません。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;precision: "day" | "minute"&lt;/code&gt; を持たせて、表示のときに切り替えます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;precision&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;minute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;fmtDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;fmtTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;まで`&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;fmtDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;まで`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;推測して埋めた時刻を、確定した時刻の顔で表示しない。&lt;/strong&gt;これが要点です。&lt;/p&gt;

&lt;h2&gt;
  
  
  一覧の絞り込みを2本立てにする
&lt;/h2&gt;

&lt;p&gt;データを分けたら、取り出し方も分けます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 期間もの: その月に「かかっている」ものを拾う（またぐイベントを落とさない）&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;phasesIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;overlaps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 時点もの: その月に「閉まる」ものを拾う&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gatesIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;inMonth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;期間ものに &lt;code&gt;overlaps&lt;/code&gt; を使うのが大事で、開始日だけで絞ると&lt;strong&gt;またぐイベントが消えます。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;たとえば、第20回アジア競技大会(2026/愛知・名古屋)のeスポーツ競技は&lt;strong&gt;9月23日から10日間&lt;/strong&gt;です。開始日で絞ると10月の一覧に出ません。東京ゲームショウ2026 の&lt;strong&gt;9月17日〜21日&lt;/strong&gt;のような5日間のものも同じ理屈で、月をまたぐ配置なら消えます。&lt;/p&gt;

&lt;h2&gt;
  
  
  締切のほうを先に出す
&lt;/h2&gt;

&lt;p&gt;表示の順序も設計の一部です。&lt;/p&gt;

&lt;p&gt;9月の画面を作るなら、こうします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;■ 今月閉まるもの
  09/06  グランツーリスモ7部門 予選エントリー（全国都道府県対抗 2026）
  09/10 18:00  全日本高校eスポーツ選手権 エントリー

■ 今月ひらくもの
  09/13        ALGS Year 6 Split 2 APAC North リージョナルファイナル
  09/17〜09/21 東京ゲームショウ2026（幕張メッセ）
  09/23〜      第20回アジア競技大会 eスポーツ競技（Aichi Sky Expo 展示ホールD）
  09/24〜      VALORANT Champions 2026（上海）
  09/26        全日本高校eスポーツ選手権 予選開始
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;閉まるものを上に置く。&lt;/strong&gt;開くものは見逃しても次があることが多いですが、閉まるものは一度きりです。&lt;/p&gt;

&lt;h2&gt;
  
  
  種目とタイトルが1対1ではない
&lt;/h2&gt;

&lt;p&gt;もう1つ、&lt;code&gt;phases&lt;/code&gt; の中でつまずく点があります。&lt;/p&gt;

&lt;p&gt;アジア競技大会のeスポーツ競技は、&lt;strong&gt;10日間で11種目13タイトル&lt;/strong&gt;と記載されていました。種目数とタイトル数が一致していません。&lt;/p&gt;

&lt;p&gt;つまり、&lt;code&gt;phases&lt;/code&gt; の1件に対して、実施される中身が複数あります。開催期間だけを持って「1イベント」として扱うか、種目単位まで割るかは、何を見せたいかで決めます。&lt;strong&gt;一覧に出すだけなら前者で足りますが、「この日に何があるか」を出すなら後者が要ります。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;最初から後者で作ろうとすると、データが集まらずに詰まります。&lt;strong&gt;先に前者で動かして、必要になったら割る&lt;/strong&gt;ほうが現実的でした。&lt;/p&gt;

&lt;h2&gt;
  
  
  出場枠の数は、日程ではない
&lt;/h2&gt;

&lt;p&gt;ついでに注意点を1つ。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ALGS の APAC North リージョナルファイナルは&lt;strong&gt;上位20チーム&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;VALORANT Champions 2026 は&lt;strong&gt;出場16チーム&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;こういう数字を &lt;code&gt;phases&lt;/code&gt; に混ぜたくなりますが、&lt;strong&gt;枠の数は日程の属性ではありません。&lt;/strong&gt;別のフィールドに置きます。混ぜると、日程を更新するたびに枠の数まで書き換えることになります。&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;期間もの(&lt;code&gt;phases&lt;/code&gt;)と時点もの(&lt;code&gt;gates&lt;/code&gt;)を分ける。&lt;/strong&gt;同じ配列に入れない&lt;/li&gt;
&lt;li&gt;締切は&lt;strong&gt;部門ごとに違いうる&lt;/strong&gt;。1イベント1締切にしない&lt;/li&gt;
&lt;li&gt;時刻の有無を &lt;code&gt;precision&lt;/code&gt; で持つ。&lt;strong&gt;書かれていない時刻を補わない&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;期間の絞り込みは &lt;code&gt;overlaps&lt;/code&gt;。開始日だけで絞ると&lt;strong&gt;またぐイベントが消える&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;表示は&lt;strong&gt;閉まるものを先に&lt;/strong&gt;出す&lt;/li&gt;
&lt;li&gt;枠の数など、日程以外の属性を &lt;code&gt;phases&lt;/code&gt; に混ぜない&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;日程は変更されることがあります。取り込むなら取得日を添えて持ってください。&lt;/p&gt;

&lt;p&gt;本ページはプロモーションが含まれています&lt;/p&gt;

</description>
      <category>pressrelease</category>
      <category>seo</category>
      <category>japan</category>
    </item>
    <item>
      <title>特典と引き換えに受け取ったメールアドレスを、あとから止められる形にしておく</title>
      <dc:creator>Hajime</dc:creator>
      <pubDate>Sun, 13 Sep 2026 08:13:28 +0000</pubDate>
      <link>https://dev.to/marketer2234/te-dian-toyin-kihuan-enishou-kequ-tutameruadoresuwo-atokarazhi-merareruxing-nisiteoku-155a</link>
      <guid>https://dev.to/marketer2234/te-dian-toyin-kihuan-enishou-kequ-tutameruadoresuwo-atokarazhi-merareruxing-nisiteoku-155a</guid>
      <description>&lt;h1&gt;
  
  
  特典と引き換えに受け取ったメールアドレスを、あとから止められる形にしておく
&lt;/h1&gt;

&lt;p&gt;配布物から個人情報を受け取る導線は、作るのは簡単で、&lt;strong&gt;やめるのが難しい&lt;/strong&gt;という性質があります。&lt;/p&gt;

&lt;p&gt;紙に印刷したQRコードは回収できません。入口を閉じても、あとから読み取る人は出てきます。受け取ったアドレスは、送るのをやめても手元に残ります。&lt;strong&gt;始める前に、やめ方を決めておく&lt;/strong&gt;話です。&lt;/p&gt;

&lt;p&gt;題材として、AR対応の販促カードが公開している仕様を使います(2026年9月9日時点)。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frmdhq64dn5zlckc1qo5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frmdhq64dn5zlckc1qo5r.png" alt="デジスタカード" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://digi-sta.link/" rel="noopener noreferrer"&gt;認証付きのAR対応カードを検討するなら デジスタカード&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  どこで個人情報が入ってくるか
&lt;/h2&gt;

&lt;p&gt;カードのプランは3つあり、そのうち&lt;strong&gt;認証付き&lt;/strong&gt;のものが該当します。&lt;/p&gt;

&lt;p&gt;シリアルコードやログイン認証で対象者だけに中身を公開でき、&lt;strong&gt;認証でのメール収集に対応&lt;/strong&gt;すると記載されています。&lt;/p&gt;

&lt;p&gt;流れとしてはこうなります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;カード（QR）→ AR体験 → 認証（シリアル or ログイン）→ 特典の公開
                                   └─ ここでメールアドレスを受け取る
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;設計上の要点は、&lt;strong&gt;認証と収集が同じ画面に同居している&lt;/strong&gt;ことです。利用者から見れば「特典を見るための手続き」であって、「メールアドレスを渡す手続き」という自覚は薄い。ここを混ぜたまま作ると、後から説明できなくなります。&lt;/p&gt;

&lt;h2&gt;
  
  
  目的ごとに分けて受け取る
&lt;/h2&gt;

&lt;p&gt;最低限、&lt;strong&gt;認証のために必要な入力&lt;/strong&gt;と、&lt;strong&gt;継続的な連絡のための同意&lt;/strong&gt;を分けます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"serial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"XXXX-XXXX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"purposes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"unlock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"granted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-09T12:00:00+09:00"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"newsletter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"granted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"campaign"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"card-2026-autumn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"plan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auth"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;unlock&lt;/code&gt; は、特典を開くために必要な処理&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;newsletter&lt;/code&gt; は、それとは独立した同意&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;既定値を &lt;code&gt;false&lt;/code&gt; にしておく&lt;/strong&gt;こと。そして &lt;code&gt;at&lt;/code&gt; を必ず持つこと。「いつ同意を得たか」が残っていないと、後から確認できません。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;source.campaign&lt;/code&gt; を入れておくのも効きます。どの配布物から入ってきたアドレスかが分かると、&lt;strong&gt;その配布物ぶんだけを止める&lt;/strong&gt;ということができます。全部まとめて止めるか続けるか、の二択にしないためです。&lt;/p&gt;

&lt;h2&gt;
  
  
  「やめる」を3段階で用意する
&lt;/h2&gt;

&lt;p&gt;撤退の粒度を分けておきます。実際に必要になる場面が違います。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;段階&lt;/th&gt;
&lt;th&gt;何を止めるか&lt;/th&gt;
&lt;th&gt;いつ使うか&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;配信停止&lt;/td&gt;
&lt;td&gt;そのアドレスへの送信&lt;/td&gt;
&lt;td&gt;利用者からの申し出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;導線停止&lt;/td&gt;
&lt;td&gt;新規の受け付け&lt;/td&gt;
&lt;td&gt;キャンペーン終了時&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;削除&lt;/td&gt;
&lt;td&gt;保持しているデータ&lt;/td&gt;
&lt;td&gt;保存期間の満了、利用者からの請求&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;多いのは、&lt;strong&gt;配信停止だけ実装して残り2つを忘れる&lt;/strong&gt;パターンです。&lt;/p&gt;

&lt;p&gt;とくに2番目が抜けやすい。キャンペーンが終わっても、&lt;strong&gt;カードは配られたまま世の中にあります。&lt;/strong&gt;QRを読み取った人が入力欄にたどり着いて、どこにも届かないアドレスを入力する、ということが起こります。&lt;/p&gt;

&lt;p&gt;入口を閉じるときは、&lt;strong&gt;閉じたことが分かる画面&lt;/strong&gt;を出します。入力欄を残したまま送信だけ捨てるのは最悪の形です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;campaign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;closed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;renderClosed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;このキャンペーンの受け付けは終了しました。&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// 特典そのものは見られる形にできるなら残す&lt;/span&gt;
    &lt;span class="na"&gt;contentStillAvailable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;campaign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentPublic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  期限は印刷物の寿命より長く見積もる
&lt;/h2&gt;

&lt;p&gt;カードは財布に入ります。数年後に出てくることがあります。&lt;/p&gt;

&lt;p&gt;ですから、&lt;strong&gt;キャンペーンの終了日 = 導線の終了日にしない&lt;/strong&gt;ほうがいい。終了後も「終了しました」と表示するページは生かしておきます。QRの遷移先が404になるのは、体験としても、信頼の面でも悪い。&lt;/p&gt;

&lt;p&gt;保持期間は逆に、&lt;strong&gt;必要な期間で切る&lt;/strong&gt;。両者は別のタイマーです。同じ設定値で動かさないようにします。&lt;/p&gt;

&lt;h2&gt;
  
  
  認証方式で、集まるものが変わる
&lt;/h2&gt;

&lt;p&gt;認証には&lt;strong&gt;シリアルコード&lt;/strong&gt;と&lt;strong&gt;ログイン認証&lt;/strong&gt;の2通りが挙げられていました。性質が違います。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;シリアルコード&lt;/strong&gt; — カードに印刷された値。&lt;strong&gt;刷った時点で確定し、後から変えられない&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ログイン認証&lt;/strong&gt; — 利用者のアカウントに紐づく。カード側は入口だけを持つ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;シリアルは、印刷物の性質をそのまま引き受けます。発行数は刷った枚数で固定され、流出しても差し替えられません。&lt;strong&gt;1枚1コードにするのか、全枚数で共通コードにするのかを、入稿前に決める必要があります。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;共通コードは運用が楽ですが、1人に配ったつもりが共有されます。個別コードは管理が要りますが、&lt;strong&gt;誰が開いたかが分かるので、配信停止の対象を特定できます。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;撤退可能性の観点では、個別コードのほうが扱いやすい。ただし、&lt;strong&gt;刷り直しはできない&lt;/strong&gt;という前提は変わりません。&lt;/p&gt;

&lt;h2&gt;
  
  
  LINE連携を足す場合、止める対象が増える
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6080wrtvxd1bnoyxptrq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6080wrtvxd1bnoyxptrq.png" alt="LINE連携" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;オプションとして、AR体験を入口に &lt;strong&gt;LIFFブラウザ経由でLINEミニアプリを起動&lt;/strong&gt;する構成や、&lt;strong&gt;公式アカウントの友だち追加へ誘導する導線&lt;/strong&gt;を設計できる、という機能が用意されていました。その先にスタンプ配布やキャンペーンを重ねる使い方も挙がっています。&lt;/p&gt;

&lt;p&gt;導入には企業側の準備が要ります。&lt;strong&gt;LINE公式アカウントの開設&lt;/strong&gt;、&lt;strong&gt;LINE Developers での設定(プロバイダー / LIFFアプリ)&lt;/strong&gt;、アカウントの管理・設定対応。LINEの仕様により、構成や運用方法に応じて設計の調整が必要になる場合がある、とも書かれていました。&lt;/p&gt;

&lt;p&gt;ここで効いてくるのが、FAQ にあったこの記述です。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;プロバイダーへの公式アカウントの紐付けは1回限りで、あとから差し替えられません。&lt;/strong&gt;自社環境で構築して紐付けるのが最も安全なフローとして案内されています。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;これは撤退可能性が最も低い項目です。&lt;/strong&gt;アドレスは消せますが、紐付けは戻せません。着手の順序としては、ここを最初に決めます。&lt;/p&gt;

&lt;p&gt;なお、利用者が新しくアプリを入れる必要はなく、LINEアプリの上でミニアプリとして起動する、ともありました。利用者側の負担は増えません。増えるのは&lt;strong&gt;運用する側が止められないものの数&lt;/strong&gt;です。&lt;/p&gt;

&lt;h2&gt;
  
  
  期間
&lt;/h2&gt;

&lt;p&gt;カード自体の製造は、仕様確定後&lt;strong&gt;通常1〜2週間程度&lt;/strong&gt;。LINE連携の組み込みやスタンプの申請(LINE側の審査期間を含む)は、アカウントの準備状況で変動する、とされています。&lt;/p&gt;

&lt;p&gt;実装側から見ると、&lt;strong&gt;製造期間のほうが読める&lt;/strong&gt;ということになります。詰まるとしたらアカウント周りです。&lt;/p&gt;

&lt;h2&gt;
  
  
  チェックリスト
&lt;/h2&gt;

&lt;p&gt;着手前に決めておく項目を並べます。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] 認証のための入力と、継続連絡の同意を&lt;strong&gt;別のフラグ&lt;/strong&gt;で持つ&lt;/li&gt;
&lt;li&gt;[ ] 同意の&lt;strong&gt;取得日時&lt;/strong&gt;を保存する&lt;/li&gt;
&lt;li&gt;[ ] 配布物を識別する値(&lt;code&gt;campaign&lt;/code&gt;)を、受け取るデータに含める&lt;/li&gt;
&lt;li&gt;[ ] 配信停止・導線停止・削除を&lt;strong&gt;別々の操作&lt;/strong&gt;として実装する&lt;/li&gt;
&lt;li&gt;[ ] 終了後に表示するページを用意する。&lt;strong&gt;404にしない&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;[ ] シリアルを個別にするか共通にするかを、入稿前に決める&lt;/li&gt;
&lt;li&gt;[ ] 保持期間のタイマーと、導線の公開期間のタイマーを分ける&lt;/li&gt;
&lt;li&gt;[ ] LINEのプロバイダー紐付けを、&lt;strong&gt;着手の最初に&lt;/strong&gt;決める&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;仕様は更新されることがあります。設計に入る前に、そのときの記載を確認してください。&lt;/p&gt;

&lt;p&gt;本ページはプロモーションが含まれています&lt;/p&gt;

</description>
      <category>pressrelease</category>
      <category>seo</category>
      <category>japan</category>
    </item>
  </channel>
</rss>
