เทคโนโลยีและนวัตกรรมด้านชีววิทยาศาสตร์ เพื่อประเทศไทยเกี่ยวกับเราติดต่อเรา
ขนาดอักษร
TILSNA · Engineering documentation

Developer guide

Everything needed to build and run the Single Gateway of Thailand Life Sciences — a public web portal and analytics layer over a central life-sciences knowledge database, for TCELS (สทนว.). The system is structured as Web → API → Database; the staff admin CMS is a phase-2 addition.

Introduction

TILSNA presents researchers, companies, research, products, innovations and funding across five focus industries, with dashboards, smart search, detail views, graph analysis and supply-chain visualization. This guide is the engineering reference for the front-end web app, the REST API it consumes, and the PostgreSQL knowledge database behind it.

The platform is delivered against the project TOR and the central-database / API specification. Mandated non-functional standards: microservice architecture, On-Cloud deployment, a relational database (PostgreSQL / MySQL / MSSQL / Oracle), API security with OAuth 2.0 + JWT, WSS/WAS web-security standards, PDPA compliance, and ISO/IEC 29110 project management.

PHARMA_CELL Pharmaceuticals & Cell TherapyGENOMICS_PRECISION Genomics & Precision MedicineMED_DEVICE_AI Medical Devices & Medical AINUTRA_COSME Nutraceuticals & CosmeceuticalsDIGITAL_HEALTH Digital Health

Architecture

Three tiers, loosely coupled so each can scale and deploy independently. The web app never talks to the database directly — it only calls the versioned REST API, which enforces auth and shapes responses for the front-end.

Web → API → Database. Microservices behind an OAuth2/JWT gateway; PostgreSQL holds the entity + relationship model.
  • Web (front-end) — Astro + React islands, served at the edge on Cloudflare. Renders landing, dashboards, explore, detail, graph and auth UI.
  • API (services) — REST /api/v1 with a standard JSON envelope. Microservices: catalog/search, graph & supply-chain, auth (Thai ID), import, and (phase 2) admin/CMS — all behind an OAuth 2.0 / JWT gateway.
  • Database — PostgreSQL "Core Knowledge Database": master entities + a relationship layer + data-governance & staging tables.
  • External — Thai ID (DOPA) for identity, DBD API for company auto-fill, TCELS data sources, and the Excel data-prep import path.

Technology stack

Front-end

  • Astro 6 (SSG/SSR) + React 19 islands (client:load)
  • TypeScript · Tailwind v4 · iconsax-react
  • Hand-built inline-SVG charts & force-directed graph (no chart libs)
  • Cloudflare adapter (Workers / CDN) · Node 22.12 (.nvmrc)

API & data

  • REST /api/v1 · JSON · OAuth 2.0 + JWT
  • PostgreSQL 15+ (any RDBMS per TOR)
  • Microservices · On-Cloud · stateless, horizontally scalable
  • Excel → staging import pipeline

Getting started (web app)

# Node 22.12 (see .nvmrc)
nvm use            # or: install Node 22.12
npm install        # install dependencies

npm run dev        # local dev server → http://localhost:4330
npm run build      # production build (Cloudflare adapter)
npm run preview    # preview the production build

