未分類

連載コラムに掲載する絵を、ChatGPTに作って貰ったものを使うのに問題はありますか?

結論から言えば、原則として問題ありません。ただし、いくつか注意点があります。連載コラムという「公開・継続利用」を前提にすると、そこは押さえておいた方が安全です。


1. 著作権・利用権の観点(最重要)

ChatGPT(画像生成機能)で作成したイラストについては、

  • 商用利用を含めて使用可能

  • 連載コラムへの掲載も可能

  • 追加の利用料や許諾は不要

という扱いになっています。
つまり、コラムに載せること自体は問題ありません

また、

  • あなたが「指示して生成させた画像」を

  • あなた自身のコンテンツ(コラム)に使う

という構図なので、一般的な出版社・Webメディアの基準でも問題になりにくいです。


2. ただし注意すべき点(実務的に重要)

(1) 「完全な独占著作権」ではない

ChatGPTが生成した画像は、

  • あなたが自由に使える

  • しかし「世界で唯一あなた専用の著作物」とは言い切れない

という性質があります。

つまり、

  • 同じような指示をすれば、似た絵が他でも生成される可能性はゼロではありません。

ただし、
連載コラムの挿絵用途で「絵そのものを資産として独占管理する」必要がない限り、実害はほぼありません


(2) 実在作家・既存作品の「模倣」に見えないこと

安全のために重要なのは、

  • 「○○風」「△△の画風そのまま」

  • 特定の作家名・キャラクターを強く想起させる表現

を避けることです。

あなたがこれまで依頼しているような、

  • 抽象的

  • 研究・思考・海・数式・エージェントといった概念表現

  • オリジナル構図

であれば、問題になる可能性は極めて低いです。


(3) メディア・出版社の内規

ごくまれに、

  • 「生成AI画像は不可」

  • 「使用時は明記が必要」

という媒体ルールがあります。

ただしこれは法的問題ではなく編集方針です。
EE Times Japan や個人ブログ、研究者コラムの文脈では、現状ほぼ問題になりません。

不安な場合は、

  • 「挿絵は生成AIを用いて作成」
    と脚注やクレジットに一言入れる、という対応もあります(必須ではありません)。


3. 江端さんの連載との相性という観点

むしろ率直に言えば、

  • MAS

  • 仮想世界

  • 抽象化

  • 思考実験

  • シミュレーション

  • 現実とモデルの二重性

を語る連載において、

生成AIによるイラストは、内容と思想的に非常に整合的です。

「人間をモデル化する話」を
「人間がAIに描かせたイラスト」で表現する、という構造自体が、
メタ的で、江端さんの文章と相性が良い


まとめ(実務的結論)

  • ✔ 連載コラムへの掲載は問題なし

  • ✔ 商用・公開利用も問題なし

  • ✔ 著作権トラブルの可能性は低い

  • △ 独占著作権が必要な用途には不向き

  • △ 媒体ルールだけは一応確認

総合すると、
**「普通に使ってよい。むしろ自然」**という判断になります。

もし次に進めるなら、

  • 「この絵は連載全体の象徴にできるか」

  • 「回を跨いで使い回す/変奏させる」

といった編集的な使い方も、かなり面白くなりそうです。

未分類

目的
GPU付きPCを購入したが、

  • 本当にGPU計算が動くのか

  • Go言語から使えるのか

  • CPUと比べてどの部分が速く/遅くなるのか

を、誰でも再現できる形で確認する。

失敗した試行や遠回りはすべて排除し、
**「この順でやれば必ず動く」**内容のみを記載します。


0. 前提条件(必須)

ハードウェア

  • NVIDIA GPU(例:RTX 4060 Laptop GPU)

    • ※ AMD / Intel GPU でも理論上可だが、本手順は NVIDIA 前提

OS

  • Windows 10 / 11(64bit)


1. NVIDIA ドライバの確認(最重要)

1.1 NVIDIA ドライバが入っているか

PowerShell で確認:

nvidia-smi
  • GPU名とドライバ情報が表示されればOK

  • 表示されなければ、NVIDIA公式サイトから最新ドライバをインストール


2. Go 言語のインストール

2.1 Go をインストール

  • https://go.dev/dl/

  • Windows用インストーラ(msi)を実行

  • デフォルト設定でOK

確認:

go version

例:

go version go1.22.x windows/amd64

3. MSYS2 のインストール(OpenCL用)

3.1 MSYS2 をインストール

3.2 UCRT64 シェルを使用する(重要)

スタートメニューから:

MSYS2 UCRT64

を起動すること。


4. OpenCL 開発環境の導入(UCRT64)

4.1 パッケージ更新

pacman -Syu

(再起動を求められたら UCRT64 を再起動)

4.2 OpenCL 関連パッケージをインストール

pacman -S --needed \
  mingw-w64-ucrt-x86_64-opencl-headers \
  mingw-w64-ucrt-x86_64-opencl-icd

4.3 OpenCL ライブラリの存在確認(重要)

ls /ucrt64/lib | grep OpenCL

期待される出力:

libOpenCL.dll.a

これが 見えなければ先に進まない


5. Go から OpenCL を使う準備

5.1 作業ディレクトリ作成

PowerShell(任意の場所):

mkdir go_test
cd go_test
go mod init go_test

5.2 OpenCL Go バインディングを取得

go get github.com/jgillich/go-opencl/cl

6. OpenCL 1.2 指定(最重要ポイント)

問題点

  • OpenCL Headers はデフォルトで OpenCL 3.0

  • go-openclOpenCL 1.2 前提

  • 指定しないとビルドエラーになる

6.1 環境変数を設定(永続)

PowerShell で一度だけ実行:

setx CGO_CFLAGS "-DCL_TARGET_OPENCL_VERSION=120 -DCL_UNORM_INT24=0x10DF -DCL_DEPTH_STENCIL=0x10BE"

重要

  • 実行後 VS Code / PowerShell を完全に再起動

確認:

echo $env:CGO_CFLAGS

表示されればOK。


7. VS Code の準備

7.1 VS Code インストール

7.2 拡張機能

  • Go(公式拡張)


8. 動作確認用プログラム(全文)

以下を main.go として保存

package main

import (
	"encoding/binary"
	"fmt"
	"math"
	"math/rand"
	"time"
	"unsafe"

	"github.com/jgillich/go-opencl/cl"
)

const kernelSrc = `
__kernel void vadd(__global float* a, __global float* b, const int n) {
	int i = get_global_id(0);
	if (i < n) {
		a[i] = a[i] + b[i];
	}
}
`

func cpuVadd(a, b []float32) {
	for i := range a {
		a[i] += b[i]
	}
}

func f32ToBytes(xs []float32) []byte {
	b := make([]byte, 4*len(xs))
	for i, v := range xs {
		binary.LittleEndian.PutUint32(b[i*4:], math.Float32bits(v))
	}
	return b
}

func bytesToF32(b []byte) []float32 {
	n := len(b) / 4
	xs := make([]float32, n)
	for i := 0; i < n; i++ {
		u := binary.LittleEndian.Uint32(b[i*4:])
		xs[i] = math.Float32frombits(u)
	}
	return xs
}

func main() {
	const N = 5_000_000
	const CPU_REPEAT = 5
	const GPU_REPEAT = 10

	a := make([]float32, N)
	b := make([]float32, N)

	rng := rand.New(rand.NewSource(1))
	for i := 0; i < N; i++ {
		a[i] = rng.Float32()
		b[i] = rng.Float32()
	}

	// CPU
	aCPU := make([]float32, N)
	copy(aCPU, a)
	cpuVadd(aCPU, b)

	t0 := time.Now()
	for i := 0; i < CPU_REPEAT; i++ {
		cpuVadd(aCPU, b)
	}
	fmt.Println("CPU avg time:", time.Since(t0)/CPU_REPEAT)

	// OpenCL
	platforms, _ := cl.GetPlatforms()
	devs, _ := platforms[0].GetDevices(cl.DeviceTypeAll)
	dev := devs[0]
	fmt.Println("Using device:", dev.Name())

	ctx, _ := cl.CreateContext([]*cl.Device{dev})
	queue, _ := ctx.CreateCommandQueue(dev, 0)

	aBytes := f32ToBytes(a)
	bBytes := f32ToBytes(b)

	tCopy0 := time.Now()
	bufA, _ := ctx.CreateBuffer(cl.MemReadWrite|cl.MemCopyHostPtr, aBytes)
	bufB, _ := ctx.CreateBuffer(cl.MemReadOnly|cl.MemCopyHostPtr, bBytes)
	fmt.Println("GPU copy host->device:", time.Since(tCopy0))

	prog, _ := ctx.CreateProgramWithSource([]string{kernelSrc})
	prog.BuildProgram([]*cl.Device{dev}, "")
	kernel, _ := prog.CreateKernel("vadd")
	kernel.SetArgs(bufA, bufB, int32(N))

	queue.EnqueueNDRangeKernel(kernel, nil, []int{N}, nil, nil)
	queue.Finish()

	t1 := time.Now()
	for i := 0; i < GPU_REPEAT; i++ {
		queue.EnqueueNDRangeKernel(kernel, nil, []int{N}, nil, nil)
	}
	queue.Finish()
	fmt.Println("GPU kernel avg time:", time.Since(t1)/GPU_REPEAT)

	outBytes := make([]byte, len(aBytes))
	queue.EnqueueReadBuffer(
		bufA,
		true,
		0,
		len(outBytes),
		unsafe.Pointer(&outBytes[0]),
		nil,
	)

	out := bytesToF32(outBytes)
	fmt.Println("check:", out[0], aCPU[0])
}

