Jamorham
Nightscout용 xDrip+ 버전
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A bilingual (Thai/English) osteoporosis self-care PWA. Lifelong and
risk-tier driven rather than calendar driven: registration → risk
assessment → daily/weekly habits → monthly check-in → yearly nutrition
review, all built around a risk-tier + balance-level resolver.README 전문 보기 ↗A bilingual (Thai/English) osteoporosis self-care PWA. Lifelong and risk-tier driven rather than calendar driven: registration → risk assessment → daily/weekly habits → monthly check-in → yearly nutrition review, all built around a risk-tier + balance-level resolver.
index.html — the page: markup, styles and the Content-Security-Policy.
app-ui.js — the interface: eight tabs, forms, charts, sync, and state in
localStorage under OSTEO_STATE. No build step. It was inline in
index.html until 2026-09-24; it is a file so the CSP can be
script-src 'self' — never put a <script> block or an onclick="…"
back into the page, the browser will refuse to run it.
app-core.js — pure, dependency-free logic and content shared by the
browser and the Node test suite: the CONTENT TH/EN dictionary, the
tier and balance-level resolvers, medication schedules and care
information, the nutrition estimators, exercise/safety data, tracking
helpers and sync queueing.
tests/t*.js — regression suites, run with node tests/run_all.js.
tests/fake_backend.js runs the real Code.gs against a stand-in Sheet.
apps-script/Code.gs — Google Apps Script backend (doPost): binds each
phone to its HN, checks every record, and appends to the Devices /
Registrations / CheckIns / Falls / Adherence / Nutrition / Bmd /
FractureRisk / Audit tabs. It never overwrites a row.
docs/SECURITY.md — identity, audit trail, limits, hosting, moving and
keeping patients' local data, and the clinic decisions still open.
tools/verify-backend.js — checks the deployed script after a redeploy.
vercel.json — security headers for the app's own origin
(osteoporosis-care.vercel.app).
manifest.webmanifest, sw.js, icon.svg — installability (Add to
Home Screen) and offline app-shell caching.
media/all-media-prompts.md — the prompt behind every media file, kept so
any of them can be regenerated in the same style.
media/osteo-video-clip-list.md — filming checklist, and how to add a
clip to the Move tab's media manifest.
| Tab | What it holds |
|---|---|
| หน้าหลัก / Home | Bone condition and fall risk with what each means and what to do, the 10-year fracture risk card, a countdown ring to the next dose, due prompts and a safety tip |
| ยา / Medicine | The patient's own drug, picked from icon cards: next dose, how long they have been on it, doses received, course position for romosozumab, how to take it, missed doses, side effects, dental note, do-not-stop warning, and past medicines with their durations |
| อาหาร / Food | Yearly nutrition review and its calcium, vitamin D and protein recommendations, plus bulleted guides to where calcium and vitamin D come from and what to cut down |
| ออกกำลัง / Move | Exercises scoped to the balance level, each with written how-to steps; information only, no logging |
| กันล้ม / Safety | 20-item home safety check with per-item icons; done items turn blue, outstanding ones stay highlighted |
| ติดตาม / Track | Spine and hip T-scores, height, chair-stand, TUG, falls and adherence — each with a progression chart, a change-since-first chip and a table view, with past scan results listed at the bottom |
| ความรู้ / Learn | Explainer panels (what osteoporosis is, the T-score, who should be scanned, why medication matters, what to expect) and the reading list |
| ฉุกเฉิน / Urgent | Red flags as severity cards, each with the action to take, and one-tap hospital/1669 calling |
The header carries the patient's HN. Every page ends with a footer naming the supervising doctor and an "erase and start over" link that clears this phone's data behind a confirmation. When records are stuck in the queue, a quiet line appears there with a retry button.
Two separate judgements, because they are managed differently and mixing them misleads the patient:
The card gathers everything the official FRAX calculator asks for — age,
sex, weight, height and BMI, a previous fracture from a small fall, a
parent who broke a hip, current smoking, regular steroid tablets,
rheumatoid arthritis, another illness that thins bone, three or more
alcoholic drinks a day, and the femoral neck T-score when a DXA has been
recorded. Anything the assessment already asked is carried over and shown
read-only rather than asked twice. The card links straight to the Thai
FRAX tool (frax.shef.ac.uk, country 57), and the two percentages that
come back are recorded, dated, and synced to the FractureRisk sheet.
The card then works in one of two modes, and never mixes them:
FRAX itself is not and cannot be computed here. Its coefficients are licensed and unpublished, so no code in this repo reproduces them. The estimate is a deliberately simple stand-in: a published population baseline for the patient's age and sex, multiplied by a relative risk for each factor present.
FRAX_ESTIMATE_SOURCE.Applied naively, that model reads two to three times above FRAX — it put a 74-year-old with a previous fracture, a parental hip fracture and a femoral neck T-score of −2.6 at 55%, against roughly 20–25% from the real tool. Three corrections bring it back into range, all following from the fact that the baseline is a population average rather than a risk-free person:
rr / (1 + prevalence × (rr − 1))). A published RR compares people
who have the factor against people who do not; applying it whole to an
average that already contains both counts the risk twice.t13_frax_medication checks the result against published FRAX ranges for
three reference profiles, so the estimate cannot silently drift off by
multiples again.
FRAX_BASELINE_RISK in
app-core.js. Those age/sex baselines, and the meanTScore column
beside them, are approximate figures for an Asian population.
Replacing them with Thai epidemiology is the single change that would
most improve the estimate, and the card says on screen that a doctor
should check them first. The prevalence figures on FRAX_FACTORS
are the second such guess and deserve the same look.So the intended flow stays: fill in the details → open the FRAX site → enter them → record the two percentages back in the app, which then replaces the estimate with the real thing.
The app blocks the everyday ways content leaves it: text selection, right-click, the iOS long-press "Save Image" menu, dragging pictures out, Ctrl+C, the video player's Download and picture-in-picture options, and printing to paper or PDF. Form fields stay fully selectable so a patient can still fix a mistyped HN.
This is friction, not protection. Be clear-eyed about it before relying on it:
FLAG_SECURE on Android; iOS can detect one after the fact but not prevent
it). If blocking screen capture is a real requirement, it means shipping a
native app, not a web app.app-core.js, which
the browser must download to run. Anyone can open developer tools and read it.media/exercises/heel_raises.jpg
fetched directly returns the picture, whatever the app does.So the deterrents are worth having — they stop a patient casually forwarding a medication instruction into a chat app, which is the realistic risk — but they are not a licensing or confidentiality control, and should not be described to anyone as one.
node tests/run_all.js
Any static server, e.g. python3 -m http.server 8080, then open
http://localhost:8080/index.html. Service workers and the install
prompt need a real HTTP(S) origin, not file://.
apps-script/Code.gs is self-contained — paste the whole file in,
replacing anything already there.
Open your Google Sheet → Extensions → Apps Script.
Select everything in the editor, delete it, paste in Code.gs, save.
In Project Settings, set the time zone to (GMT+07:00) Bangkok.
Deploy → Manage deployments → pencil icon → Version: New version →
Deploy. This step is the one that matters: saving alone does not
change what the live /exec URL runs, so an older copy of the script
keeps answering.
Confirm it took: open the /exec URL in a browser. It answers
{"ok":true,"service":"osteoporosis-care","protocol":3,"tokenConfigured":true,"sheets":[...],"version":"2026-10-07"}
Check the deployed backend, from any computer with Node 18 or later:
node tools/verify-backend.js https://script.google.com/macros/s/…/exec
Every probe must pass. They send requests the script must refuse, so they
write nothing (and they stop at once if the version is wrong, before an
older script could accept anything). Then run it once more with
--write: only that round trip — two made-up ZZTEST- patients —
proves the script accepts records, and it prints the rows to delete
afterwards. Until both pass, the fixes are not live.
Access must be Anyone, not "Anyone with a Google account" — the
latter answers with a login page the app cannot follow. SHARED_TOKEN
must be identical in Code.gs and app-ui.js.
When a version changes a tab's columns, the first request after deploying moves the old tab aside as " (before )" — intact — and starts a fresh one. 2026-09-25.2 did this for the tabs from before it (phones register again on their own, and one registered before it first asks its patient to confirm consent once); later versions change no columns.
The tabs are append-only. A change to a day's check-in, nutrition, BMD or
FRAX record is a new row with the next version, naming the row it
supersedes; the highest version is current. A second fall on one day is a
second event; an exact repeat is skipped. The Audit tab logs every accepted
change, conflict and refusal from a registered phone.
The data tabs above are the record, written for the script: English column
names, codes such as severeOsteoporosis, every version, and the phone each
row came from. For reading, the script builds two more tabs, first in the
file:
Everything is in Thai as the app shows it to the patient, with headers in Thai and English and dates in the Buddhist era (25 ก.ย. 2569). "Needs attention" uses only the app's own cut-offs: an injured fall or two falls in 12 months, height down 2 cm or more since the first measurement, TUG of 12 seconds or more, chair stands below the norm for age and sex, missed doses at the last check-in, and a phone waiting in Devices.
The two tabs are rebuilt each time the spreadsheet is opened, and from the
menu ดูแลกระดูกพรุน → อัปเดตผลผู้ป่วย while it is open. They show only
each record's current version and nothing from a revoked phone. They hold
nothing of their own: an edit made there is gone at the next rebuild, so
correct a result in the app, not in the sheet. Rebuilding never changes the
data tabs. Rows in the "(before …)" tabs are not included.
The short version; docs/SECURITY.md has the detail.
SHARED_TOKEN is not a password. It ships inside the app and the code
is public. What protects a patient's rows is the phone's own key: made at
registration, stored only as a hash in the Devices tab, and required for
every record. Knowing someone's HN is not enough to write to their rows,
and the script has no way to read rows back out.pending. Each phone
keeps its own records, so no phone can change another's. This proves
knowledge of those details, not identity — docs/SECURITY.md has the
trade-off.0012345 is not turned into a number.
Submissions are rate-limited per phone and per HN.wrong-hn), registers the right one and sends its whole history
again. The rows already sent under the wrong HN stay in the data tabs but
are left out of that HN's summary. Staff can do the same by setting a
phone's status to wrong-hn; its app then asks the patient to change HN.
The HN's real owner, held back as pending, is let in as soon as no other
phone holds the HN.Active, wrong hn, Wrong_HN and
the like as active and wrong-hn.revoked. A pending phone (details did not match) can be let
in by setting it to active.localStorage;
"ล้างข้อมูลและเริ่มใหม่" in the footer erases it on a shared phone.Records that are not accepted stay queued and a line appears in the page footer with a retry button — nothing is silently dropped. The likely causes, in order:
tools/verify-backend.js
says so in its first line.active in Devices to let it in.invalid token with HTTP 200.This sandbox blocks script.google.com, so the live endpoint cannot be
called from here. The client sync path is verified end to end in a browser
against the real Code.gs running on a stand-in Sheet.
Static hosting: index.html, app-core.js, app-ui.js,
manifest.webmanifest, sw.js, icon* and media/. Every push to main
deploys to both GitHub Pages and Vercel.
Use https://osteoporosis-care.vercel.app as the address patients get.
It is an origin of its own, and vercel.json sends the anti-framing and
other security headers. GitHub Pages shares one origin, and so one
localStorage, with every other Pages site on the account, and it cannot
send headers; there the app only protects itself by refusing to draw inside
a frame. docs/SECURITY.md has the plan for moving existing patients' data
across, and for how long it is kept.
https://osteoporosis-care.vercel.app/start.html. It
has a QR code, the address and four steps in Thai and English, and prints on
one A4 page. app-qr.png is the same QR code as a picture to send in LINE
(the page offers it for saving). The QR code opens
https://osteoporosis-care.vercel.app/; it was checked by decoding it.openExternalBrowser=1.pending in
the Devices tab for staff.media/all-media-prompts.md has the prompt behind each
file.app-core.js → DOCTOR:
รศ.นพ.สรวุฒิ ธรรมยงค์กิจ / Assoc. Prof. Sorawut Thamyongkit, M.D.,
matching easybone.org. The build plan listed
the ธรรมยงค์กิจ vs. ธำรงค์กิจ spelling as an open decision — it is now
confirmed as ธรรมยงค์กิจ, so reuse that across the other three apps.FRAX_BASELINE_RISK). Review those figures, and decide whether you
want the estimate shown to patients at all or only the official
result — it is one else if in renderFraxCard(). Note that for
high-risk profiles (for example 75, T-score −4, two risk factors) the
estimate shows a hip figure above the major one, which a real
FRAX result never does; the multiplier caps in estimateFractureRisk
are the place to look.The app is read by people in their seventies and eighties, often on a small screen, so the content follows a few rules the test suite enforces:
LISTS in app-core.js, rendered as bullets.t1_i18n_parity catches any string where the
two are identical, which usually means one was never really written.