KESS ESG
Reference Guide
This document covers every configurable part of the KESS ESG Assessment 2026 system — project modes, API endpoints, user types, schema wiring, and step-by-step instructions for common changes.
Architecture
The system is a single-page HTML application (no framework) backed by PHP API endpoints. All state lives in window.* globals. Three JS libraries handle rendering, upload, and panel UI.
| Layer | What it does |
|---|---|
| gen59__1_.html | Application shell — config, page logic, API calls, mode control, user auth |
| gflib02.js | Form renderer — renders schemas into interactive fields, scoring, jumperhelper, QSummary |
| upl3.js | Chunked file upload panel — handles attachment uploads, dedup, progress |
| microx.js | Panel UI framework — createPanel, addFooterButton, drag, animations |
| PHP APIs | api622.php · api_keeper.php · aui.php · api_board.php |
Files & Roles
| File | Edit when you need to… |
|---|---|
gen59__1_.html |
Change project title, dates, mode, API host, flag→template map, user level behaviour, add UI panels |
gflib02.js |
Change form rendering, field types, score logic, jumperhelper, QSummary, scroll-to-field |
upl3.js |
Change upload chunk size, file dedup, allowed file types, upload panel UI |
microx.js |
Change panel animations, footer structure, drag behaviour — rarely needed |
api622.php |
Main form data read/write (bucket + cup answers) |
api_keeper.php |
Keeper slot read/write (answer snapshots per user per slot) |
aui.php |
User table CRUD — list, update, create users |
api_board.php |
Board + board_summary read/write |
Hardcode Config Block
All persistent project settings live in one block near the bottom of gen59__1_.html. Search for ▼▼▼ CHANGE THESE VALUES.
// ── PROJECT IDENTITY ─────────────────────────────────
window.projectTitle = "ESG Assessment 2026";
// ↑ Shown in menu title bar and panels
window.projectStart = "2026-02-25T00:00:00.000Z";
// ↑ Used by progress clock — when did submission open?
// Format: ISO 8601 UTC "YYYY-MM-DDTHH:MM:SS.000Z"
// ── MODE DATES (UTC) ─────────────────────────────────
window.dateExpired = "2026-04-07T17:00:00.000Z";
// ↑ 🔴 OPEN → EXPIRED after this moment
window.dateAnnounced = "2026-03-22T08:00:00.000Z";
// ↑ 🏆 EXPIRED → ANNOUNCED after this moment
window.dateArchived = "2026-06-01T00:00:00.000Z";
// ↑ 📦 ANNOUNCED → ARCHIVED after this moment
// ── MASTER MODE SWITCH ───────────────────────────────
// 'auto' = auto-resolve from dates above (recommended)
// 'open' = force OPEN regardless of date
// 'expired' = force EXPIRED
// 'announced' = force ANNOUNCED (scores visible now)
// 'archived' = force ARCHIVED (permanent read-only)
window.kessProjectMode = 'auto';
// ── API ───────────────────────────────────────────────
const API_HOST = "https://q.kunok.com/s9";
const API_PATH = "api622.php";
const KEEPER_API_PATH = "api_keeper.php";
const ITEMS_API_PATH = "aui.php";
const BOARD_API_PATH = "api_board.php";
// ── FLAG → TEMPLATE MAP ──────────────────────────────
const _flagToTemplate = {
"1": 18, // Food Factory → template 18
"2": 17, // Food Collector → template 17
"3": 16, // Non-Food Factory → template 16
"4": 15, // Non-Food Collector → template 15
};
Project Modes
The system has four modes that control read/write access, score visibility, and banner display. Mode is resolved once per page load (and whenever _kessApplyMode() is called).
Mode Flow
| Mode | Read-only? | Score visible? | Banner shown? | Save button? |
|---|---|---|---|---|
| 🟢 open | ❌ No — editable | ❌ No (admin can see) | Hidden | ✅ Active |
| ⛔ expired | ✅ Yes — locked | ❌ No | ✅ Shown | 🚫 Disabled |
| 🏆 announced | ✅ Yes — locked | ✅ Yes — forced | ✅ Shown | 🚫 Disabled |
| 📦 archived | ✅ Yes — locked | ✅ Yes — forced | ✅ Shown | 🚫 Disabled |
Auto-Resolution Logic
When kessProjectMode = 'auto' the function _kessResolveMode() determines the mode:
function _kessResolveMode() {
const manual = (window.kessProjectMode || 'auto').toLowerCase();
if (manual !== 'auto') return manual; // hard-forced
const now = new Date();
const expired = window.dateExpired ? new Date(window.dateExpired) : null;
const announce = window.dateAnnounced ? new Date(window.dateAnnounced) : null;
const archive = window.dateArchived ? new Date(window.dateArchived) : null;
if (archive && now >= archive) return 'archived';
if (announce && now >= announce) return 'announced';
if (expired && now >= expired) return 'expired';
return 'open';
}
What Changes Per Mode
kess-mode-openkess-mode-expiredkess-mode-announcedkess-mode-archived- Controls input enabled/disabled via CSS selectors
- open/expired → score mode locked OFF for users
- announced/archived → score mode forced ON for all
- Admins (level ≥ 2) always see scores
- Score menu items hidden in open/expired for users
- open → banner hidden entirely
- expired → banner shown + one-time overlay
- announced → banner + score reveal overlay
- archived → banner shown, no overlay
Force a Mode Immediately (in browser)
// Paste in browser console or add to HTML:
window.kessProjectMode = 'announced';
_kessApplyMode();
User Flags & Template IDs
Each user has a flag (stored in localStorage.flag after login). The flag determines which template ID loads for their Assessment form (bucket).
| Flag | User Type | Template ID | Form loaded |
|---|---|---|---|
"1" | Food Factory | 18 | bucket (Assessment) |
"2" | Food Collector | 17 | bucket (Assessment) |
"3" | Non-Food Factory | 16 | bucket (Assessment) |
"4" | Non-Food Collector | 15 | bucket (Assessment) |
Where the map lives
const _flagToTemplate = { "1": 18, "2": 17, "3": 16, "4": 15 };
Also duplicated in the admin user list (flagMap) and at login resolution (_kessTemplateFromUsercode fallback). Change all three if you add a new flag.
How to Add a New User Type / Flag
Example: adding flag "5" for "Service Provider" with template ID 19.
-
Update
_flagToTemplatein the hardcode block:const _flagToTemplate = { "1":18, "2":17, "3":16, "4":15, "5":19 }; -
Update
flagMapin admin user list (~line 1993):const flagMap = {'1':18,'2':17,'3':16,'4':15,'5':19}; -
Update
_flagLabel(~line 2069):const _flagLabel = { '1':'Food Factory', '2':'Food Collector', '3':'Non-Food Factory', '4':'Non-Food Collector', '5':'Service Provider' }; - Create template 19 in your database / schema system.
- Ensure
api622.phpcan serve template 19 for that user.
User Levels
Level is stored in localStorage.level after login. It controls UI capabilities independently of mode.
| Level | Label | Capabilities |
|---|---|---|
1 | USER | Fill forms, save answers, view own score (only in announced/archived mode). Cannot see admin fields in user info panel, cannot change user category/flag/level. |
2 | ADMIN | Everything level 1 has, plus: always sees scores, can view/edit user info panel fully, can switch modes via Config panel, can use "View As" to impersonate users. |
3 | SUPER | All of admin. Typically used for super-administrators with full data export access. |
4 | SYS | System-level — reserved for automated or system accounts. |
Key Level Gates in Code
const _lv = parseInt(localStorage.getItem('level') || '1');
// Score visibility — admin always sees, user only in announced/archived
const _scoreVisible = meta.showScore || _lv >= 2;
// Admin fields in user info panel
if (_lv < 2) { /* hide category, flag, level rows */ }
// Save block — enforced by CSS class on body
// body:not(.kess-mode-open) .panel-footer-save-btn → display:none
// So save button is hidden whenever mode != open, regardless of level
All API Paths
All requests use API_HOST as the base. Change API_HOST to point to a different server.
Current Host Config
const API_HOST = "https://q.kunok.com/s9";
const API_PATH = "api622.php"; // main form data
const KEEPER_API_PATH = "api_keeper.php"; // answer snapshots
const ITEMS_API_PATH = "aui.php"; // user table
const BOARD_API_PATH = "api_board.php"; // board + summary
| Constant | File | Purpose | Methods |
|---|---|---|---|
API_PATH |
api622.php |
Load/save Assessment (bucket) and Profile (cup) form answers. Also loads schema templates. | GET (load) · POST (save) |
KEEPER_API_PATH |
api_keeper.php |
Per-user answer snapshots in named slots: bucket, cup, board. Fast key-value store. |
GET (load slot) · POST (save slot) |
ITEMS_API_PATH |
aui.php |
User table: list users, get user by id, update user fields (name, flag, level, board data). | GET list · GET single · POST update |
BOARD_API_PATH |
api_board.php |
Reads and writes the board and board_summary columns for a user. Lighter than full user update. |
GET · POST |
API Details
api622.php — Form Data
// Load template schema
GET {API_HOST}/api622.php?action=get&templateId={id}&userId={uid}
// Save answers
POST {API_HOST}/api622.php
Body: { action:"save", userId, templateId, slot:"bucket"|"cup", data:{...answers} }
api_keeper.php — Answer Snapshots
// Load a slot
GET {API_HOST}/api_keeper.php?id={userId}_{slot}
// e.g. id = "user123_bucket"
// Save a slot
POST {API_HOST}/api_keeper.php
Body: { id:"{userId}_{slot}", data:{...answers} }
Slots are: bucket (Assessment answers), cup (Profile answers), board (board summary).
aui.php — User Table
// List users (admin)
GET {API_HOST}/aui.php?action=list
// Get single user
GET {API_HOST}/aui.php?action=list&id={userId}
// Update user fields
POST {API_HOST}/aui.php
Body: { action:"update", id, name?, flag?, level?, board?, board_summary? }
api_board.php — Board Data
// Load board for user
GET {API_HOST}/api_board.php?id={userId}
// Save board
POST {API_HOST}/api_board.php
Body: { id:userId, board:{...}, board_summary:{...} }
Changing the Host
To point to a different server, change only API_HOST in the hardcode block. All four API path constants are relative to it — you do not need to change them unless the PHP filenames also change.
// Example: move to staging server
const API_HOST = "https://staging.example.com/api";
Data Flow
On page load, the sequence is:
- Login session sets
localStorage:userid,flag,level,user_name - The flag resolves a
templateIdvia_flagToTemplate api622.phpis called → returns the JSON schema for that template, merged with the user's saved answers → stored inwindow.bucket- Profile schema (fixed template) is loaded → stored in
window.cup genform(divId, schema)renders the schema into the panel- Keeper slot is loaded from
api_keeper.phpand applied on top viaapplyKeeper() _kessApplyMode()resolves and applies the current mode- Live progress, scores, and jumperhelper update via
_menuUpdateTracking()
On Save (💾 button)
- Extracts answers from schema:
extractKeeper(schema)→window.keeper.bucket - Posts to
api_keeper.php(fast snapshot) - Posts to
api_board.php(board summary update) - Optionally posts to
api622.php(full save)
Schema Objects
Both forms live as global objects. The schema is a flat key→field map returned by api622.php.
| Global | Form | Panel ID | Div ID | Template source |
|---|---|---|---|---|
window.bucket | 📋 Assessment | bucketPanel | gform | Flag-mapped template (15–18) |
window.cup | 👤 Profile | cupPanel | pro | Fixed profile template |
window.board | — | — | — | Board data from api_board.php |
window.keeper | — | — | — | { bucket:{}, cup:{}, board:{} } snapshots |
Field object shape
{
type: "field",
data_type: "radioatt", // see Field Types below
label: { en: "Has anti-corruption policy?", th: "..." },
required: true,
score: 5, // max points for this field
weights: [5, 3, 1], // per-option score weights
section: "governance",
segment: ["environmental"],
value: null, // filled in after answers applied
options: [ // for radio/select/checkbox types
{ label: { en: "Yes", th: "ใช่" } },
{ label: { en: "No", th: "ไม่ใช่" } }
]
}
Keeper Storage
Keeper is a lightweight answer-snapshot system. It stores only the values (not the full schema) per user per slot, allowing fast save/restore without re-fetching templates.
| Slot | Contains | When saved |
|---|---|---|
bucket | Assessment field values | 💾 Save button on Assessment panel |
cup | Profile field values | 💾 Save button on Profile panel |
board | Board + board_summary | Any save, also standalone board save |
Keeper API key format
// Key = "{userId}_{slot}"
"user123_bucket"
"user123_cup"
"user123_board"
On Load
// Load bucket slot and apply to schema
const answers = await _loadKeeperSlot(userId, "bucket");
applyKeeper(window.bucket, answers);
genform("gform", window.bucket); // re-render with loaded values
Panels
All panels are created by microx.js createPanel(). Form panels are special — they use a move strategy to physically relocate the real DOM node into the panel (preserving all event listeners and IDs).
| Panel ID | Content ID | Opens via | Position |
|---|---|---|---|
bucketPanel | gform | Menu → Assessment / toggleBucketPanel() | Left side, slides from CC |
cupPanel | pro | Menu → Profile / toggleCupPanel() | Right side, CC |
summaryPanel | summaryInner | Menu → Summary | TR |
dashPanel | dashInner | Menu → Dashboard | BC |
keeperPanel | keeperInner | Menu → Keeper | BR |
menuPanel | — | Hamburger / toggleMenu() | Left edge |
Panel Footer Buttons (Assessment & Profile)
| Button | Align | Action |
|---|---|---|
| ▲ | Left | Scroll to top of form |
| ▼ | Left | Scroll to bottom |
| ↕ | Left | Toggle all sections open/closed |
| ⛶ | Left | Toggle section headers only |
| 📌 | Left | Pin current scroll position |
| ☆ | Left | Jump to pinned position |
| 🔒 | Left | Lock/unlock panel drag |
| 💾 | Right | Save answers + keeper + board |
| 📂 | Right | Load answers from keeper |
| 📊 | Right | Open dashboard for this form |
Footer center shows live progress: 24/25 required (96%) with a colour bar. Updated every time a field changes via _menuUpdateTracking().
Field Types
Defined in gflib02.js. The data_type property of each field controls rendering and answer logic.
| data_type | Input UI | Answered when | File required? |
|---|---|---|---|
text / string | Text input | Non-empty string | No |
textarea | Textarea | Non-empty | No |
number | Number input | Non-empty | No |
radio | Radio buttons | Any option selected | No |
select | Dropdown | Any option selected | No |
checkbox | Checkboxes | ≥1 checked | No |
radioatt | Radio + file upload | Option 0 (Yes): needs file. Option >0 (No/NA): just selection | Only if index 0 selected |
radiotextatt | Radio + text + file | Same as radioatt + text must be filled | Only if index 0 selected |
radiotext | Radio + text | Any radio selected | No |
checktext | Checkboxes + text | ≥1 checked; text required if "Others" active | No |
checktextatt | Checkboxes + text + file | ≥1 checked; if index 0 NOT selected → file+text required | Conditional |
attachment | File upload only | ≥1 file uploaded | Yes (always) |
section | Section header | N/A | N/A |
Scoring Logic
Scoring is computed live by gflib02.js functions. No server round-trip needed.
Score Modes (window._scoreMode)
| Mode | Value | What shows |
|---|---|---|
| Off | 0 | No score bars |
| Basic | 1 | Score bars, field scores |
| Super | 2 | All scores + weights + debug info |
Score Calculation
// Per-field score achieved:
// If field has weights array → sum weights for selected options
// Else if answered → field.score (or 1)
// Else → 0
// Max possible score:
// If field has weights → max(weights)
// Else → field.score || 1
// For required fields with score > 0, the dashboard shows:
// ✅ Score: achieved/max (answered)
// ❌ Score: 0/max (unanswered)
// Non-required fields: show file icon only, no score line
QSummary
QSummary(schema) in gflib02.js computes live totals from any schema object. Returns { summary: { totals, segments } }. Used by jumperhelper, dashboard, and footer progress.
Common Change Guide
Change Project Title
window.projectTitle = "ESG Assessment 2027";
The title updates in the menu header automatically via _menuUpdateTracking().
Change Submission Deadline
window.dateExpired = "2027-04-15T17:00:00.000Z"; // UTC
All clocks, timelines, and mode resolution re-read this on each page load. No other changes needed.
Force a Mode Permanently
window.kessProjectMode = 'announced'; // override all date logic
Set back to 'auto' to re-enable date-driven mode.
Change API Server
const API_HOST = "https://newserver.example.com/api";
All four API paths are relative — no other changes needed unless filenames differ.
Add a New Score Mode Behaviour
Score mode is an integer (0/1/2). To add mode 3:
- Find
setScoreMode()ingen59__1_.htmland add a case for3 - Add a button to the score mode panel
- In
gflib02.js, readwindow._scoreModeinupdateFieldDisplay()and add mode-3 rendering logic
Change Mode Banner Text
const _KESS_MODES = {
announced: {
banner: '🏆 Results are published — view your score below.',
// ↑ change this string
}
};
Add a New Panel to the Dashboard
- Add a tab button in the
dashInnerHTML block - Add a corresponding
<div id="myTab-tab" class="tab-content" data-tab="myTab"> - Write a
renderMyTabDashboard()function - Add calls to it in
refreshScoreDashboard()andswitchDashboardTab()
Full Checklist: Adding a New User Type
-
Create the template in your backend database. Note the new template ID (e.g.
19). -
Update hardcode block —
_flagToTemplate:const _flagToTemplate = { "1":18, "2":17, "3":16, "4":15, "5":19 }; -
Update admin user list —
flagMap(searchconst flagMap, appears ~3 times):const flagMap = {'1':18, '2':17, '3':16, '4':15, '5':19}; -
Update flag label (search
_flagLabel, appears ~3 times):const _flagLabel = { '1':'Food Factory', ..., '5':'Service Provider' }; -
Add flag option to user edit form (search for
option value="4"):<!-- add after flag 4 option --> <option value="5" ${user.flag == 5 ? "selected" : ""}>5 — Service Provider</option> -
Ensure
api622.phpcan serve template 19 for a valid user request. - Set the flag for the new user(s) via the Admin Users panel → Edit → Flag dropdown.
- Test by logging in as a new user, verifying the correct template loads in Assessment.
18 (Food Factory). This means a misconfigured new flag will silently load the wrong form — always test with a real user account.