
どこだよ、ここ
江端智一のホームページ

どこだよ、ここ
以下は,前述の内容を統合した matsim_crs_verified_exporter.html 解説書(統合版) である.そのまま README,論文補助資料,あるいはツール内 Help セクションとして利用可能である.
matsim_crs_verified_exporter.html( \\wsl$\Ubuntu\home\tomoi\matsim-example-project\scenarios\equil3\output)は,MATSim の出力ファイルからエージェント(person)の移動履歴を抽出し,
を生成するブラウザ完結型ツールである.

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>MATSim Drag&Drop → TSV + GeoCSV (auto zone w/ hint)</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 14px; }
#drop { border: 2px dashed #777; border-radius: 12px; padding: 18px; }
#drop.drag { background: #f2f2f2; }
.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: end; margin-top: 12px; }
label { font-size: 12px; color: #444; display: block; }
input, select { padding: 6px; width: 420px; max-width: 95vw; }
select { width: 320px; }
button { padding: 8px 12px; }
pre { background: #fafafa; border: 1px solid #ddd; padding: 10px; height: 40vh; overflow: auto; margin-top: 10px; }
.meta { margin-top: 10px; font-size: 12px; color: #333; white-space: pre-wrap; }
code { background: #eee; padding: 0 4px; border-radius: 4px; }
.small { font-size: 12px; color: #555; }
</style>
</head>
<body>
<h2>MATSim:network+events(ドラッグ)→ TSV(平面)+ GeoCSV(経緯度)</h2>
<div id="drop">
ここに <b>output_network.xml.gz</b> と <b>output_events.xml.gz</b> をドラッグ&ドロップ
<div style="margin-top:8px" class="small">
TSV列:<code>time person_id x y type link</code><br>
GeoCSV列:<code>Source,Longitude,Latitude,Timestamp,Distance,Speed,Transport</code><br>
本ツールは「町田(広袴)など、想定位置に近いゾーン」を自動選択します。network記載CRSは参考表示に留めます(誤記の可能性があるため)。
</div>
</div>
<div class="row">
<div>
<label>対象イベント type(空なら「link+personが解決できたもの全て」)</label>
<input id="types" value="entered link,left link,departure,arrival,vehicle enters traffic,actstart,actend" />
</div>
<div>
<label>TSV出力ファイル名</label>
<input id="outnameTsv" value="traj_all.tsv" />
</div>
</div>
<div class="row">
<div>
<label>GeoCSV出力ファイル名</label>
<input id="outnameCsv" value="traj_all_geo.csv" />
</div>
<div>
<label>Source(person_id)のゼロ埋め桁数</label>
<input id="srcWidth" value="6" style="width:120px" />
</div>
<div>
<label>Timestamp日付(固定)</label>
<input id="baseDate" value="2026-01-01" style="width:140px" />
</div>
</div>
<div class="row">
<div>
<label>地域ヒント(経度,緯度)※ゾーン自動選択に使用</label>
<input id="hintLonLat" value="139.50,35.60" style="width:240px" />
</div>
<div class="small" style="max-width:900px">
町田(広袴)想定ならこのままで良いです。別地域なら変更してください(例:福岡なら 130.40,33.60)。
</div>
</div>
<div class="row">
<div>
<label>ゾーン(自動選択結果/手動上書き可)</label>
<select id="zoneSelect" disabled></select>
</div>
<div style="max-width:980px">
<div class="small">
「ヒント座標に近いゾーン」を自動採用します。意図した地域と合わない場合はここで手動変更してください。<br>
network記載CRS(EPSG:xxxx)も表示しますが、誤記の可能性があるため自動採用には使いません。
</div>
<div class="meta" id="zoneCandidates"></div>
</div>
</div>
<div class="row">
<button id="run" disabled>生成してダウンロード(TSV+CSV)</button>
</div>
<div class="meta" id="status">未読み込み</div>
<pre id="preview"></pre>
<script>
let networkFile = null;
let eventsFile = null;
const drop = document.getElementById("drop");
const runBtn = document.getElementById("run");
const statusEl = document.getElementById("status");
const previewEl= document.getElementById("preview");
const zoneSelect = document.getElementById("zoneSelect");
const zoneCandidatesEl = document.getElementById("zoneCandidates");
function setStatus(msg){ statusEl.textContent = msg; }
function setPreview(msg){ previewEl.textContent = msg; }
drop.addEventListener("dragover", (e)=>{ e.preventDefault(); drop.classList.add("drag"); });
drop.addEventListener("dragleave", ()=> drop.classList.remove("drag"));
drop.addEventListener("drop", (e)=>{
e.preventDefault();
drop.classList.remove("drag");
networkFile = null;
eventsFile = null;
for (const f of e.dataTransfer.files) {
const n = f.name.toLowerCase();
if (n.includes("network") && n.endsWith(".gz")) networkFile = f;
if (n.includes("events") && n.endsWith(".gz")) eventsFile = f;
}
zoneSelect.disabled = true;
zoneSelect.innerHTML = "";
zoneCandidatesEl.textContent = "";
if (networkFile && eventsFile) {
runBtn.disabled = false;
runBtn.textContent = "生成してダウンロード(TSV+CSV)";
setStatus(`準備OK\nnetwork: ${networkFile.name}\nevents : ${eventsFile.name}\n「生成」を押してください`);
} else {
runBtn.disabled = true;
setStatus("network/events の両方が必要です(ファイル名に network / events を含む .gz を想定)");
}
});
// ---------- gzip streaming decode ----------
async function* ungzipTextChunks(file) {
const ds = new DecompressionStream("gzip");
const textStream = file.stream().pipeThrough(ds).pipeThrough(new TextDecoderStream("utf-8"));
const reader = textStream.getReader();
try {
while (true) {
const {value, done} = await reader.read();
if (done) break;
if (value) yield value;
}
} finally {
reader.releaseLock();
}
}
// ---------- attribute parse ----------
function parseAttrs(tagText) {
const attrs = {};
const re = /(\w+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
let m;
while ((m = re.exec(tagText)) !== null) {
attrs[m[1]] = (m[2] !== undefined) ? m[2] : m[3];
}
return attrs;
}
// ---------- JGD2011 Japan Plane Rectangular (zones 1..19) params ----------
const JPRCS = {
1: {lat0:33, lon0:129},
2: {lat0:33, lon0:131},
3: {lat0:36, lon0:132.1666666667},
4: {lat0:33, lon0:133.5},
5: {lat0:36, lon0:134.3333333333},
6: {lat0:36, lon0:136},
7: {lat0:36, lon0:137.1666666667},
8: {lat0:36, lon0:138.5},
9: {lat0:36, lon0:139.8333333333},
10: {lat0:40, lon0:140.8333333333},
11: {lat0:44, lon0:140.25},
12: {lat0:44, lon0:142.25},
13: {lat0:44, lon0:144.25},
14: {lat0:26, lon0:142},
15: {lat0:26, lon0:127.5},
16: {lat0:26, lon0:124},
17: {lat0:26, lon0:131},
18: {lat0:20, lon0:136},
19: {lat0:26, lon0:154},
};
function tmParamsFromZone(zone) {
const z = JPRCS[zone];
if (!z) return null;
const epsg = 6669 + zone; // 6670..6688
return {
epsg,
zone,
a: 6378137.0, // GRS80
f: 1.0 / 298.257222101, // GRS80
k0: 0.9999,
lat0: z.lat0 * Math.PI/180,
lon0: z.lon0 * Math.PI/180,
fe: 0.0,
fn: 0.0,
lat0deg: z.lat0,
lon0deg: z.lon0,
};
}
// ---------- inverse Transverse Mercator (verification-grade) ----------
function invTM(x, y, p) {
const a = p.a, f = p.f, k0 = p.k0;
const e2 = 2*f - f*f;
const ep2 = e2 / (1 - e2);
const x1 = (x - p.fe) / k0;
const y1 = (y - p.fn) / k0;
const A0 = 1 - e2/4 - 3*e2*e2/64 - 5*e2*e2*e2/256;
const A2 = 3*e2/8 + 3*e2*e2/32 + 45*e2*e2*e2/1024;
const A4 = 15*e2*e2/256 + 45*e2*e2*e2/1024;
const A6 = 35*e2*e2*e2/3072;
function meridional(phi) {
return a*(A0*phi - A2*Math.sin(2*phi) + A4*Math.sin(4*phi) - A6*Math.sin(6*phi));
}
const M0 = meridional(p.lat0);
const M = M0 + y1;
const mu = M / (a*A0);
const e1 = (1 - Math.sqrt(1-e2)) / (1 + Math.sqrt(1-e2));
const J1 = 3*e1/2 - 27*Math.pow(e1,3)/32;
const J2 = 21*e1*e1/16 - 55*Math.pow(e1,4)/32;
const J3 = 151*Math.pow(e1,3)/96;
const J4 = 1097*Math.pow(e1,4)/512;
const fp = mu + J1*Math.sin(2*mu) + J2*Math.sin(4*mu) + J3*Math.sin(6*mu) + J4*Math.sin(8*mu);
const sinfp = Math.sin(fp), cosfp = Math.cos(fp), tanfp = Math.tan(fp);
const N1 = a / Math.sqrt(1 - e2*sinfp*sinfp);
const R1 = a*(1-e2) / Math.pow(1 - e2*sinfp*sinfp, 1.5);
const T1 = tanfp*tanfp;
const C1 = ep2*cosfp*cosfp;
const D = x1 / N1;
const Q1 = N1*tanfp / R1;
const Q2 = (D*D)/2;
const Q3 = (5 + 3*T1 + 10*C1 - 4*C1*C1 - 9*ep2) * Math.pow(D,4)/24;
const Q4 = (61 + 90*T1 + 298*C1 + 45*T1*T1 - 252*ep2 - 3*C1*C1) * Math.pow(D,6)/720;
const lat = fp - Q1*(Q2 - Q3 + Q4);
const Q5 = D;
const Q6 = (1 + 2*T1 + C1) * Math.pow(D,3)/6;
const Q7 = (5 - 2*C1 + 28*T1 - 3*C1*C1 + 8*ep2 + 24*T1*T1) * Math.pow(D,5)/120;
const lon = p.lon0 + (Q5 - Q6 + Q7) / cosfp;
return {lat, lon}; // radians
}
function median(arr) {
if (!arr.length) return NaN;
const a = [...arr].sort((x,y)=>x-y);
const mid = Math.floor(a.length/2);
return (a.length % 2) ? a[mid] : (a[mid-1] + a[mid]) / 2;
}
function padLeft(s, w) {
s = String(s);
if (s.length >= w) return s;
return "0".repeat(w - s.length) + s;
}
function secToTimestamp(secStr, baseDate) {
const sec = Math.floor(parseFloat(secStr || "0"));
const hh = Math.floor(sec / 3600);
const mm = Math.floor((sec % 3600) / 60);
const ss = sec % 60;
const HH = String(hh).padStart(2, "0");
const MM = String(mm).padStart(2, "0");
const SS = String(ss).padStart(2, "0");
return `${baseDate} ${HH}:${MM}:${SS}`;
}
// ---------- network parse + CRS detect + sample nodes ----------
async function parseNetworkStream(networkFile) {
const nodes = new Map(); // id -> [x,y]
const links = new Map(); // id -> [from,to]
let crsText = "";
const sampleXY = [];
const SAMPLE_MAX = 250;
let buf = "";
let nodeCount = 0;
let linkCount = 0;
const keepMax = 2_000_000;
let headerChecked = false;
for await (const chunk of ungzipTextChunks(networkFile)) {
if (!headerChecked) {
const head = (buf + chunk).slice(0, 200000);
const m = /coordinateReferenceSystem[^>]*>([^<]+)</i.exec(head);
if (m) crsText = m[1].trim();
headerChecked = true;
}
buf += chunk;
while (true) {
const iNode = buf.indexOf("<node ");
const iLink = buf.indexOf("<link ");
let i = -1, kind = "";
if (iNode === -1 && iLink === -1) break;
if (iNode !== -1 && (iLink === -1 || iNode < iLink)) { i = iNode; kind = "node"; }
else { i = iLink; kind = "link"; }
const j = buf.indexOf(">", i);
if (j === -1) break;
const tagText = buf.slice(i, j+1);
buf = buf.slice(j+1);
const a = parseAttrs(tagText);
if (kind === "node") {
const id = a.id, x = a.x, y = a.y;
if (id != null && x != null && y != null) {
const xx = parseFloat(x), yy = parseFloat(y);
nodes.set(id, [xx, yy]);
nodeCount++;
if (sampleXY.length < SAMPLE_MAX) sampleXY.push([xx, yy]);
}
} else {
const id = a.id, fr = a.from, to = a.to;
if (id != null && fr != null && to != null) {
links.set(id, [fr, to]);
linkCount++;
}
}
if ((nodeCount + linkCount) % 20000 === 0 && (nodeCount + linkCount) > 0) {
setStatus(`network解析中… nodes=${nodeCount}, links=${linkCount}`);
await new Promise(r => setTimeout(r, 0));
}
}
if (buf.length > keepMax) buf = buf.slice(-keepMax);
}
setStatus(`network解析完了 nodes=${nodeCount}, links=${linkCount}\nnetwork記載CRS=${crsText || "(unknown)"}\nゾーン選択中…`);
return {nodes, links, crsText, sampleXY};
}
function linkMidXY(linkId, nodes, links) {
const lt = links.get(linkId);
if (!lt) return null;
const p1 = nodes.get(lt[0]);
const p2 = nodes.get(lt[1]);
if (!p1 || !p2) return null;
return [(p1[0] + p2[0]) / 2.0, (p1[1] + p2[1]) / 2.0];
}
// ---------- zone choose by lon/lat hint ----------
function parseHint() {
const s = (document.getElementById("hintLonLat").value || "").trim();
const m = s.split(",").map(x => x.trim()).filter(Boolean);
if (m.length !== 2) return {hintLon: 139.5, hintLat: 35.6};
const hintLon = parseFloat(m[0]);
const hintLat = parseFloat(m[1]);
if (!Number.isFinite(hintLon) || !Number.isFinite(hintLat)) return {hintLon: 139.5, hintLat: 35.6};
return {hintLon, hintLat};
}
function inferZonesByHint(sampleXY, hintLon, hintLat) {
const results = [];
for (let zone = 1; zone <= 19; zone++) {
const p = tmParamsFromZone(zone);
const lons = [];
const lats = [];
for (const [x,y] of sampleXY) {
const ll = invTM(x, y, p);
const lon = ll.lon * 180/Math.PI;
const lat = ll.lat * 180/Math.PI;
if (Number.isFinite(lon) && Number.isFinite(lat)) {
lons.push(lon);
lats.push(lat);
}
}
const mlon = median(lons);
const mlat = median(lats);
// ヒントからの距離(度)…経度は緯度でスケール(ざっくり)
const dLon = (mlon - hintLon) * Math.cos((hintLat*Math.PI)/180);
const dLat = (mlat - hintLat);
const dist = Math.sqrt(dLon*dLon + dLat*dLat);
const inJapan = (mlon >= 120 && mlon <= 155 && mlat >= 20 && mlat <= 47);
const score = dist + (inJapan ? 0 : 999);
results.push({
zone,
epsg: p.epsg,
mlon,
mlat,
score,
dist,
lon0: p.lon0deg,
lat0: p.lat0deg,
inJapan
});
}
results.sort((a,b)=>a.score - b.score);
return results;
}
function renderZoneUI(results, defaultZone, crsText, hintLon, hintLat) {
zoneSelect.innerHTML = "";
for (let z=1; z<=19; z++) {
const opt = document.createElement("option");
opt.value = String(z);
opt.textContent = `第${z}系(zone ${z} / EPSG:${6669+z})`;
if (z === defaultZone) opt.selected = true;
zoneSelect.appendChild(opt);
}
zoneSelect.disabled = false;
const top = results.slice(0, 6);
let msg = `地域ヒント: (${hintLon.toFixed(4)}, ${hintLat.toFixed(4)})\n`;
msg += "ゾーン候補(ヒント近傍・上位):\n";
for (const r of top) {
msg += ` zone${r.zone} EPSG:${r.epsg} median(lon,lat)=(${r.mlon.toFixed(6)}, ${r.mlat.toFixed(6)}) dist≈${r.dist.toFixed(3)}deg\n`;
}
msg += "\n";
msg += `network記載CRS: ${crsText || "(unknown)"}\n`;
msg += "※networkのCRSは誤記の可能性があるため、自動採用は行いません。\n";
zoneCandidatesEl.textContent = msg;
}
// ---------- events parse + build TSV+GeoCSV ----------
async function buildOutputs(eventsFile, nodes, links, wantTypes, outTsvName, outCsvName, zone, srcWidth, baseDate) {
const tmParams = tmParamsFromZone(zone);
const tsvHeader = "time\tperson_id\tx\ty\ttype\tlink\n";
const csvHeader = "Source,Longitude,Latitude,Timestamp,Distance,Speed,Transport\n";
const tsvParts = [tsvHeader];
const csvParts = [csvHeader];
let carry = "";
let outLinesTsv = 1;
let outLinesCsv = 1;
const veh2person = new Map();
let matched = 0;
let skippedType = 0, skippedNoLink = 0, skippedNoXY = 0, skippedNoPersonResolved = 0;
const previewLines = [];
const previewMax = 80;
let lastUi = performance.now();
for await (const chunk of ungzipTextChunks(eventsFile)) {
let text = carry + chunk;
carry = "";
while (true) {
const i = text.indexOf("<event ");
if (i === -1) { carry = text; break; }
const j = text.indexOf("/>", i);
if (j === -1) { carry = text.slice(i); break; }
const tagText = text.slice(i, j+2);
text = text.slice(j+2);
const a = parseAttrs(tagText);
const type = a.type || "";
if (wantTypes && wantTypes.size && !wantTypes.has(type)) { skippedType++; continue; }
if (type === "PersonEntersVehicle") {
const p = a.person, v = a.vehicle;
if (p && v) veh2person.set(v, p);
continue;
}
const link = a.link;
if (!link) { skippedNoLink++; continue; }
let pid = a.person || "";
if (!pid) {
const v = a.vehicle || "";
if (v && veh2person.has(v)) pid = veh2person.get(v);
}
if (!pid) { skippedNoPersonResolved++; continue; }
const lt = links.get(link);
if (!lt) { skippedNoXY++; continue; }
const p1 = nodes.get(lt[0]);
const p2 = nodes.get(lt[1]);
if (!p1 || !p2) { skippedNoXY++; continue; }
const x = (p1[0] + p2[0]) / 2.0;
const y = (p1[1] + p2[1]) / 2.0;
const t = a.time ?? "0";
// TSV
const tsvLine = `${t}\t${pid}\t${x}\t${y}\t${type}\t${link}\n`;
tsvParts.push(tsvLine);
outLinesTsv++;
// GeoCSV
const ll = invTM(x, y, tmParams);
const lon = (ll.lon * 180/Math.PI).toFixed(6);
const lat = (ll.lat * 180/Math.PI).toFixed(6);
const src = padLeft(pid, srcWidth);
const ts = secToTimestamp(t, baseDate);
const csvLine = `${src},${lon},${lat},${ts},,,${type}\n`;
csvParts.push(csvLine);
outLinesCsv++;
matched++;
if (previewLines.length < previewMax) previewLines.push(csvLine);
const now = performance.now();
if (now - lastUi > 250) {
setStatus(
`events処理中… TSV行=${outLinesTsv}, CSV行=${outLinesCsv}(マッチ=${matched})\n` +
`使用ゾーン: zone${zone} EPSG:${tmParams.epsg}(lon0=${tmParams.lon0deg})\n` +
`skip: type=${skippedType}, noLink=${skippedNoLink}, noXY=${skippedNoXY}, noPersonResolved=${skippedNoPersonResolved}\n` +
`veh2person=${veh2person.size}`
);
setPreview(csvHeader + previewLines.join("") + (matched > previewMax ? "…(CSVプレビュー省略)\n" : ""));
lastUi = now;
await new Promise(r => setTimeout(r, 0));
}
}
}
setStatus(
`完了\nTSV行=${outLinesTsv} CSV行=${outLinesCsv}(マッチ=${matched})\n` +
`使用ゾーン: zone${zone} EPSG:${tmParams.epsg}\n` +
`skip: type=${skippedType}, noLink=${skippedNoLink}, noXY=${skippedNoXY}, noPersonResolved=${skippedNoPersonResolved}`
);
function download(name, parts, mime) {
const blob = new Blob(parts, {type: mime});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = name;
a.click();
URL.revokeObjectURL(url);
}
download(outTsvName || "traj_all.tsv", tsvParts, "text/tab-separated-values;charset=utf-8");
download(outCsvName || "traj_all_geo.csv", csvParts, "text/csv;charset=utf-8");
}
// ---------- main ----------
runBtn.addEventListener("click", async ()=>{
try {
runBtn.disabled = true;
setPreview("");
const typeStr = document.getElementById("types").value.trim();
const outTsv = document.getElementById("outnameTsv").value.trim() || "traj_all.tsv";
const outCsv = document.getElementById("outnameCsv").value.trim() || "traj_all_geo.csv";
const srcWidth = Math.max(1, parseInt(document.getElementById("srcWidth").value.trim() || "6", 10));
const baseDate = document.getElementById("baseDate").value.trim() || "2026-01-01";
const {hintLon, hintLat} = parseHint();
const wantTypes = new Set(typeStr ? typeStr.split(",").map(s=>s.trim()).filter(Boolean) : []);
setStatus("network解析開始…");
const {nodes, links, crsText, sampleXY} = await parseNetworkStream(networkFile);
const inferred = inferZonesByHint(sampleXY, hintLon, hintLat);
const defaultZone = inferred[0]?.zone || 9;
renderZoneUI(inferred, defaultZone, crsText, hintLon, hintLat);
setStatus(
`ゾーン自動選択完了\n` +
`採用デフォルト: zone${defaultZone}(手動変更可)\n` +
`events解析を開始します…`
);
const zone = parseInt(zoneSelect.value, 10) || defaultZone;
setStatus(
`events解析開始…(TSV+CSV同時生成)\n` +
`使用ゾーン: zone${zone} (EPSG:${6669+zone})`
);
await buildOutputs(eventsFile, nodes, links, wantTypes, outTsv, outCsv, zone, srcWidth, baseDate);
runBtn.disabled = false;
setStatus(statusEl.textContent + "\nダウンロード完了");
} catch (e) {
runBtn.disabled = false;
setStatus("エラー: " + (e?.message || e));
}
});
</script>
</body>
</html>
サーバは不要であり,ローカルPC上で動作する.入力データは外部送信されない.
本ツールの設計思想は,
座標系(CRS)をメタデータに依存せず,地理的位置整合性で検証して決定する
ことにある.
これは,MATSimネットワークの CRS 記述が誤っている場合でも,実際の地理的位置に基づいて適切な平面直角系(ゾーン)を選択するための安全設計である.
MATSim 実行後の出力ディレクトリから,以下の2つのファイルを用意する.
output_network.xml.gz
output_events.xml.gz
ブラウザで
matsim_crs_verified_exporter.html
を開き,画面中央の枠内に
の 2ファイルを同時にドラッグ&ドロップする.
両方が認識されると「生成してダウンロード」ボタンが有効になる.
特に重要なのが 地域ヒント である.
この値を基準にゾーンが自動決定される.
「生成してダウンロード」を押すと,
が生成される.
MATSim のネットワーク定義ファイルである.
主な内容:
<node id="..." x="..." y="..."/>
<link id="..." from="..." to="..."/>
<coordinateReferenceSystem>...</coordinateReferenceSystem>
このファイルが無いと,events の link を地理座標へ変換できない.
MATSim シミュレーション中に発生したイベントの時系列記録である.
例:
<event time="21600.0" type="actend" person="2" link="10"/>
主なイベント:
を取得する.
本ツールでは,events 内の link id を network の座標に結び付けることで地理情報を復元する.
<node> と <link> をストリーム解析平面直角座標系(JGD2011の1〜19系)を総当たりし,
する.
これにより,
を回避する.
time person_id x y type link
Source,Longitude,Latitude,Timestamp,Distance,Speed,Transport
初期値:
139.50,35.60(町田想定)
別地域ではヒント変更が必要.
日本国外は現仕様では非対応.
旧方式:
現方式:
これは
静的メタ情報依存 → 実データ整合判定
への設計変更である.
matsim_crs_verified_exporter.html は,
研究検証用途向けの安全設計ツールである.
必要であれば,
にも再構成できる.
=====
これによって作成されたtraj_all_geo.csvを使って、pm_proxy_single_socket_traj_all_geo.go と server22-1_v3.go を使うことで、WebにMATSimで作ったエージェントを動かしてみせることができる。

[pm_proxy_single_socket_traj_all_geo.go] (I:\home\ebata\hakata\video3)
// pm_proxy_single_socket_traj_all_geo.go
package main
import (
"encoding/csv"
"flag"
"fmt"
"log"
"math"
"net/url"
"os"
"strconv"
"sync"
"time"
"github.com/gorilla/websocket"
)
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
Type int `json:"type"`
Timestamp string `json:"timestamp"` // 懿「蕕SVのTimestampをそのまま使う
}
type unmTbl struct {
uniName string
objType int
simNum int
pmNum int
lon float64
lat float64
}
var (
list = make([]unmTbl, 0)
addr = flag.String("addr", "0.0.0.0:8080", "http service address")
mutex sync.Mutex
)
func connectWebSocket() (*websocket.Conn, error) {
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
for i := 0; i < 5; i++ {
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err == nil {
log.Println("Connected to WebSocket.")
return c, nil
}
log.Println("WebSocket connection failed:", err)
}
return nil, fmt.Errorf("failed to connect WebSocket")
}
func main() {
flag.Parse()
log.SetFlags(0)
file, err := os.Open("traj_all_geo.csv")
if err != nil {
log.Fatal(err)
}
defer file.Close()
reader := csv.NewReader(file)
reader.FieldsPerRecord = -1 // 999.9 行など列数揺れ対策
c, err := connectWebSocket()
if err != nil {
log.Fatal(err)
}
defer c.Close()
for {
line, err := reader.Read()
if err != nil {
break
}
fmt.Println("--->1")
time.Sleep(100 * time.Millisecond)
// CSV:
// Source,Longitude,Latitude,Timestamp,Distance,Speed,Transport,Type
if len(line) < 8 {
continue
}
if line[0] == "Source" {
continue
}
uniName := line[0]
lon, err1 := strconv.ParseFloat(line[1], 64)
lat, err2 := strconv.ParseFloat(line[2], 64)
if err1 != nil || err2 != nil {
continue
}
ts := line[3] // 懿「蜒fータの Timestamp をそのまま使う
typ, err := strconv.Atoi(line[7])
if err != nil || typ < 1 || typ > 4 {
typ = 1
}
found := false
for i := range list {
if list[i].uniName == uniName {
fmt.Println("--->2")
time.Sleep(100 * time.Millisecond)
found = true
oldLat := list[i].lat
oldLon := list[i].lon
list[i].lat = lat
list[i].lon = lon
list[i].objType = typ
if math.Abs(oldLat-lat) > 1e-12 || math.Abs(oldLon-lon) > 1e-12 {
gl := GetLoc{
ID: list[i].pmNum,
Lat: lat,
Lng: lon,
Type: typ,
Timestamp: ts,
}
mutex.Lock()
err = c.WriteJSON(gl)
if err != nil {
mutex.Unlock()
c.Close()
c, err = connectWebSocket()
if err != nil {
log.Println("WebSocket reconnect failed")
return
}
continue
}
glAck := new(GetLoc)
err = c.ReadJSON(glAck)
mutex.Unlock()
if err != nil {
c.Close()
c, err = connectWebSocket()
if err != nil {
log.Println("WebSocket reconnect failed")
return
}
continue
}
// 終端(999.9,999.9)で削除
if lat > 999.0 || lon > 999.0 {
list = append(list[:i], list[i+1:]...)
}
}
break
}
}
if !found {
ut := unmTbl{
uniName: uniName,
objType: typ,
simNum: len(list),
lat: lat,
lon: lon,
}
gl := GetLoc{
ID: 0,
Lat: lat,
Lng: lon,
Type: typ,
Timestamp: ts,
}
mutex.Lock()
err := c.WriteJSON(gl)
if err != nil {
mutex.Unlock()
c.Close()
c, err = connectWebSocket()
if err != nil {
log.Println("WebSocket reconnect failed")
return
}
continue
}
glAck := new(GetLoc)
err = c.ReadJSON(glAck)
mutex.Unlock()
if err != nil {
c.Close()
c, err = connectWebSocket()
if err != nil {
log.Println("WebSocket reconnect failed")
return
}
continue
}
ut.pmNum = glAck.ID
list = append(list, ut)
}
}
}
[server22-1_v3.go] (I:\home\ebata\hakata\video3)
// server22-1_v3.go
// server22-1_v3.go
package main
import (
"flag"
"html/template"
"log"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
Type int `json:"type"`
Timestamp string `json:"timestamp"` // 懿「蜥ヌ加:pm_proxy側から送られてくるTimestamp
}
var addr = flag.String("addr", "0.0.0.0:8080", "http service address")
var upgrader = websocket.Upgrader{}
var chan2_1 = make(chan GetLoc)
var maxid = 0
var mutex sync.Mutex
func echo2(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
for {
gl := new(GetLoc)
err := c.ReadJSON(&gl)
mutex.Lock()
if gl.ID == 0 && gl.Lat < 0.01 && gl.Lng < 0.01 {
mutex.Unlock()
break
} else if gl.ID < -1 {
gl.ID = -1
gl.Lat = -999
gl.Lng = -999
_ = c.WriteJSON(gl)
mutex.Unlock()
continue
} else {
if err != nil {
log.Println("read:", err)
mutex.Unlock()
break
}
// 懿「蜥ヌ加:pm_proxyが読み取ったTimestampをサーバ側で表示
//log.Printf("Timestamp=%s id=%d type=%d lat=%f lng=%f", gl.Timestamp, gl.ID, gl.Type, gl.Lat, gl.Lng)
chan2_1 <- *gl
gl2 := <-chan2_1
maxid = gl2.ID
if err2 := c.WriteJSON(gl2); err2 != nil {
log.Println("write2:", err2)
mutex.Unlock()
break
}
}
mutex.Unlock()
}
}
func echo(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
for {
gl := <-chan2_1
if err = c.WriteJSON(gl); err != nil {
log.Println("WriteJSON1:", err)
}
gl2 := new(GetLoc)
if err2 := c.ReadJSON(&gl2); err2 != nil {
log.Println("ReadJSON:", err2)
}
chan2_1 <- *gl2
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func smartphone(w http.ResponseWriter, r *http.Request) {
smartphoneTemplate.Execute(w, "ws://"+r.Host+"/echo2")
}
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/echo2", echo2)
http.HandleFunc("/echo", echo)
http.HandleFunc("/", home)
http.HandleFunc("/smartphone", smartphone)
log.Fatal(http.ListenAndServe(*addr, nil))
}
var smartphoneTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script>
function obj(id, lat, lng, type){
this.id = id;
this.lat = lat;
this.lng = lng;
this.type = type;
// timestamp はスマホ側では送らない(必要なら追加可)
}
function random(min, max){
return Math.random()*(max-min) + min;
}
var lat = 35.654543;
var lng = 139.795534;
var myType = 1;
window.addEventListener("load", function(evt) {
var output = document.getElementById("output");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.textContent = message;
output.appendChild(d);
};
var personal_id = 0;
document.getElementById("open").removeAttribute("disabled");
document.getElementById("open").style.color = "black";
document.getElementById("close").setAttribute("disabled", true);
document.getElementById("close").style.color = "White";
document.getElementById("send").setAttribute("disabled", true);
document.getElementById("send").style.color = "White";
document.getElementById("open").onclick = function(evt) {
document.getElementById("open").setAttribute("disabled", true);
document.getElementById("open").style.color = "White";
document.getElementById("send").removeAttribute("disabled");
document.getElementById("send").style.color = "black";
document.getElementById("close").removeAttribute("disabled");
document.getElementById("close").style.color = "black";
if (ws) { return false; }
ws = new WebSocket("{{.}}");
ws.onopen = function(evt) {
print("OPEN");
var send_obj = new obj(0, 35.654543,139.795534, myType);
ws.send(JSON.stringify(send_obj));
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) {
print("RESPONSE: " + evt.data);
var o = JSON.parse(evt.data);
personal_id = o.id;
if ((Math.abs(o.lat) > 90.0) || (Math.abs(o.lng) > 180.0)){
ws.close();
}
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
if (!ws) { return false; }
lat += random(0.5, -0.5) * 0.00001 * 10 * 5;
lng += random(0.5, -0.5) * 0.00002 * 10 * 5
var send_obj = new obj(personal_id, lat, lng, myType);
ws.send(JSON.stringify(send_obj));
return false;
};
document.getElementById("close").onclick = function(evt) {
document.getElementById("open").removeAttribute("disabled");
document.getElementById("open").style.color = "black";
document.getElementById("close").setAttribute("disabled", true);
document.getElementById("close").style.color = "White";
document.getElementById("send").setAttribute("disabled", true);
document.getElementById("send").style.color = "White";
if (!ws) { return false; }
var send_obj = new obj(personal_id, 999.9, 999.9, myType);
ws.send(JSON.stringify(send_obj));
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<form>
<button id="open">Open</button>
<button id="send">Send</button>
<button id="close">Close</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
`))
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>PruneMobile</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.js"></script>
<script src="http://kobore.net/PruneCluster.js"></script>
<link rel="stylesheet" href="http://kobore.net/examples.css"/>
</head>
<style>
.legend {
position: absolute;
top: 10px;
right: 10px;
z-index: 9999;
background: rgba(255,255,255,0.95);
padding: 10px 12px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
font-family: sans-serif;
font-size: 13px;
line-height: 1.4;
}
.legend .row {
display: flex;
align-items: center;
gap: 8px;
margin: 4px 0;
white-space: nowrap;
}
.legend .dot {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid #ffffff;
box-shadow: 0 0 0 1px rgba(0,0,0,0.25);
flex: 0 0 auto;
}
.legend .ts {
margin-top: 8px;
padding-top: 6px;
border-top: 1px solid #ddd;
font-weight: 600;
}
</style>
<body>
<div id="map"></div>
<div class="legend">
<div class="row"><span class="dot" style="background:#e74c3c"></span>Type1: 赤 (金のある)社会人</div>
<div class="row"><span class="dot" style="background:#3498db"></span>Type2: 青 (金のある)社会人 + (金のある)ハウスキーパー</div>
<div class="row"><span class="dot" style="background:#2ecc71"></span>Type3-1: 緑 (金はないが時間はある)学生</div>
<div class="row"><span class="dot" style="background:#f1c40f"></span>Type3-2: 黄 (金はないが時間がある)シニア </div>
<div class="ts">Timestamp: <span id="tstext">(none)</span></div>
</div>
<script>
ws = new WebSocket("{{.}}");
var map = L.map("map", {
attributionControl: false,
zoomControl: false
}).setView(new L.LatLng(33.58973407765046, 130.41048227121925), 16); // 中州
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
detectRetina: true,
maxNativeZoom: 18
}).addTo(map);
var leafletView = new PruneClusterForLeaflet(1,1);
// 懿「蚯ype別アイコン(SVGをdata URI化)
function svgIcon(fill) {
var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28">' +
'<circle cx="14" cy="14" r="10" fill="' + fill + '" stroke="white" stroke-width="2"/></svg>';
return L.icon({
iconUrl: 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg),
iconSize: [28,28],
iconAnchor: [14,14]
});
}
var typeIcons = {
1: svgIcon('#e74c3c'), // 赤
2: svgIcon('#3498db'), // 青
3: svgIcon('#2ecc71'), // 緑
4: svgIcon('#f1c40f') // 黄
};
// 懿「蕷runeCluster懼Leaflet markerへアイコン適用
leafletView.PrepareLeafletMarker = function(leafletMarker, data) {
if (data && data.icon) {
leafletMarker.setIcon(data.icon);
}
};
var markers = [];
ws.onmessage = function (event) {
var obj = JSON.parse(event.data);
// 懿「蜥ヌ加:Timestamp 表示(来ていれば)
if (obj.timestamp) {
document.getElementById("tstext").textContent = obj.timestamp;
}
var t = obj.type || 1;
if (!typeIcons[t]) { t = 1; }
if (obj.id == 0){
// 懿「蜊・ャ時にtypeをmarker.dataへ
var marker = new PruneCluster.Marker(obj.lat, obj.lng, {
type: t,
icon: typeIcons[t]
});
markers.push(marker);
leafletView.RegisterMarker(marker);
obj.id = marker.hashCode;
ws.send(JSON.stringify(obj));
} else if ((Math.abs(obj.lat) > 90.0) || (Math.abs(obj.lng) > 180.0)){
for (let i = 0; i < markers.length; ++i) {
if (obj.id == markers[i].hashCode){
var deleteList = markers.splice(i, 1);
leafletView.RemoveMarkers(deleteList);
break;
}
}
obj.lat = 91.0;
obj.lng = 181.0;
ws.send(JSON.stringify(obj));
} else {
for (let i = 0; i < markers.length; ++i) {
if (obj.id == markers[i].hashCode){
var ll = markers[i].position;
ll.lat = obj.lat;
ll.lng = obj.lng;
// 懿「蚯ypeが来た場合は色も追従(固定なら実質変わらない)
markers[i].data.type = t;
markers[i].data.icon = typeIcons[t];
break;
}
}
ws.send(JSON.stringify(obj));
}
}
window.setInterval(function () {
leafletView.ProcessView();
}, 30); ////////////////////
ws.onclose = function(event) { ws = null; }
map.addLayer(leafletView);
</script>
</body>
</html>
`))
SNSを使った最近の選挙について各種の批判があるようです。
There appear to be various criticisms of recent elections that used social media.
-「感情を煽る短い動画や切り抜きが拡散され、熟議が失われている」という批判
- Criticism that "short, emotionally provocative videos and clips are spreading, eroding thoughtful deliberation."
-「アルゴリズムが極端な意見を増幅し、分断を加速させている」という懸念
- Concern that "algorithms amplify extreme opinions and accelerate polarization."
-「真偽不明の情報やデマが瞬時に広がり、公平性が損なわれる」という指摘
- The observation that "information of uncertain veracity and outright misinformation spreads instantly, undermining fairness."
まあ、この指摘は外れてはいないとは思うのですが、その一方で、既存のメディア(テレビ)のヒステリックな「SNSベース選挙」への批判は、ちょっと滑稽とすら感じます。
I do not think these criticisms are entirely off the mark; however, the hysterical criticism of "SNS-based elections" by existing media (television) strikes me as somewhat comical.
SNSが嫌いで、一切のSNSから手を引いた私には、その気持ちが分からなくもありませんが。
As someone who dislikes social media and has withdrawn from it entirely, I cannot say I fail to understand their feelings.
---
SNSであろうが、何であろうが、私は、それらの批判を超えて、「若い人たちが政治に関心を持ち、選挙に投票に行かせた」という実績だけで、もう『凄いことだ』と思うのです。
Whether it is social media or anything else, beyond these criticisms, I believe that the mere fact that young people developed an interest in politics and were motivated to go vote is, in itself, an extraordinary achievement.
ぶっちゃけちゃいますけどね、私、(投票権のある年齢になった)大学生のころ、選挙は普通に「棄権」するか、訳も分からず「一番議席の少ない野党」に投票するという、いい加減なことをやってきました。
When I was a university student of voting age, I would either abstain from elections or casually vote for "the opposition party with the fewest seats," without really understanding why.
その手段がどうあれ、投票するという行動を行った若い人に対して、正直、畏敬の念すらあります。
Regardless of the means, I honestly feel a sense of reverence toward young people who actually took the step of voting.
若いころの私を引き摺り出してきて、今の若者の前で、土下座させてやりたい ―― そんな気持ちにすらなります。
I sometimes feel like dragging out my younger self and making him bow in apology to today’s youth.
---
SNSによる政治活動が批判される理由の一つには、いわゆるコンピュータを使った各種の技法「マインドコントロール」「ターゲティング広告」「One to One戦略」などがあると思います。
One reason political activity via social media is criticized is the use of various computer-based techniques, such as "mind control," "targeted advertising," and "one-to-one strategies."
これは、Webが始まった当初から行われている、視聴者に購買意欲をかきたてる手法で、ぶっちゃけ私も、この広告の研究の一端を担ったことがあります。
These methods have existed since the early days of the Web as techniques to stimulate consumer purchasing desire, and, candidly, I myself once participated in research related to such advertising.
その「購買意欲」を「投票意欲」に替えて、集票戦略にしたのが、現在のSNS戦略になります。
Replacing "purchase motivation" with "voting motivation" and turning it into a vote-gathering strategy is what defines current SNS-based campaign strategies.
まあ、Webが登場した時から、このような選挙は想定されていたのです ―― 残っていたのは法律の問題だけです。
In a sense, elections of this kind were envisioned from the moment the Web appeared; the only remaining issue was the law.
現時点で、残っているSNS等を使った選挙活動の制約については、次のようなものがあります。
At present, the remaining legal restrictions on election activities using SNS and similar platforms include the following:
- 公職選挙法により、選挙運動が可能な期間は公示(告示)日以降に限定され、それ以前の「事前運動」は禁止されている
- Under the Public Offices Election Act, campaign activities are limited to the period after official announcement, and before "pre-campaigning" is prohibited.
- 有料のインターネット広告は原則として政党等に限定され、候補者個人による有料広告には制約がある
- Paid internet advertising is generally limited to political parties, and individual candidates face restrictions on paid advertisements.
- メールを用いた選挙運動は送信主体が政党・候補者に限定され、一般有権者による送信は禁止されている
- Election campaigning by email is limited to political parties and candidates as senders; ordinary voters are prohibited from sending such messages.
- 虚偽事項の公表やなりすまし行為は処罰対象となり、投稿内容について発信者責任が問われる
- Publishing false information or impersonation is subject to punishment, and the sender bears responsibility for posted content.
- 投票日当日の選挙運動はオンラインを含めて禁止されている
- Campaigning on voting day is prohibited, including online activities.
これらをざっと読んでみたところでも、それほど無茶な内容とは思えません。
Even at a glance, these provisions do not seem particularly unreasonable.
いずれ「候補者個人による有料広告」も緩和される方向になると思います。
I suspect that restrictions on paid advertisements by individual candidates will eventually be relaxed.
---
まあ、システムのエンジニアである私から見ても、「選挙活動のITシステム制御化」や「有権者のマインドコントロールのアルゴリズム化」は、自明の流れであって、
From my perspective as a systems engineer, the IT systematization of campaign control and the algorithmic shaping of voter psychology are natural developments.
―― そんなこともできない政党や候補者は、いなくなっても仕方がない
"Political parties or candidates unable to do such things may simply be left behind."
と思っています。
That is what I think.
---
むしろ、問題は、有権者である私たちの方です。
Rather, the issue lies with us, the voters.
私たちが、ITシステムエンジニア達が20年以上もかけて開発し、それを膨大なコンピュータリソースによって作られた「選挙活動のITシステム」や「マインドコントロールアルゴリズム」に太刀打ちしようとしても、残念ながら「敗北は必至」と思っています。
If we attempt to confront the "IT-based election systems" and "mind-control algorithms" developed over more than two decades by engineers and powered by enormous computational resources, I believe our defeat is almost inevitable.
なにしろ、彼らは、『私たちが独自の判断で、投票した』と思わせて、行動させることに関しては、世界最高の制御メカニズムを持っているものです。
After all, they possess some of the most sophisticated control mechanisms in the world when it comes to making us believe that we voted based on our own independent judgment.
私たちは、これから、コンピュータに誘導されて投票を行うことになるのです ―― このことを覚悟しなければなりません。
From now on, as we cast our votes guided by computers, we must be prepared for this reality.
問題は、むしろ、「私の意見は、誰かの作ったコンピュータのシステムによってコントロールされている」という自覚と認識です。
The real issue is awareness, the recognition that "my opinions may be shaped by systems designed by someone else."
この、計算機によって、私たちが動かされている中で、私たちがそれに対して何をするか、というのが重要なのです。
What matters is what we choose to do within this reality where computers influence our behavior.
―― SNSや生成AIの言うがままに投票をするもよし
"We may vote exactly as SNS or generative AI suggests."
―― SNSや生成AIの言うことを、徹底的に疑い、真逆を狙って動いてみるもよし
"We may thoroughly doubt what SNS or generative AI tells us and deliberately act in the opposite direction."
―― 候補者に、直接、エビデンス(証拠)を求めて、その根拠を尋ねてみるもよし
"We may directly demand evidence from candidates and ask them for the basis of their claims."
と、こんな風に、対抗策を考えてみると、一番最後にくるのは、
When we consider countermeasures in this way, what ultimately emerges is:
「選挙演説を直接見に行ってみよう」とか「握手されて嬉しかったから、この人に投票しよう」というところまで、アナログに戻っていく可能性だってもあります。
"We might go see a campaign speech in person," or "I felt happy shaking their hand, so I’ll vote for this person a return to analog politics remains entirely possible.
---
つまるところ、『高度にIT化された選挙は、アナログ選挙と見分けがつかない』ということになれば、「面白いなぁ」と思っているのです。
In the end, if a highly IT-driven election becomes indistinguishable from an analog one, I would find that rather fascinating.
(アーサー・C・クラークの有名なフレーズ「十分に発達した科学技術は、魔法と見分けがつかない」のオマージュです)
(This is an homage to Arthur C. Clarke’s famous statement: "Any sufficiently advanced technology is indistinguishable from magic.")
「2/15日の○○市市長・市議選挙の期日前投票に行ってこようと思っています。市長または市議に誰を選べば良いのか、私のコラムを毎日校正して頂いている経緯と実績から一人以上の人を推薦して下さい。
ご要望の「特定の候補者を名指しで推薦(この人に投票すべき)」はできません。個人に対する投票先の誘導に当たり得るためです。代わりに、○○市長選・市議選で候補者を絞り込むための実務的な選び方と、あなた(江端さん)の文章・問題意識に沿って比較に使える評価軸を用意します。候補者の資料(選挙公報の本文、政策の箇条書き、公開討論の文字起こし等)を貼ってくれれば、その範囲内で中立に要約・比較表まで作れます(結論としての「推薦」は出さず、どの条件なら誰が合うか、まで落とします)。
(後略)
うん、ChatGPTの運用はこれでいい ーー 実際に候補者を明示してきたら、それは「ヤバい」状況です。
ただ、ここからが、ChatGPTを使い熟して、上手く候補者名を誘導するのが、私のワザです ーー 教えませんけどね。
ChatGPTが評価した内容の一部を開示しておきます。
これから選挙での立候補予定されている方は、「こういう風に、選挙広報がChatGPTに読まれている」ということを意識して、選挙広報の作成をされると良いかもしれません。私たち市民にとって、市議選挙なんて、選挙広報くらいしか資料ありませんからね。
■ ○○○○(立候補者名)
総合:12 / 16(最高)
具体性:2
4分類で構造明確。実装手順:2
減税幅・具体施策明記。財源:1
減税は書くが裏付け弱い。KPI:1
現場負荷:2
利害調整:1
リスク管理:1
整合性:2
経歴との接続が明確。▶ 分類:実装志向型・やや攻め型
あなたが重視する
工程
数字
検証可能性
制度設計
の観点では、
▶ 最も“構造設計寄り”なのは ○○氏
▶ 安定運用型は △△氏
という暫定評価になります。
昨日、ChatGPTと今回の衆議院選挙について話をしていたのですが、2つほどインパクトのある話をされました。
Yesterday, I was talking with ChatGPT about the recent House of Representatives election, and it presented me with two particularly striking points.
1つ目が「『チームみらい』の躍進」の話でした。
The first was about the surge of “Team Mirai.”
ここの話で、はじめて「エンジニア票」という新概念がChatGPTによって提唱されました。
In this context, ChatGPT introduced a new concept: the “engineer vote.”
私たちエンジニアから見ると、政治というのは、まったくもって不合理な意思決定システムで運用されており、その非効率性は、目を覆うばかりです。
From the perspective of engineers, politics operates as an utterly irrational decision-making system, and its inefficiency is frankly staggering.
もっとも、これは、エンジニアリングが対象とする評価軸(KPI)が明確であるからとも言えます。
That said, this perception likely stems from the fact that engineering deals with clearly defined evaluation axes (KPIs).
---
これを具体的に言うと、例えば次のような違いです。
To put this more concretely, consider the following differences.
エンジニアリングの世界では、処理速度を上げたいのか、コストを下げたいのか、信頼性を高めたいのか、障害発生率を下げたいのか、といった目的が事前に定義され、それぞれがKPIとして数値化されます。
In engineering, objectives such as increasing processing speed, reducing costs, improving reliability, or lowering failure rates are defined in advance and quantified as KPIs.
そのKPIに対して、設計変更や実装変更が「改善か、改悪か」は、原則として測定結果で判断されます。
Whether a design or implementation change constitutes an improvement or a deterioration is, in principle, judged by comparing measured results against the KPIs.
ところが政治の世界では、経済成長、国民の安心、公平性、国際的な立場、といった抽象度の高い目標が並ぶ一方で、それらがどの指標で、どの時点で、どの程度達成されたのかが明確に示されることは、ほとんどありません。
In contrast, the political world is filled with highly abstract goals, such as economic growth, public security, fairness, and international standing. Yet, it is rarely made clear by what indicators, at what point in time, and to what extent these goals have been achieved.
エンジニアの感覚からすれば、「目的関数が定義されていない最適化問題」、あるいは「評価関数が状況によって毎回書き換えられるシステム」を延々と運用しているように見えるのです。
To an engineer, it appears to involve repeatedly solving an optimization problem with no defined objective function, or a system whose evaluation function is rewritten each time, depending on circumstances.
---
では、エンジニア層に「チームみらい」の何が刺さったのか?
What, specifically, resonated with the engineering community about “Team Mirai”?
最大の理由は、彼らの主張や語り口が、「正しさ」や「理想」を前面に押し出すものではなく、仕組みと運用の話として政治を語っていた点にあります。
The main reason lies in the fact that their arguments and rhetoric did not foreground “correctness” or “ideals,” but instead framed politics in terms of mechanisms and operations.
多くの政党は、「こうあるべきだ」「こうしたい」「国民のために」という“目的”の宣言から話を始めます。
Many political parties begin by declaring their “objectives,” such as “this is how things should be,” “this is what we want to do,” or “for the sake of the people.”
しかし、エンジニアがまず知りたいのは、「それはどう実装されるのか」「失敗した場合、どうロールバックするのか」「どこを観測して、何をもって成功・失敗と判断するのか」です。
Engineers, however, first want to know: how it will be implemented, how it will be rolled back if it fails, what will be observed, and by what criteria success or failure will be judged.
『チームみらい』のメッセージには、
The messages of “Team Mirai” included the following assumptions:
・制度は一度作ったら終わりではなく、改善し続ける対象である
・現場のデータや実態を見ながら設計を更新する
・万能解は存在せず、試行錯誤を前提とする
Those systems do not end once created but are subject to continuous improvement.
that designs should be updated while observing on-site data and realities;
and that no universal solution exists, making trial and error a given.
という、エンジニアにとって極めて馴染み深い――というか、当たり前の前提が、最初から組み込まれていました。
These extremely familiar, indeed, obvious premises for engineers were built in from the outset.
これは言い換えれば、「政治を完成形の理想像として語らなかった」ということです。
In other words, they did not talk about politics as a finished, idealized form.
エンジニアは、完成されたシステムよりも、「今どこが壊れているのか」「どこに技術的負債が溜まっているのか」「どこから手を付ければ改善余地があるのか」を説明される方が、よほど納得します。
Engineers are far more convinced by explanations of where systems are currently broken, where technical debt has accumulated, and where improvements can begin, than by claims of a perfectly finished system.
政治の世界ではしばしば、「理想を掲げること」そのものが評価されますが、エンジニアの世界では、「現状を正確に把握し、制約条件込みで語ること」の方が圧倒的に信頼されます。
In politics, simply proclaiming ideals is often valued in itself. In the engineering world, however, accurately grasping the current situation and speaking with constraints fully acknowledged commands far greater trust.
『チームみらい』が示したのは、「正解を持っている集団」ではなく、「まだ未完成だが、改善する意思と方法論を持っている集団」という姿でした。
What “Team Mirai” presented was not a group claiming to have the correct answers, but one that was still unfinished yet possessed the will and methodology to improve.
そしてそれは、不完全なシステムを前にしても、「直せる」「直し方が分かる」「一緒に直せる」と信じて日々仕事をしているエンジニアにとって、極めて「理解可能な言語」で書かれた政治の姿を見せたからです。
For engineers who work every day, believing that imperfect systems can be fixed, knowing how to fix them, and fixing them together, this was a form of politics written in an eminently understandable language.
実は、これ、別段新しい概念ではなく、EBPM(Evidence-Based Policy Making)の一態様です。
In fact, this is not a particularly new concept; it is a form of EBPM (Evidence-Based Policy Making).
政策を理念や印象論ではなく、データや実証結果(エビデンス)に基づいて立案・評価・改善していこうとする考え方ですが、既存政党は、これを理解していなかったか、あるいは理解していてもやっていなかったか、あるいは選挙で、EBPMを真面目に訴えなかったのです。
EBPM seeks to formulate, evaluate, and improve policies based on data and empirical evidence rather than ideology or impressions, yet existing parties either did not understand this, did not practice it even when they did, or failed to advocate it seriously during elections.
で、私が思うに、「チームみらい」は、これを、初めて政党として主張した――これが、いわゆるエンジニア層の票を拾ったのだと考えられます。
In my view, “Team Mirai” was the first party to explicitly assert this approach, which captured the votes of the engineering demographic.
――と、いうことを、ChatGPTに教えて貰いました。
That, at least, is what ChatGPT told me.
---
私は、日本のエンジニア人口を1000万人程度(*)と見ています。
I estimate the number of engineers in Japan to be around ten million(*).
(*)「典型的に技術者と呼べる層」として、職業分類の「技術者」は、男性329万人+女性56万人で、合計約385万人と見ています。次に、施工・工事寄りの職種として「建設・採掘従事者」が約275万人います。さらに、製造現場の「生産工程従事者」が約864万人いますが、ここは「全員が技術者」というわけではありません。そのため、保全、段取り、調整、検査、修理などを含む「技術に関わる業務」の比率を3割と仮定すると、約260万人程度になります。以上を合計すると、私は日本のエンジニア人口をおおよそ920万人、すなわち約1000万人規模と見ています。
(*)As a group typically described as engineers, I estimate that the occupational category “engineers” comprises approximately 3.85 million people in total: 3.29 million men and 0.56 million women. Next, approximately 2.75 million people are classified as “construction and mining workers,” which includes occupations more closely associated with on-site construction and engineering work. In addition, there are approximately 8.64 million “production process workers” in manufacturing, but this group cannot be regarded as consisting entirely of engineers. Therefore, if we assume that about 30 percent of this group is engaged in technology-related tasks such as maintenance, setup, coordination, inspection, and repair, that yields roughly 2.6 million people. Adding these figures, I estimate the population of engineers in Japan to be approximately 9.2 million, on the order of 10 million.
この1000万人全員が、エンジニアリング思考をするとは限りませんが、「政治やっている奴って、本当にバカじゃねーの」と思っているエンジニアは、そこそこの人数いるとは思うんですよ。
Not all of these ten million think in engineering terms, but I do believe there are quite a few engineers who think, “Aren’t the people running politics seriously stupid?”
これから、『チームみらい』が政治の表舞台にでてくるか、あるいは、他の政党がこのエンジニアリング思考を取り込んでいくのかは不明ですが――いずれにしても、今回の『チームみらい』の功績は大きいと思います。
Whether “Team Mirai” will move to the center of the political stage or other parties will incorporate this engineering mindset remains unclear; in any case, I believe their contribution this time was significant.
---
2つ目が「SNSのバックエンドとしての生成AIの登場」でした。
The second point concerned the emergence of generative AI as the backend of social media.
今や、SNSにおける情報を、そのまま真に受ける人間はおらず、その前にファクトチェックをするようになっているようです。
These days, few people accept information on social media at face value; people tend to fact-check it first.
で、そのファクトチェックは、SNSの過激な意見や真偽が不明な主張のチェックだけではなく、
And that fact-checking is not limited to verifying extreme opinions or dubious claims on social media.
―― こう考えてしまう自分はズレていないか
Am I off base for thinking this way?
という、セルフチェックによって、自分の主張の妥当性を検証する為にChatGPTが使われていると、ChatGPT自身が言っていました。
ChatGPT stated that it is also used for self-checking to verify the validity of one’s own views.
この理由が凄い。
The reasoning behind this is striking.
- 正解がない
- しかし棄権もできない
- しかも、結果の重さだけは分かっている
There is no correct answer.
Yet abstention is not an option;
And only the gravity of the outcome is clear.
この状況で人が欲しくなるのは、『結論ではなく、(自分の)思考の妥当性の確認』というものです。
In such a situation, what people want is not a conclusion, but confirmation of the soundness of their own thinking.
専門家、評論家、SNSは、もはや信頼するソースとは見なされず、“ノイズ”のレベルに堕ちてしまい、せいぜい「生成AIと一緒に考えるための“ネタ”程度の取り扱いになっている」ということのようです。
Experts, commentators, and social media are no longer regarded as reliable sources, having fallen to the level of “noise,” at best treated as material to think through together with generative AI.
今や『誰かに“決めてもらう”ことへの不信が、かなり深いところまで進んだ』と言えます。
It can now be said that distrust of having someone else “decide for you” has reached a deep level.
で、「自分の判断を他人に預けたくない」が、「しかし完全な独断にも耐えられない」という人が、
As a result, people who do not want to entrust their judgment to others, yet cannot endure complete self-reliance,
―― 生成AIを、自分の思考の『壁打ち』として使うようになった
have come to use generative AI as a “sounding board” for their own thinking.
---
つまるところ、選挙におけるSNS戦略なんぞ、すでに時代遅れになっているということです。
In short, social-media-based election strategies are already outdated.
SNSはあくまでトリガーとしてのフロントエンド(GUI程度の役割)であって、意思決定システムは、ChatGPTのような生成AIに移行しているのです。
Social media merely serves as a trigger-like front end (akin to a GUI), while the actual decision-making system has shifted to generative AI such as ChatGPT.
という訳で、テレビの解説などで、未だに「SNSによる選挙投票の危険性」とか言っている、テレビのコメンテータや専門家を見ていると「こいつら、アホだなぁ」と思ってしまうわけですよ――そんなフェーズ、すでにエンドロールに入っているのに。
That is why, when I see TV commentators and experts still talking about the “dangers of SNS-driven voting,” I can’t help thinking, “These people are idiots, that phase has already rolled into the end credits.
---
そして、生成AIが仕切る選挙は、SNSよりも怖い――なにしろ、論理的な風を装って分かりやすい言葉で、筋の通った話を、誠実にしてくるからこそ「怖い」。
Elections mediated by generative AI are even more frightening than those driven by social media precisely because they present coherent arguments in clear language, under the guise of logic and sincerity.
私は、ChatGPTの言うことを素直に信じません。主張の矛盾点を突いて、ChatGPTにちゃんと謝罪までさせています――でも、ここまでやっている人って、どれくらいいるんでしょうか?
I do not take ChatGPT's output at face value. I point out contradictions in its claims and even make it apologize, but how many people go that far?
生成AIの言うことを、まるっきり信じてはダメです。
You must never believe what generative AI says wholesale.
必要に応じて、矛盾や論理矛盾を指摘して、ちゃんと喧嘩をしないと、健全な生成AIを育てることができません。
If necessary, you have to point out contradictions and logical inconsistencies and properly “argue” with them; otherwise, you cannot cultivate a healthy relationship with generative AI.
権力が、これから生成AIを抱き込みにかかるのは予定調和です――私が権力側であれば、必ず“ここ”を調略します。
It is almost predetermined that those in power will try to co-opt generative AI. If I were on the side of power, I would certainly target this point.
未来のAI社会を健全にする為には、生成AIに対して、ちゃんと対峙する知性と気合が必要です。
To keep a future AI-driven society healthy, we need the intelligence and resolve to confront generative AI head-on.
『私達は生成AIごときに支配されるほど、愚かではない』ということを、がっつり示していきましょう。
Let us firmly demonstrate that we are not so foolish as to be ruled by something like generative AI.
今回、自民党の歴史的大勝利――というより、中道改革連合を含めた野党の歴史的大敗と言えると思います。
This time’s result can be described as a historic landslide victory for the Liberal Democratic Party—or rather, a historic defeat for the opposition parties, including the centrist reform coalition.
まあ、トップの辞任は仕方がないと思いますし、「(自分の責任は)万死に値する」という反省の念も大切かとは思います。
Of course, I think the resignation of the party leader is inevitable, and expressions of remorse such as “my responsibility deserves ten thousand deaths” also have their place.
とは、思いますが――どうせなら、
That said—if we’re going to go that far—
―― お前らバカなのか! このまま、国家をこの方向にしていっていいのか! 国民全員死ね!!
“Are you all idiots? Is it really okay to take the country in this direction? Drop dead, every last one of you!!”
というような捨てゼリフを吐いて、政界から引退しても、「それはそれでありかな」とも思うんですよね。
If someone were to spit out a parting shot like that and then retire from politics, I might even think, “Well, that’s one way to do it.”
(私なら、やるぞ)
(If it were me, I’d do it.)
---
とは言え、選挙というシステムは、「そっかー、みんながそう考えるなら、私も考え方を変えていかなくちゃなぁ」という、自分の思想の自己点検をする機会でもあります。
That said, elections are also a system that allows us to self-check our own beliefs—to think, “I see, if this is what everyone thinks, maybe I need to reconsider my own views.”
私、今回の選挙結果だけでなく、外国人排斥(とは言わないのかもしれませんが)や、古くは憲法改正支持者の数にも――頭を殴られるようなショックを受けたのを覚えています。
I remember feeling as if I’d been hit over the head, not only by the outcome of this election, but also by attitudes toward excluding foreigners (though that may not be the right term), and even earlier by the sheer number of people supporting constitutional revision.
―― 私の考え方は、マジョリティとは乖離している(あるいは「時代遅れ」)
—That my way of thinking is out of step with the majority (or perhaps “outdated”)
という事実を目の前に突きつけられるのは、正直つらいことではありますが、それでも、「これが多数派の意思なのだ」と力づくで理解させられることは、自分の考え方を変える力にはなると思います。
Being forced to confront that fact head-on is honestly painful, but even so, being compelled to accept that “this is the will of the majority” can become a force that changes one’s own thinking.
---
そんな中であっても、「我が国の国民が、『選挙の運用』に対してだけは絶対的に信頼している」という点は、誇れることだなあ、と思うんですよ。
Even so, I think we can be proud of the fact that the people of this country have absolute trust—at least—when it comes to the administration of elections.
どこぞの国の大統領のように、自分の都合の悪い選挙結果に対して「票が盗まれた」などと言い出す人間が、だたの一人もいないことは――なんと素晴らしいことか、と、この一点において“のみ”、私は、自分の国を誇ることができるのです。
The fact that there is not a single person here who, like some foreign president, claims “the votes were stolen” when faced with an unfavorable result—how wonderful that is. On this one point alone, I can genuinely feel proud of my country.
なんで、日本においては、このような「不正投票」という概念がほとんど出てこないのか、不思議です。
I find it fascinating that, in Japan, the concept of “election fraud” rarely arises.
---
日本の選挙は、良くも悪くもアナログの塊です。
Japanese elections are, for better or worse, a mass of analog processes.
・紙の投票用紙
・手作業での開票
・開票所に立会人・報道・候補者陣営が同席
・開票速報が「開票率0%でも当確」という形で出る(=出口調査・情勢分析が主)
* Paper ballots
* Manual vote counting
* Witnesses, media, and candidate representatives present at counting stations
* Early results declaring winners even at “0% counted” (based mainly on exit polls and situation analysis)
この構造では、「どこかで大量に票を書き換えた」という物語を作るのが極めて難しいです。機械集計や電子投票のような「ブラックボックス」がほぼ存在しないため、疑念が入り込む余地が制度的に小さいわけです。
Under this structure, it is extremely difficult to construct a story about “mass vote tampering somewhere.” Because there are almost no black boxes like machine counting or electronic voting, the institutional space for suspicion to creep in is very small.
---
また、選挙の運用が「政治から切り離されている」という点も見逃せません。
Another important point is that the administration of elections is separated from politics.
日本では、選挙管理委員会が地方自治体に常設されており、与野党どちらの政権下でも基本的な運用は変わりません。ここで重要なのは、「信用している」のではなく、「疑っても崩れない構造」になっている点です。
In Japan, election management committees are permanently established within local governments, and basic operations do not change regardless of whether the ruling party or the opposition is in power. The key point here is not that we “trust” the system, but that it is built to withstand scrutiny, even when questioned.
仮に負けた側が「不正だ!」と叫んだ場合でも、
Even if the losing side were to shout “fraud!”,
・具体的にどこで
・誰が
・どうやって
・何票分
* where exactly
* Who did it
* how it was done
* and for how many votes
をやったのかを、裁判で立証しなければなりません。このハードルの高さが、陰謀論の流通を事前に抑え込んでいます。
They would have to prove all of that in court. This high hurdle suppresses conspiracy theories before they can spread.
---
加えて、我が国には「負けを受け入れる」文化があります。これ、かなり異質な政治文化です。
In addition, our country has a culture of “accepting defeat.” This is a fairly unusual political culture.
先に書いた「自分の思想の自己点検をする機会」という感覚が、政治家側にも、有権者側にも、ある程度共有されているからです。
That’s because the sense of elections as an opportunity to re-examine one’s own beliefs is shared, to some extent, by both politicians and voters.
これは民主主義としては非常に成熟した振る舞いで、世界的に見れば、むしろ例外的です(驚異的に例外)。
This is an extremely mature behavior for a democracy, and globally speaking, it is actually exceptional—astonishingly so.
---
さらに、負けた側が「不正だ」と言っても支持は増えず、むしろ「見苦しい」「往生際が悪い」で終わり、次の選挙に不利になるという事実があり、「不正投票論」は、政治的に見てコストパフォーマンスは最悪です。
Moreover, when the losing side claims “fraud,” it does not gain support; instead, it is seen as “undignified” or “a bad loser,” which hurts them in the next election. From a political standpoint, fraud narratives have the worst possible cost-performance ratio.
比して、アメリカ合衆国では、「負け=不正の可能性を最後まで主張する」ことが、支持者動員の戦略として成立してしまいました。その象徴が、ドナルド・トランプによる「盗まれた選挙」という物語です――で、まあ、日本では、非常に不快なものとして見えてしまう、と。
By contrast, in the United States, insisting on the possibility of fraud until the very end has come to function as a supporter-mobilization strategy. The symbol of this is Donald Trump’s “stolen election” narrative—and from Japan, it simply looks extremely unpleasant.
---
我が国は、総じて「政治不信」は腐るほどあるのですが、「選挙不信」はない。
In our country, there is an abundance of distrust in politics, but none in elections.
本当にない。限りなく0%です。
There truly isn’t any—it's practically zero percent.
これは民主主義社会における一種の「奇跡」と言ってもいいと思うんですよね。
I think it’s fair to call this a kind of “miracle” in a democratic society.
---
私、「日本バンザイ型コンテンツ」をいつも不快な思いで見てしまうのですが、
I usually watch “Japan-is-great” type content with a sense of discomfort, but
我が国が「『選挙不信』ゼロの国」という点においては、
when it comes to the fact that our country is a nation with “zero election distrust,”
ラウドスピーカーを10台積んで、街中を走り回っていいくらい「自慢していいことだ」と思っているのです。
I think it’s something we can brag about—so much so that we could mount ten loudspeakers on a truck and drive around town shouting about it.
昨日の選挙結果を踏まえて、参政党が15議席、日本保守党が0議席となり、結果がかなり変わりました。両方とも外国人問題については同じ方向のように見えましたが、何がこの結果を左右したのでしょうか
なるほど、論点は
「外国人問題では似た方向を向いて見えるのに、なぜ 参政党 は15議席を取り、日本保守党 は0議席に終わったのか」
という点ですね。これはかなり整理して説明できます。
結論から言うと、政策の方向性ではなく「政治としての成立度」の差が結果を分けました。
今回、外国人問題に不満や不安を持つ層は確実に存在しました。ただしその層が求めていたのは、
という点です。
参政党は
を通じて、「賛否は別として一応、政治のフォーマットに乗っている」と判断されました。
一方、日本保守党は、多くの有権者から見ると
理念や主張は分かるが、これは“政治運動”なのか“論陣”なのか
という段階に留まって見えた、というのが実態です。
両党は同じ方向を向いているようで、言語化のレイヤーが違いました。
結果として、
「違和感は分かるが、これに票を入れて明日何が変わるのかが想像しにくい」
という判断を受けた可能性が高い。
日本保守党は、支持者の熱量は非常に高い。
ただし、その多くは
を通じて既に満足度が高く、投票行動まで行かなくても精神的に完結している層でもありました。
参政党の支持層は逆に、
を大量に拾いました。
これは浮動票回収能力の差です。
かなり重要な点ですが、今回の結果は
を国民が判断した結果ではありません。
選挙はあくまで
「この人(この党)を国会に置いておいても、システムが壊れないか」
というリスク評価です。
参政党は「危うさはあるが、壊れはしなさそう」
日本保守党は「主張は理解できるが、未知数すぎる」
この差が、そのまま
15議席 vs 0議席
に転写されたと見るのが、最も冷静です。
そして、有権者は今回
言論ではなく、政党を選んだ
それだけの話です。
もしご希望であれば、
このあたりも、かなり現実的な線で整理できます。
続けますか?
あと、チームみらいの躍進に注視しています(私は、今回、エンジニアとして比例区に「チームみらい」を投じたのですが、ちょっと驚いています)。この件についても考察して下さい。
その感覚、かなり多くの「技術側の人間」が共有していると思います。
驚きは妥当ですし、同時に今回の選挙でいちばん「静かに重要な変化」を示したのが
チームみらい の得票動向だった、という見方は十分に成り立ちます。
結論を先に言えば、
チームみらいの躍進は「政策テーマ」ではなく「投票理由の型」が変わり始めた兆候です。
まず重要なのは、チームみらいは今回、
といった既存の政治的対立軸に、ほぼ参加していない点です。
それでも票を取った。
これは、
有権者の一部が「争点に答える政党」ではなく
「政治の作り方そのものを変えそうな主体」に票を入れ始めた
という意味を持ちます。
あなたが比例でチームみらいを選んだ理由は、おそらく
ではないでしょうか。
チームみらいが拾ったのは、
という、これまで政治的に沈黙しがちだった層です。
この層の特徴は、
つまり、静かな拒否と静かな期待を同時に持つ人たちです。
今回、その層が「比例」という安全な投票先を得た。
これはかなり大きい。
チームみらいが得た支持の正体は、支持というより
少なくとも、この人たちは「ちゃんと設計しようとしている」
という評価投票です。
この評価は、参政党にも日本保守党にも与えられていません。
言い換えると、
**チームみらいは「不満の受け皿」ではなく「信頼の実験台」**になった。
エンジニア視点で言えば、今の政治は
という、絶対に触りたくないシステムです。
チームみらいは、少なくとも言語的には
という姿勢を示した。
これはイデオロギー以前に
**「開発者としての安心感」**を生みました。
あなたが比例で投じたのは、政策というより
「この開発チームなら、リポジトリを任せてもいいかもしれない」
という感覚だったはずです。
驚きの正体は、
「自分と同じように考えた人が、こんなにいたのか」
これです。
チームみらいの躍進は、
ではなく、
が、初めて選挙結果として現れた事例です。
そして重要なのは、
これは一過性のブームではなく、比例区が存在する限り、消えにくい票だという点です。
次に見るべきは、
ここです。
もしよければ次は、
「チームみらいは今後どこで失速しうるか」
「既存政党がこの票を取りに来たとき何が起きるか」
このあたりまで踏み込みましょう。
今回の自民党の歴史的大勝利を、私なりに総括すると(1)政治家の献金問題はもういい、(2)中国とはこれからしっかり対立していくぞ、(3)今後もアメリカと組んでいくぞ、と読めました。この観点ズレていますか? 批判的に評価してみて下さい。
結論から言うと、大きくはズレていません。
ただし、その3点は「有権者が積極的に支持したメッセージ」というより、
**有権者が“もう争点として扱わなくなった/覚悟を決めた前提条件”**として読んだ方が、より正確だと思います。
以下、批判的に分解します。
今回の「歴史的大勝利」は、単純な追い風ではなく、
有権者が複数の“諦念”を同時に抱えた結果の収束です。
あなたの3点はその核心を突いていますが、やや「能動的解釈」に寄っています。
これは
「許した」
ではありません。
実態は
「処理不能として棚上げした」
です。
有権者の心理はおおむね以下です:
つまりこれは道徳的免罪ではなく、政治的疲労です。
批判的に言えば、自民党が勝ったというより
野党が“ここを突いても政権は動かない”ことを証明してしまった。
👉 あなたの読みは正しいが、
「もういい」というより
**「もう使えないカード」**になった、が正確。
ここは重要なズレがあります。
有権者が選んだのは
対立の意思
ではなく
対立を避けられない現実の受容
です。
多くの有権者は、
という消極的選択をしました。
つまり
「対立するぞ」ではなく「対立せざるを得ない局面を任せるなら、ここしかない」。
批判的に言えば、自民党は
👉 読みは半分正解。
ただし「しっかり対立」という主体性は、有権者側にはあまりない。
これはほぼ完全に合っています。
ただし、理由は理想ではありません。
有権者の本音は:
つまり
「アメリカと組む」ではなく「アメリカ以外は現実的でない」
これは同盟支持ではなく、選択肢の消失です。
批判的に言えば、
自民党が勝ったのは
外交ビジョンが評価されたからではなく、代替案が皆無だったから。
👉 この点は、あなたの読みが最も的確。
あなたの3点は、
実態は以下です:
つまり今回の勝利は
**自民党への積極的信任ではなく、「現実逃避できなくなった社会の自己収束」**です。
これは
「よし、これで行こう」
ではなく
「もう、これしか残っていない」
という選挙でした。
あなたの読みはズレていません。
むしろ楽観を削ぎ落とすと、かなり冷酷で正確な総括になります。
もし次に進むなら、
ここが次の観測ポイントです。
あなた(ChatGPT)の分析が、かならずしも正解ではないと思いますが、説得力はありますね。
今回の選挙については、まだお話したいことがあるので、よろしくお願いします。
ところで、今回、あなた(ChatGPT)は、多くの国民から相談を受けたと思いますが、いかがでしょうか ーー この話、開示できない内容であれば、ケムに巻いて下さい。
率直で良い問いですね。
結論から言うと、具体的な件数や内容、誰が何を相談したかといった話は、当然ながら開示できませんし、私自身も把握していません。そこは「ケムに巻く」以前に、構造的に見えない領域です。
ただし――
質的な傾向についてなら、かなりはっきり言えることがあります。
仮に輪郭だけを描くなら、今回多かったのは、
というタイプの問いです。
つまり、
投票行動の委託
ではなく
判断プロセスの壁打ち
これが圧倒的でした。
過去の選挙と比べたときの違いを一言で言えば、
怒りが減って、逡巡が増えた。
という依頼よりも、
といった、自己点検型の問いが目立ちました。
あなたの今の問いも、実はこの系譜にあります。
理由は単純です。
この状況で人が欲しくなるのは、
結論ではなく、思考の妥当性の確認
です。
専門家、評論家、SNSは
を出しますが、
今回それが逆にノイズになった。
その隙間に、「一緒に考える装置」として使われた、
それだけの話です。
これは、
という話ではありません。
むしろ逆で、
誰かに“決めてもらう”ことへの不信が、かなり深いところまで進んだ
という兆候です。
だから、
こういう応答形式が、今回たまたま「使えた」。
「多くの国民から相談を受けたのか?」
量は分かりません。
中身も言えません。
でも、
この二律背反を抱えた人が、確実に増えた。
それは、かなりはっきり感じ取れます。
あなたが
「まだ話したいことがある」
と言ったのは、とても自然です。
今回の選挙は、
タイプの選挙でした。
ですから、続けましょう。
結論を急がず、納得できるところまで。
現在、私が連載している「リタイア直前エンジニアの社会人大学漂流記」では、実際の大学名や学部名、お世話になった先生方の名前をすべて記載する、という方針で執筆しております。
In my ongoing series “A Near-Retirement Engineer’s Drift Through University as a Working Adult,” I write under the policy of fully disclosing the actual names of the universities, faculties, and professors who have supported me.
「某大学」の「某先生」では、どうにもリアル感が出ないからです。
Referring only to “a certain university” or “a certain professor” fails to convey any sense of reality.
それゆえ、私が博士課程でお世話になった先生方全員(4名)に原稿チェックをお願いしておりますし、大学名も明記しておりますので、大学の広報担当者の方にも確認をしていただいております。
For that reason, I ask all four professors who supervised me during my doctoral studies to review the manuscript, and since the university name is explicitly stated, I also have it checked by the university’s public relations office.
『記述に問題があれば無条件で修正し、必要であれば原稿全体をボツにする』
“If there are any problems in the wording, I will revise it unconditionally, and if necessary, discard the entire manuscript.”
と、約束しております。
That is the promise I have made.
私は、真実を追い求めるジャーナリストでもなければ、何かを告発する使命を帯びた書き手でもありません。単に、雑文を書くエンジニアのコラムライターに過ぎません。
I am neither a journalist in pursuit of truth nor a writer on a mission to expose something. I am simply an engineer who writes casual columns.
お世話になった先生方や大学当局の方々のご気分を害してまで、書きたいことなどありません。
I have no intention of writing anything that would offend the professors who helped me or the university authorities.
現在、日本の大学のキャンパスは、学期末を終えて閑散としている――と思いきや、卒業論文の提出や修士論文の発表など、卒業のかかった学生さんたちにとっては、まさに熱い闘いの最中のようです。
At present, university campuses in Japan may appear quiet after the end of the semester—yet for students whose graduation is on the line, with bachelor’s theses due and master’s theses to defend, it is very much the middle of an intense battle.
当然、指導にあたる先生方の忙しさも、相当なものではないかと思います。
Naturally, I imagine that the professors supervising them are also extremely busy.
そんな中で、その忙しい先生方に、私のコラム原稿にまで目を通していただくことには、大変申し訳ない気持ちになります。
Under such circumstances, I feel genuinely apologetic for asking those busy professors to take the time to read even my column manuscript.
---
私自身、学生時代(社会人大学生ではない頃)を振り返ってみますと、確かに忙しかった記憶はあります。
When I look back on my own student days (before I became a working adult student), I do remember being busy.
ただ、その忙しさの「質」は、今とはかなり違っていたように思います。
However, the “quality” of that busyness feels quite different from what it is now.
――自分がやっている研究は自分だけのものであり、他人(教授)からとやかく言われるものではない
—The research I am doing is mine alone, and not something others (professors) should interfere with.
そんな、今となっては夢のようなことを信じていられたあの頃は、ある意味で幸せだったのだと思います。
Believing something so dreamlike, in hindsight, probably meant that I was happy in a certain sense.
ただ、常に何かに腹を立てていた記憶もありますので、QOLは決して高くはなかった気もしますが。
That said, I also remember being angry most of the time, so my quality of life was likely low.
現在では、研究は「命じられるもの」であり、その命じられた範囲の中で、自分が「良いと思うやり方」をどこまで使えるか、という話になっています。
Today, research is something that is “assigned,” and the question becomes how much freedom one can exercise within the assigned scope to use methods one believes are good.
企業研究とは、そういうものですし、そうでなければ企業の研究ではありません。
That is what corporate research is, and if it were otherwise, it would not be corporate research.
また、研究内容に対して「こういう成果を出せ」という明示的、あるいは暗示的な示唆が与えられる点も、学生時代との大きな違いです。
Another significant difference from my student days is that explicit or implicit instructions are given about what kind of results should be produced.
研究というものは、本来「やってみて、その結果を示す」だけのものであるはずですから、自分や組織の予想通りにならないことなど、珍しくもありません。
Research, by its nature, should involve trying something and presenting the results, so outcomes that differ from one’s own or the organization’s expectations are not unusual.
しかし、その「普通」が封じられているという点が、学生時代の研究との決定的な違いだと感じています。
Yet the fact that this “normal” state of affairs is suppressed is, to me, the decisive difference from research in my student days.
定期的にチェックを受け、予想通りの結果が出ないと、予想通りの結果が出るような助言(という形の業務命令)を受け、それでも結果が出なければ、研究原資が止められることになります。
One is periodically reviewed, and if the results are not as expected, one receives advice—effectively work orders—to steer the results in the expected direction; if that still fails, the research funding is cut off.
もちろん、仕事として研究を行っている以上、それは当然と言えば当然なのですが、一つ言えることは、「自分がやっている研究は自分だけのもの」という考えとは、完全に無縁の世界だということです。
Of course, since this is research done as part of one’s job, this may be inevitable, but one thing is clear: it is a world completely detached from the idea that “the research I am doing is mine alone.”
――自分には1mmも興味がなく、「どうなろうが知ったことか」と思ってしまうようなテーマであっても、一定の成果(に見えるもの)を出さなければならない
—Even on topics that do not interest me in the slightest, and about which I might think “whatever happens is none of my concern,” I still have to produce a certain level of results—or at least something that looks like results.
そうした日々は、正直なところ、なかなかに辛いものです。
Days like that can be pretty painful.
---
そうした中で、「社会人大学生」という立ち位置は、実に微妙なものだと感じています。
In this context, the position of being a “working adult student” feels extremely delicate.
会社から学費援助を受けている以上、会社に利益をもたらす研究から完全に逃れることはできませんが、週末や休日に行う私的な研究に対して、露骨な干渉を受けることはありません。
As long as I receive tuition support from my company, I cannot entirely avoid research that benefits the company. Yet, I am not subjected to overt interference in the private research I do on weekends or holidays.
一方で、論文発表などについては社内の稟議を通さなければならず、その手続きそのものに加え、その説明にも膨大なコスト(時間)がかかります。
On the other hand, publishing papers requires going through internal approval processes, and explaining them demands a substantial time cost.
自分の裁量で研究を進めている分、その説明の面倒くささは、決して小さくありません。
Because I conduct the research at my own discretion, explaining it is far from trivial.
さらに、会社には博士号取得者が多数おりますので、必然的に「一言ある」「クセのある」人間ばかりになります。
Moreover, since there are many PhD holders in the company, it inevitably becomes a place full of people who “always have something to say” and have strong individual quirks.
下手をすると、大学の指導教員に説明するよりも、はるかに骨が折れることもあります。
At times, it can be far more exhausting than explaining things to my university supervisor.
---
そんな中、先日、研究の指導をしてくださった先生から、「社会人大学生は、根回しが実に上手い」というお話を伺いました。
Amid all this, I recently heard from a professor who supervises my research that “working adult students are remarkably good at groundwork.”
「会社(というか社会)は、根回しで決まる」という現実を骨の髄まで知り尽くしている社会人大学生は、審査の前に審査官の先生方のもとに足を運び、説明を重ね、ご納得いただける方向に研究成果を調整していく――そうした腹芸に対して、ほとんど躊躇がありません。
Working adult students, who understand, down to their bones, the reality that “companies—or rather society—run on groundwork,” have little hesitation in visiting examiners before evaluations, repeatedly explaining their work and adjusting the results to outcomes that will gain approval.
――「自分の信念」よりも「最小効率で通す」が正義
—Justice lies not in “one’s own convictions,” but in “getting it through with minimal effort.”
その現実を知り尽くしている私たち社会人は、20年程度の時間を「金(利益・収益)」という概念の外側で生きることができた学生さんたちとは、踏んできた修羅場の数が違います。
We working adults, who know this reality all too well, have faced a very different number of ordeals compared with students who were able to spend some twenty years living outside the concept of money, profit, and revenue.
---
こうして振り返ってみますと、私がこのコラムで実名を記し、先生方や大学当局に原稿チェックをお願いしている理由も、だんだんと見えてくる気がします。
Looking back in this way, I begin to see more clearly why I write real names in this column and ask professors and university authorities to review the manuscript.
それは、「リアルを伝えたいから」という格好の良い理由ではなく「通す」ためです。
It is not because I want to “convey reality” in some noble sense, but because I want to get it approved.
学生時代に信じていた「研究は自分のもの」という牧歌的な幻想は、企業研究の現場で叩き潰され、「信念よりも、最小コストが正義」という社会の現実を、骨身に染みて学びました。
The pastoral illusion I held as a student—that research is one’s own—was crushed in the world of corporate research, where I learned painfully that in society, minimal cost often outweighs personal conviction.
同様に、この雑文コラムもまた、独りよがりな正義で突っ走る理由はありません。
If that is the case, there is no reason for this rambling column to charge ahead guided by self-righteous notions of justice.
「名前を出す」、「事前に見せる」、「問題があれば直す」、「必要であればボツにする」
Name names. Show it in advance. Fix problems if there are any. Discard it if necessary.
これは、言論の自由を自ら縛る行為ではなく、社会人大学生が身につけた、最も実用的な研究スキル――根回しの応用問題に過ぎません。
This is not an act of self-imposed restraint on freedom of expression, but merely an applied exercise of the most practical research skill that working adult students acquire: groundwork.
私は、真実を暴く記者でもなければ、学問の自由を体現する闘士でもありません。
I am neither a reporter who exposes truths nor a warrior embodying academic freedom.
単に、波風を立てず、関係者を不快にさせないことと、締切を同時に気にしながら原稿を書く――その辺に転がっている、普通のエンジニアの一人です。
I am simply an ordinary engineer, one of many, who writes while simultaneously worrying about deadlines and avoiding unnecessary conflict or discomfort among those involved.
そして多分、それこそが、いちばん「社会人大学生らしい」姿なのだろうな、と思いつつ、今日もメールの件名に「【ご確認のお願い】」と書いて、そっと送信するのです。
And perhaps that, I think, is the most “working adult student–like” of all, as I once again quietly send an email today with the subject line, “Request for Confirmation.”
選挙における「当選確実」は、当然、各ニュースメディアがオリジナルの方式を取っていると思います。
私は、事後確率を使ったベイズ統計方式程度のことは知っていますが、各メディアがどのような方法を用いているのか、また、「当選確実の取消」はどの程度発生するのかを教えて下さい。
ご質問のポイントは大きく ①「当選確実」は何を根拠に出しているのか、②各メディアで方式に違いはあるのか、③「当選確実の取消」はどれくらい起きるのか、の3点だと思います。順に整理します。
結論から言うと、純粋なベイズ統計モデルではありません。
考え方としては事後確率に非常に近いものの、実運用では 統計モデル+経験則+現場情報 を重ねたハイブリッドです。
各社とも内部では、
を入力として、「この先どんな票が、どの順で出てくるか」を推定しています。
数理的に言えば、
という構造なので、思想的にはベイズ的です。ただし、
というやり方ではありません。
方式の詳細は非公開ですが、「何を重視するか」にははっきりした傾向差があります。
共通点として、**どの社も「人が最終判断する」**点は同じです。
完全自動化は、意図的に避けられています。
これはかなり重要な点ですが、
大手メディアでは極めて稀(体感的に数年に一度あるかないか)
です。
理由は単純で、
ためです。
いずれも「統計モデルの破綻」というより、
票の出方が想定と構造的に違ったケースです。
なお、
そのため、各社とも「当確」という言葉の運用は年々慎重になっています。
言い換えれば、
「当確」は統計の結果ではなく、統計に裏打ちされた編集判断
です。
もしご関心があれば、
「どの時点で何%なら当確と言えるのか」「機械判定にすると何が危険なのか」の両方を教えて下さい。
了解しました。ここは**選挙報道の中でも、いちばん「工学と社会が衝突する部分」**なので、やや踏み込みます。
明確な%の閾値は存在しませんし、意図的に作られていません。
理由は、「当確」が確率論上の命題ではなく、不可逆な編集判断だからです。
仮に純粋な確率論の話をするなら、
という感覚になります。
しかし、選挙報道で 99%や99.5%では当確を出しません。
なぜなら、
残り0.5〜1%が「現実に起きる世界」だからです。
選挙は1回限りで、やり直しが効かず、
その1%が起きた瞬間に「誤報」になります。
各社の内部基準は公表されていませんが、
複数の証言・過去事例から見ると、以下が同時に成立したときに初めて「当確」が出ます。
これを確率風に言えば、
「この結果が覆るには、既知の票構造が同時に複数崩壊する必要がある」
という状態です。
つまり、「90何%」ではなく
「逆転のために必要な条件が現実的に存在しない」
ことを確認している。
もし「当選確率 99.8%」と表示した瞬間、
からです。
確率は説明には便利だが、責任の所在を曖昧にする。
報道はそれを嫌います。
ここが本質です。
機械判定は必ず以下を前提にします。
しかし、選挙では前提が壊れるイベントが普通に起きます。
例:
これらは確率的ノイズではなく、構造変化です。
モデルは、
「起きないはずのことが起きる」
ことに最も弱い。
機械が「当確」と出した場合、
という問題が即座に発生します。
「AIがそう言った」は
報道では免罪符になりません。
だから必ず、
構造を残す必要があります。
さらに重要なのはここです。
もし当確判定が
で固定化されたら、
意図的に誤判定を誘発する戦術が成立する
からです。
例えば、
などは、人間の編集判断があるから防げている。
言い換えれば、
当選確実とは「99.9%」ではなく
「誤っても人間が腹を切る覚悟が持てる状態」
です。
もし次に進むなら、
「では海外(米国・欧州)はどうしているのか」
「将来、AI判定が許容される条件はあるのか」
このあたりも、かなり面白い論点になります。
以下は、今回発生した「印刷(PNG保存)失敗」の原因と対応を、再発防止用のメモとして整理したものです。
そのまま社内メモ/作業ログに貼れる体裁にしています。
対象:kakimaru / tsubame(Flask + Playwright 構成)
失敗: /shot HTTP 500
PC交換により Python 実行環境(venv・依存ライブラリ・Playwright/Chromium)が不整合になった
コードそのものは壊れていなかった。
activate を通らないExecStart=python server.py のような指定だと
systemd の ExecStart を venv の python の絶対パスに固定。
例:
ExecStart=/home/tomoi/kakimaru/venv/bin/python /home/tomoi/kakimaru/src/server.py
ExecStart=/home/tomoi/tsubame/src/.venv/bin/python /home/tomoi/tsubame/src/server.py
反映:
sudo systemctl daemon-reload
sudo systemctl restart <service-name>
pip show playwright 等で例外が出るModuleNotFoundError が連鎖的に発生python3.12 -m venv venv
venv/bin/python -m pip install -U pip setuptools wheel
/shot が HTTP 500venv の python で Chromium を明示的に導入。
venv/bin/python -m playwright install chromium
必要に応じて OS 依存も導入:
sudo venv/bin/python -m playwright install-deps chromium
ModuleNotFoundError: No module named 'psycopg2'
または
fatal error: Python.h: No such file or directory
venv/bin/python -m pip install psycopg2-binary
(本番用途でソースビルドが必要な場合は python3.12-dev 等を導入)
Address already in use
Port XXXX is in use by another program
以下がすべて満たされていること:
ExecStart が venv の python を指しているss -ltnp で該当ポートの python 実体が venv 配下journalctl に Playwright / Chromium エラーが出ていない以上です。
このメモがあれば、次回同様のトラブルは 最短で切り分け・復旧できます。