第 02 課 · Lesson 02

產品分類與產品目錄 | Product Taxonomy & Catalogue

八大產品家族;attribute schema、benefit tables、版本控制同生效日。

Plays in the sticky player at the bottom of the page

課堂筆記

學習目標 · Learning Objectives

  1. Classify any Hong Kong insurance product into one of eight families — life, medical, critical illness, savings, ILAS, accident, home, car — and understand why that classification drives which POS modules, disclosures and regulators become active for a case.
  2. Read a product attribute record and know which fields are legally load-bearing (sum assured, benefit definition, exclusions, premium mode, eligibility) versus presentational (marketing copy, colour, banner), so you can explain why the catalogue is versioned rather than edited in place — using effective dates, the "what did the client actually see" test, and the repainting risk that version immutability eliminates.
  3. Apply benefit tables and eligibility rules to a real client profile and predict whether a quote will come back standard, loaded, referred or declined.

Product Taxonomy & Catalogue (產品分類與產品目錄)

Learning Objectives

  • Classify any Hong Kong insurance product into one of eight families — life, medical, critical illness, savings, ILAS, accident, home, car — and understand why that classification drives which POS modules, disclosures and regulators become active for a case.
  • Read a product attribute record and know which fields are legally load-bearing (sum assured, benefit definition, exclusions, premium mode, eligibility) versus presentational (marketing copy, colour, banner), so you can explain why the catalogue is versioned rather than edited in place — using effective dates, the "what did the client actually see" test, and the repainting risk that version immutability eliminates.
  • Apply benefit tables and eligibility rules to a real client profile and predict whether a quote will come back standard, loaded, referred or declined.

The Eight Families

In practice: a client says "I want a medical plan". You do not search for "medical". You filter the catalogue to family MEDICAL, which excludes the ILAS medical rider that would have looked right on a search, and includes the one that pays HK$0 for a pre-existing condition.

Hong Kong's product shelf splits cleanly into eight families. This is not a marketing taxonomy — it is the axis along which POS behaviour changes.

Family definitions

Family中文中文俗稱Core promiseTypical premium shapePOS modules engagedRegulator lens
Life壽險壽險 / 定期壽險 (term)Pays a lump sum on deathLevel or increasing, 10–40 yrCatalogue, Quoting, App, UW, CRMIA
Medical醫療險醫療/住院醫療Reimburses hospital bills up to an annual limitAnnual, renewable, age-bandedCatalogue, Quoting, App, UW, ClaimsIA
Critical illness危疾險危疾 / 癌症Lump sum on diagnosis of a defined conditionLevel, 10–25 yr, or attached to savingsCatalogue, Quoting, App, UW, ClaimsIA
Savings儲蓄險儲蓄 / 終身壽Accumulates a cash value; pays on death or maturityLevel premium, often 5–20 yr payCatalogue, Quoting, App, FNA, CRMIA
ILAS投資壽險投資壽險 / 投資連接壽險Separates premium into guaranteed + investment-linked partsFlexible, long-payCatalogue, Quoting, FNA, App, App*, CRMIA and SFC
Accident意外險意外Pays on injury, often by schedule of fixed benefitsCheap, annual, high-volumeCatalogue, Quoting, AppIA
Home家居保險家居 / 家居保Covers building, contents, liabilityAnnual, short-termCatalogue, Quoting, App, ClaimsIA / HKMA if bank-distributed
Car汽車保險汽車Mandatory third-party cover; optional own-damageAnnual, usage and driver ratedCatalogue, Quoting, App, ClaimsHKMA if the insurer is in a banking group

Three observations from that table that are worth more than the taxonomy itself:

  1. General-insurance families (accident, home, car) skip underwriting entirely in most cases. There is no health question, so the UW module in Lesson 06 never activates. But they do have heavy claims workflows, so the claims module is busier per case than for life.
  2. Car insurance in Hong Kong is compulsory, which inverts the sales relationship. The client is not shopping; they are renewing or they cannot drive. The POS job for car is retention and quote accuracy, not persuasion.
  3. ILAS is the only family that activates two regulators at once. That is why Lesson 05's suitability step has an ILAS-specific branch, and why the product record for an ILAS plan carries an SFC-shaped disclosure block.

Family membership is data, not logic

A product declares its families as an array. A dual-purpose product can belong to two. A savings-participating whole life plan is ["LIFE", "SAVINGS"] — which is exactly right, and it means the POS will run the FNA module for it (because of SAVINGS) while also offering the CI/simplified-underwriting track (because of LIFE).

The value of data-driven families shows up in the medical edge case:

export type ProductFamily =
  | 'LIFE' | 'MEDICAL' | 'CRITICAL_ILLNESS' | 'SAVINGS'
  | 'ILAS' | 'ACCIDENT' | 'HOME' | 'CAR';