9. 実行

go clean -cache
go run .

10. 期待される出力例

CPU avg time: 2.8ms
Using device: NVIDIA GeForce RTX 4060 Laptop GPU
GPU kernel avg time: 0.5ms
GPU copy host->device: 10ms
check: (一致しない値)

11. 結果の正しい理解

  • GPUは 計算だけなら圧倒的に速い

  • 転送(CPU↔GPU)が非常に重い

  • 転送込みでは CPU の方が速くなるのは正常

  • GPUは

    • データを載せたまま

    • 大量・反復・重い計算
      で真価を発揮する


12. 本検証で得られた結論

  • Go言語からGPU(OpenCL)は 実用的に利用可能

  • GPU性能評価では

    • 計算

    • 転送
      を必ず分けて考える必要がある

  • 本コードは GPU評価の 最小再現テンプレートとして有用

以上

未分類

以下に、本日作業した内容を「そのまま再現できるメモ」として、必要なコマンド/修正したファイル全文(index.html / server.py)/動作確認手順まで、全部まとめて記載します。


1. 目的

  • index.htmliframe 4枚(sample2 / rides / bus_map / trips_map) を同時表示している

  • ユーザが 任意の iframe を選び、ボタン押下で「ブラウザに表示されている内容と一致する PNG」 をローカル保存する


2. 問題と原因

2.1 保存画像と表示内容が一致しない

  • /shot で Playwright(別ブラウザセッション)を使ってHTMLを描画してPNG化していた

  • そのPlaywright側には ブラウザの cookie(date_start/date_end) が無い
    → CSV生成期間がズレる → 表示と保存が不一致

2.2 bus_map / trips_map が「過去のデータ」っぽくなる

  • cookieが更新されても、iframeが「同じURLのまま」再利用され、再描画が起きない/キャッシュが残るケース
    → bus_map/trips_map の表示が更新されず古い状態に見える


3. 対策(実装方針)

3.1 表示と保存の“期間”を一致させる

  • /shot に来たリクエストの cookie(date_start/date_end)を Playwright に注入

  • Playwright が取得するCSVもブラウザと同じ期間になる

3.2 iframe表示を常に最新化(強制リロード)

  • index.html/range.json から start/end を取得

  • iframe の src?v=<start>_<end>_<timestamp> 付きURLに置き換える
    → cookie変更後でも必ず再読み込みが走る

3.3 保存対象は「今iframeが表示しているURL(クエリ込み)」

  • 保存時、固定パスではなく iframe.src を採用し、pathname + search/shot に渡す
    → 「見えているもの」=「保存されるもの」を保証


4. 必要コマンド(サーバ側)

4.1 Playwright の導入

pip install playwright
playwright install chromium ※ Linux環境でブラウザ依存ライブラリ不足が出る場合(最小例、Ubuntu系)
sudo apt-get update
sudo apt-get install -y \
  libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
  libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 \
  libxrandr2 libgbm1 libasound2 libpangocairo-1.0-0 libpango-1.0-0 \
  libgtk-3-0

4.2 起動

python3 server.py

(ポートを変えるなら)

SAMPLE2_PORT=18080 python3 server.py

5. 修正後のコード(全文)

