島根県安来市のフリーランスエンジニア_プログラマー画像1
買い物難民支援システムの作り方【デモ付き】地域課題をITで解決 | 安来市フリーランスエンジニアの実践ガイド – Eatransform

買い物難民支援システムの作り方【デモ付き】地域課題をITで解決 | 安来市フリーランスエンジニアの実践ガイド

🚧 デモシステム

🛒 買い物支援マッチングシステム

買い物難民とサポーターをつなぐ地域共助プラットフォーム

💡 使い方のヒント
具体的に書く: 商品名、サイズ、個数を明確に
連絡先: 電話番号も記載すると安心
お礼: 地域通貨や現金で感謝の気持ちを
👵
田中さん(78歳)
30分前
募集中
📍 プラーナ安来店
牛乳1本、食パン1斤、卵1パック、バナナ1房
👴
佐藤さん(82歳)
1時間前
募集中
📍 ツルハドラッグ
風邪薬、湿布、ティッシュ5箱
👵
鈴木さん(75歳)
2時間前
マッチング済
📍 サンアイ安来店
お米5kg、醤油、味噌

サポーター登録

登録済みサポーター

👩
佐々木さん(45歳)
📍 安来町周辺
プラーナ ツルハドラッグ
🕐 平日午前中 | 🚗 車で移動可能
買い物のついでにお手伝いします!重いものも大丈夫です。
✅ 対応回数: 23回 ⭐ 4.9
👨
田村さん(38歳)
📍 荒島町周辺
サンアイ ドラッグストア
🕐 土日終日 | 🚗 車で移動可能
週末に安来市内どこでも伺えます。お気軽にどうぞ。
✅ 対応回数: 15回 ⭐ 5.0
👩
高橋さん(52歳)
📍 飯島町周辺
プラーナ コンビニ
🕐 平日午後 | 🚲 自転車
近所の方を中心にお手伝いしています。
✅ 対応回数: 31回 ⭐ 4.8
💡 サポーターの心得
無理のない範囲で: 自分の買い物のついでに
連絡はこまめに: 買い物前後に状況を伝える
レシートは必ず: 金額の透明性を保つ
謝礼は受け取ってOK: 地域通貨や少額のお礼
📚 実装ガイド

買い物支援システム構築の完全ガイド

地域で実際に運用できるシステムの作り方

1
システム設計とコンセプト定義

まずは問題の本質を理解する

  • 買い物難民の実態: 高齢化率38%の安来市では、免許返納後の移動手段がない人が増加
  • 既存の課題: 民間配達サービスは都市部のみ、移動販売車も週1-2回程度
  • このシステムの強み: 既存の「買い物に行く人」を活用した相互扶助モデル
  • ターゲットユーザー: 依頼者(70歳以上)とサポーター(30-60代の地域住民)

💡 成功の鍵: ITシステムだけでなく、地域コミュニティとの連携が不可欠。社協、民生委員、町内会との協力体制を構築しましょう。

2
技術スタックの選定

実装しやすく、運用しやすい技術を選ぶ

🎯 推奨構成(初期フェーズ)
フロントエンド: HTML/CSS/JavaScript バックエンド: Node.js + Express データベース: PostgreSQL 通知: LINE Messaging API インフラ: AWS / Heroku
  • なぜシンプルな構成?: 地方では複雑なシステムの保守が困難。メンテナンスしやすさを最優先
  • LINE連携は必須: 高齢者のスマホ普及率は低いが、LINEは使える人が多い
  • WordPress統合: 既存の市のサイトがWordPressなら、カスタムHTMLで埋め込み可能
  • スケーラビリティ: 最初は1地区100人規模、将来は市全体3万人に拡大可能な設計

💡 コスト重視なら: Herokuの無料プラン廃止後は、Railway(月5ドル〜)やRender(無料プランあり)がおすすめ。

3
データベース設計

必要最小限のテーブル構成

  • usersテーブル: 依頼者・サポーター共通(user_id, name, location, phone, line_id, role)
  • requestsテーブル: 買い物依頼(request_id, user_id, shop_name, items, preferred_date, status)
  • matchesテーブル: マッチング記録(match_id, request_id, helper_id, matched_at, completed_at)
  • reviewsテーブル: 評価(review_id, match_id, rating, comment)