/** Which POS behaviour each family switches on. */
export interface FamilyBehaviour {
  requiresNeedsAnalysis: boolean;
  requiresSuitabilityAssessment: boolean;
  healthQuestions: 'full' | 'simplified' | 'none';
  /** ILAS is the only family with a second regulator's product regime. */
  secondaryRegulator: 'SFC' | null;
  /** Which agent appointment class can sell it. */
  appointmentClass: 'LONG_TERM' | 'GENERAL' | 'LONG_TERM_AND_GENERAL';
  claimComplexity: 'LOW' | 'MEDIUM' | 'HIGH';
}

export const FAMILY_BEHAVIOUR: Record<ProductFamily, FamilyBehaviour> = {
  LIFE: {
    requiresNeedsAnalysis: true, requiresSuitabilityAssessment: false,
    healthQuestions: 'full', secondaryRegulator: null,
    appointmentClass: 'LONG_TERM', claimComplexity: 'LOW',
  },
  MEDICAL: {
    requiresNeedsAnalysis: false, requiresSuitabilityAssessment: false,
    healthQuestions: 'full', secondaryRegulator: null,
    appointmentClass: 'LONG_TERM', claimComplexity: 'HIGH',
  },
  CRITICAL_ILLNESS: {
    requiresNeedsAnalysis: true, requiresSuitabilityAssessment: false,
    healthQuestions: 'full', secondaryRegulator: null,
    appointmentClass: 'LONG_TERM', claimComplexity: 'MEDIUM',
  },
  SAVINGS: {
    requiresNeedsAnalysis: true, requiresSuitabilityAssessment: false,
    healthQuestions: 'full', secondaryRegulator: null,
    appointmentClass: 'LONG_TERM', claimComplexity: 'LOW',
  },
  ILAS: {
    requiresNeedsAnalysis: true, requiresSuitabilityAssessment: true,
    healthQuestions: 'full', secondaryRegulator: 'SFC',
    appointmentClass: 'LONG_TERM', claimComplexity: 'MEDIUM',
  },
  ACCIDENT: {
    requiresNeedsAnalysis: false, requiresSuitabilityAssessment: false,
    healthQuestions: 'none', secondaryRegulator: null,
    appointmentClass: 'GENERAL', claimComplexity: 'LOW',
  },
  HOME: {
    requiresNeedsAnalysis: false, requiresSuitabilityAssessment: false,
    healthQuestions: 'none', secondaryRegulator: null,
    appointmentClass: 'GENERAL', claimComplexity: 'MEDIUM',
  },
  CAR: {
    requiresNeedsAnalysis: false, requiresSuitabilityAssessment: false,
    healthQuestions: 'none', secondaryRegulator: null,
    appointmentClass: 'LONG_TERM_AND_GENERAL', claimComplexity: 'MEDIUM',
  },
};

The MEDICAL line is the interesting one. requiresNeedsAnalysis: false is not because medical insurance is unimportant — it is because a pure medical plan has no cash-flow projection to defend. Add a rider that converts it to a savings-linked medical product and that flips to true, because now the agent must project cash flows to show the premium gap is fundable. Family membership is what makes that switch automatic.


Attribute Schema

In practice: you open a product record in the POS and the first thing you check is not the headline benefit. It is the version, the effective-from date, and whether the definition block is marked "pre-2019 style" or "core definition".

A Hong Kong POS product record is a versioned document with roughly forty fields. They fall into four tiers, and only the first three are legally load-bearing.

The schema

