Meridian

# Meridian **The meridian line to connect your goals.** A unified education platform for students in Georgia — international programmes, scholarships, exchanges, tutors and college counselling in one place.

Run it

The easy way: open the Meridian folder in Finder and double-click

Start Meridian.command

It sets everything up on first run, starts the server and opens your browser. Leave the Terminal window it opens running — closing that window stops the site.

From the terminal, if you prefer:

cd ~/Desktop/Meridian
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python3 run.py

Either way the site is at http://127.0.0.1:5001

That is the whole setup. There is no build step, no bundler and no database server — Flask serves the site and SQLite stores the data in a single file.


What is in the box

Page Route What it does
Home / Hero with a live rotating globe, the deadlines closing soonest, why Meridian exists, the four-step journey, newest programmes, student outcomes
Programs /programs Interactive globe with a pin per programme. Filter by family, region and funding; click a pin and the globe flies to it and opens the full brief
Teachers /teachers Tutor directory with photos, short intros, filters, prices shown with the fee included
Tutor profile /teacher?id=… Photo, intro, experience, services, and a calendar of the exact times the tutor opened
Pricing /pricing Explorer / Compass / Navigator memberships, the tutoring fee explained, comparison, FAQ
Checkout /checkout?order=… Card, Apple Pay, Google Pay, Bank of Georgia, TBC Pay
Become a tutor /tutors/apply Tutor application with photo upload
Tutor dashboard /tutor Weekly availability calendar, booked lessons, earnings, profile editing
About us /about Mission and vision, plus four sub-pages below
— Leadership /about/leadership The co-founders, and the roles we are hiring for
— Programs & universities /about/offerings The six programme families in the database
— Teachers around the world /about/global-teachers Where our tutors are, grouped by country
— FAQ /about/faq Straight answers on cost, eligibility, data and counselling
Engage /engage Partnership enquiry form — sends straight to the team inbox
Log in / Sign up /login Registration (stage from grade 7 to PhD, country, interests), email verification, forgot password
My account /account Saved programmes, lesson requests, Compass membership
Admin /admin Everything stored, behind an admin token

Every page has the dark/light switch, the English/ქართული switch and the full footer.


Where the data lives — and how you read it

This is the part worth understanding properly.

The file

Everything a visitor submits goes into one file:

backend/meridian.db

It is a standard SQLite database. No server to install, nothing running in the background — just a file you can copy, back up or email to yourself. It is created automatically the first time you start the site.

What is in it

Table Holds
users First name, last name, email, optional phone, hashed password, verified flag, plan, stage of study, created/last-login timestamps
verification_codes Hashed 6-digit codes, attempt counter, expiry
sessions Login tokens and their expiry
tutors Tutor profiles: photo, headline, intro, bio, subjects, price, status (pending / approved)
tutor_slots Every time a tutor opened, and whether it is open, held during checkout, or booked
bookings Lessons: student, tutor, time, service, note, price paid, status
orders Every payment: lesson or membership, tutor price, fee, total, method (brand + last 4 only)
ledger Where each payment’s money goes: the tutor’s share and Meridian’s fee, plus refunds
saved_programs Which programmes each student saved
partnership_inquiries Everything submitted through the Engage page

Programme and tutor content is not in the database — it lives in editable JSON files (see below), so the research team can update content without touching SQL.

Three ways to read it

1. The admin page — open http://127.0.0.1:5001/admin and enter your admin token (MERIDIAN_ADMIN_TOKEN, default meridian-admin). You get counts and tables for accounts, lesson requests and partnership enquiries.

2. The command line

python3 tools/admin.py stats        # summary counts
python3 tools/admin.py users        # every account
python3 tools/admin.py bookings     # lesson requests with the student's email
python3 tools/admin.py inquiries    # partnership enquiries
python3 tools/admin.py saved        # saved programmes per student
python3 tools/admin.py export students.csv --table users
python3 tools/admin.py sql "SELECT email, plan FROM users WHERE verified = 1"
python3 tools/admin.py reset --yes   # wipe all visitor records, keep the content

The database currently contains two test accounts created while building the site (demo@meridian.ge and ana.test@meridian.ge) so the admin page has something to show. Run python3 tools/admin.py reset --yes to clear them.

3. Any SQLite browser — open backend/meridian.db in DB Browser for SQLite, TablePlus, or the sqlite3 CLI.

About passwords

Passwords are never stored as text. Each one is hashed with PBKDF2-HMAC-SHA256, 240,000 iterations, with a unique random salt per user. Nobody can read them back — not an attacker with the file, and not you. If a student forgets their password the only option is to reset it, which is correct.

Verification codes are hashed the same way, expire after 15 minutes, allow five attempts and are limited to five per email per hour.


