第 07 課 · Lesson 07

多間公司比價 | Multi-Carrier Comparison

點解要 N 間公司;normalise 唔同保額定義;報價對比表;switching cost。

Plays in the sticky player at the bottom of the page

課堂筆記

學習目標 · Learning Objectives

  1. Explain, in a client meeting, why a competent Hong Kong agent must quote at least three carriers, and name the commercial and regulatory reasons (product shelf breadth, commission sustainability, non-solicited (NS) status, IA referral rules, and career-development agency consent) rather than just "price shopping".
  2. Normalise (標準化) two or more carrier quotes that define the same benefit differently into one comparable basis: convert guarantee periods, benefit definitions, deductible design, occupation classes, waiting periods, reinstatement terms and premium-paying periods into a canonical unit (HK$ per HK$1m of normalised benefit, and total cost of ownership over the client's stated horizon), and show the arithmetic.
  3. Build and present a bid sheet (報價對比表 / 比價單) as a compliant proposal presentation: every figure traceable to a carrier quotation with an issue date and expiry, non-guaranteed benefits shown as illustrations and never added to the sum-insured, and a written record of which carrier declined, loaded or referred.

Multi-Carrier Comparison (多間公司比價)

Lesson 07 · Insurance POS 101 · Hong Kong market · agent-facing

Learning Objectives

  • Explain, in a client meeting, why a competent Hong Kong agent must quote at least three carriers, and name the commercial and regulatory reasons (product shelf breadth, commission sustainability, non-solicited (NS) status, IA referral rules, and career-development agency consent) rather than just "price shopping".
  • Normalise (標準化) two or more carrier quotes that define the same benefit differently into one comparable basis: convert guarantee periods, benefit definitions, deductible design, occupation classes, waiting periods, reinstatement terms and premium-paying periods into a canonical unit (HK$ per HK$1m of normalised benefit, and total cost of ownership over the client's stated horizon), and show the arithmetic.
  • Build and present a bid sheet (報價對比表 / 比價單) as a compliant proposal presentation: every figure traceable to a carrier quotation with an issue date and expiry, non-guaranteed benefits shown as illustrations and never added to the sum-insured, and a written record of which carrier declined, loaded or referred.

Why One Carrier Is Not Enough

In practice: the agent opens a need-analysis interview in Mong Kok on a Tuesday evening and, by Wednesday afternoon, needs to be able to say "Carrier B declines on a smoker under 40, Carrier C will offer but with a 50% loading, Carrier A offers standard plus a 3-year premium waiver" — with all three answers traceable to a system screen, not to memory.

The single-carrier trap

An agent who represents one carrier can end up in one of two bad states. In the first state, the client needs a product the carrier's shelf does not contain — a high-deductible medical plan for a self-employed client with a $1.6m deductible and an annual out-of-pocket cap, a defined-benefit retirement product, an ILAS fund, an accident plan that covers amateur football on a Sunday league — and the only honest thing to say is "I cannot offer that here." In the second, and more insidious state, the agent is confident, fluent, and only knows one company's underwriting appetite, one company's benefit wording, and one company's complaint-handling culture. They have never been asked by a client "why not the other bank?" and cannot answer without bluffing. Both states end the agency relationship.

The structural reason is that Hong Kong life and general insurance is not a two-player market. The Insurance Authority (保險業監管局, IA) supervises more than 130 authorised insurers, of which roughly 60–70 write life or health business, and the agency force is tens of thousands of people. A general agent's office that carries six to twelve active agency agreements can see two to five genuinely comparable quotes for most cases. An agent with one agreement is, in effect, an in-house salesperson for that carrier and should be paid and evaluated like one.

The five reasons that actually move the number

1. Product shelf breadth. Carriers do not compete on a single product grid. As at 2025–2026 the HK market contains at least four distinct medical architectures — full reimbursement with an annual deductible, an annual limit with per-day room-and-board caps, a high-deductible plus "top-up rider" structure, and an out-of-pocket cap ("自付額封頂") design — plus separate accident-and-medical (BMX/A&H) legacy books, defined-benefit critical illness products, term life with decreasing or level sums assured, and savings/participating plans with non-guaranteed benefits. No single carrier's shelf is competitive across all of them for every client profile. A 34-year-old male consultant with a disclosed hypertension history is standard at one carrier, referred at another, and declined at a third. If you only hold one agency agreement, you inherit that carrier's appetite as your own.

2. Commission sustainability and carrier concentration risk. First-year commission for a Hong Kong regular-premium (期繳) protection plan commonly sits in the range of 40%–60% of the first-year premium for a strong shelf (CLIC-style high-commission shelves exist and are real, not hype), with renewal commission of 5%–15% thereafter and a possible 10%–30% bonus tier on persistency and/or case rate over the first 6–24 months. An agent whose entire income comes from one carrier is exposed to that carrier's product withdrawal, commission cuts, and channel re-organisation. Carrying three or more carriers that are genuinely non-correlated — ideally one with a strong protection shelf, one with a strong savings shelf, one with a strong accident/ILAS shelf — is basic income-risk management. Note the asymmetry: this is a diversification argument, not an arbitrage argument. Rebating off another agent's quoted plan and pocketing the difference is not a business model; it is a complaint waiting to happen.

3. Non-solicited (NS) and referral status. The IA's Best Practice Guide on Referral Schemes and Disclosure of Existing Relationships is the single most misunderstood document in agency practice. If the client already holds a policy of the same class with the carrier you represent, and you induce them to take out a new one and cancel or replace the old, you are dealing in a replacement (取代舊單). The old agent is entitled to handle the case as NS or declining. The permitted path for a new agent is to act on a referral basis: the existing agent must agree in writing to release the case, and typically shares the commission. Attempting to take a replacement case without the old agent's written consent exposes you to an IA complaint, a compliance investigation, and a commission clawback. This alone is enough to justify holding more than one agency agreement, because you cannot refer a case you do not represent.

4. Agency agreement and cross-selling consent. Every agency agreement grants you the right to sell that carrier's products to clients you approached. Selling Carrier D's product to a client whose details came from a Carrier A proposal requires A's written agency-consent or a genuine independent introduction. A multi-carrier agent can do this; a single-agreement agent who fabricates one is committing a fraud-adjacent act. The POS makes this visible: a consent flag is per-carrier, per-product-class, and time-bound.

5. Career and mobility protection. Statistics published by the IA on agency force turnover have consistently shown a high exit rate, and a common (and expensive) reason is dependency. An agent whose only product knowledge is one shelf re-uses it verbatim in their next role and misprices work. A multi-carrier agent's skill — normalisation, needs mapping, comparison presentation — is portable and interviewable.

What "N carriers" actually means in practice

The honest answer is depth on a shelf, breadth across shelves, not a data dump. A workable configuration:

Shelf roleTypical strengthWhy it is in the portfolio
Protection specialist (1–2 carriers)Term life, critical illness, medical with stable definitionsThe bread-and-butter cases; must have reliable underwriting and a known claims reputation
Savings / participating specialist (1–2)Whole life, limited-pay, endowment, non-guaranteed bonus plansOnly a minority of clients should be steered here; you need a non-guaranteed-benefits conversation you can actually have
Accident & hospital cash (1–2)BMX, hospital cash, low-cost supplementary coverFeeds the medical stack; very different margin profile
ILAS / linked general (1)Investment-linked assuranceNeeded to answer "what if the client wants market exposure inside a wrapper"; also a referral magnet
Narrow, opportunistic (0–2)A carrier with a genuinely better niche product or a temporary underwriting appetiteReviewed quarterly, not defaulted to

Practically: three to six active agreements, each one you have sold at least five cases from, each one you can quote without asking. A thirteenth agreement you have never submitted is a liability, not an asset.

The agency is the system of comparison

Here is the point that matters for the POS: the comparison is not a spreadsheet the agent maintains at home. It is a system function. The POS holds the normalised catalogue, runs the normalisation engine, caches carrier rates with issue and expiry dates, and emits a bid sheet with an audit trail. If the agent cannot reproduce the bid sheet six months later for a complaint or a mis-sale investigation, the comparison never happened in any meaningful sense.

flowchart LR
  A[Need analysis<br/>client profile + budget] --> B[Fan-out quote request<br/>carrier adapters]
  B --> C1[Carrier A<br/>rates + wording]
  B --> C2[Carrier B<br/>rates + wording]
  B --> C3[Carrier C<br/>rates + wording]
  C1 --> D[Normalisation engine]
  C2 --> D
  C3 --> D
  D --> E[Canonical quote objects<br/>guaranteed + non-guaranteed kept separate]
  E --> F[Bid sheet generator<br/>PDF + audit row]
  F --> G{Client decision}
  G -->|place| H[Application<br/>Lesson 08]
  G -->|decline/withdraw| I[Archive + no-recontact timer]
  D --> J[Rejection / referral learning set]
  J --> K[Profile appetite map]

The Normalisation Problem

In practice: the agent discovers that "HK$1,000,000 surgical" from Carrier A and "HK$1,000,000 surgical" from Carrier D are not the same promise, and that showing them side by side without adjustment is a misrepresentation risk, not just a bad comparison.

Why raw quotes cannot be placed side by side

Carriers write benefits in their own house language. Three things are routinely true at once:

  1. The names differ but the promises overlap only partly. "Major Illness Benefit" in one wording pays on diagnosis of any one of 50+ specified conditions. In another, "Critical Illness Benefit" lists 30 conditions but pays a lump sum only if the insured survives 30 days after diagnosis, or pays in tranches (e.g. 20% on diagnosis of the specified condition, 80% on confirmed heart attack). Same HK$1,000,000 headline, materially different expected claim.
  2. Deductible architecture differs. Carrier A's medical plan has a HK$0 deductible with an HK$8,000 annual limit and HK$850/day room-and-board capped at HK$6,800. Carrier D's has a HK$20,000 annual deductible with an HK$8m annual limit, unlimited room-and-board up to HK$12,000/day in a private room, and an out-of-pocket cap of HK$200,000/yr. Compare HK$7,900 vs HK$5,200 of annual premium and you have compared two different products for different people. A healthy 32-year-old with no admissions in four years should usually prefer the deductible design; a 52-year-old with two admissions and a chronic condition should usually prefer the low-limit design.
  3. Occupation and smoking treatment differ invisibly. Client is an office manager in an architecture firm. Carrier A classes him occupation class 2. Carrier D classes office-based architects class 4 and office managers class 2, and has no occupation class for self-employed consultants at all. Carrier B's medical is flat-rated by age and smoking with a 1.75 multiplier for smokers. If you normalise only premium you will present a plan the client cannot actually buy.

The canonical benefit unit

The normalisation target used in the POS is deliberately boring and deliberately absolute:

Normalised first-year premium (HK$) per HK$1,000,000 of normalised guaranteed sum insured, plus a stated total-cost-of-ownership figure over the client's chosen horizon.

Two rules make this defensible:

  • Guaranteed and non-guaranteed never mix. Non-guaranteed benefits (非保證利益 — terminal bonuses 終期紅利, reversionary bonuses 歸屬紅利, illustrated dividend options) are shown in a separate column, under an explicitly labelled illustration with the assumed investment return stated (illustrations are not projections; see the Insurance Authority Best Practice Guide on illustrations of non-guaranteed benefits). A normalised premium per unit of cover figure must use guaranteed sums assured only. If you fold illustrations into the denominator you are quoting a number that can evaporate.
  • Non-equivalent benefit sets are normalised by mapping, not by averaging. Where a benefit does not map 1:1, you either build an explicit equivalence multiplier from the carrier's published benefit wording, or you mark the cell NOT_EQUIVALENT and exclude that dimension from the scoring. Silently averaging incommensurable benefits is how mis-selling happens.

The normalisation engine

The engine takes a CarrierQuote (raw, carrier-shaped) and produces a NormalisedQuote (canonical) plus a NormalisationTrace that records every adjustment made, by whom, and why. The trace is what you hand to compliance when a client complains that the comparison was misleading.

// content is illustrative: shapes mirror the carrier-adapter boundary described above.

export type Money = { amount: number; currency: "HKD" };   // integer cents avoided; HKD only

export type GuaranteeTier = "GUARANTEED" | "NON_GUARANTEED";

export type Deduction = {
  scheme: "ANNUAL_DEDUCTIBLE" | "ANNUAL_LIMIT" | "FRANCHISE" | "CO_PAY_PERCENT" | "NONE";
  amount: Money;
  appliesPer: "PER_YEAR" | "PER_CLAIM" | "PER_ADMISSION";
};

export type RoomBenefit = {
  dailyLimit: Money;
  maxDaily: Money;                 // e.g. private-room cap
  coveredRoomClass: "SHARED" | "SEMI_PRIVATE" | "PRIVATE" | "DELUXE";
  daysPerAdmissionMax: number | null;
};

export type RenewalCommitment = "GUARANTEED_RENEWAL" | "GUARANTEED_PERMANENT" | "ANNUAL_REVIEWABLE";

/** Raw, carrier-shaped benefit as it arrives from the adapter. */
export interface CarrierBenefit {
  carrierId: string;
  productCode: string;
  productName: string;
  productVersion: string;          // MUST be pinned; never quote an unpinned version
  rateTableEffectiveFrom: string; // ISO date
  quotationNumber: string;
  quotedAt: string;               // ISO date-time
  expiresAt: string;              // ISO date-time — HK quotes commonly expire in 14–30 days
  coverage: {
    basicSumInsured: Money;
    tier: GuaranteeTier;
    termYears: number | null;     // null = to age 100/105/120
    premiumPayingYears: 1 | 5 | 10 | 15 | 20 | 25 | "LIFETIME";
    benefits: BenefitLine[];
  };
  medical?: {
    deduction: Deduction;
    room: RoomBenefit;
    outOfPocketCap: Money | null;
    preExistingExclusionApplied: boolean;
    renewalCommitment: RenewalCommitment;
  };
  risk: {
    ageNextBirthday: number;
    sex: "M" | "F";
    smoker: boolean;
    declaredConditions: string[];
    occupationClass: number | null;    // carrier-specific, NOT portable
    loadingPercent?: number;           // e.g. 50 = 50% loading
    referralRequired: boolean;
  };
  premium: {
    firstYear: Money;
    subsequentYearAnnual?: Money;      // level or stepped — steps must be itemised, never a single number
    premiumSchedule: Array<{
      policyYear: number;
      premium: Money;
    }>;
  };
  nonGuaranteedIllustration?: {
    assumedReturnPercent: number;
    projectedSumInsuredAtAge: number;
    ageProjected: number;
  };
}

export type BenefitLine =
  | { kind: "LUMP_SUM_DEATH"; trigger: string; paysWithinDaysOfTrigger: number }
  | { kind: "LUMP_SUM_DIAGNOSIS"; conditions: string[]; paysWithinDaysOfTrigger: number; staged?: number[] }
  | { kind: "HOSPITAL_CASH"; perDay: Money; maxDays: number; triggers: string[] }
  | { kind: "REIMBURSEMENT"; subLimit: Money; territory: "HK" | "HK_PLUS_OVERSEAS" };

/* ------------------------------------------------------------------ */
/* Step 1: equivalence mapping — the heart of normalisation            */
/* ------------------------------------------------------------------ */

export type EquivalenceMultiplier = {
  benefitKind: string;
  carrierA: { value: number; source: string };
  carrierB: { value: number; source: string };
  multiplier: number | null;      // null ⇒ NOT_EQUIVALENT, exclude from scoring
  rationale: string;
};

/**
 * Benefit equivalence is *authored by an underwriter*, not computed.
 * The engine refuses to run if an equivalence row is missing, because
 * an unauthored mapping is a guess and a guess in a client document is a
 * misrepresentation risk.
 */
export function requireEquivalence(
  rows: EquivalenceMultiplier[],
  benefitKind: string,
): EquivalenceMultiplier {
  const row = rows.find((r) => r.benefitKind === benefitKind);
  if (!row) throw new MissingEquivalenceError(benefitKind);
  if (row.multiplier === null) return row; // explicitly NOT_EQUIVALENT
  if (!Number.isFinite(row.multiplier) || row.multiplier <= 0) {
    throw new InvalidEquivalenceError(benefitKind);
  }
  return row;
}
export interface NormalisationAdjustment {
  field: string;
  rule: string;                    // stable rule id, e.g. "R-MED-DED-01"
  from: string | number | null;
  to: string | number | null;
  reason: string;
  authorisedBy: string;            // underwriter id — four-eyes, see Lesson 09
}

export interface NormalisationTrace {
  quoteId: string;
  engineVersion: string;
  adjustedAt: string;
  adjustments: NormalisationAdjustment[];
  excludedDimensions: string[];    // dimensions marked NOT_EQUIVALENT
  reviewedBySecondPairOfEyes: boolean;
}

export interface NormalisedQuote {
  quoteId: string;
  carrierId: string;
  productCode: string;
  productVersion: string;
  quotationNumber: string;
  validUntil: string;
  guaranteedSumInsured: Money;
  /** premium per HK$1m of normalised guaranteed cover — the headline comparable */
  premiumPerMillion: number;
  /** total of scheduled premiums across the client's horizon — for savings/participating */
  totalPremiumOverHorizon: Money;
  horizonYears: number;
  /** never summed with guaranteed figures; client must be told these may fall to zero */
  nonGuaranteedIllustrated: { assumedReturnPercent: number; projectedSumInsured: Money } | null;
  medical?: {
    annualDeductible: Money;
    outOfPocketCap: Money | null;
    effectiveRoomClass: string;
    renewalCommitment: RenewalCommitment;
  };
  underwritingOutcome: "STANDARD" | "STANDARD_WITH_EXCLUSION" | "LOADED" | "REFERRED" | "DECLINED";
  loadingPercent: number;
  trace: NormalisationTrace;
}

/* ------------------------------------------------------------------ */
/* Step 2: the normalisation itself                                     */
/* ------------------------------------------------------------------ */

const HK_PER_MILLION = 1_000_000;

export function normaliseQuote(
  raw: CarrierQuote,
  client: NormalisationContext,
): NormalisedQuote | { outcome: "DECLINED"; reason: string } {
  // 0. Refuse to normalise anything the compliance layer will not honour.
  assertQuotationIsLive(raw);            // expiresAt must be in the future
  assertVersionIsPinned(raw.productVersion);
  assertRateTableEffective(raw.rateTableEffectiveFrom, client.effectiveDate);

  const adjustments: NormalisationAdjustment[] = [];
  const excluded: string[] = [];

  // 1. Decide the horizon. The agent must state it; the engine must not infer it.
  const horizonYears = client.horizonYears;

  // 2. Drop non-guaranteed benefit lines from the comparable sum insured.
  const guaranteedLines = raw.coverage.benefits.filter((b) => b.payTier === "GUARANTEED");
  const droppedLines = raw.coverage.benefits.filter((b) => b.payTier === "NON_GUARANTEED");
  if (droppedLines.length > 0) {
    adjustments.push({
      field: "coverage.benefits",
      rule: "R-GT-01",
      from: raw.coverage.benefits.length,
      to: guaranteedLines.length,
      reason: `${droppedLines.length} non-guaranteed benefit line(s) excluded from the comparable sum insured`,
      authorisedBy: "SYSTEM",
    });
  }

  // 3. Normalise sum insured into a comparable *cover unit*.
  const coverUnit = normaliseCoverUnit(
    raw.coverage.basicSumInsured,
    raw.coverage.termYears,
    client,
    adjustments,
  );

  // 4. Normalise the premium to the horizon.
  const schedule = buildHorizonSchedule(raw.premium.premiumSchedule, horizonYears);
  const totalPremiumOverHorizon = sumMoney(schedule);

  // 5. Medical architecture normalisation.
  let medical: NormalisedQuote["medical"];
  if (raw.medical) {
    // Deductible designs are NOT equivalent; carry the flag rather than a number.
    if (raw.medical.deduction.scheme === "ANNUAL_LIMIT") {
      excluded.push("deductible_vs_annual_limit");
      adjustments.push({
        field: "medical.deduction",
        rule: "R-MED-DED-02",
        from: "ANNUAL_LIMIT",
        to: "NOT_EQUIVALENT",
        reason:
          "annual-limit medical architecture cannot be compared per HK$1m on the same basis as an " +
          "annual-deductible plan; compare out-of-pocket exposure instead",
        authorisedBy: client.underwriterId,
      });
    }
    medical = {
      annualDeductible:
        raw.medical.deduction.scheme === "ANNUAL_DEDUCTIBLE"
          ? raw.medical.deduction.amount
          : { amount: 0, currency: "HKD" },
      outOfPocketCap: raw.medical.outOfPocketCap,
      effectiveRoomClass: raw.medical.room.coveredRoomClass,
      renewalCommitment: raw.medical.renewalCommitment,
    };
  }

  // 6. Loading: a loaded quote is a *different product at a different price*.
  const loadingPercent = raw.risk.loadingPercent ?? 0;
  if (loadingPercent > 0) {
    adjustments.push({
      field: "premium.firstYear",
      rule: "R-UND-01",
      from: raw.premium.firstYear.amount,
      to: round2(raw.premium.firstYear.amount * (1 + loadingPercent / 100)),
      reason: `substandard loading ${loadingPercent}% applied by underwriting`,
      authorisedBy: raw.carrierId,
    });
  }

  // 7. Occupation class is carrier-specific and must never be compared numerically.
  //    We keep it for display but flag it.
  adjustments.push({
    field: "risk.occupationClass",
    rule: "R-OCC-01",
    from: raw.risk.occupationClass ?? null,
    to: raw.risk.occupationClass ?? null,
    reason:
      "occupation class is carrier-specific; class numbers are NOT comparable across carriers " +
      "and are excluded from scoring",
    authorisedBy: "SYSTEM",
  });
  excluded.push("occupation_class_number");

  const adjustedFirstYear = round2(
    raw.premium.firstYear.amount * (1 + loadingPercent / 100),
  );

  const premiumPerMillion =
    coverUnit.hkdEquivalent >= HK_PER_MILLION
      ? round2(
          (adjustedFirstYear * HK_PER_MILLION) / coverUnit.hkdEquivalent,
        )
      : null;

  const trace: NormalisationTrace = {
    quoteId: raw.quotationNumber,
    engineVersion: "norm-engine/2.4.0",
    adjustedAt: new Date().toISOString(),
    adjustments,
    excludedDimensions: excluded,
    reviewedBySecondPairOfEyes: false,
  };

  return {
    quoteId: raw.quotationNumber,
    carrierId: raw.carrierId,
    productCode: raw.productCode,
    productVersion: raw.productVersion,
    quotationNumber: raw.quotationNumber,
    validUntil: raw.expiresAt,
    guaranteedSumInsured: raw.coverage.basicSumInsured,
    premiumPerMillion,
    totalPremiumOverHorizon,
    horizonYears,
    nonGuaranteedIllustrated: raw.nonGuaranteedIllustration
      ? {
          assumedReturnPercent: raw.nonGuaranteedIllustration.assumedReturnPercent,
          projectedSumInsured: {
            amount: raw.nonGuaranteedIllustration.projectedSumInsuredAtAge,
            currency: "HKD",
          },
        }
      : null,
    medical,
    underwritingOutcome: raw.risk.referralRequired
      ? "REFERRED"
      : loadingPercent > 0
        ? "LOADED"
        : raw.medical?.preExistingExclusionApplied
          ? "STANDARD_WITH_EXCLUSION"
          : "STANDARD",
    loadingPercent,
    trace,
  };
}

Note two design decisions in that engine that are worth more than the code itself. First, normaliseQuote refuses to run on an expired quotation or an unpinned product version — a stale quote in a bid sheet is worse than no quote, because it invites a client to make a decision on information the agent knew was out of date. Second, every adjustment carries a rule id. When a client or a regulator asks "why is Carrier D showing HK$7,412 per million when its quote says HK$6,980?", the answer is a rule id lookup, not a recollection.

Worked normalisation: three medical quotes, one client

Client: Chan Siu Ming (陳小明), age 34 next birthday, non-smoker, no declared conditions, history of two day-cases in 2019 (appendicectomy, D&C), employed as an office manager in a property management company, budget HK$8,000–9,000/month for a family of three. Horizon chosen by the agent and stated on the sheet: 20 years, because the youngest child is 6 and the agent is recommending the parent's income protection to age 65 with the medical running alongside.

The key input the agent had to normalise first: "hospital cash of HK$1,000/day" does not mean the same thing at the three carriers, so the agent asked each carrier what triggers payment.

DimensionCarrier A · "MediCare Choice"Carrier D · "HealthGuard HD"Carrier F · "CarePlus Prime"
Versionv3.2 (eff. 2025-04-01)v5.0 (eff. 2025-07-15)v2.1 (eff. 2025-01-20)
ArchitectureFull reimbursement, HK$0 deductible, HK$8,000 annual limitHigh deductible: HK$20,000/yr, HK$8m limit, HK$200,000 out-of-pocket capDeductible HK$5,000/yr, HK$3m limit, HK$100,000 out-of-pocket cap
Room & boardHK$850/day, capped at HK$6,800/admissionHK$850/day standard / HK$1,600 private, capped HK$12,000/dayHK$800/day, cap HK$5,500/admission
Hospital cashHK$1,000/day, any admission ≥ 6 hoursHK$1,000/day, surgical admissions onlyHK$800/day, admission ≥ 24 hours or HK$500 for 6–23 hours
Day-case benefitHK$1,000 lump per day-casenilHK$600 lump per day-case
Guaranteed renewalGuaranteed, annual review of rates onlyGuaranteed renewal to age 120Guaranteed to 65, then annual reviewable
Declared conditions effectnone (below underwriting thresholds)nonenone
First-year premiumHK$7,880HK$5,240HK$6,410
20-year premium totalHK$157,600 (level)HK$104,800 + 5% steps by year 15HK$128,200 (level)

Now the normalisation the agent did, in the open, on the sheet:

  • Hospital cash is not equivalent. Carrier A pays HK$1,000 for any admission ≥ 6h; Carrier F pays HK$500 for the same 6–23h admission. For Chan, whose 2019 appendicectomy was a 1-night stay, the realised difference is real money. Carrier D pays nothing for the 1,400+ non-surgical admissions a 34-year-old man generates over 20 years. So hospital_cash is marked NOT_EQUIVALENT and moved to a narrative column, with the agent's stated position: "if you are in hospital for anything other than an operation, this plan pays you HK$1,000 a day and the other two do not."
  • Architecture is not equivalent. The agent refuses to score annual premium per million for the medical plans, because an HK$8,000 annual limit and an HK$200,000 out-of-pocket cap are not the same promise. Instead the sheet shows a modelled two-year family cost for each: Chan stays healthy for the first 18 months, then the father has a HK$260,000 emergency appendicectomy in a private room (HK$2 days private @1,600 + surgeon + anaesthesia + drugs + HK$5,000 deductible, capped at HK$200,000 out-of-pocket).
    • Carrier A: pays HK$2,600 (2 days cash @1,000 + 1 day-case 0) → out-of-pocket HK$257,400 (annual limit exhausted immediately).
    • Carrier D: pays HK$3,200 → out-of-pocket HK$60,000 (HK$5,000 deductible + HK$50,000 before the HK$200k annual out-of-pocket cap is reached; total household exposure capped at HK$205,000/yr).
    • Carrier F: pays HK$1,600 + HK$600 day-case = HK$2,200 → out-of-pocket HK$70,600 (HK$5,000 deductible + HK$65,600).
  • The agent's recommendation, written on the sheet: Carrier D on architecture, Carrier A on cash flow, and a 30-day decision window. The honest summary is that Carrier A's premium is 50% higher and buys Chan a guaranteed, unlimited-in-time annual-limit benefit that suits a young healthy family with a large savings buffer; Carrier D's deductible design suits a family that would be financially devastated by a HK$260,000 bill. The bid sheet says both, in those words.

Rules that make the engine defensible

Rule idRuleWhy it exists
R-GT-01Non-guaranteed benefit lines never enter the comparable sum insuredIllustrated benefits may fall to zero; comparing them to guaranteed cover misleads
R-GT-02Illustrations must state assumed return %, age, and be labelled not-a-projectionIA best practice on illustrations; also stops "you'll get $3m at 65" claims
R-MED-DED-02Annual-limit vs annual-deductible architectures are NOT_EQUIVALENTForces a modelled-out-of-pocket comparison instead of a fake per-million number
R-MED-DED-03Out-of-pocket cap is reported as a household figure, never a policy figureClients read "HK$200,000 cap" as "I only ever lose HK$200,000"; the real statement is per insured per policy year
R-OCC-01Occupation class numbers excluded from scoringClass 2 at one carrier is class 4 at another
R-UND-01Loading applied to first-year premium and shown on the sheetA loaded quote is a different price; hiding it creates a referral dispute at claim time
R-QTE-01Quotation expiry hard-stops bid sheet generationStale pricing in a client document is a mis-sale vector
R-VER-01Product version pinned to rate-table effective dateProduct wording and rates change; an unpinned quote cannot be reproduced
R-NS-01Existing-policy overlap triggers NS/referral determination before presentationIA best practice on referral schemes; prevents a complaint that cannot be answered
R-CMP-01Minimum 3 carriers attempted for a comparison case; fewer requires a recorded reasonOne-carrier comparisons are the signature of agent-captured business
-- The bid sheet must be reconstructable years later. Store the whole thing.
CREATE TABLE bid_sheet (
  bid_sheet_id      TEXT PRIMARY KEY,
  client_id         TEXT NOT NULL REFERENCES client(client_id),
  agent_id          TEXT NOT NULL REFERENCES agent(agent_id),
  created_at        TEXT NOT NULL,
  horizon_years     INTEGER NOT NULL,
  assumption_notes  TEXT NOT NULL,
  engine_version    TEXT NOT NULL,
  status            TEXT NOT NULL CHECK (status IN
                      ('DRAFT','ISSUED','VIEWED','ACCEPTED','DECLINED','WITHDRAWN','EXPIRED')),
  selected_quote_id TEXT REFERENCES carrier_quote(quotation_number)
);

CREATE TABLE carrier_quote (
  quotation_number   TEXT PRIMARY KEY,
  carrier_id         TEXT NOT NULL,
  product_code       TEXT NOT NULL,
  product_version    TEXT NOT NULL,
  rate_table_effective_from TEXT NOT NULL,
  quoted_at          TEXT NOT NULL,
  expires_at         TEXT NOT NULL,
  outcome            TEXT NOT NULL CHECK (outcome IN
                       ('STANDARD','STANDARD_WITH_EXCLUSION','LOADED','REFERRED','DECLINED','WITHDRAWN')),
  outcome_reason     TEXT,
  raw_payload_json   TEXT NOT NULL,   -- verbatim carrier response, never mutated
  adapter_version    TEXT NOT NULL
);

CREATE TABLE normalisation_trace (
  trace_id           TEXT PRIMARY KEY,
  bid_sheet_id       TEXT NOT NULL REFERENCES bid_sheet(bid_sheet_id),
  quote_id           TEXT NOT NULL REFERENCES carrier_quote(quotation_number),
  engine_version     TEXT NOT NULL,
  adjustments_json   TEXT NOT NULL,
  excluded_dimensions_json TEXT NOT NULL,
  second_review_by   TEXT REFERENCES agent(agent_id),
  second_reviewed_at TEXT
);

CREATE TABLE bid_sheet_line (
  bid_sheet_id     TEXT NOT NULL REFERENCES bid_sheet(bid_sheet_id),
  quote_id         TEXT NOT NULL REFERENCES carrier_quote(quotation_number),
  display_rank     INTEGER NOT NULL,
  normalised_json  TEXT NOT NULL,
  presentation_note TEXT,            -- the agent's own words, shown verbatim to the client
  PRIMARY KEY (bid_sheet_id, quote_id)
);

-- Every carrier you approached, including the one that said no.
CREATE TABLE comparison_attempt (
  bid_sheet_id    TEXT NOT NULL REFERENCES bid_sheet(bid_sheet_id),
  carrier_id      TEXT NOT NULL,
  attempted_at    TEXT NOT NULL,
  declined_to_quote INTEGER NOT NULL DEFAULT 0,
  decline_reason  TEXT,
  ns_check_result TEXT NOT NULL CHECK (ns_check_result IN
                     ('NOT_APPLICABLE','NO_OVERLAP','NS_EXISTING_AGENT_HELD','REFERRED_WITH_CONSENT','BLOCKED')),
  PRIMARY KEY (bid_sheet_id, carrier_id)
);

CREATE INDEX ix_bid_sheet_client ON bid_sheet(client_id, created_at DESC);
CREATE INDEX ix_trace_quote ON normalisation_trace(quote_id);

Building a Bid Sheet

In practice: the agent opens a new bid sheet in the POS, fans the quote out to the four carriers they actively represent, waits for three responses, and presses Generate — which produces a paginated PDF that can be emailed to the client before the meeting ends, with every figure hyperlinked to its source quotation.

What a bid sheet is, precisely

A bid sheet (比價單 / 建議書對比表) is a decision document, not a rate table. It has five jobs and each one has a hard requirement:

JobContent requirementFailure mode if you skip it
Show the client there was a genuine choiceMinimum 3 carriers attempted, including declines and referralsClient assumes you only have one product; no leverage, no trust, no referral from their friends
Make the comparison honestNormalised basis stated in one line at the top of the page, non-equivalent dimensions flaggedMis-sale complaint; IA investigation; commission clawback
State the price exactlyFirst-year premium, full subsequent-year schedule (never "about HK$700/month thereafter"), and the horizon totalClient's first-year claims experience differs from expectation → complaint; the commonest complaint in the market
State what is not guaranteedSeparate block, labelled, with assumed return % and the words "may be reduced or not paid at all"Mis-selling on non-guaranteed benefits; the most heavily penalised area of agency conduct
Carry the audit trailQuote numbers, product versions, issue/expiry dates, agent name and IA registration-relevant detailsSix months later you cannot prove what you said

The one-line basis statement is the piece most often missing and the piece most worth memorising:

"All figures below are for guaranteed benefits only, on a per HK$1,000,000 of sum insured basis, over a 20-year horizon assuming level premiums. Benefits marked † are not directly comparable between carriers and are explained in the note. Non-guaranteed benefits are shown separately in Appendix B and are not included in any comparison here."

The page-by-page layout the POS renders

BID SHEET — Prepared 2026-03-11 14:22 HKT
Client: CHAN SIU MING (陳小明)   Agent: WONG KA HO (FSIR no. on file)
Client profile used for rating: age 34 (next birthday), male, non-smoker,
  no declared conditions, occupation "office manager (property mgmt)"
Horizon chosen by agent: 20 years — reason: youngest child is 6;
  income protection recommended to age 65
Carriers approached: 4        Live comparable quotes: 2 + 1 NOT_EQUIVALENT on 2 dimensions

PAGE 1 — Summary comparison
  column: A | D | F
  1-year premium            7,880   5,240   6,410
  20-year premium total   157,600  104,800  128,200
  premium per HK$1m       7,880    5,240   6,410
  sum insured            1,000,000 (level)  1,000,000 (level)  1,000,000 (level)
  underwriting            standard  standard  standard
  PES applied             no        no        no
  renewal                 guaranteed guaranteed guaranteed to 65
  quote ref               Q-77120   Q-90344   Q-51288
  expires                 2026-04-01 2026-04-02 2026-03-28
  † modelled out-of-pocket, HK$260k private surgical episode:
                           257,400   60,000   70,600

PAGE 2 — Medical architecture in words (the "read this before you decide")
  agent-authored, max 250 words, no superlatives, no competitor disparagement

PAGE 3 — Benefit-by-benefit detail with NOT_EQUIVALENT flags and rules applied

PAGE 4 — Underwriting outcomes including declines and referrals, with reasons
  Carrier B — DECLINED: smoker/applicant age band + declared hypertension (see Lesson 06)
  Carrier C — REFERRED to underwriter; response expected 2026-03-14, not in this sheet
  Carrier E — DECLINED TO QUOTE: occupation class not defined for self-employed
    consultants in this plan; the client is employed, so reclassified as office manager
    on 2026-03-11 and re-quoted — see comparison_attempt row

PAGE 5 — APPENDIX B: non-guaranteed benefits, clearly boxed, grey background,
  stating assumed return 4.0% p.a. for illustration only, not a projection,
  may be reduced or not paid at all

PAGE 6 — Declarations the client signs: I have read the benefit definitions; I
  understand the non-equivalent dimensions flagged †; I understand the
  premiums shown assume level premiums and are subject to the schedule on page 1;
  I have been told which carriers declined and why.

That last block matters. A signed acknowledgement that the client saw the flags is worth a great deal in a complaint investigation, and it costs one checkbox in the POS.

The generator: turning normalised quotes into the sheet

export interface BidSheetContext {
  client: ClientProfile;
  agent: AgentProfile;
  horizonYears: number;
  horizonReason: string;
  attempts: ComparisonAttempt[];       // includes declines
  quotes: CarrierQuote[];
  equivalences: EquivalenceMultiplier[];
  narrative: {
    architectureNote: string;           // agent-authored, length-checked
    benefitNotes: Record<string, string>;
  };
}

export interface BidSheet {
  bidSheetId: string;
  generatedAt: string;
  basisStatement: string;
  rows: ComparisonRow[];
  attempts: ComparisonAttempt[];
  appendixB: NonGuaranteedAppendix;
  pdfPath: string;
  sha256: string;                       // tamper-evidence for the audit record
  clientAcknowledgementRequired: boolean;
}

export interface ComparisonRow {
  label: string;
  unit: string;
  values: Array<{ carrierId: string; value: string | number | null; comparable: boolean }>;
  notEquivalent?: boolean;
  footnote?: string;
}

export function generateBidSheet(ctx: BidSheetContext, quotes: NormalisedQuote[]): BidSheet {
  // 1. Gate: a comparison case needs at least three carriers *attempted*.
  if (ctx.attempts.length < 3) {
    throw new InsufficientComparisonError(
      `only ${ctx.attempts.length} carrier(s) attempted; rule R-CMP-01 requires 3`,
    );
  }
  // A recorded reason is not an escape hatch if the reason is empty.
  for (const a of ctx.attempts) {
    if (a.declinedToQuote && !a.declineReason?.trim()) {
      throw new MissingDeclineReasonError(a.carrierId);
    }
  }

  // 2. Gate: no live quote may be expired, and NS status must be resolved
  //    for every carrier before anything is shown to the client.
  for (const a of ctx.attempts) {
    if (a.nsCheckResult === "BLOCKED") {
      throw new BlockedByNsCheckError(a.carrierId);
    }
    if (a.nsCheckResult === "NS_EXISTING_AGENT_HELD") {
      throw new ReferralConsentRequiredError(a.carrierId);
    }
  }

  const rows: ComparisonRow[] = [
    row("First-year premium", "HKD", quotes.map((q) => ({
      carrierId: q.carrierId,
      value: q.trace.adjustments.find((x) => x.rule === "R-UND-01")?.to ?? 0,
      comparable: true,
    }))),
    row("Premium per HK$1m of guaranteed cover", "HKD", quotes.map((q) => ({
      carrierId: q.carrierId,
      value: q.premiumPerMillion,
      comparable: true,
    }))),
    row(`Total premium over ${ctx.horizonYears} years`, "HKD", quotes.map((q) => ({
      carrierId: q.carrierId,
      value: q.totalPremiumOverHorizon.amount,
      comparable: true,
    }))),
    row("Underwriting outcome", "—", quotes.map((q) => ({
      carrierId: q.carrierId,
      value: q.underwritingOutcome,
      comparable: true,
    }))),
    row("Renewal commitment", "—", quotes.map((q) => ({
      carrierId: q.carrierId,
      value: q.medical?.renewalCommitment ?? "n/a",
      comparable: true,
    }))),
    // Non-comparable dimension: carried across, explicitly flagged, never scored.
    {
      label: "Modelled out-of-pocket for a HK$260,000 private surgical episode",
      unit: "HKD",
      notEquivalent: true,
      footnote:
        "† Not comparable on premium alone — annual-limit and annual-deductible architectures " +
        "protect different financial profiles. Modelled by the agent from published benefit limits.",
      values: ctx.quotes.map((q) => ({
        carrierId: q.carrierId,
        value: q.modelledOutOfPocket?.hkd ?? null,
        comparable: false,
      })),
    },
    // Second non-comparable dimension.
    {
      label: "Hospital cash trigger",
      unit: "—",
      notEquivalent: true,
      footnote:
        "† Carriers define the qualifying admission differently (≥6h vs surgical only vs ≥24h). " +
        "For a client with two admissions under 24h in the last five years this is decisive.",
      values: ctx.quotes.map((q) => ({
        carrierId: q.carrierId,
        value: q.hospitalCashTrigger ?? null,
        comparable: false,
      })),
    },
  ];

  const basis = buildBasisStatement(ctx.horizonYears, ctx.horizonReason);

  const sheet: BidSheet = {
    bidSheetId: newId("BDS"),
    generatedAt: new Date().toISOString(),
    basisStatement: basis,
    rows,
    attempts: ctx.attempts,
    appendixB: buildNonGuaranteedAppendix(quotes),
    pdfPath: `bid_sheets/${newId("BDS")}.pdf`,
    sha256: "",                      // filled after render
    clientAcknowledgementRequired: true,
  };
  return sheet;
}

function buildBasisStatement(horizon: number, reason: string): string {
  return (
    `All figures are for guaranteed benefits only, on a per HK$1,000,000 of sum insured basis, ` +
    `over a ${horizon}-year horizon assuming level premiums. Dimensions marked † are not ` +
    `directly comparable between carriers and are explained in the note. Non-guaranteed benefits ` +
    `appear in Appendix B, are based on an assumed return stated there, and are not included in ` +
    `any comparison. Horizon reason: ${reason}.`
  );
}

/** Appendix B is the most-litigated page in the file. It never shares a table with guaranteed figures. */
function buildNonGuaranteedAppendix(quotes: NormalisedQuote[]): NonGuaranteedAppendix {
  return {
    disclaimer:
      "The values shown are illustrations based on the assumed return stated. They are not " +
      "projections, not guarantees, and the benefits may be reduced or not paid at all. " +
      "Non-guaranteed benefits are not part of the sum insured and do not increase the guaranteed " +
      "death benefit shown in the main comparison.",
    entries: quotes
      .filter((q) => q.nonGuaranteedIllustrated)
      .map((q) => ({
        carrierId: q.carrierId,
        assumedReturnPercent: q.nonGuaranteedIllustrated!.assumedReturnPercent,
        projectedAtAge: q.nonGuaranteedIllustrated!.ageProjected,
        projectedSumInsured: q.nonGuaranteedIllustrated!.projectedSumInsured.amount,
      })),
  };
}

Two gates in that generator are worth calling out to an agent directly. First, R-CMP-01 blocks the PDF from generating if fewer than three carriers were attempted — including cases where you did try and were declined to quote. Second, the NS gate blocks the PDF if the client holds a same-class policy with a carrier whose existing agent has not consented in writing. In both cases the POS shows a red banner naming the exact missing item, not a generic error. That is deliberate: the agent should be prevented from producing a document, not from producing a document and hoping.

The signed acknowledgement, as data

{
  "bidSheetId": "BDS-2026-00417",
  "client": { "id": "C-88213", "nameZh": "陳小明", "nameEn": "CHAN SIU MING" },
  "issuedAt": "2026-03-11T14:22:31+08:00",
  "sha256": "9f2c7b1e4a8d0c3f5b6e2a19d4c7b8e0f3a6d9c2b5e8f1a4d7c0b3e6f9a2d5c8",
  "acknowledgements": [
    { "id": "ACK-1", "text": "I have read the benefit definitions for each quoted plan.", "accepted": true },
    { "id": "ACK-2", "text": "I understand that the dimensions marked † are not directly comparable and have read the explanation.", "accepted": true },
    { "id": "ACK-3", "text": "I understand premiums assume a level schedule as stated on page 1 and are subject to the schedule shown there.", "accepted": true },
    { "id": "ACK-4", "text": "I have been told which carriers declined or referred my case and the reasons given.", "accepted": true },
    { "id": "ACK-5", "text": "I understand the non-guaranteed benefits in Appendix B may be reduced or not paid at all.", "accepted": true },
    { "id": "ACK-6", "text": "I have had the opportunity to ask questions and have received satisfactory answers.", "accepted": true }
  ],
  "signedAt": "2026-03-11T16:04:09+08:00",
  "signature": {
    "method": "E_SIGNATURE",
    "methodDetail": "adopted handwritten signature on tablet, drawn in the client's own hand",
    "signedBy": "陳小明",
    "ipOrDevice": "ipad-9f3c…",
    "timestampedBy": "signsvc/1.8.2"
  },
  "agentAttestation": {
    "agentId": "AG-2210",
    "attestedAt": "2026-03-11T16:05:40+08:00",
    "carriersPresented": ["CARR_A", "CARR_D", "CARR_F"],
    "carriersDeclinedOrReferred": [
      { "carrierId": "CARR_B", "result": "DECLINED", "reason": "smoker/applicant band + declared hypertension" },
      { "carrierId": "CARR_C", "result": "REFERRED", "eta": "2026-03-14" },
      { "carrierId": "CARR_E", "result": "DECLINED_TO_QUOTE", "reason": "occupation class undefined; reclassified and re-quoted" }
    ]
  }
}

Presenting the Comparison

In practice: the agent does not email the PDF and wait. The agent prints two copies, sits the client down, turns the sheet sideways, and spends twenty minutes on page 1 — starting with what the client cannot have, not with what they can.

The presentation order that works

  1. Start with the exclusions. "Before you look at any premium, here is what each plan does not cover." Clients who understand the exclusions first do not later feel misled. This is also the single most effective compliance behaviour available to an agent.
  2. Show the client that carriers disagreed. Read out the declines and the referral. Naming Carrier B's decline out loud does three things: it demonstrates you worked, it demonstrates the client's profile is not a universal one, and it makes the eventual "yes, Carrier D" feel like the client's decision rather than your only option.
  3. Present the non-comparable dimensions as the decision. If two architectures are not comparable, the decision is which financial profile the client has, not which premium is smaller. Say it in those words: "These two are not better and worse. They are built for a family with savings and a family without."
  4. Give a recommendation with the runner-up. "My recommendation is D, and if you want a plan that does not have a deductible at all, it is A, and it costs you HK$2,640 more a month." Naming the runner-up is the cheapest credibility available.
  5. State the horizon and the reason out loud. "I have costed this over twenty years because your youngest is six. If you think the need changes at ten, say so and I will re-run it."
  6. Close on the deadline, honestly. Quotation numbers expire. "These prices hold until the 1st of April. After that I have to re-quote and I cannot promise the same number." A manufactured deadline is both obvious and, if the client notices, fatal to trust.

Language discipline

Do not saySay insteadWhy
"This plan will cover your cancer treatment.""This plan pays your claim up to the stated limit for the covered events listed on page 3, subject to the exclusions on page 4.""Will cover" implies a promise about claims outcome
"You'll get $3 million by 65.""If the bonuses declared average the 4% assumed, the illustration shows $3m. If they average zero, you get the guaranteed $1m."Non-guaranteed benefits; the most penalised area of agency conduct
"Carrier A is bad value.""Carrier A's premium is 50% higher and it removes your deductible. Whether that is worth it depends on your savings."Disparagement of a competitor; also just wrong about the client
"That's the market rate.""That is the rate on Carrier D's current version for a 34-year-old non-smoker. I have two other quotes on this sheet.""Market rate" implies you checked all 130 insurers
"It won't lapse.""It lapses if premium is not paid for three months from the due date. Free-look is 21 days — see Lesson 10."Lapse statements are a persistent source of complaint
"This covers pre-existing conditions.""Carrier A declined to apply an exclusion to your two 2019 admissions because they were below their threshold. Carrier D did the same. Here is the wording."PES is applied at underwriting, never assumed by the agent

What goes in the file afterwards

The POS writes, automatically on issuance: the bid sheet PDF with its hash, the acknowledgement, the client view event (opened, page, timestamp), the presentation notes, and the no-recontact timer if declined. If the client asks for 45 days to think, that is a scheduling fact, not a suspension of the file.

export function onClientDecision(input: {
  bidSheetId: string;
  decision: "ACCEPT" | "DECLINE" | "DEFER";
  selectedQuoteId?: string;
  deferUntilDays?: number;
}): Outcome {
  const sheet = loadBidSheet(input.bidSheetId);
  if (sheet.status === "EXPIRED") throw new StaleDecisionError(sheet.bidSheetId);

  switch (input.decision) {
    case "ACCEPT": {
      if (!input.selectedQuoteId) throw new MissingSelectionError();
      // Acceptance is an instruction to start Lesson 08, not an end state.
      const app = createApplicationFromQuote(input.selectedQuoteId, sheet);
      markOtherQuotesSuperseded(sheet, input.selectedQuoteId);
      return { status: "ACCEPTED", next: `application/${app.applicationId}`, freezeQuotes: true };
    }
    case "DEFER": {
      const days = Math.min(Math.max(input.deferUntilDays ?? 45, 1), 90);
      scheduleReQuotation(sheet, days);              // before expiry, not after
      armNoContactTimer(sheet.clientId, days * 2);   // polite, auditable
      return { status: "DEFERRED", reQuoteOn: addDays(new Date(), days) };
    }
    case "DECLINE": {
      closeAllQuotes(sheet);
      // Do not train "no" as "no forever". One re-contact is permitted in 24 months.
      armNoContactTimer(sheet.clientId, 730, reason: "DECLINED_COMPARISON");
      recordRefusalReason(sheet, await askAgentWhy());   // required, free text, min 20 chars
      return { status: "DECLINED" };
    }
  }
}

armNoContactTimer(sheet.clientId, 730) is the quiet compliance feature agents use without noticing. If the POS did not stop you contacting a client who said no, the Compliance (合規) module would flag 20+ contacts to a declined client as a red line — and it would be right to.

Switching Cost

In practice: a client who asks "what if I move my policy to another company?" is asking a question about friction, not about price. The agent's job is to name the friction accurately — and that means being honest that the friction is real.

What switching actually costs

Switching is not a free option, and pretending otherwise is how agencies lose their reputation. The real costs:

Cost componentRealistic value in HKNotes
Surrender value shortfall5%–35% of the policy's cash valueThe dominant cost. Surrender value is usually well below the sum assured and well below the accumulated premiums
New underwritingFree for standard cases; can decline, load, or apply PESHealth disclosures must be repeated in full — the agent's ageing file does not substitute for a medical report if the carrier asks
New premium holidayNone by defaultA new plan starts at premium due
Re-underwriting age bandAlmost always at a worse bandYou are older than when you bought; a 40 → 50 jump can add 30%–60% to a medical premium
Disruption of claims continuityTypically resets nothing for medical, but resets 2-year waiting periods for new accident/CI ridersA new CI plan starts a fresh 30–90 day waiting period and a fresh survival period
Time and paperwork2–6 hours across 2–4 weeksNew medical records, financial evidence, signatures, delivery
Loss of no-claim or loyalty discountsVariesSome carriers give a loyalty discount at age 60/65 that resets at a new carrier
Agent relationshipNot a cost — an assetThe current agent's servicing record and knowledge of the case history matter more than most clients realise

The calculation an agent should be able to do in the meeting

Switch if:  surrenderValue_A  +  newPolicyFirstYearPremium_A  <  surrenderValue_B  +  newPolicyFirstYearPremium_B
Since surrender value today is identical for A and B (same policy), the test collapses to:
            newPolicyPremium_A  <  newPolicyPremium_B   by more than the transaction cost of doing it.

Example: existing policy, cash value at 2026-03-11 = HK$412,000.
  Option 1 — keep:   pay HK$0 surrender cost, premium HK$38,400/yr continuing.
  Option 2 — surrender + replace: surrender at 88% of value → HK$412,000 - tax (HK$0, insurance
    surrender is capital gains-exempt; the treaty is generous) = HK$362,560 received,
    lose HK$49,440. New plan premium HK$44,000/yr → saves HK$5,600/yr, break-even in 8.8 years.
  If the client's horizon is under 8.8 years: DO NOT SWITCH. Say so.

The discipline this demands: compute the break-even and show it. "It would take you just under nine years to get your money back, so if you're fairly sure you'll be gone or insured elsewhere in five years, keeping the policy is the better decision" is a sentence that wins more clients than any product pitch, because it is the first time anyone has talked to them like a rational adult.

The case where switching IS right

  • The original agent stopped responding, or there is a service failure the carrier cannot resolve. Cost of the friction is worth paying.
  • The plan has a decreasing sum assured and the client's need has risen.
  • The plan's guaranteed benefits were reduced, or the carrier withdrew the product and the client was moved onto an uncompetitive successor plan.
  • The original plan was participating with a persistently low declared bonus and the client has a strong reason to think the guarantee mechanism is not working.
  • The original agent held the policy on a replacement basis from a competitor within the first two years, and the client now wants to deal with the competitor's agent.

Replacement and NS: the case that ends careers badly

An agent persuades a client to replace a policy held with another carrier. Eighteen months later the client complains to that carrier. The carrier's compliance department produces: your agent's call notes, the recorded pre-call statement that the client had "a policy I am not happy with," the comparison sheet you prepared, and the surrender form signed at the counter.

The path that avoids this is entirely procedural and entirely boring: the incumbent agent is contacted in writing, the case is registered as NS (non-solicited) or referral with consent, the commission sharing is agreed and recorded, and the replacement is disclosed to the incumbent. If the incumbent refuses consent, the honest answer to the client is "I can look at other options for you, but I cannot take over your existing policy from the agent who holds it, and neither can anyone else without that agent's agreement."

stateDiagram-v2
  [*] --> ExistingPolicyHeld
  ExistingPolicyHeld --> NSAssessment: client mentions existing policy
  NSAssessment --> OverlapNone: no same-class policy found
  NSAssessment --> IncumbentAgentHeld: same-class policy at another agency
  NSAssessment --> SameAgencyHeld: same-class policy in your own book

  IncumbentAgentHeld --> ConsentRequested: client wants to switch
  IncumbentAgentHeld --> DeclinedToSwitch: incumbent refuses consent
  ConsentRequested --> ReferralWithConsent: written consent + commission share agreed
  ConsentRequested --> Expired: no response in 10 business days ⇒ treat as refusal

  ReferralWithConsent --> BreakevenComputed: surrender + new premium calculated
  DeclinedToSwitch --> AdviseNoSwitch: client keeps existing policy
  Expired --> AdviseNoSwitch
  OverlapNone --> BreakevenComputed
  SameAgencyHeld --> BreakevenComputed

  BreakevenComputed --> SwitchNotRecommended: horizon < break-even
  BreakevenComputed --> SwitchRecommended: horizon > break-even
  SwitchRecommended --> Reunderwrite: new medical disclosure + evidence
  SwitchNotRecommended --> DocumentedNo: advice recorded in CRM

  Reunderwrite --> Declined: new application declined or loaded
  Reunderwrite --> Standard: new application issued
  Declined --> KeepExisting: outcome documented for the client
  Standard --> ReplacementDisclosed: incumbent notified in writing
  ReplacementDisclosed --> Closed
  AdviseNoSwitch --> Closed
  DocumentedNo --> Closed
  KeepExisting --> Closed

Handling Objections

In practice: the POS surfaces the top six objections as a one-tap coaching panel with the evidence attached, so the agent walks into the meeting knowing the published wording rather than improvising.

Objection 1 — "You are cheaper everywhere else"

The truthful response is that you are not, and that you could not be. Only a handful of general agents hold every shelf: ILAS, every medical architecture, every accident variant, offshore legacy books, and the specialist health products are distributed through different channels. Where the client is right — for a specific, genuinely cheaper, single-carrier product — the right move is to say so and refer them, which builds more business than a defensive answer. Where they are quoting an agent who is preparing to lapse the policy to commission, the answer is the break-even arithmetic above.

Objection 2 — "I want to think about it"

Do not discount it and do not attack it. The useful questions are: what specifically do you want to think about — the budget, the coverage, the carrier, or whether to insure at all? Then answer only that. If it is budget, re-run the horizon. If it is carrier, go back to the referral. If it is whether to insure at all, that is a need conversation you should have had earlier, and it is better to have it now.

Objection 3 — "Carrier A is cheaper by HK$200 a month"

Correct, and here is why, and here is what it buys. This objection is really a statement that the client has read the per-million figure and stopped. The whole point of normalisation is that the cheap number is cheap because of something. Name it. If the agent cannot name it, the agent should not have shown the number.

Objection 4 — "I already have medical cover through my employer"

Often true, often much thinner than the client believes. The check is three questions: what is the annual limit, what is the pre-existing exclusion situation for the employee, and what happens if the client leaves the job. Employer group medical is usually an indemnity plan with a per-admission cap, an annual limit, a defined-network or preferred-provider panel, and a pre-existing condition exclusion that does not apply to the employee but does to dependants. The POS should carry the employer-benefit capture in the client profile precisely so this objection becomes a two-minute question rather than a lost case.

Objection 5 — "Is my information safe?"

The answer is specific, not reassuring: what is collected, where it is stored (Hong Kong), who can see it at the carrier (the underwriter and the claims team, not the whole company), what leaves the building (name, DOB, HKID/contact, medical disclosure, banking — and only what the application requires), how long it is kept (per the record-keeping rule in Lesson 09, five years after the relationship ends or the policy ends, whichever is later), and how to ask for a copy or a correction. Vague assurances are worse than no answer.

Objection 6 — "What if I die and the family don't get it?"

This is a nomination conversation (指定受益人), which is Lesson 10, and it is worth having before the policy is written rather than at claim time. The agent should establish the nominated beneficiary, the trustee/guardian question for minor children, the "nominate a person to act for the beneficiaries" field, and whether the beneficiary is aware. A policy with no nomination and no guardian clause is a common source of family dispute that the insurance was supposed to prevent.

export const objectionPlaybook: Record<string, {
  trigger: RegExp;
  evidenceRef: string;                    // the POS attaches the published document
  agentScript: string;
  neverSay: string[];
}> = {
  CHEAPER_ELSEWHERE: {
    trigger: /平啲|便宜啲|貴咗|competitor|cheaper/i,
    evidenceRef: "IA_BPG_referral_disclosure; carrier_shelf_matrix",
    agentScript:
      "I am not going to tell you I am the cheapest — I would be lying, because I do not carry " +
      "every product. Here is what I do carry, here is the sheet, and if you want me to ask " +
      "whether your existing plan is worth keeping, I will do that before we decide anything new.",
    neverSay: ["nobody can beat me", "my price is guaranteed market-wide"],
  },
  NEED_TO_THINK: {
    trigger: /諗下|考慮下|think about|not sure/i,
    evidenceRef: "needs_analysis_recap",
    agentScript:
      "Of course — that is the right thing to do. So that my follow-up is useful rather than a " +
      "nagging call, can I ask: is it the premium, the coverage, the company, or whether to do " +
      "this at all? Those four need four different conversations.",
    neverSay: ["the price will go up tomorrow", "this offer expires tonight"],
  },
  CHEAPER_CARRIER: {
    trigger: /per million|per million|每月平|平啲/i,
    evidenceRef: "bid_sheet_page1 + normalisation_trace R-MED-DED-02",
    agentScript:
      "Yes, on the headline number they are cheaper, and the reason is on page 2 — they use an " +
      "annual limit instead of a deductible. That is a real trade, not a trick. Let me show you " +
      "what happens to your family in the worst realistic year under each one.",
    neverSay: ["they are not really cheaper", "that is misleading advertising"],
  },
  EMPLOYER_COVER: {
    trigger: /公司|僱主|employer|group medical|醫療計劃/i,
    evidenceRef: "employer_benefit_capture",
    agentScript:
      "Let's check three things on your company plan: the annual limit, whether dependants get " +
      "the pre-existing exclusion waived, and what happens to you when you leave. Two of those " +
      "three usually change the answer.",
    neverSay: ["your company cover is no good"],
  },
  DATA_SAFETY: {
    trigger: /私隱|個人資料|privacy|data|安全/i,
    evidenceRef: "PDPO_notice; carrier_privacy_summary; retention_schedule",
    agentScript:
      "Here is exactly what leaves this room and where it goes, who at the carrier can open it, " +
      "how long it is kept and how to ask for a copy or a correction. I will give you the sheet " +
      "rather than tell you it is fine.",
    neverSay: ["it is all encrypted so it is fine", "nobody sees it"],
  },
  BENEFICIARY_UNCERTAIN: {
    trigger: /受益人|beneficiary|如果我死|named/i,
    evidenceRef: "nomination_guidance (Lesson 10)",
    agentScript:
      "Let's settle that now rather than at claim time. Who should receive it, who looks after it " +
      "if they are minors, and do they know? If we skip this the family can end up fighting while " +
      "they are grieving.",
    neverSay: ["it goes to the family automatically", "the company sorts it out"],
  },
};

Key Takeaways

  1. Multi-carrier is a risk-control decision before it is a pricing decision. Depth on a protection shelf, depth on a savings shelf, one accident/ILAS shelf, and two or three narrow opportunistic agreements is the working configuration. An agreement you have never submitted is a liability.
  2. Raw quotes are not comparable, and showing them raw is a misrepresentation risk. The canonical comparable is premium per HK$1,000,000 of normalised guaranteed sum insured, plus a stated-horizon premium total, with non-equivalent dimensions carried separately and explicitly flagged.
  3. Guaranteed and non-guaranteed benefits never share a table. Illustrative bonus figures go in a separate appendix, with the assumed return stated, the words "not a projection" written out, and the guarantee that they may be reduced or not paid at all. R-GT-01 and R-GT-02 exist for exactly this.
  4. "Not equivalent" is a finding, not a failure. Annual-limit versus annual-deductible medical, different hospital-cash triggers, different room caps, different occupation class numbering, different survival periods — mark them NOT_EQUIVALENT, exclude the dimension from scoring, and let the client's financial profile make the decision.
  5. The normalisation trace is the deliverable. Every adjustment carries a rule id, a reason and an authorising underwriter id. When a client asks why two numbers differ, the answer is a rule lookup, never a recollection.
  6. A bid sheet must attempt at least three carriers and must show the declines. R-CMP-01 blocks generation below three; the refusal list is often the most persuasive page in the document because it proves the work was done.
  7. State the price exactly. First-year premium, the full subsequent-year schedule (not "about HK$700 thereafter"), and the horizon total with the reason for the horizon written on the sheet.
  8. Switching cost is dominated by surrender value shortfall, and the agent should compute the break-even live. If the horizon is shorter than the break-even, the correct advice is "do not switch", and giving that advice is worth more than the sale it loses.
  9. Replacement cases are procedural, not persuasive. Check NS status before presenting, obtain the incumbent agent's written consent, register the referral, agree the commission share, and disclose the replacement. No consent, no switch — regardless of how good the reason sounds.

課堂測驗 · 8 題

Question 1 of 8Answered 0 / 8
Question 1 of 8

兩份保單:A 公司 HK$500,000 保額每年 HK$3,600;B 公司 HK$500,000 保額每年 HK$3,200。以下邊個做法先可以將佢哋擺埋一齊比?

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
8 unanswered