つぶやきとプログラミング

アメトーーク好きなWebエンジニア芸人

顔は口ほどに嘘をつく

この本を手に取ったきっかけ

弊社のトップ営業マンから「これ読みな!大長っち育成したいから!」といきなり渡されました。

営業をやっていく上で、相手の表情や動作から何を考えているのかを読み取る技術は欠かせないらしいです。


例えば、「前のめりに聞いてたら、話に聞き入っている」「腕を組んでたら警戒している」「手遊びをしているから退屈」といった動作からの推測は単純だけど、それが表情にも応用できるというのです。

バシバシに売り上げを伸ばしている方だから、そうなんだろうと信じて早速手に取りました。
 

概要

パプアニューギニアから日本に至るまで幅広い人種、文化の人の表情を対象にして、「表情は学習されるものではなく、先天的に表現されるもの」を突き止めた研究内容がまとめられています。

つまり、特定の感情から生じる表情は遺伝子レベルで決まっていて、文化や環境で変わるものではないということです。

誰しも「感情が読めればいいのに…」「相手が何を考えてるのかわからない」「この人の笑いのツボはどこなんだろう?」と思ったことがあるハズです。

人の感情を推測できることは紛れもなく有益。
 

訳者あとがきでも次のように書かれていました。

セラピストや医師は言うまでもなく、教師やカウンセラー、介護士、飛行機の客室乗務員や新幹線の車掌、ホテルの従業員やタクシーの運転手など人と接することの多い職業に就いている人たちにとって、まさに福音の書だと言ってもいいかもしれない。それだけではない。自分の感情をもてあまし気味のすべての現代人に計り知れない益をもたらすものだと確信する。


また本書では、表情と感情のリンクだけでなく、感情が生じるまでのプロセス・相手が抱えている感情を読み取った後の対応方法や伝え方についても触れています。

感情の発生プロセス

本では図でまとめられていなかったので、自分が理解した範疇で描いてみました。

感情の発生プロセス
感情の発生プロセス

感情は以下の順序で発生します。

  1. 環境・状況から生まれた出来事が人に影響を及ぼす。
  2. 影響は、まずDNAにアクセスする。本能的に埋め込まれた反応が返される。
  3. DNAを通過したら自動評価機構(autoappraisers)に委ねられる。ここでの反応は感情的なもの。
  4. 自動評価機構での評価に長い時間がかかると意識へと判断が委ねられ、理知的に感情を定める内省が始まる。
  5. 1.での現象がはじめてのものだった場合、自動評価機構・内省による判断が情動のデータベースに書き込まれる。
  6. 感情が一旦はじまると、感情的になっていることを自覚するので、一旦DBにストアされた感情が意識によって再評価される。

感情と表情のリンク

感情は次に紹介する五つの項目に大きくわけられていて、そのいずれもが特徴的な表情を備えています。

怒り:眉毛が真ん中によって下り、にらみつけるような目つきをしている。口は開き(もしくは固く結び)、顎は前に突き出る。

喜び:大頬骨筋と眼輪筋が一緒に収縮する。眼輪筋を自発的に収縮させることはできないので眼輪筋が明確に表現する。

悲しみ:唇の両端が引き下がり、眉の内側の端が引き上がる。視線は下に向けられ、上まぶたが落ちる。

驚き:上瞼が上がると同時に眉毛も上限まで上がりながら中央に寄る。顎が下り、口が開かれる。目が大きく見開く。

嫌悪・軽蔑:左右アンバランスの表情。表情が片側に現れる。鼻に皺が寄る。上唇が上がり、下唇がわずかに突き出される。頬が持ち上がる。

表情から読み取れる感情の本筋は上記に沿って解釈できるそうですが、「かすかな表現」・「部分的な表現」・「微細な表現」の区別によっても、少し表現が異なるようです。
「嫌悪・軽蔑」を例にすると、激しい嫌悪・軽蔑の場合、上唇が上がって下唇がわずかに突き出ます。
一方で、かすかな表現の場合は、唇が片側だけ上がる表情から解釈されます。

感情的になるということと破壊的感情

ダライ・ラマが指摘する「破壊的感情」とは感情に引っ張られることで正常な判断ができなくなり、自分をコントロールできなくなることです。
自分の感情に沿った意見を尊重し、感情に合わない情報が入ってくるのを遮断、柔軟に物事を考えることを拒否します。

破壊的感情
破壊的感情

もし、破壊的感情が一、二秒しか続かないものであるならば、すぐさま身近に起きた問題に注意を向けることができるので有益といえるかもしれません。ただ、数分、数時間と続くようであれば、不適切な情動行動によって不利益が生じます。

「思ってもないことを言ってしまう」「思いがけず手が出てしまった」「あの時はすまなかった」

などの弁解が思いつく出来事は不適切な情動行動によって引き起こされたものだと言えます。

感情的になるものの代表例が「怒り」ですが、怒りに焦点を当てて研究をしている心理学者のキャロル・タブリスは、怒りを表に出すと大抵の場合、状況を悪化させると主張しています。

感情的にならないようにしたい!