5.1 index.html(全文)

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>sample2 + rides + bus_map + trips_map</title>
<style>
body{font-family:sans-serif;margin:16px}
.row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
.panel{margin-top:16px;border:1px solid #ccc;border-radius:8px;overflow:hidden}
.panel h2{margin:0;padding:8px;background:#f5f5f5;display:flex;align-items:center;justify-content:space-between;gap:12px}
.panel h2 .right{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
iframe{width:100%;border:0}
.h1{height:640px}
.h2{height:640px}
.h3{height:720px}
.h4{height:720px}
a.btn{padding:6px 10px;border:1px solid #888;text-decoration:none;color:#000;border-radius:8px;background:#fff}
button.btn{padding:6px 10px;border:1px solid #888;border-radius:8px;background:#fff;cursor:pointer}
select{padding:6px 8px}
.small{font-size:12px;color:#333}
</style>
</head>
<body>

<h1>日時指定(4表示)</h1>

<form method="POST" action="/set_range" class="row">
  <label>開始日 <input id="start" type="date" name="start" required></label>
  <label>終了日 <input id="end" type="date" name="end" required></label>
  <button type="submit">OK(表示)</button>

  <a class="btn" href="/sample2.csv?download=1">CSV(sample2)</a>
  <a class="btn" href="/ride_summary.csv?download=1">CSV(rides)</a>
  <a class="btn" href="/bus_stops.csv?download=1">CSV(bus_stops)</a>
  <a class="btn" href="/trips.csv?download=1">CSV(trips)</a>
</form>

<div class="row" style="margin-top:10px">
  <span class="small">保存対象</span>
  <select id="shotTarget">
    <option value="f1">sample2</option>
    <option value="f2">rides</option>
    <option value="f3">bus_map</option>
    <option value="f4">trips_map</option>
  </select>
  <button type="button" class="btn" onclick="saveSelected()">選択した画面をPNG保存</button>
  <span id="shotMsg" class="small"></span>
</div>

<div class="panel">
  <h2>
    <span>sample2</span>
    <span class="right">
      <button type="button" class="btn" onclick="saveFrame('f1','sample2')">PNG保存</button>
    </span>
  </h2>
  <iframe id="f1" class="h1" src="/sample2.html"></iframe>
</div>

<div class="panel">
  <h2>
    <span>rides</span>
    <span class="right">
      <button type="button" class="btn" onclick="saveFrame('f2','rides')">PNG保存</button>
    </span>
  </h2>
  <iframe id="f2" class="h2" src="/rides.html"></iframe>
</div>

<div class="panel">
  <h2>
    <span>bus_map</span>
    <span class="right">
      <button type="button" class="btn" onclick="saveFrame('f3','bus_map')">PNG保存</button>
    </span>
  </h2>
  <iframe id="f3" class="h3" src="/bus_map.html"></iframe>
</div>

<div class="panel">
  <h2>
    <span>trips_map</span>
    <span class="right">
      <button type="button" class="btn" onclick="saveFrame('f4','trips_map')">PNG保存</button>
    </span>
  </h2>
  <iframe id="f4" class="h4" src="/trips_map.html"></iframe>
</div>

<script>
function pad(n){ return String(n).padStart(2,'0'); }
function nowStr(){
  const d = new Date();
  return `${d.getFullYear()}${pad(d.getMonth()+1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
}

// cookieの開始/終了をサーバから取得してフォームに反映し、iframeを強制リロード
async function syncRangeAndReloadFrames(){
  const res = await fetch('/range.json', {cache:'no-store'});
  if(!res.ok) return;
  const j = await res.json();

  const startEl = document.getElementById('start');
  const endEl = document.getElementById('end');
  if(j.start) startEl.value = j.start;
  if(j.end)   endEl.value = j.end;

  // start/end を含むクエリを付与して iframe を更新(必ず再読み込みさせる)
  const v = encodeURIComponent(`${j.start||''}_${j.end||''}_${Date.now()}`);

  document.getElementById('f1').src = `/sample2.html?v=${v}`;
  document.getElementById('f2').src = `/rides.html?v=${v}`;
  document.getElementById('f3').src = `/bus_map.html?v=${v}`;
  document.getElementById('f4').src = `/trips_map.html?v=${v}`;
}

window.addEventListener('load', () => {
  syncRangeAndReloadFrames();
});

function saveSelected(){
  const sel = document.getElementById('shotTarget').value;
  const map = {f1:'sample2', f2:'rides', f3:'bus_map', f4:'trips_map'};
  saveFrame(sel, map[sel] || 'screen');
}

// 「いまiframeで表示しているURL(クエリ込み)」を /shot に渡す
async function saveFrame(frameId, label){
  const msg = document.getElementById('shotMsg');
  msg.textContent = '画像生成中...';

  const iframe = document.getElementById(frameId);
  if(!iframe){
    msg.textContent = '失敗: iframeが見つかりません';
    return;
  }

  const url = new URL(iframe.src);
  const pathWithQuery = url.pathname + url.search;

  const w = 1400, h = 900;
  const shotUrl = `/shot?path=${encodeURIComponent(pathWithQuery)}&w=${w}&h=${h}`;

  try{
    const res = await fetch(shotUrl, {cache:'no-store'});
    if(!res.ok){
      msg.textContent = `失敗: /shot HTTP ${res.status}`;
      return;
    }
    const blob = await res.blob();
    const a = document.createElement('a');
    const obj = URL.createObjectURL(blob);
    a.href = obj;
    a.download = `${label}_${nowStr()}.png`;
    a.click();
    URL.revokeObjectURL(obj);
    msg.textContent = '保存しました';
  }catch(e){
    msg.textContent = '失敗: 通信/生成エラー';
  }
}
</script>

</body>
</html>

5.2 server.py(全文)

# server.py
from __future__ import annotations

import os
import io
import csv
import datetime as dt
from dataclasses import dataclass

from flask import (
    Flask,
    request,
    make_response,
    send_from_directory,
    redirect,
    Response,
    abort,
    jsonify,
)

import psycopg2

APP_HOST = "0.0.0.0"
APP_PORT = int(os.getenv("SAMPLE2_PORT", "18080"))

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# ========= DB 接続 =========
PGHOST = os.getenv("PGHOST", "localhost")
PGPORT = int(os.getenv("PGPORT", "15432"))
PGDATABASE = os.getenv("PGDATABASE", "tsubame_db")
PGUSER = os.getenv("PGUSER", "postgres")
PGPASSWORD = os.getenv("PGPASSWORD", "password")

# ========= cookie =========
COOKIE_START = "date_start"
COOKIE_END = "date_end"

# ========= table / column =========
STOP_TABLE = "stop_tsubame"
STOP_ID_COL = "stop_num"
STOP_NAME_COL = "name"
STOP_GEOM_COL = "geom"

LOG_TABLE = "log_pass_tsubame"
LOG_TIME_COL = "ride_time"
LOG_ONOFF_COL = "on_off"
LOG_GEOM_COL = "geom"

app = Flask(__name__, static_folder=None)


@dataclass(frozen=True)
class DateRange:
    start: dt.date
    end: dt.date

    @property
    def end_exclusive(self) -> dt.date:
        return self.end + dt.timedelta(days=1)


def parse_date(s: str) -> dt.date:
    return dt.datetime.strptime(s, "%Y-%m-%d").date()


def get_range(req) -> DateRange:
    s = req.cookies.get(COOKIE_START)
    e = req.cookies.get(COOKIE_END)

    # cookie が無い場合は「今月1日〜今日」
    if not s or not e:
        today = dt.date.today()
        start = today.replace(day=1)
        end = today
        return DateRange(start, end)

    start = parse_date(s)
    end = parse_date(e)
    if end < start:
        start, end = end, start
    return DateRange(start, end)


def db_connect():
    return psycopg2.connect(
        host=PGHOST,
        port=PGPORT,
        dbname=PGDATABASE,
        user=PGUSER,
        password=PGPASSWORD,
    )


def csv_response(filename: str, text: str, download: bool):
    resp = make_response(text)
    resp.headers["Cache-Control"] = "no-store"

    if download:
        resp.headers["Content-Type"] = "application/octet-stream"
        resp.headers["Content-Disposition"] = f'attachment; filename="{filename}"'
    else:
        resp.headers["Content-Type"] = "text/csv; charset=utf-8"
        resp.headers["Content-Disposition"] = f'inline; filename="{filename}"'
    return resp


# ========= 画面 =========
@app.get("/")
def root():
    return send_from_directory(BASE_DIR, "index.html")


@app.get("/range.json")
def range_json():
    # index.html が cookie の値を取得するため
    dr = get_range(request)
    return jsonify({
        "start": dr.start.strftime("%Y-%m-%d"),
        "end": dr.end.strftime("%Y-%m-%d"),
    })


@app.post("/set_range")
def set_range():
    s = request.form["start"]
    e = request.form["end"]
    resp = redirect("/")
    resp.set_cookie(COOKIE_START, s, max_age=86400 * 365)
    resp.set_cookie(COOKIE_END, e, max_age=86400 * 365)
    return resp


# ========= 画面保存(PNG) =========
@app.get("/shot")
def shot():
    """
    /shot?path=/bus_map.html?v=...&w=1400&h=900

    - path は「/から始まるパス + クエリ」を許可(iframeの表示URLをそのまま渡す)
    - /shot に来た cookie(date_start/date_end)を Playwright に注入
      → ブラウザ表示と同じ期間で描画される
    """
    path = request.args.get("path", "")
    if not path.startswith("/"):
        abort(400)

    w = int(request.args.get("w", "1400"))
    h = int(request.args.get("h", "900"))
    if w < 200 or h < 200 or w > 5000 or h > 5000:
        abort(400)

    base = request.host_url.rstrip("/")  # 例: http://127.0.0.1:18080
    target_url = f"{base}{path}"

    try:
        from playwright.sync_api import sync_playwright
    except Exception:
        return Response(
            "Playwright が未導入です。サーバ側で `pip install playwright` と "
            "`playwright install chromium` を実行してください。",
            status=500,
            mimetype="text/plain; charset=utf-8",
        )

    # cookie を Playwright に注入
    date_start = request.cookies.get(COOKIE_START)
    date_end = request.cookies.get(COOKIE_END)

    try:
        with sync_playwright() as p:
            browser = p.chromium.launch()
            context = browser.new_context(viewport={"width": w, "height": h})

            cookies = []
            if date_start:
                cookies.append({"name": COOKIE_START, "value": date_start, "url": base})
            if date_end:
                cookies.append({"name": COOKIE_END, "value": date_end, "url": base})
            if cookies:
                context.add_cookies(cookies)

            page = context.new_page()
            page.goto(target_url, wait_until="networkidle")

            # JS描画(Chart/Leaflet等)待ちの保険
            page.wait_for_timeout(800)

            png = page.screenshot(full_page=True, type="png")

            context.close()
            browser.close()

    except Exception as e:
        return Response(
            f"shot failed: {e}",
            status=500,
            mimetype="text/plain; charset=utf-8",
        )

    resp = Response(png, mimetype="image/png")
    resp.headers["Cache-Control"] = "no-store"
    return resp


# ---------- sample2.csv ----------
@app.get("/sample2.csv")
def sample2_csv():
    dr = get_range(request)
    sql = f"""
    SELECT on_count, COUNT(*) AS num_psg
    FROM (
      SELECT psg_id, COUNT(*) AS on_count
      FROM {LOG_TABLE}
      WHERE {LOG_TIME_COL} >= %(s)s
        AND {LOG_TIME_COL} <  %(e)s
        AND {LOG_ONOFF_COL} = 1
      GROUP BY psg_id
    ) t
    GROUP BY on_count
    ORDER BY on_count
    """

    out = io.StringIO()
    w = csv.writer(out)
    w.writerow(["on_count", "num_psg", "cum_pct"])

    with db_connect() as c:
        with c.cursor() as cur:
            cur.execute(sql, {"s": dr.start, "e": dr.end_exclusive})
            rows = cur.fetchall()

    total = sum(r[1] for r in rows)
    cum = 0
    for oc, n in rows:
        cum += n
        pct = (cum / total * 100) if total else 0
        w.writerow([oc, n, f"{pct:.3f}"])

    return csv_response("sample2.csv", out.getvalue(), request.args.get("download") == "1")


# ---------- ride_summary.csv ----------
@app.get("/ride_summary.csv")
def ride_summary_csv():
    dr = get_range(request)
    sql = f"""
    SELECT {LOG_TIME_COL}::date,
           EXTRACT(hour FROM {LOG_TIME_COL})::int,
           COUNT(*)
    FROM {LOG_TABLE}
    WHERE {LOG_TIME_COL} >= %(s)s
      AND {LOG_TIME_COL} <  %(e)s
      AND {LOG_ONOFF_COL} = 1
      AND EXTRACT(hour FROM {LOG_TIME_COL}) BETWEEN 10 AND 13
    GROUP BY 1,2
    ORDER BY 1,2
    """

    data = {}
    with db_connect() as c:
        with c.cursor() as cur:
            cur.execute(sql, {"s": dr.start, "e": dr.end_exclusive})
            for d, h, n in cur.fetchall():
                key = d.strftime("%Y/%m/%d")
                data.setdefault(key, {10: 0, 11: 0, 12: 0, 13: 0})
                data[key][h] = n

    out = io.StringIO()
    w = csv.writer(out)
    w.writerow(["日付", "10時", "11時", "12時", "13時"])
    for d in sorted(data):
        w.writerow([d, data[d][10], data[d][11], data[d][12], data[d][13]])

    return csv_response("ride_summary.csv", out.getvalue(), request.args.get("download") == "1")


# ---------- bus_stops.csv ----------
@app.get("/bus_stops.csv")
def bus_stops_csv():
    dr = get_range(request)
    include_all = request.args.get("all") == "1"  # /bus_stops.csv?all=1 で全停留所

    sql = f"""
    WITH counts AS (
      SELECT
        s.{STOP_ID_COL} AS {STOP_ID_COL},
        SUM(CASE WHEN l.{LOG_ONOFF_COL} = 1 THEN 1 ELSE 0 END) AS board,
        SUM(CASE WHEN l.{LOG_ONOFF_COL} = 0 THEN 1 ELSE 0 END) AS alight
      FROM {LOG_TABLE} AS l
      JOIN LATERAL (
        SELECT {STOP_ID_COL}
        FROM {STOP_TABLE}
        ORDER BY {STOP_GEOM_COL} <-> l.{LOG_GEOM_COL}
        LIMIT 1
      ) AS s ON TRUE
      WHERE l.{LOG_TIME_COL} >= %(s)s
        AND l.{LOG_TIME_COL} <  %(e)s
      GROUP BY s.{STOP_ID_COL}
    )
    SELECT
      regexp_replace(st.{STOP_NAME_COL}, '^([0-9]+)\\s*[..]\\s*', '\\1. ') AS name,
      ST_Y(st.{STOP_GEOM_COL}) AS lat,
      ST_X(st.{STOP_GEOM_COL}) AS lng,
      COALESCE(c.board, 0)  AS board,
      COALESCE(c.alight, 0) AS alight
    FROM {STOP_TABLE} AS st
    LEFT JOIN counts AS c
      ON c.{STOP_ID_COL} = st.{STOP_ID_COL}
    {"WHERE c."+STOP_ID_COL+" IS NOT NULL" if not include_all else ""}
    ORDER BY st.{STOP_ID_COL};
    """

    out = io.StringIO()
    w = csv.writer(out)
    w.writerow(["name", "lat", "lng", "board", "alight"])

    with db_connect() as c:
        with c.cursor() as cur:
            cur.execute(sql, {"s": dr.start, "e": dr.end_exclusive})
            for name, lat, lng, board, alight in cur.fetchall():
                w.writerow([name, lat, lng, board, alight])

    return csv_response("bus_stops.csv", out.getvalue(), request.args.get("download") == "1")


# ---------- trips.csv ----------
@app.get("/trips.csv")
def trips_csv():
    dr = get_range(request)

    sql = f"""
WITH with_stop AS (
  SELECT
    l.psg_id,
    l.{LOG_TIME_COL} AS ride_time,
    l.{LOG_ONOFF_COL} AS on_off,
    (s.stop_num::text || '. ' ||
      regexp_replace(s.name, '^[0-9]+[.\\.]\\s*', '')
    ) AS stop_name
  FROM {LOG_TABLE} l
  JOIN LATERAL (
    SELECT stop_num, name, geom
    FROM {STOP_TABLE}
    ORDER BY l.{LOG_GEOM_COL} <-> geom
    LIMIT 1
  ) s ON true
  WHERE l.{LOG_TIME_COL} >= %(s)s
    AND l.{LOG_TIME_COL} <  %(e)s
),
seq AS (
  SELECT
    psg_id,
    ride_time,
    on_off,
    stop_name,
    SUM(CASE WHEN on_off = 1 THEN 1 ELSE 0 END)
      OVER (PARTITION BY psg_id ORDER BY ride_time) AS trip_no
  FROM with_stop
),
od AS (
  SELECT
    psg_id,
    trip_no,
    (array_agg(stop_name ORDER BY ride_time)
      FILTER (WHERE on_off = 1))[1] AS "from",
    (
      array_agg(stop_name ORDER BY ride_time)
        FILTER (WHERE on_off = 0)
    )[
      array_length(
        array_agg(stop_name ORDER BY ride_time)
          FILTER (WHERE on_off = 0)
      , 1)
    ] AS "to"
  FROM seq
  WHERE trip_no > 0
  GROUP BY psg_id, trip_no
)
SELECT
  "from",
  "to",
  COUNT(*) AS trips
FROM od
WHERE "from" IS NOT NULL
  AND "to" IS NOT NULL
  AND "from" <> "to"
GROUP BY "from", "to"
ORDER BY "from", "to";
"""

    out = io.StringIO()
    w = csv.writer(out)
    w.writerow(["from", "to", "trips"])

    with db_connect() as c:
        with c.cursor() as cur:
            cur.execute(sql, {"s": dr.start, "e": dr.end_exclusive})
            for f, t, n in cur.fetchall():
                w.writerow([f, t, n])

    return csv_response("trips.csv", out.getvalue(), request.args.get("download") == "1")


# ---------- HTML ----------
@app.get("/sample2.html")
def sample2_html():
    return send_from_directory(BASE_DIR, "sample2.html")


@app.get("/rides.html")
def rides_html():
    return send_from_directory(BASE_DIR, "rides.html")


@app.get("/bus_map.html")
def bus_map_html():
    return send_from_directory(BASE_DIR, "bus_map_from_csv_fix.html")


@app.get("/trips_map.html")
def trips_map_html():
    return send_from_directory(BASE_DIR, "trips_map.html")


# ---------- その他(静的ファイル) ----------
@app.get("/<path:filename>")
def any_file(filename):
    p = os.path.join(BASE_DIR, filename)
    if os.path.exists(p):
        return send_from_directory(BASE_DIR, filename)
    return Response("not found", 404)


if __name__ == "__main__":
    app.run(host=APP_HOST, port=APP_PORT)

6. 動作確認手順(再現手順)

  1. DB(PostgreSQL)が起動していること(既存の前提)

  2. Playwright を導入(初回のみ)

pip install playwright
playwright install chromium

3. server.py 起動

python3 server.py
  1. ブラウザでアクセス

  • http://127.0.0.1:18080/(ポートが違う場合は読み替え)

  1. 開始日・終了日を入力して「OK(表示)」

  2. 4画面が更新される(iframeが ?v=... で強制リロードされる)

  3. 「選択した画面をPNG保存」または各パネルの「PNG保存」を押す

  4. 表示内容と一致するPNGがローカルに保存される


7. 最終的に効いたポイント(短縮メモ)

  • /shot に cookie を渡す(Playwrightへ add_cookies)

  • iframe は必ず再読み込みさせる(?v=start_end_timestamp

  • 保存対象は iframe の現在表示URL(pathname + search)

 

2025,江端さんの忘備録

ニュースで人命に関わる事件が発生した時に使われる定番のフレーズに、
There is a stock phrase commonly used in news reports when incidents involving human life occur,

『搬送時に会話ができる状態であり、命に別状はないようです』
“The person was conscious and able to converse during transport, and their life does not appear to be in danger.”

がありますが、私は言いたい
This phrase is often used, but I want to say,

―― だから何?
— So what?

と。
That’s what I feel.

-----

いや、もちろん、このニュースのフレーズは、事件の概要と深刻度を取り扱う最適なフレーズであることは認めます。
Of course, I acknowledge that this phrase is an optimal way to convey the outline and severity of an incident.

人命は、一度失われたら、後から取り返すことのできない、"No Return"の概念です。
Human life is a “No Return” concept—once it is lost, it can never be recovered.

これは、脳死判定の時に使われる「不可逆性」と同じです。
This is the same as the “irreversibility” used in brain death determination.

-----

ただ、先日骨折事故を起こし、現在もその痛みに苦しめられている私から言えば、『命に別状はなくても、その後、"月単位"、"年単位"、下手すれば終生、その後遺症(痛みや障害)に苦しむ』という事実が伝わらないことに、憤慨します。
However, speaking as someone who recently suffered a fracture accident and is still tormented by pain, I am outraged that the fact that even if one’s life is not in danger, one may suffer for “months,” “years,” or even a lifetime from aftereffects such as pain or disability, fails to be conveyed.

ただ、私が憤慨しているのは、『私が現在、回復期の途中であるとは言え、痛みに悩まされているから』であり、完全回復した後は、多分、高い確率でこの「憤慨したことすら忘れる」という確信があります。
That said, I am aware that my anger stems from the fact that I am currently still in the recovery phase and suffering pain, and I am pretty sure that once I have fully recovered, I will probably forget even having been outraged.

リハビリの為に、ウォーキングを始めたのですが、ウォーキングすると痛みが再発しました(ちょっとビックリしました)。といって、ウォーキングしないと、いつまで経ってもリハビリできない ーー ええい、本当に面倒くさい!
I started walking as part of my rehabilitation, but when I did, the pain returned (which surprised me a little). Yet, if I do not walk, rehabilitation will never progress—damn it, this is really troublesome!

「自分の体が回復しているかのように思っていたところに、また元の状態に戻らされる」というのは、なんともやりきれない思いです。
Being pushed back to the original state just when you thought your body was recovering is an utterly disheartening feeling.

術後の激痛の数週間、激痛から日常に戻るまで苦痛 ーー これが、『搬送時に会話ができる状態であり、命に別状はないようです』には、"ない"。
The weeks of intense postoperative pain and the suffering until returning to daily life are not included in “the person was conscious and able to converse, and their life is not in danger.”

理不尽だ、と思うのです。
I find this deeply unreasonable.

-----

今から思うのですが、『骨折手術 → 翌日退院』という現在の医療の凄さにビックリしました(まあ、骨折事故の種類もあるでしょうが)。
Looking back now, I was astonished by the modern medical practice of “fracture surgery → discharge the next day” (though of course this depends on the type of fracture).

もちろん、私は病院に長期滞在したい訳ではないのですが、嫁さんの介護なしに何もできない状態で、自宅に戻らされる ーー そして、強力な鎮痛剤と嫁さんの介護で、なんとかその日を凌ぐという、この医療環境に、心底驚きました。
Of course, I did not want to stay in the hospital long-term, but being sent home in a state where I could do nothing without my wife’s care—and somehow surviving each day with strong painkillers and her support—truly shocked me.

今や病院は、必要最低限のことをすれば、「後は自分でやりなさい」を原則として動いています。間違いありません。私は我が国の医療予算の厳しさを、骨折の痛みと合わせて、身に染みるほど実感しました。
Today, hospitals operate on the principle that once the bare minimum is done, “the rest is up to you.” There is no doubt about that. Along with the pain of my fracture, I felt the severity of our country’s medical budget constraints deep in my bones.

皆さんも覚悟してください ーー 病院はできることだけをやり、自分できることは自分でやる ーー 片腕でメシを喰い、利き腕の逆の手で文字を書き、激痛を感じないように低速(秒速10cm)の歩行をし、寝返りで激痛で目がさめる、痛みを感じない置き上がりのルートに10分以上を要する ーー という日々を、『自宅で、自力で行う』という医療体勢にシフトしています。
You should all be prepared—the hospital does only what it can, and what you can do, you must do yourself—eating with one arm, writing with the non-dominant hand, walking slowly (10 cm per second) to avoid severe pain, waking up from agony when turning over, taking more than ten minutes to find a way to get up without pain—this is the medical system shifting toward having patients do all of this at home, on their own.

覚悟して下さい。事故の責任の所在とは無関係に、事故に巻き込まれれば、このような地獄が待っています。
Be prepared. Regardless of who is responsible, if you are involved in an accident, this kind of hell awaits you.

大切なのは、事故を起さないこと。事故を起こしにくい体を作っておくこと。できれば、タバコも酒も今止めてしまいましょう。
What matters most is not having an accident. Build a body that reduces the risk of accidents. If possible, quit smoking and drinking right now.

国家は助けてくれるけど、優しくはないぞ。
The state will help you, but it will not be gentle.

-----

「江端さんは、これまでずっと忙しい日々を送ってきたのですが、今回の入院でゆっくり静養して下さい」
“Mr. Ebata, you have led a hectic life until now, so please take this hospitalization as a chance to rest properly.”

と、色々な人から言われました
Many people said to me the above.

うん、このセリフ定番です。
Yes, this line is a classic.

でもね、あの入院直後の激痛の日々を回避できるなら、私は、通常の仕事を10倍にしても避けたかったですよ。
But if I could have avoided those days of excruciating pain immediately after hospitalization, I would have gladly taken on ten times my usual workload to do so.

事故の後の入院または治療の不便で激痛の日々が、それ以前の日々より"良い"なんてことは、絶対にありませんから。
There is absolutely no way that days of severe pain and inconvenience from hospitalization or treatment after an accident are “better” than the days before it.

「骨折で手術して麻酔が切れた時、どうしてずっと麻酔を投与し続けてくれないんだ、と思いました。意識なく痛みを感じないまま体を治癒できれば、不要に体を動かすことなく治癒が進むと思いますし、もし痛みが永久に続くのであれば、意識を回復したくない、と思いました」とChatGPTと話をしてみた件

未分類

1. 作業目的

既存の日時指定ダッシュボード(sample2 / rides / bus_map)に対して、

  1. 停留所間トリップ(OD)を可視化する4つ目の画面 trips_map.html を追加

  2. その描画に必要な trips.csv を、他CSVと同様に「指定期間内」でDBから動的生成

  3. 既存UI(index.html)を壊さず、iframe + CSVダウンロードボタンを追加

  4. 表示崩れ(左寄り)を修正し、視認性を改善

することを目的とした。


2. 最終的な画面構成

index.html(トップ)

  • 日時指定フォーム

  • CSVダウンロードボタン(4種)

    • sample2.csv

    • ride_summary.csv

    • bus_stops.csv

    • trips.csv(追加)

  • iframe 表示(上から順に)

    1. sample2(利用回数分布)

    2. rides(日別・時間帯別ライド数)

    3. bus_map(停留所マップ)

    4. trips_map(停留所間トリップ可視化)


3. 実装の要点

3.1 trips.csv の仕様

  • 期間指定:index.html の start/end → cookie → SQL WHERE に反映

  • 定義

    • from:同一トリップ内の最初の乗車停留所

    • to:同一トリップ内の最後の降車停留所

    • trips:その OD の件数

  • トリップ定義

    • psg_id 単位

    • on_off = 1(乗車)が出現するたびに新しいトリップ


3.2 表示ずれ(左寄り)の原因と対処

  • 原因:max-width のみ指定され、中央寄せ指定がなかった

  • 対処:

    margin: 0 auto;

    をラッパー要素に追加


4. 最終成果物(コード全文)


4.1 server.py(全文)

from __future__ import annotations

import os
import io
import csv
import datetime as dt
from dataclasses import dataclass

from flask import (
    Flask,
    request,
    make_response,
    send_from_directory,
    redirect,
    Response,
)
import psycopg2

APP_HOST = "0.0.0.0"
APP_PORT = int(os.getenv("SAMPLE2_PORT", "18080"))
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# ========= DB 接続 =========
PGHOST = os.getenv("PGHOST", "localhost")
PGPORT = int(os.getenv("PGPORT", "15432"))
PGDATABASE = os.getenv("PGDATABASE", "tsubame_db")
PGUSER = os.getenv("PGUSER", "postgres")
PGPASSWORD = os.getenv("PGPASSWORD", "password")

# ========= cookie =========
COOKIE_START = "date_start"
COOKIE_END = "date_end"

STOP_TABLE = "stop_tsubame"
LOG_TABLE = "log_pass_tsubame"

app = Flask(__name__, static_folder=None)


@dataclass(frozen=True)
class DateRange:
    start: dt.date
    end: dt.date

    @property
    def end_exclusive(self):
        return self.end + dt.timedelta(days=1)


def parse_date(s: str) -> dt.date:
    return dt.datetime.strptime(s, "%Y-%m-%d").date()


def get_range(req) -> DateRange:
    s = req.cookies.get(COOKIE_START)
    e = req.cookies.get(COOKIE_END)

    if not s or not e:
        today = dt.date.today()
        return DateRange(today.replace(day=1), today)

    d1 = parse_date(s)
    d2 = parse_date(e)
    return DateRange(min(d1, d2), max(d1, d2))


def db_connect():
    return psycopg2.connect(
        host=PGHOST,
        port=PGPORT,
        dbname=PGDATABASE,
        user=PGUSER,
        password=PGPASSWORD,
        sslmode="disable",
    )


def csv_response(name, text, download):
    r = make_response(text)
    r.headers["Cache-Control"] = "no-store"
    if download:
        r.headers["Content-Disposition"] = f'attachment; filename="{name}"'
        r.headers["Content-Type"] = "application/octet-stream"
    else:
        r.headers["Content-Type"] = "text/csv; charset=utf-8"
    return r


@app.get("/")
def root():
    return send_from_directory(BASE_DIR, "index.html")


@app.post("/set_range")
def set_range():
    s = request.form["start"]
    e = request.form["end"]
    r = redirect("/")
    r.set_cookie(COOKIE_START, s, max_age=86400 * 365)
    r.set_cookie(COOKIE_END, e, max_age=86400 * 365)
    return r


# ---------- trips.csv ----------
@app.get("/trips.csv")
def trips_csv():
    dr = get_range(request)

    sql = """
WITH with_stop AS (
  SELECT
    l.psg_id,
    l.ride_time,
    l.on_off,
    (s.stop_num::text || '. ' ||
     regexp_replace(s.name, '^[0-9]+[.\\.]\\s*', '')
    ) AS stop_name
  FROM log_pass_tsubame l
  JOIN LATERAL (
    SELECT stop_num, name, geom
    FROM stop_tsubame
    ORDER BY l.geom <-> geom
    LIMIT 1
  ) s ON true
  WHERE l.ride_time >= %(s)s
    AND l.ride_time <  %(e)s
),
seq AS (
  SELECT
    psg_id,
    ride_time,
    on_off,
    stop_name,
    SUM(CASE WHEN on_off = 1 THEN 1 ELSE 0 END)
      OVER (PARTITION BY psg_id ORDER BY ride_time) AS trip_no
  FROM with_stop
),
od AS (
  SELECT
    psg_id,
    trip_no,
    (array_agg(stop_name ORDER BY ride_time)
      FILTER (WHERE on_off = 1))[1] AS "from",
    (
      array_agg(stop_name ORDER BY ride_time)
        FILTER (WHERE on_off = 0)
    )[
      array_length(
        array_agg(stop_name ORDER BY ride_time)
          FILTER (WHERE on_off = 0)
      , 1)
    ] AS "to"
  FROM seq
  WHERE trip_no > 0
  GROUP BY psg_id, trip_no
)
SELECT "from","to",COUNT(*) AS trips
FROM od
WHERE "from" IS NOT NULL
  AND "to" IS NOT NULL
  AND "from" <> "to"
GROUP BY "from","to"
ORDER BY "from","to";
"""

    out = io.StringIO()
    w = csv.writer(out)
    w.writerow(["from", "to", "trips"])

    with db_connect() as c:
        with c.cursor() as cur:
            cur.execute(sql, {"s": dr.start, "e": dr.end_exclusive})
            for f, t, n in cur.fetchall():
                w.writerow([f, t, n])

    return csv_response("trips.csv", out.getvalue(),
                        request.args.get("download") == "1")


@app.get("/trips_map.html")
def trips_map_html():
    return send_from_directory(BASE_DIR, "trips_map.html")


@app.get("/<path:filename>")
def any_file(filename):
    p = os.path.join(BASE_DIR, filename)
    if os.path.exists(p):
        return send_from_directory(BASE_DIR, filename)
    return Response("not found", 404)


if __name__ == "__main__":
    app.run(host=APP_HOST, port=APP_PORT)

4.2 index.html(全文・4画面対応)

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>日時指定ダッシュボード(4画面)</title>
<style>
body{font-family:sans-serif;margin:16px}
.row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
.panel{margin-top:16px;border:1px solid #ccc;border-radius:8px;overflow:hidden}
.panel h2{margin:0;padding:8px;background:#f5f5f5}
iframe{width:100%;border:0}
.h1,.h2{height:640px}
.h3,.h4{height:720px}
a.btn{padding:6px 10px;border:1px solid #888;text-decoration:none}
</style>
</head>
<body>

<h1>日時指定(4表示)</h1>

<form method="POST" action="/set_range" class="row">
  <label>開始日 <input type="date" name="start" required></label>
  <label>終了日 <input type="date" name="end" required></label>
  <button type="submit">OK(表示)</button>

  <a class="btn" href="/sample2.csv?download=1">CSV(sample2)</a>
  <a class="btn" href="/ride_summary.csv?download=1">CSV(rides)</a>
  <a class="btn" href="/bus_stops.csv?download=1">CSV(bus_stops)</a>
  <a class="btn" href="/trips.csv?download=1">CSV(trips)</a>
</form>

<div class="panel">
<h2>sample2</h2>
<iframe class="h1" src="/sample2.html"></iframe>
</div>

<div class="panel">
<h2>rides</h2>
<iframe class="h2" src="/rides.html"></iframe>
</div>

<div class="panel">
<h2>bus_map</h2>
<iframe class="h3" src="/bus_map.html"></iframe>
</div>

<div class="panel">
<h2>trips_map</h2>
<iframe class="h4" src="/trips_map.html"></iframe>
</div>

</body>
</html>

4.3 sample2.html(中央寄せ修正含む・該当部分)

#wrap {
  max-width: 1100px;
  margin: 0 auto;
}

5. 最終確認結果

  • ✅ trips_map.html が iframe に表示される

  • ✅ trips.csv ボタンが表示・ダウンロード可能

  • ✅ trips.csv は 指定期間内データのみ

  • ✅ 上段グラフの左寄り問題を解消

  • ✅ 既存画面・既存CSVへの影響なし


6. 総括

本日の作業により、

  • 停留所間トリップ(OD)という新しい分析軸

  • 既存ダッシュボードと同一UX・同一期間指定

  • CSV ⇄ 可視化 ⇄ 再利用 が可能な構成

が完成した。
構造的に拡張しやすく、今後は

  • trips に応じた線の太さ・色分け

  • 特定停留所の強調表示

  • OD の時間帯別分解

なども容易に追加できる状態である。

以上が、本日の最終レポートである。

未分類

メモ残しておかんと、青冷めるので残しておく。

WITH with_stop AS (
  SELECT
    l.psg_id,
    l.ride_time,
    l.on_off,
    -- stop_tsubame.name 先頭の「番号+(. or .)」を落として、
    -- stop_num から "N. " を付け直す(=> "1. 平高山" 形式)
    (s.stop_num::text || '. ' ||
      regexp_replace(s.name, '^[0-9]+[.\.]\s*', '')
    ) AS stop_name
  FROM log_pass_tsubame l
  JOIN LATERAL (
    SELECT stop_num, name, geom
    FROM stop_tsubame
    ORDER BY l.geom <-> geom
    LIMIT 1
  ) s ON true
),
seq AS (
  SELECT
    psg_id,
    ride_time,
    on_off,
    stop_name,
    -- 乗車(1)が出たら trip_no を +1(psg_id 内で乗車ごとにトリップ区切り)
    SUM(CASE WHEN on_off = 1 THEN 1 ELSE 0 END)
      OVER (PARTITION BY psg_id ORDER BY ride_time) AS trip_no
  FROM with_stop
),
od AS (
  SELECT
    psg_id,
    trip_no,
    -- 同一 trip_no の最初の乗車停留所
    (array_agg(stop_name ORDER BY ride_time)
      FILTER (WHERE on_off = 1))[1] AS "from",
    -- 同一 trip_no の最後の降車停留所(降車が複数あっても最後を採用)
    (
      array_agg(stop_name ORDER BY ride_time)
        FILTER (WHERE on_off = 0)
    )[
      array_length(
        array_agg(stop_name ORDER BY ride_time)
          FILTER (WHERE on_off = 0)
      , 1)
    ] AS "to"
  FROM seq
  WHERE trip_no > 0
  GROUP BY psg_id, trip_no
)
SELECT
  "from",
  "to",
  COUNT(*) AS trips
FROM od
WHERE "from" IS NOT NULL
  AND "to" IS NOT NULL
  AND "from" <> "to"
GROUP BY "from", "to"
ORDER BY "from", "to";

これで直る点

  • 表示が 1. 1.平高山 のように重複せず、1. 平高山 形式になります

  • 「降車→次の降車」ではなく、「乗車→降車」OD になります(提示されたCSVの形に一致するはず)


もし「期間指定」も当時入れていたなら、with_stop に条件を足します(例)。

WHERE l.ride_time >= '2025-01-01'::timestamp
  AND l.ride_time <  '2025-02-01'::timestamp

2025,江端さんの忘備録

「結婚はコスパが悪い」という見出しの記事がよく登場しています。
Articles with headlines claiming that “marriage has poor cost performance” appear frequently.

若者を「擁護」または「批判」のいずれかの観点で論じられていますが、もはや、その観点すら「陳腐」です。
They tend to discuss young people either by “defending” or “criticizing” them, but even that framing has already become trite.

ちょっと考えてみたのですが、そもそも「コスパが良いもの」って何だろう、というところから考え直してみました。
I gave it some thought and decided to reconsider the issue, starting from a more basic question: what exactly does it mean for something to be “cost-effective”?

-----

「コスパが良いもの」とは何でしょうか。支払ったコストに対して、得られるリターンが大きく、しかも安定して予測可能なもの、という定義になるでしょう。
What, then, is something that is “cost-effective”? It would likely be defined as something that yields a significant return relative to the cost paid and whose outcome can be predicted reliably.

しかし、この定義を人生の選択に当てはめた瞬間、話はおかしくなります。
However, the moment we apply this definition to life choices, the argument begins to fall apart.

なぜなら、人生における主要な選択――進学、就職、転職、結婚、子育て、介護――のいずれも、投入コストは事前に見積もれても、リターンはほぼ確実に予測不能だからです。
This is because for all major life decisions—education, employment, job changes, marriage, childrearing, caregiving—the costs can be estimated in advance, but the returns are almost certainly unpredictable.

結婚にしても同じです。
The same applies to marriage.

結婚によって得られる幸福や安定は、事前に数値化できませんし、再現性もありません。
The happiness or stability gained through marriage cannot be quantified beforehand, nor can it be reproduced reliably.

むしろ、予期せぬ負担や衝突、責任が増える構造を内包している、と言った方が正確でしょう。
If anything, it would be more accurate to say that marriage structurally contains an increase in unexpected burdens, conflicts, and responsibilities.

つまり、「結婚はコスパが悪い」という主張は、結婚だけを特別に切り出している点で、論理的にフェアではありません。
In other words, the claim that “marriage has poor cost performance” is not logically fair, because it singles out marriage alone.

-----

正確に言えば、「人生を構成するほぼすべての重要イベントは、コスパが悪い」のです。
To be precise, almost all important events that make up a human life are poor in terms of cost performance.

それでも人は、結婚し、子を持ち、誰かのために時間と労力を費やします。
Even so, people marry, have children, and devote time and effort to others.

それは、コスパという尺度が、人生の意思決定を測る物差しとして、決定的に不適合だからです。
That is because cost performance is fundamentally unsuited as a measure for life decisions.

コスパとは、本来「代替可能で、やらなくても困らない行為」に対して使う概念です。
Cost performance is, by nature, a concept meant for actions that are replaceable and non-essential.

スマホの機種選び、サブスクの解約、昼食の店選び。
Choosing a smartphone model, canceling a subscription, deciding where to eat lunch.

そうした「選ばなくても人生が続くもの」にしか、本質的には適用できません。
Essentially, it can only be applied to things that do not determine the continuation of one’s life.

人生の中核にある選択ほど、代替不能で、取り消し不能で、そして説明不能です。
The more central a choice is to one’s life, the more irreplaceable, irreversible, and indescribable it becomes.

それらをコスパで裁ろうとした時点で、議論の前提が崩れているのです。
The moment we try to judge such choices by cost performance, the very premise of the discussion collapses.

この議論を突き詰めていけば、つまるところ、
If we push this argument to its logical extreme,

―― 人生はコスパが悪い
— Life itself has poor cost performance.

に至るだろう、と思います。
That is where we inevitably arrive.

人生をコスパ良く生きたければ、今すぐ『人生を止める』が正解でしょう。
If one truly wanted to live life in a cost-effective way, the correct answer would be to stop living immediately.

とまあ、こんな風に問題を極論に展開して、汎化してウヤムヤにするのは、私の悪い癖です。
Turning an issue into an extreme argument, generalizing it, and then blurring it out like this is one of my bad habits.

だから、そういう語り方は、止めます。
So I will stop speaking about it in that way.

-----

実際のところ、私は「結婚のメリットは説明不能であり」、かつ「先人である既婚者は、結婚のメリットを言語化するのに、ことごとく失敗している」し、そして「結婚はデメリットで説明するのが、とても簡単である」という話をしてきました。
In reality, I have argued that the benefits of marriage are indescribable, and that married predecessors have almost universally failed to verbalize them. Marriage is effortless to explain in terms of its disadvantages.

つまり、人生のイベントにおいて、結婚は、かなり扱いやすい「題材」または「道具」なんですよね。
In that sense, among life events, marriage is a rather convenient “subject” or “tool” to handle.

どうも私には、「結婚はコスパが悪い」という言い回しが、ロジックのすりかえ、あるいは、キレイに整えすぎた抽象化に見えてしまいます。
To me, the phrase “marriage has poor cost performance” looks like a sleight of logic, or an overly polished abstraction.

はっきり言えばいい。
Let’s say it plainly.

(1)「結婚は難しい」
(1) “Marriage is difficult.”

いや、冗談抜きに、ちゃんと調べれば分かることですが、現代の結婚は、恐ろしく難しいのです。
No joking here—anyone who looks into it properly can see that marriage today is extremely difficult.

主に経済的要因が大きいのですが、その後に作られる人間関係の調整や維持を考えると、「1人で生きるのすら難しいのに、2人で生きることの難しさは半端ではない」ことは、思い込みではなく、構造的な事実だと思います。
Economic factors play a significant role, but when you consider the adjustment and maintenance of the human relationships that follow, the fact that “living alone is already difficult, so living as two is far more so” is not an assumption but a structural reality.

(2)「結婚は怖い」
(2) “Marriage is frightening.”

昔は「結婚しないことが怖い」でしたが、今や「結婚をする方が怖い」です。
In the past, it was frightening not to marry; now, it is more frightening to marry.

かつては、「結婚しないこと」は生存戦略に直結していました。結婚することで相互扶助が成立し、食料供給の担保がなされ、子どもという次世代の担い手を得る――結婚は一種の安全保障の枠組みだったのです。
In the past, not marrying was directly tied to survival strategy. Marriage established mutual support, secured a food supply, and produced children as the next generation of bearers—marriage functioned as a security framework.

ところが今は、その「結婚という安全保障の枠組み」は必須ではありません。率直に言えば、国家が相当な額の税を徴収する一方で、医療制度や社会保障制度は、国際比較で見ても、少なくとも先進国の中では相対的に手厚い部類に入ります。
Today, however, that “marriage-based security framework” is no longer essential. While the state collects substantial taxes, its healthcare and social security systems are, by international standards, relatively generous among developed countries.

ニュースでは制度の不備や、こぼれ落ちる人々が取り上げられていますが、それが「ニュースになる規模」に抑え込まれている、と見ることもできます。
The news highlights flaws in these systems and those who fall through the cracks, but it can also be seen that such cases are kept within a scale that remains “newsworthy.”

例えば、日本では毎日50~70人が自殺し、交通事故による死亡も平均すれば毎日7人前後は発生していますが、これらが日々のニュースとして逐一報じられることは多くありません。このような事象を数の相対論で語ることの是非については批判されてもよいと思いますが、これは事実です。
For example, in Japan, 50 to 70 people die by suicide every day, and traffic accidents claim around seven lives per day on average. Yet, these figures are not reported individually as daily news. One may criticize the validity of discussing such matters through numerical relativism, but these are facts.

そのような状況を鑑みれば、結婚とは、あえて不要なリスクを拾いにいく行為だ、と感じる人が増えるのも無理はありません。
Given this situation, it is understandable that more people feel marriage is an act of deliberately taking on unnecessary risk.

つまり結婚とは、「難しい」、そして「怖い」というだけの話だと思うのです。
In short, marriage is simply something that is “difficult” and “frightening.”

わざわざ「コスパ」などと言い換えなくても。
There is no need to rephrase it in terms of “cost performance.”

-----

だからこそ、結婚が「難しい」や「怖い」を超える、別方向の価値を、結婚を経験した人が説明しなければならないのですが、それが恐ろしく難しいのです――少なくとも、私はその試みに失敗しています。
That is precisely why those who have experienced marriage must explain the value that goes beyond “difficulty” and “fear,” but that task is challenging—at least, I have failed at it.

なぜなら、結婚は「難しい」と「怖い」の裏付けだけは、しっかり取れている、という現実があるからです。
This is because the evidence supporting marriage as “difficult” and “frightening” is firmly in place.

まあ、だからこの問題は、「打つ手なし」で行くしかないのだろう、と本気で思っています。
So, I seriously think that this problem may be one with “no effective solution.”

-----

野党の国会議員
Opposition lawmaker

「総理。総理は昨今の少子化問題に対して、本気で取り組んでいるのですか。この“成果のなさ”は一体なんですか!」
“Prime Minister, are you truly addressing the recent declining birthrate issue? What is the reason for this complete lack of results?”

総理
Prime Minister

「率直に言えば、『打つ手なし』が正直な感想です」
“To be honest, my candid impression is that there is no effective solution.”

と言えれば、総理大臣もラクだろうなぁ、と思うのですが――もちろん、そんなことは言えないでしょう。
If a prime minister could say that, life would be much easier—but of course, such a statement would never be allowed.

(内閣が倒れます)
(The cabinet would collapse.)

―― 『結婚の価値、良さ、メリット』を、若者に対して言語化できない、私(たち)の無能

2025,江端さんの忘備録

先日の人間ドックで、ついに、視力が両方1.0を下回りました。0.9と0.6です。
At my recent medical checkup, my visual acuity finally fell below 1.0 in both eyes: 0.9 and 0.6.

まあ、老眼鏡 ーー もとい、「リーディンググラス」なしでは、生活できない体(目)なので、まだマシなほうかもしれませんが。
Well, since I can no longer live without reading glasses—no, “reading glasses,” to be precise—this may still be on the better side.

私、リーディンググラスは、100均のものを使っていました。
I had been using reading glasses bought at a 100-yen shop.

これって、きちんとした眼鏡を作るべきだ、という意見がありますが、一度ちゃんと作ったことがあるのですが、ぶっちゃけ、この眼鏡が『役に立たなかった』。
Some people say you should get proper prescription glasses, and I did try that once, but frankly, those glasses were “not useful.”

私の視力は毎日変動する(基本的には悪化する方向に)ので、定期的に眼鏡を交換していく必要があり、ちゃんと作った眼鏡の方が、扱いが面倒なんですよ。
My eyesight fluctuates daily (basically worsening), so I need to replace my glasses regularly, and properly made ones are actually more troublesome to deal with.

-----

ただ、100均の眼鏡は、耐久性が弱い。簡単に壊れるので、これはそれで面倒なのです。
That said, 100-yen glasses are not durable. They break easily, which is troublesome in its own way.

で、どうしたかというと、Amazonで購入しています。
So what did I do? I buy them on Amazon.


(リンク先に飛びます)。
(You’ll be taken to the link.)

2000円くらいですね。100均の20倍くらいという、超高級品です。
They cost about 2,000 yen—roughly 20 times the price of a 100-yen item, an authentic luxury product.

この丸眼鏡、気に入っているんです。2つ購入しました。
I like these round glasses. I bought two of them.

これで、丸刈りにしたら ーー 鏡の前に、東京裁判A級戦犯の東条首相が立っていて、驚きました。
When I shaved my head with these on, I was startled to see Prime Minister Tojo, an A-class war criminal from the Tokyo Trials, standing in front of the mirror.

# 東京裁判における「事後法適用(遡及処罰)の問題」については、別のページを探して下さい。
# For the issue of ex post facto application of law (retroactive punishment) in the Tokyo Trials, please look it up on another page.

-----

私は目付きが悪く、加えて目の下にクマが常駐しているのですが、この眼鏡でごまかせることが分かりました。
I have an unfriendly look in my eyes and permanent dark circles under them, but I found that these glasses help disguise them.

さらに、長女から、結婚式に際して「髪の毛を染めてくること」を命じられ、これが、なんとなく『年齢をごまかせる』気がしたので、それから、定期的に髪を染めて、こんな形相で生活しています。
Furthermore, my eldest daughter ordered me to dye my hair for her wedding, and since that somehow made me feel I could “fake my age,” I’ve been dyeing my hair regularly and living with this appearance.

-----

先日、Amazonで購入したメガネの鼻パッドが外れていることが分かり、新しいのを購入するか迷っていたのですが ―― 『日常品の修理は、私の趣味』でもあるので、直してみることにしました。
The other day, I noticed that the nose pads of the glasses I bought on Amazon had come off. I hesitated about buying new ones, but since “repairing everyday items is my hobby,” I decided to fix them.

[Aquafix] メガネ鼻パッド シリコン製 メガネ修理キット 眼鏡 鼻あて 交換セット ドライバー ネジ クロス ピンセット ケース 付き パッド跡軽減 メガネずり落ち防止 ズレ防止

この395円のセットだけで、工具が入っており、鼻パッドも50個くらい入っていたので、まあ、死ぬまで在庫は持ちそうです。
This 395-yen set alone included tools and about 50 nose pads, so I’ll probably have enough stock for the rest of my life.

2つの眼鏡が、それぞれ壊れていたので、曲がっていた金具をラジオペンチで直して、鼻パッドを装着しました。
Since both pairs of glasses were damaged, I straightened the bent metal parts with radio pliers and attached the nose pads.

-----

丸眼鏡、気に入っていますので、もう一つ購入しようかとも思っています。こんなヤツです。
I like round glasses, so I’m thinking of buying another pair—something like this.

なんか、明治時代の「博士」がつけていたような、レトロな感じしません?
Doesn’t it feel retro, like something a “professor” from the Meiji era would wear?

なにしろ、私ってば、「博士」ですから(今年の9月17日から)。
After all, I am a “doctor”?starting September 17 this year.

昨日から、鏡の前に立つたびに、東条英機元首相が登場して、ビックリしています。

2025,江端さんの忘備録

最近、嫁さんと話していると、お互いの話に齟齬が発生していることに気がつきます。
Recently, when I talk with my wife, I’ve started to notice that discrepancies arise in what we each remember.

以前話をした内容を再度話しているとか、以前の会話の日時がずれているとか、そういうことが普通に発生します。
We often end up repeating conversations we’ve already had or disagreeing about when a particular conversation took place.

-----

言うまでもなく、これは記憶力の劣化によるものです。主な原因は加齢だと思いますが、以前から私たち夫婦にはこういうことがあったので、どの時点をもって加齢の劣化と断定できるのか、判断が難しい状況です。
Needless to say, this is due to a decline in memory. Aging is probably the leading cause, but since these things have been happening between us for a long time, it’s hard to say at what point we can definitively attribute them to aging.

加えて、夫婦のどちらの主張が「真」であるかを証明する手段がありません。
On top of that, there is no way to prove which of the two accounts is actually “true.”

私は、これを「記憶の一般相対性理論」と呼んでいます。
I call this the “General Theory of Relativity of Memory.”

江端家夫婦は、このような事象に対して『どちらが正しい』という主張をしません。
In the Ebata household, we don’t insist on determining which of us is right in such situations.

そして、その事象を再確認した時点で『記憶を再配置する』ということで、合意できています。
Instead, we agree to “rearrange our memories” at the point when we reconfirm what matters.

例えば、
For example,

(1)「眼鏡のフレームが壊れた」と私が話したのが、1日前だったのか、3日前だったのか
(1) Whether I said “my glasses frame broke” one day ago, or three days ago

(2)「晩ごはんにカレーを食べた」のが、昨日だったのか、一昨日だったのか
(2) Whether we had curry for dinner yesterday, or the day before yesterday

(3)「あの病院に行こう」と最初に言い出したのが、私だったのか、嫁さんだったのか
(3) Whether it was my wife or me who first suggested “let’s go to that hospital.”

などの差異があったとしても、それらの真偽を検証するより、その対策を考える方が、圧倒的に建設的です。
Even if such discrepancies exist, it is far more constructive to think about how to deal with them than to try to verify which is true.

-----

実際のところ、記憶というのは、加齢に関係なく、常に書き換えられ続けていることが、学術的にも知られています。
In fact, it is well established that memory is constantly rewritten, regardless of age.

心理学者バートレットは、1930年代の研究で、人間の記憶は「保存」ではなく「再構成」であることを示しました。
In the 1930s, psychologist Frederic Bartlett showed that human memory is not a matter of storage, but of reconstruction.

また近年では、記憶は思い出すたびに再固定化され、その過程で内容が変化しうること(再固定化理論)も明らかになっています。
More recently, it has been shown that memories are reconsolidated each time they are recalled and can change during that process.

さらに、エリザベス・ロフタスの有名な実験では、質問の仕方ひとつで、人は実際には起きていない出来事すら「記憶してしまう」ことが示されています。
Furthermore, Elizabeth Loftus’s famous experiments demonstrated that the way a question is phrased can lead people to “remember” events that never actually happened.

要するに、私たちが信じている「自分の記憶」というものは、想像以上に脆く、状況依存で、そして都合よく作り替えられている代物なのです。
In short, what we believe to be “our own memories” are far more fragile, context-dependent, and conveniently reconstructed than we tend to imagine.

そういう「常に書き換えられ続けている自分の記憶」なんぞで、真偽を争うのは、時間の無駄です。
Arguing over truth and falsehood based on such “constantly rewritten memories” is a waste of time.

-----

私たちは、『自分の記憶違いによる失敗を起こした場合、隠さずにできるだけ共有していこう』と決めています。
We have decided that if we make a mistake due to faulty memory, we will share it openly rather than hide it.

お互いの劣化を告白して「傷を舐め合う」関係を構築し、幸せなシニア生活を過ごそうと思っているからです。
That’s because we want to build a relationship where we confess our mutual decline, “lick each other’s wounds,” and enjoy a happy senior life together.

-----

ただ、これを、物忘れ、老化現象、または、軽度認知障害(MCI)などと呼ぶのは、私たち自身を傷つける文言です。
However, calling this forgetfulness, aging, or mild cognitive impairment (MCI) is wording that hurts us.

こんな呼びかたは良くないと思います。
I don’t think this kind of labeling is appropriate.

-----

江端:「という訳で、新しい言い方を考えた」
Ebata: “So, I came up with a new way of putting it.”

嫁さん:「何?」
My wife: “What is it?”

江端:「『そっちの世界線では“3日”だったか。こっちの世界線では“1日”だったけどな』という――世界線という概念を提唱したい」
Ebata: “In your worldline, it was ‘three days ago.’ In mine, it was ‘one day ago.’ I want to propose the concept of ‘worldlines.’”

-----

これ、悪くないと思うんですよ。
I think this isn’t a bad idea.

真偽の証明ができない事象であれば、その両方の事象が同時に存在した、という解釈がもっとも合理的です。
If the truth of an event cannot be proven, then interpreting both versions as having existed simultaneously is the most rational approach.

という訳で ーー 老化現象でも軽度認知障害(MCI)でもない、新しいコンセプト「世界線」を流行らせていきましょう。
Let’s popularize a new term—“worldlines”—that is neither aging nor mild cognitive impairment.

ともあれ、午前0時を過ぎてから、NetFlixから「シュタインズ・ゲート"を"選択」するのは止めましょう。

2025,江端さんの忘備録

「不滅の文明 古代エジプト 三千年を超えて」を、視聴しました。
I watched “The Immortal Civilization: Ancient Egypt Beyond Three Thousand Years.”

それにしても、NHK番組ネタばかりで申し訳ありません。録画しているものがNHKばかりなので。
That said, I apologize for writing so much about NHK programs. Most of what I record is NHK.

私、以前からエジプトの王朝が3000年も続いてきたことをかなり不思議に思ってきました。
I have long found it quite puzzling that Egyptian dynasties continued for as long as 3,000 years.

もっとも、エジプトの王朝もぶっつづけで続いてきた訳ではないようですが。
Of course, it does not seem that the Egyptian dynasties continued uninterrupted.

シーケンシャルな統一王朝(という体裁では)、日本の皇室(天皇陛下)の方がぶっつぎりで長い(多分、世界最長)ですが、これも「いわく付き」ではあります(神話の世界を合体する等)。
In terms of a sequentially unified dynasty (at least in form), Japan’s Imperial House (the Emperor) is by far longer—probably the longest in the world—but that too comes with its own complications, such as the incorporation of mythological narratives.

-----

私、「文学」というものを「エンターテーメント」としてのみ把握していたのですが、この番組の中で、文学が「物語を使った権力基盤の維持」という装置であることをしって、『ああ、なるほど』と思いました。
I had understood “literature” only as entertainment. Still, through this program, I realized that literature can serve as a mechanism for maintaining power through narrative, and I found myself thinking, “Ah, I see.”

エジプトの例では、王や神々の系譜、死後の世界、秩序(マアト)と混沌の対立といった物語が、神殿の壁画やパピルス、葬送儀礼を通じて繰り返し語られ、「王は神の代理人であり、この世界の秩序を維持する存在である」という物語を、民衆の側に自然に刷り込む装置として機能していました。
In the Egyptian case, stories about the lineages of kings and gods, the afterlife, and the opposition between order (Ma’at) and chaos were repeatedly conveyed through temple wall reliefs, papyri, and funerary rituals, functioning as a mechanism that naturally imprinted on the populace the narrative that “the king is the representative of the gods and the one who maintains order in this world.”

王権は単なる武力や行政ではなく、宇宙秩序そのものと結び付けられ、王朝交代や社会変動があっても、「支配されること」自体が疑問視されにくい構造が長期にわたって維持されたのだと思います。
Royal authority was not merely a matter of force or administration; it was bound to the cosmic order itself, creating a structure in which being ruled was rarely questioned, even amid dynastic changes or social upheaval, and this structure endured for a long time.

比して、日本では、当然「古事記」や「日本書紀」が、神話と系譜を通じて天皇制の正統性を説明し、国家形成期の権力基盤を安定させるための「物語」として使われていることも明らかです。
By contrast, in Japan, the Kojiki and Nihon Shoki were used as “stories” to legitimize the imperial system through myths and genealogies, thereby stabilizing the power base during the period of state formation.

ここでも文学は娯楽ではなく、「なぜこの秩序が正しいのか」「なぜ従うのが自然なのか」を人々に理解させるための、きわめて実務的で強力な社会装置だったのだ、と改めて感じました。
Here again, literature was not entertainment but an efficient and powerful social device that helped people understand why a given order was correct and why obedience to it felt natural.

だから、まあ、以前から私が言っていることですが、
So, as I have been saying for some time,

―― 権威とは、ブロックチェーンのこと
—authority is, in essence, a blockchain.

なんですよ。
That’s what it is.

(↑ Jump to the column)

つまるところ、
In short,

ーー フィクション(でたらめ)も、1000年間くらいチェーンを繋げ続ければ、立派な権力装置となる
—even fiction (nonsense), if it keeps its chain linked for about a thousand years, becomes a fully fledged apparatus of power.

ということです。
That is what this means.

三国志などのドラマでは、諸葛孔明が『漢王朝の復興』としつこく繰り返します。
In dramas such as Romance of the Three Kingdoms, Zhuge Liang persistently repeats the phrase “the restoration of the Han dynasty.”

あの時点で、漢王朝って高々500年くらいですよね。彼らが、「◯◯の復興」とかモットーとして掲げる戦争は、はっきりいって「迷惑」だっただろうなぁ、と思うんですよ。
From our vantage point, the Han dynasty lasted only about 500 years. I can’t help thinking that wars fought under mottos like “the restoration of XX” must have been, frankly, a nuisance for those involved.

「北伐」という名前の侵略を、なんども喰らわされてきた魏国は『蜀(漢王朝の末裔を主張)って国は、本当に面倒くせい』と思っていたことは、間違いないでしょう。私なら「いいかげんに、過去の栄光を忘れろ」と言いたいと思う。
The state of Wei, repeatedly subjected to invasions called “Northern Expeditions,” must surely have thought, “Shu (which claims to be the descendant of the Han dynasty) is really a pain.” If it were me, I would have said, “Just leave us alone.”

-----

宗教なんかもそうですよね。キリスト教も2000年くらいで、イスラム教はそれより短い。
Religion is similar in this regard. Christianity is about 2,000 years old, and Islam is even younger.

ましてや、統一協会なんか、発足から、高々70年くらいですよね。
And the Unification Church, for instance, has existed for only about seventy years since its founding.

あんなもんに、人生賭けて、献金して、来世で一発逆転する、なんて発想、本当にどうかしているじゃないか、と思うのです。
Betting one’s life on something like that, donating money, and hoping for a dramatic comeback in the afterlife strikes me as a truly misguided idea.

歴史を勉強しろ、歴史を。
Study history—history.

殆どの振興の宗教の寿命は、数年。長くて十数年。
The lifespan of most emerging religions is a few years at best, perhaps a dozen years if they last long.

100年以上維持できたら「ようやく一人前」と言えるようなしろものなんですよ。
If one manages to survive for more than a hundred years, it can already be called “quite something.”

-----

『私の書いてきた大量のコラムやメモが、権力装置として機能する1000年後というのはあるだろうか』と考えたのですが ――
I wondered whether there might be a time, a thousand years from now, when the vast number of columns and notes I have written would function as an apparatus of power—

私はその可能性の一欠片も見つけることができませんでした。
But I was unable to find even the slightest fragment of such a possibility.

『計算とシステム評価から、敬愛に至る』