AIエージェント元年 第10話
スタンドみたいにパーソナライズされたAIアプリがあると、いいですよね。
今年と言わず今月はまず交通系アプリを開発したいと考えています。
東京だと山手線のように次から次と電車が来るので、何時に公共交通機関が来るというのはあまり重要ではないかもしれませんが、常に徒歩時間を逆算し何時に乗れる予定か教えてくれる、そんなアプリを作りたいと思います。
iPhoneの時計って、リアルタイムで動いてくれるじゃないですか?あんな感じで、次に乗れる可能性のある時間を常時表示してくれるような、ありそうでない、そんなアプリを目指したいと思います。ゆくゆくはインバウンド向けなどサービス拡充していきたいですね。
実装方針は以下です。
Google APIを用いてGPSを取得し、最寄り駅を算出
同様に徒歩時間を算出
札幌のオープンデータを用いて、地下鉄の時刻データベースを作る
まずは札幌の地下鉄にフォーカスします。APIを使えば、簡単に乗り物も時刻情報も取得できそうですが、基本的には自前でJson形式のオープンデータを持ちたいので、GPSとか特殊なもの以外は極力データを手元に寄せ、開発していきたいと思います。
時間は3候補あげるようにしたかったのですが、undefinedになってしまいました。
フロントはReact Nativeで、バックエンドはNode.jsで書きました。
const { Client } = require('pg');
// Database connection configuration
const dbConfig = {
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT || 5432,
};
// Main handler function
exports.handler = async (event) => {
let client = null;
try {
// Parse input parameters
let nearestStation, walkingMinutes;
if (typeof event === 'string') {
const parsedEvent = JSON.parse(event);
nearestStation = parsedEvent.nearestStation;
walkingMinutes = parsedEvent.walkingMinutes;
} else if (event.body) {
const parsedBody = JSON.parse(event.body);
nearestStation = parsedBody.nearestStation;
walkingMinutes = parsedBody.walkingMinutes;
} else {
nearestStation = event.nearestStation;
walkingMinutes = event.walkingMinutes;
}
// Basic validation
if (!nearestStation || typeof walkingMinutes !== 'number') {
return {
statusCode: 400,
body: JSON.stringify({
error: 'Invalid parameters. Required: nearestStation (string) and walkingMinutes (number)',
}),
};
}
// Get current JST time and calculate arrival time
const jstNow = new Date(new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' }));
const arrivalTime = new Date(jstNow);
arrivalTime.setMinutes(arrivalTime.getMinutes() + walkingMinutes);
// Get the hour part of the arrival time directly
const validDepartureHour = arrivalTime.getHours();
// Connect to database
client = new Client(dbConfig);
await client.connect();
const arrivalMinute = arrivalTime.getMinutes();
const nextHour = validDepartureHour + 1; // 現在の時間に1を足す
// Simplified query for next available trains (current hour and next hour)
const query = `
SELECT
"direction",
"hour",
"minutes",
"nearestStation",
"weekdayOrEnd"
FROM "TimeSchedule"
WHERE "nearestStation" = $1
AND "hour" = $2
AND "weekdayOrEnd" = $3
UNION ALL
SELECT
"direction",
"hour",
"minutes",
"nearestStation",
"weekdayOrEnd"
FROM "TimeSchedule"
WHERE "nearestStation" = $1
AND "hour" = $4
AND "weekdayOrEnd" = $3
LIMIT 2;`;
const params = [
nearestStation,
validDepartureHour + "時", // Current hour
arrivalTime.getDay() === 0 || arrivalTime.getDay() === 6 ? 'weekend' : 'weekday',
nextHour + "時", // Next hour
];
const res = await client.query(query, params);
if (res.rows.length > 0) {
const schedules = res.rows.map((row) => {
// Split the minutes string into an array and filter it based on arrivalTime's minute
const filteredMinutes = row.minutes
.split(' ') // Split the string into an array
.filter(minute => parseInt(minute) >= arrivalMinute) // Filter minutes >= arrivalMinute
.slice(0, 3) // Take only the first 3
.join(' '); // Join the filtered minutes back into a space-separated string
return {
direction: row.direction,
hour: row.hour,
minutes: filteredMinutes, // Return the filtered minutes
nearestStation: row.nearestStation,
weekdayOrEnd: row.weekdayOrEnd,
searchInfo: {
queryTime: jstNow.toLocaleTimeString("ja-JP"),
arrivalTime: arrivalTime.toLocaleTimeString("ja-JP"),
walkingMinutes: walkingMinutes,
},
};
});
// Check if we have less than 3 results, and fetch from the next hour if necessary
if (schedules[0].minutes.split(' ').length < 3) {
// Fetch missing minutes from the next hour's data
const missingMinutesCount = 3 - schedules[0].minutes.split(' ').length;
const nextHourSchedule = res.rows.find(row => row.hour === (validDepartureHour + 1) + "時");
if (nextHourSchedule) {
const additionalMinutes = nextHourSchedule.minutes
.split(' ')
.slice(0, missingMinutesCount) // Take the missing number of minutes
.join(' ');
// Append the missing minutes to the current schedule
schedules[0].minutes += ' ' + additionalMinutes;
}
}
return {
statusCode: 200,
body: JSON.stringify(schedules),
};
} else {
return {
statusCode: 404,
body: JSON.stringify({
error: 'No schedules found',
searchParams: {
station: nearestStation,
searchTime: jstNow.toLocaleTimeString('ja-JP'),
arrivalTime: arrivalTime.toLocaleTimeString('ja-JP'),
walkingMinutes: walkingMinutes,
dayType:
arrivalTime.getDay() === 0 || arrivalTime.getDay() === 6
? 'weekend'
: 'weekday',
},
}),
};
}
} catch (error) {
console.error('Error processing request:', error);
return {
statusCode: 500,
body: JSON.stringify({
error: 'Internal server error',
message: error.message,
type: error.name,
}),
};
} finally {
if (client) {
try {
await client.end();
} catch (error) {
console.error('Error closing database connection:', error);
}
}
}
};
明日はこのundefinedを修正し、少なくとも札幌の地下鉄データは全部入れたいと思います(できれば、リリースも着手したい)。
それではまたごきげんよう。