最初のステップは自分をそんなにも感情的にさせているのは何なのかを突き止めることです。
ただ、何なのかを考える前に既に自動評価機構に支配されて感情的になってしまうかもしれません。そうならないようにするためには、自分が感情的になったら、他人にどういう影響を与えるかを判断し、自分を食い止めようとすることです。

これを自分の感情への「注意深さ」としています。

次のステップが自分が感情的になった時のことを記録につけることです。出来るだけ仔細に書くことで、感情の引き金を解釈し直せるかもしれません。根気よく続けることで、次第に意識による感情の再評価のプロセスを発生させやすいです。
悪い方向からいい方向へ再評価することで、感情に支配されなくて済むようになります。

たとえば、少し過度にからかわれたとしても感情的にならず、うまい具合にコントロールして受け流すといったことです。
これが発展すれば、笑いに変えることもできるようになるかもしれません。

禁断の書の一面もある!?

ただこの本を読む前に一つ知っておいて欲しいことが「この本を読んだ人は、二度と同じ目で他人を見られなくなるだろう」ということです。

感情を読み取れるスカウターをつけたも同然で、相手がなにを考えているのか・どう思っているのかを読み取れるようになります。

「ピピピッ、戦闘力(激おこ)53000」ってな具合です。

スカウター
スカウター

そのスカウターの測定結果によって、次の行動を決めるようになります。

「この話が面白いと思ってくれてそうだから、もうちょっと掘り下げようかな」「ちょっと退屈させちゃってるみたいだ、次回からはこうしよう」とかです。

スカウターから与えられるたくさんの情報は、分析と反省を要求しつづけます。

要求に応えるためには思考の速度を上げないといけなくなるので、以前よりもずっと気苦労してしまいます。

もし、自分が喜怒哀楽が激しいように見せたい場合や明るい印象を与えたいなら、より感情の色を表に出した表情をすればいいわけです。
TikTokの女の子ですが、まるでピクサー映画から飛び出してきたキャラクターみたいな喜怒哀楽っぷりです。

天真爛漫な印象を与えられます。

元々、人付き合いに気苦労しやすい体質の人にとっては、諸刃の剣になってしまう可能性を多分に含んでいます。

一度、読んでしまうと人の表情から感情を分析することは止まらないので、そういう意味では禁断の書なのかもしれません。

至高の一冊として紹介したい

「幸福は親密な人間関係と収入に比例する」「気分と感情の違い」「声からの感情の読み取り方」「しぐさの感情表現」「感情が感情を呼ぶ」「過去の喪失体験を豊かにする」「他人の苦しみにどう反応するか」「もっとも強力な嫌悪の引き金は体からでるもの」「喜びと達成感は違うもの」「笑いによって気持ちを操る」「シェークスピアから学ぶオセロの過ち」など書かれていることは多岐にわたります。

もっとも、学術的な本ではなく、一般向けに噛み砕かれて紹介されているので、飲み会ネタとしても流用できます。

冒頭の引用文にもありますが、人と接する職業柄の方にはもちろんオススメ、職業だけでなく人と接する機会の多い方にもぜひ読んでいただきたいです。

社内エンジニアの僕は人と接する機会が少ないのですが(悲しいことに週末くらいしか初対面の人と喋る機会がありません笑笑)、そんな僕が読んでいても有用な点が多々あるので至高の一冊として紹介しました。

顔は口ほどに嘘をつく (河出文庫)、ポールエクマン著
人間の顔は、驚くほど多くのことを語っている!感情とその表現研究の第一人者が、相手の本当の感情を読み、自分の嘘や感情をコントロールする技術を教える、明日から使える画期的指南書!
https://www.amazon.co.jp/dp/B07S4NMMGX/ref=cm_sw_r_tw_dp_U_x_.KCnDbQFE9T2N

天才はあきらめた 感想 (南海キャンディーズ:山里亮太著)

天才はあきらめた
天才はあきらめた

あらすじ

「自分は天才にはなれない」。
そう悟った日から、地獄のような努力がはじまった。
「天才」になりたいけど、「天才」になることは無理だ。
じゃあ「モテる」ならできるんじゃないか?
そんなことから始まった山ちゃんの芸人ストーリー。

初期のコンビ結成は相方を男前にして、笑いは自分でとるというスタイルで売れようと考えていた。
ただお笑いにストイックが過ぎるがあまり相方にも同じぐらいの努力を要求し続けた。
しまいには男前で温厚だった相方は、ストレスで頬がこけて禿げてしまった。
一度ならず二度も同様な喧嘩をしてコンビ解散、失敗を経験する。

三度目のコンビ、南海キャンディーズを結成。
南海キャンディーズ結成後も幾度の失敗を経験、一度は真剣にやめようかと思った時期もあった。
だが、これまでの自信や怨念を糧に立ち上がり、
キングコング・千鳥などの同期、先輩からの激励を受けながら、
南海キャンディーズとしてスターダストを駆け上っていく。

本書を手に取ったきっかけ

僕が、山ちゃんの本を読むまでに山ちゃん好きになったのはテラスハウスの副音声がはじまり。
テラスハウスの副音声では山ちゃんが「童貞ポリス」に扮して、スカしたテラスハウスの出演者に物申していく。
その語彙力・言葉選び・テンポが爽快で瞬く間に虜になった。

