Question engine (chaipat.cc)
The question engine is the part of chaipat.cc that decides how a typed question is answered. It lives in src/engine.js, is plain JavaScript, and runs in the visitor's browser; the same file is used by the evaluation script, so the questions in tests/search-eval.json are answered exactly as the page answers them.
Steps
- Entity linking. The question is matched against verified resource addresses and the graph's entity names, short labels and alternative labels, longest names first, so "knowledge graph explorer" links one project rather than two skills. Complete addresses are protected from partial-name matches. Product aliases belong to the graph rather than per-project query rules. Names that are also ordinary words ("Go", "Make", "Express") need their original capitalization or a contextual cue such as "in", "with", "using" or "about", unless the whole query is the name. A name shared by multiple entities remains ambiguous rather than silently selecting one.
- Spelling fallback. After planning the original question, the page can try a correction using Damerau–Levenshtein distance against a vocabulary built from graph names and wiki words. A correction that finds more is applied with Showing results for; otherwise the page only suggests it.
- Intent and task. The planner preserves question kinds such as personal, period, comparison, list, explanation, definition and fact, and separately records a task (
navigate,learn,source,discoverorcompare) and operation (overview,explain,stack,count,list,factorcompare). This separates the entity being discussed, the answer being requested and the resource action to offer. - Retrieval and fusion. Candidates come from the graph neighbours of what was linked (through relations the reasoner inferred, such as deals with) and from the wiki full-text search. The lists are merged with reciprocal rank fusion, weighted by intent, and key work is nudged ahead.
- The answer. The engine picks a SPARQL question with a written answer, a wiki passage, a Wikipedia definition, a period, a comparison table, or a list of articles. For supported focused questions, the pure shared composer in
src/answer.jsreturns one answer text with sources for evidence it actually read: graph facts, a passage from the focused article, or a selected Wikipedia extract. For a focused project, paper or component overview, it uses the article opening when available and otherwise the graph description; it does not merge them into competing descriptions. Technology lists are composed from graph facts for explicit stack questions. It does not append a second description or technology summary. Related entities can be returned separately and are not citations. Finding an entity and choosing where to go are separate from answering a factual question.
Entities, tasks and resources
The shared catalog in src/resources.js describes every typed graph entity except the profile itself, including entities with no written wiki article. A record has a name, aliases, type, description, outgoing entity relationships and resource roles. It is built from the loaded graph in the browser or Node.js, not from a project-name switch or a set of special queries.
The resource selector accepts the planner's task. Navigate prefers an explicit live destination, source prefers the repository, and learn or discover prefers the local about page. A bare entity lookup is navigation when the planner marks it navigate; the resource module does not interpret question text. If the requested live site or repository is absent, the about page is the fallback. Other available roles remain explicit secondary actions, so discovering a project does not hide its live site or source. Answer-wide resource actions belong to the focused entity or an explicit personal/site overview, not whichever related result appears in a context panel. Counts have no single-project panel or answer-wide project action; lists and comparisons retain links on their individual results rather than borrowing one result's destination.
The distinction comes from graph properties: cj:liveUrl means a live product, cj:repository means source code, and the local wiki address means about. A general cj:url can instead be a publication or store listing and is never silently treated as a live product. External resource actions must be HTTP(S) URLs without credentials.
Navigation and action selection are deterministic and local: they do not require a model or API response. Eligible factual or explanatory questions can receive optional model augmentation grounded in graph rows and wiki passages; without it, the local answer remains available. This optional layer must not replace a navigation destination with generated prose. When a local composed answer exists, the API preserves its text and authoritative citations, including for cached responses; it must not promote retrieved neighbours into extra sources. Resource actions stay separate from the answer's evidence links.
Regression coverage combines curated query fixtures with generated catalog-wide cases for unique work names and aliases, bare navigation, explicit open/source/learn requests, destination URLs and missing-resource fallbacks. The cases derive from the current graph rather than a fixed set of featured projects. Validation and npm run test:search report current coverage; this article does not maintain a separate baseline count.
A question about a period is turned into a range of dates first: relative words ("this year", "two months ago", "the past six months"), named parts of a year ("early 2024", "Q3 2025") and written dates at the precision given ("on 5 March 2025", "from 2024-10-21 to 2025-05-01", day before month as written in Thailand). The time data then says which works had commits in it.
Keeping the explanation current
npm run build:wiki regenerates the wiki graph, evidence, passage index and data/knowledge-state.json. The manifest covers the engine, answer composer, resource selector, questions and their local module dependencies along with schema, graph and wiki inputs. Validation recomputes hashes and expected artifacts without writing, so a source change without rebuilding or an old passage index fails. The component's generated cj:implementationDigest tracks its declared planner (src/engine.js), answer composer (src/answer.js) and resource selector (src/resources.js) source files; the manifest covers other shared dependencies. Neither digest proves this explanation is correct: behavior changes require review of the affected prose, not an arbitrary unrelated documentation edit.
Kinds of question it handles
| Kind | Example |
|---|---|
| About him | "Who is Chaipat?", "selected work" |
| About the site | "What's the stack of this site?" |
| A period | "What did he work on in 2024?", "last month", "since last year", "21 Oct 2024 – 1 May 2025" |
| A comparison | "cedit vs Octopath save editor", "Python or Rust" |
| A concept | "Where has he used BM25?" |