{
  "$schema": "https://pos.hk/schema/product-2.1.json",
  "productCode": "HK-whole-life-spp-2024",
  "version": 4,
  "status": "APPROVED_FOR_SALE",
  "carrierCode": "PRC-HK",
  "carrierProductId": "LIFE-SPP-F4",
  "displayNames": {
    "zhHant": "裕悅终身壽險計劃",
    "en": "Prosperity Whole Life Plan"
  },
  "families": ["LIFE", "SAVINGS"],
  "productType": "PARTICIPATING_WHOLE_LIFE",
  "termOptions": [10, 15, 20, 25, 30, 40],
  "currency": { "base": "HKD", "settleOptions": ["HKD", "USD"] },

  "premium": {
    "modes": ["MONTHLY", "QUARTERLY", "SEMI_ANNUAL", "ANNUAL", "SINGLE"],
    "discountableTo": 0.35,
    "minimumAnnualPremium": 24000,
    "premiumPayingYears": [5, 10, 15, 20, "TO_AGE_100"],
    "waiverOfPremiumAvailable": true,
    "paymentGraceDays": 31
  },

  "sumAssured": {
    "mode": "MULTIPLE_OF_BASIC_SA",
    "multiples": { "min": 1, "max": 20, "step": 1 },
    "basicSaCurrency": "USD",
    "minBasicSa": 12500
  },

  "benefits": {
    "deathBenefit": {
      "definition": "greater of sum assured or 101% of basic sum assured",
      "boardApprovalReference": "BA-2024-11-03"
    },
    "maturityBenefit": { "applies": false },
    "terminalIllnessBenefit": { "amountMultipleOfSumAssured": 1.0, "waitingDays": 30 },
    "accidentalDeathBenefit": { "amountMultipleOfSumAssured": 1.0 }
  },

  "benefitTables": ["participating-bonus-2025q1", "sum-assured-sched-riders"],

  "eligibility": {
    "minIssueAge": 18,
    "maxIssueAge": 70,
    "minPremiumPayingAge": 18,
    "smokerDeclared": "REQUIRED",
    "residency": [
      { "labelZh": "香港居民", "mustHoldHkid": true },
      { "labelZh": "非香港居民(持有工作或學生簽證)", "mustHoldHkid": false, "allowed": true }
    ],
    "occupationClasses": { "allowed": [1, 2, 3, 4], "referred": [5], "declined": [6] },
    "residencyExclusions": { "sanctionedJurisdictions": true, "maxOffshorePolicyCountPerClient": 2 }
  },

  "underwriting": {
    "track": "FULL",
    "medicalEvidenceThresholdHKD": 250000,
    "simplifiedTrackEligibleIfSumAssuredUnder": 250000,
    "facultative": { "requiredFor": ["OCCUPATION_5_PLUS", "BMI_OVER_35", "DIABETES_ON_INSULIN"] }
  },

  "exclusions": [
    { "code": "EXC-SUICIDE-24M", "zh": "自殺或自導致傷亡:首 24 個月不賠" },
    { "code": "EXC-INTOX-24M", "zh": "受酒精或藥物影響下意外:首 24 個月不賠" },
    { "code": "EXC-WAR", "zh": "戰爭、暴動、恐怖活動" },
    { "code": "EXC-NUCLEAR", "zh": "核輻射或核污染" }
  ],

  "risks": [
    { "code": "RISK-NON-GUAR", "zh": "非保證利益:紅利及終期紅利並非保證,非分紅基金表現不理想" },
    { "code": "RISK-ILLUSTRATION", "zh": "此為非保證利益,只作說明用途,實際結果可能與說明有重大差異" }
  ],

  "fees": {
    "policyFeeAnnualPctOfPremium": 0.08,
    "surrenderCharge": [
      { "yearFrom": 1, "yearTo": 2, "pctOfBasicSa": 4.0 },
      { "yearFrom": 3, "yearTo": 5, "pctOfBasicSa": 2.0 },
      { "yearFrom": 6, "yearTo": 10, "pctOfBasicSa": 1.0 },
      { "yearFrom": 11, "yearTo": null, "pctOfBasicSa": 0.0 }
    ]
  },

  "lifecycle": {
    "effectiveFrom": "2025-04-01",
    "effectiveTo": null,
    "supersedesVersion": 3,
    "boardApprovedAt": "2025-03-12",
    "boardApprovedBy": "Board Product Committee",
    "productGovernanceRef": "IA-PPG-2025-0417"
  },

  "regulatory": {
    "authority": "IA",
    "secondaryAuthority": null,
    "disclosureVersionRequired": "DISC-2025.03",
    "coolingOffDays": 31,
    "taxNoteZh": "退稅只適用於以保費作主要用途的合約,並非所有產品均合資格"
  },

  "commission": {
    "fycMaxPctOfFirstYear": 0.42,
    "renewalPctFromYear": { "from": 2, "to": 5, "pct": 0.10 },
    "clawbackWindowMonths": 13,
    "estimateOnly": true
  }
}

The four tiers

TierExamplesLoad-bearing?If you get it wrong
IdentityproductCode, version, carrierProductId, lifecycle.*Yes — determines which artefact is legally being quotedThe audit trail points at the wrong document
Contractualbenefits.*, exclusions[], fees.*, risks[], premium.modes, sumAssured.*Yes — these are the contract termsMis-selling; the client can complain and can be right
Eligibilityeligibility.*, underwriting.*Yes — determines whether the case can existA declined-at-underwriting case the agent promised would be fine
PresentationaldisplayNames, banners, highlight colours, marketing blurbsNoNothing, but it is what the agent actually looks at first

That last row is the human factor worth naming: agents spend their time on tier 4 and are trained on tier 2. The POS's job is to surface tier 2 and 3 in a form an agent will actually act on — which is why exclusions are rendered as sentences rather than codes, and why EXC-SUICIDE-24M is accompanied by the zh string that goes on the proposal.

Cross-referencing: benefit tables

benefitTables in the product record points at versioned table documents rather than inlining them, because a table is updated far more often than the product definition. A participating policy's bonus illustration changes quarterly; the contract terms do not.

-- Resolution used at quote time. The join is ON VERSION, never "latest".
SELECT
  p.product_code,
  p.version,
  bt.table_id,
  bt.table_version,
  bt.effective_from,
  json_extract(bt.rows_json, '$."[31,0]"') AS row_for_age_31
FROM product p
JOIN product_benefit_table_ref r ON r.product_code = p.product_code
                           AND r.product_version = p.version
JOIN benefit_table bt ON bt.table_id = r.table_id
                      AND bt.table_version = :resolved_table_version
WHERE p.product_code = :productCode
  AND p.version = :productVersion
  AND p.status = 'APPROVED_FOR_SALE';

The AND p.version = :productVersion is load-bearing. A quote for version 4 must never resolve a table "as at latest", or a proposal can silently quote version 4 terms against a version 5 table.


