Synaplan
Open-source AI platform for companies: self-hosted AI agents, GDPR-compliant chatbot widget, RAG knowledge base, WhatsApp and Outlook AI, MCP, multi-model routing. Docker, Kubernetes or hosted in Germ
顧客・関係者のつながりをまとめて管理
Translation pending
BusinessTypeScript · PLpgSQLGitHub @yotatameguchi-boopOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
RelationNote — 顧客・関係者のつながりをまとめて管理
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗顧客・関係者のつながりをまとめて管理。顧客や関係者のデータを組織ごとに管理する汎用のSaaS
基本設計書 v0.1(2026-10-21)に沿った実装です。最初の想定利用者はNPO(基本設計書の前提)。複数の組織が共通基盤を使うマルチテナント型で、 応募フォームの回答を人物単位に整理し、イベント参加履歴と、写真からの人数推定を管理します。
| 設計書 | 実装 |
|---|---|
| 画面・業務API | Next.js 16(App Router / Server Actions / Route Handlers)・TypeScript |
| DB・組織分離 | Supabase PostgreSQL。全主要テーブルに organization_id、外部キーにも組織IDを含め、RLS で参照・追加・更新・削除を制御 |
| 認証・権限 | Supabase Auth + memberships(オーナー/管理者/編集者/閲覧者 + 一括出力・一括削除・人物統合・権限変更の個別権限) |
| 写真保存 | Supabase Storage の非公開バケット。アップロードは期限付きURL、閲覧は所属メンバーのみ期限付きURL |
| 非同期処理 | Supabase Queues(pgmq)+ Edge Function process-jobs + Supabase Cron(毎分起動) |
| 人物検出 | Amazon Rekognition DetectLabels(Person)。開発用にダミー検出器(DETECTOR=mock) |
ディレクトリ:
supabase/migrations/ … スキーマ・RLS・業務ロジック(名寄せ、統合、利用枠、ジョブ、集計)supabase/functions/process-jobs/ … ワーカー(写真解析・回答の取り込み・一括出力・期限切れ整理)src/app/orgs/[orgId]/ … 管理画面src/app/api/ … アップロード・取り込み・外部フォーム受信・出力ダウンロードtests/ … 単体テストと、ローカルSupabaseに対する受け入れテストnpm install
supabase start -x studio,postgres-meta,imgproxy,vector,logflare,realtime,supavisor
supabase status -o env # 値を .env.local に転記(.env.example 参照)
cp supabase/functions/.env.example supabase/functions/.env # WORKER_SECRET を設定
supabase functions serve --env-file supabase/functions/.env --no-verify-jwt # ワーカー
./scripts/setup-local-cron.sh # Cron から毎分ワーカーを呼ぶための Vault 登録
node scripts/seed-dev.mjs # デモ組織とサンプルデータ(任意)
npm run dev # http://localhost:3080
ポートは他プロジェクトと衝突しないよう API 55621 / DB 55622 にしています。
CSV取り込みの試験用ファイル: scripts/sample-volunteer-form.csv
npm test
ローカルの Supabase が起動している必要があります。設計書 §16 の受け入れ条件のうち、自動で確かめられるものを網羅しています。
| 状況 | 結果 |
|---|---|
| メールまたは電話が一致し、氏名も一致する人物が1人 | 既存人物に紐づけ(空欄の補完と連絡先・タグの追加のみ。上書きしない) |
| メール・電話は一致するが氏名が違う | 確認待ち(家族で共有している可能性) |
| 氏名だけ一致 | 確認待ち(同姓同名の可能性) |
| 一致なし | 新規作成 |
確認待ちは「確認待ちの回答」画面でスタッフが判断します。登録済み人物どうしの重複は「重複候補」画面で統合でき、統合前の状態は履歴に残ります。
ワーカー(Edge Function)の環境変数 MAIL_TRANSPORT=smtp・SMTP_*・MAIL_FROM・APP_URL(supabase/functions/.env.example 参照)を設定し、
Vercel の環境変数 MAIL_SENDING_CONFIGURED=1 を入れると画面の送信ボタンが有効になる。Edge Functions は 25・587 番ポートに接続できないため、Gmail は 465 を使う。
ローカルでは Supabase 付属のテスト用メールサーバー(Mailpit、http://127.0.0.1:55624)に送られ、外部には届かない。
「取り込み・連携」でフォーム(取り込み元)を作るときに方式を選ぶ。取り込んだ回答は CSV と同じ流れ(二重取り込み防止 → 名寄せ → 確認待ち)に乗る。
| 方式 | 仕組み | 利用者の操作 |
|---|---|---|
| Google フォーム | OAuth(読み取り専用)で連携し、Forms API から前回以降の回答を定期取得(既定15分ごと) | 「Google と連携」→ フォームの URL を貼る → 提案された項目の対応を確認 →「自動取り込みを始める」 |
| Google スプレッドシート | 同上。Sheets API で1行目を見出しとして読み、内容が同じ行は二重に取り込まない | 同上(シート名は任意) |
| Typeform / Tally | 相手のサービスから Webhook。署名(HMAC-SHA256)で送信元を確認 | 「署名用の秘密を発行」→ 相手の Webhook 設定に URL と秘密を入れる |
| 汎用 Webhook | Authorization: Bearer <秘密> 付きの JSON | Apps Script などから送る |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET を Vercel と Edge Function の両方に設定する。
承認済みのリダイレクト URI は https://relationnote.vercel.app/api/connectors/google/callback。
フォームの回答・スプレッドシートの読み取りは Google の「機密性の高いスコープ」のため、一般公開には Google の審査が必要(審査前はテストユーザーに登録した100人まで)node scripts/mock-google.mjs(Google を模した疑似サーバー)で、連携から取り込みまで確認できる。本物の Google での検証ではないrelation-note(ref sufuttgmkfmpsscuqdld、東京、無料プラン、組織 yotatameguchi-boop's Org)process-jobs(DETECTOR=mock)を Supabase Cron が毎分起動.env.remote(Git 管理外)の DEV_LOGIN_EMAIL / DEV_LOGIN_PASSWORDENV_FILE=.env.remote ALLOW_NON_LOCAL=1 node scripts/seed-dev.mjs、続けて同じ指定で seed-demo-features.mjs・seed-demo-crm.mjsmain に push すると本番へ自動デプロイ(他のブランチはプレビュー。プレビューも同じデモ用 Supabase につながる。プレビューは Vercel の保護で関係者のみ閲覧可)mailer_autoconfirm)。人物検出はダミー。実在の個人情報は入れないことsupabase db push、supabase functions deploy process-jobsWORKER_SECRET、DETECTOR=rekognition、AWS_REGION、AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEYproject_url(https://<ref>.supabase.co)と worker_secret を登録(scripts/setup-local-cron.sh と同じ内容)NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY / SUPABASE_SERVICE_ROLE_KEY / NEXT_PUBLIC_APP_URL を設定src/proxy.ts)my_org_ids・has_role・has_perm)で強制するので、API を直接呼んでも回避できない。締め出し防止のため、必須にできるのは自分が二段階認証済みのオーナーだけ。管理者はスタッフの設定状況を確認できるENV_FILE=.env.remote node scripts/reset-mfa.mjs <メールアドレス> で解除する/orgs/:id/insights)で、知りたいことを文章で入力すると、指標・切り口・内訳・期間に読み取ってグラフにする(例:「今年の新規登録数を月別に」「地域別の人数」「直近6か月の参加数をタグ別の内訳で」)src/lib/insights.ts の parseQuestion)。生成AIは使わず、入力した文章を外部に送らない。読み取った条件は画面で確認・修正できるinsight_query(指標・切り口は一覧にあるものだけ受け付ける。RLS のまま実行)。10万人規模の組織で 0.35 秒以内trialing)。オーナーは「料金・契約」から申し込む。無料期間中に申し込んでも、請求は無料期間の終わりから/api/billing/webhook)は署名を確かめ、イベントIDで1回だけ処理する。通知の順序に頼らず、毎回 Stripe から最新の契約を取り直して反映するrefresh_all_org_access() が契約状態を確認する(pg_cron npo-crm-org-access)。無償で提供する組織は subscription_status = 'complimentary'node scripts/mock-stripe.mjs)で申し込み〜解約〜再申し込みを確認済み。本物の Stripe での確認は未実施有効にする手順(運営者)
tax_behavior=exclusive、税込みなら inclusive)。消費税は税率(10%)を作るか Stripe Tax を使う
https://relationnote.vercel.app/api/billing/webhook を登録(イベント:checkout.session.completed、customer.subscription.created/updated/deleted、invoice.paid、invoice.payment_failed)STRIPE_SECRET_KEY、STRIPE_WEBHOOK_SECRET、STRIPE_PRICE_MONTHLY、STRIPE_PRICE_YEARLY、(任意)STRIPE_TAX_RATE_ID を設定。まずテストモードの鍵(sk_test_…)で通しで確認する/terms、/privacy、/legal。ログイン画面と全画面の下部にリンクsrc/lib/legal.ts の 【要記入】を運営者が埋める。公開前に弁護士等の確認を受けること(文面はひな形)TERMS_VERSION)と日時を users に記録。規約を改定したら TERMS_VERSION を上げると、ログイン中のスタッフに再同意を求める/api/health:状態(ok/degraded/down)。Authorization: Bearer <HEALTH_TOKEN> 付きで指標と問題の一覧monitor:30分ごとに確認し、異常なら Issue(ラベル monitor)を作成して GitHub からメールで知らせる。回復したら自動で閉じる。通知の経路は手動実行の simulate_failure で試せるworker_stale(ワーカーが10分以上動いていない → Supabase の Edge Function のログと pg_cron の実行履歴)、queue_backlog(ジョブの滞留)、dead_jobs_24h(失敗の多発 → 「処理状況」画面)、db_size(容量。無料枠の上限は500MB)HEALTH_TOKEN は Vercel(本番・プレビュー)と GitHub の secrets に同じ値。手元の .env.local の REMOTE_HEALTH_TOKENscripts/backup.sh(保存先 ~/Claude/npo-crm-backups/日時/。個人情報を含むのでリポジトリの外。30日より古いものは削除)scripts/restore-test.sh <バックアップのフォルダ>(本番と同じ版で空の Supabase を手元に立てて復元し、件数・RLS・ファイルのハッシュ・ログインを確認)scripts/backup.sh を実行するroles.sql → schema.sql → select pgmq.create('jobs') → set session_replication_role = replica で data.sql(restore-test.sh と同じ順)。続けて storage-sync.mjs upload、マイグレーション履歴の supabase migration repair、pg_cron の予定と Vault の秘密の再設定、外部連携の再連携、Vercel の接続先の切り替えeval/reports/operations-readiness.mdnode scripts/loadtest-setup.mjs(手元に10万人規模の組織を作る)→ 本番ビルドを起動 → BASE=http://localhost:3090 node scripts/loadtest.mjs 1,5,10,20,30 60eval/reports/operations-readiness.mdpartner_sharing)people を一切読めず、公開済みスナップショット(person_share_versions)を DB 関数経由でだけ閲覧するshare_grant_state() で毎回判定(停止・期限・連携・根拠・機能フラグ・所属・権限)。期限切れは定期ジョブを待たずに閲覧不可manage_sharing(管理者以上に個別付与)、view_shared(閲覧用)。監査は sharing_audit_logs(本文は保存しない)photo_pipeline_v2、領域分割は photo_tiling)analysis_meta)を記録。元ファイルの座標へは uprightToRaw で戻せる_shared/detector.ts)。人物クラスの枠のみ、顔検出は使わない_shared/counting.ts。同じ解析内はほぼ同一の枠(IoU≥0.9)だけを重複とし、全体⇔領域の間は IoU≥0.55+境界の包含判定。件数は単純加算しないphoto_analysis_results を拡張:版・設定・所要時間・API呼び出し数・確認を促す理由)と検出枠(photo_detections)photo_reviews(修正内容・修正時間)。再解析は新しい版として保存し、確定値は上書きしないcontent_sha256)で検出。人物の重複判定とは別event_count_records(人数・計測方法・根拠・参考写真・注記・確定者・日時)。写真人数は自動転記しないusage_records)と外部API呼び出し(analysis_usage_records、試行単位)は別記録jobs.deadline_at)。期限切れ・失敗の隔離分は利用枠に計上しないnode scripts/eval-counting.mts synthetic --out eval/reports/synthetic.md # 合成データ(実写真の精度ではない)
node scripts/eval-counting.mts real eval/dataset.json # 実写真(実際の検出APIを設定したワーカーが必要)
node scripts/eval-counting.mts sweep <評価用組織ID> eval/dataset.json # tune で調整し test で評価(API呼び出しなし)
実写真での精度評価は未実施(評価用写真と検出APIの認証情報がないため)。eval/reports/synthetic-2026-10-22.md は合成データでのアルゴリズム比較で、
同一解析内で積極的に重複を除くと密集写真で別人をまとめて過小カウントが悪化することが分かったため、既定値を保守的(0.9)にした。
密集写真では検出枠方式そのものの見逃しが主な誤差要因になる見込みで、密度推定など別方式の要否は実写真の評価結果で判断する。
node scripts/set-feature.mjs "<組織名またはID>" partner_sharing on
node scripts/set-feature.mjs "<組織名またはID>" photo_pipeline_v2 on
node scripts/set-feature.mjs "<組織名またはID>" photo_tiling on
src/lib/legal.ts の PRICING、plans 表の monthly_price_jpy・yearly_price_jpy、料金・契約画面、特定商取引法に基づく表記)。写真解析を別料金にする仕組み(オプションの申し込み・有効な組織だけ解析できる制御・従量の集計)も未実装plans で変更可能)