📄 PostgreSQL テーブル定義例
CREATE TABLE users (
  user_id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  location VARCHAR(200),
  phone VARCHAR(20),
  line_id VARCHAR(100),
  role VARCHAR(20), -- 'requester' or 'helper'
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE requests (
  request_id SERIAL PRIMARY KEY,
  user_id INT REFERENCES users(user_id),
  shop_name VARCHAR(200),
  items TEXT,
  preferred_date VARCHAR(100),
  status VARCHAR(20) DEFAULT 'waiting', -- 'waiting', 'matched', 'completed'
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE matches (
  match_id SERIAL PRIMARY KEY,
  request_id INT REFERENCES requests(request_id),
  helper_id INT REFERENCES users(user_id),
  matched_at TIMESTAMP DEFAULT NOW(),
  completed_at TIMESTAMP
);

💡 個人情報保護: 電話番号やLINE IDは暗号化して保存。GDPR/個人情報保護法に準拠したプライバシーポリシーを必ず用意。

4
バックエンドAPI開発

Node.js + Expressでシンプルなサーバー構築

📄 基本的なサーバー構成(server.js)
const express = require('express');
const { Pool } = require('pg');
const app = express();

// データベース接続
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: { rejectUnauthorized: false }
});

app.use(express.json());

// 買い物依頼の投稿API
app.post('/api/requests', async (req, res) => {
  const { user_id, shop_name, items, preferred_date } = req.body;
  
  try {
    const result = await pool.query(
      'INSERT INTO requests (user_id, shop_name, items, preferred_date) VALUES ($1, $2, $3, $4) RETURNING *',
      [user_id, shop_name, items, preferred_date]
    );
    
    // LINE通知を送信(後述)
    await sendLineNotification(result.rows[0]);
    
    res.json({ success: true, request: result.rows[0] });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// 依頼一覧取得API
app.get('/api/requests', async (req, res) => {
  try {
    const result = await pool.query(
      'SELECT * FROM requests WHERE status = $1 ORDER BY created_at DESC',
      ['waiting']
    );
    res.json(result.rows);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(process.env.PORT || 3000);
  • RESTful API: GET(取得)、POST(作成)、PUT(更新)のシンプルな設計
  • エラーハンドリング: try-catchで確実にエラーをキャッチ
  • 環境変数: データベース接続情報は.envファイルで管理(GitHubに上げない!)
  • CORS設定: フロントエンドからのアクセスを許可

💡 セキュリティ: 本番環境では必ずHTTPS化し、helmet.jsでセキュリティヘッダーを設定しましょう。

5
LINE Messaging API連携

LINEで通知を受け取れるようにする

  • LINE Developersで準備: LINE公式アカウントを作成し、Messaging APIを有効化
  • Channel Access Token取得: 管理画面から発行(環境変数で管理)
  • Webhook設定: サーバーのURLを登録して、メッセージ受信時の処理を実装
  • プッシュ通知: 新しい依頼が投稿されたらサポーターに自動通知
📄 LINE通知の実装例
const line = require('@line/bot-sdk');

const client = new line.Client({
  channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN
});

// 新規依頼をサポーターに通知
async function sendLineNotification(request) {
  // サポーター全員のLINE IDを取得
  const helpers = await pool.query(
    "SELECT line_id FROM users WHERE role = 'helper' AND line_id IS NOT NULL"
  );
  
  const message = {
    type: 'text',
    text: `🛒 新しい買い物依頼が届きました!\n\n📍 ${request.shop_name}\n📝 ${request.items}\n\n依頼を確認: https://yourapp.com/requests/${request.request_id}`
  };
  
  // 全サポーターに通知
  for (const helper of helpers.rows) {
    try {
      await client.pushMessage(helper.line_id, message);
    } catch (err) {
      console.error('LINE通知エラー:', err);
    }
  }
}

⚠️ LINE公式アカウント料金: 月200通まで無料、それ以降は有料プランが必要。地域規模に応じてプラン選択を。

6
マッチングロジックの実装

依頼者とサポーターを効率的にマッチング

  • 地理的条件: 依頼者とサポーターの距離を計算(徒歩5分圏内を優先)
  • 時間的条件: サポーターの対応可能時間帯と依頼の希望日時をマッチ
  • 店舗条件: サポーターがよく行く店と依頼の店舗が一致するか
  • 自動マッチング: 条件が完全一致したら自動提案、それ以外は手動選択
📄 マッチング処理の例
// サポーター受諾API
app.post('/api/match', async (req, res) => {
  const { request_id, helper_id } = req.body;
  
  try {
    // トランザクション開始
    await pool.query('BEGIN');
    
    // マッチング記録を作成
    await pool.query(
      'INSERT INTO matches (request_id, helper_id) VALUES ($1, $2)',
      [request_id, helper_id]
    );
    
    // 依頼のステータスを更新
    await pool.query(
      "UPDATE requests SET status = 'matched' WHERE request_id = $1",
      [request_id]
    );
    
    // 依頼者にLINE通知
    const request = await pool.query(
      'SELECT * FROM requests WHERE request_id = $1',
      [request_id]
    );
    
    const requester = await pool.query(
      'SELECT * FROM users WHERE user_id = $1',
      [request.rows[0].user_id]
    );
    
    if (requester.rows[0].line_id) {
      await client.pushMessage(requester.rows[0].line_id, {
        type: 'text',
        text: `✅ サポーターが見つかりました!\n\n間もなく連絡があります。`
      });
    }
    
    await pool.query('COMMIT');
    res.json({ success: true });
  } catch (err) {
    await pool.query('ROLLBACK');
    res.status(500).json({ error: err.message });
  }
});

💡 スケーラビリティ: 将来的にAIを使った自動マッチング(移動ルート最適化、過去の実績から相性予測)も検討できます。

7
地域通貨・謝礼システムの導入

現金の直接やり取りを避け、透明性のある謝礼システムを

  • 地域通貨の導入: 「やすぎコイン」のような独自ポイント制
  • 謝礼の目安: 1回の買い物代行で100〜300ポイント(100〜300円相当)
  • ポイント利用先: 地域商店での買い物、市の施設利用料などに使える
  • 自治体負担: ポイントの原資は自治体の社会福祉予算から拠出
📄 地域通貨テーブル設計
CREATE TABLE local_currency (
  transaction_id SERIAL PRIMARY KEY,
  user_id INT REFERENCES users(user_id),
  amount INT, -- プラスが受取、マイナスが支払い
  transaction_type VARCHAR(50), -- 'reward', 'payment', 'bonus'
  related_match_id INT REFERENCES matches(match_id),
  created_at TIMESTAMP DEFAULT NOW()
);

-- サポーターへの謝礼付与
INSERT INTO local_currency (user_id, amount, transaction_type, related_match_id)
VALUES (helper_id, 200, 'reward', match_id);

-- 残高確認
SELECT SUM(amount) as balance 
FROM local_currency 
WHERE user_id = $1;

💡 他の選択肢: 地域通貨が難しい場合は、ボランティアポイント制度(社協発行)や、商品券での謝礼も検討できます。

8
地域との連携体制構築

システムだけでは成功しない。地域コミュニティとの協働が鍵

  • 安来市社会福祉協議会: 民生委員ネットワークを通じた依頼者の募集
  • 地域商店との提携: プラーナ、サンアイ、ツルハドラッグなどと協定締結
  • 町内会への説明会: サポーター登録を各地区で呼びかけ
  • 市役所との連携: 広報誌、市公式サイトでの告知、予算確保

💡 実証実験の進め方: まず1地区(100世帯程度)で3ヶ月運用し、課題を洗い出してから全市展開を。

⚠️ 個人情報の取り扱い: 民生委員や町内会と情報共有する際は、本人同意を必ず取得。書面での同意書を準備しましょう。

9
運用開始とKPI管理

システムを継続的に改善していくための指標設定

  • 登録ユーザー数: 依頼者・サポーターそれぞれの推移を追跡
  • マッチング成功率: 投稿された依頼のうち何%が成立したか
  • 平均対応時間: 依頼投稿から完了までの時間
  • 利用者満足度: 月次アンケートで★5段階評価
  • コスト効率: 1件あたりの運営コスト(人件費含む)
💰 運用コスト見積もり(月間)
サーバー費用(AWS/Heroku) 月 3,000〜10,000円
LINE公式アカウント 月 0〜15,000円
システム保守管理 月 30,000〜50,000円
地域通貨原資(100件想定) 月 20,000〜30,000円
合計 月 53,000〜105,000円
💡 補助金活用:
地域福祉推進事業補助金、過疎地域等集落ネットワーク圏形成支援事業など、国・県の補助金で大半をカバー可能。

💡 持続可能性: 2年目以降は地域商店からの手数料収入(1件100円程度)も検討。Win-Winのエコシステムを構築しましょう。

WordPressからTwitterへ自動投稿するプラグインを作りました

エクセル自動DB化システム | デモ&実装ガイド完全版