Configure the API base URL via an environment variable (e.g. PUBLIC_API_BASE_URL=https://api.tilsna…/api/v1). Deployment is On-Cloud; this front-end ships to Cloudflare via a build/deploy hook.

Project structure

src/
  pages/        # routes (.astro) → /, /explore, /database, /news, /developer …
  components/   # React islands (home, explore, database, news, site, auth, …)
  layouts/      # Layout.astro (theme tokens, fonts, head)
  lib/          # site.tsx — language + theme context
  i18n/         # content.ts — shared bilingual copy
  data/         # mock JSON (mirrors the API: explore.json, overview.json, news.ts)
public/
  developer/    # architecture / data-model / supply-chain / import diagrams + prompts
  resources/    # brand assets, videos, network diagram

Today the front-end reads mock JSON in src/data that mirrors the API contract, so swapping to the live API is a per-endpoint change with no UI rewrite.

Database schema

The database is an entity + relationship model, not a flattened spreadsheet. Twelve core tables in three groups: master data (the entities shown in the UI), a relationship layer (one row per typed edge, powering graph & supply-chain) and data governance (provenance + import staging).

Core knowledge tables — master data + entity_relationships + evidence_sources / staging. The accounts & operations tables are listed below.
-- Core knowledge data — 12 tables (logical schema; portable to MySQL / MSSQL / Oracle)

CREATE TABLE industry_categories (
  industry_category_id SERIAL PRIMARY KEY,
  code VARCHAR(50) UNIQUE NOT NULL,        -- PHARMA_CELL, GENOMICS_PRECISION, …
  name_th VARCHAR(255), name_en VARCHAR(255) NOT NULL,
  description TEXT, is_active BOOLEAN DEFAULT TRUE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE organizations (
  organization_id SERIAL PRIMARY KEY,
  name_th VARCHAR(255), name_en VARCHAR(255),
  organization_type VARCHAR(100), country VARCHAR(100),
  website_url TEXT, description TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE researchers (
  researcher_id SERIAL PRIMARY KEY,
  full_name_th VARCHAR(255), full_name_en VARCHAR(255),
  academic_position VARCHAR(255),
  organization_id INT REFERENCES organizations(organization_id),
  orcid VARCHAR(100), profile_summary TEXT, research_interest TEXT,
  verification_status VARCHAR(50),         -- verified / pending / needs_update
  last_verified_date DATE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE expertise_tags (
  expertise_tag_id SERIAL PRIMARY KEY,
  tag_name_th VARCHAR(255), tag_name_en VARCHAR(255),
  industry_category_id INT REFERENCES industry_categories(industry_category_id)
);

CREATE TABLE companies (
  company_id SERIAL PRIMARY KEY,
  company_name_th VARCHAR(255), company_name_en VARCHAR(255),
  registration_no VARCHAR(100), parent_company VARCHAR(255),
  country_origin VARCHAR(100), thailand_status VARCHAR(255),
  business_model VARCHAR(255), thailand_footprint TEXT,
  has_thai_legal_entity BOOLEAN, regulator_status VARCHAR(255),
  main_regulator VARCHAR(255), customer_group TEXT, main_market_channel TEXT,
  has_thailand_manufacturing BOOLEAN, has_boi BOOLEAN, website_url TEXT,
  latest_data_year INT, verification_status VARCHAR(50),
  evidence_strength VARCHAR(50), priority_score INT, note TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE research_projects (
  research_project_id SERIAL PRIMARY KEY,
  title_th TEXT, title_en TEXT, abstract TEXT, research_type VARCHAR(100),
  industry_category_id INT REFERENCES industry_categories(industry_category_id),
  technology_readiness_level VARCHAR(50), start_year INT, end_year INT,
  lead_organization_id INT REFERENCES organizations(organization_id),
  status VARCHAR(100), source_id INT, last_verified_date DATE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE products (
  product_id SERIAL PRIMARY KEY,
  product_name_th VARCHAR(255), product_name_en VARCHAR(255),
  product_type VARCHAR(100), description TEXT,
  industry_category_id INT REFERENCES industry_categories(industry_category_id),
  regulatory_status VARCHAR(255), fda_license_no VARCHAR(100),
  market_status VARCHAR(100), target_customer_group TEXT, website_url TEXT,
  last_verified_date DATE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE innovations (
  innovation_id SERIAL PRIMARY KEY,
  innovation_name_th VARCHAR(255), innovation_name_en VARCHAR(255),
  innovation_type VARCHAR(100), description TEXT,
  industry_category_id INT REFERENCES industry_categories(industry_category_id),
  technology_readiness_level VARCHAR(50),
  intellectual_property_status VARCHAR(100),
  commercialization_status VARCHAR(100), last_verified_date DATE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE funding_sources (
  funding_source_id SERIAL PRIMARY KEY,
  organization_id INT REFERENCES organizations(organization_id),
  funding_source_name VARCHAR(255), country VARCHAR(100),
  funding_type VARCHAR(100), website_url TEXT, description TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE funding_programs (
  funding_program_id SERIAL PRIMARY KEY,
  funding_source_id INT REFERENCES funding_sources(funding_source_id),
  program_name VARCHAR(255),
  primary_category_id INT REFERENCES industry_categories(industry_category_id),
  segment TEXT, thailand_relevance TEXT, thailand_footprint TEXT,
  stage VARCHAR(255), instrument VARCHAR(255), typical_ticket_size TEXT,
  target_beneficiary TEXT, application_period TEXT, website_url TEXT,
  evidence_strength VARCHAR(50), priority_score INT,
  verification_status VARCHAR(50), note TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE evidence_sources (
  evidence_source_id SERIAL PRIMARY KEY,
  entity_type VARCHAR(100) NOT NULL, entity_id INT NOT NULL,
  source_title TEXT, source_url TEXT, source_type VARCHAR(100),
  evidence_strength VARCHAR(50), retrieved_date DATE, note TEXT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE entity_relationships (        -- powers Graph + Supply-chain
  relationship_id SERIAL PRIMARY KEY,
  source_entity_type VARCHAR(100) NOT NULL, source_entity_id INT NOT NULL,
  target_entity_type VARCHAR(100) NOT NULL, target_entity_id INT NOT NULL,
  relationship_type VARCHAR(100) NOT NULL, relationship_strength VARCHAR(50),
  evidence_source_id INT REFERENCES evidence_sources(evidence_source_id),
  note TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Master tables: industry_categories · organizations · researchers · expertise_tags · companies · research_projects · products · innovations · funding_sources · funding_programs. Governance: evidence_sources, staging stg_* + import_batches.

Accounts, access & operations

The TOR requires authentication, role requests with staff/admin approval, a landing CMS, analytics and an audit trail — so the schema adds a platform layer of 11 tables alongside the knowledge data. See the data dictionary for every column.

-- Accounts, access & operations (TOR 3.5–3.10)

CREATE TABLE users (
  user_id SERIAL PRIMARY KEY,
  email VARCHAR(255) UNIQUE,
  password_hash VARCHAR(255),          -- null for Thai-ID-only accounts
  thai_id_sub VARCHAR(100) UNIQUE,     -- subject id from Thai ID (DOPA)
  full_name VARCHAR(255),
  phone VARCHAR(50),
  line_id VARCHAR(100),
  status VARCHAR(50) DEFAULT 'active', -- active / suspended / pending
  email_verified BOOLEAN DEFAULT FALSE,
  last_login_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE roles (
  role_id SERIAL PRIMARY KEY,
  code VARCHAR(50) UNIQUE NOT NULL,    -- guest / researcher / company_rep / tcels_staff / admin
  name_th VARCHAR(100), name_en VARCHAR(100), description TEXT
);

CREATE TABLE user_roles (
  user_role_id SERIAL PRIMARY KEY,
  user_id INT REFERENCES users(user_id),
  role_id INT REFERENCES roles(role_id),
  granted_by INT REFERENCES users(user_id),
  granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE (user_id, role_id)
);

CREATE TABLE role_requests (
  request_id SERIAL PRIMARY KEY,
  user_id INT REFERENCES users(user_id),
  requested_role VARCHAR(50) NOT NULL,    -- researcher / company_rep
  status VARCHAR(50) DEFAULT 'pending',   -- pending / approved / rejected
  academic_position VARCHAR(255), orcid VARCHAR(100),
  organization_name VARCHAR(255), research_interest TEXT,   -- researcher fields
  company_name VARCHAR(255), registration_no VARCHAR(100),
  company_objective TEXT, registered_capital VARCHAR(100), directors TEXT, -- company fields
  reviewer_id INT REFERENCES users(user_id),
  review_note TEXT, decided_at TIMESTAMP,
  linked_entity_type VARCHAR(50), linked_entity_id INT,    -- set on approval
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE company_members (             -- company representatives (3.8.2)
  company_member_id SERIAL PRIMARY KEY,
  company_id INT REFERENCES companies(company_id),
  user_id INT REFERENCES users(user_id),
  member_role VARCHAR(50) DEFAULT 'representative',  -- owner / representative
  added_by INT REFERENCES users(user_id),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE (company_id, user_id)
);

CREATE TABLE password_reset_tokens (       -- email password reset (3.5.3)
  token_id SERIAL PRIMARY KEY,
  user_id INT REFERENCES users(user_id),
  token_hash VARCHAR(255) NOT NULL,
  expires_at TIMESTAMP NOT NULL, used_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE content_articles (            -- landing CMS: news & funding (3.9.3 / 3.10.2)
  article_id SERIAL PRIMARY KEY,
  type VARCHAR(50) NOT NULL,               -- news / funding_announcement
  title_th VARCHAR(255), title_en VARCHAR(255), slug VARCHAR(255),
  industry_category_id INT REFERENCES industry_categories(industry_category_id),
  body TEXT, cover_image_url TEXT,
  status VARCHAR(50) DEFAULT 'draft',      -- draft / published / archived
  author_id INT REFERENCES users(user_id), published_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE documents (                   -- uploads: company certificate, certs (3.6.3)
  document_id SERIAL PRIMARY KEY,
  owner_type VARCHAR(50) NOT NULL, owner_id INT NOT NULL,  -- role_request / company / researcher
  doc_type VARCHAR(100), file_name VARCHAR(255), file_url TEXT,
  mime_type VARCHAR(100), uploaded_by INT REFERENCES users(user_id),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE entity_views (                -- view / visit analytics (3.7.1.5 / 3.10.6)
  view_id BIGSERIAL PRIMARY KEY,
  entity_type VARCHAR(50) NOT NULL, entity_id INT,
  user_id INT REFERENCES users(user_id),  -- null for guests
  session_id VARCHAR(100), viewed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE entity_metrics (              -- derived rankings / citations / growth (3.7 / 3.8)
  metric_id SERIAL PRIMARY KEY,
  entity_type VARCHAR(50) NOT NULL, entity_id INT NOT NULL,
  period VARCHAR(20),                      -- 2026-05 / all-time
  citation_count INT DEFAULT 0, view_count INT DEFAULT 0,
  rank_overall INT, computed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE audit_log (                   -- database change history (3.10.6.8)
  audit_id BIGSERIAL PRIMARY KEY,
  user_id INT REFERENCES users(user_id),
  action VARCHAR(50),                      -- create / update / delete / approve / reject / publish
  entity_type VARCHAR(100), entity_id INT,
  changes JSONB, ip_address VARCHAR(64),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Data dictionary

Every table in the schema, grouped. Entities carry verification_status where relevant and link out to evidence_sources for traceability. Jump to a table:

Core knowledge data

The entities shown in the UI plus the relationship + governance layer (from the central-database spec).

industry_categoriesThe 5 focus-industry taxonomy
ColumnTypeDescription
industry_category_idSERIAL · PKUnique id
codeVARCHAR(50) · UNIQUEPHARMA_CELL · GENOMICS_PRECISION …
name_th / name_enVARCHAR(255)Display name (EN required)
descriptionTEXTOptional notes
is_activeBOOLEANShow in filters
created_atTIMESTAMPRow created
organizationsUniversities, institutes, agencies, parent firms
ColumnTypeDescription
organization_idSERIAL · PKUnique id
name_th / name_enVARCHAR(255)Org name
organization_typeVARCHAR(100)university / gov_research_institute / …
countryVARCHAR(100)Country
website_urlTEXTOfficial site
descriptionTEXTProfile
created_atTIMESTAMPRow created
researchersResearchers / experts
ColumnTypeDescription
researcher_idSERIAL · PKUnique id
full_name_th / _enVARCHAR(255)Name
academic_positionVARCHAR(255)Professor / Assoc. Prof …
organization_idINT · FK→ organizations
orcidVARCHAR(100)ORCID id
profile_summaryTEXTBio
research_interestTEXTTopics
verification_statusVARCHAR(50)verified / pending / needs_update
last_verified_dateDATELast check
created_atTIMESTAMPRow created
expertise_tagsSearchable expertise tags
ColumnTypeDescription
expertise_tag_idSERIAL · PKUnique id
tag_name_th / _enVARCHAR(255)Tag label
industry_category_idINT · FK→ industry_categories
companiesCompanies / juristic persons
ColumnTypeDescription
company_idSERIAL · PKUnique id
company_name_th / _enVARCHAR(255)Legal name
registration_noVARCHAR(100)DBD registration no.
parent_companyVARCHAR(255)Parent firm
country_originVARCHAR(100)Origin country
thailand_statusVARCHAR(255)Thai presence status
business_modelVARCHAR(255)Importer / Manufacturer / Brand …
thailand_footprintTEXTOperations in Thailand
has_thai_legal_entityBOOLEANRegistered Thai entity
regulator_statusVARCHAR(255)Licence status
main_regulatorVARCHAR(255)e.g. Thai FDA (อย.)
customer_groupTEXTCustomers
main_market_channelTEXTSales channel
has_thailand_manufacturingBOOLEANManufactures in TH
has_boiBOOLEANBOI privileges
website_urlTEXTSite
latest_data_yearINTLatest data year
verification_statusVARCHAR(50)verified / pending / needs_update
evidence_strengthVARCHAR(50)high / medium / low
priority_scoreINTEditorial priority
noteTEXTInternal note
created_atTIMESTAMPRow created
research_projectsResearch projects
ColumnTypeDescription
research_project_idSERIAL · PKUnique id
title_th / title_enTEXTTitle
abstractTEXTAbstract
research_typeVARCHAR(100)basic / applied …
industry_category_idINT · FK→ industry_categories
technology_readiness_levelVARCHAR(50)TRL 1–9
start_year / end_yearINTPeriod
lead_organization_idINT · FK→ organizations
statusVARCHAR(100)active / completed
source_idINT→ evidence_sources
last_verified_dateDATELast check
created_atTIMESTAMPRow created
productsProducts / services
ColumnTypeDescription
product_idSERIAL · PKUnique id
product_name_th / _enVARCHAR(255)Name
product_typeVARCHAR(100)medical_device / software …
descriptionTEXTDescription
industry_category_idINT · FK→ industry_categories
regulatory_statusVARCHAR(255)Licence status
fda_license_noVARCHAR(100)Thai FDA licence
market_statusVARCHAR(100)commercial / pilot
target_customer_groupTEXTCustomers
website_urlTEXTSite
last_verified_dateDATELast check
created_atTIMESTAMPRow created
innovationsInnovations / technology / IP
ColumnTypeDescription
innovation_idSERIAL · PKUnique id
innovation_name_th / _enVARCHAR(255)Name
innovation_typeVARCHAR(100)biomaterial / ai_platform …
descriptionTEXTDescription
industry_category_idINT · FK→ industry_categories
technology_readiness_levelVARCHAR(50)TRL 1–9
intellectual_property_statusVARCHAR(100)filed / granted
commercialization_statusVARCHAR(100)prototype / early_commercial
last_verified_dateDATELast check
created_atTIMESTAMPRow created
funding_sourcesFunding agencies
ColumnTypeDescription
funding_source_idSERIAL · PKUnique id
organization_idINT · FK→ organizations
funding_source_nameVARCHAR(255)Agency name
countryVARCHAR(100)Country
funding_typeVARCHAR(100)government_grant / matching_fund
website_urlTEXTSite
descriptionTEXTProfile
created_atTIMESTAMPRow created
funding_programsFunding programmes / calls
ColumnTypeDescription
funding_program_idSERIAL · PKUnique id
funding_source_idINT · FK→ funding_sources
program_nameVARCHAR(255)Programme
primary_category_idINT · FK→ industry_categories
segmentTEXTTarget segment
thailand_relevanceTEXTTH relevance
stageVARCHAR(255)Prototype / Pilot / Seed
instrumentVARCHAR(255)Grant / Matching
typical_ticket_sizeTEXTFunding size
target_beneficiaryTEXTBeneficiaries
application_periodTEXTWhen to apply
evidence_strengthVARCHAR(50)high / medium / low
priority_scoreINTPriority
verification_statusVARCHAR(50)verified / …
created_atTIMESTAMPRow created
evidence_sourcesProvenance for every entity
ColumnTypeDescription
evidence_source_idSERIAL · PKUnique id
entity_type · entity_idVARCHAR · INTWhat it backs
source_titleTEXTTitle
source_urlTEXTLink
source_typeVARCHAR(100)official_website / government_registry
evidence_strengthVARCHAR(50)high / medium / low
retrieved_dateDATECaptured on
noteTEXTNote
created_atTIMESTAMPRow created
entity_relationshipsGraph + supply-chain edges
ColumnTypeDescription
relationship_idSERIAL · PKUnique id
source_entity_type · _idVARCHAR · INTOrigin node
target_entity_type · _idVARCHAR · INTDestination node
relationship_typeVARCHAR(100)leads · works_on · derived_to · owned_by · funded_by · classified_as
relationship_strengthVARCHAR(50)strong / medium / weak
evidence_source_idINT · FK→ evidence_sources
created_atTIMESTAMPRow created

Accounts & access

Users, roles and the role-request / approval workflow (TOR 3.5 – 3.10).

usersUser accounts (guest → registered)
ColumnTypeDescription
user_idSERIAL · PKUnique id
emailVARCHAR(255) · UNIQUELogin email
password_hashVARCHAR(255)Argon2/bcrypt; null for Thai-ID-only
thai_id_subVARCHAR(100) · UNIQUESubject id from Thai ID (DOPA)
full_nameVARCHAR(255)Name
phoneVARCHAR(50)Contact phone
line_idVARCHAR(100)Contact LINE id
statusVARCHAR(50)active / suspended / pending
email_verifiedBOOLEANVerified flag
last_login_atTIMESTAMPLast sign-in
created_atTIMESTAMPRow created
rolesRole definitions
ColumnTypeDescription
role_idSERIAL · PKUnique id
codeVARCHAR(50) · UNIQUEguest / researcher / company_rep / tcels_staff / admin
name_th / name_enVARCHAR(100)Display name
descriptionTEXTWhat it can do
user_rolesRoles granted to a user
ColumnTypeDescription
user_role_idSERIAL · PKUnique id
user_idINT · FK→ users
role_idINT · FK→ roles
granted_byINT · FK→ users (admin)
granted_atTIMESTAMPWhen granted
role_requestsResearcher / company-rep applications + approval
ColumnTypeDescription
request_idSERIAL · PKUnique id
user_idINT · FK→ users
requested_roleVARCHAR(50)researcher / company_rep
statusVARCHAR(50)pending / approved / rejected
academic_positionVARCHAR(255)(researcher) position
orcidVARCHAR(100)(researcher) ORCID
organization_nameVARCHAR(255)(researcher) affiliation
research_interestTEXT(researcher) topics
company_nameVARCHAR(255)(company) name
registration_noVARCHAR(100)(company) DBD no.
company_objectiveTEXT(company) objective
registered_capitalVARCHAR(100)(company) capital
directorsTEXT(company) directors
reviewer_idINT · FK→ users (staff/admin)
review_noteTEXTApprove/reject comment
decided_atTIMESTAMPDecision time
linked_entity_type · _idVARCHAR · INTresearcher/company created on approval
created_atTIMESTAMPSubmitted
company_membersCompany representatives (3.8.2)
ColumnTypeDescription
company_member_idSERIAL · PKUnique id
company_idINT · FK→ companies
user_idINT · FK→ users
member_roleVARCHAR(50)owner / representative
added_byINT · FK→ users
created_atTIMESTAMPRow created
password_reset_tokensEmail password-reset (3.5.3)
ColumnTypeDescription
token_idSERIAL · PKUnique id
user_idINT · FK→ users
token_hashVARCHAR(255)Hashed token
expires_atTIMESTAMPExpiry
used_atTIMESTAMPConsumed at
created_atTIMESTAMPIssued

Content & operations

Staff CMS, document uploads, analytics and the change/audit log (TOR 3.9 – 3.10).

content_articlesCMS landing content — news & funding (3.9.3 / 3.10.2)
ColumnTypeDescription
article_idSERIAL · PKUnique id
typeVARCHAR(50)news / funding_announcement
title_th / title_enVARCHAR(255)Title
slugVARCHAR(255)URL slug
industry_category_idINT · FK→ industry_categories
bodyTEXTRich content
cover_image_urlTEXTCover
statusVARCHAR(50)draft / published / archived
author_idINT · FK→ users
published_atTIMESTAMPPublish time
created_at / updated_atTIMESTAMPTimestamps
documentsUploaded files (company certificate, certs — 3.6.3)
ColumnTypeDescription
document_idSERIAL · PKUnique id
owner_type · owner_idVARCHAR · INTrole_request / company / researcher
doc_typeVARCHAR(100)company_certificate / certificate
file_nameVARCHAR(255)Original name
file_urlTEXTStorage URL
mime_typeVARCHAR(100)Content type
uploaded_byINT · FK→ users
created_atTIMESTAMPRow created
entity_viewsView / visit analytics (3.7.1.5 / 3.10.6)
ColumnTypeDescription
view_idBIGSERIAL · PKUnique id
entity_type · entity_idVARCHAR · INTresearch / product / page …
user_idINT · FK→ users (null = guest)
session_idVARCHAR(100)Anonymous session
viewed_atTIMESTAMPWhen
entity_metricsDerived rankings / citations / growth for dashboards (3.7 / 3.8)
ColumnTypeDescription
metric_idSERIAL · PKUnique id
entity_type · entity_idVARCHAR · INTSubject
periodVARCHAR(20)2026-05 / all-time
citation_countINTIncoming relationships
view_countINTViews in period
rank_overallINTComputed rank
computed_atTIMESTAMPComputed at
audit_logDatabase edit history / change log (3.10.6.8)
ColumnTypeDescription
audit_idBIGSERIAL · PKUnique id
user_idINT · FK→ users
actionVARCHAR(50)create / update / delete / approve / reject / publish
entity_type · entity_idVARCHAR · INTWhat changed
changesJSONBbefore / after diff
ip_addressVARCHAR(64)Origin
created_atTIMESTAMPWhen

Relationships & graph

All connections live in a single entity_relationships table — one typed, directional row per edge with an optional evidence reference. The same rows drive both the force-directed graph analysis and the layered supply-chain view.

Value-chain layers: research → innovation → product → company → funding, with researchers, tags and industries as contributors.

Relationship types: affiliated_with · has_expertise · works_on · derived_to · owned_by / distributed_by · commercialized_by · funded_by · classified_as · related_to. Start with the single table; split into bridge tables (e.g. company_product) later for performance.

Import pipeline

Source data arrives as Excel. It lands in staging tables first, then is validated, mapped to the industry taxonomy, de-duplicated, and committed to master + relationship tables — never written straight into production.

Excel → staging → validate/map/dedupe → master → relationships → API → web.
POST /api/v1/import/excel          → { import_batch_id, sheets_detected[] }
POST /api/v1/import/{id}/validate  → { valid_rows, warning_rows, error_rows, warnings[] }
POST /api/v1/import/{id}/commit    → { created{}, updated{}, skipped{ duplicate_companies } }

Mock data

The front-end ships with mock JSON under src/data/ that mirrors the API contract, so the whole UI runs before the live API exists — swapping to the real API is a per-endpoint change with no UI rewrite. Expand a dataset to browse it as a readable table or toggle to the raw JSON.

Download SQL

Two ready-to-run database packages — schema + sample data + media files, zipped. Each unzips to /sql (DDL + seed inserts) and /medias/<entity>/* (images keyed to each row's id). Built with PostgreSQL; portable to MySQL / MSSQL / Oracle.

Core knowledge data

12 tables · ~140 sample rows · media
  • sql/01_schema.sql — industry_categories, organizations, researchers, expertise_tags, companies, research_projects, products, innovations, funding_sources, funding_programs, evidence_sources, entity_relationships
  • sql/02_sample_data.sql — seed rows derived from the live portal data
  • medias/research · researchers · companies · products — images keyed to each *_id
tilsna-core-knowledge.zip

Accounts & CMS data

11 tables · users · roles · approvals
  • sql/01_schema.sql — users, roles, user_roles, role_requests, company_members, password_reset_tokens, content_articles, documents, entity_views, entity_metrics, audit_log
  • sql/02_sample_data.sql — sample admin/staff/researcher/company users, role-request approvals, CMS articles, audit log
  • medias/news — CMS article cover images
tilsna-cms.zip

Regenerate after data changes with bash scripts/build-sql-packages.sh (writes both zips to public/sql-package/). Sample password hashes are placeholders — replace before any real use.

API reference

REST under a single version, https://api.…/api/v1. Every response uses the same envelope so the front-end handles success, lists and errors uniformly.

// success (single)               // list (paginated)
{                                {
  "success": true,                 "success": true,
  "data": { … },                   "data": [ … ],
  "meta": {                        "pagination": {
    "timestamp": "2026-05-29…"       "page": 1, "limit": 10,
  }                                  "total": 100, "total_pages": 10
}                                  }
                                 }
// error
{ "success": false,
  "error": { "code": "ENTITY_NOT_FOUND", "message": "Company not found" } }

Catalog, search & graph

MethodEndpointPurposeParameters
GET/dashboard/overviewKPI + charts for the overview dashboardsindustry_category_id · year · verification_status
GET/industry-categoriesIndustry taxonomy for filters
GET/researchersList / search researcherskeyword · industry_category_id · expertise_tag · organization_id · page · limit
GET/researchers/{id}Researcher detail
GET/companiesList / search companieskeyword · industry_category_id · country_origin · business_model · page · limit
GET/companies/{id}Company detail
GET/research-projectsList / search researchkeyword · industry_category_id · researcher_id · trl · page · limit
GET/research-projects/{id}Research detail
GET/productsList / search productskeyword · industry_category_id · company_id · market_status · page · limit
GET/products/{id}Product detail
GET/innovationsList / search innovationskeyword · industry_category_id · trl · commercialization_status · page · limit
GET/innovations/{id}Innovation detail
GET/funding-programsList / search funding programskeyword · country · stage · instrument · target_beneficiary · page · limit
GET/funding-programs/{id}Funding program detail
GET/searchCross-entity keyword searchkeyword · entity_type · industry_category_id · limit
POST/filterAdvanced multi-condition filterrequest body
GET/entities/{type}/{id}/relationshipsRelationships of an entity
GET/graphGraph nodes + edgescenter_entity_type · center_entity_id · depth
GET/supply-chainSupply-chain layersentity_type · entity_id · depth
GET/evidence-sourcesProvenance / sources for an entityentity_type · entity_id
POST/import/excelUpload an Excel batch to stagingmultipart file
POST/import/{id}/validateValidate a staged batch
POST/import/{id}/commitCommit a validated batch to master

Accounts, roles & operations

Auth, role requests & approval, company members, the landing CMS, analytics and admin — secured with OAuth 2.0 / JWT and scoped per role.

MethodEndpointPurposeParameters
POST/auth/registerEmail sign-up (3.5.1.2)email · password · full_name · phone · line_id
POST/auth/loginEmail + password loginemail · password
POST/auth/thai-idSign-up / login via Thai ID (DOPA)code (OAuth)
POST/auth/forgot-passwordSend reset link by emailemail
POST/auth/reset-passwordSet a new passwordtoken · password
GET/meCurrent user + roles
GET/dbd/company/{registration_no}DBD company auto-fill (3.6.3.2)
POST/role-requestsSubmit a researcher / company-rep requestrequest body + documents
GET/role-requestsList requests (staff/admin)status · requested_role · page · limit
POST/role-requests/{id}/approveApprove a request + commentreview_note
POST/role-requests/{id}/rejectReject a request + commentreview_note
GET/companies/{id}/membersList company representatives
POST/companies/{id}/membersAdd a representative (3.8.2)user_id
GET/contentList landing content (news / funding)type · status · page · limit
POST/contentCreate article (CMS)request body
PATCH/content/{id}Edit / publish articlerequest body
DELETE/content/{id}Delete article
POST/viewsTrack an entity / page viewentity_type · entity_id
GET/me/dashboardPersonalized dashboard (researcher / company)period
GET/admin/dashboardAdmin KPIs (visitors, pending reviews …)
GET/admin/usersList / manage userskeyword · role · page · limit
POST/admin/reportsGenerate overview report (Word / PDF)format · scope
GET/admin/audit-logDatabase change historyentity_type · user_id · page · limit

Example — dashboard overview

GET /api/v1/dashboard/overview?industry_category_id=3&verification_status=verified
{
  "summary": {
    "total_researchers": 1284, "total_companies": 642,
    "total_research_projects": 1950, "total_products": 1120,
    "total_innovations": 870, "total_funding_programs": 96
  },
  "industry_distribution": [
    { "industry_category_id": 3, "industry_name_en": "Medical Devices and Medical AI",
      "research_project_count": 560, "product_count": 330,
      "innovation_count": 250, "company_count": 204 }
  ],
  "verification_summary": { "verified": 2460, "pending": 540, "needs_update": 180 }
}

Example — graph

GET /api/v1/graph?center_entity_type=company&center_entity_id=1&depth=2
{
  "nodes": [
    { "id": "company:1",  "entity_type": "company",  "label": "Example Medical Co., Ltd.", "group": "company" },
    { "id": "product:11", "entity_type": "product",  "label": "Wound dressing", "group": "product" }
  ],
  "edges": [
    { "source": "company:1", "target": "product:11", "relationship_type": "owned_by", "weight": 1 }
  ]
}

Authentication & security

Identity supports Thai ID (DOPA) and email/password; password reset is email-based. The API is protected with OAuth 2.0 + JWT, and the platform must meet TCELS security requirements.

  • Access control — role-based; least privilege per endpoint.
  • Encryption — TLS in transit; encryption at rest for sensitive data.
  • API security — OAuth 2.0 authorization + short-lived JWT access tokens.
  • Web standards — Website Security Standard (WSS) & Web Application Security Standard (WAS).
  • PDPA — Thailand Personal Data Protection Act B.E. 2562: consent, data-subject rights, 30-day response.

Sign-up captures email, password, name and a contact channel (phone / LINE ID); the front-end sign-in, forgot-password and reset-password flows are already built.

Roles, journey & approval

Five roles — guest, researcher, company representative, TCELS staff and admin. A guest signs up, optionally requests an elevated role (researcher or company rep), and staff/admin review and approve it; on approval the user is linked to a researcher/company record and gets a personalized workspace. Staff/admin accounts are created by an administrator only.

From guest → sign-up → role request → staff/admin review → granted workspace, with the tables each step writes to.

Guest (public)

  • Landing, overview dashboards (research/innovation & companies)
  • Smart search + advanced filter, detail views
  • Supply-chain & graph visualization

Researcher role

  • Personalized dashboard: rankings, citations, growth, views
  • Recommended researchers / outputs · funding & news
  • Researcher-centred supply-chain

Company role

  • Rankings, competitors, citations, growth
  • Recommended partners · government work · members
  • Company-centred supply-chain

TCELS staff / admin phase 2

  • CMS for landing content (funding & news)
  • Company-role review & approval (DBD API)
  • User management · reports (Word/PDF) · admin dashboard

Network diagram

Reference deployment / network topology for the On-Cloud environment.

TILSNA network diagram (On-Cloud deployment topology).

Roadmap & delivery

  • Phase 1 — public web portal: landing, overview dashboards, smart search/filter, detail views, graph & supply-chain, auth + role-request UI.
  • Phase 2 — staff/admin CMS: content management, company-role approval with DBD auto-fill, user management, exportable reports (Word/PDF), admin analytics; full live API + database.

Timeline: March–May 2026, delivered within 90 days across three milestones (10% / 60% / 30%), per the project TOR.

Sources: core_database.pdf (schema + API spec) · Update TOR 2.5.pdf (requirements). Diagram assets & regeneration prompts live in public/developer/.

ประตูข้อมูลกลางด้านชีววิทยาศาสตร์ของไทย

หากมีคำถาม กรุณาติดต่อ

ติดต่อเรา

Every decision starts with TILSNA.