Lalabase
티켓·문서·근무시간·고객대화를 한 곳에 모아 KI로 검색, 소프트웨어 기업 고객 프로젝트용, 오픈소스로 자체 서버·독일 클라우드에 배포
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기WebGIS rekomendasi site-selection berbasis AI untuk ritel & F&B di kawasan transit Jakarta — MAPID WebGIS Competition 2026, tim Triple T
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
An AI-driven site-selection WebGIS for retail & F&B around Jakarta's transit network.
Built for the MAPID WebGIS Competition 2026 — Maps That Think! by team Triple T
(Universitas Bina Nusantara).
Don't guess where to open. Ask the map.
For every walking catchment around a transit station, SpotOn reads three signals that are
normally scattered — demand (Struk Go), competition (Menu Go), and available
commercial space (Properti Go) — then computes an Opportunity Score per business
category and explains its reasoning in human language.README 전문 보기 ↗An AI-driven site-selection WebGIS for retail & F&B around Jakarta's transit network. Built for the MAPID WebGIS Competition 2026 — Maps That Think! by team Triple T (Universitas Bina Nusantara).
Don't guess where to open. Ask the map.
For every walking catchment around a transit station, SpotOn reads three signals that are normally scattered — demand (Struk Go), competition (Menu Go), and available commercial space (Properti Go) — then computes an Opportunity Score per business category and explains its reasoning in human language.
npm install
npm run dev
No database is needed to run it. With MONGODB_URI left empty the app runs in demo
mode: signing in is one button, everything else behaves exactly as it does with a
cluster behind it. See Accounts, plans and quotas.
| Route | Contents |
|---|---|
/ | Landing page |
/app | The WebGIS: map, control panel, AI recommendations, attribute table. Needs an account. |
/signin | Sign in, or create an account. One button in demo mode. |
/account | The plan, what is left of both quotas, and the way to add more |
/api/catchments | Raw indicators per catchment |
/api/scores?kategori=kopi&wd=0.5&ws=0.5&gate=1&radius=800 | Computed Opportunity Score |
/api/meta | Business categories, data coverage, provenance, and the language model currently active |
/api/ai/query | POST { question, kategori, weights } → ranked recommendations. Costs one AI query credit, so it needs a session. |
/api/account | GET → who is signed in and what is left. { account: null } when nobody is. |
/api/analysis | POST → spends one area/spot analysis credit. 402 when there are none left. |
/api/billing | POST { plan } or POST { pack } → change tier, or buy a top-up |
/api/auth/login, /api/auth/register, /api/auth/logout | Sessions |
Files are grouped by the role they play, so where to look follows from what you want to change.
src/lib/
types.ts the data shapes every layer uses
data/ hexagon grid + transit nodes
domain/ pure business rules — no DOM, used by server and client alike
scoring.ts the Opportunity Score engine
composition.ts the same score taken apart again, step by step, for the panel
transit.ts what a cell reaches: stops, modes, and the access index explained
weights.ts default weights + value sanitiser (a single way in)
nlq.ts question → structured query → answer
narrate.ts scoring-engine output → human sentences
categories.ts thirteen business categories, their OSM tags and MAPID datasets
plans.ts the three tiers, the top-up packs, and the week's arithmetic
account.ts what counts as a usable address and password, for both sides at once
server/ server-only (enforced by SvelteKit)
source.ts the one place the data source is decided ← swap here when the MAPID API is ready
gridmap.ts where each cell sits on a drawn page, shared by every page that draws one
llm.ts the language-understanding layer (OpenRouter)
params.ts query string → scoring-engine arguments
mongo.ts the one place "is there a database" is answered
accounts.ts accounts, sessions and the meters, over Mongo or over memory
session.ts the session cookie, written in one place
state/ runes that live for the length of a session
app.svelte.ts interface state, distributed via context
tapak.svelte.ts the guide's conversation
account.svelte.ts who is signed in and what is left of their quotas
theme.svelte.ts light/dark/follow-system
lang.svelte.ts Bahasa Indonesia / English
i18n/ the bilingual script: id.ts defines the shape, en.ts fills it
utils/ pure helpers: format.ts (numbers, hours, scale colours), geo.ts, motion.svelte.ts
scene/ the isometric models: area.ts (a selected area, read off the basemap), street.ts (landing block), grid.ts (hexagon grid)
+ daylight.ts (a 24-hour light model) + world.ts (the scene contract)
components/
app/ the WebGIS surface — components that read AppState
landing/ the landing page's own composition
account/ the account page: quota meters, the week strip, plan crests,
the catchment field and the grid model
ui/ stateless components, shared between the surfaces
src/hooks.server.ts reads the session cookie into `locals.account`, once per request
src/routes/
+page.svelte landing
+page.server.ts the landing page's figures and sample conversation, computed by the scoring engine
app/ WebGIS. Its layout is what requires an account.
signin/ sign in or create an account
account/ the plan, the balances, and the way to add more
api/ endpoints
scripts/ data builders (Overpass + MAPID); their helpers live in scripts/lib/
docs/ competition rules, the proposal, and implementation status
Real (OSM). 1,105 transit nodes across four modes (MRT 20, KRL 76, LRT 33, TransJakarta 976), the geometry of all four networks, and 8,158 competitor POIs across nine categories — from OpenStreetMap via the Overpass API (ODbL). Each cell's transit access is computed from this.
The spatial unit is an H3 hexagon at resolution 8 (±531 m edge), not a per-stop catchment: TransJakarta stops sit 400–500 m apart while the walking range is 800 m, so per-stop catchments would overlap and count the same shoppers over and over. On a grid each cell is counted once and transit access becomes a property of the cell — a location served by both MRT and TransJakarta genuinely is worth more.
Rebuild the data:
node scripts/build-hexes.mjs # grid + transit access + competitors → src/lib/data/hexes.json
node scripts/build-routes.mjs # route geometry for 4 modes → static/data/routes.json
Real (MAPID). 24,614 competitor POIs from 55 datasets in the MAPID premium data catalogue — all nine categories, complete for all five administrative cities of DKI. Read straight from the catalogue, with no manual import step:
node scripts/fetch-mapid.mjs # search + read from the catalogue → src/lib/data/mapid-poi.json
node scripts/join-mapid.mjs # join onto the grid → src/lib/data/hexes.json
The OSM | MAPID switch in the top bar picks which source does the scoring; the two are
kept separate and never mixed into one score. Their densities differ enormously — OSM records
65 drinks stalls across all of Jakarta, MAPID 858 — so competitor counts must not be compared
across sources. The full comparison is in docs/04-data-mapid.md,
and the dataset list in docs/mapid-layers.md.
Field surveys (MAPID APPS). The three competition missions — Struk Go, Menu Go, Properti Go — and the community notes filed beside them. They are not in the premium catalogue and not in the public layer index, which is where this project spent a long time looking for them. MAPID APPS serves them from its own public endpoints, and those need no key at all:
node scripts/fetch-missions.mjs # → src/lib/data/mission.json
node scripts/join-missions.mjs # → adds `field` to hexes.json
node scripts/build-field.mjs # → static/data/field.json
1,027 records across the grid's extent: 195 receipts, 99 eateries, 141 property records and 592 community notes. 55 of the property records are offered for rent, which is the only rental data in the product: the premium catalogue publishes none for Jakarta.
They are used differently from everything else here, and the difference matters. The two
catalogues claim completeness for the city they cover, so a zero from them is a finding.
These are surveys somebody walked: 191 of the 562 catchments carry a record, and the rest
are not quiet streets, they are streets nobody has been down. So none of it enters the
score. It appears on the area card as evidence, every label says recorded, and the
panel says outright that it is not a census. Details in
docs/04-data-mapid.md §4.
A catchment with no data is shown as "belum terdata" (not yet surveyed) and is never interpolated. Every score comes with the N data points behind it.
The interface is available in Bahasa Indonesia (the default) and English; the ID/EN button
sits in the top bar of both pages and the choice is stored in the browser. The script lives in
src/lib/i18n/: id.ts defines the shape of the dictionary, en.ts fills that same shape,
and TypeScript refuses to build if a single sentence is left behind.
What switches: the whole landing page, the whole application interface, Tapak's sentences, and
the language instruction given to the model (so the "I don't understand" reply comes back in
the reader's language). What stays in Bahasa Indonesia: the API output (headline, why,
evidence, provenance) — that is a contract for API consumers, not text a user reads.
Copy .env.example to .env, then fill it in.
Two things are metered, because two things actually cost something to produce: one question to Tapak, which is a call out to a shared language model, and one area or unit opened by hand, which is the moment a place's competitors, stations, listings and opening hours are all read and drawn. Panning the map, colouring it, changing business type or moving the radius are all free and always will be — they are computed in the browser from a grid it already has.
Three tiers — Free, Personal and Premier — each granting a weekly allowance of both. They refill every Monday at midnight Jakarta time, and an account that has been quiet for a month comes back to one week, not to the four it slept through. Two one-off top-up packs cover a week that needs more than the tier grants, and what they add does not expire.
What each tier costs and grants is not written here on purpose. Those figures live in
src/lib/domain/plans.ts and nowhere else, and the account
page reads them from there, for the same reason no cell count is typed into the landing
page: a number copied into prose goes stale in silence. Open that file to see the ladder,
or the /account page to see it rendered.
Demo mode. With MONGODB_URI empty there is one account, signing in needs no
password, and the records live in the server's memory until it restarts. Everything else
is identical: same tiers, same allowances, same meters, same session tokens. The
interface says which mode it is in on the sign-in page and again on the account page.
There is no payment processor behind the buy buttons, and the account page says so. What
runs is everything that follows a payment, so wiring one in means calling
changePlan / buyPack from its webhook and changing nothing below that line.
The model only understands the question: it picks an operation and fills in its arguments
through function-calling, then stops. Every figure — score, demand, competitor count, N — is
computed by src/lib/domain/scoring.ts from the data, exactly the same figures the map and
the table use. That is why there is no value the model could invent.
If a question falls outside the reach of the data, the model calls tidak_dimengerti and the
interface admits it, rather than answering a misread question. Every response carries
parsedBy (model or aturan) so the path taken is never disguised.
@sveltejs/adapter-vercel is already in use. Manually:
npx vercel deploy
.github/workflows/ci.yml runs typecheck and build on every pull request and every
push. On a push to main specifically, once those checks pass, the result is deployed
straight to production. If typecheck or build fails, nothing ships — which is why the two
are one pipeline rather than two that run independently.
Fill in three secrets under Settings → Secrets and variables → Actions:
| Secret | Where from |
|---|---|
VERCEL_TOKEN | Vercel → Account Settings → Tokens |
VERCEL_ORG_ID | .vercel/project.json after npx vercel link (or Team Settings → General) |
VERCEL_PROJECT_ID | the same source, .vercel/project.json |
Until all three are filled in, the deploy job stops quietly and names what is missing — it does not fail red.
The application's Environment Variables (PUBLIC_MAPID_STYLE_URL, OPENROUTER_API_KEY,
OPENROUTER_MODEL) stay on Vercel, not on GitHub. This pipeline pulls them itself via
vercel pull, so there is no key that needs copying into two places.
Pick one. If this repository is also connected to Vercel through its built-in Git integration, every push will be deployed twice. Either turn off Connected Git Repository on Vercel, or delete the
deployjob and let Vercel handle it.
npm run check # typecheck + a11y
npm run selftest # the score breakdown against the scoring engine, no network
npm run build # production build
npm run preview # run the build
selftest scores 640 combinations of weights, demand, competition, transit access and
listings with the engine, takes each one apart with domain/composition, and holds the
two against each other: the steps have to add up to the score the engine printed, and
the transit share plus the rest has to equal it. It runs in CI alongside the typecheck.
docs/04-data-mapid.mdMAPID_PROJECT_ID | Optional — the GEO MAPID project the scripts read. Empty → the default project. |
MONGODB_URI | Where accounts, sessions and quotas are stored. May be left empty — empty is demo mode, which is a supported way to run this rather than a broken one. |
MONGODB_DB | Optional — the database inside that cluster. Empty → spoton. |