{"id":620,"date":"2025-10-04T12:38:40","date_gmt":"2025-10-04T12:38:40","guid":{"rendered":"https:\/\/tomchan.hk\/?page_id=620"},"modified":"2025-10-04T12:51:31","modified_gmt":"2025-10-04T12:51:31","slug":"flu","status":"publish","type":"page","link":"https:\/\/tomchan.hk\/?page_id=620","title":{"rendered":"Influenza Situation in Hong Kong"},"content":{"rendered":"\n<div id=\"hkflu-embed\" class=\"hkflu\">\n  <style>\n    \/* ---- Namespaced, light theme ---- *\/\n    #hkflu-embed.hkflu {\n      --bg: #f9fafb; --card: #ffffff; --border: #dce1e7;\n      --muted: #4b5563; --text: #1a1d21;\n      --accent: #0078d4; --accent2: #00a884;\n      font-family: Inter, system-ui, Segoe UI, Roboto, Helvetica, Arial, sans-serif;\n      color: var(--text);\n      background: transparent;\n    }\n    #hkflu-embed * { box-sizing: border-box; }\n    #hkflu-embed .wrap { max-width: 1200px; margin: 0 auto; padding: 18px; background: transparent; }\n    #hkflu-embed .header {\n      position: sticky; top: 0; z-index: 10;\n      background: rgba(255,255,255,0.9);\n      border-bottom: 1px solid var(--border);\n      backdrop-filter: saturate(160%) blur(6px);\n    }\n    #hkflu-embed h1 { font-size: 22px; margin: 0; letter-spacing: .3px; }\n    #hkflu-embed .sub { color: var(--muted); font-size: 13px; margin-top: 6px; }\n    #hkflu-embed .controls { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }\n    #hkflu-embed .control {\n      background: var(--card);\n      border: 1px solid var(--border);\n      padding: 10px 12px; border-radius: 12px;\n      display: flex; align-items: center; gap: 8px;\n    }\n    #hkflu-embed select, #hkflu-embed input {\n      background: #f0f2f5; border: 1px solid var(--border);\n      color: var(--text); padding: 8px 10px; border-radius: 10px;\n    }\n    #hkflu-embed button {\n      background: linear-gradient(90deg, var(--accent), var(--accent2));\n      color: #fff; font-weight: 700; border: none; padding: 10px 14px;\n      border-radius: 12px; cursor: pointer;\n    }\n    #hkflu-embed .grid { display: grid; grid-template-columns: repeat(12,1fr); gap: 16px; margin: 16px 0; }\n    #hkflu-embed .card {\n      grid-column: span 12; background: var(--card); border: 1px solid var(--border);\n      border-radius: 16px; padding: 16px; box-shadow: 0 4px 14px rgba(0,0,0,0.05);\n    }\n    #hkflu-embed .kpis { display: grid; grid-template-columns: repeat(4,1fr); gap: 12px; }\n    #hkflu-embed .kpi {\n      background: #f4f6f8; border: 1px solid var(--border); border-radius: 14px; padding: 14px;\n    }\n    #hkflu-embed .kpi h3 { margin: 0 0 6px 0; font-size: 12px; color: var(--muted); font-weight: 600; }\n    #hkflu-embed .kpi .val { font-size: 24px; font-weight: 800; }\n    #hkflu-embed .kpi .trend { font-size: 12px; color: var(--muted); }\n    #hkflu-embed .split { display: grid; grid-template-columns: repeat(12,1fr); gap: 16px; }\n    #hkflu-embed .span6 { grid-column: span 12; }\n    @media (min-width:900px) { #hkflu-embed .span6 { grid-column: span 6; } }\n    #hkflu-embed .chartbox { width:100%; height:320px; }\n    @media (max-width:899px) { #hkflu-embed .chartbox { height:240px; } }\n    #hkflu-embed canvas { width: 100% !important; height: 100% !important; }\n    #hkflu-embed footer { color: var(--muted); font-size: 12px; margin: 20px 0; }\n    #hkflu-embed .badge {\n      font-size: 11px; padding: 3px 8px; border-radius: 999px;\n      background: #f4f6f8; border: 1px solid var(--border); color: var(--muted);\n    }\n    #hkflu-embed .inline { display: inline-flex; gap: 10px; align-items: center; }\n    #hkflu-embed .error { color: #b80000; }\n    #hkflu-embed .abbr { margin-top: 12px; font-size: 11px; color: var(--muted); }\n  <\/style>\n\n  <div class=\"header\">\n    <div class=\"wrap\">\n      <div class=\"inline\">\n        <h1>Influenza Situation in Hong Kong<\/h1>\n        <span class=\"badge\" id=\"hk_status\">Loading live data\u2026<\/span>\n      <\/div>\n      <div class=\"sub\">Real-time dashboard using CHP \u201cFlu Express\u201d CSV. Displays ILI, lab detections, outbreaks, admissions, severe cases.<\/div>\n      <div class=\"controls\">\n        <div class=\"control\">\n          <label for=\"hk_daterange\">Date range:<\/label>\n          <select id=\"hk_daterange\">\n            <option value=\"4\">Last 4 weeks<\/option>\n            <option value=\"8\">Last 8 weeks<\/option>\n            <option value=\"12\">Last 12 weeks<\/option>\n            <option value=\"26\">Last 26 weeks<\/option>\n            <option value=\"52\" selected>Last 52 weeks<\/option>\n            <option value=\"104\">Last 2 years<\/option>\n            <option value=\"260\">Last 5 years<\/option>\n            <option value=\"all\">All<\/option>\n          <\/select>\n        <\/div>\n        <button id=\"hk_reload\" type=\"button\">Reload<\/button>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <main class=\"wrap\">\n    <section class=\"grid\">\n      <div class=\"card\">\n        <div class=\"kpis\" id=\"hk_kpis\"><\/div>\n      <\/div>\n\n      <div class=\"card split\">\n        <div class=\"span6\">\n          <h3>ILI rates (per 1,000 consultations): GOPC vs PMP<\/h3>\n          <div class=\"chartbox\"><canvas id=\"hk_iliChart\"><\/canvas><\/div>\n        <\/div>\n        <div class=\"span6\">\n          <h3>Lab detections by subtype<\/h3>\n          <div class=\"chartbox\"><canvas id=\"hk_labChart\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"card split\">\n        <div class=\"span6\">\n          <h3>Hospital admissions (per 10,000 people) by age group<\/h3>\n          <div class=\"chartbox\"><canvas id=\"hk_admChart\"><\/canvas><\/div>\n        <\/div>\n        <div class=\"span6\">\n          <h3>Outbreaks &amp; A&amp;E ILI<\/h3>\n          <div class=\"chartbox\"><canvas id=\"hk_outbreakChart\"><\/canvas><\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"card\">\n        <h3>Severe influenza cases by age<\/h3>\n        <div class=\"chartbox\"><canvas id=\"hk_severeChart\"><\/canvas><\/div>\n      <\/div>\n\n      <div class=\"card\">\n        <h3>Raw data preview<\/h3>\n        <div id=\"hk_table\" class=\"sub\">First 15 rows shown.<\/div>\n      <\/div>\n    <\/section>\n\n    <footer>\n      Source: CHP \u201cFlu Express\u201d CSV (updated weekly).<br>\n      <div id=\"hk_err\" class=\"error\"><\/div>\n      <div class=\"abbr\">\n        <strong>Abbreviations (per CHP spec):<\/strong><br>\n        ILI_GOPC = Influenza-like illness consultation rate (per 1,000 consultations) at sentinel general outpatient clinics<br>\n        ILI_PMP = Influenza-like illness consultation rate (per 1,000 consultations) at sentinel private medical practitioners<br>\n        A&amp;E = Accident &amp; Emergency department<br>\n        \u201cH1\u201d, \u201cH3\u201d, \u201cB\u201d = influenza virus subtypes A(H1), A(H3), B respectively<br>\n        A+B = sum of A and B detections<br>\n        Adm = hospital admissions<br>\n        SevereCase_x_y = laboratory-confirmed severe influenza cases in age group x\u2013y<br>\n        \u201cFrom \/ To\u201d = start \/ end dates of surveillance week (DD\/MM\/YYYY)\n      <\/div>\n    <\/footer>\n  <\/main>\n\n  <!-- External libs (ok in WP Custom HTML for admins) -->\n  <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/papaparse@5.4.1\/papaparse.min.js\"><\/script>\n  <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n  <script>\n  (function(){\n    \/\/ Scoped constants & state\n    const CSV_URL = 'https:\/\/www.chp.gov.hk\/files\/misc\/flux_data.csv';\n    const PROXY_URL = 'https:\/\/api.allorigins.win\/raw?url=';\n    const state = { rows: [], view: [] };\n\n    \/\/ Element helpers (scoped to this embed)\n    const root = document.getElementById('hkflu-embed');\n    const $ = sel => root.querySelector(sel);\n    const fmt = new Intl.NumberFormat('en-US');\n    const fmt1 = x => (x == null || isNaN(x)) ? '\u2014' : fmt.format(+Number(x).toFixed(1));\n\n    function toDate(dmy) {\n      const [d,m,y] = String(dmy||'').split('\/').map(Number);\n      if (!d || !m || !y) return null;\n      return new Date(Date.UTC(y, m-1, d));\n    }\n    function parseNum(v) {\n      if (v == null || v === '') return null;\n      const n = Number(v);\n      return isNaN(n) ? null : n;\n    }\n    function withinRange(rows, n) {\n      if (n === 'all') return rows;\n      const k = Number(n);\n      return rows.slice(-k);\n    }\n    function kpiCard(title, value, trend) {\n      return '<div class=\"kpi\"><h3>'+title+'<\/h3><div class=\"val\">'+value+'<\/div><div class=\"trend\">'+trend+'<\/div><\/div>';\n    }\n    function renderKPIs(view) {\n      if (!view.length) return;\n      const last = view[view.length-1];\n      const prev = view[view.length-2] || {};\n      const del = (a,b) => (a != null && b != null) ? (a - b) : null;\n      const sign = x => x == null ? '' : (x > 0 ? '\u25b2 +' + fmt1(x) : x < 0 ? '\u25bc ' + fmt1(x) : '\u2014');\n\n      const kpis = [\n        { t:'ILI \u2013 GOPC', v: last.ILI_GOPC, p: prev.ILI_GOPC },\n        { t:'ILI \u2013 PMP', v: last.ILI_PMP, p: prev.ILI_PMP },\n        { t:'Lab detections (A+B)', v: last.AandB, p: prev.AandB },\n        { t:'A&#038;E ILI per 1k', v: last.ILI_AED, p: prev.ILI_AED }\n      ].map(o => kpiCard(o.t, fmt1(o.v), sign(del(o.v, o.p))));\n      $('#hk_kpis').innerHTML = kpis.join('');\n    }\n\n    let charts = [];\n    function drawCharts(view) {\n      charts.forEach(c => c && c.destroy && c.destroy());\n      charts = [];\n      const labels = view.map(r => r.Year + '-W' + r.Week);\n      const series = k => view.map(r => r[k]);\n\n      charts.push(new Chart($('#hk_iliChart'), {\n        type: 'line',\n        data: {\n          labels,\n          datasets: [\n            { label: 'ILI_GOPC', data: series('ILI_GOPC'), borderColor: '#0078d4' },\n            { label: 'ILI_PMP', data: series('ILI_PMP'), borderColor: '#00a884' }\n          ]\n        },\n        options: { responsive: true, maintainAspectRatio: false }\n      }));\n\n      charts.push(new Chart($('#hk_labChart'), {\n        type: 'line',\n        data: {\n          labels,\n          datasets: [\n            { label: 'A(H1)', data: series('H1'), borderColor: '#0078d4' },\n            { label: 'A(H3)', data: series('H3'), borderColor: '#ff7b00' },\n            { label: 'B', data: series('B'), borderColor: '#00a884' },\n            { label: 'A+B', data: series('AandB'), borderColor: '#8c1aff' }\n          ]\n        },\n        options: { responsive: true, maintainAspectRatio: false }\n      }));\n\n      charts.push(new Chart($('#hk_admChart'), {\n        type: 'line',\n        data: {\n          labels,\n          datasets: [\n            { label: '0\u20135', data: series('Adm_0_5') },\n            { label: '65+', data: series('Adm_65_higher') }\n          ]\n        },\n        options: { responsive: true, maintainAspectRatio: false }\n      }));\n\n      charts.push(new Chart($('#hk_outbreakChart'), {\n        type: 'line',\n        data: {\n          labels,\n          datasets: [\n            { label: 'School outbreaks', data: series('ILI_School') },\n            { label: 'A&E ILI', data: series('ILI_AED') }\n          ]\n        },\n        options: { responsive: true, maintainAspectRatio: false }\n      }));\n\n      charts.push(new Chart($('#hk_severeChart'), {\n        type: 'bar',\n        data: {\n          labels,\n          datasets: [\n            { label: '0\u201317', data: series('SevereCase_0_17') },\n            { label: '65+', data: series('SevereCase_65_higher') }\n          ]\n        },\n        options: { responsive: true, maintainAspectRatio: false }\n      }));\n    }\n\n    function buildTable(view) {\n      const cols = ['Year','Week','From','To','ILI_GOPC','ILI_PMP','H1','H3','B','AandB'];\n      const head = '<tr>' + cols.map(c => '<th>'+c+'<\/th>').join('') + '<\/tr>';\n      const rows = view.slice(-15).map(r => '<tr>' + cols.map(c => '<td>' + (r[c] ?? '') + '<\/td>').join('') + '<\/tr>').join('');\n      $('#hk_table').innerHTML = '<div style=\"overflow:auto\"><table style=\"width:100%;border-collapse:collapse\" border=\"1\">'+head+rows+'<\/table><\/div>';\n    }\n\n    function sanitizeRow(row) {\n      const out = Object.assign({}, row);\n      const numKeys = [\n        'Year','Week','ILI_GOPC','ILI_PMP','H1','H3','B','AandB',\n        'ILI_School','ILI_NonSchool','Adm_0_5','Adm_65_higher',\n        'ILI_AED','SevereCase_0_17','SevereCase_65_higher'\n      ];\n      numKeys.forEach(k => out[k] = parseNum(out[k]));\n      if (out.From) out.FromDate = toDate(out.From);\n      if (out.To) out.ToDate = toDate(out.To);\n      return out;\n    }\n\n    function applyRange() {\n      const range = $('#hk_daterange').value;\n      state.view = withinRange(state.rows, range);\n      renderKPIs(state.view);\n      drawCharts(state.view);\n      buildTable(state.view);\n      const last = state.view[state.view.length-1];\n      $('#hk_status').textContent = last ? ('Updated to week ' + last.Week + ', ' + last.Year) : 'No data';\n    }\n\n    async function fetchCSV() {\n      try {\n        const res = await fetch(CSV_URL, { cache: 'no-store' });\n        if (!res.ok) throw new Error('Direct fetch failed');\n        return await res.text();\n      } catch (e) {\n        const res = await fetch(PROXY_URL + encodeURIComponent(CSV_URL), { cache: 'no-store' });\n        return await res.text();\n      }\n    }\n\n    async function load() {\n      $('#hk_status').textContent = 'Loading\u2026';\n      $('#hk_err').textContent = '';\n      try {\n        const txt = await fetchCSV();\n        const parsed = Papa.parse(txt, { header: true, skipEmptyLines: true });\n        const rows = parsed.data.map(sanitizeRow).filter(r => r.Year && r.Week);\n        rows.sort((a,b) => (a.Year === b.Year ? a.Week - b.Week : a.Year - b.Year));\n        state.rows = rows;\n        applyRange();\n      } catch (err) {\n        $('#hk_err').innerHTML = 'Could not fetch CSV data. ' + String(err && err.message ? err.message : err);\n        $('#hk_status').textContent = 'Load failed';\n      }\n    }\n\n    \/\/ Wire up controls\n    $('#hk_reload').addEventListener('click', load);\n    $('#hk_daterange').addEventListener('change', applyRange);\n\n    \/\/ Kickoff\n    load();\n  })();\n  <\/script>\n<\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Influenza Situation in Hong Kong Loading live data\u2026 Real-time dashboard using CHP \u201cFlu Express\u201d CSV. Displays ILI, lab detections, outbreaks, admissions, severe cases. Date range: Last 4 weeksLast 8 weeksLast 12 weeksLast 26 weeksLast 52 weeksLast 2 yearsLast 5 yearsAll Reload ILI rates (per 1,000 consultations): GOPC vs PMP Lab detections by subtype Hospital admissions&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"page-full.php","meta":{"footnotes":""},"tags":[],"class_list":["post-620","page","type-page","status-publish","article"],"_links":{"self":[{"href":"https:\/\/tomchan.hk\/index.php?rest_route=\/wp\/v2\/pages\/620","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tomchan.hk\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/tomchan.hk\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/tomchan.hk\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tomchan.hk\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=620"}],"version-history":[{"count":6,"href":"https:\/\/tomchan.hk\/index.php?rest_route=\/wp\/v2\/pages\/620\/revisions"}],"predecessor-version":[{"id":626,"href":"https:\/\/tomchan.hk\/index.php?rest_route=\/wp\/v2\/pages\/620\/revisions\/626"}],"wp:attachment":[{"href":"https:\/\/tomchan.hk\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=620"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tomchan.hk\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=620"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}