Benefit Tables

In practice: a client asks whether HK$500,000 will still be HK$500,000 in 20 years' time under a participating policy. You open the benefit table version, show the projected value of 51% of it, and say out loud that the other 49% is not guaranteed.

A benefit table is a versioned, dated grid of how much a policy pays under specified circumstances. There are four shapes in active use in Hong Kong.

Shape 1: Sum assured schedule

Used in products where the benefit escalates with term to fight under-insurance at maturity.

Policy yearSum assured (indexed)Pay on deathPay on diagnosis of covered CI
1250,000250,000250,000
10268,000268,000268,000
20296,000296,000296,000
30331,000331,000331,000

Shape 2: Participating / non-guaranteed bonus projection

The table every agent must learn to present honestly. It has three rows and only one of them is guaranteed.

Policy yearGuaranteed benefitIllustrative non-guaranteed benefit @ 4.0%Illustrative @ 2.0%Illustrative @ 6.0%
10251,300279,100268,400290,600
20300,900384,600350,200423,800
30349,900512,400428,700611,200

Note what changes and what does not: the guaranteed column is identical in every scenario. That is the whole client conversation. An agent who shows only the middle column has mis-sold.

Shape 3: Rider schedule (fixed-benefit, schedule-of-benefits style)

Accident and hospital-cash products pay by schedule, not by percentage. This is why they are cheap and why claims are fast.

BenefitAmount (HKD)Waiting periodMax days per policy year
住院津貼 — 每日800Day 1120
住院津貼 — 首 30 日8,000—per claim
重疾津貼 — 確診50,00030-day survivalper claim
意外死亡200,00030 days—
意外殘疾 — 永久完全200,00030 days—
意外殘疾 — 部分(按比例)50%–100% of 200,00030 days—
器官移植100,00030 days—

Shape 4: ILAS fund-and-charge table

ILAS benefit tables do not describe payments. They describe charges, because the value is unit-linked and no figure is guaranteed.

AccountGuaranteed portionNon-guaranteed portionAnnual policy feeFund ongoing charge (max)Allocation
A20%80%0.08% p.a. of premium1.50% p.a. of fund valueBond 60 / Equity 20 / Cash 20
B10%90%0.08% p.a. of premium1.75% p.a. of fund valueBond 40 / Equity 55 / Cash 5
C0%100%0.08% p.a. of premium2.00% p.a. of fund valueEquity 100

The maximum ongoing charge figure is a regulatory disclosure that has to appear in the POS at quote time and on the proposal. An ILAS illustration that omits it is defective on its face.

Choosing a table: the rule

Client's questionTable to openNever
"Is my cover guaranteed?"Guaranteed benefit row onlyNever quote a total that blends guaranteed and non-guaranteed
"What if I live to 100?"Whole-table projection across scenariosNever present one scenario as expected
"How much for cash hospitalisation?"Rider scheduleNever substitute a percentage-based rider
"What's the ILAS yield?"Fund-and-charge table + live fund dataNever use a static projection for ILAS

Eligibility Rules

In practice: a 34-year-old mainland resident in HK on a work visa opens the catalogue, and 41% of the long-term products on the shelf grey out with the reason "此產品不適用於非香港居民".

An eligibility rule answers one question: may this product be offered to this client, as of today, by this agent? It is the rule the POS enforces hardest, because every failure is a legally defective sale.

The rule vocabulary

Rule typeQuestionExampleFailure mode
AGEIs the client old/young enough?minIssueAge 18, maxIssueAge 70Cover cannot be offered
PREMIUM_FLOORIs the premium big enough?minimumAnnualPremium 24,000Quote not produced
RESIDENCYIs the client resident?Non-HK resident: product unavailableRegulatory breach
OCCUPATIONIs the occupation insurable?Class 6 (military, commercial diving) declinedQuote refused
HEALTHIs the health history acceptable?Diabetes on insulin → facultativeCase referred
SMOKERDeclared smoking statusSmoker vs non-smoker rate classMispricing
APPOINTMENTDoes this agent hold the appointment?Not appointed with carrier XCannot submit
EXISTING_COVEROffshore holdingsMax 2 offshore policies per clientRegulatory breach
SANCTIONSJurisdiction / name screeningSanctioned country of residenceAML breach
CHANNELIs this channel permitted for this product?Online-only products excluded from face-to-faceConduct breach

Evaluating them in the right order

Order matters for two reasons: it is faster, and it produces a better error message. A 25-year-old Mainland resident applying for a HK$500,000 participating whole life plan should be told about the age and the residency, but they should be told about the residency first if the product is unavailable to them entirely.

export type RuleType =
  | 'AGE' | 'PREMIUM_FLOOR' | 'RESIDENCY' | 'OCCUPATION'
  | 'HEALTH' | 'SMOKER' | 'APPOINTMENT' | 'EXISTING_COVER'
  | 'SANCTIONS' | 'CHANNEL';