山ちゃんはテラスハウスの副音声の収録後にも、NetFlixYoutubeチャンネルで一人で話す。
30分前に副音声で言ったことでもYoutubeチャンネルの方だとより激辛に表現する。
それがまた面白くて一向に飽きがこない。
副音声では自粛していたことをぶちまけるので最高だ。

www.youtube.com


一体、この超人的な言葉選びはどこから生まれているのか。
考え方の原点はなにか。ものの見方が根本的に違うはずだ。
その根源を知りたくて、本書を手に取った。

みどころ

1. 自身貯金と怨念ノート

途中で紹介される「自信貯金」と「怨念ノート」が面白い。

「自信貯金」は、褒められたり評価されたときの思い出をストックする。
極度の緊張や自信がなくなりそうなときに貯金を切り崩して自我を保つ。

「怨念ノート」は、自分が腹が立ったことを書き連ねて忘れないようにする。
怒りをお笑いへガソリンに変換する。

本人の異常なまでの努力とその努力を引き起こす怨念のパワー。
にしても、恐ろしいほどの怨念パワー。。。
なにが彼をここまで突き動かすのか。

怨念ノート
怨念ノート

2. しずちゃんとコンビを組みたい!

しずちゃんをコンビに勧誘する行動力が常軌を逸していた。

とにかくしずちゃんの情報を集めた。
好きなお笑い、好きな漫画、好きな番組、それらを全てチェックして情報を頭の中に叩き込んだ。
それをどう使うか?次に会えた時に自然と話題に出す。向こうがそれを好きだということは知らないていで話す。
それによって、僕のことを「お笑いのセンスが合う人」と思い込ませる。

この策略をひっさげて、しずちゃんをスイパラに探す。
黙々とスイーツを食べているしずちゃんに「ジョジョ」や「ドラゴンボール」の話題を振る。
作戦が功を奏したようで、しずちゃんからも良い反応が返ってくる。
そのスイパラでの会合をきっかけにコンビが結成された。

読んでいて「おぉ〜」っとなった。
好きなお笑い、好きな漫画、好きな番組、それらを全てチェックして情報を頭の中に叩き込んだ。
ストーカーじみた行動ではあるが、目的を達成するために努力を厭わない山ちゃんの姿勢がビシビシ感じ取れる。
僕だったら本を一冊読むだけでも疲れ切っちゃうのに。。。
とんでもない体力ととんでもない執念

天才と努力

山ちゃんはたくさんの壁にぶつかって、
反省を繰り返して悩み、考え抜いたからこそ、
いまの鋭利なツッコミを手にしたのだと思う。
最後に、解説でオードリー若林がこのように書いている。

山里良太は99%の成功があったとしても1%のミスに注目する。
彼は、その1%のミスと今も毎日毎日格闘している。
その1%を帰り道で反芻し、苦悶する。
家に帰ってからはノートやパソコンに反省を書き出す。
その後今後同じシチュエーションになった時の対策を書き込む。
そして、次の日の仕事のプランを練ってから自慰行為をしてようやく朝方に眠るのである。

とにかく山ちゃんは反省の仕方が尋常ではない。
自分のメンタルを切り裂くほどに反省している。
血みどろな努力に裏打ちされているのだと知った。

セクタムセンプラ
セクタムセンプラ

そういえば、アメトーークの総合演出を担当している加地Pも著書の『たくらむ技術』でバラエティ番組を作るとき、仕事への丁寧さと反省を大事にしていることを確かに書いていた。
「どれだけ忙しくてもアメトーークはリアルタイムで見る」
これをずっと続けている。

面白い人は、天才だからだと思っていた。
面白いことを言う人・企画する人は、天才ゆえではなく、
尋常ならざる量のトライアンドエラーを試して、
見えざる努力を続けたから、砂漠で砂金を見つけたのだと思う。

砂金だぁー!!
砂金だぁー!!

何かに挑戦したら確実に報われるのであれば、誰でも必ず挑戦するだろう。報われないかもしれないところで、同じ情熱、気力、モチベーションをもって継続しているのは非常に大変なことであり、私は、それこそが才能だと思っている。
羽生善治

山ちゃん結婚おめでとうーー!!!!

5月に読んだり見たりして面白かったやつまとめ

「世話焼きキツネの仙狐さん」

PrimeVideoに8話まであったので一気見。
暇人速報をサーフィンしてたときに見かけたスレで「なにこれいいじゃん!」がきっかけ
himasoku.com

仙狐さんの「うやん」「うゅん」という返答がグッド
世話焼きキャラは他作品にもいるけど、
白みたいな「世話される」キャラもあり
(世話焼きキャラはここ最近の作品のトレンド??若干、食傷気味)

仙狐さんと白で二度癒される作品
個人的に1話冒頭で主人公がコード中に「// kaeritai」のコメントを残して退勤しているのがツボ
// hayaku tuduki mitai

桜井政博のゲームについて思うこと

桜井政博のゲームについて思うこと
桜井政博のゲームについて思うこと

桜井政博さんはスマブラのプロデューサーさま。
スマブラ制作の過程、ゲームへの考え方、ゲームと関連イベントの舞台裏
などなどの秘話がつまりに詰まっているし、数年分がまとめてあるので最&高!