Email — making codes arrive in real inboxes

Until a mailbox is connected, Meridian cannot reach anyone’s inbox: messages are saved to backend/outbox/ and the code is shown on screen. That is why verification emails were not arriving.

Connect Gmail in one step — double-click Set Up Email.command in the Meridian folder (or run python3 tools/setup_email.py). It asks for:

  1. the address Meridian sends from (e.g. hello@meridian.ge on Google Workspace, or any Gmail),
  2. a Gmail App Password — not your normal password:

It tests the login, saves everything to .env (readable only by you, never committed), and sends you a test email. Restart Meridian and from then on every verification code, password reset, receipt and booking notice goes to real inboxes. The /admin page shows whether email is live or failing.


Payments, the 10% fee and payouts

Checkout takes Visa and Mastercard only. It runs in test mode until you connect a provider, so nothing is charged. Test cards: 4242 4242 4242 4242 (Visa) or 5555 5555 5555 4444 (Mastercard) succeed, and 4000 0000 0000 0002 is declined. The full card number never leaves the browser: only the brand and the last four digits are sent. Any other brand or method is refused by the server.

Going live with real money: Stripe does not accept companies registered in Georgia or Qatar (checked on stripe.com/global), and Paddle and Lemon Squeezy forbid tutoring and counselling. The plan, from the research in backend/data/research/investment.json:

  1. Georgia first. Register a Georgian LLC and use TBC Checkout or BOG Payments in lari (about 2.5%, recurring billing included). Pay tutors by local bank transfer. The LLC also makes GITA grants possible.
  2. Qatar. Once there is a Qatari entity (QFC, Media City Qatar or QSTP), use Tap Payments. It is licensed by the Qatar Central Bank and supports subscriptions and split payouts.
  3. Global / US investors. Form a Delaware company through Stripe Atlas ($500 + $100/yr). Pay tutors in countries Stripe cannot reach through Wise Business.

All three are hosted checkouts: the card is typed on the provider’s page, which keeps Meridian outside PCI-DSS scope. charge() in backend/payments.py is the single function to fill in.

Memberships (/pricing): Explorer (free), Compass (29 ₾/month) and Navigator (79 ₾/month); yearly billing gives two months free.


Tutors


Checking that everything works

python3 tools/smoke_test.py

This runs 31 end-to-end checks against the running site: sign-up, verification, password reset, the 10% fee, slot holds, a declined card, payment, the ledger, a tutor application with photo, approval, the tutor’s calendar, a refund, memberships, and that only Visa and Mastercard are accepted. It deletes its test accounts afterwards.


Adding content

No code changes needed — these are plain JSON files.

A new programme — backend/data/programs.json

{
  "id": "unique-slug",
  "family": "uwc",
  "type": "school",
  "org": "Name of the organisation",
  "name":    { "en": "English title",   "ka": "ქართული სათაური" },
  "country": { "en": "United Kingdom",  "ka": "გაერთიანებული სამეფო" },
  "city": "City, region",
  "iso": "GB", "flag": "🇬🇧", "region": "europe",
  "lat": 51.4, "lon": -3.54,
  "cost": "scholarship",
  "duration": { "en": "2 years", "ka": "2 წელი" },
  "grades": "10-11",
  "opens": "2026-09-08", "deadline": "2026-11-16",
  "summary":     { "en": "…", "ka": "…" },
  "highlights":  { "en": ["…"], "ka": ["…"] },
  "eligibility": { "en": "…", "ka": "…" },
  "url": "https://official-site.org",
  "premium": false
}

Research agents — finding programmes every day

tools/agents/discover.py runs nine agents on Claude Opus 5.5 with web search and web fetch. Eight each cover one kind of opportunity: schools/UWC, exchanges, Erasmus+, summer, undergraduate, postgraduate, startups and competitions. The ninth re-checks published records, because a stale deadline does more harm than a missing programme. More agents mostly re-find the same programmes.

pip install -r tools/agents/requirements.txt
export ANTHROPIC_API_KEY=...
python3 tools/agents/discover.py --minutes 10      # quick run
python3 tools/agents/discover.py                   # all nine, 60-minute budget

Research — /about/research

backend/data/research.json drives the Research page, the facts on the home page and the live counts on /about. It holds cited facts, a solution-fit table, a competitor table, the business model, the funding calendar, payments and the open questions. The raw agent reports, with every source, are in backend/data/research/. GET /api/research serves it, together with live counts of programmes, countries and checked records.

A new tutor

Tutors now join through /tutors/apply and are approved on /admin; they manage their own profile, photo, price and calendar from /tutor. backend/data/teachers.json is only the starting set: it is copied into the database the first time the site runs.