export interface EligibilityContext {
  client: {
    ageAtNextBirthday: number;
    residency: 'HK_RESIDENT' | 'NON_HK_WORK_VISA' | 'NON_HK_OTHER';
    smoker: boolean;
    occupationClass: 1 | 2 | 3 | 4 | 5 | 6;
    offshorePolicyCount: number;
    healthFlags: string[];
  };
  product: {
    minIssueAge: number; maxIssueAge: number;
    minimumAnnualPremium: number;
    residencyAllowed: string[];
    occupationAllowed: number[];
    maxOffshorePolicyCount: number;
  };
  channel: 'FACE_TO_FACE' | 'PHONE' | 'ONLINE' | 'BROKER_REFERRAL';
  agentCarrierAppointments: string[];
}

export interface RuleOutcome {
  type: RuleType;
  passed: boolean;
  /** Hard failure => product cannot be offered. Soft => must be referred. */
  severity: 'BLOCK' | 'REFER' | 'WARN';
  messageZh: string;
}

export function evaluateEligibility(ctx: EligibilityContext): RuleOutcome[] {
  const { client: c, product: p, channel } = ctx;
  const out: RuleOutcome[] = [];

  // 1. Appointment first: if we cannot sell it, no rule below is worth running.
  if (!ctx.agentCarrierAppointments.length) {
    out.push({ type: 'APPOINTMENT', passed: false, severity: 'BLOCK',
      messageZh: '你未持有此保險公司的委任,不能推廣此產品' });
    return out; // fail fast: stop here
  }

  // 2. Residency — regulatory, not commercial.
  if (!p.residencyAllowed.includes(c.residency)) {
    out.push({ type: 'RESIDENCY', passed: false, severity: 'BLOCK',
      messageZh: '此產品不適用於目前客戶身分' });
  }

  // 3. Sanctions / jurisdiction: overrides everything below it.
  if (c.residency === 'NON_HK_OTHER') {
    out.push({ type: 'SANCTIONS', passed: false, severity: 'BLOCK',
      messageZh: '需先完成制裁及居住地查核,未能通過前不可報價' });
  }

  // 4. Age.
  if (c.ageAtNextBirthday < p.minIssueAge || c.ageAtNextBirthday > p.maxIssueAge) {
    out.push({ type: 'AGE', passed: false, severity: 'BLOCK',
      messageZh: `投保年齡須為 ${p.minIssueAge} 至 ${p.maxIssueAge} 歲` });
  }

  // 5. Occupation: the REFER band is where agents get caught out.
  if (p.occupationAllowed.length && !p.occupationAllowed.includes(c.occupationClass)) {
    const declined = c.occupationClass === 6;
    out.push({ type: 'OCCUPATION', passed: false,
      severity: declined ? 'BLOCK' : 'REFER',
      messageZh: declined
        ? '該職業類別不獲承保'
        : '該職業類別需轉介核保,POS 可報價但不可直接提交' });
  }

  // 6. Health flags that force facultative underwriting.
  const facultative = c.healthFlags.filter((f) =>
    ['DIABETES_ON_INSULIN', 'BMI_OVER_35', 'CARDIAC_HISTORY'].includes(f));
  if (facultative.length) {
    out.push({ type: 'HEALTH', passed: false, severity: 'REFER',
      messageZh: `需提交體檢報告作個別核保:${facultative.join('、')}` });
  }

  // 7. Premium floor (checked against the *intended* premium, not the minimum).
  out.push({ type: 'PREMIUM_FLOOR', passed: true, severity: 'WARN',
    messageZh: `年繳保費須不少於 HK$${p.minimumAnnualPremium.toLocaleString('en-HK')}` });

  // 8. Offshore concentration limit.
  if (c.offshorePolicyCount >= p.maxOffshorePolicyCount) {
    out.push({ type: 'EXISTING_COVER', passed: false, severity: 'BLOCK',
      messageZh: `客戶已持有 ${c.offshorePolicyCount} 份境外保單,已達上限` });
  }

  // 9. Channel restriction.
  if (channel === 'ONLINE' && p.residencyAllowed.length === 0) {
    out.push({ type: 'CHANNEL', passed: false, severity: 'BLOCK',
      messageZh: '此產品不設網上投保渠道' });
  }
  return out;
}

Hard block, referral, warning — the three severities

Getting these apart is a skill. The POS should:

  • BLOCK → grey the product out, do not offer it, and say why in one line.
  • REFER → allow the quote, forbid straight-through submission, and show the agent exactly what to tell the client: "呢個 case 需要核保師批,我哋今日會交,兩個工作天內有答覆".
  • WARN → allow everything, but render the caution on the comparison sheet so it does not get lost in a four-product table.

An agent who cannot distinguish "the POS says no" from "the POS says wait" will either lose a case or make a promise the carrier will not honour.


Versioning & Effective Dates

In practice: a client calls about a policy quoted in March. The POS does not look up "March". It looks up the product version whose effectiveFrom ≤ 2025-03-14 < effectiveTo, and shows the agent the exact document the client signed.

This is the heart of the lesson. A catalogue that can be edited in place is a catalogue that cannot answer "what did you sell me?".

Why never edit in place