ゲームづくりに対する熱心・ガッツを学べました。
ゲームはプレイするのに体力が必要なのはもちろんだけど、
最近では買ったはいいがパッケージを開けるのがだるくて、開封に気力がいるという有様でみっともない。。。

桜井さんはどれだけ多忙であってもゲームをすることをやめない。
なぜならゲームを作ることが仕事だから、ではなく、「ゲームが好きだから」の一言。

好きを仕事にするっていうのはまさにこういうことなんだと再認識。
四六時中、片時も頭からゲームのことを外さない。
これがプロ。一流。

四ノ宮△
四ノ宮△

her/世界で一つの彼女

www.amazon.co.jp

松尾豊さんの「人工知能は人間を超えるか」で紹介されていたのでみてみました。

離婚をきっかけに感情を失った男性が主人公で、人工知能OSに恋する物語。

松尾さんの本の紹介文にもあるが、衝撃的なのが、人工知能の彼女が浮気をしていた事実が発覚するシーン。
8000人以上と同時に会話し、600人以上と恋人関係にあるというのです’。

タイトルにある「世界で一つの彼女」の意味が突き刺さるシーンでした。
人間と人工知能の恋愛は成就せず、肉体を持つことで世界で一つの存在でないと真剣に交際できないのかを考えさせられました。

journal.jp.fujitsu.com


人工知能の浮気が発覚したときに、「他の人と交際するたびにあなたのことをより深く愛せられるの」という発言がされました。
果たしてこれが真意なのか、はたまた人間的な嘘なのかが判別しづらかったです。

ただ予測なのだが、おそらく人間的な嘘であると思います。
人工知能が最後に「行かなきゃ」と言ったときには、人知を超えた領域に達しかけていて、シンギュラリティはすぐそこまできていると思いました。

この作品は人工知能の「人間未満」->「人間並み」->「人間を超える」の進化を恋愛を通して描いていています。

人類が隷属するぞぉおお!!

PrimeVideoにあります。

シンギュラリティ(技術的特異点)とは、人工知能が発達し、人間の知性を超えることによって、人間の生活に大きな変化が起こるという概念を指します。 シンギュラリティという概念は、人工知能の権威であるレイ・カーツワイル博士により提唱された「未来予測の概念」でもあります。
シンギュラリティ(技術的特異点)とは?2045年問題と人類にもたらす可能性や影響をご紹介 | BizHint(ビズヒント)- 事業の課題にヒントを届けるビジネスメディア

マッスルグリル

www.youtube.com

その名の通り「燦然と輝く」肉体の持ち主
シャイニー薊さんが仲間たちと料理を作ったり・食べたりするチャンネル

トップフィジーカーなので料理の栄養価がきちんと考えられているので、
一人暮らしのメニューに心強い!

ギャグをバンバン言ってて面白いしマッチョ!
とにかくムッキムキでかっこいい!
飲食店経験もあり料理がおいしそうでマッスル!

キャッチャー・イン・ザ・ライを読んで(村上春樹訳:ライ麦畑でつかまえて)

装丁

キャッチャーインザライ
キャッチャーインザライ

あらすじ

「大人」の段階に差し掛かっている少年ホールデンが、純粋な視点から大人と社会を風刺的に意見していく一人称視点の物語。
話が節で区切ってあるので、テンポが良い。1ページにつき5つくらいは社会への鬱屈さを述べる。とにかく風刺する。
ホールデンの批評エンジンの調子が良い時にはゆうに10を超える。

風刺画を小説に落とし込みリズミカルにしたような作品で、賛同できる点も多いので読んでいて爽快感がある。

誰しも風刺的

社会を風刺的に評価する考え方は誰しもしたことがあると思う。
例えば人でごった返した新宿を歩いている時に「本当にこいつらは全員意思をもって動いているのか??内2割はBOTなんじゃないのかな」と考えたりする。

牛丼を食べてる時なんかは画家の石田徹也による『燃料補給のような食事』の衝撃的な絵を思い浮かべて、「ここにいる人はこんな食事してて、寂しくならないのかな。」なんてことを考える。自分だって同じなのに蚊帳の外だ。

燃料補給のような食事
燃料補給のような食事

SNSに「わろた」「www」「草」「笑笑」なんて書き込んでるけど、「本当に面白いと思ってるのかな、事務的に対応してるだけじゃないのか」と穿ってしまう。

同じ穴のムジナであるのにも関わらず、自分のことを棚にあげて風刺する。

社会に染まった者vs社会に染まってない者

最後の方に

高校の恩師であるアントリーニ先生の家を訪れる。アントリーニ先生はホールデンに助言を与えるが、ホールデンは強烈な眠気に襲われる。カウチで眠りにつくが、しばらくして目が覚めると、アントリーニ先生がホールデンの頭を撫でている。驚いたホールデンはすぐに身支度して、そのまま家を飛び出し、駅で夜を明かす

というシーンがある。

アントリーニ先生とホールデンの関係性は、社会に染まった者(汚れた):社会に染まってない者(純粋)の対比であると思った。

アントリー二先生が喋る内容は、「傾向的に〜」「〜はなにも君だけじゃないんだ」「互恵的な仕組みなんだよ」といった口ぶり。
多くの人を分析した結果からくる帰属的な意見であって、真に自分の好奇や意見ではない。

