Synaplan
開源 AI 平台,供企業使用:自行託管的 AI 代理、符合 GDPR 的聊天機器人小工具、RAG 知識庫、WhatsApp 與 Outlook AI、MCP、多模型路由。Docker、Kubernetes 或託管於 Germ
AI 翻譯
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Almanac - multi-role appointment booking. Flask + JWT, live availability from recurring hours, automatic email notifications, and coffee-chat invites that need no account.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Booking, from both directions: clients book a provider's free slots, and providers can email someone an invite that turns into a booking without the guest ever making an account.
Set weekly hours, add a block or a booking, pick a date — and the board recomputes in the tab. Each example shows the engine's own arithmetic: how many windows it read, how many slots it tiled, how many it dropped and why.
This is the slot engine only, not the platform — JWT auth, a relational
database, transactional email and a background reminder thread genuinely can't
run in a browser tab, so none of them are mimed. js/slots.js is a port of
domain/calendar_logic.py, and tools/build_static.py runs the two against
each other in a real browser over 21 scenarios before publishing, refusing to
write a single file if they disagree about one slot.
Read the full write-up → — what a free slot has to survive, how five kinds of email get sent exactly once, and every bug this has had. There's also an architecture map.
pip install -r requirements.txt
python seed_data.py # creates [email protected] / admin12345
python app.py # http://127.0.0.1:5003
No .env needed to start — SQLite and a dev JWT secret by default. It binds
127.0.0.1; set HOST=0.0.0.0 only when you mean it. Running with
FLASK_DEBUG=0 and no real SECRET_KEY is refused outright, because the dev
secret is committed and therefore public, and it signs every session token.
Switching to managed Postgres (Supabase, Neon, Render, RDS) is one environment
variable, DATABASE_URL. No code changes.
The root holds the things you run; everything you import lives in a package
named for its layer. The arrows only point one way —
core ← accounts/domain ← notify ← routes ← app.py — so an import
pointing back up is a cycle and says so immediately.
app.py Flask app factory, blueprint registration
core/ config.py, database.py settings; SQLite or Postgres
accounts/ auth.py JWT creation, RBAC decorators
domain/ calendar_logic.py the free-slot engine
coffee_chats.py, offerings.py,
pounds.py, schedule.py the rules: no HTTP, no email
notify/ mailer.py, notifications.py, transport, occasions, layout,
coffee_notifications.py, and the one background timer
email_render.py, scheduler.py
routes/ nine blueprints, one per area of the API
docs/app/ the slot engine, ported to JS and published on its own
tools/ build_static.py, build_planner.py, refresh_figures.py
app.py stays at the root because Flask(__name__) resolves templates/ and
static/ relative to its own directory. login_page.py, seed_data.py and
seed_luke.py stay because they're run as scripts — which puts their
directory on sys.path, not the project's.
python tools/build_planner.py --blank --out calendar.html
Blank, then yours. Nothing is seeded and nothing leaves the browser — the entries above were typed into the page while the frames were captured.
The planner is one self-contained HTML file you double-click — no server, no
assets, everything kept in that browser. It ships two ways. planner.html is
seeded with an invented term, which shows what it does and is the wrong place
to start a real one from: you'd be deleting somebody else's lectures first.
calendar.html is the same engine with nothing in
it — no term, no diary, no fixtures, no to-dos, no countdown.
Your own timetable is not in this repository and should not be. A term
timetable says where somebody is at every hour of every weekday; .gitignore
blocks the real seed and tests/test_planner.py fails if one appears beside
the sample. Build yours with --data and keep it outside the repo.
Where the full-stack half comes in. A file:// page can show a
notification while its tab is open and nothing at all once it's closed — it
can't register a service worker, so there's no background to run in. That's
the gap Almanac closes: export a backup from the calendar, import it, and the
server's sweep emails you before each event whether or not anything is open.
domain/schedule.py owns that, keyed on the planner's own event ids so
re-importing updates rather than duplicates. A lecture is deliberately not
an appointment — no second party, no provider to notify, no slot to hold — so
it lives in its own table with no booking semantics.
A free slot has to survive a lot. domain/calendar_logic.py turns
recurring weekly hours, one-off blocks and existing bookings into bookable
times — dropping anything that overlaps something busy at all (not just an
exact match), anything already past on today's date, and anything too short for
the requested session. A partial-unique index makes race-condition
double-booking impossible at the database level rather than in application code.
Five kinds of email, each sent exactly once. Welcome, confirmation,
cancellation, completion and a 24-hour reminder, all without anyone pressing a
button. Delivery runs on a background thread so a slow mail server never slows
a booking; every attempt is recorded in an email_log; and a unique index over
that log is what guarantees nobody is mailed the same thing twice — the
guarantee lives in the schema, not in a code path that could be missed.
Coffee chats run the other way. An email that produces a booking: send an invite, they click, they see real availability, they pick a time — with no account. A coffee chat is usually first contact, and asking an alum to register before picking a slot loses most of them. Follow-ups go out after three days, capped at two, because a third is pestering.
Money is an integer in minor units, and 0 means free — a real answer, not
a missing one.
pytest -q --cov --cov-branch --cov-fail-under=100
629 tests, 100% of 2,708 statements, and of every branch, over every module in
the repository -- the seed scripts, the standalone login page and tools/
included. CI fails a push below that. 14 of those guard the published slot-engine
demo: every copied file byte-identical to its source, no fetch() anywhere so
file:// keeps working, and every clause of the "this is not the platform"
banner still present.
The build's own check is the other half, and it's the one that can't run in CI:
it needs a real browser. (tests/test_build_static.py runs the build's logic in
CI against a stand-in browser -- the comparison, the refusals, the sabotage
list -- but only a real one can say the port agrees.) python tools/build_static.py --prove sabotages the
published port twelve ways — shifting every weekday by one, treating a
whole-day block as zero length, dropping the provider_id filter — and
requires each to be caught.
MIT — see CONTRIBUTING.md for setup and conventions.