The failure mode has a name: repainting history. The moment a benefit, exclusion, rate or age band can be changed on a live product record, three things break simultaneously:

  1. Audit. The client's signed proposal says benefit clause X. The catalogue now says clause Y. Neither the client nor the carrier can prove which was in force on 14 March.
  2. In-force business. A policy issued under version 3 must keep its version 3 terms forever. If version 3 was edited to fix a typo in an exclusion, and version 4 is "the corrected one", you have just silently altered a live contract — or, worse, retroactively corrected the client's paperwork.
  3. Premium disputes. If an exclusion was widened after 500 policies were issued on version 3, and the POS shows version 4 to a new client, the agent is knowingly misrepresenting the older book. If it shows version 3, it is proposing obsolete terms.

None of those is acceptable in a regulated sale. So the catalogue is append-only.

The version lifecycle

  DRAFT ──────► INTERNAL_REVIEW ──────► BOARD_APPROVED ──────► APPROVED_FOR_SALE
    │                   │                      │                        │
    │                   ▼                      ▼                        ▼
    └────────────► WITHDRAWN ◄────────── SUPERSEDED ◄───────────── WITHDRAWN
                                                              (sold, not for sale)
Status中文MeaningCan it be quoted?Can it be quoted for submission?
DRAFT草擬Being writtenNoNo
INTERNAL_REVIEW內部審核Actuarial + compliance reviewNoNo
BOARD_APPROVED董事會批准Approved, not yet effectivePreview only, clearly watermarkedNo
APPROVED_FOR_SALE核准銷售Effective and sellableYesYes
SUPERSEDED已被取代Replaced by a newer versionYes, for in-force and pending casesNo — re-quote on the new version
WITHDRAWN撤回Pulled from saleYes, read-only, in-force onlyNo

The SUPERSEDED row is the one that saves you. When a carrier improves a product mid-month and version 5 goes live, every case still sitting in your queue at version 4 keeps its version 4 terms. You are not obliged to re-quote; you are obliged to record which version you used.

The effective-dating fields that actually matter

FieldMeaningTrap if wrong
effectiveFromFirst day this version may be quoted and submittedQuoting from 1 day early = selling an unapproved product
effectiveToLast day (inclusive)Off-by-one on the boundary day
boardApprovedAtWhen governance approvedDisclosing "approved" for a version approved after the sale
supersedesVersionExplicit pointer to predecessorSilent lineage breaks; a query for "all changes" misses a link
rateCardVersion (Lesson 03)Which pricing table appliesTerm quoted on one card, priced on another
disclosureVersionRequiredWhich disclosure must be acknowledgedClient signs a disclosure that is not the one in force

Why every quote stores the version triple

{
  "quoteId": "QT-20881-A",
  "productCode": "HK-whole-life-spp-2024",
  "productVersion": 4,
  "rateCardVersion": "PRC-LIFE-2025-03-14",
  "benefitTableVersion": "participating-bonus-2025q1",
  "assumptionsVersion": "ASSUMP-2025.02",
  "quotedAt": "2025-03-14T11:38:22+08:00",
  "quotedByAgentRef": "AG-4471",
  "deviceId": "TBLT-07",
  "validUntil": "2025-04-13T11:38:22+08:00",
  "immutability": {
    "payloadHash": "9f2c1a44e0b7",
    "supersededBy": null,
    "supersedes": "QT-20881-A-ORIG"
  }
}

Five version pointers on one quote. An auditor asking "reconstruct this sale" needs all five, and the POS has all five because there was never a moment when they could have been overwritten.

The withdrawal drill

Every version needs a plan for the day it is withdrawn. A product withdrawn because of a regulatory concern must be handled differently from one withdrawn because it is being replaced:

Withdrawal reason在架處理Existing quotesIn-force businessPOS behaviour
Regulatory concernRemove from sale immediately, flag in-force bookAll pending quotes invalidated, client re-advisedProactive review with the carrierBlock submission; generate a call list
Replacement with successorSupersede, keep readablePending quotes stay valid until expiryNo actionKeep quoting version N-1 if the quote is unexpired
Data-entry error in the recordCorrect by issuing N+1Pending quotes flagged for reviewNo changeAmber badge + human review
Carrier commercial decisionWithdrawPending quotes re-quoted onto successorNo changeAuto-migrate with a client notification task

Catalogue Loading and Caching

In practice: you open the catalogue on the MTR at 8:40am and every product, including two that were launched yesterday afternoon, is there. The device synced for eleven minutes while you were on a night flight.

The catalogue is the largest static dataset in a POS, and because the POS is offline-first (Lesson 01), it is the thing that most needs a disciplined distribution strategy.

Sizes and shapes

DatasetTypical rowsPayload sizeRefresh cadence
Product headers (identity + eligibility)400–1,5001–4 MBDaily, plus push on launch
Benefit tables2,000–6,0006–20 MBWeekly, plus push on board approval
Rate cards (Lesson 03)1,000–3,0003–12 MBOn effective date, sometimes intraday
Disclosure templates20–600.5–2 MBOn regulatory change — must be immediate
Asset icons / illustrations5,000–40,00020–120 MBWeekly, image-heavy, biggest of the set