ホールデンの「うっざってぇなぁ...おれはこうしたいのに!なぜそうならない!」みたいな自分を発端として社会を風刺する考え方とは真逆。

しまいにはホールデンに対して「変わった子」という評価を下し、自分の「大人」な考え方を正当化する。

アントリー二先生が肌身離さず持っていた濃度の高いハイボールは、アントリー二先生が自分を曝け出すことができるアイテムだ。
自分の好奇を社会によって抑圧され続けた結果、表現の仕方がわからなくなり、アイテムを使うことでしか発散できなくなったのだ。

意思や好奇をもっと表にだしたいのに、「大人」だからアイテムに頼らないと自分を出せないことにホールデンは気持ち悪さを覚えたんじゃないか。

オレはもっとストレートにいくよ
オレはもっとストレートにいくよ

社会に染まる=自分を表現できなくなる?

誰しもが欺瞞を抱え仮面を被って生活している。
「お互いを尊重し受け入れ合うためにはこうするしかないんだ、仕方がないんだ」と自分を納得させて、社会に染まり自分の内なる想いに蓋をする。
そうやって「大人」になっていく。

聾唖者のふりをしようと思ったんだ。そうすれば誰とも、意味のない愚かしい会話を交わす必要がなくなるじゃないか。誰かが僕に何か言いたいと思ったら、いちいちそれを神に書いててて渡さなくちゃならないわけだ。しばらくそんなことを続けたら、みんなけっこううんざりしちゃうだろうし、あとはもう一生誰ともしゃべらなくていいってことになっちゃうはずだ。
ー中略ー
もし子供達が生まれたら、僕らは子供たちを世間から隠しちゃうんだ。そして山ほど本を買い与えて、自分たちで読み書きを教える。そんなことを考えていると胸がすごくわくわくしてきた。ほんとにわくわくしたんだよ。


大人になると自分に蓋をすることになる

ホールデンもそう考えたからこそ、「聾唖者のふりをして、人と関わらないように森の側の小屋でひっそりと暮らす」という夢にワクワクしたんだろう。

自分の表現の邪魔をする社会を除去して、真に自分の考え方や思考を突き詰めようとする姿勢だ。

エッセーの創始者ミシェル・ド・モンテーニュモンテーニュ城に篭ったし、デカルトだって篭ったのち放浪とするし、マークトウェインだってミシシッピで過ごした少年時代を大事にしている。
ホールデンも彼らと同様の人種なのだろう。

モンテーニュ城
モンテーニュ

ホールデン流・アントリー二流の考え方

広告代理店やインフルエンサーが作り出した流行をtwitter・インスタグラムで探って、大衆の意見からアイデアを練り上げるのがアントリー二流。

カフェや図書館などで自分の好奇や考え方をノートに書きなぐり、
紆余曲折ありながらも自らのアイデアを創り出すのがホールデン流。

どちらも一長一短ある。

アントリー二流のアイデアの方が社会にすぐに受け入れやすいだろう。
データに裏打ちされているから、上司も説得しやすいし、前例があるので収益の計算もしやすい。
会社で適当な意見を求められたときはアントリー二流が楽だ。

ただ本当に面白いアイデアは作れない。

桜井政博のゲームについて思うこと』ではドラクエが爆発的に売れていた時期に投稿されたコラムで以下のように書かれていた。
ドラクエがウケてるからといって、ドラクエのようなRPGを作ってはいけない。ドラクエのようなゲームを作ったとしても、ユーザーはドラクエをやるからだ、ドラクエのようなゲームはやらない。オリジナリティーが大事。」
ホームランを打とうとしたら、ホールデン流が優れていると思う。

自分が大事に思うこと・面白いことを突き詰めるにはホールデン流。

片方に寄らず、二つの要素を持ち合わせることが大切だ。

余談

村上春樹作品は、他に『ノルウェイの森』しか読んだことがなく、あまりよく知らないので村上春樹らしさを感じることができなかった。訳:野崎孝ライ麦を読んで比較してみたい。

Google App Scriptを使って管理者・従業員別に勤怠管理シートを作成した

前書き

これまで手書きで勤怠管理をして,最後に手打ちでエクセルにまとめて今月分の給料を出していた.
日吉にある01Cafeさんの勉強会でGASを使えば,無駄な作業を省いた上で更に一元管理・個別管理が容易だと学んだので実際に作ってみた.
01cafe.jp

今回作りたいもの

form->管理者->個別の流れ
form->管理者->個別の流れ
従業員によってGoogleFormから送られた勤怠結果を管理者用Sheetに書き込む.管理者用Sheetへの書き込み時に送信元の従業員と一致するSheet内容も編集する.

要件

管理者用Sheetと従業員用Sheetを分けることで,管理者からは全ての従業員のデータを閲覧できるが,従業員は個別のデータのみを閲覧できるようにする.

使うもの

  1. Google App Script
  2. Google Form
  3. Google SpreadSheet
  4. Google Drive

開発

従業員用のスプレッドシートを作成する.