New subject names go in frontend/js/taxonomy.js. Add the slug with its English and Georgian labels and it appears in every filter and form automatically.


Translations

All interface text lives in frontend/js/i18n.js as two dictionaries, en and ka.

'nav.programs': 'Programs',      // en
'nav.programs': 'პროგრამები',    // ka

Use a key in markup with data-i18n="nav.programs", or in JavaScript with M.t('nav.programs'). Content coming from JSON uses { "en": …, "ka": … } objects and is picked up automatically by M.pick().


How it is put together

Meridian/
├── run.py                     start here
├── requirements.txt           one dependency: Flask
├── .env.example               copy to .env
│
├── backend/
│   ├── app.py                 routes and the JSON API
│   ├── db.py                  SQLite schema, password hashing, sessions
│   ├── mailer.py              SMTP, with a file-based fallback
│   ├── config.py              settings, read from the environment
│   ├── meridian.db            ← created on first run
│   ├── outbox/                ← emails land here when SMTP is off
│   └── data/
│       ├── programs.json      the programme database
│       └── teachers.json      the tutor directory
│
├── frontend/
│   ├── *.html                 one file per page
│   ├── css/
│   │   ├── tokens.css         colour, type, spacing, motion — the whole system
│   │   ├── base.css           reset, typography, layout primitives
│   │   ├── components.css     buttons, cards, nav, footer, forms, calendar
│   │   └── pages.css          page-level composition
│   ├── js/
│   │   ├── app.js             theme, language, nav, footer, reveal, toasts, API
│   │   ├── i18n.js            English + Georgian dictionaries
│   │   ├── taxonomy.js        subjects, regions, languages
│   │   ├── globe.js           the globe engine
│   │   ├── home.js  programs.js  teachers.js  teacher.js
│   │   ├── about.js  engage.js  auth.js  account.js  admin.js
│   └── assets/
│       ├── globe-dots.json    land data for the globe
│       └── favicon.svg
│
└── tools/
    ├── admin.py               read the database from the terminal
    ├── build_globe.py         regenerate the globe land data
    └── countries-110m.json    source map data (Natural Earth, public domain)

The Earth

The globe uses real NASA satellite imagery on WebGL (globe.gl, stored locally in frontend/vendor/, so it works offline). The textures are in frontend/assets/earth/.

Country outlines with English and Georgian names are generated by python3 tools/build_countries.py. Borders follow United Nations practice: Crimea is drawn as part of Ukraine, Northern Cyprus as part of Cyprus, Somaliland as part of Somalia, Kosovo is shown within Serbia, Taiwan as a province of China, and Western Sahara as a non-self-governing territory. Clicking a country shows its official UN name (UNTERM).

Motion

The intro (logo assembles, curtain lifts, headline types in) plays once per visit, can be skipped with a click or any key, and is skipped entirely for people whose system asks for reduced motion. It follows the animate skill’s rules: transform, opacity and clip-path only, strong ease-out curves, CSS keyframes so it stays smooth while the page loads, and a CSS-only fallback that never traps the page.

Your logo is split into exact layers in frontend/assets/brand/: the star facets, book pages and wordmark as masks. It recolours for dark and light mode, and the star turns on its own (a compass quarter-turn on hover). meridian-logo.png is the full-colour version, used in emails.

Design system

  Dark Light
Background deep navy dodger blue
Search bar navy blue royal blue
Accents cobalt blue royal blue
Surfaces translucent navy glass white paper

Both themes are defined entirely as CSS custom properties in tokens.css. Nothing else in the codebase hardcodes a colour, so changing the palette is a one-file job. Body text always sits on a surface with at least 4.5:1 contrast — which is why light mode puts content on white cards over the blue field rather than directly on it.

Typography is Sora for headings, Inter for body, Newsreader italic for editorial accents, and Noto Sans Georgian whenever the language is set to ქართული.


Free vs paid

  Signed out Explorer (free) Compass 29 ₾/mo Navigator 79 ₾/mo
Programme list, countries, deadlines ✅ ✅ ✅ ✅
Full programme briefs and official links partial ✅ ✅ ✅
Tutor profiles and booking first 4 ✅ ✅ ✅
Monthly shortlist by email, deadline alerts, tracker — — ✅ ✅
Priority booking with counsellors — — ✅ ✅
Monthly counsellor check-in + one essay review — — — ✅
Meridian AI — — — coming soon

Lessons are paid per lesson at the tutor’s price plus 10%.


Before going live


Coming soon

Meridian AI — built into the globe. Describe your grades, budget and ambition, and it proposes the programmes worth your time and the order to apply in.


Based in Doha, Qatar. © 2026 Meridian.