The delta-sync model

The device keeps a watermark per dataset, and the server sends only what changed since the watermark. Watermarks are per dataset, not global — a disclosure change must not force a 120 MB asset resend.

CREATE TABLE device_catalogue_watermark (
  device_id      TEXT NOT NULL,
  dataset        TEXT NOT NULL,   -- PRODUCTS | BENEFIT_TABLES | RATE_CARDS | DISCLOSURES | ASSETS
  watermark      TEXT NOT NULL,   -- monotonic: ULID of the last applied change
  applied_at     TIMESTAMP NOT NULL,
  rows_applied   INTEGER NOT NULL DEFAULT 0,
  PRIMARY KEY (device_id, dataset)
);

CREATE TABLE catalogue_change_log (
  change_id     TEXT PRIMARY KEY,   -- ULID, sortable = insertion order
  dataset       TEXT NOT NULL,
  entity_key    TEXT NOT NULL,      -- e.g. 'HK-whole-life-spp-2024@v4'
  change_type   TEXT NOT NULL,      -- UPSERT | WITHDRAW | SUPERSEDE
  effective_from TIMESTAMP NOT NULL,
  payload       TEXT,               -- NULL for WITHDRAW
  published_at  TIMESTAMP NOT NULL
);

CREATE INDEX idx_catalogue_change_watermark
  ON catalogue_change_log (dataset, change_id);

The delete is the interesting part. In an append-only catalogue, "withdraw" is an UPSERT with status: WITHDRAWN, never a DELETE. A device that has been offline for a week must not lose a version; it must learn that the version is withdrawn.

Staleness policy per dataset

DatasetMax tolerated stalenessBeyond that
DisclosuresZero. A stale disclosure is a defective saleBlock the application module entirely
Rate cards0 hours for promo-tied rates; 48 h otherwiseRe-quote required
Product headers24 hoursShow amber "資料可能過時"; block submission on withdrawn items only
Benefit tables7 daysShow the table version on every projection, always
Assets30 daysCosmetic only; never blocks a transaction

Why disclosures are the hard real-time case

A disclosure document is the one artefact whose staleness is immediately unlawful. If the IA issues a revised Code of Conduct disclosure at 09:00 and a device's disclosure cache is from yesterday, then every application signed on that device today is signed against the wrong document.

This is why the disclosure set must be:

  1. Pushed rather than polled, on publication.
  2. Version-pinned to the moment of signature (DISCLOSURE_ACKNOWLEDGEMENT.artefact_version), never to the moment of reading.
  3. Refused-if-unavailable — an agent who cannot fetch the current disclosure cannot proceed to signature.
/**
 * Guard for the application module's signature step.
 * Deliberately fails CLOSED: unknown disclosure state means no signature.
 */
export function canSignWith(
  deviceDisclosureVersion: string | null,
  serverDisclosureVersion: string,
  loadedAt: Date | null,
  now: Date,
): { allowed: boolean; reasonZh?: string } {
  if (deviceDisclosureVersion === null || loadedAt === null) {
    return { allowed: false, reasonZh: '此裝置未載入任何風險披露文件版本,請先連線更新' };
  }
  if (deviceDisclosureVersion !== serverDisclosureVersion) {
    return {
      allowed: false,
      reasonZh: `披露文件版本不符(裝置 ${deviceDisclosureVersion},系統 ${serverDisclosureVersion}),請更新後再簽署`,
    };
  }
  const ageMin = (now.getTime() - loadedAt.getTime()) / 60000;
  if (ageMin > 30) {
    return { allowed: false, reasonZh: '披露文件載入已逾 30 分鐘,請重新連線確認' };
  }
  return { allowed: true };
}

Note the 30-minute window. It is deliberately shorter than any real staleness risk, because a disclosure change is fast, cheap to check, and catastrophic to get wrong. This is the one place in the POS where "fail closed, retry later" beats "carry on and reconcile".


Worked Example: One Client, Four Shelves

In practice: you search the catalogue for a 34-year-old Mainland resident in HK on a work visa, and 58% of the long-term shelf greys out. Three different rules did it, and the client sees one screen that says "not available".

This is the single most instructive thing in the lesson, because it shows how independent rules compose into one outcome the client experiences as a wall.

The client

FieldValue
Age at next birthday34
SexF
ResidencyNON_HK_WORK_VISA — mainland resident, HK work visa valid 22 months
HKIDNot held
SmokerNo (5 pack-years, stopped 3 years ago)
OccupationClass 2 (registered nurse)
BMI22.1
Existing cover1 offshore savings policy (Shanghai)
BudgetHK$4,000/month
Primary need"If I can't work, my parents don't lose the house"

What the catalogue returns