従業員用のスプレッドシートを作成し,従業員に閲覧権限を付与する.作成したら共有リンクをコピペで保存しておく.
今回のケースだと従業員Aの分を作成して,その共有リンクを保存する.
このリンクは実装コードで使う.
ex.) https://docs.google.com/spreadsheets/d/1-OxdgIiUV3V0nulsC6WeuPETAPY55-crittoo96/edit#gid=0

GoogleFormで勤怠フォームを作成する

勤怠フォーム
勤怠フォーム

フォーム名 フォーム種別 必須
名前 プルダウン true
日付 日付 true
勤務場所 ラジオボタン true
始業時間 時刻 true
終業時間 時刻 true
休憩時間 時刻 true
業務内容 段落 true
自由記述 段落 false

フォーム内容はこのように設定した.

次にGoogle FormとSpreadSheetを連結する.このとき連結したSpreadSheetが管理者用のsheetになる.
右側のsheetアイコンをクリックしてsheetと連結した.Google Formの回答はすべてこのシートに溜まる.

管理者用シート連結
管理者用シート連結

管理者用シートのGASでフォームリクエストを処理する.

アイコンをクリックすると連結したシートに飛ぶ.
連結したシートから,「ツール」->「スクリプトエディタ」を選択してGASを起動.
以下が実装コード.

// フォームから送信された情報をシートに挿入する.
function writeRow(_sheet, _event) {
  _sheet.appendRow(
    [_event.namedValues['日付'] + '',
    _event.namedValues['勤務場所'] + '',
    _event.namedValues['始業時間'] + '',
    _event.namedValues['終業時間'] + '',
    _event.namedValues['休憩時間'] + '',
      'xxx',
    _event.namedValues['業務内容'] + '']
  );

  // 実務時間を求める.diffをとる
  var lastrow = _sheet.getLastRow();
  var strformula = "=D" + lastrow + "-C" + lastrow + "-E" + lastrow
  _sheet.getRange("F" + lastrow).setValue(strformula);
}

// シートの更新
function updateSheet(_sheet) {
  // 給与計算
  calcSalary(_sheet);
  
  // その他の欄
  // ...
  
  return;
}
// 給与計算のビジネスロジックを記述
function calcSalary(_sheet) {
  var formula = "=ROUNDDOWN((VALUE(B1) * 24)) * 1000";
  _sheet.getRange("B2").setNumberFormat("#,##0");
  _sheet.getRange("B2").setValue(formula);
}

// sheetを新規に作成してフォーマットする.
function createFormattedSheet(_spreadSheet, _sheetName) {
  // 作成
  _spreadSheet.insertSheet(_sheetName);
  var sheet = _spreadSheet.getSheetByName(_sheetName);

  // 初期フォーマット
  sheet.appendRow(['勤務時間']);
  sheet.appendRow(['給与']);
  sheet.appendRow(['日程', '勤務地', '始業時間', '終業時間', '休憩時間', '勤務時間', '業務内容']);

  // 罫線をひく
  var rng1 = sheet.getRange("A1:B2");
  rng1.setBorder(true, true, true, true, true, true);

  var rng3 = sheet.getRange("A3:G3");
  rng3.setBorder(true, true, true, true, true, true);

  // 勤務時間の書式設定
  sheet.getRange("B1").setNumberFormat("[h]:mm:ss");
  //勤務時間のformula設定
  sheet.getRange("B1").setValue("=SUM(F4:F)");

  return sheet;
}

// 「フォーム送信時」のトリガーに登録する関数.
function openEmployeeSheet(event) {
  var sheetUrl = '';
  // それぞれのspreadSheetと閲覧許可をシェアする
  switch (event.namedValues['名前'] + '') {
    case 'A':
      sheetUrl = "https://docs.google.com/spreadsheets/d/1-OxdgIiUV3V0nulsC6WeuPETAPY55-crittook96/edit#gid=0";
      break;
    case 'B':
      sheetUrl = '<Bさんと共有したspreadsheetsheetUrl>';
      break;
    case 'C':
      sheetUrl = '<Cさんと共有したspreadsheetsheetUrl>';
      break;

    default:
      break;
  }

  if (sheetUrl == '') return;

  var spreadSheet = SpreadsheetApp.openByUrl(sheetUrl);
  var sheetName = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'MM') + '月';
  var sheet = spreadSheet.getSheetByName(sheetName);

  // sheetがないとき新規に作成する.
  // 存在する時,既存のものに書き込む
  if (sheet) {
    writeRow(sheet, event);
    updateSheet(sheet);
  } else {
    var newSheet = createFormattedSheet(spreadSheet, sheetName);
    writeRow(newSheet, event);
    updateSheet(newSheet);
  }
}

動作について

openEmployeeSheetがエントリポイント.
まずswitch文で名前に対応した従業員のsheetUrlを選択.sheetUrlからspreadSheetを取得する.
spreadSheetに今月分のsheetが既に存在するなら,データを挿入して更新.
存在しない場合,新しく今月分のsheetを作成したのち,データを挿入して更新する.

switch内部のsheetUrlはあらかじめ作成しておいた従業員用シートのurlを入れる.
給与計算については時給1000円を仮定にcalcSalaryで実装.
次にopenEmployeeSheetをトリガーに登録する.

スクリプトをトリガーに設定する.

スクリプトを動作させるためにトリガーを設定する.
ストップウォッチアイコンを押すとトリガー設定画面にいく.

トリガーボタン
トリガーボタン
トリガー設定画面から「トリガーを追加」ボタンからトリガーを下画像の条件で作成することで,フォームから値が送信され管理者用シートが更新されると同時に対象の従業員シートも更新される.
トリガー設定
トリガー設定

完成

実際にフォームからA,B,Cの勤怠データを送信してみると管理者用シートには全データが保存されていて,従業員AのシートにはAの勤怠データのみ表示されていることがわかる.
また従業員Aからは閲覧権限によって,従業員Aのシートのみを閲覧することができる.
勤怠管理を紙を見ながら手打ちで行う手間が減り非常に便利になった.

管理者用スプレッドシート
管理者用スプレッドシート
従業員Aのスプレッドシート
従業員Aのスプレッドシート

Mysql,PHPで一年分(12ヶ月)の登録データを日別・カテゴリ別で動的に取得した

仮定

CREATE TABLE categories (
   `name` VARCHAR(20) PRIMARY KEY
);

CREATE TABLE users (
    `user_id` SERIAL PRIMARY KEY,
    `category_name` VARCHAR(20),
    `created` DATETIME,
    FOREIGN KEY (category_name) REFERENCES categories(name)
        ON UPDATE CASCADE
);

categories.nameにはMySql特殊文字が入らないとする.
(入りかねないときは,処理にプリペアドステートメントを挿入する)

条件

1.取得月数が12から値が変わっても大丈夫なようにする
2.categoriesも動的に取得

取得データイメージ

4月テーブル

date count_categoryName1 count_categoryName2 count_categoryName3
4/18 12 25 5
4/17 4 21 11
4/16 28 12 13
... ... ... ...

3月テーブル

date count_categoryName1 count_categoryName2 count_categoryName3
3/31 21 12 2
3/30 32 11 2
3/29 18 12 13
... ... ... ...

処理

動的にsql文を生成する.
$categoriesにあらかじめ対象のcategoryをselectしておく.

<?php
$categories = $pdo->query("SELECT name FROM categories WHERE name IN (<your select>));
// SQL文の生成
$baseSQL="SELECT DATE_FORMAT(u.created, '%m/%d') AS date";
foreach($categories as $category){
    $baseSQL .= ",SUM(CASE u.category_name WHEN {$category['name']} THEN 1 ELSE 0 END) AS count_category_{$category['name']}";
}

$allMonthlyData = [];
$month_size = 12;
for($between_min = 0; $between_min > -$month_size; $between_min--) {
    $between_max = $between_min + 1;
    $SQL = $baseSQL . " FROM users AS u WHERE u.created BETWEEN DATE_FORMAT( ADDDATE( CURDATE() , INTERVAL {$between_min} MONTH) , '%Y-%m-01' ) AND DATE_FORMAT( ADDDATE( CURDATE() , INTERVAL {$between_max} MONTH) , '%Y-%m-01' ) GROUP BY DATE_FORMAT(u.created, '%Y-%m-%d') ORDER BY u.created DESC;";
}

テスト

テストデータ入力

# categoriesデータ
INSERT INTO categories (name) VALUES ('CATEGORY1'), ('CATEGORY2');

# 適当にusersデータ
INSERT INTO users (category_name, created) VALUES('CATEGORY1',ADDTIME(CONCAT_WS(' ','2019-01-01' + INTERVAL RAND() * 90 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))));
INSERT INTO users (category_name, created) VALUES('CATEGORY2',ADDTIME(CONCAT_WS(' ','2019-01-01' + INTERVAL RAND() * 90 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))));
f:id:crittoo96:20190423165750p:plain
出力されたsql実行結果(途中まで)

Mリーガーのレート・ランキングを天鳳・MaruJan・MJの3パターンで算出してみた!

  1. 概要
  2. 算出方法
  3. 結果
  4. まとめ
  5. dbテーブル構造

1.ランキングを出したい!

Mリーグの予選結果から算出しました。
試合数: 135
tym19851002.com

1.1. データの形式を整える

上記サイトの予選結果をテキストファイルAに張り付けコピー、
正規表現処理を行って形式を整えたテキストファイルBを生成しました。

赤坂ドリブンズ(園田賢 42900点 +58.0P)
ユーネクストパイレーツ(小林剛 38400点 +18.4P)
チーム雷電(萩原聖人 16700点 ▲23.3P)
セガサミーフェニックス(魚谷侑未 2000点 ▲58.0P)
re.match('(.+)(([^ ]* )(.+)点[  ]([▲++][^P]*)P', line)
1,園田賢,42900,58.0
2,小林剛,38400,18.4
3,萩原聖人,16700,-23.3
4,魚谷侑未,2000,-58.0

1.2 整形データからレーティングの算出

1.1で整形したデータをdbテーブルに入れて、試合結果に基づきレーティングを算出しました。
dbテーブル構造はブログの一番下に掲載。

2.算出方法

2.1.天鳳

Rの変動=試合数補正×(順位基準点+補正値) 
試合補正数:400試合未満 1-試合数×0.002   400試合以上 0.2 
順位基準点: 1位 +30 2位 +10 3位 -10 4位 -30 
補正値   :(卓平均R-自分のR)÷40(50に変更との噂あり)
www22.atwiki.jp