ProductFamilyResultRule that fired
HK Whole Life SPP v4LIFE, SAVINGSAvailableresidency allows NON_HK_WORK_VISA
Evergreen Medical v2MEDICALAvailableno residency restriction; note: offshore limit applies
Shield CI Basic v1CRITICAL_ILLNESSAvailableCI core definition, age 34 in band
Prosper ILAS v3ILASReferralCHANNEL + SFC suitability step required; fund options restricted for non-HK residents at some carriers
Motor Accident Pro v9ACCIDENTAvailablegeneral insurance, no health questions
Heritage Mortgage Life v7LIFEBlockedRESIDENCY — HK mortgage cover, HKID required
HK Term Life 30 v5LIFEBlockedRESIDENCY — HKID required for issue
Voyager Offshore v1ILASBlockedEXISTING_COVER — client already holds 1 offshore policy, carrier limit is 1 for this class

Three rules fired across eight products. From the client's side it was one screen. From the compliance side it was three separate decisions with three separate reasons, each of which must be recorded.

What the POS must show the client

符合你條件的方案:3 個

未能提供的方案及原因:
  ✗ Heritage Mortgage Life v7    — 此產品只接受香港身份證明持有人
  ✗ HK Term Life 30 v5           — 此產品只接受香港身份證明持有人
  ✗ Voyager Offshore v1          — 你已持有 1 份境外儲蓄保單,達此產品上限

  ⚠ Prosper ILAS v3               — 可申請,但需完成投資風險評估,
                                   且投資選項較一般產品受限

That last block is the one an amateur POS omits, and it is the one that protects the agent. It converts a silent refusal into an explanation, and it separates blocked from referral visually — the difference between "no" and "yes, but with work".

The agent's follow-up, and why the POS must support it

The right move for this client is not a third medical plan. It is to notice that her actual need — income protection — is under-served by every product that just greyed out, and that the honest answer is a CI plan or an accident plan she can buy today, plus an explicit note that long-term savings products for non-HK residents are constrained.

A POS that only answers "is this product available?" makes it easy to sell the wrong thing. A POS that answers "is this product available, and what does that mean for the client's need?" teaches the right thing. That is the difference between a catalogue and an adviser tool.


Key Takeaways

In practice: nine sentences that will keep you out of trouble when a carrier rep, a compliance officer or a client asks you a question you were not expecting.

  1. Eight families — LIFE, MEDICAL, CRITICAL_ILLNESS, SAVINGS, ILAS, ACCIDENT, HOME, CAR — and family membership is data that switches POS behaviour on. A savings-participating whole life plan belongs to two families, which is why it runs both the FNA and the underwriting track.
  2. The four attribute tiers are identity, contractual, eligibility and presentational. Only the first three are legally load-bearing; the fourth is what agents actually look at, so the POS must re-render tier 2 and 3 in a form that gets read.
  3. A benefit table is versioned and referenced, never inlined. A quote resolves its table on table_version, never "latest", or version 4 terms get priced against a version 5 projection.
  4. Never blend guaranteed and non-guaranteed benefit in a single number. The guaranteed column is identical across every scenario in a participating projection — that identical column is the client conversation.
  5. ILAS benefit tables describe charges, not payments. The maximum ongoing charge is a regulatory disclosure that must appear at quote time and on the proposal.
  6. Eligibility has three severities, not two. BLOCK greys the product out, REFER lets you quote but forbids straight-through submission, WARN renders on the comparison sheet. Confusing REFER with BLOCK loses cases; confusing it with WARN makes promises the carrier will not keep.
  7. The catalogue is append-only and never edited in place. Because editing in place breaks the audit trail, alters in-force contracts, and creates a repainting-history exposure on every live book.
  8. SUPERSEDED is the status that saves you. A version replaced mid-month keeps its terms for pending quotes and in-force business; the successor is used for new cases only.
  9. Every quote stores five version pointers — product, rate card, benefit table, assumptions, disclosure — because "what did you sell me?" is unanswerable without all five. Disclosures additionally fail closed at 30 minutes, which is the one place in the POS where retrying is correct and carrying on is not.

Glossary (zh-Hant-HK)

Term中文Note
Catalogue產品目錄The versioned shelf of products
Product family產品分類One of the eight families
Sum assured基本保額 / 保額The death benefit base amount
Rider附加保障 / 附加險An optional add-on benefit
Participating分紅Policy with non-guaranteed bonuses
Non-participating非分紅Policy with only guaranteed benefits
Benefit table利益一覽表Versioned grid of payables
Effective date生效日期First day a version may be sold
Supersede取代A newer version replaces an older one
Eligibility投保資格May this product be offered to this client?
Occupation class職業類別1–6 insurable-banding used by underwriting
Issue age投保年齡Age at the policy's in-force date
Persistency續期率Share of policies still in force
Onboarding迎新 / 載入Loading a dataset onto a device
Delta sync差異同步Send only what changed since a watermark
Fail closed封閉失敗Refuse the action when state is unknown

課堂測驗 · 7 題

Question 1 of 7Answered 0 / 7
Question 1 of 7

以下哪一項最能說明為甚麼產品目錄必須「版本化」而不能就地修改(edit in place)?

Pick an answer to lock it in. We'll tell you immediately whether you got it right and show an explanation. Then press Enter or click Next to continue.

Shortcuts:ABCDpick answer on current questionEntergo to next unanswered
7 unanswered