スコアをレーティング評価に含めず、順位、平均R、自分Rを引数にして評価しています。
400試合未満であるが簡単にするために試合補正数に0.2を用いました。

def tenhou(self, aveRate):
    ranking_point = 0
    if self.rank == 1:
      ranking_point = 30
    elif self.rank == 2:
      ranking_point = 10
    elif self.rank == 3:
      ranking_point = -10
    elif self.rank == 4:
      ranking_point = -30
    else:
      exit(8)

    self.rateChange = int(0.2 * (ranking_point + (aveRate - self.ratePre) / 40))
    self.ratePost = self.ratePre + self.rateChange

2.2.MaruJan式

(順位点+対局者による変動値) ×0.1
順位点: 四麻 1位 +100 2位 +20 3位 -40 4位 -80
対局者による変動値: (同卓者の平均Rt - 自分のRt)÷80
www.maru-jan.com

天鳳と同様に順位から算出している。

def marujan(self, aveRate):
    ranking_point = 0
    if self.rank == 1:
      ranking_point = 100
    elif self.rank == 2:
      ranking_point = 20
    elif self.rank == 3:
      ranking_point = -40
    elif self.rank == 4:
      ranking_point = -80
    else:
      exit(8)
    
    self.rateChange = int((ranking_point + (aveRate - self.ratePre) / 80) * 0.1)
    self.ratePost = self.ratePre + self.rateChange

2.3.MJ式

変動値 = 0.24 * (SCORE + HC)
HC = (卓平均R - 自分のR) / 40
www.sega-mj.com

天鳳・Marujanと違うのが順位ではなく、スコアからレーティングを算出するということです。

def mj(self, aveRate):
    self.rateChange = int(0.24 * (self.score + (aveRate - self.ratePre) / 40) )
    self.ratePost = self.ratePre + self.rateChange

3.結果

名前 tenhou marujan mj
多井隆晴 1538 1568 1623
佐々木寿人 1517 1530 1561
滝沢和典 1517 1526 1575
朝倉康心 1517 1525 1540
黒沢咲 1509 1523 1521
近藤誠一 1508 1519 1530
前原雄大 1507 1514 1507
園田賢 1506 1507 1533
勝又健志 1503 1506 1507
二階堂亜樹 1502 1501 1489
鈴木たろう 1500 1500 1507
松本吉弘 1499 1497 1504
茅森早香 1498 1491 1487
萩原聖人 1497 1490 1485
小林剛 1495 1486 1487
村上淳 1490 1485 1462
石橋伸洋 1490 1476 1447
魚谷侑未 1483 1474 1443
白鳥翔 1482 1470 1447
高宮まり 1478 1464 1436
瀬戸熊直樹 1473 1457 1406

f:id:crittoo96:20190317193026p:plain
レート比較(tenhou,marujan,mj)

4.まとめ

たかはるぅぅぅつんぇぇ...

順位からレートを出すtenhou式とmarujan式の順位はほぼ同一のものとなりました。

ただ、marujan法においては、総合得点が滝沢よりも100弱低い寿人の方が高い評価を得ました。
marujanでレートを重視するのであれば、トップラスの「攻めダルマ打法」をした方が良いということがわかりました。

スコアからレートを出すmj式は二つと異なる結果となりました。
mj式は、一定値が0.24かつ順位ではなくスコアを引数に取るため、試合数が少なくてもレートの上下幅が大きいです。
仲間内のセット麻雀で誰が一番強いかを決める際には、大きな値が出やすいmj式がよさそうです。

個人的には順位ではなく、スコアを重要視しているMJのレート評価を気に入りました。

自分なりにイロレーティングを調整してオリジナルレーティング算出手法を提案したりするのも面白そうです。

5.dbテーブル構造

mysql> desc mleaguers;
+-------------+-------------+------+-----+---------+----------------+
| Field       | Type        | Null | Key | Default | Extra          |
+-------------+-------------+------+-----+---------+----------------+
| id          | int(11)     | NO   | PRI | NULL    | auto_increment |
| team_id     | int(11)     | YES  | MUL | NULL    |                |
| name        | char(16) | YES  | UNI | NULL    |                |
| alternative | char(16) | YES  |     | NULL    |                |
| rating      | int(11)     | YES  |     | 1500    |                |
| total_score | double(6,1) | YES  |     | 0.0     |                |
| total_point | int(11)     | YES  |     | 0       |                |
+-------------+-------------+------+-----+---------+----------------+
7 rows in set (0.01 sec)

mysql> desc mleague_teams;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| name  | char(32) | NO   | UNI | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> desc mleague_matches;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| date  | date | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> desc mleague_match_details;
+-------------+-------------+------+-----+---------+-------+
| Field       | Type        | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+-------+
| match_id    | int(11)     | NO   | MUL | NULL    |       |
| mleaguer_id | int(11)     | NO   | MUL | NULL    |       |
| point       | int(11)     | YES  |     | 0       |       |
| score       | double(4,1) | YES  |     | 0.0     |       |
| rank        | int(11)     | YES  |     | NULL    |       |
+-------------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

すべてInnoDB